From 97c66eeeaf07f3c76fedb0622d2e74e681c9c086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=9D=E5=B7=9D?= Date: Tue, 24 May 2022 02:25:59 +0800 Subject: [PATCH] Initial commit --- .gitignore | 4 + .gitlab-ci.yml | 14 + DBHelper/DBConfig.json | 12 + DBHelper/MongoHelper.py | 232 ++++++++++++++ DBHelper/MongoHelperInstance.py | 7 + DBHelper/__init__.py | 0 Dockerfile | 6 + Modules/AdminUser/UserAuthUtils.py | 95 ++++++ Modules/AdminUser/UserImpl.py | 318 +++++++++++++++++++ Modules/AdminUser/UserObject.py | 287 +++++++++++++++++ Modules/AdminUser/UserRoutes.py | 137 ++++++++ Modules/AdminUser/UserUtils.py | 80 +++++ Modules/AdminUser/__init__.py | 0 Modules/AdminUser/static/menus.json | 6 + Modules/AdminUser/static/rsa_private_key.pem | 15 + Modules/AdminUser/static/rsa_public_key.pem | 6 + Modules/__init__.py | 0 Utils/CommonUtil.py | 27 ++ Utils/ErrorUtil.py | 90 ++++++ Utils/ObjUtil.py | 83 +++++ Utils/RouteUtil.py | 21 ++ Utils/ValidateUtil.py | 133 ++++++++ Utils/__init__.py | 0 app.py | 20 ++ gunicorn.conf.py | 8 + requirements.txt | 10 + 26 files changed, 1611 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 DBHelper/DBConfig.json create mode 100644 DBHelper/MongoHelper.py create mode 100644 DBHelper/MongoHelperInstance.py create mode 100644 DBHelper/__init__.py create mode 100644 Dockerfile create mode 100644 Modules/AdminUser/UserAuthUtils.py create mode 100644 Modules/AdminUser/UserImpl.py create mode 100644 Modules/AdminUser/UserObject.py create mode 100644 Modules/AdminUser/UserRoutes.py create mode 100644 Modules/AdminUser/UserUtils.py create mode 100644 Modules/AdminUser/__init__.py create mode 100644 Modules/AdminUser/static/menus.json create mode 100644 Modules/AdminUser/static/rsa_private_key.pem create mode 100644 Modules/AdminUser/static/rsa_public_key.pem create mode 100644 Modules/__init__.py create mode 100644 Utils/CommonUtil.py create mode 100644 Utils/ErrorUtil.py create mode 100644 Utils/ObjUtil.py create mode 100644 Utils/RouteUtil.py create mode 100644 Utils/ValidateUtil.py create mode 100644 Utils/__init__.py create mode 100644 app.py create mode 100644 gunicorn.conf.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10e0b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +/venv/ +*/__pycache__/ +*.cpython-38.pyc diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..c3251fa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,14 @@ +stages: + - deploy + +job: + stage: deploy + script: + - docker stop guarantee_admin_api_v0.2 + - docker rm guarantee_admin_api_v0.2 + - docker build -t guarantee_admin_api_v0.2 . + - docker run -d -p 51011:51011 --name guarantee_admin_api_v0.2 -v /etc/timezone:/etc/timezone:ro -v /etc/localtime:/etc/localtime:ro guarantee_admin_api_v0.2 + only: + - master + tags: + - guarantee_admin_api_v0.2 diff --git a/DBHelper/DBConfig.json b/DBHelper/DBConfig.json new file mode 100644 index 0000000..a4424c3 --- /dev/null +++ b/DBHelper/DBConfig.json @@ -0,0 +1,12 @@ +{ + "MongoDB": { + "guarantee": "root:RYIHrqml#LSW6#!*@116.63.130.34:27020", + "tyc": "root:gP@DwMSVd5Sh6EiH@116.63.130.34:27019" + }, + "Mysql": { + + }, + "Redis": { + + } +} \ No newline at end of file diff --git a/DBHelper/MongoHelper.py b/DBHelper/MongoHelper.py new file mode 100644 index 0000000..4dba38f --- /dev/null +++ b/DBHelper/MongoHelper.py @@ -0,0 +1,232 @@ +import re +import os +import json + +import gridfs +import pymongo + +from urllib import parse +from bson import ObjectId +from gridfs import GridFS + + +class MongoHelper: + + def __init__(self, param): + """ + param: + type:str + desc: 选择连接哪个MongoDB数据库 + """ + with open(os.path.abspath(os.path.dirname(__file__) + '/DBConfig.json')) as f: + db_configs = json.load(f) + this_mongo_cfg = db_configs['MongoDB'][param] + m = re.match('([\s\S].*?):([\s\S].*)@([\s\S].*)', this_mongo_cfg) + parsed_mongo_config = "{}:{}@{}".format(parse.quote_plus(m.group(1)), parse.quote_plus(m.group(2)), m.group(3)) + self.client = pymongo.MongoClient('mongodb://{}'.format(parsed_mongo_config)) + + def find_single_column(self, param1, param2, param3, param4): + """ + 查询符合条件的第一条数据的某个指定字段值 + param1: str 数据库 + param2: str 数据表 + param3: dict 查询条件 + param4: str 一个指定查询字段 + return: + type: None or dict + desc: 查询结果为空,返回None; 查询结果正常,返回查询结果的第一条数据; + """ + collection = self.client[param1][param2] + column = {**{'_id': False}, **{param4: 1}} + record = list(collection.find(param3, column)) + return None if record == [] else record[0][param4] + + def find_single_data(self, param1, param2, param3, param4): + """ + 查询符合条件的第一条数据 + param1: str 数据库 + param2: str 数据表 + param3: dict 查询条件 + param4: list 查询字段 + return: + type: bool or dict + desc: 查询结果为空,返回False; 查询结果正常,返回查询结果的第一条数据; + """ + collection = self.client[param1][param2] + columns = {**{'_id': False}, **dict(zip(param4, [1] * len(param4)))} + record = list(collection.find(param3, columns)) + return False if record == [] else record[0] + + def find_single_data_with_single_sort(self, param1, param2, param3, param4, param5): + """ + 查询符合条件的第一条数据,按单个排序条件返回 + param1: str 数据库 + param2: str 数据表 + param3: dict 查询条件 + param4: list 查询字段 + param5: dict 排序条件 例如 {"name": 1} 表示按照name字段正序返回 + return: + type: bool or dict + desc: 查询结果为空,返回False; 查询结果正常,返回查询结果的第一条数据; + """ + collection = self.client[param1][param2] + columns = {**{'_id': False}, **dict(zip(param4, [1] * len(param4)))} + record = list(collection.find(param3, columns).sort(list(param5.keys())[0], list(param5.values())[0]).limit(1)) + return False if record == [] else record[0] + + def find_all_data(self, param1, param2, param3, param4): + """ + 查询符合条件的所有数据 + param1: str 数据库 + param2: str 数据表 + param3: dict 查询条件 + param4: list 查询字段 + return: + type: list + desc: 查询结果 + """ + collection = self.client[param1][param2] + columns = {**{'_id': False}, **dict(zip(param4, [1] * len(param4)))} + record = list(collection.find(param3, columns)) + return record + + def find_all_data_with_count(self, param1, param2, param3): + """ + 查询所有符合条件的数据,并返回统计数量 + param1: str 数据库 + param2: str 数据表 + param3: str 查询条件 + return: int 符合条件的数据数量 + """ + collection = self.client[param1][param2] + num = collection.find(param3).count() + return num + + def find_all_data_with_single_sort(self, param1, param2, param3, param4, param5): + """ + 查询符合条件的数据,按单个排序条件返回 + param1: str 数据库 + param2: str 数据表 + param3: dict 查询条件 + param4: list 查询字段 + param5: dict 排序条件 例如 {"name": 1} 表示按照name字段正序返回 + return: + type: bool or dict + desc: 查询结果为空,返回False; 查询结果正常,返回查询结果的第一条数据; + """ + collection = self.client[param1][param2] + columns = {**{'_id': False}, **dict(zip(param4, [1] * len(param4)))} + record = list(collection.find(param3, columns).sort(list(param5.keys())[0], list(param5.values())[0])) + return False if record == [] else record + + def find_data_with_aggregate(self, param1, param2, param3): + """ + 根据聚合条件查询 + param1: str 数据库 + param2: str 数据集 + param3: + type: list + desc: 聚合条件 + demo: [{'$match':{'price':{'$gte':50}}}, {'$group': {'_id': "$fName", 'count': {'$sum': 1}}}] + """ + collection = self.client[param1][param2] + data = list(collection.aggregate(param3)) + return data + + def find_data_by_page_with_sort(self, param1, param2, param3, param4, param5, param6, param7): + """ + 根据聚合翻页查询,且按照需求字段排序返回 + param1: str 数据库 + param2: str 数据集 + param3: dict 查询条件 + param4: list 显示字段 + param5: dict 排序条件 例如 {"name": 1} 表示按照name字段正序返回 + param6: int 即 page_size 每页数据条数 + param7: int 即 page_no 当前页码 + """ + collection = self.client[param1][param2] + columns = {**{'_id': False}, **dict(zip(param4, [1] * len(param4)))} + page_size = int(param6) + page_no = int(param7) + skip_num = page_size * (page_no - 1) + record = list(collection.find(param3, columns).sort(list(param5.keys())[0], list(param5.values())[0]).limit(page_size).skip(skip_num)) + return False if record == [] else record + + def insert_single_data(self, param1, param2, param3): + """ + 插入一条数据 + param1: str 数据库 + param2: str 数据集 + param3: obj 插入数据 + return: None + """ + collection = self.client[param1][param2] + collection.insert_one(param3) + + def upsert_single_data(self, param1, param2, param3, param4): + """ + 插入单条数据 + param1: str 数据库 + param2: str 数据表 + param3: dict 查询条件 + param4: dict 更新或新插入的数据 + return: + None + """ + collection = self.client[param1][param2] + collection.update_one(param3, {"$set": param4}, upsert=True) + + def update_single_data(self, param1, param2, param3, param4): + """ + 插入单条数据 + param1: str 数据库 + param2: str 数据表 + param3: dict 查询条件 + param4: dict 更新或新插入的数据 + return: + None + """ + collection = self.client[param1][param2] + collection.update_one(param3, {"$set": param4}) + + def delete_single_data(self, param1, param2, param3): + """ + 根据查询条件删除一条文档 + param1: str 数据库 + param2: str 数据集 + param3: obj 查询条件 + return: None + """ + collection = self.client[param1][param2] + collection.delete_one(param3) + return True + + def find_file(self, param1, param2, param3): + """ + 读取一个文件 + param1: str 数据库 + param2: str 存储桶 + param3: str 文件id + return: + type: binary? + desc: 二进制文件流 + """ + try: + # 实例化一个文件存储器 + gfs = GridFS(self.client[param1], collection=param2) + # 二进制读取文件 + data_stream = gfs.get(ObjectId(param3)).read() + # 返回文件二进制流 + return data_stream + except gridfs.errors.NoFile: + return False + + def delete_file(self, param1, param2, param3): + """ + 根据id删除文件 + param1: str 数据库 + param2: str 存储桶 + param3: str 文件fid + """ + fs = GridFS(self.client[param1], param2) + fs.delete(ObjectId(param3)) diff --git a/DBHelper/MongoHelperInstance.py b/DBHelper/MongoHelperInstance.py new file mode 100644 index 0000000..ddd51a0 --- /dev/null +++ b/DBHelper/MongoHelperInstance.py @@ -0,0 +1,7 @@ +from DBHelper.MongoHelper import MongoHelper + +# 天眼查数据库 +DB_TYC = MongoHelper("tyc") + +# 股交项目数据库 +DB_GUA = MongoHelper("guarantee") diff --git a/DBHelper/__init__.py b/DBHelper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7b317f2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.8 +WORKDIR /usr/src/app/guarantee_admin_api_v0_2 +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.mirrors.ustc.edu.cn/simple +COPY . . +CMD ["gunicorn", "app:app", "-c", "./gunicorn.conf.py"] diff --git a/Modules/AdminUser/UserAuthUtils.py b/Modules/AdminUser/UserAuthUtils.py new file mode 100644 index 0000000..ba2a2fe --- /dev/null +++ b/Modules/AdminUser/UserAuthUtils.py @@ -0,0 +1,95 @@ +import functools + +from flask import request +from itsdangerous import Serializer +from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpired, BadSignature + +from DBHelper.MongoHelperInstance import DB_GUA + +TOKEN_KEY = "P0eAym@&CbaQWWkq" +TOKEN_EXPIRE = 60*60*8 + + +def create_token(param): + """ + 创建token + Parameters: + param: 传入参数,用于创建token + Returns: + token: 用户访问令牌 + """ + s = Serializer(TOKEN_KEY, expires_in=TOKEN_EXPIRE) + token = '' + s.dumps(param).decode('ascii') + return token + + +def verify_token(func): + """ + 校验token + return: + type:str + desc: token被解析后的值 + """ + @functools.wraps(func) + def internal(*args, **kwargs): + try: + # step 1.1 + # 解析请求头传送的token + s = Serializer(TOKEN_KEY) + session_id = s.loads(request.headers.get('token')) + + # step 1.2 + # 请求头中没有token参数 返回错误提示 + if session_id is None: + return {"info": "缺少token"}, 401 + + # step 2.1 + # token解析成功 从token记录中查询session_id对应的uid + uid = DB_GUA.find_single_column( + "管理端", + "token记录", + {"session_id": session_id}, + "UID" + ) + + # step 2.2 + # 根据session_id没有找到对应的uid 返回错误提示 + if not uid: + return {"info": "提示: 账号已在别处登录"}, 401 + + except TypeError: + return {"info": "异常token"}, 401 + except KeyError: + return {"info": "异常token"}, 401 + except BadSignature: + return {"info": "错误token"}, 401 + except SignatureExpired: + return {"info": "过期token"}, 401 + return func(*args, **kwargs, uid=uid) + return internal + + +def authority_scope(scope): + def decorate(func): + @functools.wraps(func) + def internal(*args, ** kwargs): + + records = DB_GUA.find_single_data( + "管理端", + "用户", + {"UID": kwargs['uid']}, + ['status', 'role'] + ) + + if not records: + return {"info": "提示: 账户不存在"}, 401 + + if records['status'] != "normal": + return {"info": "提示: 账户已被禁用"}, 401 + + if records['role'] not in scope: + return {"info": "提示: 没有此项操作权限"}, 401 + + return func(*args, ** kwargs) + return internal + return decorate diff --git a/Modules/AdminUser/UserImpl.py b/Modules/AdminUser/UserImpl.py new file mode 100644 index 0000000..d8f73fa --- /dev/null +++ b/Modules/AdminUser/UserImpl.py @@ -0,0 +1,318 @@ +import json +import os +import time +import random +import requests + +from werkzeug.security import check_password_hash + +from DBHelper.MongoHelperInstance import DB_GUA +from Utils.ErrorUtil import ReturnConditionCheckFailed + +from Modules.AdminUser.UserUtils import decrypt_data +from Modules.AdminUser.UserAuthUtils import create_token +from Modules.AdminUser.UserObject import UserManage, ListUser, User, UserLogin, SendLoginVcodeEmail + + +class ListUserImpl(ListUser): + """用户列表实现""" + + def list(self): + + # 查询体构造方法 + def make_search_body(param): + body = dict() + search_keys = list(param.keys()) + + if "uid" in search_keys: + body['UID'] = param['uid'] + + if "姓名" in search_keys: + body['name'] = {"$regex": param['姓名']} + + if "邮箱" in search_keys: + body['email'] = {"$regex": param['邮箱']} + + if "状态" in search_keys: + if param['状态'] == "正常": + body['status'] = "normal" + elif param['状态'] == "停用": + body['status'] = "disable" + else: + pass + + if "角色" in search_keys: + roles = list() + role_map = {"管理员": "admin", "分析师": "analysts", "访客": "guest"} + for role in list(set(param['角色'])): + roles.append(role_map[role]) + body['role'] = {"$in": roles} + + return body + + # 顺序条件构造方法 + def make_sort_body(param): + if param != {}: + columns_map = {"姓名": "name", "邮箱": "email", "状态": "status", "角色": "role", "创建时间": "create_time"} + asc_or_desc = 1 if list(param.values())[0] == "asc" else -1 + sort_column = columns_map[list(param.keys())[0]] + body = {sort_column: asc_or_desc} + else: + body = {"create_time": -1} + + return body + + search_body = make_search_body(self.search) + sort = make_sort_body(self.sort) + page_size = 10 if self.page_size > 10 else self.page_size + page_no = int(self.page_no) + + total = DB_GUA.find_all_data_with_count( + "管理端", + "用户", + search_body + ) + + records = DB_GUA.find_data_by_page_with_sort( + "管理端", + "用户", + search_body, + ["UID", "email", "name", "status", "role", "create_time"], + sort, + page_size, + page_no + ) + + table_data = list() + if records: + for record in records: + user = User() + user.uid = record['UID'] + user.email = record['email'] + user.name = record['name'] + user.status = record['status'] + user.role = record['role'] + user.create_time = record['create_time'] + table_data.append(user.dict_to_show()) + + result = { + "records": table_data, + "total": total + } + + return result + + +class UserLoginImpl(UserLogin): + """用户登录实现""" + + def login(self): + """""" + user_info = DB_GUA.find_single_data( + "管理端", + "用户", + {"email": self.email}, + ["UID", "name", "pwd", "status", "role"] + ) + + def check_email_existed(): + if not user_info: + raise ReturnConditionCheckFailed("邮箱不存在", 200) + + def check_user_is_disable(): + if user_info['status'] != 'normal': + raise ReturnConditionCheckFailed("账户已禁用", 200) + + def check_vcode_is_correct(): + record = DB_GUA.find_single_data( + "管理端", + "邮箱验证码记录", + {"email": self.email}, + ["vcode", "timestamp"] + ) + + if not record: + raise ReturnConditionCheckFailed("验证码不存在", 200) + + if record['vcode'] != self.vcode: + raise ReturnConditionCheckFailed("验证码错误", 200) + + if time.time() - record['timestamp'] > 300: + raise ReturnConditionCheckFailed("验证码过期", 200) + + DB_GUA.delete_single_data( + "管理端", + "邮箱验证码记录", + {"email": self.email} + ) + + def check_pwd_is_correct(): + try: + if not check_password_hash(user_info['pwd'], decrypt_data(encrypt_msg=self.pwd)): + raise ReturnConditionCheckFailed("密码错误", 200) + except Exception: + raise ReturnConditionCheckFailed("密码错误", 200) + + def make_menus(): + role = user_info['role'] + with open(os.path.abspath(os.path.dirname(__file__)+'/static/menus.json'), "r", encoding='utf-8') as f: + duties = json.load(f) + self.menus = duties[role] + + def make_token(): + choices = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + session_id = '' + for i in range(4): + session_id += random.choice(choices) + + DB_GUA.update_single_data( + "管理端", + "token记录", + {"UID": user_info['UID']}, + {"session_id": session_id} + ) + + self.token = create_token(session_id) + + def __main__(): + check_email_existed() + check_user_is_disable() + check_pwd_is_correct() + check_vcode_is_correct() + + make_menus() + make_token() + + self.name = user_info['name'] + + return self.dict_to_show() + + return __main__() + + +class SendLoginVcodeEmailImpl(SendLoginVcodeEmail): + """发送登录验证码邮件 实现""" + + def send_vcode_email(self): + + def gen_vcode(): + choices = '0123456789' + salt = '' + for i in range(6): + salt += random.choice(choices) + self.vcode = salt + + def save_vcode(): + DB_GUA.upsert_single_data( + "管理端", + "邮箱验证码记录", + {"email": self.email}, + {"vcode": self.vcode, "timestamp": round(time.time())} + ) + + def send_email(): + email_api = 'http://116.63.130.34:30001' + headers = {"Content-Type": "application/json;charset=UTF-8"} + data = { + "title": "【远东资信】{}".format("登录验证码"), + "sender": 'fecribd@fecr.com.cn', + "recipients": [self.email], + "msg_body": "您{}的验证码为 【{}】,5分钟内有效。".format("登录", self.vcode) + } + requests.post(url=email_api + '/send_mail', headers=headers, data=json.dumps(data)) + + def __main__(): + gen_vcode() + save_vcode() + send_email() + + return __main__() + + +class UserManageImpl(UserManage): + + def create(self): + def check_email_registered(): + """检查邮箱是否已注册""" + + email_is_existed = DB_GUA.find_single_column( + "管理端", + "用户", + {"email": self.email}, + "email" + ) + + if email_is_existed: + raise ReturnConditionCheckFailed("邮箱已被注册", 200) + + def generate_new_uid(): + """生成新的用户ID""" + + def uid_maker(num): + """ + 用户ID生成器 + num: ID长度 int + """ + + choices = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + salt = '' + for i in range(num): + salt += random.choice(choices) + return salt + + def uid_checker(__uid): + """ + 用户ID重复检查 + __uid: 用户ID str + return: + True 用户ID可用 + False 用户ID不可用 + """ + is_uid_existed = DB_GUA.find_single_column( + "管理端", + "用户", + {"UID": __uid}, + "UID" + ) + return True if is_uid_existed is None else False + + uid = uid_maker(8) + while not uid_checker(uid): + uid = uid_maker(8) + self.uid = uid + + def __main__(): + check_email_registered() + generate_new_uid() + + self.status = "normal" + self.create_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + + DB_GUA.upsert_single_data( + "管理端", + "用户", + {"UID": self.uid}, + self.dict_to_save() + ) + + __main__() + + def disable(self): + """禁用用户 实现""" + + DB_GUA.update_single_data( + "管理端", + "用户", + {"UID": self.uid}, + {"status": self.status} + ) + + def active(self): + """激活用户 实现""" + + DB_GUA.update_single_data( + "管理端", + "用户", + {"UID": self.uid}, + {"status": self.status} + ) diff --git a/Modules/AdminUser/UserObject.py b/Modules/AdminUser/UserObject.py new file mode 100644 index 0000000..b395633 --- /dev/null +++ b/Modules/AdminUser/UserObject.py @@ -0,0 +1,287 @@ +import re + +from Utils.ErrorUtil import ReturnConditionCheckFailed +from Modules.AdminUser.UserUtils import decrypt_data + + +class User: + """管理端用户""" + + def __init__(self): + self.uid = None # 用户ID str + self.email = None # 邮箱 str + self.name = None # 用户名 str + self.pwd = None # 密码 str + self.status = None # 用户状态 str + self.role = None # 角色 str + self.create_time = None # 创建时间 str + + def check_uid(self): + """用户ID校验""" + + if type(self.uid) is not str: + raise ReturnConditionCheckFailed("用户ID格式错误", 200) + + if len(self.uid) != 8: + raise ReturnConditionCheckFailed("用户ID格式错误", 200) + + def check_email(self): + """邮箱格式仅允许@fecr.com.cn""" + + regex = "^.+\\@fecr.com.cn" + case = (len(self.email) > 7) and (re.match(regex, self.email) is not None) + result = True if case else False + if not result: + raise ReturnConditionCheckFailed("邮箱格式错误", 200) + + def check_name(self): + """用户名格式校验""" + + if type(self.name) is not str: + raise ReturnConditionCheckFailed("用户名格式错误", 200) + + def check_pwd(self): + """密码格式校验""" + + if type(self.pwd) is not str: + raise ReturnConditionCheckFailed("密码格式错误", 200) + + password = decrypt_data(encrypt_msg=self.pwd) + if not password: + raise ReturnConditionCheckFailed("密码格式错误", 200) + + regex = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)^.{8,}$" + case = (len(password) >= 8) and (re.match(regex, password) is not None) + result = True if case else False + if not result: + raise ReturnConditionCheckFailed("密码格式错误", 200) + + def check_status(self): + """用户状态校验""" + + if self.status not in ['normal', 'disable']: + raise ReturnConditionCheckFailed("用户状态格式错误", 200) + + def check_role(self): + """用户角色校验""" + + if self.role not in ['admin', 'analysts', 'developer', 'operator', 'guest']: + raise ReturnConditionCheckFailed("用户角色格式错误", 200) + + def check_obj(self, **kwargs): + """对象字段校验""" + + columns = kwargs['columns'] + + for column in columns: + if column == "uid": + self.check_uid() + elif column == "email": + self.check_email() + elif column == "name": + self.check_name() + elif column == "pwd": + self.check_pwd() + elif column == "status": + self.check_status() + elif column == "role": + self.check_role() + else: + pass + + def trans_status(self): + """用户状态转换成前端显示格式""" + + return "正常" if self.status == "normal" else "停用" + + def trans_role(self): + """用户角色转换成前端显示格式""" + + role_map = {"admin": "管理员", "analysts": "分析师", "developer": "开发者", "operator": "运营人员", "guest": "访客"} + return role_map[self.role] + + def dict_to_save(self, **kwargs): + """存储对象""" + user_dict = { + "UID": self.uid, + "email": self.email, + "name": self.name, + "pwd": self.pwd, + "status": self.status, + "role": self.role, + "create_time": self.create_time + } + + if 'columns' in list(kwargs.keys()): + user_dict = {key: user_dict[key] for key in kwargs['columns']} + + return user_dict + + def dict_to_show(self, **kwargs): + """显示对象""" + user_dict = { + "uid": self.uid, + "邮箱": self.email, + "姓名": self.name, + "状态": self.trans_status(), + "角色": self.trans_role(), + "创建时间": self.create_time + } + + if 'columns' in list(kwargs.keys()): + user_dict = {key: user_dict[key] for key in kwargs['columns']} + + return user_dict + + +class ListUser: + """用户列表""" + + def __init__(self, search, sort, page_size, page_no): + self.search = search + self.sort = sort + self.page_size = page_size + self.page_no = page_no + + def list(self): + """查询用户列表""" + + def check_search(self): + """ + type: dict + desc: 用户搜索条件 支持模糊查询条件 + demo: {"姓名": "xxx", "邮箱": "xxx"} + """ + if type(self.search) is not dict: + raise ReturnConditionCheckFailed("参数 search 格式错误", 400) + + search_keys = list(self.search.keys()) + + if "uid" in search_keys: + if type(self.search['UID']) is not str: + raise ReturnConditionCheckFailed("参数 search.uid 格式错误", 400) + + if "姓名" in search_keys: + if type(self.search['姓名']) is not str: + raise ReturnConditionCheckFailed("参数 search.姓名 格式错误", 400) + + if "邮箱" in search_keys: + if type(self.search['邮箱']) is not str: + raise ReturnConditionCheckFailed("参数 search.邮箱 格式错误", 400) + + if "状态" in search_keys: + if type(self.search['状态']) is not str: + raise ReturnConditionCheckFailed("参数 search.状态 格式错误", 400) + + if self.search['状态'] not in ["正常", "停用"]: + raise ReturnConditionCheckFailed("参数 search.状态 格式错误", 400) + + if "角色" in search_keys: + + if type(self.search['角色']) is not list: + raise ReturnConditionCheckFailed("参数 search.角色 格式错误", 400) + + for role in self.search['角色']: + if role not in ["管理员", "分析师", "开发者", "运营人员", "访客"]: + raise ReturnConditionCheckFailed("参数 search.角色 格式错误", 400) + + def check_sort(self): + """ + type: dict + desc: 排序条件 asc正序 desc倒序 + demo: {"姓名": asc} + """ + if type(self.sort) is not dict: + raise ReturnConditionCheckFailed("参数 sort 格式错误", 400) + + if len(list(self.sort.values())) > 1: + raise ReturnConditionCheckFailed("参数 sort 格式错误", 400) + + if len(list(self.sort.values())) == 1 and list(self.sort.values())[0] not in ["asc", "desc"]: + raise ReturnConditionCheckFailed("参数 sort 格式错误", 400) + + def check_page_size(self): + """ + type: int + desc: 每页数据数量 + """ + if type(self.page_size) is not int: + raise ReturnConditionCheckFailed("参数 page_size 格式错误", 400) + + def check_page_no(self): + """ + type: int + desc: 当前页数 + """ + if type(self.page_no) is not int: + raise ReturnConditionCheckFailed("参数 page_no 格式错误", 400) + + def check_obj(self): + """ """ + self.check_search() + self.check_sort() + self.check_page_size() + self.check_page_no() + + +class UserLogin(User): + """用户登录""" + + def __init__(self): + super().__init__() + self.vcode = None + self.token = None + self.menus = None + + def login(self): + """登录""" + + def check_vcode(self): + """检查验证码格式""" + if type(self.vcode) is not str: + raise ReturnConditionCheckFailed("验证码格式错误", 200) + + def check_obj(self): + """""" + self.check_email() + self.check_vcode() + + def dict_to_show(self): + """显示对象""" + show_dict = { + "name": self.name, + "token": self.token, + "menus": self.menus + } + + return show_dict + + +class SendLoginVcodeEmail(User): + """发送登录验证码邮件""" + + def __init__(self): + super().__init__() + self.vcode = None + + def check_obj(self): + self.check_email() + + +class UserManage(User): + """用户管理""" + + def create(self): + """创建新用户""" + + def disable(self): + """禁用用户""" + + def active(self): + """激活用户""" + + def delete_user(self): + """删除用户""" + + def manage_role_of_user(self): + """管理用户角色""" diff --git a/Modules/AdminUser/UserRoutes.py b/Modules/AdminUser/UserRoutes.py new file mode 100644 index 0000000..f1ef203 --- /dev/null +++ b/Modules/AdminUser/UserRoutes.py @@ -0,0 +1,137 @@ +from flask import Blueprint, request + +from Utils.ErrorUtil import ReturnConditionCheckFailed +from Modules.AdminUser.UserImpl import UserManageImpl, ListUserImpl, UserLoginImpl, SendLoginVcodeEmailImpl +from Modules.AdminUser.UserAuthUtils import verify_token, authority_scope + +user_route = Blueprint('user', __name__) + + +@user_route.route('/online_check', methods=['GET']) +@verify_token +def online_check_route(**kwargs): + """ + 在线检查,检查token是否有效 + """ + return {"info": "正常"}, 200 + + +@user_route.route('/create', methods=['POST']) +@verify_token +@authority_scope(['admin']) +def create_user_route(**kwargs): + """新建用户""" + try: + req = request.json + user_manage = UserManageImpl() + user_manage.email, user_manage.name, user_manage.pwd, user_manage.role = req['email'], req['name'], req['pwd'], req['role'] + user_manage.check_obj(columns=["email", "name", "pwd", "role"]) + user_manage.create() + return {"info": "用户创建成功"} + except ReturnConditionCheckFailed as e: + e.log_error() + return {"info": e.__str__()}, e.status_code + + +@user_route.route('/list', methods=['POST']) +@verify_token +@authority_scope(['admin']) +def list_user_route(**kwargs): + """ + 用户信息列表接口 + """ + try: + req = request.json + list_user = ListUserImpl( + req['search'], + req['sort'], + req['page_size'], + req['page_no'] + ) + list_user.check_obj() + result = list_user.list() + return {"info": '查询结果', "result": result}, 200 + except ReturnConditionCheckFailed as e: + e.log_error() + return {"info": e.__str__()}, e.status_code + + +@user_route.route('/login', methods=['POST']) +def login_route(): + """登录""" + try: + req = request.json + user_login = UserLoginImpl() + user_login.email, user_login.pwd, user_login.vcode = req['email'], req['pwd'], req['vcode'] + user_login.check_obj() + user_login.login() + result = user_login.dict_to_show() + return {"info": "登录成功", "result": result}, 200 + except ReturnConditionCheckFailed as e: + e.log_error() + return {"info": e.__str__()}, e.status_code + + +@user_route.route('/send_vcode', methods=['POST']) +def send_vcode_to_user_route(): + """发送验证码""" + try: + req = request.json + obj = SendLoginVcodeEmailImpl() + obj.email = req['email'] + obj.check_obj() + obj.send_vcode_email() + return {"info": "发送成功"}, 200 + except ReturnConditionCheckFailed as e: + e.log_error() + return {"info": e.__str__()}, e.status_code + + +@user_route.route('/disable', methods=['GET']) +@verify_token +@authority_scope(['admin']) +def disable_user_route(**kwargs): + """停用用户接口""" + try: + user = UserManageImpl() + user.uid = request.args['UID'] + user.status = "disable" + user.check_obj(columns=["uid", "status"]) + user.disable() + return {"info": "已停用该用户"}, 200 + except ReturnConditionCheckFailed as e: + e.log_error() + return {"info": e.__str__()}, e.status_code + + +@user_route.route('/active', methods=['GET']) +@verify_token +@authority_scope(['admin']) +def active_user_route(**kwargs): + """激活用户接口""" + try: + user = UserManageImpl() + user.uid = request.args['UID'] + user.status = "normal" + user.check_obj(columns=["uid", "status"]) + user.active() + return {"info": "已激活该用户"}, 200 + except ReturnConditionCheckFailed as e: + e.log_error() + return {"info": e.__str__()}, e.status_code + + +@user_route.route('/delete', methods=['GET']) +@verify_token +@authority_scope(['admin']) +def delete_user_route(**kwargs): + """删除用户接口""" + return {"info": "调整中"}, 200 + + +@user_route.route('/manage_role', methods=['POST']) +@verify_token +@authority_scope(['admin']) +def manage_role_of_user_route(**kwargs): + """管理用户角色接口""" + return {"info": "调整中"}, 200 diff --git a/Modules/AdminUser/UserUtils.py b/Modules/AdminUser/UserUtils.py new file mode 100644 index 0000000..3f09e2e --- /dev/null +++ b/Modules/AdminUser/UserUtils.py @@ -0,0 +1,80 @@ +import os +import re +import base64 + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher + + +def encrypt_data(**kwargs): + """ + 用公钥加密 + Parameters: + msg str 待加密信息 + Returns: + 加密后结果 + """ + msg = kwargs['msg'] # 待加密信息 + + with open(os.path.abspath(os.path.dirname(__file__)+'/static/rsa_public_key.pem')) as f: + data = f.read() + public_key = RSA.importKey(data) + + cipher = PKCS1_cipher.new(public_key) + encrypt_text = base64.b64encode(cipher.encrypt(bytes(msg.encode("utf8")))) + return encrypt_text.decode('utf-8') + + +def decrypt_data(**kwargs): + """ + 用私钥解密 + Parameters: + encrypt_msg str 加密信息 + Returns: + 执行正确 解密后结果 + 执行错误 False + """ + try: + encrypt_msg = kwargs['encrypt_msg'] # 加密信息 + + with open(os.path.abspath(os.path.dirname(__file__)+'/static/rsa_private_key.pem')) as f: + data = f.read() + private_key = RSA.importKey(data) + + cipher = PKCS1_cipher.new(private_key) + back_text = cipher.decrypt(base64.b64decode(encrypt_msg), 0) + return back_text.decode('utf-8') + except Exception: + return False + + +def check_mail_fmt(email): + """ + 邮箱地址格式校验,仅允许@fecr.com.cn + Parameters: + email: 邮箱 + Returns: + result: 邮箱校验结果,正确返回True,不正确返回False + """ + regex = "^.+\\@fecr.com.cn" + case = (len(email) > 7) and (re.match(regex, email) is not None) + result = True if case else False + return result + + +def check_pwd_fmt(pwd): + """ + 密码强度校验 + Parameters: + pwd: 密码(已加密) + Returns: + result: 密码强度校验结果,正确返回True,不正确返回False + """ + password = decrypt_data(encrypt_msg=pwd) + if not password: + return False + + regex = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)^.{8,}$" + case = (len(password) >= 8) and (re.match(regex, password) is not None) + result = True if case else False + return result diff --git a/Modules/AdminUser/__init__.py b/Modules/AdminUser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Modules/AdminUser/static/menus.json b/Modules/AdminUser/static/menus.json new file mode 100644 index 0000000..5edca6d --- /dev/null +++ b/Modules/AdminUser/static/menus.json @@ -0,0 +1,6 @@ +{ + + "admin": ["Board", "Company", "Rating", "Setting"], + "Analysts": ["Board", "Company", "Rating"], + "guest": ["Board"] +} \ No newline at end of file diff --git a/Modules/AdminUser/static/rsa_private_key.pem b/Modules/AdminUser/static/rsa_private_key.pem new file mode 100644 index 0000000..34c11c5 --- /dev/null +++ b/Modules/AdminUser/static/rsa_private_key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDLoijMi4Ng7TkN+Dqc4nHgnnSq7y4AjPGd3C7qLej1mXBvh2wp +jNlpMIlIfhBIsOHW3+H/VmuCpWBtWk73P7VhkAdqiMZOC9OWBEwVuzNOPid+/Yjo +Guppz4YAB/sIhWUdiVoB1866/HnYHbf/+5sVx1Nvh8Vp85sgOZchIdmS/wIDAQAB +AoGBAKr+89W3vc4Rxlxluwps2QWu6fd0O6P4txhBgh/iB4Ldo130US6e+R5sxItc +WN5BspOWkxewgT1HFC8fq7nSBMN0ijG1j/+XIBRyWX6TwHppDPmN6r0MTAjwVNRz +MEVJIyHxlLOpC8B+n+knG5xKMhmks+BtPlYbLmqSf5a4pLZZAkEA+asJMyaJx0lG +yqFClprAWA65UtUlpjbdHI6xQer8NB0/PuSz3FL8b0EfVrAxqM1CpDxcHR3mFoVs +Yyk/4DAGSwJBANDMPpPjoWI3IPiLAdJ5POjIn86CkUSe1ZEucis2NEnC9UDUq0sG +xgG0SsI8UolZQdcnCRDfN92Q7bl1FG3qxZ0CQFKo//jBb6hdaGS6E1PNlJUS+uSj +0T1AuOA1lhZe+HVAoanvCmWNzCoBg6Ct4SMkIkZB/bVeKsmWDxadl5pgDBUCQQDA +EyN96GkNcKlj+nwyolTlz6kyz+nStkrAw3lDRxnSwQXcHcd1vUVpS/F5vQQwVWu8 +AKzWWIGL0Ube1FV2yAIBAkBfc2UyKnrJ9zG9l1E199/5l6m9U2Vhexl/b0DNSz4L +v/PNIsO3aeKXvnnRxCBCdiS7/ve4XRh+mBdfyvM93dz9 +-----END RSA PRIVATE KEY----- diff --git a/Modules/AdminUser/static/rsa_public_key.pem b/Modules/AdminUser/static/rsa_public_key.pem new file mode 100644 index 0000000..5b92297 --- /dev/null +++ b/Modules/AdminUser/static/rsa_public_key.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLoijMi4Ng7TkN+Dqc4nHgnnSq +7y4AjPGd3C7qLej1mXBvh2wpjNlpMIlIfhBIsOHW3+H/VmuCpWBtWk73P7VhkAdq +iMZOC9OWBEwVuzNOPid+/YjoGuppz4YAB/sIhWUdiVoB1866/HnYHbf/+5sVx1Nv +h8Vp85sgOZchIdmS/wIDAQAB +-----END PUBLIC KEY----- diff --git a/Modules/__init__.py b/Modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Utils/CommonUtil.py b/Utils/CommonUtil.py new file mode 100644 index 0000000..7ec02a8 --- /dev/null +++ b/Utils/CommonUtil.py @@ -0,0 +1,27 @@ + + +def sub_dict(param1, param2): + """ + 获取字典的子集 + Parameters: + param1: 原字典 + param2: 子集字段 + Returns: + 子集 + """ + return dict((key, value) for key, value in param1.items() if key in param2) + + +def df_iterrows(param): + """ + 按行以数组形式返回DataFrame的index、data + Parameters: + param: DataFrame 某个df对象 + Returns: + result: list 遍历df对象每行数据,包括index + """ + result = [] + for row in param.iterrows(): + index, data = row + result.append([index] + data.tolist()) + return result diff --git a/Utils/ErrorUtil.py b/Utils/ErrorUtil.py new file mode 100644 index 0000000..64bdbee --- /dev/null +++ b/Utils/ErrorUtil.py @@ -0,0 +1,90 @@ +import time +import traceback + +from flask import request +from werkzeug.exceptions import BadRequest + +from DBHelper.MongoHelperInstance import DB_GUA + + +def get_req_detail(req, param_type): + """ + 获取请求参数信息 + 若异常请求则返回空字符串 + """ + try: + if param_type == 'args': + return req.args.__str__() + elif param_type == 'json': + return req.json.__str__() + else: + return '' + except BadRequest: + return '' + + +class JustThrowError(RuntimeError): + """自定义抛出异常信息""" + + def __init__(self, error_info): + self.error_info = error_info + + def __str__(self): + return self.error_info + + +class APIReturnError(RuntimeError): + """接口返回异常信息""" + def __init__(self, error_info, status_code): + self.error_info = error_info # 异常信息 + self.status_code = status_code # 状态码 + + def __str__(self): + return self.error_info + + +class CheckFailed(RuntimeError): + """检查异常""" + + def __init__(self, failed_info, status_code): + self.failed_info = failed_info # 失败信息 + self.status_code = status_code # 状态码 + + def __str__(self): + return self.failed_info + + def log_error(self): + + info = { + "ip": request.remote_addr, + "request_info": { + "path": request.path, + "method": request.method, + "headers": request.headers.__str__(), + "args": get_req_detail(request, 'args'), + "json": get_req_detail(request, 'json') + }, + "traceback": traceback.format_exc(), + "exception": type(self).__name__, + "is_solved": "no", + "time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + } + + DB_GUA.insert_single_data( + "日志", + "异常日志", + info + ) + + +class LogConditionCheckFailed(CheckFailed): + """直接记录检查异常""" + + def __init__(self, failed_info, status_code): + self.failed_info = failed_info # 失败信息 + self.status_code = status_code # 状态码 + self.log_error() + + +class ReturnConditionCheckFailed(CheckFailed): + """条件检查失败 抛出异常 接口返回失败原因和状态码""" diff --git a/Utils/ObjUtil.py b/Utils/ObjUtil.py new file mode 100644 index 0000000..122e0d9 --- /dev/null +++ b/Utils/ObjUtil.py @@ -0,0 +1,83 @@ + + +class SpecObject(object): + """自定义类""" + + fields_map = {} + + def dict_keys_toggle(self, **kwargs): + """字典键值切换""" + _dict_ = dict() + default_types = ['str', 'int', 'float', 'dict', 'bool', 'tuple'] + for key in self.__dict__.keys(): + if key in self.fields_map.keys(): + # 常规类型 + if type(self.__dict__[key]).__name__ in default_types: + _dict_[self.fields_map[key]] = self.__dict__[key] + + # 列表类型 + elif type(self.__dict__[key]).__name__ == 'list': + if len(self.__dict__[key]) == 0: + _dict_[self.fields_map[key]] = self.__dict__[key] + elif type(self.__dict__[key][0]).__name__ in default_types: + _dict_[self.fields_map[key]] = self.__dict__[key] + else: + _dict_[self.fields_map[key]] = [item.dict_keys_toggle() for item in self.__dict__[key]] + + # 空值类型 + elif self.__dict__[key] is None: + _dict_[self.fields_map[key]] = self.__dict__[key] + + # 对象类型 + else: + _dict_[self.fields_map[key]] = self.__dict__[key].dict_keys_toggle() + + if 'columns' in kwargs: + _dict_ = {key: _dict_[key] for key in kwargs['columns']} + + return _dict_ + + def dict_to_show(self, **kwargs): + """字典显示格式""" + return self.dict_keys_toggle(**kwargs) + + def dict_to_save(self, **kwargs): + """字典存储格式""" + return self.dict_keys_toggle(**kwargs) + + def dict_to_return(self, **kwargs): + """字典返回格式""" + + @staticmethod + def dict_to_set(**kwargs): + """实例设值""" + instance = kwargs['instance']() + fields_map = dict([v, k] for k, v in instance.fields_map.items()) + for field in list(kwargs['data'].keys()): + instance.__setattr__(fields_map[field], kwargs['data'][field]) + return instance + + @staticmethod + def instance_list_to_set(**kwargs): + """实例数组设值""" + list_ = list() + for item in list(kwargs['data']): + instance = kwargs['instance']() + fields_map = dict([v, k] for k, v in instance.fields_map.items()) + for field in list(item.keys()): + instance.__setattr__(fields_map[field], item[field]) + list_.append(instance) + + return list_ + + @staticmethod + def get_attr(_dict_, _key_, **kwargs): + """获取字典属性值""" + value = kwargs['default'] if kwargs.__contains__('default') else None + try: + value = _dict_[_key_] + except KeyError: + pass + except TypeError: + pass + return value diff --git a/Utils/RouteUtil.py b/Utils/RouteUtil.py new file mode 100644 index 0000000..89ba712 --- /dev/null +++ b/Utils/RouteUtil.py @@ -0,0 +1,21 @@ +from Utils.ErrorUtil import APIReturnError + + +class RouteUtil(object): + """路由工具""" + + @staticmethod + def require_params_check(req_body, req_params): + """必需参数检查""" + + if not isinstance(req_body, dict): + raise Exception + + if not isinstance(req_params, list): + raise Exception + + params_in_req_body = list(req_body.keys()) + for param in req_params: + if param not in params_in_req_body: + error_info = "缺失必需参数: {}".format(param) + raise APIReturnError(error_info=error_info, status_code=200) diff --git a/Utils/ValidateUtil.py b/Utils/ValidateUtil.py new file mode 100644 index 0000000..71bab7f --- /dev/null +++ b/Utils/ValidateUtil.py @@ -0,0 +1,133 @@ +import re + +from Utils.ErrorUtil import ReturnConditionCheckFailed + + +class Validate(object): + """常用格式检查""" + + @staticmethod + def email(param): + """邮箱格式""" + regex = "^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$" + case = (len(param) > 7) and (re.match(regex, param) is not None) + result = True if case else False + return result + + @staticmethod + def password(param): + """密码格式""" + regex = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)^.{8,}$" + case = (len(param) >= 8) and (re.match(regex, param) is not None) + return True if case else False + + @staticmethod + def telephone(param): + """手机号格式""" + regex = "(^(13[0-9]|14[01456879]|15[0-3,5-9]|16[2567]|17[0-8]|18[0-9]|19[0-3,5-9])d{8}$)" + case = re.match(regex, param) is not None + return True if case else False + + @staticmethod + def year_format(param): + """yyyy年""" + regex = "\d{4}年" + case = re.match(regex, param) is not None + return True if case else False + + @staticmethod + def date_format(param): + """yyyy-mm-dd""" + regex = "\d{4}-\d{1,2}-\d{1,2}" + case = re.match(regex, param) is not None + return True if case else False + + @staticmethod + def time_format(param): + """yyyy-mm-dd hh:mm:ss""" + regex = "\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}" + case = re.match(regex, param) is not None + return True if case else False + + @staticmethod + def image(param): + """图片格式""" + mimetype = param.mimetype + return True if mimetype in ['image/jpeg', 'image/png'] else False + + +class ValidateAttr(object): + """对象属性值检查""" + + FIELD_ERROR_INFO = '字段【{}】异常' + FILED_MAP_ERROR = '字段未映射完整' + + def __init__(self, **kwargs): + self.kwargs = kwargs + + self.code = kwargs['error_code'] if 'error_code' in kwargs else 202 + + if 'error_info' in kwargs: + self.info = kwargs['error_info'] + elif 'mark' in kwargs: + self.info = self.FIELD_ERROR_INFO.format(kwargs['mark']) + else: + self.info = None + + def __get__(self, instance, owner): + if self.kwargs['field'] in instance.__dict__: + return instance.__dict__[self.kwargs['field']] + + def __set__(self, instance, value): + try: + if not self.info: + if instance.fields_map.__contains__(self.kwargs['field']): + self.info = self.FIELD_ERROR_INFO.format(instance.fields_map[self.kwargs['field']]) + except AttributeError: + raise ReturnConditionCheckFailed(self.FILED_MAP_ERROR, self.code) + + if 'type' in self.kwargs: + """检查实例的属性类型""" + if type(self.kwargs['type']) is list: + if type(value) not in self.kwargs['type']: + raise ReturnConditionCheckFailed(self.info, self.code) + else: + if not isinstance(value, self.kwargs['type']): + if value is not None and 'default' not in self.kwargs: + raise ReturnConditionCheckFailed(self.info, self.code) + + if 'length' in self.kwargs: + """检查实例的属性值长度(一般是str类型)""" + if len(value) != self.kwargs['length']: + raise ReturnConditionCheckFailed(self.info, self.code) + + if 'in_list' in self.kwargs: + """检查实例属性是否包含于列表中(属性有列表和非列表两种情况)""" + if type(value) is not list: + if value not in self.kwargs['in_list']: + raise ReturnConditionCheckFailed(self.info, self.code) + else: + for item in value: + if item not in self.kwargs['in_list']: + raise ReturnConditionCheckFailed(self.info, self.code) + + if 'instance_list' in self.kwargs: + """检查实例列表""" + if type(value) is not list: + raise ReturnConditionCheckFailed(self.info, self.code) + else: + for item in value: + if not isinstance(item, self.kwargs['instance_list']): + raise ReturnConditionCheckFailed(self.info, self.code) + + if 'func' in self.kwargs: + """属性检查函数""" + if not list(map(self.kwargs['func'], [value]))[0]: + raise ReturnConditionCheckFailed(self.info, self.code) + + if 'default' in self.kwargs: + """实例属性默认值""" + if value is None: + value = self.kwargs['default'] + + instance.__dict__[self.kwargs['field']] = value diff --git a/Utils/__init__.py b/Utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app.py b/app.py new file mode 100644 index 0000000..7b633f5 --- /dev/null +++ b/app.py @@ -0,0 +1,20 @@ +from flask import Flask +from flask_cors import * + +from Modules.AdminUser.UserRoutes import user_route + +app = Flask(__name__) +CORS(app, supports_credentials=True) +app.config['JSON_SORT_KEYS'] = False + + +app.register_blueprint(user_route, url_prefix='/admin/user') + + +@app.route('/admin/version') +def hello_world(): + return 'WideRating Guarantee Admin API v0.2' + + +if __name__ == '__main__': + app.run() diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 0000000..c0ca9df --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,8 @@ +APP_PORT = 51014 + +# 并行工作进程数 +workers = 10 +# 监听内网端口 +bind = '0.0.0.0:{}'.format(APP_PORT) +# 工作模式协程 +worker_class = 'gevent' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..48258bc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +markupsafe==2.0.1 +itsdangerous==2.0.1 +flask +flask_cors +gunicorn +gevent +pymongo~=3.11.0 +requests~=2.25.1 +pandas~=1.3.5 +pycryptodome \ No newline at end of file