User 功能开发
“⽤户中⼼” 模块功能概览
- 获取短信验证码
- 通过验证码登录、注册
- 获取个⼈资料
- 修改个⼈资料
- 头像上传
User 模型及接⼝规划
- User 模型设计 (仅作参考)
Field | Description |
---|---|
phonenum | |
nickname | 昵称 |
sex | 性别 |
birth_year | 出⽣年 |
birth_month | 出⽣⽉ |
birth_day | 出⽣⽇ |
avatar | 个⼈形象 |
location | 常居地 |
- 接⼝规划
- 接⼝1: 提交⼿机号,发送短信验证码
- 接⼝2: 提交验证码,登录注册
开发中的难点
- 如何向前端返回 json 数据
- 短信发送如何处理
- 验证码如何处理
- 验证码需随机产⽣,登录注册验证码⼀般为 4~6 位随机数
- 第⼀个接⼝获取到的验证码在登录接⼝还需要使⽤,需如何保存
- 每个验证码都有有效期,应如何处理
RESTful 与前后端分离
一 . RESTful
- RESTful 最佳实践 http://www.ruanyifeml
- RESTful 是⼀种⽹络软件架构⻛格, ⽽⾮⽤ URL 定位⼀个⽹络资源
- ⽤ HTTP 动词描述对资源的操作
- GET: 获取资源
- POST: 新建资源
- PUT: 更新资源
- PATCH: 局部更新资源
- DELETE: 删除资源
- 误区
-
URL 中使⽤动词
-
URL 中出现版本号
(1) 参数⽤ querystring 表示, ⽽不要拼在 path 部错误示范: GET /user/books/foo/bar
(2) 正确示范: GET /user/books?foo=bar -
状态码的使⽤要精确
2xx:操作成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误 -
RESTful 与 Django REST framework 的区别
二 . 前后端分离
传统 Web 开发, view 函数中需要进⾏模版渲染, 逻辑处理与显示的样式均需要后端开发.
变成前后端分离后, 显示效果的处理完全交给前端来做, 前端⾃由度变⼤. 后端只需要传递前端需要
的数据即可, 将后端⼈员从繁琐的显示处理中解放出来, 专⼼处理业务逻辑
- 优点: 前端负责显示, 后端负责逻辑, 分⼯更加明确, 彻底解放前、后端开发者
- JSON: 完全独⽴于编程语⾔的⽂本格式, ⽤来存储和表示数据
- 前后端分离后的开发流程
三. 代码实现
from json import dumps
from django.http import HttpResponse
def render_json(data=None, error_code=0):
'''将返回值渲染为 JSON 数据'''
result = {
'data': data, # 返回给前端的数据
'code': error_code # 状态码 (status code)
}
json_str = dumps(result, ensure_ascii=False, separators=[',', ':'])
return HttpResponse(json_str)
四. 接⼝的定义
- 定义接⼝基本格式
{
"code": 0, // 错误码 (status code)
"data": { // 接口数据
"user": {
"uid": 123321,
"username": "Lion",
"age": 21,
"sex": "Male"
},
"date": "2018-09-12",
}
}
- 定义返回码
code | description |
---|---|
0 | 正常 |
1000 | 服务器内部错误 |
1001 | 参数错误 |
1002 | 数据错误 |
- 详细定义每⼀个接⼝的各个名称 (Name)
- 描述 (Description)
- ⽅法 (Method)
- 路径 (Path)
- 参数 (Params)
- 返回值 (Returns)
- 接⼝定义举例:
接⼝名称:提交验证码登录
- Description: 根据上⼀步的结果提交需要的数据
- Method: POST
- Path: /user/login
- Params:
field | required | type | description |
---|---|---|---|
phone | Yes | int | ⼿机号 |
code | Yes | int | 验证码 |
- Return:
field | required | type | description |
---|---|---|---|
uid | Yes | int | ⽤户 id |
nickname | Yes | str | ⽤户名 |
age | Yes | int | 年龄 |
sex | Yes | str | 性别 |
location | Yes | str | 常居地 |
avatars | Yes | list | 头像 URL 列表, 最多为 6 张 |
示例:
{
"code": 0,
"data": {
"uid": 123, // 用户 id
"nickname": "Miao", // 用户名
"age": 21, // 年龄
"sex": "M", // 性别
"location": "China/Beijing", // 常居地
"avatars": "http://xxx.com/icon/1.jpg" // 头像
},
}
第三⽅短信平台的接⼊
一 . 短信验证整体流程:
- ⽤户调⽤应⽤服务器 “获取验证码接⼝” (点击 “获取验证码” 按钮时触发)
- 应⽤服务器调⽤短信平台接⼝, 将⽤户⼿机号和验证码发送到短信平台
- 短信平台向⽤户发送短信
- ⽤户调⽤ “提交验证码接⼝”,向应⽤服务器进⾏验证
- 验证通过,登录、注册……
二 . 可选短信平台
阿⾥云: https://www.aliyun.com/product/sms
腾讯云: https://cloud.tencent.com/document/product/382
⽹易云: https://netease.im/sms
(大公司的注册流程比较严格,可选下面两项)
云之讯: https://www.ucpaas.com/
互亿⽆线: http://www.ihuyi.com/
三 . 注册账号后, 将平台分配的 APP_ID 和 APP_SECRET 添加到配置中
APP_ID: 平台分配的 ID
APP_SECRET: 与平台交互时, ⽤来做安全验证的⼀段加密⽤的⽂本, 不能泄漏给其他⼈
四 . 注册平台的短信模版
五 . 按照平台接⼝⽂档开发接⼝
短信平台的接⼝通常是 HTTP 或 HTTPS 协议, 接⼊的时候只需按照接⼝格式发送 HTTP 请求
即可
接⼝的返回值⼀般为 json 格式,收到返回结果后需要解析
Django 中的缓存
- 接⼝及⽤法
from django.core.cache import cache
# 在缓存中设置 age = 123, 10秒过期
cache.set('age', 123, 10)
# 获取 age
a = cache.get('age')
print(a)
# ⾃增
x = cache.incr('age')
print(x)
- 使⽤ Redis 做缓存后端
安装
pip install django-redis
settings 配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PICKLE_VERSION": -1,
}
}
}
- 利⽤过期时间可以处理⼀些定时失效的临时数据, ⽐如⼿机验证码
Cookie、 Session 机制剖析
一 . 产⽣过程
- 浏览器: 向服务器发送请求
- 服务器: 接受并创建 session 对象 (该对象包含⼀个 session_id)
- 服务器: 执⾏ views 函数, 并得到⼀个 response 对象
- 服务器: 执⾏ response.set_cookie(‘sessionid’, session_id) 将 session_id 写⼊ cookie
- 服务器: 将 response 传回浏览器
- 浏览器: 读取 response 报⽂, 从 Cookies 取出 session_id 并保存
二 . 后续请求
- 浏览器: 向服务器发送请求, session_id 随 Cookies ⼀同发给 Server
- 服务器: 从 Headers 的 Cookies 中取出 session_id
- 服务器: 根据 session_id 找出对应的数据, 确认客户端身份
三 . Django 中的代码实现
class SessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
self.get_response = get_response
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore # 设置 Session 存储类
def process_request(self, request):
# 从 Cookie 获取 sessionid
session_key = request.COOKIES.get('session_id')
# 通过 session_key 获取之前保存的数据
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
try:
# View 函数结束后, 获取 session 状态
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
pass
else:
# 如果 Cookie 中有 sessionid, 但 session 为空,
# 说明 view 中执行过 session.flush 等操作,
# 直接删除 Cookie 中的 session
if 'session_id' in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
)
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
# 设置过期时间
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
# 保存会话数据, 并刷新客户端 Cookie
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
# 让客户端将 sessionid 添加到 Cookie 中
response.set_cookie(
'session_id',
request.session.session_key,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
)
return response