结对项目分析总结
综述
说实话这次的项目对我的提升并不大,无论是前端、后端我过去都有所经验,对于团队协作、版本控制、自动化CI/CD也有所了解。所以本篇博客我不会将主要的分析放在前后端的实现上,更多的着眼于部署等方面。
附项目体验地址。
前后端实现
我们完成的是典型的前后端分离项目,首先要做的就是落实前后端交互格式,我们采用了JSON
这种轻量的数据交换格式,具体格式约定如下:
{
"status": "success",
"data": interface{}
}
{
"status": "error",
"err_msg": "error message"
}
接下来我们要做的就是前后端的分别实现,前端的实现我们放到代码复用部分讲,这里我们看看后端。后端我们选择Python
作为主要的开发工具,我们先来看APP的实例获取过程:
from flask import Flask
from flask_cors import CORS
from app.config import FlaskConfig
from app.controllers import register_routers
from app.models import connect_db
def new_flask_app() -> Flask:
app = Flask(__name__)
CORS(app, supports_credentials=True)
# 添加配置文件
app.config.from_object(FlaskConfig)
# 注册路由
register_routers(app)
# 链接数据库
connect_db(app)
return app
代码中的注释都比较详细,不想讲很多。大家注意一下Python中的类型注解这一个特性这样可以获得非常好的代码提示等IDE支持。
接下来,我们分别看一个Model和Controller的例子:
from app.models import db
from app.models import session_commit
class User(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
username = db.Column(db.String, nullable=True)
password = db.Column(db.String, nullable=True)
def __init__(self, username, password):
self.username = username
self.password = password
def __str__(self):
return "User(username={})".format(self.username)
@classmethod
def check_password(cls, username: str):
return cls.query.filter_by(username=username).first()
@classmethod
def change_password(cls, username: str, password: str):
user = cls.query.filter_by(username=username).first()
user.password = password
return session_commit()
def new_user(self):
db.session.add(self)
return session_commit()
注意其中类方法的使用即可,其余的就是简单的Python的ORM的使用
from flask import Blueprint, request, session
from app.models.user import User
import app.utils.return_warp as warp
login_out_page = Blueprint('login_out', __name__, url_prefix='/log')
@login_out_page.route('/in', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
# 校验必须参数
if username is None or password is None:
return warp.fail_warp('params error')
user = User.check_password(username=username)
if user.password == password:
session.clear()
session['user'] = username
return warp.success_warp('login success')
else:
return warp.fail_warp('user error')
@login_out_page.route('/out', methods=['GET'])
def logout():
session.clear()
return warp.success_warp('logout success')
其余的内容便不再赘述,具体的代码实现也没有什么难懂的地方,也没什么讲解的必要。
代码复用部分
本次项目我们主要复用的是前端部分的代码,并在其基础上进行了优化。
可以说本次结对编程,我的队友负责前端部分,因为复用的是我个人项目时的前端代码,所以我们采用的是React
这样的技术。我的队友对于现在前端工程化的方法有了很大的理解,对于前端工作流的使用也初步进行了入门,对于流行的MVVM模型有了深入的理解。
接下来举一个React的Context的例子说明是怎么进行代码复用的:
// 个人项目代码
import React, {createContext, useState} from 'react';
export const TypeContext = createContext(null);
export const TypeProvider = props => {
let [userType, setUserType] = useState({
name: '张三1',
type: 1
});
return (
<TypeContext.Provider value={{userType, setUserType}}>
{props.children}
</TypeContext.Provider>
)
};
export const TypeConsumer = TypeContext.Consumer;
//结对项目
import React, {createContext, useState} from 'react';
export const UserContext = createContext(null);
export const TypeProvider = props => {
const [user, setUser] = useState('');
const [errorMessage, setErrorMessage] = useState('');
return (
<UserContext.Provider value={{user, setUser, errorMessage, setErrorMessage}}>
{props.children}
</UserContext.Provider>
)
};
export const TypeConsumer = UserContext.Consumer;
可以看到两者的代码基本上是一致的,只是我们所需要共享的数据不太一样,所以对外提供了不同的Provide。
有兴趣的可以了解一下React Hooks的原理和使用
部署部分
Docker
Docker是一个开放源代码软件项目,让应用程序部署在软件货柜下的工作可以自动化进行,借此在Linux操作系统上,提供一个额外的软件抽象层,以及操作系统层虚拟化的自动管理机制。 Docker利用Linux核心中的资源分离机制,例如cgroups,以及Linux核心名字空间,来创建独立的容器。
我们的后端项目即采用Docker进行部署,具体的命令等大家可以查看Docker官网,下面给出Dockerfile:
# 基础镜像
FROM python:3.7
WORKDIR /app
ADD . /app
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
CMD ["gunicorn", "main:app", "-c", "gunicorn.conf.py"]
Nginx
Nginx是异步框架的网页服务器,也可以用作反向代理、负载平衡器和HTTP缓存。该软件由伊戈尔·赛索耶夫创建并于2004年首次公开发布。 2011年成立同名公司以提供支持。2019年3月11日,Nginx公司被F5 Networks以6.7亿美元收购。 Nginx是免费的开源软件,根据类BSD许可证的条款发布。
我们的服务器只在443、80两个端口运行,其余的部署通过Nginx反代进行:
#PROXY-START/pair
location /pair
{
expires 12h;
if ($request_uri ~* "(php|jsp|cgi|asp|aspx)")
{
expires 0;
}
proxy_pass http://内网IP:6000/;
proxy_set_header Host localhost;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-store;
proxy_cache cache_one;
proxy_cache_key $host$uri$is_args$args;
proxy_cache_valid 200 304 301 302 1m;
}
#PROXY-END/pair
注意其中因为使用了Docker不可以使用localhost,必须使用内网IP,记得作为API服务器应该设置不开启浏览器缓存。