├── .env ├── Dockerfile ├── README.md ├── aerich.ini ├── app ├── __init__.py ├── commons │ ├── __init__.py │ └── tencent_cdn.py ├── logs │ └── __init__.py ├── middleware │ └── __init__.py ├── models │ ├── __init__.py │ └── model.py ├── mydbs │ ├── __init__.py │ ├── database.py │ └── tencen_cdn.py ├── routers │ ├── __init__.py │ └── cdn_views.py ├── schemas │ ├── __init__.py │ └── tencten_cdn.py └── utils │ ├── .qiniu_pythonsdk_hostscache.json │ ├── __init__.py │ ├── common_util.py │ ├── constant.py │ ├── fake_data.py │ ├── qiniu_sdk_python.py │ └── tencentcloud_sdk_python.py ├── config.py ├── create_table.sql ├── deploy.sh ├── docker-compose.yaml ├── main.py ├── migrations └── models │ ├── 0_202029062420142956_init.json │ ├── 1_202031062420143154_update.json │ └── old_models.py ├── pip.conf ├── prestart.sh ├── requirements.txt └── run.sh /.env: -------------------------------------------------------------------------------- 1 |  2 | # 服务器环境,指明生产服务器,开发服务器,测试服务器来动态触发配置变量的调用 3 | # 系统中设置的环境变量优先于此处设置的环境变量 4 | ENVIRONMENT = "development" # production or development or testing 5 | 6 | # 开发服务器环境的配置 7 | # redis配置 8 | DEV_REDIS_HOST= 9 | DEV_REDIS_PORT="6379" 10 | DEV_REDIS_USERNAME="root" 11 | DEV_REDIS_PASSWORD="123456" 12 | DEV_REDIS_DB="1" 13 | 14 | # 七牛云配置 15 | DEV_QINIU_ACCESS_KEY = 16 | DEV_QINIU_SECRET_KEY = 17 | DEV_QINIU_BUCKET_NAME = 'dengsz' 18 | DEV_QINIU_DOMAIN_NAME = 'http://qayumwil2.bkt.clouddn.com/' 19 | 20 | # 腾讯CND配置 21 | DEV_TENCENT_SECRET_KEY = '中文' 22 | DEV_TENCENT_ACCESS_ID = 'tencent4' 23 | 24 | # mysql数据库配置 25 | DEV_MYSQL_SERVER = 26 | DEV_MYSQL_USER = 'root' 27 | DEV_MYSQL_PASSWORD = '123456' 28 | DEV_MYSQL_DB_NAME = 'users' 29 | DEV_MYSQL_PORT = '3306' 30 | 31 | 32 | # 生产服务器环境配置 33 | PROD_REDIS_HOST= 34 | PROD_REDIS_PORT="6379" 35 | PROD_REDIS_USERNAME="root" 36 | PROD_REDIS_PASSWORD="123456" 37 | PROD_REDIS_DB="1" 38 | 39 | PROD_QINIU_ACCESS_KEY = 40 | PROD_QINIU_SECRET_KEY = 41 | PROD_QINIU_BUCKET_NAME = 'dengsz' 42 | PROD_QINIU_DOMAIN_NAME = 'http://qayumwil2.bkt.clouddn.com/' 43 | 44 | PROD_TENCENT_SECRET_KEY = 'tencent3' 45 | PROD_TENCENT_ACCESS_ID = 'tencent4' 46 | 47 | PROD_MYSQL_SERVER = 48 | PROD_MYSQL_USER = 'root' 49 | PROD_MYSQL_PASSWORD = 'cdndev' 50 | PROD_MYSQL_DB_NAME = 'cdn_users' 51 | PROD_MYSQL_PORT = '3306' 52 | 53 | 54 | # 测试服务器环境配置 55 | TEST_REDIS_HOST= 56 | TEST_REDIS_PORT="6379" 57 | TEST_REDIS_USERNAME="root" 58 | TEST_REDIS_PASSWORD="123456" 59 | TEST_REDIS_DB="1" 60 | 61 | TEST_QINIU_ACCESS_KEY = 62 | TEST_QINIU_SECRET_KEY = 63 | TEST_QINIU_BUCKET_NAME = 'dengsz' 64 | TEST_QINIU_DOMAIN_NAME = 'http://qayumwil2.bkt.clouddn.com/' 65 | 66 | TEST_TENCENT_SECRET_KEY = 'tencent3' 67 | TEST_TENCENT_ACCESS_ID = 'tencent4' 68 | 69 | TEST_MYSQL_SERVER = '127.0.0.1' 70 | TEST_MYSQL_USER = 'root' 71 | TEST_MYSQL_PASSWORD = '123456' 72 | TEST_MYSQL_DB_NAME = 'users' 73 | TEST_MYSQL_PORT = '3306' 74 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 2 | 3 | EXPOSE 8001/tcp 4 | 5 | RUN mkdir -p /root/.pip/ &&\ 6 | mkdir -p /ikglobal/logs 7 | 8 | WORKDIR /app 9 | 10 | COPY . /app 11 | COPY ./pip.conf /root/.pip/ 12 | 13 | RUN pip install gunicorn && pip install --no-cache-dir -r requirements.txt 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDN用户模块 2 | 3 | 4 | ## 简介 5 | 使用FastAPT框架构架的一个web工程,集成了数据orm工具,支持异步,拿来开箱即用的工程demo 6 | 7 | 8 | ## 技术栈 9 | 10 | ```bash 11 | Python3.7 + FastAPI + MySQL + Redis + Tortoise-orm + aerich 12 | ``` 13 | 14 | ## API接口文档地址 15 | 16 | ```bash 17 | 18 | # host 服务器地址 19 | http://host:8001/docs#/ 20 | http://host:8001/redoc 21 | 22 | ``` 23 | 24 | ## 数据库版本管理 25 | 26 | ```bash 27 | # 初始化配置, 创建一个migrates文件夹和aerich.ini配置文件 28 | aerich init -t app.mydbs.database.TORTOISE_ORM 29 | 30 | # 数据库生成表, 并创建migrations/models迁移文件 31 | aerich init-db 32 | 33 | # 新增迁移文件 *_update.json 34 | aerich migrate 35 | 36 | # 执行迁移文件更新数据库 37 | aerich upgrade 38 | 39 | # 回到上一个版本 40 | aerich downgrade 41 | 42 | # 查看历史迁移记录 43 | aerich history 44 | 45 | # 查看当前版本的迁移记录 46 | aerich heads 47 | ``` 48 | 49 | ## 目录结构说明 50 | 51 | ```bash 52 | . 53 | ├── aerich.ini 54 | ├── app # 代码主目录 55 | │   ├── commons # 一些逻辑 56 | │   │   ├── __init__.py 57 | │   │   └── tencent_cdn.py 58 | │   ├── __init__.py 59 | │   ├── logs # 日志配置目录 60 | │   │   ├── __init__.py 61 | │   ├── middleware # 中间件配置目录 62 | │   │   ├── __init__.py 63 | │   ├── models # 数据模型目录 64 | │   │   ├── __init__.py 65 | │   │   ├── model.py 66 | │   ├── mydbs # 数据库相关目录 67 | │   │   ├── database.py 68 | │   │   ├── __init__.py 69 | │   │   └── tencen_cdn.py 70 | │   ├── __pycache__ 71 | │   │   └── __init__.cpython-37.pyc 72 | │   ├── routers # 路由视图目录 73 | │   │   ├── cdn_views.py 74 | │   │   ├── __init__.py 75 | │   ├── schemas # 参数校验模型 76 | │   │   ├── __init__.py 77 | │   │   └── tencten_cdn.py 78 | │   └── utils # 工具目录 79 | │   ├── common_util.py 80 | │   ├── constant.py 81 | │   ├── fake_data.py 82 | │   ├── __init__.py 83 | │   ├── phone_code.py 84 | │   ├── qiniu_sdk_python.py 85 | │   └── tencentcloud_sdk_python.py 86 | ├── config.py # 配置文件,包含生产,测试,开发三套配置 87 | ├── create_table.sql # 建表sql 88 | ├── deploy.sh # docker镜像构建 89 | ├── docker-compose.yaml # docker容器编排管理 90 | ├── Dockerfile # 构建镜像配置文件 91 | ├── ik_cnd_user_app_logs.log # app日志 92 | ├── log.log # 日志 93 | ├── main.py # 程序启动文件 94 | ├── migrations # 数据库管理文件 95 | │   └── models 96 | │   ├── 0_202056062220145621_init.json 97 | │   └── old_models.py 98 | ├── pip.conf # pip配置 99 | ├── prestart.sh # docker启动时自动执行数据库迁移脚本 100 | ├── README.md # 程序文档 101 | ├── requirements.txt # 依赖环境 102 | └── run.sh # 启动docker容器 103 | 104 | 105 | ``` 106 | -------------------------------------------------------------------------------- /aerich.ini: -------------------------------------------------------------------------------- 1 | [aerich] 2 | tortoise_orm = app.mydbs.database.TORTOISE_ORM 3 | location = ./migrations 4 | 5 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/26 16:27 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | 9 | from fastapi import FastAPI 10 | 11 | from app.middleware import middleware_init 12 | from app.routers import router_init 13 | from app.logs import log_init, sys_log 14 | from app.mydbs.database import db_init 15 | from app.utils.common_util import write_log 16 | 17 | 18 | def conf_init(app): 19 | from config import configs 20 | sys_log.info(msg=f'Start app with {configs.ENVIRONMENT} environment') 21 | if configs.ENVIRONMENT == 'production': 22 | app.docs_url = None 23 | app.redoc_url = None 24 | app.debug = False 25 | 26 | 27 | async def start_event(): 28 | await write_log(msg='系统启动') 29 | 30 | 31 | async def shutdown_event(): 32 | await write_log(msg='系统关闭') 33 | 34 | 35 | def create_app(): 36 | app = FastAPI(title="CDN_USER_API", 37 | description="cnd平台用户模块接口文档", 38 | version="1.0.0", 39 | on_startup=[start_event], 40 | on_shutdown=[shutdown_event] 41 | ) 42 | 43 | # 初始化日志 44 | log_init() 45 | 46 | # 加载配置 47 | conf_init(app) 48 | 49 | # 初始化路由配置 50 | router_init(app) 51 | 52 | # 初始化中间件 53 | middleware_init(app) 54 | 55 | # 建表 56 | db_init(app) 57 | 58 | return app 59 | -------------------------------------------------------------------------------- /app/commons/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/12 12:27 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | -------------------------------------------------------------------------------- /app/commons/tencent_cdn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/12 12:27 5 | # @File : tencent_cdn.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | import json 8 | import pickle 9 | 10 | from fastapi import HTTPException 11 | 12 | from app.logs import sys_log 13 | from app.mydbs.database import redis_session 14 | from app.utils.constant import MyConstant, HTTP 15 | from app.utils import tencentcloud_sdk_python as tencent 16 | from config import configs 17 | 18 | 19 | # CDN产品 20 | from app.mydbs.tencen_cdn import save_child_account_to_db, get_tencet_user_by_id 21 | 22 | production = configs.CDN_PRODUCTION_NAME 23 | 24 | secretId = configs.TENCENT_ACCESS_ID 25 | secretKey = configs.TENCENT_SECRET_KEY 26 | 27 | 28 | class TencentCdnAccount(object): 29 | 30 | def __init__(self): 31 | self.secret_id = secretId 32 | self.secret_key = secretKey 33 | self.production = production 34 | 35 | async def make_tencent_child_account(self): 36 | """创建腾讯云子账户""" 37 | ret = await tencent.make_tencent_child_account( 38 | self.secret_id, 39 | self.secret_key 40 | ) 41 | if not ret[0]: 42 | return False, '' 43 | return True, ret[1] 44 | 45 | async def login_tencent_cdn(self, tencent_user_id): 46 | """登录腾讯云获取token""" 47 | return await tencent.login_tencent_cdn( 48 | self.secret_id, 49 | self.secret_key, 50 | tencent_user_id, 51 | 52 | ) 53 | 54 | async def assume_role(self, tencent_user_id): 55 | """获取临时秘钥""" 56 | return await tencent.assume_role( 57 | self.secret_id, 58 | self.secret_key, 59 | tencent_user_id, 60 | ) 61 | 62 | async def get_auth_status(self, token): 63 | """获取实名状态""" 64 | 65 | return await tencent.get_auth_status( 66 | self.secret_id, 67 | self.secret_key, 68 | token 69 | ) 70 | 71 | async def sumbit_bank_data(self, bank_data): 72 | """提交银行卡信息""" 73 | return await tencent.sumbit_bank_data( 74 | self.secret_id, 75 | self.secret_key, 76 | bank_data 77 | ) 78 | 79 | async def submit_auth_code(self, pay_data): 80 | """提交打款信息""" 81 | return await tencent.submit_auth_code( 82 | self.secret_id, 83 | self.secret_key, 84 | pay_data 85 | ) 86 | 87 | async def maker_sure_auth(self): 88 | """确认实名信息""" 89 | return await tencent.maker_sure_auth( 90 | self.secret_id, 91 | self.secret_key 92 | ) 93 | 94 | async def submit_auth_info(self): 95 | """个人实名认证""" 96 | return await tencent.submit_auth_info( 97 | self.secret_id, 98 | self.secret_key 99 | ) 100 | 101 | async def user_resource(self, tencent_account_id): 102 | """个人名下购买的资源""" 103 | return await tencent.user_resource( 104 | self.secret_id, 105 | self.secret_key, 106 | tencent_account_id 107 | ) 108 | 109 | 110 | account = TencentCdnAccount() 111 | 112 | 113 | async def child_account_save_to_db(data): 114 | """将创建的子账号保存到数据库中""" 115 | ret = await save_child_account_to_db(data) 116 | if not ret: 117 | raise ValueError("保存数据失败") 118 | 119 | 120 | async def open_tencent_cdn(tencent_account_id): 121 | """开通cdn""" 122 | return True, '' 123 | 124 | 125 | async def get_child_assume_role_token(tencent_user_id): 126 | """获取腾讯子账户的临时秘钥并保存到redis中""" 127 | ret, token_data = await account.assume_role(tencent_user_id) 128 | if not ret: 129 | return False, "" 130 | 131 | try: 132 | redis_session.setex(tencent_user_id + "token", 7200, pickle.dumps(token_data)) 133 | return True, token_data 134 | except Exception as e: 135 | sys_log.error(msg="保存腾讯子账号:{} token到redis失败,err:{}".format(tencent_user_id, e)) 136 | return False, "" 137 | 138 | """ 139 | "tencent_user_id":{ 140 | "expiredTime": 1506433269, 141 | "expiration": "2017-09-26T13:41:09Z", 142 | "credentials": { 143 | "sessionToken": "sdadffwe2323er4323423", 144 | "tmpSecretId": "VpxrX0IMC pHXWL0Wr3KQNCqJix1uhMqD", 145 | "tmpSecretKey": "VpxrX0IMC pHXWL0Wr3KQNCqJix1uhMqD" 146 | } 147 | } 148 | """ 149 | 150 | 151 | async def get_child_account_token(tencent_user_id): 152 | """先去redis中拿token,没有在请求腾讯接口""" 153 | try: 154 | token_data = pickle.loads(redis_session.get(tencent_user_id + "token")) 155 | except Exception as e: 156 | token_data = None 157 | sys_log.error(msg="访问redis出错err:{}".format(e)) 158 | 159 | if not token_data: 160 | ret, token_data = await get_child_assume_role_token(tencent_user_id) 161 | if not ret: 162 | return None 163 | return token_data 164 | 165 | return token_data 166 | 167 | 168 | async def is_tencent_child_account(auth_user_id): 169 | """判断用户是否有子账户,并返回子账户ID""" 170 | 171 | cdn_user = await get_tencet_user_by_id(auth_user_id) 172 | if not cdn_user: 173 | raise HTTPException( 174 | status_code=HTTP.HTTP_404_NOT_FOUND, 175 | ) 176 | if not cdn_user.tencent_user_id: 177 | return False, "" 178 | 179 | return True, cdn_user.tencent_user_id 180 | 181 | 182 | async def get_user_resource(tencent_user_id): 183 | """从腾讯或者本地redis中获取用户购买的资源""" 184 | 185 | try: 186 | user_resources = pickle.loads(redis_session.get(tencent_user_id + "resources")) 187 | except Exception as e: 188 | user_resources = None 189 | sys_log.error(msg="访问redis出错err:{}".format(e)) 190 | 191 | if not user_resources: 192 | ret, user_resources = await account.user_resource(tencent_user_id) 193 | if not ret: 194 | return None 195 | 196 | redis_session.setex(tencent_user_id + "resources", 7200, pickle.dumps(user_resources)) 197 | 198 | return user_resources 199 | 200 | return user_resources 201 | 202 | 203 | async def get_user_auth_info(tencent_id): 204 | """从腾讯获取实名信息""" 205 | pass 206 | 207 | 208 | async def get_auth_status(token): 209 | """获取认证状态""" 210 | return await account.get_auth_status(token) -------------------------------------------------------------------------------- /app/logs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/11 13:23 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | 9 | import logging 10 | from logging import handlers 11 | 12 | 13 | sys_log = logging.getLogger('ik_cdn_user_manage') 14 | sys_log.setLevel(level=logging.DEBUG) 15 | 16 | 17 | def log_init(): 18 | sys_log.setLevel(level=logging.DEBUG) 19 | formatter = logging.Formatter( 20 | '进程ID:%(process)d - ' 21 | '线程ID:%(thread)d- ' 22 | '日志时间:%(asctime)s - ' 23 | '代码路径:%(pathname)s:%(lineno)d - ' 24 | '日志等级:%(levelname)s - ' 25 | '日志信息:%(message)s' 26 | ) 27 | sys_log.handlers.clear() 28 | file_handler = handlers.TimedRotatingFileHandler('ik_cnd_user_app_logs.log', encoding='utf-8', when='W6') 29 | file_handler.setLevel(level=logging.INFO) 30 | file_handler.setFormatter(formatter) 31 | sys_log.addHandler(file_handler) 32 | -------------------------------------------------------------------------------- /app/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/2 9:53 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | from starlette.middleware.cors import CORSMiddleware 8 | 9 | 10 | # 指定允许跨域请求的url 11 | origins = [ 12 | "*" 13 | ] 14 | 15 | 16 | def middleware_init(app): 17 | app.add_middleware( 18 | CORSMiddleware, 19 | allow_origins=origins, 20 | allow_credentials=True, 21 | allow_methods=["*"], 22 | allow_headers=["*"], 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/28 10:03 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | from .model import * 9 | -------------------------------------------------------------------------------- /app/models/model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/28 10:28 5 | # @File : model.py 6 | 7 | from tortoise import fields 8 | from tortoise.contrib.pydantic import pydantic_model_creator 9 | from tortoise.models import Model 10 | 11 | 12 | class MyAbstractBaseModel(Model): 13 | 14 | id = fields.IntField(pk=True) 15 | created_at = fields.DatetimeField(null=True, auto_now_add=True) 16 | modified_at = fields.DatetimeField(null=True, auto_now=True) 17 | is_del = fields.IntField(null=False, default="0") 18 | 19 | class Meta: 20 | abstract = True 21 | 22 | 23 | class ClientUser(MyAbstractBaseModel): 24 | 25 | auth_user_id = fields.CharField(255, null=False, unique=True) 26 | username = fields.CharField(100, null=True) 27 | mobile = fields.CharField(100, null=True) 28 | real_name = fields.CharField(100, null=True) 29 | email = fields.CharField(100, null=True) 30 | avatar = fields.CharField(100, null=True, default='98d2d0f6-6851-49a8-8d31-8603204cc7311591066453.png') 31 | status = fields.IntField(null=True) 32 | tencent_user_id = fields.CharField(100, null=True, unique=True) 33 | tencent_auth_status = fields.IntField(null=True) 34 | 35 | class Meta: 36 | table = "client_users" 37 | table_description = "save ik user info" 38 | ordering = ["-created_at", "id"] 39 | 40 | class PydanticMeta: 41 | exclude = ["created_at", "modified_at", "id"] 42 | 43 | def __str__(self): 44 | return self.auth_user_id if self.auth_user_id else "ClientUserModel" 45 | 46 | 47 | ClientUserSchemaModel = pydantic_model_creator(ClientUser, name="ClientUser") 48 | ClientUserInSchemaModel = pydantic_model_creator(ClientUser, name="ClientUserIn", exclude_readonly=True) 49 | 50 | -------------------------------------------------------------------------------- /app/mydbs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/28 10:02 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/mydbs/database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/28 10:03 5 | # @File : database.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | 9 | import redis 10 | from tortoise.contrib.fastapi import register_tortoise 11 | 12 | from config import configs 13 | 14 | 15 | # mysql数据库url 16 | SQLALCHEMY_DATABASE_URL = "mysql://{}:{}@{}:{}/{}?charset=utf8".format( 17 | configs.MYSQL_USER, 18 | configs.MYSQL_PASSWORD, 19 | configs.MYSQL_SERVER, 20 | configs.MYSQL_PORT, 21 | configs.MYSQL_DB_NAME 22 | ) 23 | 24 | # 数据库迁移配置 25 | TORTOISE_ORM = { 26 | "connections": {"default": SQLALCHEMY_DATABASE_URL}, 27 | "apps": { 28 | "models": { 29 | "models": ["aerich.models", "app.models.model"], 30 | # 须添加“aerich.models” 后者“models”是上述models.py文件的路径 31 | "default_connection": "default", 32 | }, 33 | }, 34 | } 35 | 36 | 37 | # db = databases.Database(SQLALCHEMY_DATABASE_URL) 38 | # metadata = sqlalchemy.MetaData() 39 | # engine = sqlalchemy.create_engine(SQLALCHEMY_DATABASE_URL, pool_size=20, max_overflow=0, echo=True) 40 | # metadata.create_all(engine) 41 | 42 | 43 | def db_init(app): 44 | register_tortoise( 45 | app, 46 | db_url=SQLALCHEMY_DATABASE_URL, 47 | modules={"models": ["app.models.model"]}, 48 | generate_schemas=True, 49 | add_exception_handlers=True, 50 | ) 51 | 52 | 53 | # SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 54 | # class CustomBase(object): 55 | # 56 | # @declared_attr 57 | # def __tablename__(cls): 58 | # return cls.__name__.lower() 59 | # 60 | # 61 | # async def get_db(): 62 | # db = SessionLocal() 63 | # try: 64 | # yield db 65 | # finally: 66 | # db.close() 67 | 68 | 69 | # Base = declarative_base(cls=CustomBase) 70 | 71 | 72 | pool = redis.ConnectionPool( 73 | host=configs.REDIS_HOST, 74 | port=configs.REDIS_PORT, 75 | # password=configs.REDIS_PASSWORD, 76 | db=configs.REDIS_DB, 77 | ) 78 | redis_session = redis.Redis(connection_pool=pool) 79 | 80 | 81 | if __name__ == "__main__": 82 | token = { 83 | "expiredTime": "1506433269", 84 | "expiration": "2017-09-26T13:41:09Z", 85 | "credentials": { 86 | "sessionToken": "sdadffwe2323er4323423", 87 | "tmpSecretId": "VpxrX0IMC pHXWL0Wr3KQNCqJix1uhMqD", 88 | "tmpSecretKey": "VpxrX0IMC pHXWL0Wr3KQNCqJix1uhMqD" 89 | } 90 | } 91 | 92 | import pickle 93 | redis_session.setex("user", 100, pickle.dumps(token)) 94 | print(pickle.loads(redis_session.get("user"))) 95 | 96 | -------------------------------------------------------------------------------- /app/mydbs/tencen_cdn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/12 11:21 5 | # @File : tencen_cdn.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | import asyncio 8 | 9 | from app.logs import sys_log 10 | # from app.utils.constant import MySQLStr 11 | # from app.mydbs.database import db 12 | 13 | 14 | # async def get_tencet_user_by_id(auth_user_id: str): 15 | # """根据认证ID查询用户""" 16 | # 17 | # query = "".join([MySQLStr.USER_INFO, '"', auth_user_id, '"']) 18 | # try: 19 | # row = await db.fetch_one(query) 20 | # except Exception as e: 21 | # sys_log.error(msg={ 22 | # "msg": "查询用户失败", 23 | # "sql": "{}".format(query), 24 | # "error": "{}".format(e) 25 | # }) 26 | # return None 27 | # return row 28 | # 29 | # 30 | # async def get_tencet_user_list(skip: int, limit: int): 31 | # """查询用户列表""" 32 | # 33 | # if limit > 10000: 34 | # raise ValueError("最多只能查询10000条数据") 35 | # 36 | # # orm的写法 37 | # # query = tent_users.select() 38 | # query = "".join([MySQLStr.USER_INFO_LIST, str(skip), ",", str(limit)]) 39 | # try: 40 | # rows = await db.fetch_all(query) 41 | # except Exception as e: 42 | # sys_log.error(msg={ 43 | # "msg": "查询用户列表失败", 44 | # "sql": "{}".format(query), 45 | # "error": "{}".format(e) 46 | # }) 47 | # return None 48 | # return rows 49 | # 50 | # 51 | # async def save_child_account_to_db(data: dict): 52 | # """保存腾讯子账户到数据库中""" 53 | # 54 | # auth_user_id = data.get('auth_user_id') 55 | # if not auth_user_id: 56 | # raise ValueError('值错误,缺少auth_user_id') 57 | # 58 | # # orm的写法 59 | # # query = tent_users.insert() 60 | # query = MySQLStr.INSERT_CHLID_ACCOUNT 61 | # try: 62 | # await db.execute(query, data) 63 | # except Exception as e: 64 | # sys_log.error(msg={ 65 | # "msg": "保存子账户到数据库失败", 66 | # "sql": "{}".format(query), 67 | # "error": "{}".format(e) 68 | # }) 69 | # return False 70 | # return True 71 | # 72 | # 73 | # async def save_token_to_redis(token: str): 74 | # """将token保存到redis中""" 75 | # 76 | # try: 77 | # await redis_session.setex(token, 7200, token) 78 | # except Exception as e: 79 | # sys_log.error(msg={ 80 | # "msg": "保存用户的临时token到redis失败", 81 | # "token": "{}".format(token), 82 | # "error": "{}".format(e) 83 | # }) 84 | # return False 85 | # return True 86 | from app.models import ClientUser, ClientUserSchemaModel 87 | 88 | 89 | async def get_tencet_user_by_id(auth_user_id: str): 90 | """根据认证ID查询用户""" 91 | 92 | try: 93 | user = await ClientUserSchemaModel.from_queryset_single(ClientUser.get(auth_user_id=auth_user_id)) 94 | except Exception as e: 95 | sys_log.error(msg={ 96 | "msg": "查询用户失败", 97 | "error": "{}".format(e) 98 | }) 99 | return None 100 | return user 101 | 102 | 103 | async def get_tencet_user_list(skip: int, limit: int): 104 | """查询用户列表""" 105 | 106 | if limit > 10000: 107 | raise ValueError("最多只能查询10000条数据") 108 | 109 | try: 110 | rows = await ClientUserSchemaModel.from_queryset(ClientUser.all().limit(limit).offset(skip)) 111 | except Exception as e: 112 | sys_log.error(msg={ 113 | "msg": "查询用户列表失败", 114 | "error": "{}".format(e) 115 | }) 116 | return None 117 | return rows 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /app/routers/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/26 16:33 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | from app.routers import cdn_views 8 | from config import configs 9 | 10 | 11 | def router_init(app): 12 | app.include_router( 13 | cdn_views.router, 14 | prefix=configs.API_V1_STR, 15 | tags=["Tencent_CDN"], 16 | # dependencies=[Depends(get_token_header)], 17 | responses={404: {"description": "Not found"}}, 18 | ) 19 | -------------------------------------------------------------------------------- /app/routers/cdn_views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/12 10:42 5 | # @File : cdn_views.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | import pickle 8 | 9 | from fastapi import APIRouter, Depends, HTTPException 10 | 11 | from app.utils.constant import BUSINESS, HTTP 12 | from app.commons.tencent_cdn import account, get_child_account_token, is_tencent_child_account, get_user_resource, \ 13 | get_auth_status 14 | from app.commons.tencent_cdn import open_tencent_cdn 15 | from config import configs 16 | from app.schemas.tencten_cdn import UserListResponse, PingResponse, OpenCDNResponse, TencentLogin, PayInfoData, \ 17 | CompanyInfoData, UserInfoData, AuthStatusResponse, UserOneResponse, TokenResponse, IKLogin, UserAuthResponse, \ 18 | SubmitBankDataResponse, SubmitPayDataResponse, UserResourceListResponse 19 | from app.utils.common_util import CommonQueryParams 20 | from app.mydbs.tencen_cdn import get_tencet_user_by_id, get_tencet_user_list 21 | from app.mydbs.database import redis_session 22 | 23 | router = APIRouter() 24 | 25 | 26 | @router.get("/ping", response_model=PingResponse) 27 | async def ping(): 28 | """ 29 | 测试模块连通性 30 | """ 31 | re = {'三木': "测试redis的连接"} 32 | redis_session.setex("sz", 200, pickle.dumps(re)) 33 | re_data = redis_session.get("sz") 34 | print(pickle.loads(re_data)) 35 | return {'status_code': HTTP.HTTP_200_OK, "msg": "系统配置信息如下", 'data': [configs.dict()]} 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/28 10:04 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | -------------------------------------------------------------------------------- /app/schemas/tencten_cdn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/12 11:03 5 | # @File : tencten_cdn.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | from typing import Optional, Dict, List 8 | 9 | from pydantic import BaseModel 10 | 11 | from app.models import ClientUserSchemaModel 12 | 13 | 14 | class UserStatus(BaseModel): 15 | status: str 16 | name: str 17 | 18 | 19 | class UserInfo(ClientUserSchemaModel): 20 | pass 21 | 22 | 23 | class UserResource(BaseModel): 24 | """用户资源""" 25 | id: str 26 | type: str 27 | username: str 28 | exp_time: str 29 | status: str 30 | 31 | 32 | class TokenInfo(BaseModel): 33 | """腾讯子账户token信息嵌套内层""" 34 | sessionToken: str 35 | tmpSecretId: str 36 | tmpSecretKey: str 37 | 38 | 39 | class TokenAllInfo(BaseModel): 40 | """腾讯子账户token信息嵌套外层""" 41 | expiredTime: str 42 | expiration: str 43 | credentials: TokenInfo 44 | 45 | 46 | class TencentBaseResponse(BaseModel): 47 | """ 48 | 返回数据的基础结构 49 | """ 50 | status_code: int 51 | msg: str 52 | 53 | 54 | class UserListResponse(TencentBaseResponse): 55 | """ 56 | 返回用户列表 57 | """ 58 | data: Optional[List[Optional[UserInfo]]] = None 59 | 60 | 61 | class UserResourceListResponse(TencentBaseResponse): 62 | """ 63 | 返回用户资源列表 64 | """ 65 | data: List[UserResource] = None 66 | 67 | 68 | class SubmitBankDataResponse(TencentBaseResponse): 69 | """ 70 | 返回空 71 | """ 72 | data: Dict = None 73 | 74 | 75 | class SubmitPayDataResponse(SubmitBankDataResponse): 76 | """ 77 | 返回空 78 | """ 79 | pass 80 | 81 | 82 | class UserOneResponse(TencentBaseResponse): 83 | """ 84 | 返回一个用户 85 | """ 86 | data: UserInfo = None 87 | 88 | 89 | class TokenResponse(TencentBaseResponse): 90 | """ 91 | 返回子账户token 92 | """ 93 | data: TokenAllInfo = None 94 | 95 | 96 | class OpenCDNResponse(TencentBaseResponse): 97 | """ 98 | 开通cdn返回 99 | """ 100 | data: Dict = None 101 | 102 | 103 | class PingResponse(TencentBaseResponse): 104 | data: Optional[List] = None 105 | 106 | 107 | class AuthStatusResponse(TencentBaseResponse): 108 | """ 109 | 返回数据的基础结构 110 | """ 111 | data: UserStatus = None 112 | 113 | 114 | class UserAuthResponse(TencentBaseResponse): 115 | """ 116 | 返回数据的基础结构 117 | """ 118 | data: Dict = None 119 | 120 | 121 | class UserInfoData(BaseModel): 122 | """ 123 | 个人实名数据结构 124 | """ 125 | userIp: str 126 | userAgent: str 127 | name: str 128 | idcard: str 129 | provinceId: str 130 | cityId: str 131 | address: str 132 | 133 | 134 | class CompanyInfoData(BaseModel): 135 | """ 136 | 企业实名数据结构 137 | """ 138 | accountName: str 139 | accountId: str 140 | bankName: str 141 | bankId: str 142 | provinceName: str 143 | provinceId: str 144 | 145 | 146 | class PayInfoData(BaseModel): 147 | """ 148 | 提交打款信息 149 | """ 150 | authCode: str 151 | userIp: str 152 | userAgent: str 153 | 154 | 155 | class TencentLogin(BaseModel): 156 | """ 157 | 登录腾讯的账户结构 158 | """ 159 | tencent_account_id: str 160 | 161 | 162 | class IKLogin(BaseModel): 163 | """ 164 | 登录腾讯的账户结构 165 | """ 166 | auth_user_id: str 167 | -------------------------------------------------------------------------------- /app/utils/.qiniu_pythonsdk_hostscache.json: -------------------------------------------------------------------------------- 1 | {"http:QMZp9UCZ0VhdcAS6R0RvWMT9cTI3vwOH2h13Qm47:dengsz": {"upHosts": ["http://up-z2.qiniu.com", "http://upload-z2.qiniu.com", "-H up-z2.qiniu.com http://14.152.58.16"], "ioHosts": ["http://iovip-z2.qbox.me"], "deadline": 1591859696}} -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/26 16:34 5 | # @File : __init__.py.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | -------------------------------------------------------------------------------- /app/utils/common_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/1 10:08 5 | # @File : common_util.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | from datetime import datetime 9 | 10 | 11 | class CommonQueryParams(object): 12 | """ 13 | 定义一个公共的查询参数类,用于fastapi的依赖 14 | 可以以调用commons: CommonQueryParams = Depends() 15 | 16 | example: 17 | 18 | from fastapi import Depends, FastAPI 19 | 20 | app = FastAPI() 21 | 22 | 23 | fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] 24 | 25 | 26 | class CommonQueryParams: 27 | def __init__(self, q: str = None, skip: int = 0, limit: int = 100): 28 | self.q = q 29 | self.skip = skip 30 | self.limit = limit 31 | 32 | @app.get("/items/") 33 | async def read_items(commons: CommonQueryParams = Depends()): 34 | response = {} 35 | if commons.q: 36 | response.update({"q": commons.q}) 37 | items = fake_items_db[commons.skip : commons.skip + commons.limit] 38 | response.update({"items": items}) 39 | return response 40 | """ 41 | 42 | def __init__(self, q: str = None, skip: int = 0, limit: int = 10): 43 | self.q = q 44 | self.skip = skip 45 | self.limit = limit 46 | 47 | 48 | async def write_log(api=None, msg=None, user='root'): 49 | with open("log.log", mode="a", encoding='utf-8') as log: 50 | now = datetime.now() 51 | log.write(f"时间:{now} API调用事件:{api} 用户:{user} 消息:{msg}\n") 52 | -------------------------------------------------------------------------------- /app/utils/constant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/4 12:29 5 | # @File : constant.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | from enum import Enum 8 | 9 | 10 | class UserAction(str, Enum): 11 | """登录和注册的action参数""" 12 | USER_LOGIN = "login" 13 | USER_REGISTER = "register" 14 | 15 | 16 | class HTTP(object): 17 | HTTP_100_CONTINUE = 100 # 继续 18 | HTTP_101_SWITCHING_PROTOCOLS = 101 # 交换协议 19 | HTTP_200_OK = 200 # 查询请求成功 20 | HTTP_201_CREATED = 201 # 创建成功 21 | HTTP_202_ACCEPTED = 202 # 接受 22 | HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 # 非权威的信息 23 | HTTP_204_NO_CONTENT = 204 # 没有内容 24 | HTTP_205_RESET_CONTENT = 205 # 重置内容 25 | HTTP_206_PARTIAL_CONTENT = 206 # 部分内容 26 | HTTP_207_MULTI_STATUS = 207 # 多状态 27 | HTTP_300_MULTIPLE_CHOICES = 300 # 多个选择 28 | HTTP_301_MOVED_PERMANENTLY = 301 # 永久重定向 29 | HTTP_302_FOUND = 302 # 发现 30 | HTTP_303_SEE_OTHER = 303 # 重定向到其他 31 | HTTP_304_NOT_MODIFIED = 304 # 未修改 32 | HTTP_305_USE_PROXY = 305 # 使用代理 33 | HTTP_306_RESERVED = 306 # 未使用 34 | HTTP_307_TEMPORARY_REDIRECT = 307 # 临时重定向 35 | HTTP_400_BAD_REQUEST = 400 # 错误的请求 36 | HTTP_401_UNAUTHORIZED = 401 # 未经授权 37 | HTTP_402_PAYMENT_REQUIRED = 402 # 需要授权 38 | HTTP_403_FORBIDDEN = 403 # 禁止访问 39 | HTTP_404_NOT_FOUND = 404 # 没有找到 40 | HTTP_405_METHOD_NOT_ALLOWED = 405 # 方法不允许 41 | HTTP_406_NOT_ACCEPTABLE = 406 # 不可接受 42 | HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 # 代理省份验证 43 | HTTP_408_REQUEST_TIMEOUT = 408 # 请求超时 44 | HTTP_409_CONFLICT = 409 # 资源冲突 45 | HTTP_410_GONE = 410 # 资源存在但是不可用了 46 | HTTP_411_LENGTH_REQUIRED = 411 # 没有定义content-length 47 | HTTP_412_PRECONDITION_FAILED = 412 # 前提条件失败 48 | HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 # 请求包太大 49 | HTTP_414_REQUEST_URI_TOO_LONG = 414 # 请求url太长 50 | HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 # 不支持的媒体类型 51 | HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 # 请求范围不足 52 | HTTP_417_EXPECTATION_FAILED = 417 # 预期失败 53 | HTTP_422_UNPROCESSABLE_ENTITY = 422 # 不可加工 54 | HTTP_423_LOCKED = 423 # 被锁定 55 | HTTP_424_FAILED_DEPENDENCY = 424 # 失败的依赖 56 | HTTP_425_TOO_EARLY = 425 # 言之过早 57 | HTTP_428_PRECONDITION_REQUIRED = 428 # 先决条件要求 58 | HTTP_429_TOO_MANY_REQUESTS = 429 # 请求太多 59 | HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 # 请求头字段太大 60 | HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451 # 由于法律原因无法使用 61 | HTTP_500_INTERNAL_SERVER_ERROR = 500 # 服务器错误 62 | HTTP_501_NOT_IMPLEMENTED = 501 # 没有实现 63 | HTTP_502_BAD_GATEWAY = 502 # 网关错误 64 | HTTP_503_SERVICE_UNAVAILABLE = 503 # 服务不可用 65 | HTTP_504_GATEWAY_TIMEOUT = 504 # 网关超时 66 | HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 # HTTP协议版本不支持 67 | HTTP_507_INSUFFICIENT_STORAGE = 507 # 存储不足 68 | HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 # 网络身份验证要求 69 | 70 | 71 | class BUSINESS(object): 72 | # 认证相关 73 | INVALID_TOKEN = 20001 # 无效的token, 'Invalid token' 74 | ACCESS_TOKEN_EXPIRED = 20002 # access token过期, 'Access token expired' 75 | AUTHORIZATION_ERROR = 20004 # authorization字段错误, 'Authorization error' 76 | 77 | # 用户相关 78 | PHONE_ERROR = 20010 # 手机号格式不符合规则, 'Wrong format of mobile phone number' 79 | PHONE_EXISTED = 20011 # 手机号已被使用, 'Phone existed' 80 | EMAIL_ERROR = 20012 # 邮箱地址不符合规则, 'Wrong format of email address' 81 | EMAIL_EXISTED = 20013 # 邮箱地址已被使用, 'Email existed' 82 | PASSWORD_ERROR = 20014 # 密码不符合规则, 'Wrong format of password' 83 | USER_NOT_EXIST = 20016 # 账户不存在, 'Account does not exist' 84 | USER_OR_PASSWORD_ERROR = 20017 # 账户或密码错误, 'Wrong account or password' 85 | ACCOUNT_LOCKED = 20018 # 账户已被锁定, 'Account has been locked' 86 | ACCOUNT_EXISTED = 20019 # 账号已被使用, 'Account existed' 87 | 88 | # 验证码等 89 | CODE_ERROR = 20020 # 验证码错误, 'Wrong code' 90 | CODE_RESEND_ERROR = 20021 # 60s内不能重复发送验证码, '60s needed for resend' 91 | FILE_NOT_EXIST = 20022 # 文件不存在, 'file not found' 92 | EMPTY_FILE = 20023 # 文件为空, 'empty file' 93 | FILE_FORMAT_ERROR = 20024 # 上传的文件格式不正确, 'wrong_pic_format' 94 | 95 | # 腾讯cdn交互模块 96 | MAKE_TENT_CHILD_ACCOUNT_FIELD = 5100 # 创建用户失败 97 | CHILD_ACCOUNT_NOT_AUTH = 5101 # 子用户没有实名认证 98 | CHILD_ACCOUNT_GET_TENCENT_TOKEN_FIELD = 5102 # 获取子用户token失败 99 | CHILD_ACCOUNT_OPEN_CDN_FIELD = 5103 # 子用户CDN开通失败 100 | GET_ACCOUNT_TOKEN_FIELD = 5104 # 获取用户token失败 101 | GET_USER_AUTH_STATUS_FIELD = 5105 # 获取用户认证状态失败 102 | CDN_USER_NOT_EXISTED = 5106 # 用户不存在 103 | GET_ASSUME_ROLE_FIELD = 5107 # 获取用户临时秘钥失败 104 | CDN_API_CALL_FIELD = 5107 # API调用失败 105 | 106 | 107 | class MyConstant(object): 108 | TOKEN_EXP_TIME = 8000 # 登录腾讯云数据存入redis的过期时间 109 | TENCENT_LOGIN = 'login' # 登录操作 110 | 111 | 112 | class MySQLStr(object): 113 | """ 114 | sql语句 115 | """ 116 | # 查询单个用户 117 | USER_INFO = """SELECT a.id, a.auth_user_id, a.username, a.mobile, a.real_name, a.email, a.avatar, a.status, a.tencent_user_id, a.tencent_auth_status FROM client_users AS a WHERE a.auth_user_id = """ 118 | # 查询用户列表 119 | USER_INFO_LIST = """SELECT a.id, a.auth_user_id, a.username, a.mobile, a.real_name, a.email, a.avatar, a.status, a.tencent_user_id, a.tencent_auth_status FROM client_users AS a LIMIT """ 120 | # 插入tencent child account 121 | INSERT_CHLID_ACCOUNT = """INSERT INTO client_users (auth_user_id, tencent_user_id, tencent_auth_status) VALUES (:auth_user_id, :tencent_user_id, :tencent_auth_status)""" 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /app/utils/fake_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/6/8 10:04 5 | # @File : fake_data.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | from faker import Faker 8 | 9 | 10 | myfaker = Faker('zh_CN') 11 | 12 | 13 | if __name__ == '__main__': 14 | print(myfaker.name()) 15 | print(myfaker.address()) 16 | print(myfaker.email()) 17 | 18 | -------------------------------------------------------------------------------- /app/utils/qiniu_sdk_python.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/27 9:35 5 | # @File : qiniu-sdk-python.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | import time 8 | import uuid 9 | 10 | from qiniu import Auth, put_file 11 | # from app.databases.database import get_db 12 | 13 | from config import configs 14 | 15 | 16 | img_suffix = ["jpg", "png"] 17 | 18 | 19 | def upload_data_to_qiniu(file_data): 20 | """ 21 | # 数据上传七牛云对象存储 22 | """ 23 | q = get_auth_object() 24 | 25 | save_name = file_check(file_data) 26 | 27 | token = generate_upload_token(q, save_name) 28 | 29 | try: 30 | ret, info = upload_file(token, save_name, file_data) 31 | if info.status_code == 200: 32 | key = ret.get("key") 33 | return True, key 34 | else: 35 | return False, '文件上传七牛云失败' 36 | except: 37 | return False, '文件上传七牛云失败' 38 | 39 | 40 | def generate_upload_token(auth_object, save_name): 41 | """ 42 | :param auth_object: 鉴权对象 43 | :return: 上传token 44 | """ 45 | # 生成上传 Token,可以指定过期时间等 46 | return auth_object.upload_token(configs.QINIU_BUCKET_NAME, save_name, 3600) 47 | 48 | 49 | def get_auth_object(): 50 | """ 51 | :return: 鉴权对象 52 | """ 53 | # 构建鉴权对象 54 | return Auth(configs.QINIU_ACCESS_KEY, configs.QINIU_SECRET_KEY) 55 | 56 | 57 | def file_check(file_data): 58 | """ 59 | :param file_data:传入的文件名 60 | :return: 61 | """ 62 | # 上传后保存的文件名 63 | file_str_list = file_data.split('.') 64 | if file_str_list[1] not in img_suffix: 65 | raise ValueError('请上传文件后缀为"jpg", "png"的头像文件') 66 | save_file_name = make_save_name() 67 | return save_file_name 68 | 69 | 70 | def make_save_name(): 71 | return ''.join([str(uuid.uuid4()), str(time.time()).split('.')[0], '.', 'png']) 72 | 73 | 74 | def upload_file(token, key, file_data): 75 | """ 76 | :param token: 上传token 77 | :param key: 上传后保存的文件名 78 | :param file_data: 上传的文件 79 | :return: 80 | """ 81 | return put_file(token, key, file_data) 82 | 83 | 84 | def private_download_url(key): 85 | q = get_auth_object() 86 | base_url = ''.join([configs.QINIU_DOMAIN_NAME, key]) 87 | return q.private_download_url(base_url, expires=3600) 88 | 89 | 90 | def save_key_db(key): 91 | # db_session = get_db 92 | pass 93 | 94 | 95 | if __name__ == "__main__": 96 | file = 'C:/Users/IK/Desktop/senzhong.jpg' 97 | # settings = type('settings', (object,), dict( 98 | # QINIU_DOMAIN_NAME='http://qayumwil2.bkt.clouddn.com/', 99 | # QINIU_ACCESS_KEY='QMZp9UCZ0VhdcAS6R0RvWMT9cTI3vwOH2h13Qm47', 100 | # QINIU_SECRET_KEY='34iPk_IX6bE0mhabY-TSp-6eDBu3akj196PMo28D', 101 | # QINIU_BUCKET_NAME='dengsz' 102 | # )) 103 | res = upload_data_to_qiniu(file) 104 | print(res) 105 | print(private_download_url(res[1])) 106 | # print(private_download_url('f54ad132-5e27-447d-8018-0bea375496931591067315.png')) 107 | # import pymysql 108 | # pymysql.install_as_MySQLdb() 109 | # engine = create_engine("mysql://root:123456@localhost:3306/users?charset=utf8", pool_size=20, max_overflow=0) 110 | # 111 | # SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 112 | # db = SessionLocal() 113 | # obj_in = { 114 | # 'phone': 180114111066, 115 | # 'email': '1011@qq.com', 116 | # 'hashed_password': '1213456', 117 | # 'head_image_url': 'bc3b6db0-7e61-4505-ac2f-53e1e651467d1590629068.png' 118 | # } 119 | # CRUDUser(User).create(db, obj_in=obj_in) 120 | # save_key_db('f54ad132-5e27-447d-8018-0bea375496931591067315.png') 121 | 122 | # http://qayumwil2.bkt.clouddn.com/bc3b6db0-7e61-4505-ac2f-53e1e651467d1590629068.png 123 | pass 124 | -------------------------------------------------------------------------------- /app/utils/tencentcloud_sdk_python.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/26 17:09 5 | # @File : tencentcloud-sdk-python.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | 8 | from tencentcloud.common import credential 9 | from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException 10 | 11 | from app.logs import sys_log 12 | 13 | 14 | async def make_tencent_child_account(secret_id, secret_key): 15 | """ 16 | 创建腾讯云子账户 17 | 可以重复调用,不会再次注册,会返回已经注册的用户 18 | { 19 | "qcloudUin" 1003121321, 20 | "qcloudAppid" 132332323 21 | } 22 | """ 23 | # 导入对应产品模块的 client models。 24 | from tencentcloud.partners.v20180321 import partners_client, models 25 | 26 | try: 27 | cred = credential.Credential(secret_id, secret_key) 28 | client = partners_client.PartnersClient(cred, production) 29 | req = models.DescribeZonesRequest() 30 | resp = client.DescribeZones(req) 31 | 32 | json_data = resp.to_json_string() 33 | if json_data.get("code") == 0: 34 | return True, json_data.get("data") 35 | else: 36 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:ChannelRegisterUser,err={}" 37 | .format(json_data.get("message"))) 38 | return False, "" 39 | except TencentCloudSDKException as err: 40 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 41 | return False, "" 42 | 43 | 44 | async def login_tencent_cdn(secret_id, secret_key, tencent_user_id): 45 | """ 46 | 渠道侧获取用户登录控制台所需 token 47 | 注1:token有效期为5分钟。 48 | 注2:得到token之后,拼接url地址: 49 | https://cloud.tencent.com/login/channelAccessCallback?fromUin=1234567 50 | &token=Zx7Ut1m3181lCm26uko579OE24BTtY1d&signature=seJLwb6RxVj9q3OK8ezE9UoFqSQ%3D 51 | &redirect_uri=https%3A%2F%2Fconsol e.cloud.tencent.com%2F 52 | 用户跳转至该连接即可下发登录态并且跳转至指定url.其中参数:token 为上文接 53 | 口获得返回值, uin 为渠道侧主 uin,$key 为上文接口获得返回值 key 。签名方法为: 54 | php 55 | 签名代码: 56 | $signature = urlencode(base64_encode(hash_hmac('sha1', $token.$uin, $key, 57 | true))); 58 | 59 | { 60 | "token": xadadad, 61 | "key": xadadad, 62 | "expiredTime": 1506433269 63 | } 64 | 65 | """ 66 | 67 | try: 68 | cred = credential.Credential(secret_id, secret_key) 69 | client = cdn_client.CdnClient(cred, production) 70 | req = models.DescribeZonesRequest() 71 | resp = client.DescribeZones(req) 72 | 73 | json_data = resp.to_json_string() 74 | if json_data.get("code") == 0: 75 | return True, json_data.get("data") 76 | else: 77 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:ChannelGetLogin Token,err={}" 78 | .format(json_data.get("message"))) 79 | return False, "" 80 | except TencentCloudSDKException as err: 81 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 82 | return False, "" 83 | 84 | 85 | async def assume_role(secret_id, secret_key, tencent_user_id): 86 | """ 87 | 获取临时秘钥 88 | { 89 | "expiredTime": 1506433269, 90 | "expiration": "2017-09-26T13:41:09Z", 91 | "credentials": { 92 | "sessionToken": "sdadffwe2323er4323423", 93 | "tmpSecretId": "VpxrX0IMC pHXWL0Wr3KQNCqJix1uhMqD", 94 | "tmpSecretKey": "VpxrX0IMC pHXWL0Wr3KQNCqJix1uhMqD" 95 | } 96 | """ 97 | 98 | try: 99 | cred = credential.Credential(secret_id, secret_key) 100 | client = cdn_client.CdnClient(cred, production) 101 | req = models.DescribeZonesRequest() 102 | resp = client.DescribeZones(req) 103 | 104 | json_data = resp.to_json_string() 105 | if json_data.get("code") == 0: 106 | return True, json_data.get("data") 107 | else: 108 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:AssumeRole Token,err={}" 109 | .format(json_data.get("message"))) 110 | return False, "" 111 | except TencentCloudSDKException as err: 112 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 113 | return False, "" 114 | 115 | 116 | async def get_auth_status(secret_id, secret_key, token): 117 | """ 118 | 获取实名状态 119 | status 状态含义: 120 | 0 未实名 121 | 1 未实名,打款中(提交银行卡信息后即为此状态)(只有企业实名会有这 122 | 个状态,个人实名不会出现) 123 | 2 未实名,银行卡信息不正确(只有企业实名会有这个状态,个人实名不 124 | 会出现) 125 | 33 : 用户输入打款金额并且正确后,待确认。确认后即完成实名,变为状态 3 126 | (只有企业实名会有这个状态,个人实名不会出现 127 | 3 已经完成实名。此时 name 表示实名信息 128 | 129 | {"status": "3","name": "dad_dwd"} 130 | """ 131 | 132 | try: 133 | cred = credential.Credential(secret_id, secret_key) 134 | client = cdn_client.CdnClient(cred, production) 135 | req = models.DescribeZonesRequest() 136 | resp = client.DescribeZones(req) 137 | 138 | json_data = resp.to_json_string() 139 | if json_data.get("code") == 0: 140 | return True, json_data.get("data") 141 | else: 142 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:GetAuthStatus Token,err={}" 143 | .format(json_data.get("message"))) 144 | return False, "" 145 | except TencentCloudSDKException as err: 146 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 147 | return False, "" 148 | 149 | 150 | async def sumbit_bank_data(secret_id, secret_key, bank_data): 151 | """ 152 | 提交银行卡信息 153 | {} 154 | """ 155 | 156 | try: 157 | cred = credential.Credential(secret_id, secret_key) 158 | client = cdn_client.CdnClient(cred, production) 159 | req = models.DescribeZonesRequest() 160 | resp = client.DescribeZones(req) 161 | 162 | json_data = resp.to_json_string() 163 | if json_data.get("code") == 0: 164 | return True, json_data.get("data") 165 | else: 166 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:SubmitBankData Token,err={}" 167 | .format(json_data.get("message"))) 168 | return False, "" 169 | except TencentCloudSDKException as err: 170 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 171 | return False, "" 172 | 173 | 174 | async def submit_auth_code(secret_id, secret_key, pay_data): 175 | """ 176 | 提交打款信息 177 | {} 178 | """ 179 | 180 | try: 181 | cred = credential.Credential(secret_id, secret_key) 182 | client = cdn_client.CdnClient(cred, production) 183 | req = models.DescribeZonesRequest() 184 | resp = client.DescribeZones(req) 185 | 186 | json_data = resp.to_json_string() 187 | if json_data.get("code") == 0: 188 | return True, json_data.get("data") 189 | else: 190 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:SubmitAuthCode Token,err={}" 191 | .format(json_data.get("message"))) 192 | return False, "" 193 | except TencentCloudSDKException as err: 194 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 195 | return False, "" 196 | 197 | 198 | async def maker_sure_auth(secret_id, secret_key): 199 | """ 200 | 确认实名信息 201 | 实名认证步骤: 202 | 1.未提交实名信息时,用户 认证状态为 0 203 | 2.提交银行卡信息,此时用户实名认证状态为 1 204 | 3.用户收到打款后,提交打款正确信息,此时状态为 33 。如果用户信息错误,状态为 2 。 205 | 4.用户确认实名认证信息,调用确认接口。确认后用户实名状态为 3. 206 | 5.用户重新提交银行卡相关信息,此时用户实名状态又变成 1 。 207 | 6.每一个状态均可以通过查询接口查询获取。 208 | {} 209 | """ 210 | 211 | try: 212 | cred = credential.Credential(secret_id, secret_key) 213 | client = cdn_client.CdnClient(cred, production) 214 | req = models.DescribeZonesRequest() 215 | resp = client.DescribeZones(req) 216 | 217 | json_data = resp.to_json_string() 218 | if json_data.get("code") == 0: 219 | return True, json_data.get("data") 220 | else: 221 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:MakeSureAuth Token,err={}" 222 | .format(json_data.get("message"))) 223 | return False, "" 224 | except TencentCloudSDKException as err: 225 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 226 | return False, "" 227 | 228 | 229 | async def submit_auth_info(secret_id, secret_key): 230 | """ 231 | 个人实名认证 232 | 如果用户没有财付通绑过卡,会返回qid和qurl , 233 | 根据qurl生成二维码进行展示。用户会使用微信扫描二维码进行支付, 234 | 支付通过后会通过认证,此时调用实名状态查询接口会返回已经通过实名认证。 235 | 236 | { 237 | "qurl": "weixin://xxx/xx" # 如果曾经财付通绑过卡,会直接通过, data 为空 238 | "qid": "dadadasd" 239 | } 240 | """ 241 | 242 | try: 243 | cred = credential.Credential(secret_id, secret_key) 244 | client = cdn_client.CdnClient(cred, production) 245 | req = models.DescribeZonesRequest() 246 | resp = client.DescribeZones(req) 247 | 248 | json_data = resp.to_json_string() 249 | if json_data.get("code") == 0: 250 | return True, json_data.get("data") 251 | else: 252 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:SubmitAuthInfo Token,err={}" 253 | .format(json_data.get("message"))) 254 | return False, "" 255 | except TencentCloudSDKException as err: 256 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 257 | return False, "" 258 | 259 | 260 | async def user_resource(secret_id, secret_key, tencent_account_id): 261 | """ 262 | 个人名下购买的资源 263 | 264 | [ 265 | { 266 | "id": "资源id", 267 | "type": "资源类型", 268 | "username": "用户名", 269 | "exp_time": "到期时间", 270 | "status": "状态" 271 | } 272 | ] 273 | """ 274 | 275 | try: 276 | cred = credential.Credential(secret_id, secret_key) 277 | client = cdn_client.CdnClient(cred, production) 278 | req = models.DescribeZonesRequest() 279 | resp = client.DescribeZones(req) 280 | 281 | json_data = resp.to_json_string() 282 | if json_data.get("code") == 0: 283 | return True, json_data.get("data") 284 | else: 285 | sys_log.error(msg="腾讯云API内部处理错误,请联系腾讯工程师,api:SubmitAuthInfo Token,err={}" 286 | .format(json_data.get("message"))) 287 | return False, "" 288 | except TencentCloudSDKException as err: 289 | sys_log.error(msg="调用腾讯云CDN业务API失败,err={}".format(err)) 290 | return False, "" 291 | 292 | 293 | if __name__ == "__main__": 294 | pass 295 | """ 296 | { 297 | "TotalCount": 7, 298 | "ZoneSet": [ 299 | { 300 | "Zone": "ap-shanghai-1", 301 | "ZoneName": "上海一区", 302 | "ZoneId": "200001", 303 | "ZoneState": "UNAVAILABLE" 304 | }, 305 | { 306 | "Zone": "ap-shanghai-2", 307 | "ZoneName": "上海二区", 308 | "ZoneId": "200002", 309 | "ZoneState": "AVAILABLE" 310 | }, 311 | { 312 | "Zone": "ap-shanghai-3", 313 | "ZoneName": "上海三区", 314 | "ZoneId": "200003", 315 | "ZoneState": "AVAILABLE" 316 | }, 317 | { 318 | "Zone": "ap-shanghai-4", 319 | "ZoneName": "上海四区", 320 | "ZoneId": "200004", 321 | "ZoneState": "AVAILABLE" 322 | }, 323 | { 324 | "Zone": "ap-shanghai-5", 325 | "ZoneName": "上海五区", 326 | "ZoneId": "200005", 327 | "ZoneState": "UNAVAILABLE" 328 | }, 329 | { 330 | "Zone": "ap-shanghai-6", 331 | "ZoneName": "上海六区", 332 | "ZoneId": "200006", 333 | "ZoneState": "UNAVAILABLE" 334 | }, 335 | { 336 | "Zone": "ap-shanghai-7", 337 | "ZoneName": "上海七区", 338 | "ZoneId": "200007", 339 | "ZoneState": "UNAVAILABLE" 340 | } 341 | ], 342 | "RequestId": "ddd01d85-b23b-4d75-be96-ad07e32fdfc0" 343 | } 344 | 345 | """ 346 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/27 10:19 5 | # @File : config.py 6 | # @Software: 这是运行在一台超级计算机上的很牛逼的Python代码 7 | # import secrets 8 | # secrets.token_urlsafe(128) 9 | import io 10 | import logging 11 | import os 12 | from contextlib import contextmanager 13 | from functools import lru_cache 14 | from io import StringIO 15 | from typing import Optional 16 | 17 | from dotenv.main import DotEnv 18 | from pydantic import BaseSettings, Field 19 | 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | def my_get_stream(self): 25 | """重写python-dotenv读取文件的方法,使用utf-8,支持读取包含中文的.env配置文件""" 26 | if isinstance(self.dotenv_path, StringIO): 27 | yield self.dotenv_path 28 | elif os.path.isfile(self.dotenv_path): 29 | with io.open(self.dotenv_path, encoding='utf-8') as stream: 30 | yield stream 31 | else: 32 | if self.verbose: 33 | logger.warning("File doesn't exist %s", self.dotenv_path) 34 | yield StringIO('') 35 | 36 | 37 | DotEnv._get_stream = contextmanager(my_get_stream) 38 | 39 | 40 | class Settings(BaseSettings): 41 | """System configurations.""" 42 | 43 | # 系统环境 44 | ENVIRONMENT: Optional[str] = Field(None, env="ENVIRONMENT") 45 | 46 | # 系统安全秘钥 47 | SECRET_KEY = 'ZEuk2U9svM2WRJql4Fs2lEvD05ZDQXZdKboim__SQqsUUqJwStZJq6u0e30bIL4Qe80PB48X1dcIZHjxqLzUiA' 48 | 49 | # API版本号 50 | API_V1_STR = "/api/v1" 51 | 52 | # token过期时间 53 | ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 8 54 | 55 | # 算法 56 | ALGORITHM = "HS256" 57 | 58 | # 产品名称 59 | CDN_PRODUCTION_NAME = { 60 | "cdn": "cdn" 61 | } 62 | 63 | # 加载.env文件的配置 64 | class Config: 65 | env_file = ".env" 66 | case_sensitive = True 67 | 68 | 69 | class DevConfig(Settings): 70 | """Development configurations.""" 71 | 72 | # redis 73 | REDIS_HOST: Optional[str] = Field(None, env="DEV_REDIS_HOST") 74 | REDIS_PORT: Optional[int] = Field(None, env="DEV_REDIS_PORT") 75 | REDIS_USERNAME: Optional[str] = Field(None, env="DEV_REDIS_USERNAME") 76 | REDIS_PASSWORD: Optional[str] = Field(None, env="DEV_REDIS_PASSWORD") 77 | REDIS_DB: Optional[int] = Field(None, env="DEV_REDIS_DB") 78 | 79 | # 七牛玉 80 | QINIU_ACCESS_KEY: Optional[str] = Field(None, env="DEV_QINIU_ACCESS_KEY") 81 | QINIU_SECRET_KEY: Optional[str] = Field(None, env="DEV_QINIU_SECRET_KEY") 82 | QINIU_BUCKET_NAME: Optional[str] = Field(None, env="DEV_QINIU_BUCKET_NAME") 83 | QINIU_DOMAIN_NAME: Optional[str] = Field(None, env="DEV_QINIU_DOMAIN_NAME") 84 | 85 | # Tencent 86 | TENCENT_SECRET_KEY: Optional[str] = Field(None, env="DEV_TENCENT_SECRET_KEY") 87 | TENCENT_ACCESS_ID: Optional[str] = Field(None, env="DEV_TENCENT_ACCESS_ID") 88 | 89 | # Mysql 90 | MYSQL_SERVER: Optional[str] = Field(None, env="DEV_MYSQL_SERVER") 91 | MYSQL_USER: Optional[str] = Field(None, env="DEV_MYSQL_USER") 92 | MYSQL_PASSWORD: Optional[str] = Field(None, env="DEV_MYSQL_PASSWORD") 93 | MYSQL_DB_NAME: Optional[str] = Field(None, env="DEV_MYSQL_DB_NAME") 94 | MYSQL_PORT: Optional[int] = Field(None, env="DEV_MYSQL_PORT") 95 | 96 | 97 | class TestConfig(Settings): 98 | """Production configurations.""" 99 | 100 | REDIS_HOST: Optional[str] = Field(None, env="TEST_REDIS_HOST") 101 | REDIS_PORT: Optional[int] = Field(None, env="TEST_REDIS_PORT") 102 | REDIS_USERNAME: Optional[str] = Field(None, env="TEST_REDIS_USERNAME") 103 | REDIS_PASSWORD: Optional[str] = Field(None, env="TEST_REDIS_PASSWORD") 104 | REDIS_DB: Optional[int] = Field(None, env="TEST_REDIS_DB") 105 | 106 | QINIU_ACCESS_KEY: Optional[str] = Field(None, env="TEST_QINIU_ACCESS_KEY") 107 | QINIU_SECRET_KEY: Optional[str] = Field(None, env="TEST_QINIU_SECRET_KEY") 108 | QINIU_BUCKET_NAME: Optional[str] = Field(None, env="TEST_QINIU_BUCKET_NAME") 109 | QINIU_DOMAIN_NAME: Optional[str] = Field(None, env="TEST_QINIU_DOMAIN_NAME") 110 | 111 | TENCENT_SECRET_KEY: Optional[str] = Field(None, env="TEST_TENCENT_SECRET_KEY") 112 | TENCENT_ACCESS_ID: Optional[str] = Field(None, env="TEST_TENCENT_ACCESS_ID") 113 | 114 | MYSQL_SERVER: Optional[str] = Field(None, env="TEST_MYSQL_SERVER") 115 | MYSQL_USER: Optional[str] = Field(None, env="TEST_MYSQL_USER") 116 | MYSQL_PASSWORD: Optional[str] = Field(None, env="TEST_MYSQL_PASSWORD") 117 | MYSQL_DB_NAME: Optional[str] = Field(None, env="TEST_MYSQL_DB_NAME") 118 | MYSQL_PORT: Optional[int] = Field(None, env="TEST_MYSQL_PORT") 119 | 120 | 121 | class ProdConfig(Settings): 122 | """Production configurations.""" 123 | 124 | REDIS_HOST: Optional[str] = Field(None, env="PROD_REDIS_HOST") 125 | REDIS_PORT: Optional[int] = Field(None, env="PROD_REDIS_PORT") 126 | REDIS_USERNAME: Optional[str] = Field(None, env="PROD_REDIS_USERNAME") 127 | REDIS_PASSWORD: Optional[str] = Field(None, env="PROD_REDIS_PASSWORD") 128 | REDIS_DB: Optional[int] = Field(None, env="PROD_REDIS_DB") 129 | 130 | QINIU_ACCESS_KEY: Optional[str] = Field(None, env="PROD_QINIU_ACCESS_KEY") 131 | QINIU_SECRET_KEY: Optional[str] = Field(None, env="PROD_QINIU_SECRET_KEY") 132 | QINIU_BUCKET_NAME: Optional[str] = Field(None, env="PROD_QINIU_BUCKET_NAME") 133 | QINIU_DOMAIN_NAME: Optional[str] = Field(None, env="PROD_QINIU_DOMAIN_NAME") 134 | 135 | TENCENT_SECRET_KEY: Optional[str] = Field(None, env="PROD_TENCENT_SECRET_KEY") 136 | TENCENT_ACCESS_ID: Optional[str] = Field(None, env="PROD_TENCENT_ACCESS_ID") 137 | 138 | MYSQL_SERVER: Optional[str] = Field(None, env="PROD_MYSQL_SERVER") 139 | MYSQL_USER: Optional[str] = Field(None, env="PROD_MYSQL_USER") 140 | MYSQL_PASSWORD: Optional[str] = Field(None, env="PROD_MYSQL_PASSWORD") 141 | MYSQL_DB_NAME: Optional[str] = Field(None, env="PROD_MYSQL_DB_NAME") 142 | MYSQL_PORT: Optional[int] = Field(None, env="PROD_MYSQL_PORT") 143 | 144 | 145 | class FactoryConfig: 146 | """Returns a config instance dependending on the ENV_STATE variable.""" 147 | 148 | def __init__(self, env_state: Optional[str]): 149 | self.env_state = env_state 150 | 151 | def __call__(self): 152 | 153 | if self.env_state == "development": 154 | return DevConfig() 155 | 156 | elif self.env_state == "production": 157 | return ProdConfig() 158 | 159 | elif self.env_state == "testing": 160 | return TestConfig() 161 | 162 | 163 | @lru_cache() 164 | def get_configs(): 165 | """加载一下环境文件""" 166 | from dotenv import load_dotenv 167 | load_dotenv(encoding='utf-8') 168 | return FactoryConfig(Settings().ENVIRONMENT)() 169 | 170 | 171 | configs = get_configs() 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /create_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `aerich` ( 2 | `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 | `version` VARCHAR(50) NOT NULL, 4 | `app` VARCHAR(20) NOT NULL 5 | ) CHARACTER SET utf8; 6 | CREATE TABLE IF NOT EXISTS `client_users` ( 7 | `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 8 | `created_at` DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6), 9 | `modified_at` DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), 10 | `is_del` INT NOT NULL DEFAULT 0, 11 | `auth_user_id` VARCHAR(255) NOT NULL UNIQUE, 12 | `username` VARCHAR(100), 13 | `mobile` VARCHAR(100), 14 | `real_name` VARCHAR(100), 15 | `email` VARCHAR(100), 16 | `avatar` VARCHAR(100) DEFAULT '98d2d0f6-6851-49a8-8d31-8603204cc7311591066453.png', 17 | `status` INT 18 | ) CHARACTER SET utf8 COMMENT='save ik user info'; 19 | CREATE TABLE IF NOT EXISTS `tencent_users` ( 20 | `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 21 | `created_at` DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6), 22 | `modified_at` DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), 23 | `is_del` INT NOT NULL DEFAULT 0, 24 | `auth_user_id` VARCHAR(255) NOT NULL UNIQUE, 25 | `tencent_user_id` VARCHAR(100) UNIQUE, 26 | `tencent_auth_status` INT 27 | ) CHARACTER SET utf8 COMMENT='save ik and tencent user info'; 28 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build -t cdn_user_image ./ 4 | 5 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | ikddoscnfhk: 4 | build: . 5 | container_name: "IK_CDN_USERS" 6 | ports: 7 | - "22003:2003" 8 | working_dir: "/var/www/ik_cdn_users" 9 | restart: always -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | from app import create_app 4 | 5 | 6 | app = create_app() 7 | 8 | 9 | if __name__ == '__main__': 10 | uvicorn.run( 11 | app='main:app', 12 | host='0.0.0.0', 13 | port=8001, 14 | debug=False, 15 | reload=True, 16 | ) 17 | 18 | # import typer 19 | # 20 | # app = typer.Typer() 21 | # 22 | # 23 | # @app.command() 24 | # def hello(name: str): 25 | # typer.echo(f"Hello {name}") 26 | # 27 | # 28 | # @app.command() 29 | # def goodbye(name: str, formal: bool = False): 30 | # if formal: 31 | # typer.echo(f"Goodbye Ms. {name}. Have a good day.") 32 | # else: 33 | # typer.echo(f"Bye {name}!") 34 | 35 | 36 | -------------------------------------------------------------------------------- /migrations/models/0_202029062420142956_init.json: -------------------------------------------------------------------------------- 1 | { 2 | "upgrade": [ 3 | "CREATE TABLE IF NOT EXISTS `aerich` (\n `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n `version` VARCHAR(50) NOT NULL,\n `app` VARCHAR(20) NOT NULL\n) CHARACTER SET utf8;\nCREATE TABLE IF NOT EXISTS `client_users` (\n `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n `created_at` DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),\n `modified_at` DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),\n `is_del` INT NOT NULL DEFAULT 0,\n `auth_user_id` VARCHAR(255) NOT NULL UNIQUE,\n `username` VARCHAR(100),\n `username2` VARCHAR(100),\n `mobile` VARCHAR(100),\n `real_name` VARCHAR(100),\n `email` VARCHAR(100),\n `avatar` VARCHAR(100) DEFAULT '98d2d0f6-6851-49a8-8d31-8603204cc7311591066453.png',\n `status` INT,\n `tencent_user_id` VARCHAR(100) UNIQUE,\n `tencent_auth_status` INT\n) CHARACTER SET utf8 COMMENT='save ik user info';" 4 | ] 5 | } -------------------------------------------------------------------------------- /migrations/models/1_202031062420143154_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "upgrade": [ 3 | "ALTER TABLE `client_users` DROP COLUMN `username2`" 4 | ], 5 | "downgrade": [ 6 | "ALTER TABLE `client_users` ADD `username2` VARCHAR(100)" 7 | ] 8 | } -------------------------------------------------------------------------------- /migrations/models/old_models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # @Author : Senzhong 4 | # @Time : 2020/5/28 10:28 5 | # @File : model.py 6 | 7 | from tortoise import fields 8 | from tortoise.contrib.pydantic import pydantic_model_creator 9 | from tortoise.models import Model 10 | 11 | 12 | class MyAbstractBaseModel(Model): 13 | 14 | id = fields.IntField(pk=True) 15 | created_at = fields.DatetimeField(null=True, auto_now_add=True) 16 | modified_at = fields.DatetimeField(null=True, auto_now=True) 17 | is_del = fields.IntField(null=False, default="0") 18 | 19 | class Meta: 20 | abstract = True 21 | 22 | 23 | class ClientUser(MyAbstractBaseModel): 24 | 25 | auth_user_id = fields.CharField(255, null=False, unique=True) 26 | username = fields.CharField(100, null=True) 27 | mobile = fields.CharField(100, null=True) 28 | real_name = fields.CharField(100, null=True) 29 | email = fields.CharField(100, null=True) 30 | avatar = fields.CharField(100, null=True, default='98d2d0f6-6851-49a8-8d31-8603204cc7311591066453.png') 31 | status = fields.IntField(null=True) 32 | tencent_user_id = fields.CharField(100, null=True, unique=True) 33 | tencent_auth_status = fields.IntField(null=True) 34 | 35 | class Meta: 36 | table = "client_users" 37 | table_description = "save ik user info" 38 | ordering = ["-created_at", "id"] 39 | 40 | class PydanticMeta: 41 | exclude = ["created_at", "modified_at", "id"] 42 | 43 | def __str__(self): 44 | return self.auth_user_id if self.auth_user_id else "ClientUserModel" 45 | 46 | 47 | # class TencentUser(MyAbstractBaseModel): 48 | # auth_user_id = fields.CharField(255, null=False, unique=True) 49 | # tencent_user_id = fields.CharField(100, null=True, unique=True) 50 | # tencent_auth_status = fields.IntField(null=True) 51 | # 52 | # class Meta: 53 | # table = "tencent_users" 54 | # table_description = "save ik and tencent user info" 55 | # ordering = ["-created_at", "id"] 56 | # 57 | # class PydanticMeta: 58 | # exclude = ["created_at", "modified_at", "id"] 59 | # 60 | # def __str__(self): 61 | # return self.auth_user_id if self.auth_user_id else "UserTencentModel" 62 | 63 | 64 | ClientUserSchemaModel = pydantic_model_creator(ClientUser, name="ClientUser") 65 | ClientUserInSchemaModel = pydantic_model_creator(ClientUser, name="ClientUserIn", exclude_readonly=True) 66 | 67 | # TencentUserSchemaModel = pydantic_model_creator(TencentUser, name="TencentUser") 68 | # TencentUserInSchemaModel = pydantic_model_creator(TencentUser, name="TencentUserIn", exclude_readonly=True) 69 | -------------------------------------------------------------------------------- /pip.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | index-url = http://pypi.douban.com/simple/ 3 | extra-index-url = https://pypi.tuna.tsinghua.edu.cn/simple/ 4 | trusted-host= 5 | pypi.tuna.tsinghua.edu.cn 6 | pypi.douban.com 7 | -------------------------------------------------------------------------------- /prestart.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/bash 2 | # 文件路径:/app/prestart.sh  3 | echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:" 4 | 5 | echo " 6 | #! /usr/bin/env bash 7 | # Let the DB start 8 | sleep 10; 9 | # Run migrations 10 | aerich upgrade 11 | " 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aerich==0.1.9 2 | aiofiles==0.5.0 3 | aiomysql==0.0.20 4 | aiosqlite==0.13.0 5 | alembic==1.4.2 6 | anyio==1.3.0 7 | async-generator==1.10 8 | asyncclick==7.0.9 9 | bcrypt==3.1.7 10 | certifi==2020.4.5.1 11 | cffi==1.14.0 12 | chardet==3.0.4 13 | click==7.1.2 14 | colorama==0.4.3 15 | cryptography==3.2 16 | databases==0.3.2 17 | dnspython==1.16.0 18 | email-validator==1.1.1 19 | Faker==4.1.0 20 | fastapi==0.55.1 21 | h11==0.9.0 22 | idna==2.9 23 | iso8601==0.1.12 24 | jose==1.0.0 25 | Mako==1.1.3 26 | MarkupSafe==1.1.1 27 | mysql-connector-python==8.0.20 28 | passlib==1.7.2 29 | protobuf==3.12.2 30 | pycparser==2.20 31 | pydantic==1.5.1 32 | PyJWT==1.7.1 33 | PyMySQL==0.9.2 34 | PyPika==0.37.6 35 | python-dateutil==2.8.1 36 | python-dotenv==0.13.0 37 | python-editor==1.0.4 38 | python-multipart==0.0.5 39 | qiniu==7.2.8 40 | redis==3.5.3 41 | requests==2.23.0 42 | six==1.15.0 43 | sniffio==1.1.0 44 | SQLAlchemy==1.3.17 45 | starlette==0.13.2 46 | tencentcloud-sdk-python==3.0.182 47 | text-unidecode==1.3 48 | tortoise-orm==0.16.12 49 | typer==0.2.1 50 | typing-extensions==3.7.4.2 51 | unicorn==1.0.1 52 | urllib3==1.25.9 53 | uvicorn==0.11.5 54 | websockets==8.1 55 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | 2 | docker run -d --name ik_cdn_user -v /root/cdn:/app --privileged=true -p 8001:80 cdn_user_image 3 | 4 | --------------------------------------------------------------------------------