Commit 57a7b2ea authored by jackfrued's avatar jackfrued

增加了团队项目开发相关文档

parent 0c25a31f
## 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=()
<QuerySet [{'dept': 10, 'total': 4}, {'dept': 20, 'total': 7}, {'dept': 30, 'total': 3}]
```
- `first()` / `last()`
- `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. 每个视图函数构成一个事务边界。
- 事务的概念。
- 事务的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/(?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, '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/<int:prov_id>', views.list_cities_by_prov),
]
```
问题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.js"></script>
<script src="js/abc.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>
```
#### 使用类视图
更好的复用代码,不要重“复发明轮子”。
```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/<int:pk>', 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:什么是点击劫持攻击,如何防范?(不允许`<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!'
```
5. CSRF令牌和小工具
```HTML
{% csrf_token %}
```
```Python
@csrf_exempt
@csrf_protect
@require_csrf_token
@ensure_csrf_cookie
```
#### 用户敏感信息的保护
1. 哈希摘要(签名)
```Python
>>> import hashlib
>>>
>>> md5_proto = hashlib.md5()
>>> md5_hasher = md5_proto.copy()
>>> md5_hasher.update('hello, world!'.encode())
>>> md5_hasher.hexdigest()
'3adbbad1791fbae3ec908894c4963870'
>>>
>>> sha1_proto = hashlib.sha1()
>>> sha1_hasher = sha1_proto.copy()
>>> sha1_hasher.update('hello, world!'.encode())
>>> sha1_hasher.hexdigest()
'1f09d30c707d53f3d16c530dd73d70a6ce7596a9'
```
2. 加密和解密(对称加密和非对称加密)
```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. 什么是黑盒测试和白盒测试?(方法是黑盒子,不知道实现细节,通过设定输入和预期输出进行来断言方法是否实现了应有的功能)
#### 单元测试
目标:测试函数和对象的方法(程序中最基本的单元)。
实施:通过对实际输出和预期输出的比对以及各种的断言条件来判定被测单元是否满足设计需求。
```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))
```
#### 测试模型
```Python
class ModelTest(TestCase):
def test_save_province(self):
pass
def test_del_province(self):
pass
def test_update_province(self):
pass
def test_get_all_provinces(self):
pass
def test_get_province_by_id(self):
pass
```
#### 测试视图
```Python
class RentViewTest(TestCase):
def test_index_view(self):
resp = self.client.get(reverse('index'))
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.context['num'], 5)
```
#### 运行测试
配置测试数据库
```Python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'localhost',
'PORT': 3306,
'NAME': 'House',
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASS'],
'TEST': {
'NAME': 'House_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
```
#### 测试覆盖度
```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
```Shell
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
### 部署相关
请参考《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生成器代替自增主键(性能更好、适用于分布式环境)。
```Python
>>> my_uuid = uuid.uuid1()
>>> my_uuid
UUID('63f859d0-a03a-11e8-b0ad-60f81da8d840')
>>> my_uuid.hex
'63f859d0a03a11e8b0ad60f81da8d840'
```
2. 避免不必要的外键列上的约束(除非必须保证参照完整性),更不要使用触发器之类的机制。
3. 使用索引来优化查询性能(索引放在要用于查询的字段上)。
```SQL
select * from tb_goods where gname like 'iPhone%';
```
```Python
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. 使用`explain`来分析查询性能 - 执行计划。
```SQL
explain select * from ...;
```
请参考网易出品的《深入浅出MySQL》上面对应部分的讲解(已经有第二版)。
6. 使用慢查询日志来发现性能低下的查询。
```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 slow_query_log_file='/usr/local/mysql/data/slow.log';
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
## Docker入门
### Docker简介
软件开发中最为麻烦的事情可能就是配置环境了。由于用户使用的操作系统具有多样性,即便使用跨平台的开发语言(如Java和Python)都不能保证代码能够在各种平台下都可以正常的运转,而且可能在不同的环境下我们的软件需要依赖的其他软件包也是不一样的。
那么问题来了,我们再安装软件的时候可不可以把软件运行的环境一并安装课?也就是说在安装软件的时候,我们是不是可以把原始环境一模一样地复制过来呢?
虚拟机(virtual machine)就是带环境安装的一种解决方案,它可以在一种操作系统里面运行另一种操作系统,比如在Windows系统里面运行Linux系统,在macOS上运行Windows,而应用程序对此毫无感知。使用过虚拟机的人都知道,虚拟机用起来跟真实系统一模一样,而对于虚拟机的宿主系统来说,虚拟机就是一个普通文件,不需要了就删掉,对宿主系统或者其他的程序并没有影响。但是虚拟机通常会占用较多的系统资源,启动和关闭也非常的缓慢,总之用户体验没有想象中的那么好。
Docker属于对Linux容器技术的一种封装,它提供了简单易用的容器使用接口,是目前最流行的 Linux 容器解决方案。Docker将应用程序与该程序的依赖打包在一个文件里面,运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了Docker就再也不用担心环境问题了。
![](./res/docker_vs_vm.png)
目前,Docker主要用于几下几个方面:
1. 提供一次性的环境。
2. 提供弹性的云服务(利用Docker很容易实现扩容和收缩)。
3. 实践微服务架构(隔离真实环境在容器中运行多个服务)。
### CentOS下的安装和使用
下面的讲解以CentOS为例,使用[Ubuntu](https://docs.docker.com/install/linux/docker-ce/ubuntu/)[macOS](https://docs.docker.com/docker-for-mac/install/)[Windows](https://docs.docker.com/docker-for-windows/install/)的用户可以通过点击链接了解这些平台下如何安装和使用Docker。
0. 确定操作系统内核版本(CentOS 7要求64位,内核版本3.10+;CentOS 6要求64位,内核版本2.6+)。
```Shell
uname -r
```
1. 在CentOS下使用yum安装Docker并启动。
```Shell
yum -y install docker-io
systemctl start docker
```
2. 检视Docker的信息和版本。
```Shell
docker version
docker info
```
3. 运行Hello-World项目来测试Docker。第一次运行时由于本地没有hello-world的镜像因此需要联网进行下载。
```Shell
docker run hello-world
```
也可以先用下面的命令下载镜像,然后再来运行。
```Shell
docker pull <name>
```
4. 运行镜像文件。
```Shell
docker run <image-id>
docker run -p <port1>:<port2> <name>
```
6. 查看镜像文件。
```Shell
docker image ls
docker images
```
7. 删除镜像文件。
```Shell
docker rmi <name>
```
8. 查看正在运行容器。
```Shell
docker ps
```
9. 停止运行的容器。
```Shell
docker stop <container-id>
docker stop <name>
```
对于那些不会自动终止的容器,就可以用下面的方式来停止。
```Shell
docker container kill <container-id>
```
在Ubuntu(内核版本3.10+)下面安装和启动Docker,可以按照如下的步骤进行。
```Shell
apt update
apt install docker-ce
service docker start
```
在有必要的情况下,可以更换Ubuntu软件下载源来提升下载速度,具体的做法请参照<https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/>
安装Docker后,由于直接访问dockerhub下载镜像会非常缓慢,建议更换国内镜像,可以通过修改`/etc/docker/daemon.js`文件来做到。
```JavaScript
{
"registry-mirrors": [
"http://hub-mirror.c.163.com",
"https://registry.docker-cn.com"
]
}
```
### Docker实战
#### 安装Nginx
Docker的使用肯定不止上面这点东西,但是有了这些知识之后,我们已经可以开始感受Docker的强大之处。下面我们就基于Docker来搭建HTTP服务器(Nginx)环境。
```Shell
docker container run -d -p 80:80 --rm --name mynginx nginx
```
> 说明:上面的参数`-d`表示容器在后台运行;`-p`是用来映射容器的端口到宿主机的端口;`--rm`表示容器停止后自动删除容器,例如通过`docker container stop mynginx`以后,容器就没有了;`--name`是自定义容器的名字。
如果需要将自己的页面部署到Nginx上,可以使用容器的拷贝命令将当前文件夹下所有的文件和文件夹拷贝到容器的指定目录中。当然也可以从容器中拷贝文件到我们指定的路径下。
```Shell
docker container cp ./index.html mynginx:/usr/local/nginx/html
```
如果不愿意拷贝文件也可以将文件夹映射到Nginx保存页面文件的目录。
```Shell
docker container run -d -p 80:80 --rm --name mynginx --volume "$PWD/html":/usr/share/nginx/html nginx
```
#### 安装MySQL
下载MySQL镜像。
```Shell
docker search mysql
docker pull mysql:5.7
docker images
```
启动容器运行MySQL。
```Shell
docker run --name mysql-docker -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
```
在使用MySQL 8.x时可能会遇到“error 2059: Authentication plugin 'caching_sha2_password' cannot be loaded”的问题,这是因为MySQL 8.x默认使用了名为“caching_sha2_password”的机制对用户口令进行了更好的保护,但是如果客户端没有更新有可能无法基于这种方式进行身份验证,可以按照下面的方式加以解决。
```Shell
docker exec -it mysql8-docker /bin/bash
```
进入容器的交互式Shell之后,可以首先利用MySQL的客户端工具连接MySQL服务器。
```Shell
mysql -u root -p
Enter password:
Your MySQL connection id is 16
Server version: 8.0.12 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>
```
接下来通过SQL来修改用户口令就可以了。
```SQL
alter user 'root'@'%' identified with mysql_native_password by '123456' password expire never;
```
当然,如果愿意你也可以查看一下用户表检查是否修改成功。
```SQL
use mysql;
select user, host, plugin, authentication_string from user where user='root';
+------+-----------+-----------------------+-------------------------------------------+
| user | host | plugin | authentication_string |
+------+-----------+-----------------------+-------------------------------------------+
| root | % | mysql_native_password | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| root | localhost | mysql_native_password | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+------+-----------+-----------------------+-------------------------------------------+
2 rows in set (0.00 sec)
```
## 团队项目开发
我们经常听到个人开发和团队开发这两个词,所谓个人开发就是一个人把控产品的所有内容;而团队开发则是由多个人组成团队并完成产品的开发。要实施团队开发以下几点是必不可少的:
1. 必须对开发过程中的各种事件(例如:谁到什么时间完成了什么事情)进行管理和共享。
2. 各类工作成果以及新的知识技巧等必须在团队内部共享。
3. 管理工作成果的变更,既要防止成果被破坏,又要保证各个成员利用现有成果并行作业。
4. 能够证明团队开发出的软件在任何时候都是可以正常运行的。
5. 尽可能的使用自动化的工作流程,让团队成员能够正确的实施开发、测试和部署。
### 团队项目开发常见问题
#### 问题1:传统的沟通方式无法确定处理的优先级
例如:使用邮件进行沟通可能出现邮件数量太多导致重要的邮件被埋没,无法管理状态,不知道哪些问题已经解决,哪些问题尚未处理,如果用全文检索邮件的方式来查询相关问题效率过于低下。
解决方案:使用缺陷管理工具。
#### 问题2:没有能够用于验证的环境
例如:收到项目正式环境中发生的故障报告后,需要还原正式环境需要花费很长的时间。
解决方法:实施持续交付。
#### 问题3:用别名目录管理项目分支
解决方法:实施版本控制。
#### 问题4:重新制作数据库非常困难
例如:正式环境和开发环境中数据库表结构不一致或者某个表列的顺序不一致。
解决方法:实施版本控制。
#### 问题5:不运行系统就无法察觉问题
例如:解决一个bug可能引入其他的bug或者造成系统退化,不正确的使用版本系统覆盖了其他人的修改,修改的内容相互发生了干扰,如果问题不能尽早发现,那么等过去几个月后再想追溯问题就非常麻烦了。
解决方法:实施持续集成,将团队成员的工作成果经常、持续的进行构建和测试。
#### 问题6:覆盖了其他成员修正的代码
解决方法:实施版本控制。
#### 问题7:无法实施代码重构
重构:在不影响代码产生的结果的前提下对代码内部的构造进行调整。
例如:在实施代码重构时可能引发退化。
解决方法:大量的可重用的测试并实施持续集成。
#### 问题8:不知道bug的修正日期无法追踪退化
解决方法:版本控制系统、缺陷管理系统和持续集成之间需要交互,最好能够和自动化部署工具集成到一起来使用。
#### 问题9:发布过程太复杂
解决方法:实施持续交付。
基于对上述问题的阐述和分析,我们基本上可以得到以下的结论,在团队开发中版本控制、缺陷管理和持续集成都是非常重要且不可或缺的。
### 版本控制
针对上面提到的一些问题,在团队开发的首要前提就是实施版本控制,对必要的信息进行管理,需要管理的内容包括:
1. 代码。
2. 需求和设计的相关文档。
3. 数据库模式和初始数据。
4. 配置文件。
5. 库的依赖关系定义。
#### Git简介
![Logo of Git](http://upload-images.jianshu.io/upload_images/1279331-df4dada955ca9052.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Git是诞生于2005年的一个开源分布式版本控制系统,最初是Linus Torvalds(Linux之父) 为了帮助管理Linux内核开发而开发的一个版本控制软件。Git与常用的版本控制工具Subversion等不同,它采用了分布式版本控制的方式,在没有中央服务器支持的环境下也能够实施版本控制。
对于有使用Subversion(以下简称为SVN)经验的人来说,Git和SVN一样摒弃了基于锁定模式的版本控制方案(早期的CVS和VSS使用的就是锁定模式)采用了合并模式,而二者的区别在于:
1. Git是分布式的,SVN是集中式的,SVN需要中央服务器才能工作。
2. Git把内容按元数据方式存储,而SVN是按文件,即把文件的元信息隐藏在一个.svn文件夹里。
3. Git分支和SVN的分支不同。
4. Git没有一个全局版本号而SVN有。
5. Git的内容完整性要优于SVN,Git的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。
#### 安装Git
可以在[Git官方网站](http://git-scm.com/)找到适合自己系统的Git下载链接并进行安装,安装成功后可以在终端中键入下面的命令检查自己的Git版本。
```Shell
git --version
```
如果之前完全没有接触过Git,可以先阅读[《git - 简易指南》](http://www.bootcss.com/p/git-guide/)来对Git有一个大致的了解。
#### 本地实施版本控制
可以使用下面的命令将目录创建为Git仓库。
```Shell
git init
```
当你完成了上述操作后,本地目录就变成了下面的样子,左边是你正在操作的工作目录,而右边是你的本地仓库,中间是工作目录和本地仓库之间的一个暂存区(也称为缓存区)。
![](./res/git_repository.png)
通过`git add`可以将文件添加到暂存区。
```Shell
git add <file> ...
```
可以用下面的方式将暂存区的指定文件恢复到工作区。
```Shell
git checkout -- <file>
```
通过下面的命令可以将暂存区的内容纳入本地仓库。
```Shell
git commit -m '本次提交的说明'
```
可以使用下面的命令查看文件状态和进行版本比较。
```Shell
git status -s
git diff
```
可以通过`git log`查看提交日志。
```Shell
git log
git log --graph --pretty=oneline --abbrev-commit
```
如果要回到历史版本,可以使用下面的命令。
```Shell
git reset --hard <commit-id>
git reset --hard HEAD^
```
其他的一些命令可以参考阮一峰老师的[《常用Git命令清单》](http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html)或者是码云上的[《Git大全》](https://gitee.com/all-about-git)
#### Git服务器概述
对于Git来说不像SVN那样一定需要一个中心服务器,刚才我们的操作都是在本地执行的,如果你想通过Git分享你的代码或者与其他人协作,那么就需要服务器的支持。Github为Git提供了远程仓库,它是一个基于Git的代码托管平台,企业用户(付费用户)可以创建仓库,普通用户只能创建公开仓库(代码是可以是他人可见的)。Github是在2008年4月创办的,它上面代码库惊人的增长速度已经证明了它是非常成功的,在2018年6月,微软以75亿美元的天价收购了Github。国内也有类似的代码托管平台,最有名的当属[码云](https://gitee.com/)[CODING](https://coding.net/),目前码云和CODING对注册用户都提供了受限的使用私有仓库的功能,同时还提供了对Pull Request的支持(后面会讲到),而且目前提供代码托管服务的平台都集成了“缺陷管理”、“WebHook”等一系列的功能,让我们能做的事情不仅仅是版本控制。当然,如果公司需要也可以搭建自己的Git服务器,具体的方式我们就不在这里进行介绍了,有兴趣的可以自行了解。
![](./res/git_logo.png)
我们可以在码云或者CODING上注册账号,也可以使用第三方登录(github账号、微信账号、新浪微博账号、CSDN账号等)的方式。登录成功后就可以创建项目,创建项目几乎是“傻瓜式”的,我们只说几个值得注意的地方。
1. 添加项目成员。创建项目后,可以在项目的“设置”或“管理”中找到“成员管理”功能,这样就可以将其他开发者设置为项目团队的成员,项目成员通常分为“所有者”、“管理者”、“普通成员”和“受限成员”几种角色。
2. 设置公钥实现免密操作。在项目的“设置”或“管理”中我们还可以找到“部署公钥管理”的选项,通过添加部署公钥,可以通过SSH(安全远程连接)的形式访问服务器而不用每次输入用户名和口令。可以使用`ssh-keygen`命令来创建密钥对。
```Shell
ssh-keygen -t rsa -C "your_email@example.com"
```
#### 使用Git进行开发
克隆服务器上的代码到本地机器。
```Shell
git clone <url>
```
在自己的分支上进行开发。
```Shell
git branch <branch-name>
git checkout <branch-name>
```
或者
```Shell
git checkout -b <branch-name>
```
接下来可以先在本地实施版本控制(操作的方式与前面相同不再赘述),然后再将自己的分支Push到服务器。
```Shell
git push origin <branch-name>
```
最后,当工作完成时,可以发起一个Pull Request,请求将代码合并到master分支。
#### 分支策略的模式
上面讲解的方式,其实是一种称为github-flow的分支策略模式,这种模式的操作步骤包括:
1. master的内容都是可以进行发布的内容。
2. 开发时应该以master为基础建立新分支。
3. 分支先在本地实施版本控制,然后以同名分支定期向服务器进行Push。
4. 开发结束后向master发送Pull Request。
5. Pull Request通过代码审查之后合并到master,并从master向正式环境发布。
在使用github-flow时的注意事项有以下三点:
1. master是用于发布的,不能直接在master上进行修改。
2. 开始日常开发工作时要首先建立分支。
3. 工作完成后向master发送Pull Request。
除了上述的github-flow工作方式外,还有一种名为git-flow的分支策略模式,它借鉴了中央集权型版本控制系统的长处,为团队内部统一管理建立分支的方法、合并操作和关闭分支的方法。在这种模式下,项目有两个长线分支,分别是master和develop,其他的都是临时的、短暂的辅助分支,包括feature(开发特定功能的分支,开发结束后合并到develop)、release(从develop分离出来的为发布做准备的分支,发布结束后合并到master和develop)和hotfix(产品发布后出现问题时紧急建立的分支,直接从master分离,问题修复后合并到master并打上标签,同时还要合并到develop来避免将来的版本遗漏了这个修复工作,如果此时有正在发布中的release分支,还要合并到release分支)。这套方式分支策略简单清晰且容易理解,但是在运用上会稍微有些复杂,需要一些脚本来辅助版本控制的实施。
### 缺陷管理
没有好的团队管理工具必然导致项目进展不顺利,任务管理困难,而引入缺陷管理系统正好可以解决这些问题,通常一个缺陷管理系统都包含了以下的功能:
1. 任务管理(包括必须做什么、谁来做、什么时候完成、现在处于什么状态等)。
2. 直观而且可以检索过去发生的各种问题。
3. 能够对信息进行统一的管理和共享。
4. 能够生成各类报表。
5. 能够关联到其他系统,具有可扩展性。
Redmine是基于Ruby on Rails框架的开源缺陷管理系统,提供了问题管理、代码管理、Wiki等必要的功能,而且支持插件系统,扩展起来也非常容易。
![](./res/redmine_new_issue.png)
如果希望了解和使用Redmine,可以关注[Redmine中文网](http://www.redmine.org.cn/),上面提供了视频教程、经验分享以及其他的安装和使用上的指导。
### 持续集成
为了快速的产生高品质的软件,在团队开发中,持续集成(CI)也是一个非常重要的基础。按照经典的软件过程模型(瀑布模型),集成的工作一般要等到所有的开发工作都结束后才能开始,但这个时候如果发现了问题,修复问题的代价是非常具体的。基本上,集成实施得越晚,代码量越大,解决问题就越困难。持续集成将版本控制、自动化构建、代码测试融入到一起,让这些工作变得自动化和可协作。
在所有的CI工具中,Jenkins和TravisCI是最具有代表性的。
![](./res/jenkins_new_project.png)
\ No newline at end of file
## 团队项目开发
### Day01
1. 企业项目开发团队构成和角色:帮助学生了解项目中的角色及其关系,以小组为单位定义角色。
2. 项目开发流程(软件过程模型)以及各个阶段涉及的相关文档。
3. 团队开发相关工具介绍和环境搭建。
4. 项目选题和理解业务。
### Day02
1. 业务讲解和需求评审。
2. 数据库设计、接口设计、接口文档编撰。
3. 模块划分、任务分配和项目进度安排。
### Day03~Day07
1. 日常开发,每日代码和进度审查。
2. 集中解决项目开发中遇到的公共问题。
3. 项目技术重点难点及其相关技术剖析。
4. 之前未覆盖到的新技术讲解(例如:第三方授权登录、推送机制、消息队列的应用)。
### Day08
1. 单元测试。
2. 集成测试。
3. 接口测试。
4. Selenium自动化测试。
5. 性能测试(压力测试)及其相关工具。
- Apache Benchmark
- SQLSlap
- WebBench
### Day09
1. MySQL性能优化相关。
- SQL优化(执行计划、慢查询分析)
- 读写分离
- 集群配置
- 架构优化
2. 基于Redis的缓存、主从复制、哨兵和集群配置、切片。
3. 日志分析和漏洞分析。
### Day10
1. 项目部署环境搭建。
2. Nginx反向代理配置。
3. Nginx+KeepAlived集群环境配置。
4. HTTPS配置(密钥、证书、配置)。
5. 项目运维相关。
### Day11
1. 虚拟化技术和虚拟化容器。
2. Docker的安装和使用。
3. Docker镜像和虚拟化部署。
### Day12
1. ShowCase
2. 项目评审和总结
### Day13~Day15
1. 模拟面试。
2. 简历指导。
## 团队项目开发
### Day01
1. 企业项目开发团队构成和角色:帮助学生了解项目中的角色及其关系,以小组为单位定义角色。
2. 项目开发流程(软件过程模型)以及各个阶段涉及的相关文档。
3. 团队开发相关工具介绍和环境搭建。
4. 项目选题和理解业务。
### Day02
1. 业务讲解和需求评审。
2. 数据库设计、接口设计、接口文档编撰。
3. 模块划分、任务分配和项目进度安排。
### Day03~Day07
1. 日常开发,每日代码和进度审查。
2. 集中解决项目开发中遇到的公共问题。
3. 项目技术重点难点及其相关技术剖析。
4. 之前未覆盖到的新技术讲解(例如:第三方授权登录、推送机制、消息队列的应用)。
### Day08
1. 单元测试。
2. 集成测试。
3. 接口测试。
4. Selenium自动化测试。
5. 性能测试(压力测试)及其相关工具。
- Apache Benchmark
- SQLSlap
- WebBench
### Day09
1. MySQL性能优化相关。
- SQL优化(执行计划、慢查询分析)
- 读写分离
- 集群配置
- 架构优化
2. 基于Redis的缓存、主从复制、哨兵和集群配置、切片。
3. 日志分析和漏洞分析。
### Day10
1. 项目部署环境搭建。
2. Nginx反向代理配置。
3. Nginx+KeepAlived集群环境配置。
4. HTTPS配置(密钥、证书、配置)。
5. 项目运维相关。
### Day11
1. 虚拟化技术和虚拟化容器。
2. Docker的安装和使用。
3. Docker镜像和虚拟化部署。
### Day12
1. ShowCase
2. 项目评审和总结
### Day13~Day15
1. 模拟面试。
2. 简历指导。
## 团队项目开发
### Day01
1. 企业项目开发团队构成和角色:帮助学生了解项目中的角色及其关系,以小组为单位定义角色。
2. 项目开发流程(软件过程模型)以及各个阶段涉及的相关文档。
3. 团队开发相关工具介绍和环境搭建。
4. 项目选题和理解业务。
### Day02
1. 业务讲解和需求评审。
2. 数据库设计、接口设计、接口文档编撰。
3. 模块划分、任务分配和项目进度安排。
### Day03~Day07
1. 日常开发,每日代码和进度审查。
2. 集中解决项目开发中遇到的公共问题。
3. 项目技术重点难点及其相关技术剖析。
4. 之前未覆盖到的新技术讲解(例如:第三方授权登录、推送机制、消息队列的应用)。
### Day08
1. 单元测试。
2. 集成测试。
3. 接口测试。
4. Selenium自动化测试。
5. 性能测试(压力测试)及其相关工具。
- Apache Benchmark
- SQLSlap
- WebBench
### Day09
1. MySQL性能优化相关。
- SQL优化(执行计划、慢查询分析)
- 读写分离
- 集群配置
- 架构优化
2. 基于Redis的缓存、主从复制、哨兵和集群配置、切片。
3. 日志分析和漏洞分析。
### Day10
1. 项目部署环境搭建。
2. Nginx反向代理配置。
3. Nginx+KeepAlived集群环境配置。
4. HTTPS配置(密钥、证书、配置)。
5. 项目运维相关。
### Day11
1. 虚拟化技术和虚拟化容器。
2. Docker的安装和使用。
3. Docker镜像和虚拟化部署。
### Day12
1. ShowCase
2. 项目评审和总结
### Day13~Day15
1. 模拟面试。
2. 简历指导。
## 网络API接口设计
手机App以及使用了Ajax技术或做了前后端分离的页面都需要通过网络API(Application Programming Interface)和后台进行交互,所谓API,指的应用程序的编程接口;而网络API通畅指的是基于HTTP或HTTPS协议的一个URL(统一资源定位符),通过这个URL我们可以让服务器对某个资源进行操作并返回操作的结果。基于HTTP(S)协议最大的好处就在于访问起来非常的简单方便,而且没有编程语言和应用环境上的差别。
### 设计原则
#### 关键问题
为移动端或者PC端设计网络API接口一个非常重要的原则是:根据业务实体而不是用户界面或操作来设计。如果API接口的设计是根据用户的操作或者界面上的功能设置来设计,随着需求的变更,用户界面也会进行调整,需要的数据也在发生变化,那么后端开发者就要不停的调整API,或者给一个API设计出多个版本,这些都会使项目的开发和维护成本增加。
下面是某个网站开放API的接口,可以看出API的设计是围绕业务实体来进行的,而且都做到了“见名知意”。
| 评论 | |
| ----------------- | ---------------------- |
| comments/show | 获取某条微博的评论列表 |
| comments/by_me | 自己的评论列表 |
| comments/to_me | 收到的评论列表 |
| comments/mentions | @了自己的评论列表 |
| comments/create | 创建一条评论 |
| comments/destroy | 删除一条评论 |
| comments/reply | 回复一条评论 |
注意:上面的API接口并不是REST风格的,关于REST的知识,可以阅读阮一峰老师的[《理解RESTful架构》](http://www.ruanyifeng.com/blog/2011/09/restful.html)以及[《RESTful API设计指南》](http://www.ruanyifeng.com/blog/2014/05/restful_api.html)
API接口返回的数据通常都是JSON或XML格式,我们这里不讨论后者。对于JSON格式的数据,我们需要做到不要返回null这的值,因为这样的值一旦处置失当,会给移动端的开发带来麻烦(移动端可能使用强类型语言)。要解决这个问题可以从源头入手,在设计数据库的时候,尽量给每个字段都加上“not null”约束或者设置合理的默认值约束。
#### 其他问题
1. 更新提示问题:设计一个每次使用系统首先要访问的API,该API会向移动端返回系统更新的相关信息,这样就可以提升用户更新App了。
2. 版本升级问题:API版本升级时应该考虑对低版本的兼容,同时要让新版本和旧版本都能够被访问,可以在URL中包含版本信息或者在将版本号放在HTTP(S)协议头部,关于这个问题有很多的争论,有兴趣的可以看看[stack overflow](https://stackoverflow.com/questions/972226/how-to-version-rest-uris)上面对这个问题的讨论。
3. 图片尺寸问题:移动端对于一张图片可能需要不同的尺寸,可以在获取图片时传入尺寸参数并获取对应的资源;更好的做法是直接使用云存储或CDN(直接提供了图片缩放的功能),这样可以加速对资源的访问。
### 文档撰写
下面以设计评论接口为例,简单说明接口文档应该如何撰写。
#### 评论接口
全局返回状态码
| 返回码 | 返回信息 | 说明 |
| ------ | ------------ | ---------------------------------- |
| 10000 | 获取评论成功 | |
| 10001 | 创建评论成功 | |
| 10002 | 无法创建评论 | 创建评论时因违反审核机制而无法创建 |
| 10003 | 评论已被删除 | 查看评论时评论因不和谐因素已被删除 |
| 10004 | …… | …… |
1. **GET** `/comments/{article-id}`
开发者:王大锤
最后更新时间:2018年8月10日
标签:v 1.0
接口说明:获取指定文章的所有评论
使用帮助:默认返回20条数据,需要在请求头中设置身份标识(key)
请求参数:
| 参数名 | 类型 | 是否必填 | 参数位置 | 说明 |
| ------ | ------ | -------- | -------- | ------------------------------------ | |
| page | 整数 | 否 | 查询参数 | 页码,默认值1 |
| size | 整数 | 否 | 查询参数 | 每次获取评论数量(10~100),默认值20 |
响应信息:
```JSON
{
"code": 10000,
"message": "获取评论成功",
"page": 1,
"size": 10,
"contents": [
{
"userId": 1700095,
"nickname": "王大锤",
"pubDate": "2018年7月31日",
"content": "小编是不是有病呀"
/* ... */
},
{
"userId", 1995322,
"nickname": "白元芳",
"pubDate": "2018年8月2日",
"content": "楼上说得好"
/* ... */
}
]
/* ... */
}
```
2. **POST** `/comments/{article-id}`
开发者:王大锤
最后更新时间:2018年8月10日
标签:v 1.0
接口说明:为指定的文章创建评论
使用帮助:暂无
请求参数:
| 参数名 | 类型 | 是否必填 | 参数位置 | 说明 |
| ------- | ------ | -------- | -------- | ------------------------------------ |
| userId | 字符串 | 是 | 消息体 | 用户ID |
| token | 字符串 | 是 | 消息体 | 用户的令牌或URL的签名 |
| content | 字符串 | 是 | 消息体 | 评论的内容 |
响应信息:
```JSON
{
"code": 10001,
"message": "创建评论成功",
"comment": {
"pubDate": "2018年7月31日",
"content": "小编是不是有病呀"
/* ... */
}
/* ... */
}
```
\ No newline at end of file
## 团队项目开发
### Day01
1. 企业项目开发团队构成和角色:帮助学生了解项目中的角色及其关系,以小组为单位定义角色。
2. 项目开发流程(软件过程模型)以及各个阶段涉及的相关文档。
3. 团队开发相关工具介绍和环境搭建。
4. 项目选题和理解业务。
### Day02
1. 业务讲解和需求评审。
2. 数据库设计、接口设计、接口文档编撰。
3. 模块划分、任务分配和项目进度安排。
### Day03~Day07
1. 日常开发,每日代码和进度审查。
2. 集中解决项目开发中遇到的公共问题。
3. 项目技术重点难点及其相关技术剖析。
4. 之前未覆盖到的新技术讲解(例如:第三方授权登录、推送机制、消息队列的应用)。
### Day08
1. 单元测试。
2. 集成测试。
3. 接口测试。
4. Selenium自动化测试。
5. 性能测试(压力测试)及其相关工具。
- Apache Benchmark
- SQLSlap
- WebBench
### Day09
1. MySQL性能优化相关。
- SQL优化(执行计划、慢查询分析)
- 读写分离
- 集群配置
- 架构优化
2. 基于Redis的缓存、主从复制、哨兵和集群配置、切片。
3. 日志分析和漏洞分析。
### Day10
1. 项目部署环境搭建。
2. Nginx反向代理配置。
3. Nginx+KeepAlived集群环境配置。
4. HTTPS配置(密钥、证书、配置)。
5. 项目运维相关。
### Day11
1. 虚拟化技术和虚拟化容器。
2. Docker的安装和使用。
3. Docker镜像和虚拟化部署。
### Day12
1. ShowCase
2. 项目评审和总结
### Day13~Day15
1. 模拟面试。
2. 简历指导。
## 项目实战 - 开启团队项目
### 创建项目
我们的项目使用Git作为版本控制工具,首先可以在代码托管平台上创建一个新项目。这里我们使用了国内的[“码云”](https://gitee.com)来创建项目,并通过该平台实现版本控制和缺陷管理,当然如果愿意也可以使用[github](https://github.com/)或者国内的[CODING](https://coding.net/))来做同样的事情,当然也可以自己用诸如[Gitlab](https://gitlab.com)这样的工具来搭建自己的代码仓库。创建好项目之后,我们先为项目添加一个名为`.gitignore`文件,该文件用来忽略掉那些不需要纳入版本控制系统的文件,如果不知道怎么编写该文件,可以使用gitignore.io](https://www.gitignore.io/)网站提供的在线生成工具,如下所示。
![](./res/gitignore_io.png)
### 初始版本
接下来,我们将项目克隆到本地,并为项目创建真正意义上的初始版本。
```Shell
git clone https://gitee.com/jackfrued/fang.com.git
cd fang.com
python3 -m venv venv
source venv/bin/activate
pip install -U pip
pip install django django-celery django-redis djangorestframework pymysql redis pillow
pip freeze > requirements.txt
```
**提示**:在使用pip安装依赖项以后,可以通过`pip check`来检查依赖项是否兼容,确保万无一失。如果希望使用国内的PyPI源,可以按照如下所示的方式进行配置。在用户主目录下找到或创建名为`.pip`的文件夹,并在该文件夹中添加一个`pip.conf`文件,其内容如下所示。
```INI
[global]
trusted-host=mirrors.aliyun.com
index-url=http://mirrors.aliyun.com/pypi/simple/
# index-url=https://pypi.tuna.tsinghua.edu.cn/simple/
# index-url=https://mirrors.ustc.edu.cn/pypi/web/
# index-url=https://pypi.douban.com/simple
```
上面的配置文件中使用了阿里云的PyPI镜像,由于该镜像没有使用HTTPS,所以要先将其设置为受信任的站点,当然也可以直接使用下面提供的其他HTTPS镜像。使用Windows的用户可以在用户主目录下创建`pip`文件夹(注意前面有没有点)并添加该配置文件,文件名为`pip.ini`
接下来创建项目和应用,通常我们会对项目中的功能进行垂直拆分,因此需要创建多个应用来对应不同的功能模块。
```Shell
django-admin startproject fang .
python manage.py startapp common
python manage.py startapp forum
python manage.py startapp rent
```
对项目的公共配置文件进行必要修改后,就可以通过反向工程(根据关系型数据库中的表来生成模型从而减少不必要的重复劳动)完成对模型类的创建,反向工程生成的模型可能需要适当的修改和调整才能应用于项目,尤其是存在多对多关系的时候。
```Shell
python manage.py inspectdb > common/models.py
```
如果需要使用Django的admin项目或者第三方xadmin,还需要执行迁移操作来创建额外的数据库表。
```Shell
python manage.py migrate
```
至此,我们就可以将当前的工作纳入版本控制并同步到服务器。
```Shell
git add .
git commit -m '项目初始版本'
git push
```
### 日常开发
不管是使用“git-flow”还是“github-flow”,都不能够在master上面直接进行开发,对于后者要创建自己的分支并在上面进行开发。
```Shell
git checkout -b jackfrued
git add .
git commit -m '提交的原因'
git push origin jackfrued
git branch -d jackfrued
git checkout master
git pull
git checkout -b jackfrued
```
## 项目部署上线指南
### 更新Python环境到3.x
```Shell
yum -y install 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
wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz
xz -d Python-3.7.0.tar.xz
tar -xvf Python-3.7.0.tar
cd Python-3.7.0
./configure --prefix=/usr/local/python3 --enable-optimizations
make && make install
cd ~
vim .bash_profile
export PATH=$PATH:/usr/local/python3/bin
ln -s /usr/local/python3/bin/python3 /usr/bin/python3
ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3
```
### 项目目录结构
下面是项目的目录结构,四个文件夹`conf``logs``src``venv`分别用来保存项目的配置文件、日志文件、源代码和虚拟环境。`conf`目录下的子目录`cert`中保存了配置HTTPS需要使用的证书和密钥。
```
project
├── conf
│   ├── cert
│   │   ├── 214915882850706.key
│   │   └── 214915882850706.pem
│   ├── nginx.conf
│   └── uwsgi.ini
├── logs
│   ├── access.log
│   ├── error.log
│   └── uwsgi.log
├── requirements.txt
├── src
│   └── fang
│   ├── common
│   ├── fang
│   ├── forum
│   ├── manage.py
│   ├── README.md
│   ├── rent
│   ├── 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
```
### uWSGI的配置
可以激活项目的虚拟环境并通过pip安装uWSGI。
```Shell
pip install uwsgi
```
`/root/project/conf/uwsgi.ini`
```INI
[uwsgi]
# 配置前导路径
base=/root/project
# 配置项目名称
name=fang
# 守护进程
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处理)。
```Shell
uwsgi --ini uwsgi.ini &
```
### Nginx的配置
#### 全局配置
`/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;
}
```
#### 局部配置
`/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/fang/static/;
expires 30d;
}
}
```
到此为止,我们可以启动Nginx来访问我们的应用程序,HTTP和HTTPS都是没有问题的,如果Nginx已经运行,在修改配置文件后,我们可以用下面的命令重新启动Nginx。
```Shell
nginx -s reload
```
#### 负载均衡配置
下面的配置中我们使用Nginx为HTTP、HTTPS以及Redis配置负载均衡。
```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;
}
stream {
upstream redis.local {
server 172.18.61.250:36379;
server 172.18.61.250:46379;
server 172.18.61.250:56379;
}
server {
listen 6379;
proxy_pass redis.local;
}
}
http {
upstream fang.com {
server 172.18.61.250:801;
server 172.18.61.250:802;
server 172.18.61.250:803;
}
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服务器(3个节点)和Redis服务器(2个节点,每个节点是1个master和2个slave的配置)都是通过Docker来创建的,实际部署的时候无论是否使用Docker进行部署,这些主机应该都是独立的服务器。
### Keepalived
当使用Nginx进行负载均衡配置时,要考虑负载均衡服务器宕机的情况。为此可以使用Keepalived来实现负载均衡主机和备机的热切换,从而保证系统的高可用性。Keepalived的配置还是比较复杂,通常由专门做运维的人进行配置,一个基本的配置可以参照[《Keepalived的配置和使用》](https://www.jianshu.com/p/dd93bc6d45f5)
### 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中完成项目的部署,并且将整个部署好的容器打包成镜像文件进行分发和安装,这样就可以解决项目在多个节点上进行部署时可能遇到的麻烦。
\ No newline at end of file
## 团队项目开发
### Day01
1. 企业项目开发团队构成和角色:帮助学生了解项目中的角色及其关系,以小组为单位定义角色。
2. 项目开发流程(软件过程模型)以及各个阶段涉及的相关文档。
3. 团队开发相关工具介绍和环境搭建。
4. 项目选题和理解业务。
### Day02
1. 业务讲解和需求评审。
2. 数据库设计、接口设计、接口文档编撰。
3. 模块划分、任务分配和项目进度安排。
### Day03~Day07
1. 日常开发,每日代码和进度审查。
2. 集中解决项目开发中遇到的公共问题。
3. 项目技术重点难点及其相关技术剖析。
4. 之前未覆盖到的新技术讲解(例如:第三方授权登录、推送机制、消息队列的应用)。
### Day08
1. 单元测试。
2. 集成测试。
3. 接口测试。
4. Selenium自动化测试。
5. 性能测试(压力测试)及其相关工具。
- Apache Benchmark
- SQLSlap
- WebBench
### Day09
1. MySQL性能优化相关。
- SQL优化(执行计划、慢查询分析)
- 读写分离
- 集群配置
- 架构优化
2. 基于Redis的缓存、主从复制、哨兵和集群配置、切片。
3. 日志分析和漏洞分析。
### Day10
1. 项目部署环境搭建。
2. Nginx反向代理配置。
3. Nginx+KeepAlived集群环境配置。
4. HTTPS配置(密钥、证书、配置)。
5. 项目运维相关。
### Day11
1. 虚拟化技术和虚拟化容器。
2. Docker的安装和使用。
3. Docker镜像和虚拟化部署。
### Day12
1. ShowCase
2. 项目评审和总结
### Day13~Day15
1. 模拟面试。
2. 简历指导。
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