## 使用Django开发商业项目 > 说明:本文的部分插图来自于《Python项目开发实战》和《精通Django》,这两本书中都包含了对Django框架精彩的讲解,有兴趣的读者可以自行购买阅读。 ### Web应用 问题1:描述一个Web应用的工作流程。 ![](./res/web-application.png) 问题2:描述项目的物理架构。(上图中补充负载均衡(反向代理)服务器、数据库服务器、文件服务器、邮件服务器、缓存服务器、防火墙等,而且每个节点都有可能是多节点构成的集群,如下图所示,架构并不是一开始就是这样,而是逐步演进的) ![](./res/05.django_massive_cluster.png) 问题3:描述Django项目的工作流程。(如下图所示) ![](./res/django_request_response_cycle.png) ### MVC架构模式 问题1:为什么要使用MVC架构模式?(模型和视图解耦合) 问题2:MVC架构中每个部分的作用?(如下图所示) ![](./res/mvc.png) ### HTTP请求和响应 #### HTTP请求 = 请求行+请求头+空行+[消息体] ![](./res/http-request.png) #### HTTP响应 = 响应行+响应头+空行+消息体 ![](./res/http-response.png) 1. `HTTPRequest`对象的属性和方法: - `method` - 获取请求方法 - `path` / `get_full_path()` - 获取请求路径/带查询字符串的路径 - `scheme` / `is_secure()` / `get_host()` / `get_port()` - 获取请求的协议/主机/端口 - `META` / `COOKIES` - 获取请求头/Cookie信息 - `GET` / `POST` / `FILES` - 获取GET或POST请求参数/上传的文件 - `get_signed_cookie()` - 获取带签名的Cookie - `is_ajax()` - 是不是Ajax异步请求 - `body` / `content_type` / `encoding` - 获取请求的消息体(bytes流)/MIME类型/编码 2. 中间件添加的属性: - `session` / `user` / `site` 3. `HttpResponse`对象的属性和方法: - `set_cookie()` / `set_signed_cookie()` / `delete_cookie()` - 添加/删除Cookie - `__setitem__` / `__getitem__` / `__delitem__` - 添加/获取/删除响应头 - `charset` / `content` / `status_code` - 响应的字符集/消息体(bytes流)/状态码 - 1xx:请求已经收到,继续处理 - 2xx(成功):请求已经成功收到、理解和接收。 - 3xx(重定向):为完成请求要继续执行后续的操作。 - 4xx(客户端错误):请求不正确或不能够被受理。 - 5xx(服务器错误):服务器处理请求失败。 4. `JsonResponse`(`HttpResponse`的子类型)对象 ```Python >>> from django.http import HttpResponse, JsonResponse >>> >>> response = JsonResponse({'foo': 'bar'}) >>> response.content >>> >>> response = JsonResponse([1, 2, 3], safe=False) >>> response.content >>> >>> response = HttpResponse(b'...') >>> response['cotent-type'] = 'application/pdf'; >>> response['content-disposition'] = 'inline; filename="xyz.pdf"' >>> response['content-disposition'] = 'attachment; filename="xyz.pdf"' >>> >>> response.set_signed_cookie('foo', 'bar', salt='') >>> response.status_code = 200 ``` ### 数据模型(Model) 问题1:关系型数据库表的设计应该注意哪些问题(范式理论和逆范式)?如何通过表来创建模型类(反向工程)?如何通过模型类来创建表(正向工程)? ```Shell python manage.py makemigrations python manage.py migrate python manage.py inspectdb > /models.py ``` 问题2:关系型数据库中数据完整性指的是什么?什么时候需要牺牲数据完整性?(实体完整性/参照完整性/域完整性) 问题3:ORM是什么以及解决了什么问题?(对象模型-关系模型双向转换) 1. `Field`及其子类的属性: - 通用选项: - `db_column` / `db_tablespace` - `null` / `blank` / `default` - `primary_key` - `db_index` / `unqiue` - `choices` / `help_text` / `error_message` / `editable` / `hidden` - 其他选项: - `CharField`: `max_length` - `DateField`: `auto_now` / `auto_now_add` - `DecimalField`: `max_digits` / `decimal_places` - `FileField`: `storage` / `upload_to` - `ImageField`: `height_field` / `width_field` 2. `ForeignKey`的属性: - 重要属性: - `db_constraint`(提升性能或者数据分片的情况可能需要设置为`False`) - `on_delete` * `CASCADE`:级联删除。 - `PROTECT`:抛出`ProtectedError`异常,阻止删除引用的对象。 - `SET_NULL`:把外键设置为`null`,当`null`属性被设置为`True`时才能这么做。 - `SET_DEFAULT`:把外键设置为默认值,提供了默认值才能这么做。 - `related_name` ```Python class Dept(models.Model): pass class Emp(models.Model): dept = models.ForeignKey(related_name='+', ...) Dept.objects.get(no=10).emp_set.all() Emp.objects.filter(dept__no=10) ``` > 说明:`related_name`设置为`'+'`,可以防止一对多外键关联从“一”的一方查询“多”的一方。 - 其他属性: - `to_field` / `limit_choices_to` / `swappable` 3. `Model`的属性和方法 - `objects` / `pk` - `save()` / `delete()` - `clean()` / `validate_unique()` / `full_clean()` 4. `QuerySet`的方法 - `get()` / `all()` / `values()` > 说明:`values()`返回的`QuerySet`中不是模型对象而是字典 - `count()` / `order_by()` / `exists()` / `reverse()` - `filter()` / `exclude()` - `exact` / `iexact`:精确匹配/忽略大小写的精确匹配查询 - `contains` / `icontains` / `startswith / istartswith / endswith / iendswith`:基于`like`的模糊查询 - `in`:集合运算 - `gt` / `gte` / `lt` / `lte`:大于/大于等于/小于/小于等于关系运算 - `range`:指定范围查询(SQL中的`between…and…`) - `year` / `month` / `day` / `week_day` / `hour` / `minute` / `second`:查询时间日期 - `isnull`:查询空值(`True`)或非空值(`False`) - `search`:基于全文索引的全文检索 - `regex` / `iregex`:基于正则表达式的模糊匹配查询 - `aggregate()` / `annotate()` - `Avg` / `Count` / `Sum` / `Max` / `Min` ```Python >>> from django.db.models import Avg >>> Emp.objects.aggregate(avg_sal=Avg('sal')) (0.001) SELECT AVG(`TbEmp`.`sal`) AS `avg_sal` FROM `TbEmp`; args=() {'avg_sal': 3521.4286} ``` ```Python >>> Emp.objects.values('dept').annotate(total=Count('dept')) (0.001) SELECT `TbEmp`.`dno`, COUNT(`TbEmp`.`dno`) AS `total` FROM `TbEmp` GROUP BY `TbEmp`.`dno` ORDER BY NULL LIMIT 21; args=() 说明:调用`first()`方法相当于用`[0]`对`QuerySet`进行切片。 - `only()` / `defer()` ```Python >>> Emp.objects.filter(pk=7800).only('name', 'sal') (0.001) SELECT `TbEmp`.`empno`, `TbEmp`.`ename`, `TbEmp`.`sal` FROM `TbEmp` WHERE `TbEmp`.`empno` = 7800 LIMIT 21; args=(7800,) ]> >>> Emp.objects.filter(pk=7800).defer('name', 'sal') (0.001) SELECT `TbEmp`.`empno`, `TbEmp`.`job`, `TbEmp`.`mgr`, `TbEmp`.`comm`, `TbEmp`.`dno` FROM `TbEmp` WHERE `TbEmp`.`empno` = 7800 LIMIT 21; args=(7800,) ]> ``` - `create()` / `update()` / `raw()` ```Python >>> Emp.objects.filter(dept__no=20).update(sal=F('sal') + 100) (0.011) UPDATE `TbEmp` SET `sal` = (`TbEmp`.`sal` + 100) WHERE `TbEmp`.`dno` = 20; args=(100, 20) >>> >>> Emp.objects.raw('select empno, ename, job from TbEmp where dno=10') ``` 5. `Q`对象和`F`对象 > 说明:Q对象主要用来解决多条件组合的复杂查询;F对象主要用于更新数据。 ```Python >>> from django.db.models import Q >>> Emp.objects.filter( ... Q(name__startswith='张'), ... Q(sal__lte=5000) | Q(comm__gte=1000) ... ) # 查询名字以“张”开头且工资小于等于5000或补贴大于等于1000的员工 ]> ``` ```Python >>> from backend.models import Emp, Dept >>> emps = Emp.objects.filter(dept__no=20) >>> from django.db.models import F >>> emps.update(sal=F('sal') + 100) ``` 6. 原生SQL查询 ```Python from django.db import connections with connections['...'].cursor() as cursor: cursor.execute("UPDATE TbEmp SET sal=sal+10 WHERE dno=30") cursor.execute("SELECT ename, job FROM TbEmp WHERE dno=10") row = cursor.fetchall() ``` 7. 模型管理器 ```Python class BookManager(models.Manager): def title_count(self, keyword): return self.filter(title__icontains=keyword).count() class Book(models.Model): objects = BookManager() ``` ### 视图函数(Controller) #### 如何设计视图函数 1. 用户的每个操作(用户故事)对应一个视图函数。 2. [每个视图函数可以构成一个事务边界](https://docs.djangoproject.com/en/2.1/ref/settings/)。 - 事务的ACID特性。 - 原子性(Atomicity):事务中各项的操作要么全做要么全不做; - 一致性(Consistentcy):事务前后系统的状态是一致的; - 隔离性(Isolation):并发执行的事务无法看到彼此的中间状态; - 持久性(Duration):事务完成后所做的改动都会被持久化。 - 事务隔离级别 - 设置事务隔离级别是为了数据库底层依据事务隔离级别为数据加上适当的锁。如果需要保证数据的强一致性,那么关系型数据库仍然是唯一的也是最好的选择,因为关系型数据库可以通过锁机制来保护数据。事务隔离级别从低到高依次是:Read Uncommitted(读未提交)、Read Committed(读提交)、Repeatable Read(可重复读)、Serializable(串行化)。事务隔离级别越高,数据并发访问的问题越少,但是性能越差;事务隔离级别越低,数据并发访问的问题越多,但是性能越好。 - 数据并发访问会产生5种问题(请参考我的[《Java面试题全集(上)》](https://blog.csdn.net/jackfrued/article/details/44921941)第80题对该问题的讲解): - 第1类丢失更新(A事务撤销覆盖B事务更新的数据)和第2类丢失更新(A事务提交覆盖B事务更新的数据)。 - 脏读(读脏数据):一个事务读取到其他尚未提交的事务的数据。 - 不可重复读: 一个事务在读取它的查询结果时,被另一个事务更新了它的查询记录导致无法读到数据。 - 幻读:一个事务在读取它的查询结果时,发现读到了被另一个事务提交的新数据。 ```SQL -- 设置全局默认的事务隔离级别 set global transaction isolation level repeatable read; -- 设置当前会话的事务隔离级别 set session transaction isolation level read committed; -- 查询当前会话的事务隔离级别 select @@tx_isolation; ``` - Django中的事务控制。 - 给每个请求绑定事务环境(反模式)。 ```Python ATOMIC_REQUESTS = True ``` - 使用事务装饰器(简单易用) - 粗粒度(控制不够精细)。 ```Python @transaction.non_atomic_requests @transaction.atomic ``` - 使用上下文语法(细粒度 - 事务控制的范围更加精准)。 ```Python with transaction.atomic(): pass ``` - 关闭自动提交使用手动提交。 ```Python AUTOCOMMIT = False ``` ```Python transaction.commit() transaction.rollback() ``` #### URL配置 1. 可以让部分URL只在调试模式下生效。 ```Python from django.conf import settings urlpatterns = [ ... ] if settings.DEBUG: urlpatterns += [ ... ] ``` 2. 可以使用命名捕获组捕获路径参数。 ```Python url(r'api/code/(?P1[3-9]\d{9})'), path('api/code/'), ``` 3. URL配置不关心请求使用的方法(一个视图函数可以处理不同的请求方式)。 4. 如果使用`url`函数捕获的路径参数都是字符串,`path`函数可以指定路径参数类型。 5. 可以使用`include`函数引入其他URL配置,捕获的参数会向下传递。 6. 在`url`和`path`函数甚至是`include`函数中都可以用字典向视图传入额外的参数,如果参数与捕获的参数同名,则使用字典中的参数。 7. 可以用`reverse`函数实现URL的逆向解析(从名字解析出URL),在模板中也可以用`{% url %}`实现同样的操作。 ```Python path('', views.index, name='index') return redirect(reverse('index')) return redirect('index') ``` ### 模板(View) #### 后端渲染 1. 模板的配置和渲染函数。 ```Python TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates'), ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] ``` ```Python resp = render(request, 'index.html', {'foo': ...}) ``` 2. 模板遇到变量名的查找顺序。 - 字典查找(如:`foo['bar']`) - 属性查找(如:`foo.bar`) - 方法调用(如:`foo.bar()`) - 方法不能有必须传值的参数 - 在模板中不能够给方法传参 - 如果方法的`alters_data`被设置为`True`则不能调用该方法(避免误操作的风险),模型对象动态生成的`delete()`和`save()`方法都设定了`alters_data = True`。 - 列表索引查找(如:`foo[0]`) 3. 模板标签的使用。 - `{% if %}` / `{% else %}` / `{% endif %}` - `{% for %}` / `{% endfor %}` - `{% ifequal %}` / `{% endifequal %}` / `{% ifnotequal %}` / `{% endifnotequal %}` - `{# comment #}` / `{% comment %}` / `{% endcomment %}` 4. 过滤器的使用。 - `lower` / `upper` / `first` / `last` / `truncatewords` / `date `/ `time` / `length` / `pluralize` / `center` / `ljust` / `rjust` / `cut` / `urlencode` / `default_if_none` / `filesizeformat` / `join` / `slice` / `slugify` 5. 模板的包含和继承。 - `{% include %}` / `{% block %}` - `{% extends %}` 6. 模板加载器(后面优化部分会讲到)。 - 文件系统加载器 ```Python TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], }] ``` - 应用目录加载器 ```Python TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, }] ``` #### 前端渲染 1. 前端模板引擎:Handlebars / Mustache。 2. 前端MV\*框架。 - MVC - AngularJS - MVVM(Model-View-ViewModel) - Vue.js #### 其他视图 1. MIME(多用途Internet邮件扩展)类型 - 告知浏览器传输的数据类型。 | Content-Type | 说明 | | ---------------- | ------------------------------------------------------------ | | application/json | [JSON](https://zh.wikipedia.org/wiki/JSON)(JavaScript Object Notation) | | application/pdf | [PDF](https://zh.wikipedia.org/wiki/PDF)(Portable Document Format) | | audio/mpeg | [MP3](https://zh.wikipedia.org/wiki/MP3)或其他[MPEG](https://zh.wikipedia.org/wiki/MPEG)音频文件 | | audio/vnd.wave | [WAV](https://zh.wikipedia.org/wiki/WAV)音频文件 | | image/gif | [GIF](https://zh.wikipedia.org/wiki/GIF)图像文件 | | image/jpeg | [JPEG](https://zh.wikipedia.org/wiki/JPEG)图像文件 | | image/png | [PNG](https://zh.wikipedia.org/wiki/PNG)图像文件 | | text/html | [HTML](https://zh.wikipedia.org/wiki/HTML)文件 | | text/xml | [XML](https://zh.wikipedia.org/wiki/XML) | | video/mp4 | [MP4](https://zh.wikipedia.org/wiki/MP4)视频文件 | | video/quicktime | [QuickTime](https://zh.wikipedia.org/wiki/QuickTime)视频文件 | 2. 如何处置生成的内容(inline / attachment)。 ```Python >>> from urllib.parse import quote >>> >>> response['content-type'] = 'application/pdf' >>> filename = quote('Python语言规范.pdf') >>> filename 'Python%E8%AF%AD%E8%A8%80%E8%A7%84%E8%8C%83.pdf' >>> response['content-disposition'] = f'attachment; filename="{filename}"' ``` > 提醒:URL以及请求和响应头中的中文都应该处理成[百分号编码](https://zh.wikipedia.org/zh-hans/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81)。 3. 生成CSV / Excel / PDF / 统计报表。 - 向浏览器传输二进制数据。 ```Python buffer = ByteIO() resp = HttpResponse(content_type='...') resp['Content-Disposition'] = 'attachment; filename="..."' resp.write(buffer.getvalue()) ``` ```Python def get_style(name, color=0, bold=False, italic=False): style = xlwt.XFStyle() font = xlwt.Font() font.name = name font.colour_index = color font.bold = bold font.italic = italic style.font = font return style def export_emp_excel(request): # 创建Excel工作簿(使用三方库xlwt) workbook = xlwt.Workbook() # 向工作簿中添加工作表 sheet = workbook.add_sheet('员工详细信息') # 设置表头 titles = ['编号', '姓名', '主管', '职位', '工资', '部门名称'] for col, title in enumerate(titles): sheet.write(0, col, title, get_style('HanziPenSC-W3', 2, True)) # 使用Django的ORM框架查询员工数据 emps = Emp.objects.all().select_related('dept').select_related('mgr') cols = ['no', 'name', 'mgr', 'job', 'sal', 'dept'] # 通过嵌套的循环将员工表的数据写入Excel工作表的单元格 for row, emp in enumerate(emps): for col, prop in enumerate(cols): val = getattr(emp, prop, '') if isinstance(val, (Dept, Emp)): val = val.name sheet.write(row + 1, col, val) # 将Excel文件的二进制数据写入内存 buffer = BytesIO() workbook.save(buffer) # 通过HttpResponse对象向浏览器输出Excel文件 resp = HttpResponse(buffer.getvalue()) resp['content-type'] = 'application/msexcel' # 如果文件名有中文需要处理成百分号编码 resp['content-disposition'] = 'attachment; filename="detail.xls"' return resp ``` - 大文件的流式处理:`StreamingHttpResponse`。 ```Python def download_file(request): file_stream = open('...', 'rb') # 如果文件的二进制数据较大则最好用迭代器进行处理避免过多的占用服务器内存 file_iter = iter(lambda: file_stream.read(4096), b'') resp = StreamingHttpResponse(file_iter) # 中文文件名要处理成百分号编码 filename = quote('...', 'utf-8') resp['Content-Type'] = '...' resp['Content-Disposition'] = f'attachment; filename="{filename}"' return resp ``` > 说明:如果需要生成PDF文件,可以需要安装`reportlab`。另外,使用StreamingHttpResponse只能减少内存的开销,但是如果下载一个大文件会导致一个请求长时间占用服务器资源,比较好的做法还是把报表提前生成好(可以考虑使用定时任务),放在静态资源服务器或者是云存储服务器上以访问静态资源的方式访问。 - [ECharts](http://echarts.baidu.com/)或[Chart.js](https://www.chartjs.org/)。 - 思路:后端只提供JSON格式的数据,前端JavaScript渲染生成图表。 ```Python def get_charts_data(request): """获取统计图表JSON数据""" names = [] totals = [] # 通过connections获取指定数据库连接并创建游标对象 with connections['backend'].cursor() as cursor: # 在使用ORM框架时可以使用对象管理器的aggregate()和annotate()方法实现分组和聚合函数查询 # 执行原生SQL查询(如果ORM框架不能满足业务或性能需求) cursor.execute('select dname, total from vw_dept_emp') for row in cursor.fetchall(): names.append(row[0]) totals.append(row[1]) return JsonResponse({'names': names, 'totals': totals}) ``` ```HTML 统计图表
``` ### 中间件 问题1:中间件背后的设计理念是什么?(分离横切关注功能/拦截过滤器模式) 问题2:中间件有哪些不同的实现方式?(参考下面的代码) 问题3:描述Django内置的中间件及其执行顺序。(推荐阅读:[Django官方文档 - 中间件 - 中间件顺序](https://docs.djangoproject.com/zh-hans/2.0/ref/middleware/#middleware-ordering)) #### 激活中间件 ```Python MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'common.middlewares.block_sms_middleware', ] ``` #### 自定义中间件 ```Python def simple_middleware(get_response): def middleware(request, *args, **kwargs): response = get_response(request, *args, **kwargs) return response return middleware ``` ```Python class MyMiddleware(object): def __init__(self, get_response): self.get_response = get_response def __call__(self, request): response = self.get_response(request) return response ``` ```Python class MyMiddleware(object): def __init__(self): pass def process_request(request): pass def process_view(request, view_func, view_args, view_kwargs): pass def process_template_response(request, response): pass def process_response(request, response): pass def process_exception(request, exception): pass ``` #### 内置中间件 1. CommonMiddleware - 基础设置中间件 - DISALLOWED_USER_AGENTS - 不被允许的用户代理(浏览器) - APPEND_SLASH - 是否追加`/` - USE_ETAG - 浏览器缓存相关 2. SecurityMiddleware - 安全相关中间件 - SECURE_HSTS_SECONDS - 强制使用HTTPS的时间 - SECURE_HSTS_INCLUDE_SUBDOMAINS - HTTPS是否覆盖子域名 - SECURE_CONTENT_TYPE_NOSNIFF - 是否允许浏览器推断内容类型 - SECURE_BROWSER_XSS_FILTER - 是否启用跨站脚本攻击过滤器 - SECURE_SSL_REDIRECT - 是否重定向到HTTPS连接 - SECURE_REDIRECT_EXEMPT - 免除重定向到HTTPS 3. SessionMiddleware - 会话中间件 4. CsrfViewMiddleware - 防范跨站身份伪造中间件 5. XFrameOptionsMiddleware - 防范点击劫持攻击中间件 ![](./res/click-jacking.png) ![](./res/builtin-middlewares.png) ### 表单 1. 用法:通常不要用来生成页面上的表单控件(耦合度太高不容易定制),主要用来验证数据。 2. Form的属性和方法: - `is_valid()` / `is_multipart()` - `errors` / `fields` / `is_bound` / `changed_data` / `cleaned_data` - `add_error()` / `has_errors()` / `non_field_errors()` - `clean()` 3. Form.errors的方法: - `as_data()` / `as_json()` / `get_json_data()` 问题1:Django中的`Form`和`ModelForm`有什么作用?(通常不用来生成表单主要用来验证数据) 问题2:表单上传文件时应该注意哪些问题?(表单的设置、多文件上传、图片预览(FileReader)、Ajax上传文件、上传后的文件如何存储、调用云存储(如[阿里云OSS](https://www.aliyun.com/product/oss)、[七牛云](https://www.qiniu.com/)、[LeanCloud](https://leancloud.cn/storage/)等)) ```HTML
``` > 说明:上传图片文件的预览效果可以通过HTML5的FileReader来实现。 > 说明:使用云存储通常是比自己配置分布式文件系统这种方式更靠谱的做法,而且云存储通常成本并不太高,不仅如此大多数云存储还提供了如图片剪裁、生成水印、视频转码、CDN等服务。如果要自己做上传的视频文件转码,需要安装三方库ffmpeg,在程序中调用该三方库可以实现转码。 ### Cookie和Session 问题1:使用Cookie能解决什么问题?(用户跟踪,解决HTTP协议无状态问题) 1. URL重写 ``` http://www.abc.com/path/resource?foo=bar ``` 2. 隐藏域(隐式表单域)- 埋点 ```HTML
``` 3. Cookie - 浏览器中的临时文件(文本文件)- BASE64 问题2:Cookie和Session之间关系是什么?(Session的标识通过Cookie保存和传输) #### Session的配置 1. Session对应的中间件:`django.contrib.sessions.middleware.SessionMiddleware`。 2. Session引擎。 - 基于数据库(默认方式) ```Python INSTALLED_APPS = [ 'django.contrib.sessions', ] ``` - 基于缓存(推荐使用) ```Python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_CACHE_ALIAS = 'session' ``` - 基于文件(基本不考虑) - 基于Cookie(不靠谱) ```Python SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' ``` 3. Cookie相关的配置。 ```Python SESSION_COOKIE_NAME = 'djang_session_id' SESSION_COOKIE_AGE = 1209600 # 如果设置为True,Cookie就是基于浏览器窗口的Cookie,不会持久化 SESSION_EXPIRE_AT_BROWSER_CLOSE = False SESSION_SAVE_EVERY_REQUEST = False SESSION_COOKIE_HTTPONLY = True ``` 4. session的属性和方法。 - `session_key` / `session_data` / `expire_date` - `__getitem__` / `__setitem__` / `__delitem__` / `__contains__` - `set_expiry()` / `get_expiry_age()` / `get_expiry_date()` - 设置/获取会话超期时间 - `flush()` - 销毁会话 - `set_test_cookie()` / `test_cookie_worked()` / `delete_test_cookie()` - 测试浏览器是否支持Cookie(提示用户如果浏览器禁用Cookie可能会影响网站的使用) 5. session的序列化。 ```Python SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer' ``` - JSONSerializer(1.6及以后默认)- 如果想将自定义的对象放到session中,会遇到“Object of type 'XXX' is not JSON serializable”的问题(如果配置使用Redis保存Session,django-redis使用了Pickle序列化,这个问题就不存在了)。 - PickleSerializer(1.6以前的默认)- 因为安全问题不推荐使用,但是只要不去反序列化用户构造的恶意的Payload其实也没有什么风险。关于这种方式的安全漏洞,可以参考《[Python Pickle的任意代码执行漏洞实践和Payload构造》](http://www.polaris-lab.com/index.php/archives/178/)一文或《软件架构-Python语言实现》上关于这个问题的讲解。 > 说明:如果使用了django_redis整合Redis作为session的存储引擎,那么由于django_redis又封装了一个PickleSerializer来提供序列化,所以不会存在上述的问题,且Redis中保存的value是pickle序列化之后的结果。 ### 缓存 #### 配置缓存 ```Python CACHES = { # 默认缓存 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': [ 'redis://1.2.3.4:6379/0', ], 'KEY_PREFIX': 'teamproject', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'CONNECTION_POOL_KWARGS': { 'max_connections': 1000, }, 'PASSWORD': '1qaz2wsx', } }, # 页面缓存 'page': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': [ 'redis://1.2.3.4:6379/1', ], 'KEY_PREFIX': 'teamproject:page', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'CONNECTION_POOL_KWARGS': { 'max_connections': 500, }, 'PASSWORD': '1qaz2wsx', } }, # 会话缓存 'session': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': [ 'redis://1.2.3.4:6379/2', ], 'KEY_PREFIX': 'teamproject:session', 'TIMEOUT': 1209600, 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'CONNECTION_POOL_KWARGS': { 'max_connections': 2000, }, 'PASSWORD': '1qaz2wsx', } }, # 接口数据缓存 'api': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': [ 'redis://1.2.3.4:6379/3', ], 'KEY_PREFIX': 'teamproject:api', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'CONNECTION_POOL_KWARGS': { 'max_connections': 500, }, 'PASSWORD': '1qaz2wsx', } }, } ``` > 说明:通过Redis底层提供的多个数据库来隔离缓存数据有助于缓存数据的管理。如果配置了Redis的主从复制(读写分离),LOCATION列表中可以配置多个Redis连接,第一个被视为master用来进行写操作,后面的被视为slave用来进行读操作。 #### 全站缓存 ```Python MIDDLEWARE_CLASSES = [ 'django.middleware.cache.UpdateCacheMiddleware', ... 'django.middleware.common.CommonMiddleware', ... 'django.middleware.cache.FetchFromCacheMiddleware', ] CACHE_MIDDLEWARE_ALIAS = 'default' CACHE_MIDDLEWARE_SECONDS = 300 CACHE_MIDDLEWARE_KEY_PREFIX = 'djang:cache' ``` #### 视图层缓存 ```Python from django.views.decorators.cache import cache_page from django.views.decorators.vary import vary_on_cookie @cache_page(timeout=60 * 15, cache='page') @vary_on_cookie def my_view(request): pass ``` ```Python from django.views.decorators.cache import cache_page urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)), ] ``` #### 其他内容 1. 模板片段缓存。 - `{% load cache %}` - `{% cache %}` / `{% endcache %}` 2. 使用底层API访问缓存。 ```Python >>> from django.core.cache import cache >>> >>> cache.set('my_key', 'hello, world!', 30) >>> cache.get('my_key') >>> cache.clear() ``` ```Python >>> from django.core.cache import caches >>> cache1 = caches['page'] >>> cache2 = caches['page'] >>> cache1 is cache2 True >>> cache3 = caches['session'] >>> cache2 is cache3 False ``` ```Python >>> from django_redis import get_redis_connection >>> >>> redis_client = get_redis_connection() >>> redis_client.hgetall() ``` ### 日志 #### 日志级别 NOTSET < DEBUG < INFO < WARNING < ERROR < FATAL #### 日志配置 ```Python LOGGING = { 'version': 1, 'disable_existing_loggers': False, # 配置日志格式化器 'formatters': { 'simple': { 'format': '%(asctime)s %(module)s.%(funcName)s: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S', }, 'verbose': { 'format': '%(asctime)s %(levelname)s [%(process)d-%(threadName)s] ' '%(module)s.%(funcName)s line %(lineno)d: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S', } }, # 配置日志过滤器 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, # 配置日志处理器 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'level': 'DEBUG', 'filters': ['require_debug_true'], 'formatter': 'simple', }, 'file1': { 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': 'access.log', 'when': 'W0', 'backupCount': 12, 'formatter': 'simple', 'level': 'INFO', }, 'file2': { 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': 'error.log', 'when': 'D', 'backupCount': 31, 'formatter': 'verbose', 'level': 'WARNING', }, }, # 配置日志器 'loggers': { 'django': { 'handlers': ['console', 'file1', 'file2'], 'propagate': True, 'level': 'DEBUG', }, } } ``` [日志配置官方示例](https://docs.djangoproject.com/zh-hans/2.0/topics/logging/#s-examples)。 #### 日志分析 1. Linux相关命令:head、tail、grep、awk、uniq、sort ```Shell tail -10000 access.log | awk '{print $1}' | uniq -c | sort -r ``` 2. 实时日志文件分析:Python + 正则表达式 + Crontab 3. [《Python日志分析工具》](https://github.com/jkklee/web_log_analyse)。 4. [《集中式日志系统ELK》](https://www.ibm.com/developerworks/cn/opensource/os-cn-elk/index.html)。 - ElasticSearch:搜索引擎,实现全文检索。 - Logstash:负责从指定节点收集日志。 - Kibana:日志可视化工具。 5. 大数据日志处理:Flume+Kafka日志采集、Storm / Spark实时数据处理、Impala实时查询。 ### RESTful 问题1:RESTful架构到底解决了什么问题?(URL具有自描述性、资源表述与视图的解耦和、互操作性利用构建微服务以及集成第三方系统、无状态性提高水平扩展能力) 问题2:项目在使用RESTful架构时有没有遇到一些问题或隐患?(对资源访问的限制、资源从属关系检查、避免泄露业务信息、防范可能的攻击) > 补充:下面的几个和安全性相关的响应头在前面讲中间件的时候提到过的。 > > - X-Frame-Options: DENY > - X-Content-Type-Options: nosniff > - X-XSS-Protection: 1; mode=block; > - Strict­-Transport-­Security: max-age=31536000; 问题3:如何保护API中的敏感信息以及防范重放攻击?(摘要和令牌) 推荐阅读:[《如何有效防止API的重放攻击》](https://help.aliyun.com/knowledge_detail/50041.html)。 #### 使用djangorestframework 安装djangorestfrmework(为了描述方便,以下统一简称为drf)。 ```Shell pip install djangorestframework ``` 配置drf。 ```Python INSTALLED_APPS = [ 'rest_framework', ] REST_FRAMEWORK = { # 配置默认页面大小 'PAGE_SIZE': 10, # 配置默认的分页类 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 配置异常处理器 # 'EXCEPTION_HANDLER': 'api.exceptions.exception_handler', # 配置默认解析器 # 'DEFAULT_PARSER_CLASSES': ( # 'rest_framework.parsers.JSONParser', # 'rest_framework.parsers.FormParser', # 'rest_framework.parsers.MultiPartParser', # ), # 配置默认限流类 # 'DEFAULT_THROTTLE_CLASSES': (), # 配置默认授权类 # 'DEFAULT_PERMISSION_CLASSES': ( # 'rest_framework.permissions.IsAuthenticated', # ), # 配置默认认证类 # 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # ), } ``` #### 编写序列化器 ```Python from rest_framework import serializers from rest_framework.serializers import ModelSerializer from common.models import District, HouseType, Estate, Agent class DistrictSerializer(ModelSerializer): class Meta: model = District fields = ('distid', 'name') class HouseTypeSerializer(ModelSerializer): class Meta: model = HouseType fields = '__all__' class AgentSerializer(ModelSerializer): class Meta: model = Agent fields = ('agentid', 'name', 'tel', 'servstar', 'certificated') class EstateSerializer(ModelSerializer): district = serializers.SerializerMethodField() agents = serializers.SerializerMethodField() @staticmethod def get_agents(estate): return AgentSerializer(estate.agents, many=True).data @staticmethod def get_district(estate): return DistrictSerializer(estate.district).data class Meta: model = Estate fields = '__all__' ``` #### 方法1:使用装饰器 ```Python @api_view(['GET']) @cache_page(timeout=None, cache='api') def provinces(request): queryset = District.objects.filter(parent__isnull=True) serializer = DistrictSerializer(queryset, many=True) return Response(serializer.data) @api_view(['GET']) @cache_page(timeout=300, cache='api') def cities(request, provid): queryset = District.objects.filter(parent__distid=provid) serializer = DistrictSerializer(queryset, many=True) return Response(serializer.data) ``` ```Python urlpatterns = [ path('districts/', views.provinces, name='districts'), path('districts//', views.cities, name='cities'), ] ``` > 说明:上面使用了Django自带的视图装饰器(@cache_page)来实现对API接口返回数据的缓存。 #### 方法2:使用APIView及其子类 更好的复用代码,不要重“复发明轮子”。 ```Python class HouseTypeApiView(CacheResponseMixin, ListAPIView): queryset = HouseType.objects.all() serializer_class = HouseTypeSerializer ``` ```Python urlpatterns = [ path('housetypes/', views.HouseTypeApiView.as_view(), name='housetypes'), ] ``` > 说明:上面使用了drf_extensions提供的CacheResponseMixin混入类实现了对接口数据的缓存。如果重写了获取数据的方法,可以使用drf_extensions提供的@cache_response来实现对接口数据的缓存,也可以用自定义的函数来生成缓存中的key。当然还有一个选择就是通过Django提供的@method_decorator装饰器,将@cache_page装饰器处理为装饰方法的装饰器,这样也能提供使用缓存服务。 `drf-extensions`配置如下所示。 ```Python # 配置DRF扩展来支持缓存API接口调用结果 REST_FRAMEWORK_EXTENSIONS = { 'DEFAULT_CACHE_RESPONSE_TIMEOUT': 300, 'DEFAULT_USE_CACHE': 'default', # 配置默认缓存单个对象的key函数 'DEFAULT_OBJECT_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_object_cache_key_func', # 配置默认缓存对象列表的key函数 'DEFAULT_LIST_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_list_cache_key_func', } ``` #### 方法3:使用ViewSet及其子类 ```Python class HouseTypeViewSet(CacheResponseMixin, viewsets.ModelViewSet): queryset = HouseType.objects.all() serializer_class = HouseTypeSerializer pagination_class = None ``` ```Python router = DefaultRouter() router.register('housetypes', views.HouseTypeViewSet) urlpatterns += router.urls ``` djangorestframework提供了基于Bootstrap定制的页面来显示接口返回的JSON数据,当然也可以使用[POSTMAN](https://www.getpostman.com/)这样的工具对API接口进行测试。 #### 补充说明 在这里顺便提一下跟前端相关的几个问题。 问题1:如何让浏览器能够发起DELETE/PUT/PATCH? ```HTML
``` ```Python if request.method == 'POST' and '_method' in request.POST: request.method = request.POST['_method'].upper() ``` ```HTML ``` 问题2:如何解决多个JavaScript库之间某个定义(如$函数)冲突的问题? ```HTML ``` ```HTML ``` 问题3:jQuery对象与原生DOM对象之间如何转换? ```HTML ``` #### 过滤数据 如果需要过滤数据(对数据接口设置筛选条件、排序条件等),可以使用`django-filter`三方库来实现。 ```Shell pip install django-filter ``` ```Python INSTALLED_APPS = [ 'django_filters', ] REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ( 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter', ), } ``` ```Python from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter from rest_framework.generics import RetrieveAPIView, ListCreateAPIView from api.serializers import EstateSerializer from common.models import Estate @method_decorator(decorator=cache_page(timeout=120, cache='api', key_prefix='estates'), name='get') class EstateView(RetrieveAPIView, ListCreateAPIView): queryset = Estate.objects.all().select_related('district').prefetch_related('agents') serializer_class = EstateSerializer filter_backends = (DjangoFilterBackend, OrderingFilter) filter_fields = ('name', 'district') ordering = ('-hot', ) ordering_fields = ('hot', 'estateid') ``` ```Python from django_filters import rest_framework as drf from common.models import HouseInfo class HouseInfoFilter(drf.FilterSet): """自定义房源数据过滤器""" title = drf.CharFilter(lookup_expr='starts') dist = drf.NumberFilter(field_name='district') min_price = drf.NumberFilter(field_name='price', lookup_expr='gte') max_price = drf.NumberFilter(field_name='price', lookup_expr='lte') type = drf.NumberFilter() class Meta: model = HouseInfo fields = ('title', 'district', 'min_price', 'max_price', 'type') ``` ```Python class HouseInfoViewSet(CacheResponseMixin, ReadOnlyModelViewSet): queryset = HouseInfo.objects.all() \ .select_related('type', 'district', 'estate', 'agent') \ .prefetch_related('tags').order_by('-pubdate') serializer_class = HouseInfoSerializer filter_backends = (DjangoFilterBackend, OrderingFilter) filterset_class = HouseInfoFilter ordering = ('price',) ordering_fields = ('price', 'area') ``` #### 身份认证 查看drf中APIView类的代码可以看出,drf默认的认证方案是 `DEFAULT_AUTHENTICATION_CLASSES`,如果修改authentication_classes就可以自行定制身份认证的方案。 ```Python class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS # 此处省略下面的代码 ``` ```Python DEFAULTS = { # Base API policies 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ), 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', ), 'DEFAULT_THROTTLE_CLASSES': (), 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata', 'DEFAULT_VERSIONING_CLASS': None, # 此处省略下面的代码 } ``` 自定义认证类,继承`BaseAuthentication`并重写`authenticate(self, request)`方法,通过请求中的userid和token来确定用户身份。如果认证成功,该方法应返回一个二元组(用户和令牌的信息),否则产生异常。也可以重写 `authenticate_header(self, request)`方法来返回一个字符串,该字符串将用于`HTTP 401 Unauthorized`响应中的WWW-Authenticate响应头的值。如果未重写该方法,那么当未经身份验证的请求被拒绝访问时,身份验证方案将返回`HTTP 403 Forbidden`响应。 ```Python class MyAuthentication(BaseAuthentication): """自定义用户身份认证类""" def authenticate(self, request): try: token = request.GET['token'] or request.POST['token'] user_token = UserToken.objects.filter(token=token).first() if user_token: return user_token.user, user_token else: raise AuthenticationFailed('请提供有效的用户身份标识') except KeyError: raise AuthenticationFailed('请提供有效的用户身份标识') def authenticate_header(self, request): pass ``` 使用自定义的认证类。 ```Python class EstateViewSet(CacheResponseMixin, ModelViewSet): # 通过queryset指定如何获取数据(资源) queryset = Estate.objects.all().select_related('district').prefetch_related('agents') # 通过serializer_class指定如何序列化数据 serializer_class = EstateSerializer # 指定根据哪些字段进行数据筛选 filter_fields = ('district', 'name') # 指定根据哪些字段对数据进行排序 ordering_fields = ('hot', ) # 指定用于进行用户身份验证的类 authentication_classes = (MyAuthentication, ) ``` > 说明:也可以在Django配置文件中将自定义的认证类设置为默认认证方式。 #### 授予权限 权限检查总是在视图的最开始处运行,在任何其他代码被允许进行之前。最简单的权限是允许通过身份验证的用户访问,并拒绝未经身份验证的用户访问,这对应于dfr中的`IsAuthenticated`类,可以用它来取代默认的`AllowAny`类。权限策略可以在Django的drf配置中用`DEFAULT_PERMISSION_CLASSES`全局设置。 ```Python REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ) } ``` 也可以在基于`APIView`类的视图上设置身份验证策略。 ```Python from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView class ExampleView(APIView): permission_classes = (IsAuthenticated, ) # 此处省略其他代码 ``` 或者在基于`@api_view`装饰器的视图函数上设置。 ```Python from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated @api_view(['GET']) @permission_classes((IsAuthenticated, )) def example_view(request, format=None): # 此处省略其他代码 ``` 自定义权限需要继承`BasePermission`并实现以下方法中的一个或两个,下面是BasePermission的代码。 ```Python @six.add_metaclass(BasePermissionMetaclass) class BasePermission(object): """ A base class from which all permission classes should inherit. """ def has_permission(self, request, view): """ Return `True` if permission is granted, `False` otherwise. """ return True def has_object_permission(self, request, view, obj): """ Return `True` if permission is granted, `False` otherwise. """ return True ``` 如果请求被授予访问权限,则方法应该返回True,否则返False。下面的例子演示了阻止黑名单中的IP地址访问接口数据(这个在反爬虫的时候很有用哟)。 ```Python from rest_framework import permissions class BlacklistPermission(permissions.BasePermission): """ Global permission check for blacklisted IPs. """ def has_permission(self, request, view): ip_addr = request.META['REMOTE_ADDR'] blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists() return not blacklisted ``` 如果要实现更为完整的权限验证,可以考虑RBAC或ACL。 1. RBAC - 基于角色的访问控制,如下图所示。 ![](./res/rbac-basic.png) ![](./res/rbac-full.png) 2. ACL - 访问控制列表(每个用户绑定自己的访问白名单)。 #### 访问限流 可以修改dfr配置的`DEFAULT_THROTTLE_CLASSES` 和 `DEFAULT_THROTTLE_RATES`两个值来设置全局默认限流策略。例如: ```Python REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ), 'DEFAULT_THROTTLE_RATES': { 'anon': '3/min', 'user': '10000/day' } } ``` `DEFAULT_THROTTLE_RATES`中使用的频率描述可能包括`second`、`minute`、`hour`或`day`。 如果要为接口单独设置限流,可以在每个视图或视图集上设置限流策略,如下所示: ```Python from rest_framework.throttling import UserRateThrottle from rest_framework.views import APIView class ExampleView(APIView): throttle_classes = (UserRateThrottle, ) # 此处省略下面的代码 ``` 或 ```Python @api_view(['GET']) @throttle_classes([UserRateThrottle, ]) def example_view(request, format=None): # 此处省略下面的代码 ``` 当然也可以通过继承`BaseThrottle`来自定义限流策略,通常需要重写`allow_request`和`wait`方法。 ### 异步任务和计划任务 #### Celery的应用 Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。它是一个专注于实时处理的任务队列,同时也支持任务调度。 推荐阅读:[《Celery官方文档中文版》](http://docs.jinkan.org/docs/celery/),上面有极为详细的配置和使用指南。 ![](./res/celery_architecture.png) Celery是一个本身不提供队列服务,官方推荐使用RabbitMQ或Redis来实现消息队列服务,前者是更好的选择,它对AMQP(高级消息队列协议)做出了非常好的实现。 1. 安装RabbitMQ。 ```Shell docker pull rabbitmq docker run -d -p 5672:5672 --name myrabbit rabbitmq docker container exec -it myrabbit /bin/bash ``` 2. 创建用户、资源以及分配操作权限。 ```Shell rabbitmqctl add_user luohao 123456 rabbitmqctl set_user_tags luohao administrator rabbitmqctl add_vhost vhost1 rabbitmqctl set_permissions -p vhost1 luohao ".*" ".*" ".*" ``` 3. 创建Celery实例。 ```Python # 注册环境变量 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'fangtx.settings') # 创建Celery实例 app = celery.Celery( 'fangtx', broker='amqp://luohao:123456@120.77.222.217:5672/vhost1' ) # 从项目的配置文件读取Celery配置信息 app.config_from_object('django.conf:settings') # 从指定的文件(例如celery_config.py)中读取Celery配置信息 # app.config_from_object('celery_config') # 让Celery自动从参数指定的应用中发现异步任务/定时任务 app.autodiscover_tasks(['common', ]) # 让Celery自动从所有注册的应用中发现异步任务/定时任务 # app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) ``` 4. 启动Celery创建woker(消息的消费者)。 ```Shell celery -A worker -l debug & ``` 5. 执行异步任务。 ```Python @app.task def send_email(from, to, cc, subject, content): pass send_email.delay('', [], [], '', '') ``` 6. 创建定时任务。 ```Python # 配置定时任务(计划任务) app.conf.update( timezone=settings.TIME_ZONE, enable_utc=True, # 定时任务(计划任务)相当于是消息的生产者 # 如果只有生产者没有消费者那么消息就会在消息队列中积压 # 将来实际部署项目的时候生产者、消费者、消息队列可能都是不同节点 beat_schedule={ 'task1': { 'task': 'common.tasks.show_msg', 'schedule': crontab(), 'args': ('刘强东,奶茶妹妹喊你回家喝奶啦', ) }, }, ) ``` ```Python @app.task def show_msg(content): print(content) ``` 7. 启动Celery创建执行定时任务的beat(消息的生产者)。 ```Shell celery -A beat -l info ``` 8. 检查消息队列状况。 ```Shell rabbitmqctl list_queues -p vhost1 ``` 9. 监控Celery - 可以通过flower来对Celery进行监控。 ```Shell pip install flower celery flower --broker=amqp://luohao:123456@120.77.222.217:5672/vhost1 ``` ### 其他问题 问题1:如何解决JavaScript跨域获取数据的问题?(django-cors-headers) ```Python INSTALLED_APPS = [ 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', ] CORS_ORIGIN_ALLOW_ALL = True # 配置跨域白名单 # CORS_ORIGIN_WHITELIST = ('www.abc.com', 'www.baidu.com') # CORS_ORIGIN_REGEX_WHITELIST = ('...', ) # CORS_ALLOW_CREDENTIALS = True # CORS_ALLOW_METHODS = ('GET', 'POST', 'PUT', 'DELETE') ``` 问题2:网站图片(水印、剪裁)和视频(截图、水印、转码)是如何处理的?(云存储、FFmpeg) 问题3:网站如何架设(静态资源)文件系统?(FastDFS、云存储、CDN) ### 安全保护 问题1:什么是跨站脚本攻击,如何防范?(对提交的内容进行消毒) 问题2:什么是跨站身份伪造,如何防范?(使用随机令牌) 问题3:什么是SQL注射攻击,如何防范?(不拼接SQL语句,避免使用单引号) 问题4:什么是点击劫持攻击,如何防范?(不允许`