2

前言

网络上关于Django的内容其实已经很多了,包括自己也是从网络上的内容一点一从零开始学习Django的,但是这部分内容都过于零散,可以说大部分都是在讲怎么从django-admin startproject到创建Model,再到写一个TODO List或者一个Blog之类的内容,对于已经了解Django的人来说,并没有太好渠道去了解一些别人的实践,在开发自己的项目时,遇到的需求很难去了解其他人会怎么设计,当然这也是社区存在的通病,大家更多在讨论语言层面的问题.
我希望能把自己平时在工作中总结出来的tips分享出来,也许在某些地方能帮助到别人,也算是自己梳理经验,自我提高的一个过程.
既然标题是如何开发,那我就按照一个项目的生命周期来整理.

需求分析

讨论需求的事情就不谈了.

一个项目需求给到之后,第一步肯定不是django-admin startproject,而是选型,换句话说,我们得先决定要不要使用django.它适合什么样的项目,不适合什么样的项目

避免websocket
Django出现的太早了,它甚至和ajax的年纪差没几年,所以对很多现代化的需求和功能支持都不太好,比如websocket,比如异步任务.
如果需求中有一些功能会比较依赖websocket,那么就建议不要选择Django,并不是说完全不行,但是Django Channelasgi目前来说都算不上很成熟的解决方案,不得不承认在这方面django的确是没有优势.

避免对性能敏感的需求
这是老生常谈了,我说一下我自己遇到的性能问题都出现在哪里(不谈恶俗的abtest).

服务端RSA加解密

这是我偶然发现的,python进行rsa运算简直慢出天际.但是这个问题并不会非常明显的爆发,毕竟只算一次的话,最多也就是零点几秒甚至更少的时间,当访问量不大的时候并没有那么明显,很难发现.
但是有一天我使用多线程进行rsa加密的时候,发现我无论使用多少个线程,加密的速度都是一样的慢,才算是发现这个问题.它很容易成为服务器性能的瓶颈.
当然这个问题还是可以解决的.用Golang写了一个加解密的模块,编译成.so给python调用,这个问题就算解决了.

类似上传Excel导入数据

这是一个很复杂的情况.大文件传过来之后,究竟选择什么策略进行处理不能一概而论.比如前期我选择直接读出数据写入到数据库中,后来给我来了一个10W行的excel,直接GG了,python的循环操作是非常慢的,1W行的文件已经需要加载很久了,这就导致服务端处理Excel并实时对数据进行格式检查和去重并立即返回结果是很不合理的需求,当然不是说别的语言能做的完美,而是想说因为django不能直接处理异步任务,完成类似需求就必须提高系统复杂度,引入消息队列和额外的worker才行,不划算.

实际上使用Django真正能遇到性能问题的时候不多,因为绝大部分产品不存在性能瓶颈,一个产品QPS能达到10,就已经美滋滋了,这个吞吐量Django完全没问题,到了Django不行了那个时候换性能更好的方案完全不是问题.

我的通用选型

我所有使用Django开发的项目,架构上都差不多.

  • Nginx 没什么说的,配合gunicorn使用proxy_pass,方便可靠,省心.
  • gunicorn 性能优秀,使用简单,可靠,完全对uwsgi没兴趣
  • Django
  • Redis 在python技术栈中天然扮演着缓存和消息队列的双重身份,与DjangoCelery配合完美.省一个系统组件.
  • Celery 弥补Django异步任务缺陷,妙用无穷.
  • MySQL 其实postgreSQLDjango更搭,不过各个云服务都是对MySQL支持更好一些.

这套东西我使用起来可以说并没有遇到过解决不了的问题,不说无所不能,但是混口饭吃绝对是没问题了.非常适合中小型项目快速开发试错,基本上不需要开发基础功能.

前100行代码

很久没写后端渲染的项目了,这里都是前后端分离的内容

一个Django项目初始化之后,第一个要考虑的就是模块的划分,app建立好之后,我一般会在settings.py那个项目同名文件夹下建立如下几个文件:

basic.py

这个文件我用来定义一些基础的组件,最常用的是这个.
继承于HttpResponse,定义返回结构的类

import json

from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse

from Myapp.settings import DEBUG 


class Response(HttpResponse):

    def __init__(self, data=None, msg="成功",
                 status_code=200, encoder=DjangoJSONEncoder,
                 json_dumps_params=None, **kwargs):
        if json_dumps_params is None:
            json_dumps_params = {}
        if DEBUG:
            # 开发模式增加缩进,方便人工检查数据
            json_dumps_params["indent"] = 2

        json_dumps_params["ensure_ascii"] = False
        kwargs.setdefault('content_type', 'application/json')
        ret = dict(
            status_code=status_code,
            msg=msg,
        )
        if data:
            ret["data"] = data
        s = json.dumps(ret, cls=encoder, **json_dumps_params)
        super().__init__(content=s, **kwargs)

继承DjangoHttpResponse类,参考(抄)了django.http.JsonResponse的结构.接收status_code,msg,data,避免在视图函数中重复的构建返回结构,只需要关心业务数据就可以了.同时可以约束一起开发的人,避免不小心写错返回结构之类的事儿.与之配合的还有一个:
code.py

# 请求成功
OK = 200

# 资源不存在
NOT_FOUND = 404

这样在构建返回结构的时候,直接

return Response(msg="资源不存在",status_code=NOT_FOUND)

前端接收到的返回就是统一格式的JSON,开发模式带有缩进,生产环境DEBUG模式关掉就是普通JSON字符串了

{
    "status_code":404,
    "msg":"资源不存在"
}

概括的说就是,将返回结构抽象成一个对象,并且统一管理返回码,这样在开发中能少些一些重复代码,节省精力,最重要的是方便维护.

自定义中间件:

Django本身继承了模板引擎,默认开启,但是现在流行的前后端分离方案下,模板是可以放弃的(但是初期的管理后台我更喜欢用自带的admin模块,可以通过配置两套不同的settings.py文件和manage.py,实现使用不同的项目配置).同样,基于session的用户验证是否要继续采用也是可以灵活选择的.这里我说一下我们是怎么用JWT来代替session的.
Django本身是通过SessionMiddlewareAuthenticationMiddleware来管理session和用户身份的.我们使用JWT代替session,那么自然就要用一个中间件来代替SessionMiddleware,AuthenticationMiddleware依赖于session同样也就无法使用了.我们用下边这个中间件来代替他们

class JwtMiddleware(object):

    def __init__(self, get_response=None):
        self.get_response = get_response
    def __call__(self, request, *args, **kwargs):
        auth_token = request.META.get("HTTP_AUTHORIZATION")
        try:
            _id = jwt.decode(auth_token, SECRET_KEY)["id"]
            user = User.objects.get(id=_id)

        except:
            user = AnonymousUser()
        request.user = user

        response = self.get_response(request, *args, **kwargs)
        return response

和前端约定在header中携带Authorization字段,作为身份证明.这个token是登录接口签发的,这么做的好处:

  • 可以先不开发用户系统,使用djangoadmin模块进行登陆,进行开发,这意味着用户管理等模块和业务模块可以直接拆分,在两个分支上开发
  • LoginRequiredMixin等框架自身依赖request.user接口的功能依然可用,当然类似的功能我们一般也自己写了.不过在尽量不破坏框架的可用性的前提下进行自定义,是我们快速开发的基本原则之一.可以有效降低团队沟通学习成本,文档都省了,业务逻辑部分完全不需要关注自定义之后的用户鉴权方式.

值得注意的地方是,读取用户是一个数据库操作,Django原生中间件使用了懒加载的模式来操作,这么做的原因(我猜的)是,django作为框架,无法确定是否需要用到user对象,如果每次都加载会很蠢,所以采用懒加载,第一次加载的时候才获取一次并缓存user,避免第二次读库.我们的项目要求每次操作都要读,并且只会读一次,就不需要使用懒加载了.更好的设计是根据自己的需求选择是直接读取用户还是使用懒加载读取用户信息.

小结: 还有一个将请求body读取成dict绑定到request的中间件,避免业务逻辑进行json.loads(request.body)的操作,因为这个是需要try..except..的操作,最少也需要四行代码才能做完.通过这些,算是约定了一个团队内部的开发基本规则,保证大家写的逻辑返回数据结构是可以统一维护的,定义的errcode都集中在一个文件.业务逻辑中需要的信息(request.user/request.json)都已提供.

数据逻辑与业务逻辑的划分

我们写业务逻辑的时候,可能会遇到将一个Model对象序列化为一个JSON的情况,大名鼎鼎的drfDjango restful framework就是做这个的,但是今天不提他了.

Model序列化为JSON的的代码,我通常是写在Model中,哪怕他只用了一次我也习惯定义在Model中,View层只调用方法而不需要将数据分别取出进行构建.这样我们View层的逻辑会显得非常简洁,同样也更容易维护.

除了最终的序列化,Model很多时候还会出现计算属性.

  • 内容已经发表了多久?
  • 优惠当前是否在可用时段?

这些如果写在业务逻辑里,难免要写(datetime.datetime.now()-obj.publish_time).total_seconds()这种又臭又长的代码,可能还会在多个地方使用到.这些数据虽然不是直接写在数据库中,但依然属于Model层该做的事儿,那么我们就写在Model就完了.配合@property,进一步保证了代码可读性.

在项目外访问ORM

假如现在我们需要做一个定时任务,定时从网上抓取信息存入Django项目的数据库中.我们可以选择直接写SQL语句插入,但是有些问题不太好处理.

  • 数据库迁移,要多维护一个数据库连接配置
  • 开发中手动维护SQL语句使其和Model行为一致

所以我推荐使用在定时任务中使用Django ORM,保证和项目内代码逻辑一致,降低维护成本.

官方文档

现在我们需要编写一个独立于项目运行的脚本,官方给出的例子是:

import django
from django.conf import settings
from myapp import myapp_defaults

settings.configure(default_settings=myapp_defaults, DEBUG=True)
django.setup()

# Now this script or any imported module can use any part of Django it needs.
from myapp import models

我常用的是

import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Myapp.settings')
    django.setup()

其实都差不多,我也建议使用官方的方法,可以避免出现环境变量路径不对的问题.
这个操作相当于加载了django项目的配置,在这之后就可以引用你想用的项目Model,进行必要的操作了.必须在django.setup()之后引入Model
通过这个方法,也可以不用django-celery了,配合处理异步任务.

结尾

Python进行WEB开发的唯一优势大概就是速度了.并不是说用python的人比用java的人快,而是说同一个人用python开发会更快一些.
使用Django而不是Flask或者Tornado也是为了开发快,迭代快.做这些边边角角的工作,做这些细节的东西,都是为了快.写一行代码也许需要5秒钟,改一行代码可能需要一整天.也许今天多花几分钟定义的Response可以避免和前端扯皮的导致冲突升级打架斗殴住院一个月.还是非常值得的.自己看着自己写的代码整洁干净,心情也更好不是?
https://luliangce.gitee.io/bl...


50Percent
2k 声望22 粉丝

只能写写代码这个样子