一、区域选择模块
- 数据库建模
from App.ext import db
# 字母模型类
class Letter(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(2))
citys = db.relationship('City', backref='letter', lazy=True)
# 城市模型类
class City(db.Model):
# 注意,不是自增长
id = db.Column(db.Integer, primary_key=True)
regionName = db.Column(db.String(100))
cityCode = db.Column(db.String(10))
pinYin = db.Column(db.String(10))
c_letter = db.Column(db.Integer, db.ForeignKey(Letter.id))
- 数据导入(数据库操作)
# 从JSON到到数据库脚本 city-mysql.py
import json
import pymysql
# 链接数据库
db = pymysql.Connect(host="localhost", port=3306, user="root", password="123456", database="Tpp", charset="utf8")
# 数据库游标
cursor = db.cursor()
# 打开文件
with open('city.json', 'r') as f:
# json形式加载
city_collection = json.load(f)
# 获取所有的键
returnValue = city_collection.get('returnValue')
letters = returnValue.keys()
# 遍历插入到数据库中
for letter in letters:
# 游标,执行SQL语句 (注意values中的值是字符串)
db.begin()
cursor.execute("insert into letter(name) values('{}')".format(letter))
db.commit()
# 获取字母对应的主键
db.begin()
cursor.execute("select id from letter where name='{}';".format(letter))
db.commit()
result = cursor.fetchone()
letter_id = result[0]
# 获取key对应的value
citys = returnValue.get(letter)
for c_obj in citys:
# insert into city(regionName,cityCode,pinYin,c_letter)
regionName = c_obj.get('regionName')
cityCode = c_obj.get('cityCode')
pinYin = c_obj.get('pinYin')
db.begin()
cursor.execute("insert into city(regionName,cityCode,pinYin,c_letter) values('{}','{}','{}',{});".format(regionName,cityCode,pinYin,letter_id))
db.commit()
开始使用数据库时,驱动程序会发出一个BEGIN之后COMMIT,符合规范的pythonDBAPI始终以这种方式工作.
- 返回JSON数据
{
'status':200,
'msg': '获取城市列表数据成功',
"data":{
"A":[
{
"id":3643,
"parentId":0,
"regionName":"阿坝",
"cityCode":513200,
"pinYin":"ABA"
},
{
"id":3090,
"parentId":0,
"regionName":"阿克苏",
"cityCode":652901,
"pinYin":"AKESU"
}],
"B":[
{
"id":3643,
"parentId":0,
"regionName":"阿坝",
"cityCode":513200,
"pinYin":"ABA"
},
{
"id":3090,
"parentId":0,
"regionName":"阿克苏",
"cityCode":652901,
"pinYin":"AKESU"
}]
...
}
@marshal_with()装饰器,而在
flask-RESTful
文档中高级:嵌套字段
并没有使用装饰器,而是通过函数调用的方式实现格式化输出的
二、用户系统分析
- 字段
用户名
密码
邮箱
手机号
用户状态(是否激活)
用户权限
用户token
头像
逻辑删除
- 业务流程
用户名
密码
邮箱
发一个邮件,点击激活
不激活权限会被限制
三、用户注册
- 数据模型(建模)
class User(db.Model):
# 主键
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
# 用户名
name = db.Column(db.String(30), unique=True)
# 密码
password = db.Column(db.Integer(255))
# 邮箱
email = db.Column(db.String(30), unique=True)
# 手机
iphone = db.Column(db.String(20))
# 头像
icon = db.Colum(db.String(100), default='head.png')
# 是否激活
is_active = db.Column(db.Boolean, default=False)
# 用户令牌
token = db.Column(db.String(255))
# 权限
permissions = db.Column(db.Integer, default=1)
# 是否被删除
is_delete = db.Column(db.Boolean, default=False)
RESTful前后端分离,而要给移动端写接口,移动端是没有cookie的,就是用token来作代替方案。
- 注册接口
""" 注册接口数据
{
"returnCode": "0",
"returnValue": {
"token": "8f715ea6-62c5-45a1-9dab-4367f1bf24a5",
"username": "MM",
"permissions": "1"
},
"status": "200",
"err": "None"
}
"""
# 请求参数格式
parser = reqparse.RequestParser()
parser.add_argument('username', type=str, required=True, help='请提供用户名')
parser.add_argument('password', type=str, required=True, help='请提供密码')
parser.add_argument('email', type=str, required=True, help='请提供邮箱')
parser.add_argument('iphone', type=str, required=True, help='请提供手机号码')
# 定义格式的需求,可以继承 fields.Raw 类并且实现格式化函数
class IconForm(fields.Raw):
def format(self, value):
return '/static/img/' + value
# 输出格式
user_fields = {
'username': fields.String,
'token': fields.String,
'permissions': fields.String,
'icon': IconForm(attribute='icon') # attribute='对应key'
}
result_fields = {
'returnCode': fields.String,
'returnValue': fields.Nested(user_fields),
'status': fields.String,
'err': fields.String(default='None')
}
# 用户注册接口
class RegisterUser(Resource):
@marshal_with(result_fields)
def post(self):
parse = parser.parse_args()
user = User()
user.username = parse.get('username')
user.password = parse.get('password')
user.email = parse.get('email')
user.iphone = parse.get('iphone')
user.token = str(uuid.uuid4())
print(user.username)
data = {
'returnCode': '0',
'returnValue': user,
'status': '200'
}
try:
print(user.token)
db.session.add(user)
db.session.commit()
print('hello')
return data
except Exception as e:
data['err'] = '用户已经存在!'
data['returnValue'] = None
data['status'] = '406'
return data
四、flask-mail插件
- 安装
pip install flask-mail
- 配置(app.config配置)
MAIL_SERVER = "smtp.163.com"
MAIL_USERNAME = "xxxxxx@163.com"
MAIL_PASSWORD = "xxxxxx"
- 初始化
from flask_mail import Mail
mail = Mail()
mail.init_app(app)
- 使用
# 邮件信息
msg = Message(subject="Tpp激活邮件", # 主题
recipients=[user.email], # 收件人
sender="xxxxxxx@163.com") # 发件人
# 传入网页(即主体内容,可以为空)
body_html = render_template('active.html', username=user.username,active_url='http://localhost:5000/api/v1/useractive?token='+user.token)
msg.html = body_html
# 发送邮件
mail.send(msg)
MAIL_PASSWORD密码设置,可以在官网中设置
客户端授权密码
开启,即可以不使用登录密码!
五、用户激活
用户激活,其实也就是一个接口。
这个接口可以根据链接找到对应用户,并修改用户的状态。
可以该{token:userId}存储信息。
(注册接口)
# 注册请求
# 获取用户信息
# 存储数据库
# token:userid 存储cache[超时设置]
cache.set(user.token, user.id, timeout=60)
# 发送邮件
(激活接口)
# 激活请求
# 获取用户token
# 根据token在cache中获取对应的userid
userid = cache.get(token)
# 删除token
cache.delete(token)
# 根据userid找到对应用户对象
# 修改用户状态
# 保存到数据库
redis缓存{token:userId}就可以使用flask-cache,它可以缓存视图,也可以直接使用原生操作用于存取数据。
六、用户登录
# 登录请求
# 获取用户名、密码
# 根据用户名和密码验证
users = User.query.filter(User.username==username).filter(User.password==password)
if users.count()>0: # 账号密码正确
# 再验证是否激活
# 返回数据
成功,返回用户信息(用户名、token...)
失败,返回用户名或密码错误提示
七、密码安全模块
generate_password_hash(password): 输入相同,但每次输出结果都是不一样的
check_password_hash(hash,password): 出入hash与输入的值比较是否相等
八、用户修改密码
# 修改密码请求
# 获取token、旧密码、新密码
# 根据token获取用户信息
# 验证操作
旧密码一致,修改
旧密码不一致,不修改
# 返回数据
九、用户权限
# 权限设计与限制
0 未登陆
列表A(预览权限)
1 普通用户
列表A + 列表B(预览权限)
2 会员
列表A + 列表B
4 超级会员
列表A + 列表B + 下载权限
# 资源限制
if user.permissions == 1:
return {'msg': '麻麻地啦', 'data': ' 列表A + 列表B(预览权限)'}
elif user.permissions == 2:
return {'msg': '会员,奔小康水平', 'data': ' 列表A + 列表B'}
elif user.permissions == 4:
return {'msg': '超级会员,请叫我土豪', 'data': ' 列表A + 列表B + 下载'}
十、自定义权限(装饰器)
# 很多资源都有权限问题,那都会需要上述判断处理
# 类似接口Blueprint定义接口时,通过装饰器实现统一
# 添加一个权限装饰器,给需要权限限制的加上装饰器即可
- Linux文件读写权限
r 》 4 》 100
w 》 2 》 010
x 》 1 》 001
6表示有读写权限 》 110 》
判断是否有读权限? 位运算: 110 & 100 》 100 》 r
判断是否有可执行权限? 位运算: 110 & 001 》 000 》 无
- 装饰器
# 权限管理装饰器 [只管有无权限,什么数据不管]
def check_permissions_control(permissions):
def check_permissions(func):
def check(*args, **kwargs):
parse = parser.parse_args()
token = parse.get('token')
if token: # 验证token
users = User.query.filter(User.token == token)
if users.count() > 0: # 有用户
user = users.first()
if user.permissions & permissions == permissions: # 有权限
# 权限,即执行装饰的函数,否则报错跳出
return func(*args, **kwargs)
else:
abort(403,message='你没有操作权限,请联系管理员')
else: # 未登录
abort(401, message='你还没登录,请登录后操作')
else: # 未登录
abort(401, message='你还没登录,请登录后操作')
return check
return check_permissions
按位与: &
按位或: |
if user.permissions & permissions == permissions: 权限判断
十一、电影信息接口+权限管理
- 电影信息接口
- 数据库结构
- 模型结构
- 插入数据(数据库)
- 定义接口
- 参数设置
flag: 0 全部
flag: 1 热映
flag: 2 即将上映
flag = parse.get('flag') or 0
- 返回数据
- 添加电影接口(权限管理)
- 权限判断(添加装饰器即可)
通过上述装饰器方式,处理权限
有权限,才会调用post接口的函数处理
- post接口(只管数据)
获取数据
存入数据库
返回数据
十二、电影院信息接口
- 数据库结构
- 模型结构
- 插入数据
- 定义接口
- 参数设置
city: 城市
district: 地区
sort: 排序
limit: 显示条数
- 返回数据
十三、图片上传
# settings.py文件中
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
UPLOAD_FOLDER = os.path.join(BASE_DIR, 'App/static/img/')
# UploadFile.py文件上传api
parser = reqparse.RequestParser()
parser.add_argument('token', type=str, required=True, help='缺少token')
parser.add_argument('headimg', type=werkzeug.datastructures.FileStorage, location='files',required=True, help='请选择图片')
class UserHeadResource(Resource):
@marshal_with(result_fields)
def post(self):
parse = parser.parse_args()
token = parse.get('token')
returndata = {}
users = User.query.filter(User.token == token)
if users.count()>0:
user = users.first()
# 图片数据
imgfile = parse.get('headimg')
# 图片名称 secure_filename(imgFile.filename)
filename = '%d-%s' % (user.id,secure_filename(imgfile.filename))
# 图片路径
filepath = os.path.join(UPLOAD_FOLDER, filename)
# 保存文件
imgfile.save(filepath)
# 保存到数据库
user.icon = filename
db.session.add(user)
db.session.commit()
# 返回数据
returndata['status'] = 200
returndata['msg'] = '文件上传成功'
returndata['data'] = user
return returndata
else:
returndata['status'] = 401
returndata['msg'] = '上传文件失败'
returndata['err'] = 'token错误'
return returndata
备注: img目录需要有!
十四、项目依赖问题
requirements.txt 文件 里面记录了当前程序的所有依赖包及其精确版本号。
其作用是用来在另一台PC上重新构建项目所需要的运行环境依赖。
- 生成requirements.txt
pip freeze > requirements.txt
- 安装requirements.txt依赖
pip install -r requirements.txt