diff --git a/Day91-100/Django知识点概述.md b/Day91-100/Django知识点概述.md new file mode 100644 index 0000000..cd2cf2d --- /dev/null +++ b/Day91-100/Django知识点概述.md @@ -0,0 +1,1709 @@ +## Django知识点概述 + +### Web应用 + +![](./res/web-application.png) + +问题1:描述一个Web应用的工作流程。(如上图所示) + +问题2:描述项目的物理架构。(上图中补充反向代理服务器、负载均衡服务器、数据库服务器、文件服务器、缓存服务器、防火墙等,每个节点都有可能是多节点构成的集群) + +问题3:描述Django项目的工作流程。(如下图所示) + +![](./res/django-flowchart.png) + +### MVC架构模式 + +![](./res/mvc.png) + +问题1:为什么要使用MVC架构模式?(模型和视图解耦合) + +问题2:MVC架构中每个部分的作用?(如上图所示) + +### HTTP请求和响应 + +#### 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` + - `GET` / `POST` / `FILES` + - `get_signed_cookie()` + - `is_ajax()` + - `body` / `content_type` / `encoding` +2. 中间件添加的属性: + - `session` / `user` / `site` +3. `HttpResponse`对象的属性和方法: + - `set_cookie()` / `set_signed_cookie()` / `delete_cookie()` + - `__setitem__` / `__getitem__` / `__delitem__` + - `charset` / `content` / `status_code` +4. `JsonResponse`(`HttpResponse`的子类型)对象 + + ```Python + + class HouseJsonEncoder(JsonEncoder): + + def default(self, o): + # 定义如何将对象转成dict类型并返回这个字典 + pass + ``` + + ```Python + + >>> from django.http import JsonResponse + >>> response = JsonResponse({'foo': 'bar'}) + >>> response.content + + >>> response = JsonResponse([1, 2, 3], safe=False) + >>> response = JsonResponse(house, encoder=HouseJsonEncoder) + + >>> response = HttpResponse('') + >>> response['cotent-type'] = 'application/pdf'; + >>> response['content-disposition'] = 'inline; filename="xyz.pdf"' + >>> response['content-disposition'] = 'attachment; filename="xyz.pdf"' + >>> response.set_signed_cookie('', '', salt='') + >>> response.status_code = 200 + ``` + +### 数据模型(Model) + +问题1:关系型数据库表的设计应该注意哪些问题?(范式理论) + +问题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) + ``` + - 其他属性: + - `to_field` / `limit_choices_to` / `swappable` + +3. `Model`的属性和方法 + + - `objects`/ `pk` + - `save()` / `delete()` + - `from_db()` / `get_XXX_display()` / `clean()` / `full_clean()` + +4. `QuerySet`的方法 + + - `get()` / `all()` / `values()` + + - `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=() + >> 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`对象 + + ```Python + + >>> from django.db.models import Q + >>> Emp.objects.filter( + ... Q(name__startswith='张'), + ... Q(sal__lte=5000) | Q(comm__gte=1000) + ... ) # 查询名字以“张”开头且工资小于等于5000或补贴大于等于1000的员工 + ]> + ``` + + ```Python + + reporter = Reporters.objects.filter(name='Tintin') + reporter.update(stories_filed=F('stories_filed') + 1) + ``` + +6. 原生SQL查询 + + ```Python + + from django.db import connection + + with connection.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() + ``` + +### 视图函数(Controller) + +#### 如何设计视图函数 + +1. 用户的每个操作对应一个视图函数。 + +2. 每个视图函数构成一个事务边界。 + + - 事务的概念。 + + - 事务的ACID特性。 + + - 事务隔离级别。 + + Read Uncommitted < Read Committed < Repeatable Read < Serializable + + ```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, 'foo.html', {'foo': 'bar'}) + ``` + +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 - Vue.js + +#### 其他视图 + +1. MIME类型。 + + | 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. 如何处置生成的内容。 + + ```Python + + response['content-type'] = 'application/pdf' + response['content-disposition'] = 'attachment; filename="xyz.pdf"' + ``` + > 提醒: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()) + ``` + + - 大文件的流式处理:`StreamingHttpResponse`。 + + ```Python + + class EchoBuffer(object): + + def write(self, value): + return value + + + def some_streaming_csv_view(request): + rows = (["Row {}".format(idx), str(idx)] for idx in range(65536)) + writer = csv.writer(EchoBuffer()) + resp = StreamingHttpResponse((writer.writerow(row) for row in rows), + content_type="text/csv") + resp['Content-Disposition'] = 'attachment; filename="data.csv"' + return resp + ``` + + - 生成PDF:需要安装`reportlab`。 + + - 生成Excel:需要安装`openpyxl`。 + +4. 统计图表。 + + - [ECharts](http://echarts.baidu.com/)或[Chart.js](https://www.chartjs.org/)。 + - 思路:后端只提供JSON格式的数据,前端JavaScript渲染生成图表。 + +### 中间件 + +问题1:中间件背后的设计理念是什么?(分离横切关注功能/拦截过滤器模式) + +问题2:中间件有哪些不同的实现方式?(参考下面的代码) + +问题4:描述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): + + response = get_response(request) + + 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 + + def process_view(self, request, view_func, view_args, view_kwargs): + response = view_func(*view_args, **view_kwargs) + 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 + - SECURE_HSTS_INCLUDE_SUBDOMAINS + - SECURE_CONTENT_TYPE_NOSNIFF + - SECURE_BROWSER_XSS_FILTER + - SECURE_SSL_REDIRECT + - SECURE_REDIRECT_EXEMPT +3. SessionMiddleware +4. CsrfViewMiddleware - 防范跨栈身份伪造。 +5. XFrameOptionsMiddleware - 防范点击劫持攻击 + +![](./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:表单上传文件时应该注意哪些问题?(表单的设置、多文件上传、图片预览、Ajax上传文件、上传后的文件如何存储) + +### Cookie和Session + +问题1:使用Cookie能解决什么问题?(用户跟踪,解决HTTP协议无状态问题) + +1. URL重写 +2. 隐藏域(隐式表单域) +3. Cookie + +问题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 = 'default' + ``` + + - 基于文件(基本不考虑) + + - 基于Cookie(不靠谱) + + ```Python + + SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' + ``` + +3. Cookie相关的配置。 + + ```Python + + SESSION_COOKIE_NAME = 'djang_session_id' + SESSION_COOKIE_AGE = 1209600 + 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()` + +5. session的序列化。 + + ```Python + + SESSION_SERIALIZER = django.contrib.sessions.serializers.JSONSerializer + ``` + + - JSONSerializer(默认)- 如果想将自定义的对象放到session中,会遇到“Object of type 'XXX' is not JSON serializable”的问题。 + + - PickleSerializer(1.6以前的默认,但是因为安全问题不推荐使用,但是只要不去反序列化用户构造的恶意的Payload就行了。关于这种方式的安全漏洞,请参考《[Python Pickle的任意代码执行漏洞实践和Payload构造》](http://www.polaris-lab.com/index.php/archives/178/)。) + + +### 缓存 + +#### 配置缓存 + + +```Python + +CACHES = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': [ + 'redis://120.77.222.217:6379/0', + 'redis://120.77.222.217:6380/0', + 'redis://120.77.222.217:6381/0', + # 'redis://120.77.222.217:6382/0', + ], + # 'KEY_PREFIX': 'fang', + # 'TIMEOUT': 120, + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + 'CONNECTION_POOL_KWARGS': { + 'max_connections': 100, + }, + 'PASSWORD': '1qaz2wsx', + # 'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor' + } + }, +} +``` +#### 全站缓存 + +```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 + + +@cache_page(60 * 15) +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['myalias'] + >>> cache2 = caches['myalias'] + >>> cache1 is cache2 + True + ``` + +### 日志 + +#### 日志级别 + +NOTSET < DEBUG < INFO < WARNING < ERROR < FETAL + +#### 日志配置 + +```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', + } + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'level': 'DEBUG', + }, + 'inf': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'filename': 'info.log', + 'when': 'W0', + 'backupCount': 12, + 'formatter': 'simple', + 'level': 'INFO', + }, + 'err': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'filename': 'error.log', + 'when': 'D', + 'backupCount': 31, + 'formatter': 'verbose', + 'level': 'WARNING', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['console'], + 'level': 'DEBUG', + }, + 'inform': { + 'handlers': ['inf'], + 'level': 'DEBUG', + 'propagate': True, + }, + 'error': { + 'handlers': ['err'], + 'level': 'DEBUG', + 'propagate': True, + } + } +} +``` + +[日志配置官方示例](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 + +### 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)。 + +#### 修改配置文件 + +```Python + +INSTALLED_APPS = [ + + 'rest_framework', + +] +``` + +#### 编写序列化器 + +```Python + +from rest_framework import serializers + + +class ProvinceSerializer(serializers.ModelSerializer): + + class Meta: + model = Province + fields = ('prov_id', 'prov_name') +``` + +#### 最怂的做法 + +```Python + +@csrf_exempt +def list_provinces(request): + if request.method == 'GET': + serializer = ProvinceSerializer(Province.objects.all(), many=True) + elif request.method = 'POST': + serializer = ProvinceSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return HttpResponse(json.dumps(serializer.data), + content_type="application/json; charset=utf-8") +``` + +#### 使用装饰器 + +```Python + +@api_view(['GET', 'POST']) +def list_provinces(request): + if request.method == 'GET': + serializer = ProvinceSerializer(Province.objects.all(), many=True) + return Response(serializer.data) + elif request.method == 'POST': + serializer = ProvinceSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +def list_cities_by_prov(request, prov_id): + serializer = CitySerializer(City.objects.filter(prov__prov_id=prov_id), many=True) + return Response(serializer.data) + +``` + +```Python + +urlpatterns = [ + path('provinces/', views.list_provinces), + path('provinces/', views.list_cities_by_prov), +] +``` + +问题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 + + + + +``` + +#### 使用类视图 + +更好的复用代码,不要重“复发明轮子”。 + +```Python + +class CityView(APIView): + + def get(self, request, pk, format=None): + try: + serializer = CitySerializer(City.objects.get(pk=pk)) + return Response(serializer.data) + except City.DoesNotExist: + raise Http404 + + def put(self, request, pk, format=None): + try: + city = City.objects.get(pk=pk) + serializer = CitySerializer(city, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + except City.DoesNotExist: + raise Http404 + + def delete(self, request, pk, format=None): + try: + city = City.objects.get(pk=pk) + city.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + except City.DoesNotExist: + raise Http404 +``` + +```Python + +urlpatterns = [ + path('cities/', views.CityView.as_view()), +] +``` + +#### 使用ViewSet + +```Python + +class DistrictViewSet(viewsets.ReadOnlyModelViewSet): + + queryset = District.objects.all() + serializer_class = DistrictSerializer +``` + +```Python + +class DistrictViewSet(viewsets.ModelViewSet): + + queryset = District.objects.all() + serializer_class = DistrictSerializer +``` + +```Python + +router = routers.DefaultRouter() +router.register('districts', views.DistrictViewSet) + +urlpatterns += router.urls +``` + +#### 认证用户身份 + +1. 利用Django自带的User。 + +2. 自行对用户及其身份验证的摘要进行处理。 + + ```Python + + sha1_proto = hashlib.sha1() + + + def check_user_sign(get_response): + + def middleware(request): + if request.path.startswith('/api'): + data = request.GET if request.method == 'GET' else request.data + try: + user_id = data['user_id'] + user_token = cache.get(f'fang:user:{user_id}') + user_sign = data['user_sign'] + hasher = sha1_proto.copy() + hasher.update(f'{user_id}:{user_token}'.encode('utf-8')) + if hasher.hexdigest() == user_sign: + return get_response(request) + except KeyError: + pass + return JsonResponse({'msg': '身份验证失败拒绝访问'}) + else: + return get_response(request) + + return middleware + ``` + +3. 请求的时效性问题。(请求中再加上一个随机的令牌) + +### 其他问题 + +问题1:如何设计一套权限系统?(RBAC或ACL) + +1. RBAC - 基于角色的访问控制(用户-角色-权限,都是多对多关系)。 +2. ACL - 访问控制列表(每个用户绑定自己的访问白名单)。 + +问题2:如何实现异步任务和定时任务?(Celery) + +问题3:如何解决JavaScript跨域获取数据的问题?(django-cors-headers) + +问题4:网站图片(水印、剪裁)和视频(截图、水印、转码)是如何处理的?(云存储、FFmpeg) + +问题5:网站如何架设(静态资源)文件系统? + +#### Celery的应用 + +Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。它是一个专注于实时处理的任务队列,同时也支持任务调度。 + +推荐阅读:[《Celery官方文档中文版》](http://docs.jinkan.org/docs/celery/),上面有极为详细的配置和使用指南。 + +![](./res/celery.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 jackfrued 123456 + rabbitmqctl set_user_tags jackfrued administrator + rabbitmqctl add_vhost myvhost + rabbitmqctl set_permissions -p myvhost jackfrued ".*" ".*" ".*" + ``` + +3. 创建Celery实例。 + + ```Python + + project_name = 'fang' + project_settings = '%s.settings' % project_name + + # 注册环境变量 + os.environ.setdefault('DJANGO_SETTINGS_MODULE', project_settings) + + app = celery.Celery( + project_name, + backend='amqp://jackfrued:123456@120.77.222.217:5672/myvhost', + broker='amqp://jackfrued:123456@120.77.222.217:5672/myvhost' + ) + + # 从默认的配置文件读取配置信息 + app.config_from_object('django.conf:settings') + + # Celery加载所有注册的应用 + app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) + ``` + +4. 启动Celery创建woker。 + + ```Shell + + celery -A fang worker -l info & + ``` + +5. 执行异步任务。 + + ```Python + + @app.task + def send_email(from, to, cc, subject, content): + pass + + + async_result = send_email.delay('', [], [], '', '') + async_result.get() + ``` + +6. 创建定时任务。 + + ```Python + + from celery.schedules import crontab + from celery.task import periodic_task + + + @periodic_task(run_every=crontab('*', '12,18')) + def print_dummy_info(): + print('你妈喊你回家吃饭啦') + ``` + +7. 检查定时任务并交给worker执行。 + + ```Shell + + celery -A fang beat -l info + ``` + +8. 检查消息队列状况。 + + ```Shell + + rabbitmqctl list_queues -p myvhost + ``` + +### 安全保护 + +问题1:什么是跨站脚本攻击,如何防范?(对提交的内容进行消毒) + +问题2:什么是跨站身份伪造,如何防范?(使用随机令牌) + +问题3:什么是SQL注射攻击,如何防范?(不拼接SQL语句,避免使用单引号) + +问题4:什么是点击劫持攻击,如何防范?(不允许`