序言
之前公司用例管理这块比较混乱、格式与编写内容不统一。 于是想要对测试编写与归档规范化、平台化。
打算使用flaskrestful + mysql 先把后端搭建起来。前端具体使用react 还是 vue 到时候再说
本文中为随笔、记录开发过程中遇到的问题与笔记。更新无规律。甚至整体结构都没有想好 -,-
版本
Flask==2.0.2
Py == 3.10
Start
因为之前写过
flask
所以先把基础架子搭建好。
数据库大体结构v1版本
目录结构
- APP
- Controller
- init 蓝本
- view 视图
- init
- auth 权限等
- Controller
- Comment
- myException 异常获取
- myResponse response 封装
- Config
- projectConfig 环境config
- Enums
- 一些枚举值
- Models
- 实体类
- Utils
- 工具
- resource
- main.py - 启动入口
结构暂定如此、边写边改
配置Config
casesHub/Configs/projectConfig.py
class ProjectConfig:
SECRET_KEY = 'hard to guess string'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
HOST = "127.0.0.1"
redisPort = '6379'
# FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
# FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>'
# FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(ProjectConfig):
DEBUG = True
CACHE_TYPE = 'simple'
ERROR_404_HELP = False
CACHE_DEFAULT_TIMEOUT = 300
# MAIL_SERVER = 'smtp.googlemail.com'
# MAIL_PORT = 587
# MAIL_USE_TLS = True
# MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
# MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
JSON_AS_ASCII = False # 这个配置可以确保http请求返回的json数据中正常显示中文
# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite') #使用sqlite
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:root@localhost:3306/caseHub" #使用mysql
SQLALCHEMY_ECHO = True #打印sql语法
SQLALCHEMY_TRACK_MODIFICATIONS = True #如果设置成 True (默认情况),Flask-SQLAlchemy 将会追踪对象的修改并且发送信号。这需要额外的内存, 如果不必要的可以禁用它
config = {
"dev": DevelopmentConfig, #dev环境
"test": TestingConfig, #测试环境
"pro": ProjectConfig, #线上环境
"default": DevelopmentConfig
}
APP
casesHub/App/__init__.py /
工厂函数app
catch = Cache()
db = SQLAlchemy(query_class=MyBaseQuery)
auth = HTTPBasicAuth()
def create_app(configName: AnyStr = "default") -> Flask:
"""
初始化app
定义环境配置
定义中文
支持跨域
注册蓝本
:param configName: projectConfig
:return: app
"""
app = Flask(__name__)
app.config.from_object(config[configName])
app.config["BABEL_DEFAULT_LOCALE"] = "zh"
catch.init_app(app) # 支持缓存
db.init_app(app) # db绑定app
CORS(app, supports_credentials=True)
from .userController import userBP
app.register_blueprint(userBP)
return app
蓝本
casesHub/App/userController/__init__.py
from flask import Blueprint
userBP = Blueprint("user", __name__, url_prefix="/v1/api/user")
from . import register
url_prefix
路由前缀
userBP
蓝本对象需要注册到create_app
里
register
视图函数、实例化蓝本对象后引入 否则会抛出引用错误
注册视图函数
casesHub/App/userController/register.py /
class RegisterController(Resource):
def post(self) -> jsonify:
"""
注册
:return: jsonify
"""
parse = MyRequestParseUtil()
parse.add(name="username", type=str, required=True)
parse.add(name="phone", type=str, required=True)
parse.add(name="password", type=str, required=True)
parse.add(name="email", type=str, required=True)
parse.add(name="gender", type=str, choices=["MALE", "FEMALE"], required=False)
parse.add(name="isAdmin", type=bool, required=False)
parse.add(name="departmentID", type=int, required=True)
body = parse.parse_args()
User(**body).save()
return MyResponse.success()
casesHub/Utils/requestParseUtil.py
自己写了个参数校验
主要校验参数类型、必传、是否符合预期 否则raise
抛出错误
class MyRequestParseUtil:
def __init__(self, location: AnyStr = "json"):
"""
:param location: location default json
"""
self.location = location
self.args = []
self.body = getattr(request, self.location, {})
def add(self, **kwargs):
"""
添加请求数据与数据类型
:param kwargs: name type required default choices
"""
# 默认类型为字符
if not kwargs.get("type"):
kwargs.setdefault("type", str)
# 默认非必传
if not kwargs.get("required"):
kwargs.setdefault("required", False)
self.args.append(kwargs)
def parse_args(self) -> Dict:
"""
参数校验
:return: self.body
"""
if self.body is None:
raise ParamException(ResponseMsg.REQUEST_BODY_EMPTY)
for kw in self.args:
# 设定 必传 但未传
if kw['required'] is True and not self.body.get(kw["name"]):
raise ParamException(ResponseMsg.empty(kw["name"]))
# 传空字符
if self.body.get(kw['name']) == "":
raise ParamException(ResponseMsg.empty(kw["name"]))
# 传参未按照定义类型
if not isinstance(self.body[kw['name']], kw['type']):
raise ParamException(ResponseMsg.error_type(kw["name"], kw['type']))
# 传参未按照指定区间
if kw.get('choices'):
if self.body[kw['name']] not in kw['choices']:
raise ParamException(ResponseMsg.error_val(kw["name"], kw['choices']))
# 未传且设定默认
if kw.get("default") and self.body.get(kw['name']) is None:
self.body[kw['name']] = kw.get('default')
return self.body
自定义返回与异常封装
自定义Exception
ParamException
AuthException
等等 见源码
class MyException(HTTPException):
"""
自定义 Exception 基类
"""
def __init__(self, response: Dict = None):
"""
:param response: MyResponse
"""
if response:
self._response = response
else:
self._response = MyResponse.error(ResponseCode.SERVER_ERROR)
super(MyException, self).__init__(response=self.__make_response())
def __make_response(self) -> Response:
"""
自定义返回 response
:return: Response
"""
log.error(self._response)
return Response(json.dumps(self._response), mimetype="application/json")
自定义Response
ParamError
AuthError
等
class MyResponse:
@staticmethod
def success(data: Any = None) -> jsonify:
return jsonify({"code": ResponseCode.SUCCESS, "data": data, "msg": ResponseMsg.OK})
@staticmethod
def error(code: ResponseCode) -> Dict:
return {"code": code, "data": None, "msg": ResponseMsg.ERROR}
Models
总会有个Base 基类实体
对应继承基类
class Base(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
create_time = db.Column(db.Date, default=datetime.now, comment="创建时间")
update_time = db.Column(db.Date, default=datetime.now, onupdate=datetime.now, comment="修改时间")
暂时User类就这么定义
还没想好关系处理。
class User(Base):
__tablename__ = "user"
username = db.Column(db.String(20), unique=True, comment="用户名")
phone = db.Column(db.String(12), unique=True, comment="手机")
password = db.Column(db.String(200), comment="密码")
email = db.Column(db.String(40), unique=True, comment="邮箱")
gender = db.Column(db.Enum("MALE", "FEMALE"), server_default="MALE", comment="性别")
avatar = db.Column(db.LargeBinary, nullable=True, comment="头像")
isAdmin = db.Column(db.Boolean, default=False, comment="管理")
tag = db.Column(db.Enum("QA", "PR", "DEV"), comment="标签")
from .departments import Department # 不同文件下需引入
departmentID = db.Column(db.INTEGER, db.ForeignKey("department.id"), nullable=False, comment="所属部门")
from Models.ProjectModel.pro import Project,Product
projectID = db.Column(db.INTEGER, db.ForeignKey("project.id"), nullable=True, comment="所属项目")
productID = db.Column(db.INTEGER, db.ForeignKey("product.id"), nullable=True, comment="所属产品")
def __init__(self, username: AnyStr, password: AnyStr, phone: AnyStr,
email: AnyStr, tag: AnyStr,
gender: AnyStr, isAdmin: bool, departmentID: int,
projectID: int = None, productID: int = None):
self.username = username
self.hash_password(password)
self.email = email
self.gender = gender
self.phone = phone
self.tag = tag
self.isAdmin = isAdmin
self.departmentID = departmentID
self.productID = productID
self.projectID = projectID
def hash_password(self, password: AnyStr):
"""
密码加密
:param password: password
:return: hash_password
"""
self.password = generate_password_hash(password)
def generate_token(self, expires_time: int = 3600 * 24):
"""
生成token
:param expires_time: 过期时间 默认 一天
:return: token
"""
token = {"id": self.id, "expires_time": time.time() + expires_time}
return jwt.encode(token, current_app.config["SECRET_KEY"], algorithm="HS256")
@staticmethod
def verify_token(token: AnyStr) -> Union[None, Any]:
"""
token 解密
:param token:
:return: None or user
"""
try:
data = jwt.decode(token, current_app.config['SECRET_KEY'], algorithm=["HS256"])
except Exception as e:
log.error(e)
return None
return User.query.get(data['id'])
def verify_password(self, password: AnyStr) -> bool:
"""
校验密码
:param password: password
:return: bool
"""
return check_password_hash(self.password, password)
@property
def admin(self) -> bool:
"""
:return:
"""
return self.isAdmin
def __repr__(self):
return f"<{User.__name__} {self.username}>"
BaseQuery
db = SQLAlchemy(query_class=MyBaseQuery)
初始化db时 选择
class MyBaseQuery(BaseQuery):
# def filter_by(self, **kwargs):
# """
# ¹ýÂËÈíɾ³ý
# :param kwargs:
# :return:
# """
#
# kwargs.setdefault('status', 1)
# return super().filter_by(**kwargs)
def get_or_NoFound(self, ident, name):
rv = self.get(ident)
if not rv:
raise ParamException(ResponseMsg.no_existent(name))
return rv
后序
目前进度就是这样、有的模块没有录入 flask 也是边写边学、有好的想法的大佬可以留言。thank u
附上github github