Django前后端分离4——业务代码

一.settings.py

INSTALLED_APPS = [
    ......
    'corsheaders',  # cors:解决跨域问题
    'user',
    'btoken',  # 用于登陆时生成token,token好像是关键字,使用btoken
    'topic',
    'message',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',    # 新增的cors中间件
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',  # 取消csrf中间件
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# 设置成True后,存入数据库的所有时间都会转为utc时间(方便查询时时区转换)
# 应用不跨时区时直接设置为False即可
USE_TZ = False

# 前后端分离CORS相关配置

# 是否允许所有域的请求
CORS_ORIGIN_ALLOW_ALL = True

# 允许请求的域,CORS_ORIGIN_ALLOW_ALL设置为True后即所有域均能访问,此项不必配置
# CORS_ORIGIN_WHITELIST = ['http://example.com']

CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    )

CORS_ALLOW_HEADERS = (
    'accept-encoding',
    'authorization',  # 这个里面放登陆认证的token
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    )

# CORS_PREFLIGHT_MAX_AGE  默认 86400s
# CORS_EXPOSE_HEADERS = []    # 扩展的请求头,默认空
# CORS_ALLOW_CREDENTIALS = False  # 是否接收cookie,布尔值, 默认False


APPEND_SLASH = False    # 是否自动在url末尾添加/

# 前端请求资源的路径
MEDIA_URL = '/media/'
# 后端服务器图片的存储目录
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

# 以下添加在主路由文件中
from django.conf.urls.static import static
from django.conf import settings
# 添加图片的路由映射
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

二.登陆生成token

import hashlib
import json, jwt, time
from user.models import UserProfile

def make_token(username, expire=3600*24):
    key = 'abcdef1234'
    now_t = time.time()
    payload = {'username': username, 'exp': int(now_t)+expire}
    # pip install pyjwt
    return jwt.encode(payload, key, algorithm='HS256')


# POST /v1/token    登陆
def btoken(request):
    if not request.method == 'POST':
        result = {'code': 101, 'error': '请使用POST请求'}
        return JsonResponse(result)

    # 获取提交的数据
    json_str = request.body
    if not json_str:
        result = {'code': 102, 'error': '请上传数据'}
        return JsonResponse(result)

    json_obj = json.loads(json_str)
    username = json_obj.get('username')
    password = json_obj.get('password')
    if not username:
        result = {'code': 103, 'error': '请上传用户名'}
        return JsonResponse(result)
    if not password:
        result = {'code': 104, 'error': '请上传密码'}
        return JsonResponse(result)

    users = UserProfile.objects.filter(username=username)
    if not users:
        result = {'code': 105, 'error': '用户名或密码错误'}
        return JsonResponse(result)

    # hash password
    p_m = hashlib.sha1()
    p_m.update(password.encode())
    if p_m.hexdigest() != users[0].password:
        result = {'code': 106, 'error': '用户名或密码错误'}
        return JsonResponse(result)

    # 生成token
    token = make_token(username)
    # 生成的token是bytes格式,此处转为字符串存入字典中返回
    result = {'code': 200, 'username': username, 'data': {'token': token.decode()}}
    return JsonResponse(result)

三.装饰器函数验证token

tools/loging_decorator.py
装饰器作用:根据参数检查对应请求的token,验证成功则将当前user赋值给request.user,然后执行视图函数,验证失败则直接返回前端错误码

import jwt
from user.models import UserProfile

KEY = 'abcdef1234'    # 定义加密的密钥

# 最外层的函数是为了给装饰器传参(如果传入‘POST’,则POST请求时检查token,其余请求类型直接执行视图函数)
def loging_check(*methods):     # *methods:传递给装饰器的参数
    def _loging_check(func):
        def wrapper(request, *args, **kwargs):
            # token 放在 request header authorization
            token = request.META.get('HTTP_AUTHORIZATION')
            if not methods:
                # 如果没传methods参数,则直接返回视图
                return func(request, *args, **kwargs)
            # 如果当前请求的方法不在methods内,则直接返回视图
            if request.method not in methods:
                return func(request, *args, **kwargs)
                
			# 走到这里表明此次请求需要做token校验
            # 前端没有token时,传过来的可能是空或者'null',此处要根据实际清空进行判断
            if not token or token == 'null':
                result = {'code': 107, 'error': '请上传token'}
                return JsonResponse(result)
            # 校验token,pyjwt注意 异常检测
            try:
                res = jwt.decode(token, KEY, algorithms='HS256')
            except jwt.ExpiredSignatureError:
                # token过期
                result = {'code': 108, 'error': '请登录'}
                return JsonResponse(result)
            except Exception as e:
                # token错误
                result = {'code': 108, 'error': '请登录'}
                return JsonResponse(result)

            # 校验成功则根据用户名取出用户(前端传递的用户名有错误的风险,此处以token中的为准)
            username = res['username']
            user = UserProfile.objects.get(username=username)
            request.user = user    # 取出的用户直接赋给request.user
            return func(request, *args, **kwargs)
        return wrapper
    return _loging_check


def get_user_by_request(request):
    """
    通过request获取用户
    :param request:
    :return: user对象 或者 None
    """
    token = request.META.get('HTTP_AUTHORIZATION')
    if not token or token == 'null':
        return None
    try:
        res = jwt.decode(token, KEY, algorithms='HS256')
    except Exception as e:
        print('=== get_user_by_request ==jwt decode error %s' % e)
        return None
    # 获取token中的用户名
    username = res['username']

    user = UserProfile.objects.get(username=username)
    return user

四.处理前端请求

获取博客数据

import json
from message.models import Message
from tools.loging_decorator import loging_check, get_user_by_request
from topic.models import Topic
from user.models import UserProfile

@loging_check('POST', 'DELETE')
def topics(request, username=None):
    # /v1/topics/<username>
    if request.method == 'POST':
        # 发表博客,必须为登陆状态
        # 验证token后会将user对象存入request
        author = request.user
        json_str = request.body
        if not json_str:
            result = {'code': 302, 'error': '请上传数据'}
            return JsonResponse(result)
        json_obj = json.loads(json_str)

        title = json_obj.get('title')
        # 带html标签样式的内容
        content = json_obj.get('content')
        # 纯文本内容
        content_text = json_obj.get('content_text')
        # 在纯文本内容中截取文章简介>>>>>>>>>>>>>>>加一个简介的字段,而不用显示的时候进行中文截取
        introduce = content_text[:30]
        # 文章权限  public or private
        limit = json_obj.get('limit')
        if limit not in ['public', 'private']:
            # 判断权限是否合法
            result = {'code': 303, 'error': '权限类型错误'}
            return JsonResponse(result)
        # 文章种类  tec - 技术类   no-tec - 非技术类
        category = json_obj.get('category')
        if category not in ['tec', 'no-tec']:
            result = {'code': 303, 'error': '分类错误'}
            return JsonResponse(result)
        now = timezone.now()

        Topic.objects.create(title=title, content=content, limit=limit, category=category,author=author, created_time=now, modified_time=now,introduce=introduce)
        result = {'code': 200, 'username': author.username}
        return JsonResponse(result)

    # 获取用户博客列表 或 具体博客的内容( ?t_id=xx)
    # /v1/topics/kzzf
    elif request.method == 'GET':
        # 获取当前访问的博客的博主
        authors = UserProfile.objects.filter(username=username)
        if not authors:
            result = {'code': 305, 'error': '博主不存在'}
        author = authors[0]

        # 获取当前访问者 visitor,已登陆或未登录
        visitor = get_user_by_request(request)
        visitor_username = None
        # 比对 author和visitor的username,判断访问者是否为博主
        # 是博主则返回所有博客,否则返回权限为public的博客
        if visitor:
            # 访问者已登陆,获取用户名
            visitor_username = visitor.username

        # /v1/topics/kzzf?t_id=1
        # 如果有t_id,则当前请求是获取博主指定id的博客
        t_id = request.GET.get('t_id')
        if t_id:
            # 访问者是否为博主的标记
            is_self = False
            if visitor_username == username:
                is_self = True
                try:
                    author_topic = Topic.objects.get(id=t_id)
                except Exception as e:
                    result = {'code': 309, 'error': '博客不存在'}
                    return JsonResponse(result)
            else:
                try:
                    author_topic = Topic.objects.get(id=t_id, limit='public')
                except Exception as e:
                    result = {'code': 309, 'error': '博客不存在'}
                    return JsonResponse(result)
            # 根据需求文档,将查询结果集进行序列化
            res = make_topic_res(author, author_topic, is_self)
            return JsonResponse(res)
        else:
            # 没有t_id时 即为查询有所博客
            # /v1/topics/kzzf?category=tec|no-tec  关键字查询
            category = request.GET.get('category')
            if category in ['tec', 'no-tec']:
                # 判断当前的访问者是否为博主
                if visitor_username == username:
                    # 外键关联的是主键,user表中设置username为主键了,表中没有id字段
                    author_topics = Topic.objects.filter(author=username, category=category)
                else:
                    # 其他访问者只能访问pubilc类型的博客
                    author_topics = Topic.objects.filter(author=username, limit='public', category=category)
            else:
                # 判断当前的访问者是否为博主
                if visitor_username == username:
                    # 外键关联可以使用对象,也可使用主键字段进行查询;filter的字段可以是author,也可以是author_id
                    author_topics = Topic.objects.filter(author_id=username)
                else:
                    # 其他访问者在访问当前博客
                    author_topics = Topic.objects.filter(author_id=username, limit='public')

            res = make_topics_res(author, author_topics)
            return JsonResponse(res)

    # 删除博主的博客文章
    # /v1/topics/kzzf?topic_id=1
    elif request.method == 'DELETE':
        # 装饰器验证ok后会将登陆用户赋值给request.user
        author = request.user
        # 删除时为保证万无一失,比对url中的username和token中的一致时方可执行删除
        if author.username != username:
            result = {'code': 306, 'error': '不能执行'}
            return JsonResponse(result)

        # DELETE请求也可通过GET取参数》》》》》》》》》
        topic_id = request.GET.get('topic_id')
        if not topic_id:
            result = {'code': 307, 'error': '你不能这么做'}
            return JsonResponse(result)

        try:
            topic = Topic.objects.get(id=topic_id)
        except Exception as e:
            print('博客删除错误:%s' % e)
            result = {'code': 308, 'error': '博客不存在'}
            return JsonResponse(result)

        topic.delete()
        return JsonResponse({'code': 200})


def make_topics_res(author, author_topics):
    res = {'code': 200, 'data': {}}
    topics_res = []
    for topic in author_topics:
        d = dict()
        d['id'] = topic.id
        d['title'] = topic.title
        d['category'] = topic.category
        d['created_time'] = topic.created_time.strftime("%Y-%m-%d %H:%M:%S")
        d['introduce'] = topic.introduce
        d['author'] = author.nickname
        topics_res.append(d)
    res['data']['topics'] = topics_res
    res['data']['nickname'] = author.nickname
    return res


def make_topic_res(author, author_topic, is_self):
    # 生成博客详情页的返回值
    if is_self:
        # next 下一篇博客:大于当前博客id的下一个  博主的博客>>>>>>>>>>>
        next_topic = Topic.objects.filter(id__gt=author_topic.id, author=author.username).first()
        # 取出上一篇博客,取出的结果默认是按id从小大大排的,所以这里用last方法取最后一个
        last_topic = Topic.objects.filter(id__lt=author_topic.id, author=author).last()
    else:
        next_topic = Topic.objects.filter(
            id__gt=author_topic.id,
            limit='public',
            author=author.username).first()
        last_topic = Topic.objects.filter(
            id__gt=author_topic.id,
            limit='public',
            author=author.username).last()

    # 处理返回前端的数据
    if next_topic:
        next_id = next_topic.id
        next_title = next_topic.title
    else:
        next_id = None
        next_title = None
    if last_topic:
        last_id = last_topic.id
        last_title = last_topic.title
    else:
        last_id = None
        last_title = None

    result = {'code': 200, 'data': {}}
    result['data']['nickname'] = author.nickname
    result['data']['title'] = author_topic.title
    result['data']['category'] = author_topic.category
    result['data']['created_time'] = author_topic.created_time.strftime('%Y-%m-%d')
    result['data']['content'] = author_topic.content
    result['data']['introduce'] = author_topic.introduce
    result['data']['author'] = author.nickname
    result['data']['next_id'] = next_id
    result['data']['next_title'] = next_title
    result['data']['last_id'] = last_id
    result['data']['last_title'] = last_title

    # 生成message留言信息
    # 倒序拿出所有的留言对象
    all_messages = Message.objects.filter(topic=author_topic).order_by('-id')
    msg_list = []  # 里面放字典,存入留言以及对应的回复
    level_msg = {}    # key:留言id;value:回复对象[]
    for msg in all_messages:
        if msg.parent_message == 0:
            # 留言
            msg_list.append({'id': msg.id, 'content': msg.content,
                             'publisher': msg.publisher.nickname,
                             # ImageField字段拿到的是一个对象,__str__方法可返回字符串路径,方便后续转json
                             'publisher_avatar': str(msg.publisher.avatar),
                             'created_time': msg.created_time.strftime("%Y-%m-%d"),
                             'reply': []})
        else:
            # 回复
            # setdefault:设置key的默认值,key不存在时生效>>>>>>>>>
            level_msg.setdefault(msg.parent_message, [])
            level_msg[msg.parent_message].append({'msg_id': msg.id,
                                                  'publisher': msg.publisher.nickname,
                                                  'publisher_avatar': str(msg.publisher.avatar),
                                                  'content': msg.content,
                                                  'created_time': msg.created_time.strftime('%Y-%m-%d'),
                                                  })
        # 关联留言和回复,msg_list为最终返回的留言数据:{留言:回复列表}
        for m in msg_list:
            if m['id'] in level_msg:
                m['reply'] = level_msg[m['id']]

    result['data']['messages'] = msg_list  # 暂未处理留言问题
    result['data']['messages_count'] = len(all_messages)
    return result
发布了106 篇原创文章 · 获赞 8 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43667990/article/details/100998354