Commit 2d08d42f authored by jackfrued's avatar jackfrued

更新了团队项目相关内容

parent 161af9c8
## 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 <appname>
python manage.py migrate
python manage.py inspectdb > <appname>/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=()
<QuerySet [{'dept': 10, 'total': 4}, {'dept': 20, 'total': 7}, {'dept': 30, 'total': 3}]
```
- `first()` / `last()`
> 说明:调用`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,)
<QuerySet [<Emp: Emp object (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,)
<QuerySet [<Emp: Emp object (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')
<RawQuerySet: 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的员工
<QuerySet [<Emp: 张三丰>]>
```
```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. [每个视图函数可以构成一个事务边界](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/(?P<mobile>1[3-9]\d{9})'),
path('api/code/<str:mobile>'),
```
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 - 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())
```
- 大文件的流式处理:`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`;生成Excel可以使用`openpyxl`或`xlrd`。
- [ECharts](http://echarts.baidu.com/)或[Chart.js](https://www.chartjs.org/)。
- 思路:后端只提供JSON格式的数据,前端JavaScript渲染生成图表。
### 中间件
问题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):
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 - 强制使用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:表单上传文件时应该注意哪些问题?(表单的设置、多文件上传、图片预览、Ajax上传文件、上传后的文件如何存储)
### Cookie和Session
问题1:使用Cookie能解决什么问题?(用户跟踪,解决HTTP协议无状态问题)
1. URL重写
```
http://www.abc.com/path/resource?foo=bar
```
2. 隐藏域(隐式表单域)
```HTML
<form action="" method="post">
<input type="hidden" name="foo" value="bar">
</form>
```
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 = 'session'
```
- 基于文件(基本不考虑)
- 基于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()` - 测试浏览器是否支持Cookie(提示用户如果浏览器禁用Cookie可能会影响网站的使用)
5. session的序列化。
```Python
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
```
- JSONSerializer(1.6及以后默认)- 如果想将自定义的对象放到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语言实现》上关于这个问题的讲解。
- 说明:如果使用了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': 'fang',
'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': 'fang: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': 'fang:session',
'TIMEOUT': 1209600,
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'CONNECTION_POOL_KWARGS': {
'max_connections': 2000,
},
'PASSWORD': '1qaz2wsx',
}
},
# 验证码缓存
'code': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': [
'redis://1.2.3.4:6379/3',
],
'KEY_PREFIX': 'fang:code:tel',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'CONNECTION_POOL_KWARGS': {
'max_connections': 500,
},
'PASSWORD': '1qaz2wsx',
}
},
}
```
#### 全站缓存
```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': 'fang.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:日志可视化工具。
### 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': 5,
# 配置默认的分页类
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
}
```
#### 编写序列化器
```Python
from rest_framework import serializers
class HouseTypeSerializer(serializers.ModelSerializer):
class Meta:
model = HouseType
fields = '__all__'
class DistrictSerializer(serializers.ModelSerializer):
class Meta:
model = District
fields = ('distid', 'name', 'intro')
class AgentSerializer(serializers.ModelSerializer):
# 如果属性需要通过代码来获取就定义为SerializerMethodField
# 获取estates属性的方法应该命名为get_estates
estates = serializers.SerializerMethodField()
@staticmethod
def get_estates(agent):
ret_value = []
# 对于多对一外键关联(ForeignKey)可以用select_related提前加载关联属性
# 通过这种方式可以使用内连接或左外连接直接获取关联属性避免1+N查询问题
items = agent.estates.all().select_related('district')
for item in items:
ret_value.append({"estateid": item.estateid,
"name": item.name,
"district": DistrictSerializer(item.district).data})
return ret_value
class Meta:
model = Agent
fields = ('agentid', 'name', 'tel', 'certificated', 'estates')
```
#### 方法1:使用装饰器
```Python
@api_view(['GET'])
@cache_page(timeout=None)
def provinces(request):
query_set = District.objects.filter(parent__isnull=True)
serializer = DistrictSerializer(query_set, many=True)
return JsonResponse(serializer.data, safe=False)
@api_view(['GET'])
@cache_page(timeout=120)
def districts(request, pid):
query_set = District.objects.filter(parent__distid=pid)
serializer = DistrictSerializer(query_set, many=True)
return JsonResponse(serializer.data, safe=False)
```
```Python
urlpatterns = [
path('districts/', views.provinces, name='districts'),
path('districts/<int:pid>', views.districts, name='district'),
]
```
> 说明:上面使用了Django自带的视图装饰器(@cache_page)来实现对API接口返回数据的缓存。
#### 方法2:使用APIView及其子类
更好的复用代码,不要重“复发明轮子”。
```Python
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from rest_framework_extensions.cache.decorators import cache_response
def customize_cache_key(view_instance, view_method, request, args, kwargs):
"""自定义缓存的key的函数"""
full_path = request.get_full_path()
return f'fangall:api:{full_path}'
class AgentDetailView(ListAPIView):
queryset = Agent.objects.all()
serializer_class = AgentDetailSerializer
pagination_class = None
@cache_response(key_func=customize_cache_key)
def get(self, request, agentid, *args, **kwargs):
query_set = Agent.objects.filter(agentid=agentid)\
.prefetch_related("estates").last()
serializer = AgentDetailSerializer(query_set)
return Response(serializer.data)
```
```Python
urlpatterns = [
path('agents/<int:agentid>', views.AgentDetailView.as_view(), name='agent'),
]
```
> 说明:上面使用了drf_extensions提供的@cache_response实现了对接口数据的缓存,并使用自定义的函数来生成缓存中的key。
#### 方法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
```
> 说明:上面使用了drf_extensions提供的CacheResponseMixin混入类实现了对接口数据的缓存。
drf提供了基于Bootstrap定制的页面来显示接口返回的JSON数据,当然也可以使用[POSTMAN](https://www.getpostman.com/)这样的工具对API接口进行测试。
#### 补充说明
在这里顺便提一下跟前端相关的几个问题。
问题1:如何让浏览器能够发起DELETE/PUT/PATCH?
```HTML
<form method="post">
<input type="hidden" name="_method" value="delete">
</form>
```
```Python
if request.method == 'POST' and '_method' in request.POST:
request.method = request.POST['_method'].upper()
```
```HTML
<script>
$.ajax({
'url': '/api/provinces',
'type': 'put',
'data': {},
'dataType': 'json',
'success': function(json) {
// Web = 标签(内容) + CSS(显示) + JS(行为)
// JavaScript = ES + BOM + DOM
// DOM操作实现页面的局部刷新
},
'error': function() {}
});
$.getJSON('/api/provinces', function(json) {
// DOM操作实现页面的局部刷新
});
</script>
```
问题2:如何解决多个JavaScript库之间某个定义(如$函数)冲突的问题?
```HTML
<script src="js/jquery.min.js"></script>
<script src="js/abc.min.js"></script>
<script>
// $已经被后加载的JavaScript库占用了
// 但是可以直接用绑定在window对象上的jQuery去代替$
jQuery(function() {
jQuery('#okBtn').on('click', function() {});
});
</script>
```
```HTML
<script src="js/abc.min.js"></script>
<script src="js/jquery.min.js"></script>
<script>
// 将$让出给其他的JavaScript库使用
jQuery.noConflict();
jQuery(function() {
jQuery('#okBtn').on('click', function() {});
});
</script>
```
问题3:jQuery对象与原生DOM对象之间如何转换?
```HTML
<button id="okBtn">点我</button>
<script src="js/jquery.min.js"></script>
<script>
var btn = document.getElementById('okBtn'); // 原生JavaScript对象
// $(btn) --> jQuery --> 拥有更多的属性和方法而且没有浏览器兼容性问题
var $btn = $('#okBtn'); // jQuery对象
// $btn[0] / $btn.get(0) --> JavaScript --> 自己处理浏览器兼容性问题
$btn.on('click', function() {});
</script>
```
#### 身份认证
查看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 Authentication(BaseAuthentication):
def authenticate(self, request):
try:
userid = request.GET['userid']
token = request.GET['token']
user = User.objects.filter(userid=userid, token=token).first()
if not user:
raise AuthenticationFailed('用户身份信息认证失败')
return user, user
except KeyError:
raise NotAuthenticated('请提供当前用户身份认证信息')
def authenticate_header(self, request):
pass
```
使用自定义的认证类。
```Python
class AgentDetailView(ListAPIView):
queryset = Agent.objects.all()
serializer_class = AgentDetailSerializer
authentication_classes = [Authentication, ]
pagination_class = None
@cache_response(key_func=customize_cache_key)
def get(self, request, agentid, *args, **kwargs):
query_set = Agent.objects.filter(agentid=agentid)\
.prefetch_related("estates").last()
serializer = AgentDetailSerializer(query_set)
return Response(serializer.data)
```
#### 授予权限
权限检查总是在视图的最开始处运行,在任何其他代码被允许进行之前。最简单的权限是允许通过身份验证的用户访问,并拒绝未经身份验证的用户访问,这对应于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 - 基于角色的访问控制(用户-角色-权限-资源,都是多对多关系)。
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': '100/day',
'user': '1000/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.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
project_name = '...'
project_settings = '%s.settings' % project_name
# 注册环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', project_settings)
app = celery.Celery(
project_name,
broker='amqp://luohao:123456@120.77.222.217:5672/vhost1'
)
# 从默认的配置文件读取配置信息
app.config_from_object('django.conf:settings')
# Celery加载所有注册的应用
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
```
4. 启动Celery创建woker(消息的消费者)。
```Shell
celery -A <projname> 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 <projname> beat -l info
```
8. 检查消息队列状况。
```Shell
rabbitmqctl list_queues -p vhost1
```
### 其他问题
问题1:如何解决JavaScript跨域获取数据的问题?(django-cors-headers)
```Python
INSTALLED_APPS = [
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
]
# 配置跨域白名单
# CORS_ORIGIN_WHITELIST = ()
# 配置是否跨域读取Cookie信息
# CORS_ALLOW_CREDENTIALS = True
```
问题2:网站图片(水印、剪裁)和视频(截图、水印、转码)是如何处理的?(云存储、FFmpeg)
问题3:网站如何架设(静态资源)文件系统?(FastDFS、云存储、CDN)
### 安全保护
问题1:什么是跨站脚本攻击,如何防范?(对提交的内容进行消毒)
问题2:什么是跨站身份伪造,如何防范?(使用随机令牌)
问题3:什么是SQL注射攻击,如何防范?(不拼接SQL语句,避免使用单引号)
问题4:什么是点击劫持攻击,如何防范?(不允许`<iframe>`加载非同源站点内容)
#### Django提供的安全措施
签名数据的API
```Python
>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign('hello, world!')
>>> value
'hello, world!:BYMlgvWMTSPLxC-DqxByleiMVXU'
>>> signer.unsign(value)
'hello, world!'
>>>
>>> signer = Signer(salt='1qaz2wsx')
>>> signer.sign('hello, world!')
'hello, world!:9vEvG6EA05hjMDB5MtUr33nRA_M'
>>>
>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign('hello, world!')
>>> value
'hello, world!:1fpmcQ:STwj464IFE6eUB-_-hyUVF3d2So'
>>> signer.unsign(value, max_age=5)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/Hao/Desktop/fang.com/venv/lib/python3.6/site-packages/django/core/signing.py", line 198, in unsign
'Signature age %s > %s seconds' % (age, max_age))
django.core.signing.SignatureExpired: Signature age 21.020604848861694 > 5 seconds
>>> signer.unsign(value, max_age=120)
'hello, world!'
```
CSRF令牌和小工具
```HTML
{% csrf_token %}
```
- @csrf_exempt:免除令牌
- @csrf_protect:提供令牌保护
- @require_csrf_token:提供令牌保护
- @ensure_csrf_cookie:强制视图发送带令牌的cookie
> 说明:可以在Chrome浏览器中安装EditThisCookie插件来方便的查看Cookie。
#### 用户敏感信息的保护
1. 哈希摘要(签名)
```Python
>>> import hashlib
>>>
>>> md5_hasher = hashlib.md5()
>>> md5_hasher.update('hello, world!'.encode())
>>> md5_hasher.hexdigest()
'3adbbad1791fbae3ec908894c4963870'
>>>
>>> sha1_hasher = hashlib.sha1()
>>> sha1_hasher.update('hello, world!'.encode())
>>> sha1_hasher.update('goodbye, world!'.encode())
>>> sha1_hasher.hexdigest()
'1f09d30c707d53f3d16c530dd73d70a6ce7596a9'
```
2. 加密和解密(对称加密[AES]和非对称加密[RSA])
```Shell
pip install rsa
pip install pycrypto
```
```Python
>>> pub_key, pri_key = rsa.newkeys(1024)
>>> message = 'hello, world!'
>>> crypto = rsa.encrypt(message.encode(), pub_key)
>>> crypto
b'Ou{gH\xa9\xa8}O\xe3\x1d\x052|M\x9d9?\xdc\xd8\xecF\xd3v\x9b\xde\x8e\x12\xe6M\xebvx\x08\x08\x8b\xe8\x86~\xe4^)w\xf2\xef\x9e\x9fOg\x15Q\xb7\x7f\x1d\xcfV\xf1\r\xbe^+\x8a\xbf }\x10\x01\xa4U9b\x97\xf5\xe0\x90T\'\xd4(\x9b\x00\xa5\x92\x17\xad4\xb0\xb0"\xd4\x16\x94*s\xe1r\xb7L\xe2\x98\xb7\x7f\x03\xd9\xf2\t\xee*\xe6\x93\xe6\xe1o\xfd\x18\x83L\x0cfL\xff\xe4\xdd%\xf2\xc0/\xfb'
>>> origin = rsa.decrypt(crypto, pri_key).decode()
>>> origin
'hello, world!'
```
#### 安全相关建议
1. 虽然 Django 自带了稳固的安全保护措施,但是依然要采用正确的方式部署应用程序,利用 Web 服务器、操作系统和其他组件提供的安全保护措施。
2. 记得把 Python 代码放在 Web 服务器的文档根目录之外,避免代码意外泄露。
3. 谨慎处理用户上传的文件。
4. Django本身没有对请求次数加以限制(包括验证用户身份的请求),为了防止暴力攻击和破解,可以考虑使用具有一次消费性的验证码或对这类请求的次数进行限制。
5. 将缓存系统、数据库服务器以及重要的资源服务器都放在第二级防火墙之后(不要放在DMZ)。
### 测试相关
测试是发现和标记缺陷的过程。所谓的缺陷是指实际结果和期望结果之间的任何差别。有的地方,测试也被认为是执行以找出错误为目的的程序的过程。 测试是为了让产品达到以下目标:
1. 满足需求用户满意
2. 改善产品的市场占有率
3. 树立对产品的信任
4. 减少开发和维护的成本
#### 功能测试
如果一个软件单元的行为方式与它的开发规范完全一样,那么该软件单元就通过了它的功能测试。
- 白盒测试:开发人员自己实现,最基本的形式是单元测试,还有集成测试和系统测试。
- 黑盒测试:由开发团队之外的人执行,对测试代码没有可见性,将被测系统视为黑盒子。通常由测试人员或QA工程师来执行,Web应用可以通过Selenium这样的测试框架自动化实施。
#### 性能测试
软件在高工作负载下对其响应性和健壮性展开的测试。
- 负载测试:在特定负载下执行的测试。
- 压力测试:突发条件或极限条件下的性能测试。
#### 安全性测试
系统的敏感数据都是经过认证和授权之后才能访问。
#### 其他测试
易用性测试 / 安装测试 / 可访问性测试
#### 单元测试
测试函数和对象的方法(程序中最小最基本的单元)。通过对实际输出和预期输出的比对以及各种的断言条件来判定被测单元是否满足设计需求。
- 测试用例
- 测试固件 - 每次测试时都要使用的东西。
- 测试套件(测试集)- 组合了多个测试用例而构成的集合。
```Python
class UtilTest(TestCase):
def setUp(self):
self.pattern = re.compile(r'\d{6}')
def test_gen_mobile_code(self):
for _ in range(100):
self.assertIsNotNone(self.pattern.match(gen_mobile_code()))
def test_to_md5_hex(self):
md5_dict = {
'123456': 'e10adc3949ba59abbe56e057f20f883e',
'123123123': 'f5bb0c8de146c67b44babbf4e6584cc0',
'1qaz2wsx': '1c63129ae9db9c60c3e8aa94d3e00495',
}
for key, value in md5_dict.items():
self.assertEqual(value, to_md5_hex(key))
```
`TestCase`的断言方法:
- assertEqual / assertNotEqual
- assertTrue / assertFalse / assertIsNot
- assertRaise / assertRaiseRegexp
- assertAlmostEqual / assertNotAlmostEqual
- assertGreater / assertGreaterEqual / assertLess / assertLessEqual
- assertRegexpMatches / assertNotRegexpMatches
- assertListEqual / assertSetEqual / assertTupleEqual / assertDictEqual
可以使用nose2或pytest来辅助执行单元测试,同时通过cov-core或pytest-cov可以对测试覆度进行评估。覆盖率由百分比表示。比如测试代码执行过了程序的每一行,那么覆盖率就是100%。这种时候,几乎不会出现新程序上线后突然无法运行的尴尬情况。覆盖率不关心代码内容究竟是什么,覆盖率是用来检查“测试代码不足、测试存在疏漏”的一个指标,“测试内容是否妥当”并不归它管。
```Shell
pip install nose2 pytest cov-core pytest-cov
```
可以使用Selenium来实现Web应用的自动化测试,它还可以用于屏幕抓取与浏览器行为模拟,通过爬虫抓取页面上的动态数据也可以使用它。Selenium其实包括三个部分:
- Selenium IDE:嵌入到浏览器的插件,可以录制和回放脚本。
![](./res/selenium_ide.png)
- Selenium WebDriver:支持多种语言可以操控浏览器的API。
- Selenium Standalone Server:Selenium Grid、远程控制、分布式部署。
```Shell
pip install selenium
```
```Python
from selenium import webdriver
import pytest
import contextlib
@pytest.fixture(scope='session')
def chrome():
# 设置使用无头浏览器(不会打开浏览器窗口)
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
yield driver
driver.quit()
def test_baidu_index(chrome):
chrome.get('https://www.baidu.com')
assert chrome.title == '百度一下,你就知道'
```
```Shell
nose2 -v -C
pytest --cov
```
```Shell
Ran 7 tests in 0.002s
OK
Name Stmts Miss Cover
----------------------------------------------
example01.py 15 0 100%
example02.py 49 49 0%
example03.py 22 22 0%
example04.py 61 61 0%
example05.py 29 29 0%
example06.py 39 39 0%
example07.py 19 19 0%
example08.py 27 27 0%
example09.py 18 18 0%
example10.py 19 19 0%
example11.py 22 22 0%
example12.py 28 28 0%
example13.py 28 28 0%
test_ddt_example.py 18 0 100%
test_pytest_example.py 11 6 45%
test_unittest_example.py 22 0 100%
----------------------------------------------
TOTAL 427 367 14%
```
在测试过程中需要孤立各种外部依赖(数据库、外部接口调用、时间依赖),具体又包括两个方面:
1. 数据源:数据本地化 / 置于内存中 / 测试之后回滚
2. 资源虚拟化:存根/桩(stub)、仿制/模拟(mock)、伪造(fake)
- stub:测试期间为提供响应的函数生成的替代品
- mock:代替实际对象(以及该对象的API)的对象
- fake:没有达到生产级别的轻量级对象
#### 集成测试
集成多个函数或方法的输入输出的测试,测试时需要将多个测试对象组合在一起。
- 测试组件互操作性 / 需求变更测试 / 外部依赖和API / 调试硬件问题 / 在代码路径中发现异常
#### 系统测试
对需求的测试,测试成品是否最终满足了所有需求,在客户验收项目时进行。
#### 数据驱动测试
使用外部数据源实现对输入值与期望值的参数化,避免在测试中使用硬编码的数据。
被测函数:
```Python
def add(x, y):
return x + y
```
data.csv文件:
```
3,1,2
0,1,-1
100,50,50
100,1,99
15,7,8
```
测试代码:
```Python
import csv
from unittest import TestCase
from ddt import ddt, data, unpack
@ddt
class TestAdd(TestCase):
def load_data_from_csv(filename):
data_items = []
with open(filename, 'r', newline='') as fs:
reader = csv.reader(fs)
for row in reader:
data_items.append(list(map(int, row)))
return data_items
@data(*load_data_from_csv('data.csv'))
@unpack
def test_add(self, result, param1, param2):
self.assertEqual(result, add(param1, param2))
```
#### Django中的测试
1. 测试Django视图 - Django中提供的`TestCase`扩展了`unittest`中的`TestCase`,绑定了一个名为`client`的属性,可以用来模拟浏览器发出的GET、POST、DELETE、PUT等请求。
```Python
class SomeViewTest(TestCase):
def test_example_view(self):
resp = self.client.get(reverse('index'))
self.assertEqual(200, resp.status_code)
self.assertEqual(5, resp.context['num'])
```
2. 运行测试 - 配置测试数据库。
```Python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'localhost',
'PORT': 3306,
'NAME': 'DbName',
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASS'],
'TEST': {
'NAME': 'DbName_for_testing',
'CHARSET': 'utf8',
},
}
}
```
```Shell
python manage.py test
python manage.py test common
python manage.py test common.tests.UtilsTest
python manage.py test common.tests.UtilsTest.test_to_md5_hex
```
3. 评估测试覆盖度
```Shell
pip install coverage
coverage run --source=<path1> --omit=<path2> manage.py test common
coverage report
Name Stmts Miss Cover
---------------------------------------------------
common/__init__.py 0 0 100%
common/admin.py 1 0 100%
common/apps.py 3 3 0%
common/forms.py 16 16 0%
common/helper.py 32 32 0%
common/middlewares.py 19 19 0%
common/migrations/__init__.py 0 0 100%
common/models.py 71 2 97%
common/serializers.py 14 14 0%
common/tests.py 14 8 43%
common/urls_api.py 3 3 0%
common/urls_user.py 3 3 0%
common/utils.py 22 7 68%
common/views.py 69 69 0%
---------------------------------------------------
TOTAL 267 176 34%
```
#### 性能测试
问题1:性能测试的指标有哪些?
1. ab - Apache Benchmark / webbench / httpperf
```Shell
yum -y install httpd
ab -c 10 -n 1000 http://www.baidu.com/
...
Benchmarking www.baidu.com (be patient).....done
Server Software: BWS/1.1
Server Hostname: www.baidu.com
Server Port: 80
Document Path: /
Document Length: 118005 bytes
Concurrency Level: 10
Time taken for tests: 0.397 seconds
Complete requests: 100
Failed requests: 98
(Connect: 0, Receive: 0, Length: 98, Exceptions: 0)
Write errors: 0
Total transferred: 11918306 bytes
HTML transferred: 11823480 bytes
Requests per second: 252.05 [#/sec] (mean)
Time per request: 39.675 [ms] (mean)
Time per request: 3.967 [ms] (mean, across all concurrent requests)
Transfer rate: 29335.93 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 7 0.6 7 9
Processing: 20 27 22.7 24 250
Waiting: 8 11 21.7 9 226
Total: 26 34 22.8 32 258
Percentage of the requests served within a certain time (ms)
50% 32
66% 34
75% 34
80% 34
90% 36
95% 39
98% 51
99% 258
100% 258 (longest request)
```
2. mysqlslap
```Shell
mysqlslap -a -c 100 -h 1.2.3.4 -u root -p
mysqlslap -a -c 100 --number-of-queries=1000 --auto-generate-sql-load-type=read -h <负载均衡服务器IP地址> -u root -p
mysqlslap -a --concurrency=50,100 --number-of-queries=1000 --debug-info --auto-generate-sql-load-type=mixed -h 1.2.3.4 -u root -p
```
3. sysbench
```Shell
sysbench --test=threads --num-threads=64 --thread-yields=100 --thread-locks=2 run
sysbench --test=memory --num-threads=512 --memory-block-size=256M --memory-total-size=32G run
```
4. jmeter
请查看[《使用JMeter进行性能测试》](https://www.ibm.com/developerworks/cn/java/l-jmeter/index.html)。
5. LoadRunner / QTP
### 项目调试
可以使用django-debug-toolbar来辅助项目调试。
1. 安装
```Shell
pip install django-debug-toolbar
```
2. 配置 - 修改settings.py。
```Python
INSTALLED_APPS = [
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
DEBUG_TOOLBAR_CONFIG = {
# 引入jQuery库
'JQUERY_URL': 'https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js',
# 工具栏是否折叠
'SHOW_COLLAPSED': True,
# 是否显示工具栏
'SHOW_TOOLBAR_CALLBACK': lambda x: True,
}
```
3. 配置 - 修改urls.py。
```Python
if settings.DEBUG:
import debug_toolbar
urlpatterns.insert(0, path('__debug__/', include(debug_toolbar.urls)))
```
4. 使用 - 在页面右侧可以看到一个调试工具栏,上面包括了执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等调试信息,查看起来非常的方便。
### 部署相关
请参考《Django项目上线指南》。
### 性能相关
#### 网站优化两大定律:
1. 尽可能的使用缓存 - 牺牲空间换取时间(普适策略)。
2. 能推迟的都推迟 - 使用消息队列将并行任务串行来缓解服务器压力。
- 服务器CPU利用率出现瞬时峰值 - 削峰(CPU利用率平缓的增长)
- 上下游节点解耦合(下订单和受理订单的系统通常是分离的)
#### Django框架
1. 配置缓存来缓解数据库的压力,并有合理的机制应对[缓存穿透和缓存雪崩](https://www.cnblogs.com/zhangweizhong/p/6258797.html)。
2. 开启[模板缓存](https://docs.djangoproject.com/en/2.0/ref/templates/api/#django.template.loaders.cached.Loader)来加速模板的渲染。
```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',
],
'loaders': [(
'django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
], ),
],
},
},
]
```
3. 用惰性求值、迭代器、`defer()`、`only()`等缓解内存压力。
4. 用`select_related()`和`prefetch_related()`执行预加载避免“1+N查询问题”。
#### 数据库
1. 用ID生成器代替自增主键(性能更好、适用于分布式环境)。
- 自定义ID生成器
- UUID
```Python
>>> my_uuid = uuid.uuid1()
>>> my_uuid
UUID('63f859d0-a03a-11e8-b0ad-60f81da8d840')
>>> my_uuid.hex
'63f859d0a03a11e8b0ad60f81da8d840'
```
2. 避免不必要的外键列上的约束(除非必须保证参照完整性),更不要使用触发器之类的机制。
3. 使用索引来优化查询性能(索引放在要用于查询的字段上)。InnoDB用的是BTREE索引,使用>、<、>=、<=、BETWEEN或者LIKE 'pattern'(pattern不以通配符开头)时都可以用到索引。因为建立索引需要额外的磁盘空间,而主键上是有默认的索引,所以主键要尽可能选择较短的数据类型来减少磁盘占用,提高索引的缓存效果。
```SQL
create index idx_goods_name on tb_goods (gname(10));
```
```SQL
-- 无法使用索引
select * from tb_goods where gname like '%iPhone%';
-- 可以使用索引
select * from tb_goods where gname like 'iPhone%';
```
```Python
# 无法使用索引
Goods.objects.filter(name_icontains='iPhone')
# 可以使用索引
Goods.objects.filter(name__istartswith='iPhone');
```
4. 使用存储过程(存储在服务器端编译过的一组SQL语句)。
```SQL
drop procedure if exists sp_avg_sal_by_dept;
create procedure sp_avg_sal_by_dept(deptno integer, out avg_sal float)
begin
select avg(sal) into avg_sal from TbEmp where dno=deptno;
end;
call sp_avg_sal_by_dept(10, @a);
select @a;
```
```Python
>>> from django.db import connection
>>> cursor = connection.cursor()
>>> cursor.callproc('sp_avg_sal_by_dept', (10, 0))
>>> cursor.execute('select @_sp_avg_sal_by_dept_1')
>>> cursor.fetchone()
(2675.0,)
```
5. 使用数据分区。通过分区可以存储更多的数据、优化查询更大的吞吐量、可以快速删除过期的数据。关于这个知识点可以看看MySQL的[官方文档](https://dev.mysql.com/doc/refman/5.7/en/partitioning-overview.html)。
- RANGE分区:基于连续区间范围,把数据分配到不同的分区。
- LIST分区:基于枚举值的范围,把数据分配到不同的分区。
- HASH分区 / KEY分区:基于分区个数,把数据分配到不同的分区。
```SQL
CREATE TABLE tb_emp (
eno INT NOT NULL,
ename VARCHAR(20) NOT NULL,
job VARCHAR(10) NOT NULL,
hiredate DATE NOT NULL,
dno INT NOT NULL
)
PARTITION BY HASH(dno)
PARTITIONS 4;
```
```SQL
CREATE TABLE tb_emp (
eno INT NOT NULL,
ename VARCHAR(20) NOT NULL,
job VARCHAR(10) NOT NULL,
hiredate DATE NOT NULL,
dno INT NOT NULL
)
PARTITION BY RANGE( YEAR(hiredate) ) (
PARTITION p0 VALUES LESS THAN (1960),
PARTITION p1 VALUES LESS THAN (1970),
PARTITION p2 VALUES LESS THAN (1980),
PARTITION p3 VALUES LESS THAN (1990),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
```
6. 使用`explain`来分析查询性能 - 执行计划。
```SQL
explain select * from ...;
```
`explain`结果解析:
- select_type:表示select操作的类型,常见的值有SIMPLE(简单查询,没有使用子查询或者表连接查询)、PRIMARY(主查询,外层的查询)、UNION(并集操作中的第二个或者后面的查询)、SUBQUERY(子查询中的第一个SELECT)等。
- table:输出结果的表。
- type:MySQL在表中找到所需行的方式,也称为访问类型,常见的值有:
- ALL:全表扫描(遍历全表找到匹配的行)
- index:索引全扫描(遍历整个索引)
- range:索引范围扫描
- ref:非唯一索引扫描或唯一索引的前缀扫描
- eq_ref:唯一索引扫描
- const / system:表中最多有一行匹配
- NULL:不用访问表或者索引
- possible_keys:查询时可能用到的索引。
- key:实际使用的索引。
- key_len:使用到索引字段的长度。
- rows:扫描行的数量。
- Extra:额外的信息(执行情况的说明或描述)。
> 说明:关于MySQL更多的知识尤其是性能调优和运维方面的内容,推荐大家阅读网易出品的《深入浅出MySQL(第2版)》,网易出品必属精品。
7. 使用慢查询日志来发现性能低下的查询。
```SQL
mysql> show variables like 'slow_query%';
+---------------------------+----------------------------------+
| Variable_name | Value |
+---------------------------+----------------------------------+
| slow_query_log | OFF |
| slow_query_log_file | /mysql/data/localhost-slow.log |
+---------------------------+----------------------------------+
mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
```
```SQL
mysql> set global slow_query_log='ON';
mysql> set global long_query_time=1;
```
```INI
[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 1
```
#### 其他
请参考《Python性能调优》。
\ No newline at end of file
## 英语面试
以下用I表示面试官(Interviewer),用C表示面试者(Candidate)。
### 开场寒暄
1. I: Thanks for waiting. (Please follow me.)
C: It's no problem.
2. I: How are you doing this morning?
C: I'm great. / I'm doing fine. Thank you. / How about you?
3. I: How did you get here?
C: I took the subway here. / I drove here.
4. I: Glad to meet you.
C: Glad to meet you. / It's great to finally meet you in person. (之前电话沟通过的)
### 正式面试
#### 人力面试
1. I: Can you tell me a little bit about yourself? (介绍下自己)
原则:不要谈私生活和奇怪的癖好(英雄联盟干到钻石),因为别人更想知道的是你的专业技能(qulifications)和工作经验(experience),所以重点在你之前的公司(company name)、职位(title)、时间(years)和主要职责(major responsibilities)
C: Thank you for having me. My name is Dachui WANG. I'm 25 years old, and I'm single. I have a Bachelor's Degree of Computer Science from Tsinghua University. I was a Junior Java Programmer for ABC Technologies during my college life. Then I become an intermediate Java engineer for XYZ Corporation in last two years. Programming is my everyday life and programming is where my passion is. I think I have a good knowledge of Java enterprise application developement using light-weight frameworks like Spring, Guice, Hibernate and other open source middle-ware like Dubbo, Mycat, rocketmq and so on and so forth. I love reading, travelling and playing basketball in my spare time. That's all! Thank you!
2. I: How would you describe your personality? (你的性格)
C: I'm hard working, eager to learn, and very serious about my work. I enjoy working with other people and I love challenges.
3. I: What do you know about our company? (你对我们公司有什么了解)
(需要做功课,了解公司的状况和企业文化,该公司在这个行业中的一个状况,有什么核心业务,主要的竞争对手有哪些)
C: The one thing that I like the most about our company is your core values. I think they're very important in this industry because …(自由发挥的部分)... I personally really believe in the cause as well. Of course, I'm very interested in your products such as …(功课部分)… and the techniques behind them.
4. I: Why are you leaving your last job? (为什么离职)
C: I want to advance my career and I think this job offers more challenges and opportunities for me do to that.
5. I: What do you see yourself in 3 or 5 years? (3-5年职业规划)
C: My long term goals involve growing with the company, where I can continue to learn, to take on additional responsibilities and to contribute as much value as I can. I intend to take advantage of all of these.
6. I: What's your salary expectation? (期望薪资)
C: My salary expectation is in line with my experience and qualifications. I believe our company will pay me and every other employee fairly. (把球踢给对方先看看对方报价是多少,如果对方非要你报价再说后面的内容) I think 15 thousands RMB or above is fitting for me to leave in Chengdu.
7. I: Do you have any questions for me? (问面试官的问题)
C: What's the growth potential for this position?
#### 技术面试
1. I: What's difference between an interface and an abstract class?
2. I: What are pass by reference and pass by value?
3. I: What's the difference between process and threads?
4. I: Explain the available thread state in high-level.
5. I: What's deadlocks? How to avoid them?
6. I: How HashMap works in Java?
7. I: What's the difference between ArrayList and LinkedList? (类似的问题还有很多,比如比较HashSet和TreeSet、HashMap和Hashtable)
8. I: Tell me what you know about garbage collection in Java.
9. I: What're two types of exceptions in Java?
10. I: What's the advantage of PreparedStatement over Statement?
11. I: What's the use of CallableStatement?
12. I: What does connection pool mean?
13. I: Explain the life cycle of a Servlet.
14. I: What's the difference between redirect and forward?
15. I: What's EL? What're implicit objects of EL?
16. I: Tell me what you know about Spring framework and its benefits.
17. I: What're different types of dependency injection.
18. I: Are singleton beans thread safe in Spring framework?
19. I: What're the benefits of Spring framework's transaction management?
20. I: Explain what's AOP.
21. I: What's a proxy and how to implement proxy pattern?
22. I: How Spring MVC works?
23. I: What's the working scenario of Hibernate and MyBatis?
24. I: How to implement SOA?
25. I: Make a brief introduction of the projects you are involved before?
上面主要是面试Java程序员的问题,但是整个流程大致如此。
## 项目部署上线指南
### 准备上线
1. 上线前的检查工作。
```Shell
python manage.py check --deploy
```
2. 将DEBUG设置为False并配置ALLOWED_HOSTS。
```Python
DEBUG = False
ALLOWED_HOSTS = ['*']
```
3. 安全相关的配置。
```Python
# 保持HTTPS连接的时间
SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# 自动重定向到安全连接
SECURE_SSL_REDIRECT = True
# 避免浏览器自作聪明推断内容类型
SECURE_CONTENT_TYPE_NOSNIFF = True
# 避免跨站脚本攻击
SECURE_BROWSER_XSS_FILTER = True
# COOKIE只能通过HTTPS进行传输
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# 防止点击劫持攻击手段 - 修改HTTP协议响应头
# 当前网站是不允许使用<iframe>标签进行加载的
X_FRAME_OPTIONS = 'DENY'
```
4. 敏感信息放到环境变量或文件中。
```Python
SECRET_KEY = os.environ['SECRET_KEY']
DB_USER = os.environ['DB_USER']
DB_PASS = os.environ['DB_PASS']
REDIS_AUTH = os.environ['REDIS_AUTH']
```
### 更新服务器Python环境到3.x
1. 安装底层依赖库。
```Shell
yum -y install wget gcc zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel
```
2. 下载Python源代码。
```Shell
wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz
```
3. 解压缩和解归档。
```Shell
xz -d Python-3.7.0.tar.xz
tar -xvf Python-3.7.0.tar
```
4. 执行配置生成Makefile(构建文件)。
```Shell
cd Python-3.7.0
./configure --prefix=/usr/local/python37 --enable-optimizations
```
5. 构建和安装。
```Shell
make && make install
```
6. 配置PATH环境变量并激活。
```Shell
cd ~
vim .bash_profile
```
```INI
... 此处省略上面的代码...
export PATH=$PATH:/usr/local/python37/bin
... 此处省略下面的代码...
```
```Shell
source .bash_profile
```
7. 注册软链接(符号链接)。
```Shell
ln -s /usr/local/python37/bin/python3.7 /usr/bin/python3
ln -s /usr/local/python37/bin/pip3.7 /usr/bin/pip3
```
8. 测试Python环境是否更新成功。
```Shell
python3 --version
python --version
```
### 项目目录结构
假设项目文件夹为`project`,下面的四个子目录分别是:`conf`、`logs`、`src`和`venv`分别用来保存项目的配置文件、日志文件、源代码和虚拟环境。其中,`conf`目录下的子目录`cert`中保存了配置HTTPS需要使用的证书和密钥;`src`目录下的项目代码可以通过版本控制工具从代码仓库中检出;虚拟环境可以通过venv或其他工具进行创建。
```
project
├── conf
│   ├── cert
│   │   ├── 214915882850706.key
│   │   └── 214915882850706.pem
│   ├── nginx.conf
│   └── uwsgi.ini
├── logs
│   ├── access.log
│   ├── error.log
│   └── uwsgi.log
├── requirements.txt
├── src
│   └── fangall
│   ├── api
│   ├── common
│   ├── fang
│   ├── rent
│   ├── user
│   ├── manage.py
│   ├── README.md
│   ├── static
│   └── templates
│  
└── venv
├── bin
│   ├── activate
│   ├── activate.csh
│   ├── activate.fish
│   ├── celery
│   ├── celerybeat
│   ├── celeryd
│   ├── celeryd-multi
│   ├── coverage
│   ├── coverage3
│   ├── coverage-3.7
│   ├── django-admin
│   ├── django-admin.py
│   ├── easy_install
│   ├── easy_install-3.7
│   ├── pip
│   ├── pip3
│   ├── pip3.7
│   ├── __pycache__
│   ├── pyrsa-decrypt
│   ├── pyrsa-decrypt-bigfile
│   ├── pyrsa-encrypt
│   ├── pyrsa-encrypt-bigfile
│   ├── pyrsa-keygen
│   ├── pyrsa-priv2pub
│   ├── pyrsa-sign
│   ├── pyrsa-verify
│   ├── python -> python3
│   ├── python3 -> /usr/bin/python3
│   └── uwsgi
├── include
├── lib
│   └── python3.7
├── lib64 -> lib
├── pip-selfcheck.json
└── pyvenv.cfg
```
下面以阿里云为例,简单说明如何为项目注册域名、解析域名以及购买权威机构颁发的证书。
1. [注册域名](https://wanwang.aliyun.com/domain/)。
![](./res/aliyun-domain.png)
2. [域名备案](https://beian.aliyun.com/)。
![](./res/aliyun-keeprecord.png)
3. [域名解析](https://dns.console.aliyun.com/#/dns/domainList)。
![](./res/aliyun-dnslist.png)
![](./res/aliyun-resolve-settings.png)
4. [购买证书](https://www.aliyun.com/product/cas)。
![](./res/aliyun-certificate.png)
### uWSGI的配置
1. 在`project`目录下创建并激活虚拟环境。
```Shell
python3 -m venv venv
source venv/bin/activate
```
2. 安装项目依赖项。
```Shell
pip install -r requirements.txt
```
3. 通过pip安装uWSGI。
```Shell
pip install uwsgi
```
4. 修改uWSGI的配置文件(`/root/project/conf/uwsgi.ini`)。
```INI
[uwsgi]
# 配置前导路径
base=/root/project
# 配置项目名称
name=fangall
# 守护进程
master=true
# 进程个数
processes=4
# 虚拟环境
pythonhome=%(base)/venv
# 项目地址
chdir=%(base)/src/%(name)
# 指定python解释器
pythonpath=%(pythonhome)/bin/python
# 指定uwsgi文件
module=%(name).wsgi
# 通信的地址和端口(自己服务器的IP地址和端口)
socket=172.18.61.250:8000
# 日志文件地址
logto = %(base)/logs/uwsgi.log
```
> 说明:可以先将“通信的地址和端口”项等号前面改为http来进行测试,如果没有问题再改回 成socket,然后通过Nginx来实现项目的“动静分离”(静态资源交给Nginx处理,动态内容交给 uWSGI处理)。按照下面的方式可以启动uWSGI服务器。
5. 启动服务器。
```Shell
uwsgi --ini uwsgi.ini &
```
### Nginx的配置
1. 修改全局配置文件(`/etc/nginx/nginx.conf`)。
```Nginx
# 配置用户
user root;
# 工作进程数(建议跟CPU的核数量一致)
worker_processes auto;
# 错误日志
error_log /var/log/nginx/error.log;
# 进程文件
pid /run/nginx.pid;
# 包含其他的配置
include /usr/share/nginx/modules/*.conf;
# 工作模式和连接上限
events {
use epoll;
worker_connections 1024;
}
# HTTP服务器相关配置
http {
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 访问日志
access_log /var/log/nginx/access.log main;
# 开启高效文件传输模式
sendfile on;
# 用sendfile传输文件时有利于改善性能
tcp_nopush on;
# 禁用Nagle来解决交互性问题
tcp_nodelay on;
# 客户端保持连接时间
keepalive_timeout 15;
types_hash_max_size 2048;
# 包含MIME类型的配置
include /etc/nginx/mime.types;
# 默认使用二进制流格式
default_type application/octet-stream;
# 包含其他配置文件
include /etc/nginx/conf.d/*.conf;
# 包含项目的Nginx配置文件
include /root/project/conf/*.conf;
}
```
2. 编辑局部配置文件(`/root/project/conf/nginx.conf`)。
```Nginx
server {
listen 80;
server_name _;
access_log /root/project/logs/access.log;
error_log /root/project/logs/error.log;
location / {
include uwsgi_params;
uwsgi_pass 172.18.61.250:8000;
}
location /static/ {
alias /root/project/src/fangall/static/;
expires 30d;
}
}
server {
listen 443;
server_name _;
ssl on;
access_log /root/project/logs/access.log;
error_log /root/project/logs/error.log;
ssl_certificate /root/project/conf/cert/214915882850706.pem;
ssl_certificate_key /root/project/conf/cert/214915882850706.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
include uwsgi_params;
uwsgi_pass 172.18.61.250:8000;
}
location /static/ {
alias /root/project/src/fangall/static/;
expires 30d;
}
}
```
到此为止,我们可以启动Nginx来访问我们的应用程序,HTTP和HTTPS都是没有问题的,如果Nginx已经运行,在修改配置文件后,我们可以用下面的命令重新启动Nginx。
3. 重启Nginx服务器。
```Shell
nginx -s reload
```
> 说明:可以对Django项目使用`python manage.py collectstatic`命令将静态资源收集到指定目录下,要做到这点只需要在项目的配置文件`settings.py`中添加`STATIC_ROOT`配置即可。
#### 负载均衡配置
下面的配置中我们使用Nginx实现负载均衡,为另外的三个Nginx服务器(通过Docker创建)提供反向代理服务。
```Shell
docker run -d -p 801:80 --name nginx1 nginx:latest
docker run -d -p 802:80 --name nginx2 nginx:latest
docker run -d -p 803:80 --name nginx3 nginx:latest
```
```Nginx
user root;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
# 为HTTP服务配置负载均衡
http {
upstream fang.com {
server 172.18.61.250:801 weight=4;
server 172.18.61.250:802 weight=2;
server 172.18.61.250:803 weight=2;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl;
listen [::]:443 ssl;
ssl on;
access_log /root/project/logs/access.log;
error_log /root/project/logs/error.log;
ssl_certificate /root/project/conf/cert/214915882850706.pem;
ssl_certificate_key /root/project/conf/cert/214915882850706.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_pass http://fang.com;
}
}
}
```
> 说明:Nginx在配置负载均衡时,默认使用WRR(加权轮询算法),除此之外还支持ip_hash、fair(需要安装upstream_fair模块)和url_hash算法。此外,在配置upstream模块时可以指定服务器的状态值,包括:backup(备份机器,其他服务器不可用时才将请求分配到该机器)、down、fail_timeout(请求失败达到max_fails后的暂停服务时间)、max_fails(允许请求失败的次数)和weight(轮询的权重)。
### Keepalived
当使用Nginx进行负载均衡配置时,要考虑负载均衡服务器宕机的情况。为此可以使用Keepalived来实现负载均衡主机和备机的热切换,从而保证系统的高可用性。Keepalived的配置还是比较复杂,通常由专门做运维的人进行配置,一个基本的配置可以参照[《Keepalived的配置和使用》](https://www.jianshu.com/p/dd93bc6d45f5)。
### MySQL主从复制
下面还是基于Docker来演示如何配置MySQL主从复制。我们事先准备好MySQL的配置文件以及保存MySQL数据和运行日志的目录,然后通过Docker的数据卷映射来指定容器的配置、数据和日志文件的位置。
```Shell
root
└── mysql
├── conf
│   ├── master
│   │   └── mysqld.cnf
│   ├── slave1
│   │   └── mysqld.cnf
│   ├── slave2
│   │   └── mysqld.cnf
│   └── slave3
│   └── mysqld.cnf
└── data
├── master
├── slave1
├── slave2
└── slave3
```
1. MySQL的配置文件(master和slave的配置文件需要不同的server-id)。
```
[mysqld]
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
log-error=/var/log/mysql/error.log
server-id=1
log_bin=/var/log/mysql/mysql-bin.log
expire_logs_days=30
max_binlog_size=256M
symbolic-links=0
```
2. 创建和配置master。
```Shell
docker run -d -p 3306:3306 --name mysql57 \
-v /root/mysql/conf/master:/etc/mysql/mysql.conf.d \
-v /root/mysql/data/master:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
docker exec -it mysql57 /bin/bash
```
```Shell
mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.7.23-log MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> grant replication slave on *.* to 'slave'@'%' identified by 'iamslave';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 590 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
mysql> quit
Bye
exit
```
上面创建Docker容器时使用的`-v`参数(`--volume`)表示映射数据卷,冒号前是宿主机的目录,冒号后是容器中的目录,这样相当于将宿主机中的目录挂载到了容器中。
3. 创建和配置slave。
```Shell
docker run -d -p 3307:3306 --name mysql57-slave-1 \
-v /root/mysql/conf/slave1:/etc/mysql/mysql.conf.d \
-v /root/mysql/data/slave1:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--link mysql57:mysql57 mysql:5.7
docker exec -it mysql57-slave-1 /bin/bash
```
```Shell
mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.23-log MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> reset slave;
Query OK, 0 rows affected (0.02 sec)
mysql> change master to master_host='mysql57', master_user='slave', master_password='iamslave', master_log_file='mysql-bin.000003', master_log_pos=590;
Query OK, 0 rows affected, 2 warnings (0.03 sec)
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: mysql57
Master_User: slave
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 590
Relay_Log_File: f352f05eb9d0-relay-bin.000002
Relay_Log_Pos: 320
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 590
Relay_Log_Space: 534
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: 30c38043-ada1-11e8-8fa1-0242ac110002
Master_Info_File: /var/lib/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
mysql> quit
Bye
exit
```
接下来可以如法炮制配置出slave2和slave3,这样就可以搭建起一个“一主带三从”的主从复制环境。上面创建创建容器时使用的`--link`参数用来配置容器在网络上的主机名(网络地址别名),下一节有这个知识点的介绍。
### Docker
事实上,项目上线中最为麻烦的事情就是配置软件运行环境,环境的差异会给软件的安装和部署带来诸多的麻烦,而Docker正好可以解决这个问题。关于Docker在之前的文档中我们已经介绍过了,接下来我们对Docker的知识做一些必要的补充。
1. 创建镜像文件。
将容器保存成镜像:
```Shell
docker commit -m "..." -a "..." <container-name> jackfrued/<image-name>
```
使用Dockerfile构建镜像:
```Dockerfile
# 指定基础镜像文件
FROM centos:latest
# 指定维护者信息
MAINTAINER jackfrued
# 执行命令
RUN yum -y gcc
RUN cd ~
RUN mkdir -p project/src
RUN mkdir -p project/logs
# 拷贝文件
COPY ...
# 暴露端口
EXPOSE ...
# 在容器启动时执行命令
CMD ~/init.sh
```
```Shell
docker build -t jackfrued/<image-name> .
```
2. 镜像的导入和导出。
```Shell
docker save -o <file-name>.tar <image-name>:<version>
docker load -i <file-name>.tar
```
3. 推送到DockerHub服务器。
```Shell
docker tag <image-name>:<version> jackfrued/<name>
docker login
docker push jackfrued/<name>
```
4. 容器之间的通信。
```Shell
docker run --link <container-name>:<alias-name>
```
如果我们能够在Docker中完成项目的部署,并且将整个部署好的容器打包成镜像文件进行分发和安装,这样就可以解决项目在多个节点上进行部署时可能遇到的麻烦,而且整个部署可以在很短的时间内完成。
### Supervisor
[Supervisor](https://github.com/Supervisor/supervisor)是一个用Python写的进程管理工具,可以很方便的用来在类Unix系统下启动、重启(自动重启程序)和关闭进程。
1. 安装Supervisor。
```Shell
yum -y install supervisor
```
2. 查看Supervisor的配置文件。
```Shell
vim /etc/supervisord.conf
...
[include]
files = supervisord.d/*.ini
```
可以看出自定义的管理配置代码可以放在`/etc/supervisord.d`目录中,并且文件名以`ini`作为后缀即可。
3. 编写管理配置代码。
```Shell
cd /etc/supervisord.d
vim fang.ini
```
```INI
[program:fang]
...
```
4. 启动Supervisor服务和查看状态。
```Shell
systemctl start supervisord
supervisorctl status
```
### 其他服务
1. 常用开源软件。
| 功能 | 开源方案 |
| ------------------- | ------------------------- |
| 版本控制工具 | Git、Mercurial、SVN |
| 缺陷管理 | Redmine、Mantis |
| 负载均衡 | Nginx、LVS、HAProxy |
| 邮件服务 | Postfix、Sendmail |
| HTTP服务 | Nginx、Apache |
| 消息队列 | RabbitMQ、ZeroMQ、Redis |
| 文件系统 | FastDFS |
| 基于位置服务(LBS) | MongoDB、Redis |
| 监控服务 | Nagios、Zabbix |
| 关系型数据库 | MySQL、PostgreSQL |
| 非关系型数据库 | MongoDB、Redis、Cassandra |
| 搜索引擎 | ElasticSearch、Solr |
| 缓存服务 | Mamcached、Redis |
2. 常用云服务。
| 功能 | 可用的云服务 |
| -------------- | --------------------------------------- |
| 团队协作工具 | Teambition、钉钉 |
| 代码托管平台 | Github、Gitee、CODING |
| 邮件服务 | SendCloud |
| 云存储(CDN) | 七牛、OSS、LeanCloud、Bmob、又拍云、AWS |
| 移动端推送 | 极光、友盟、百度 |
| 即时通信 | 环信、融云 |
| 短信服务 | 云片、极光、Luosimao、又拍云 |
| 第三方登录 | 友盟、ShareSDK |
| 网站监控和统计 | 阿里云监控、监控宝、百度云观测、小鸟云 |
......@@ -208,40 +208,22 @@
#### Day41 - [快速上手](./Day41-55/01.快速上手.md)
#### Day42 - [深入模型](./Day41-55/02.深入模型.md)
#### Day43 - [静态资源和Ajax请求](./Day41-55/03.静态资源和Ajax请求.md)
#### Day44 - [表单的应用](./Day41-55/04.表单的应用.md)
#### Day45 - [Cookie和会话](./Day41-55/05.Cookie和会话.md)
#### Day46 - [中间件的应用](./Day41-55/06.中间件的应用.md)
#### Day47 - [日志和缓存](./Day41-55/07.日志和缓存.md)
#### Day48 - [文件上传](./Day41-55/08.文件上传.md)
#### Day49-50 - [RESTful架构和应用](./Day41-55/09-10.RESTful架构和应用.md)
#### Day51-55 - [项目实战](./Day41-55/11-15.项目实战.md)
- 项目开发流程和相关工具
......@@ -255,28 +237,16 @@
#### Day56 - [安装和入门](./Day56-65/01.安装和入门.md)
#### Day57 - [模板的使用](./Day56-65/02.模板的使用.md)
#### Day58 - [表单的处理](./Day56-65/03.表单的处理.md)
#### Day59 - [数据库操作](./Day56-65/04.数据库操作.md)
#### Day60 - [项目结构](./Day56-65/05.项目结构.md)
#### Day61-65 - [项目实战](./Day56-65/06-10.项目实战.md)
### Day66~75 - [爬虫开发](./Day66-75)
#### Day66 - [网络爬虫和相关工具](./Day66-75/01.网络爬虫和相关工具.md)
......@@ -329,26 +299,143 @@
### Day91~100 - [团队项目开发](./Day91-100)
#### Day91 - [过程模型和团队开发工具](./Day91-100/过程模型和团队开发工具.md)
#### 过程模型
1. 软件过程模型
- 经典过程模型(瀑布模型)
- 可行性分析(研究做还是不做),输出《可行性分析报告》。
- 需求分析(研究做什么),输出《需求规格说明书》和产品界面原型图。
- 概要设计和详细设计,输出概念模型图、物理模型图、类图、时序图等。
- 编码 / 测试。
- 上线 / 维护。
- 敏捷开发(Scrum)- 产品所有者、Scrum Master、研发人员 - Sprint
- 产品的Backlog(用户故事、产品原型)。
- 计划会议(评估和预算)。
- 日常开发(站立会议、番茄工作法、结对编程、测试先行、代码重构……)。
- 修复bug(问题描述、重现步骤、测试人员、被指派人)。
- 评审会议(Showcase)。
- 回顾会议(当前周期做得好和不好的地方)。
2. 项目团队组建
- 团队的构成和角色
![company_architecture](./res/company_architecture.png)
- 编程规范和代码审查(flake8、pylint)
![](./res/pylint.png)
- Python中的一些“惯例”(请参考[《Python惯例-如何编写Pythonic的代码》](Python惯例-如何编写Pythonic的代码.md)
3. 团队开发工具介绍
- 版本控制:Git、Mercury
- 缺陷管理:Github/Gitee、Redmine、禅道
- 持续集成:Jenkins、Travis-CI
请参考[《团队项目开发》](团队项目开发.md)
#### 项目选题和理解业务
1. 选题范围设定
- CMS(用户端):新闻聚合网站、问答/分享社区、影评/书评网站等。
- MIS(用户端+管理端):KMS、KPI考核系统、HRS、仓储管理系统等。
- App后台(管理端+数据接口):二手交易类App、报刊杂志类App、健康健美类App、旅游类App、社交类App、阅读类App等。
- 其他类型:自身行业背景和工作经验、业务容易理解和把控。
2. 需求理解、模块划分和任务分配
- 需求理解:头脑风暴和竞品分析。
- 模块划分:画思维导图(XMind),每个模块是一个枝节点,每个具体的功能是一个叶节点(用动词表述),需要确保每个叶节点无法再生出新节点,确定每个叶子节点的重要性、优先级和工作量。
- 任务分配:由项目负责人根据上面的指标为每个团队成员分配任务。
![](./res/requirements_by_xmind.png)
3. 制定项目进度表(每日更新)
| 模块 | 功能 | 人员 | 状态 | 完成 | 工时 | 计划开始 | 实际开始 | 计划结束 | 实际结束 | 备注 |
| ---- | -------- | ------ | -------- | ---- | ---- | -------- | -------- | -------- | -------- | ---------------- |
| 评论 | 添加评论 | 王大锤 | 正在进行 | 50% | 4 | 2018/8/7 | | 2018/8/7 | | |
| | 删除评论 | 王大锤 | 等待 | 0% | 2 | 2018/8/7 | | 2018/8/7 | | |
| | 查看评论 | 白元芳 | 正在进行 | 20% | 4 | 2018/8/7 | | 2018/8/7 | | 需要进行代码审查 |
| | 评论投票 | 白元芳 | 等待 | 0% | 4 | 2018/8/8 | | 2018/8/8 | | |
#### 概念模型和正向工程
1. UML和E-R图
![uml](./res/uml-graph.png)
2. 通过模型创建表(正向工程)
```Shell
python manage.py makemigrations app
python manage.py migrate
```
#### 物理模型和反向工程
1. PowerDesigner
![](./res/power-designer-pdm.png)
2. 通过数据表创建模型
```Shell
python manage.py inspectdb > app/models.py
```
#### 项目开发中的公共问题
1. 数据的配置
2. 缓存的配置
3. 日志的配置
4. Django的使用技巧
5. 好用的Python模块(图像处理、数据加密、三方API)
### REST API设计
#### Day92 - [模块分割与单元测试](./Day91-100/模块分割与单元测试.md)
1. RESTful架构
2. API接口文档的撰写([《网络API接口设计》](网络API接口设计.md)
3. Django-REST-Framework的应用
#### 项目中的重点难点剖析
1. 使用缓存缓解数据库压力(Redis)
2. 使用消息队列缓解服务器压力(Celery + RabbitMQ)
#### Day93~Day97 - [开发中的常见问题](./Day91-100/开发中的常见问题.md)
#### 单元测试
1. 测试的种类
2. 编写单元测试(unitest、TestCase)
3. 测试覆盖率(Coverage)
#### 项目部署
#### Day98 - [持续集成](./Day91-100/持续集成.md)
1. 部署前的准备工作
- 关键设置(SECRET_KEY / DEBUG / ALLOWED_HOSTS / 缓存 / 数据库)
- HTTPS / CSRF_COOKIE_SECUR / SESSION_COOKIE_SECURE
- 日志相关配置
2. Linux常用命令回顾
3. Linux常用服务的安装和配置
4. uWSGI和Nginx的使用
5. 虚拟化容器(Docker)
#### 性能测试
1. AB的使用
2. SQLslap的使用
3. sysbench的使用
#### Day99 - [项目部署和安全性措施](./Day91-100/项目部署和安全性措施.md)
#### 自动化测试
1. 使用Shell和Python进行自动化测试
2. 使用Selenium实现自动化测试
3. 测试工具Robot Framework介绍
#### 项目性能调优
#### Day100 - [性能测试和性能调优](./Day91-100/性能测试和性能调优.md)
1. 数据库性能调优
2. 代码性能调优
3. 静态文件服务器和CDN加速
res/uml.png

22.4 KB

Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment