项目描述
该项目实现电影系统的后台接口,包括用户,电影,场次,订单,评论,优惠券,推荐,收藏等多个模块,同时提供一个管理后台进行操作和管理。使用Swagger使用接口的可视化,方便测试。
项目截图
项目结构
MovieAdmin
├── requirements.txt # 第三方库依赖文件
└── src
├── app
│ ├── admin # 管理后台文件
│ │ ├── admin.py
│ │ ├── __init__.py
│ │ └── views.py
│ ├── api # 接口文件
│ │ ├── comment.py
│ │ ├── coupon.py
│ │ ├── favorite.py
│ │ ├── __init__.py
│ │ ├── movie.py
│ │ ├── order.py
│ │ ├── password.py
│ │ ├── screen.py
│ │ ├── session.py
│ │ └── user.py
│ ├── __init__.py
│ ├── models.py # 数据库模型
│ ├── static # 静态文件,保存用户头像和电影海报等
│ │ ├── images
│ │ │ ├── poster
│ │ │ └── user
│ │ │ └── default.jpg
│ │ └── js
│ │ ├── jquery-3.2.1.min.js
│ │ └── md5.min.js
│ ├── templates
│ │ ├── admin
│ │ │ └── index.html
│ │ └── admin.html
│ └── utils.py
├── instance # 私密配置文件夹
│ ├── __init__.py
│ └── secure_conf.py
└── server.py # 主服务运行文件
项目运行
安装Python2.7环境
安装第三方依赖
pip install -r requirements.txt
运行
python server.py
运行后会在 src 目录下生成 data.sqlite
文件,存储数据库相关数据,访问 http://localhost:5000/admin/ 进入后台管理系统,管理系统账号密码保存在 src/instance/secure_conf.py
中,可自行更改。
项目实现
数据库建模
电影系统的主要功能是用户可以浏览电影信息并选择场次和座位进行下单购票,完成支付后会随机赠送优惠券,可在下次购票时使用。观看完电影之后可进行电影评论,用户也可以收藏电影,首页会定期推荐新上映的热门电影,因此该项目至少涉及以下几张数据表:用户表,电影表,场次表,订单表,评论表,优惠券表,推荐表,收藏表。
完整的数据库模型如下图:
以上数据库模型,以users为例,对应的sql语句如下:
-- ----------------------------
-- Table structure for users 用户表
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` varchar(11) NOT NULL COMMENT '用户id(手机号码)',
`password` varchar(32) NOT NULL COMMENT '登录密码',
`payPassword` varchar(32) NOT NULL COMMENT '支付密码',
`nickname` varchar(20) NOT NULL DEFAULT '' COMMENT '昵称',
`money` float NOT NULL COMMENT '余额',
`description` varchar(50) NOT NULL DEFAULT '' COMMENT '个性签名',
`avatar` varchar(32) NOT NULL DEFAULT '' COMMENT '头像路径',
`isAdmin` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
对象关系映射 ORM
对象关系映射(Object Relational Mapping,简称ORM),用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。对象关系映射(Object-Relational Mapping)提供了概念性的、易于理解的模型化数据的方法。在大多数的 Web 应用中,都会将数据存储在关系型数据库中,使用 ORM 技术可以在代码通过操作对象实现数据库操作,无需自己写sql语句,如前面一大段建表的语句,ORM模型的简单性简化了数据库查询过程。使用ORM查询工具,用户可以访问期望数据,而不必理解数据库的底层结构。Flask 中可使用Flask-SQLAlchemy
来实现 ORM。
models.py
中定义了每张数据库表对应的对象,通过对这些对象的操作,可以直接实现对数据库的增删改查,无需编写sql语句。以订单表为例:
# -*- coding: utf-8 -*-
from datetime import datetime, date
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Order(db.Model):
"""订单"""
__tablename__ = 'orders'
__table_args__ = {'mysql_engine': 'InnoDB'} # 支持事务操作和外键
id = db.Column(db.String(32), primary_key=True)
screenId = db.Column(db.String(32), db.ForeignKey('screens.id'), nullable=False)
seat = db.Column(db.PickleType, doc='座位号(逗号分隔)', nullable=False)
username = db.Column(db.String(32), db.ForeignKey('users.id'), nullable=False)
createTime = db.Column(db.DateTime, doc='创建时间', nullable=False)
status = db.Column(db.Boolean, doc='订单状态(0:未支付,1:已支付)', default=0, nullable=False)
couponId = db.Column(db.String(32), db.ForeignKey('coupons.id'))
payPrice = db.Column(db.Float, doc='实际支付', nullable=False)
totalPrice = db.Column(db.Float, doc='原价', nullable=False)
管理系统
成功使用 ORM 建了项目所需的数据库表,现在我们要考虑一个问题,我们要怎么管理数据库中的数据呢,比如一个电影系统,肯定会有电影的信息和上映的场次信息,这个时候就需要一个后台管理系统,登录该系统后可以对数据库中的数据进行增删改查。使用Flask-Admin
可以很方便的构建一个后台管理系统
初始化
from flask import Flask
from flask_admin import Admin
app = Flask(__name__)
admin = Admin(app, name=u'电影管理系统') # 等价于下面两句
# admin = Admin(name=u'电影管理系统')
# admin.init_app(app)
app.run()
运行之后访问 http://localhost:5000/admin/ 就可以看到一个简单的管理界面。
增加视图
作为后台管理系统,当然需要添加登录界面,保证管理系统的安全性,用Flask-Login
做身份验证,Flask-WTF
防止跨站请求伪造攻击(CSRF)。
views.py
文件中,添加一个登录表单的类,其中的 User 类就是数据模型中的用户表,此时 User 类还需要继承 flask_login 中的UserMixin类。
from models import User
from flask_wtf import FlaskForm
from wtforms import fields, validators, ValidationError
class LoginForm(FlaskForm):
"""登录表单"""
username = fields.StringField(validators=[validators.data_required()])
password = fields.PasswordField(validators=[validators.data_required()])
def validate_username(self, field):
"""登录校验"""
user = self.get_user()
if user is None:
raise ValidationError('Invalid user')
if self.password.data != user.password:
raise ValidationError('Invalid password')
def get_user(self):
return User.query.filter_by(id=self.username.data, isAdmin=1).first()
有了登录表单之后,增加一个视图,如果未登录就重定向到登录界面,已登录就显示管理系统首页,这里需要在templates文件夹中添加模板文件。
import flask_login as login
from flask_login import login_required
from flask_admin import expose, AdminIndexView, helpers
class MyAdminIndexView(AdminIndexView):
@expose('/')
def index(self):
if not isAdmin():
return redirect(url_for('.login_view'))
return super(MyAdminIndexView, self).index()
@expose('/login/', methods=('GET', 'POST'))
def login_view(self):
form = LoginForm(request.form)
if helpers.validate_form_on_submit(form):
user = form.get_user()
login.login_user(user)
if isAdmin():
return redirect(url_for('.index'))
self._template_args['form'] = form
return super(MyAdminIndexView, self).index()
@expose('/logout/')
@login_required
def logout_view(self):
login.logout_user()
return redirect(url_for('.login_view'))
数据库模型视图
为数据库中的每个张表增加专用的管理页面。比如现在要为电影单独添加一个管理页面,只需新建一个继承ModelView的类
from wtforms import fields
from flask_admin.contrib.sqla import ModelView
class MovieModelView(ModelView):
column_exclude_list = ('description',) # 不显示的字段
# 表单字段
form_columns = ('expired', 'name', 'poster', 'description',
'playingTime', 'duration', 'movieType', 'playingType')
form_create_rules = form_columns[1:] # 新建时显示的字段
form_overrides = {'poster': fields.FileField} # 重写表单类型
# 自定义字段显示
form_args = {
'movieType': {
'render_kw': {
'placeholder': '电影类型, 中文逗号分隔'
}
}
}
# 当模型的数据改变时触发(新建或修改)
def on_model_change(self, form, movie, is_created):
# do something
pass
然后初始化登录,添加模型视图即可
# -*- coding: utf-8 -*-
from views import *
import flask_login as login
from flask_admin import Admin
from models import db, Movie, User
def init_login(app):
"""初始化登录"""
login_manager = login.LoginManager()
login_manager.init_app(app)
# Create user loader function
@login_manager.user_loader
def load_user(id):
return User.query.get(id)
admin = Admin(name='管理系统', template_mode='bootstrap3',
index_view=MyAdminIndexView(), base_template='admin.html')
movieModelView = MovieModelView(Movie, db.session, name='电影管理')
admin.add_view(movieModelView)
效果如下:
SwaggerUI
有了管理后台,就可以开始编写后台接口了,Flask-RESTPlus
增加了对快速构建 REST API的支持。它配置非常简单,很容易上手,而且它提供了装饰器和工具集合来描述API 并且集成了 Swagger UI 界面。
在 REST 中,一个 URI 标识一个资源,Flask-RESTPlus 中有一个Resource类,继承这个类并实现get,post,delete,patch等函数即可处理对应的 HTTP 请求。下面以收藏模块为例:
# *-* coding: utf-8 *-*
from flask import request
from app.utils import UUID
from app.models import Favorite, Movie, db
from flask_restplus import Namespace, Resource
from flask_login import current_user, login_required
api = Namespace('favorite', description='收藏模块')
@api.route('/')
class FavoritesResource(Resource):
@login_required
def get(self):
"""获取收藏列表(需登录)"""
return [f.__json__() for f in current_user.favorites], 200
@api.doc(parser=api.parser().add_argument(
'movieId', type=str, required=True, help='电影id', location='form')
)
@login_required
def post(self):
"""收藏电影(需登录)"""
mid = request.form.get('movieId', '')
movie = Movie.query.get(mid)
if movie is None:
return {'message': '电影不存在'}, 233
movie = current_user.favorites.filter_by(movieId=mid).first()
if movie is not None:
return {'message': '不能重复收藏同部电影'}, 233
favorite = Favorite()
favorite.id = UUID()
favorite.username = current_user.id
favorite.movieId = mid
db.session.add(favorite)
db.session.commit()
return {'message': '收藏成功', 'id': favorite.id}, 200
@api.route('/<id>')
@api.doc(params={'id': '收藏id'})
class FavoriteResource(Resource):
@login_required
def delete(self, id):
"""取消收藏(需登录)"""
favorite = current_user.favorites.filter_by(id=id).first()
if favorite is None:
return {'message': '您没有这个收藏'}, 233
db.session.delete(favorite)
db.session.commit()
return {'message': '取消收藏成功'}, 200
@api.route
装饰器是路由监听,@login_required
装饰器来自于 Flask-Login ,被装饰的函数需要用户登录,@api.doc
是文档说明,在后面的 Swagger UI 部分就能看到它的作用了。
创建了资源之后,只需要进行初始化即可实现 RESTful 服务。
# *-* coding: utf-8 *-*
from flask import Flask
from flask_restplus import Api
from favorite import api as ns1
api = Api(
title='MonkeyEye',
version='1.0',
description='电影系统API',
doc='/swagger/', # Swagger UI: http://localhost:5000/swagger/
catch_all_404s=True,
serve_challenge_on_401=True
)
api.add_namespace(ns1, path='/api/favorites')
app = Flask(__name__)
api.init_app(app)
if __name__ == '__main__':
app.run()
实现了 REST API 之后,我们要怎么测试这些接口呢,有没有一种操作简便的方法呢? Flask-RESTPlus 中就自带了 Swagger UI 界面,可以直接访问该界面查看 API 并进行测试,前面我们用@api.doc
修饰的资源和方法都会在 Swagger UI 界面呈现出来,Api(doc=/swagger/) 指定了 Swagger UI 的路径,效果如下图:
改进优化
为了例子能够简单快速运行,减少环境依赖,删除了原项目很多的功能点:
- MySQL数据库,例子中改用sqlite,可自行安装其他数据库,修改配置文件中的数据库URL即可。
- 手机号注册,短信验证码功能
- 使用QQ邮箱发送重置密码右键功能
- gunicorn + Nginx 部署,提高程序性能
参考链接:
- Flask 文档:https://dormousehole.readthedocs.io/en/latest/
- Flask-Admin 教程:http://flask123.sinaapp.com/article/57/