lesscode-flask 0.0.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lesscode_flask/__init__.py +1 -0
- lesscode_flask/app.py +156 -0
- lesscode_flask/db/__init__.py +60 -0
- lesscode_flask/db/datasource.py +27 -0
- lesscode_flask/db/executor.py +128 -0
- lesscode_flask/log/access_log_handler.py +62 -0
- lesscode_flask/model/access_log.py +26 -0
- lesscode_flask/model/auth_client.py +42 -0
- lesscode_flask/model/auth_permission.py +25 -0
- lesscode_flask/model/base_model.py +38 -0
- lesscode_flask/model/parameterized_query.py +210 -0
- lesscode_flask/model/response_result.py +60 -0
- lesscode_flask/model/user.py +118 -0
- lesscode_flask/service/access_log_service.py +8 -0
- lesscode_flask/service/auth_client_service.py +7 -0
- lesscode_flask/service/auth_permission_service.py +7 -0
- lesscode_flask/service/authentication_service.py +67 -0
- lesscode_flask/service/base_service.py +138 -0
- lesscode_flask/setting/__init__.py +122 -0
- lesscode_flask/setup/__init__.py +185 -0
- lesscode_flask/utils/__init__.py +1 -0
- lesscode_flask/utils/decorator/__init__.py +0 -0
- lesscode_flask/utils/decorator/cache.py +126 -0
- lesscode_flask/utils/decorator/swagger.py +19 -0
- lesscode_flask/utils/file/file_exporter.py +98 -0
- lesscode_flask/utils/helpers.py +139 -0
- lesscode_flask/utils/json/NotSortJSONProvider.py +9 -0
- lesscode_flask/utils/oss/__init__.py +0 -0
- lesscode_flask/utils/oss/ks3_oss.py +203 -0
- lesscode_flask/utils/redis/redis_helper.py +117 -0
- lesscode_flask/utils/request/request.py +96 -0
- lesscode_flask/utils/swagger/swagger_template.py +82 -0
- lesscode_flask/utils/swagger/swagger_util.py +172 -0
- lesscode_flask/wsgi.py +37 -0
- lesscode_flask-0.0.27.dist-info/METADATA +127 -0
- lesscode_flask-0.0.27.dist-info/RECORD +46 -0
- lesscode_flask-0.0.27.dist-info/WHEEL +5 -0
- lesscode_flask-0.0.27.dist-info/top_level.txt +2 -0
- redash/query_runner/__init__.py +523 -0
- redash/query_runner/clickhouse.py +230 -0
- redash/query_runner/kingbase.py +228 -0
- redash/query_runner/mysql.py +309 -0
- redash/query_runner/pg.py +284 -0
- redash/settings/__init__.py +90 -0
- redash/settings/helpers.py +66 -0
- redash/utils/requests_session.py +18 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from flask import current_app
|
|
5
|
+
|
|
6
|
+
from lesscode_flask.utils.helpers import get_start_port
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseConfig:
|
|
10
|
+
# 应用名称
|
|
11
|
+
APPLICATION_NAME: str = ""
|
|
12
|
+
# 应用id
|
|
13
|
+
CLIENT_ID: str = ""
|
|
14
|
+
# 项目名称
|
|
15
|
+
PROJECT_NAME: str = ""
|
|
16
|
+
# 统一路由前缀
|
|
17
|
+
ROUTE_PREFIX = ""
|
|
18
|
+
# 项目端口号
|
|
19
|
+
PORT = 5002
|
|
20
|
+
|
|
21
|
+
# 数据源
|
|
22
|
+
DATA_SOURCE = []
|
|
23
|
+
# SQLALCHEMY数据库连接
|
|
24
|
+
SQLALCHEMY_BINDS = {
|
|
25
|
+
# 'users': 'mysqldb://localhost/users',
|
|
26
|
+
# 'appmeta': 'sqlite:////path/to/appmeta.db'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# 日志级别
|
|
30
|
+
LESSCODE_LOG_LEVEL = os.environ.get("LESSCODE_LOG_LEVEL", "INFO")
|
|
31
|
+
# 日志格式
|
|
32
|
+
LESSCODE_LOG_FORMAT = os.environ.get("LESSCODE_LOG_FORMAT",
|
|
33
|
+
'[%(asctime)s] [%(levelname)s] [%(name)s:%(module)s:%(lineno)d] [%(message)s]')
|
|
34
|
+
# 输出管道
|
|
35
|
+
LESSCODE_LOG_STDOUT = os.environ.get("LESSCODE_LOG_STDOUT", True)
|
|
36
|
+
# 日志文件备份数量
|
|
37
|
+
LESSCODE_LOG_FILE_BACKUPCOUNT = os.environ.get("LESSCODE_LOG_FILE_BACKUPCOUNT", 7)
|
|
38
|
+
# 日志文件分割周期
|
|
39
|
+
LESSCODE_LOG_LOG_FILE_WHEN = os.environ.get("LESSCODE_LOG_LOG_FILE_WHEN", "D")
|
|
40
|
+
# 日志文件存储路径
|
|
41
|
+
LESSCODE_LOG_FILE_PATH = os.environ.get("LESSCODE_LOG_FILE_PATH", 'logs/lesscode.log')
|
|
42
|
+
# 访问日志是否DB存储
|
|
43
|
+
LESSCODE_ACCESS_LOG_DB = os.environ.get("LESSCODE_ACCESS_LOG_DB", 0)
|
|
44
|
+
# 未配置权限的资源 默认权限 1:需要登录 0:游客'
|
|
45
|
+
AUTH_DEFAULT_ACCESS = 0
|
|
46
|
+
|
|
47
|
+
# 外网地址
|
|
48
|
+
OUTSIDE_SCREEN_IP: str = "http://127.0.0.1:{}".format(get_start_port())
|
|
49
|
+
SWAGGER_URL = '{}/swagger-ui'.format(ROUTE_PREFIX)
|
|
50
|
+
SWAGGER_API_URL = '{}/swagger'.format(ROUTE_PREFIX)
|
|
51
|
+
NOT_RESPONSE_RESULT = [SWAGGER_URL, SWAGGER_API_URL]
|
|
52
|
+
# # 项目端口号
|
|
53
|
+
# PORT: int = 8080
|
|
54
|
+
#
|
|
55
|
+
# 应用运行根路径
|
|
56
|
+
# APPLICATION_PATH: str = f"{os.path.abspath(os.path.dirname(sys.argv[0]))}"
|
|
57
|
+
# # 静态资源目录
|
|
58
|
+
STATIC_PATH: str = f"{os.path.abspath(os.path.dirname(sys.argv[0]))}"
|
|
59
|
+
#
|
|
60
|
+
# # 是否启动资源注册
|
|
61
|
+
# RMS_REGISTER_ENABLE: bool = False
|
|
62
|
+
# # 注册地址
|
|
63
|
+
# RMS_REGISTER_SERVER: str = "http://127.0.0.1:8918"
|
|
64
|
+
#
|
|
65
|
+
|
|
66
|
+
# redis缓存开关
|
|
67
|
+
CACHE_ENABLE: bool = False
|
|
68
|
+
REDIS_CACHE_KEY = "redis"
|
|
69
|
+
REDIS_OAUTH_KEY = None
|
|
70
|
+
#
|
|
71
|
+
# # 外网地址
|
|
72
|
+
# OUTSIDE_SCREEN_IP: str = ""
|
|
73
|
+
# # 内网ip
|
|
74
|
+
# INSTANCE_IP: str = ""
|
|
75
|
+
#
|
|
76
|
+
# 数据服务
|
|
77
|
+
CAPABILITY_PLATFORM_SERVER: str = "http://127.0.0.1:8976"
|
|
78
|
+
# # 权限服务地址
|
|
79
|
+
# OAUTH_SERVER: str = ""
|
|
80
|
+
# # 后端管理地址
|
|
81
|
+
# UPMS_SERVER: str = ""
|
|
82
|
+
# # 报告服务地址
|
|
83
|
+
# REPORT_SERVER: str = ""
|
|
84
|
+
#
|
|
85
|
+
# # aes加密key
|
|
86
|
+
# AES_KEY: str = 'haohaoxuexi'
|
|
87
|
+
# ks3连接配置
|
|
88
|
+
# host ks3的地址; access_key_id ks3的key; access_key_secret ks3的密钥 is_secure 是否使用https协议
|
|
89
|
+
KS3_CONNECT_CONFIG: dict = {"bucket_name": "", "host": "", "access_key_id": "", "access_key_secret": "",
|
|
90
|
+
"is_secure": False}
|
|
91
|
+
# request请求的参数
|
|
92
|
+
CONNECT_CONFIG: dict = {
|
|
93
|
+
# "pool_connections": 10,
|
|
94
|
+
# "pool_maxsize": 100,
|
|
95
|
+
# "max_retries": 1,
|
|
96
|
+
# "pool_block": False
|
|
97
|
+
}
|
|
98
|
+
#
|
|
99
|
+
# # mysql ip地址
|
|
100
|
+
# MYSQL_IP: str = ""
|
|
101
|
+
# # mysql 端口号
|
|
102
|
+
# MYSQL_PORT: str = ""
|
|
103
|
+
# # mysql 用户名
|
|
104
|
+
# MYSQL_USERNAME: str = ""
|
|
105
|
+
# # mysql 用户密码
|
|
106
|
+
# MYSQL_PASSWORD: str = ""
|
|
107
|
+
#
|
|
108
|
+
# # clickhouse ip地址
|
|
109
|
+
# CK_IP: str = ""
|
|
110
|
+
# # clickhouse 端口号
|
|
111
|
+
# CK_PORT: str = ""
|
|
112
|
+
# # clickhouse 用户名
|
|
113
|
+
# CK_USERNAME: str = ""
|
|
114
|
+
# # clickhouse 密码
|
|
115
|
+
# CK_PASSWORD: str = ""
|
|
116
|
+
|
|
117
|
+
# swagger 的名称
|
|
118
|
+
SWAGGER_NAME = "API"
|
|
119
|
+
# swagger 的版本
|
|
120
|
+
SWAGGER_VERSION = "1.0.0"
|
|
121
|
+
# swagger 的描述
|
|
122
|
+
SWAGGER_DESCRIPTION = "项目接口说明文档"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
3
|
+
|
|
4
|
+
import redis
|
|
5
|
+
from flask import current_app
|
|
6
|
+
from flask_login import LoginManager
|
|
7
|
+
from flask_swagger_ui import get_swaggerui_blueprint
|
|
8
|
+
|
|
9
|
+
from lesscode_flask.db import db
|
|
10
|
+
from lesscode_flask.log.access_log_handler import AccessLogHandler
|
|
11
|
+
from lesscode_flask.model.user import AnonymousUser, User
|
|
12
|
+
from lesscode_flask.service.authentication_service import get_token_user, get_api_user
|
|
13
|
+
from lesscode_flask.utils.swagger.swagger_util import generate_openapi_spec
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def setup_logging(app):
|
|
17
|
+
"""
|
|
18
|
+
初始化日志配置
|
|
19
|
+
1. 日志等级
|
|
20
|
+
DEBUG : 10
|
|
21
|
+
INFO:20
|
|
22
|
+
WARN:30
|
|
23
|
+
ERROR:40
|
|
24
|
+
CRITICAL:50
|
|
25
|
+
:return:
|
|
26
|
+
"""
|
|
27
|
+
import logging
|
|
28
|
+
import sys
|
|
29
|
+
# 日志配置
|
|
30
|
+
# 日志级别
|
|
31
|
+
LOG_LEVEL = app.config.get("LESSCODE_LOG_LEVEL", "DEBUG")
|
|
32
|
+
# 日志格式
|
|
33
|
+
LOG_FORMAT = app.config.get("LESSCODE_LOG_FORMAT",
|
|
34
|
+
'[%(asctime)s] [%(levelname)s] [%(name)s:%(module)s:%(lineno)d] [%(message)s]')
|
|
35
|
+
# 输出管道
|
|
36
|
+
LOG_STDOUT = app.config.get("LESSCODE_LOG_STDOUT", True)
|
|
37
|
+
# 日志文件备份数量
|
|
38
|
+
LOG_FILE_BACKUPCOUNT = app.config.get("LESSCODE_LOG_FILE_BACKUPCOUNT", 7)
|
|
39
|
+
# 日志文件分割周期
|
|
40
|
+
LOG_FILE_WHEN = app.config.get("LESSCODE_LOG_LOG_FILE_WHEN", "D")
|
|
41
|
+
# 日志文件存储路径
|
|
42
|
+
LOG_FILE_PATH = app.config.get("LESSCODE_LOG_FILE_PATH", 'logs/lesscode.log')
|
|
43
|
+
formatter = logging.Formatter(LOG_FORMAT, datefmt='%Y-%m-%d %H:%M:%S')
|
|
44
|
+
logging.getLogger().setLevel(LOG_LEVEL.upper())
|
|
45
|
+
# 控制台输出
|
|
46
|
+
console_handler = logging.StreamHandler(sys.stdout if LOG_STDOUT else sys.stderr)
|
|
47
|
+
console_handler.setFormatter(formatter)
|
|
48
|
+
file_handler = logging.handlers.TimedRotatingFileHandler(LOG_FILE_PATH, when=LOG_FILE_WHEN,
|
|
49
|
+
backupCount=LOG_FILE_BACKUPCOUNT)
|
|
50
|
+
|
|
51
|
+
file_handler.setFormatter(formatter)
|
|
52
|
+
logging.getLogger().addHandler(console_handler)
|
|
53
|
+
logging.getLogger().addHandler(file_handler)
|
|
54
|
+
logging.addLevelName(100, 'ACCESS')
|
|
55
|
+
|
|
56
|
+
LESSCODE_ACCESS_LOG_DB = app.config.get("LESSCODE_ACCESS_LOG_DB", 0)
|
|
57
|
+
if LESSCODE_ACCESS_LOG_DB == 1:
|
|
58
|
+
access_log_handler = AccessLogHandler()
|
|
59
|
+
access_log_handler.level = 100
|
|
60
|
+
logging.getLogger().addHandler(access_log_handler)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def setup_blueprint(app, path=None, pkg_name="handlers"):
|
|
64
|
+
import os
|
|
65
|
+
from flask import Blueprint
|
|
66
|
+
import inspect
|
|
67
|
+
"""
|
|
68
|
+
动态注册Handler模块
|
|
69
|
+
遍历项目指定包内的Handler,将包内module引入。
|
|
70
|
+
:param path: 项目内Handler的文件路径
|
|
71
|
+
:param pkg_name: 引入模块前缀
|
|
72
|
+
"""
|
|
73
|
+
if path is None:
|
|
74
|
+
# 项目内Handler的文件路径,使用当前工作目录作为根
|
|
75
|
+
path = os.path.join(os.getcwd(), pkg_name)
|
|
76
|
+
# 首先获取当前目录所有文件及文件夹
|
|
77
|
+
dynamic_handler_names = os.listdir(path)
|
|
78
|
+
for handler_name in dynamic_handler_names:
|
|
79
|
+
# 利用os.path.join()方法获取完整路径
|
|
80
|
+
full_file = os.path.join(path, handler_name)
|
|
81
|
+
# 循环判断每个元素是文件夹还是文件
|
|
82
|
+
if os.path.isdir(full_file) and handler_name != "__pycache__":
|
|
83
|
+
# 文件夹递归遍历
|
|
84
|
+
setup_blueprint(app, os.path.join(path, handler_name), ".".join([pkg_name, handler_name]))
|
|
85
|
+
elif os.path.isfile(full_file) and handler_name.lower().endswith("handler.py"):
|
|
86
|
+
# 文件,并且为handler结尾,认为是请求处理器,完成动态装载
|
|
87
|
+
module_path = "{}.{}".format(pkg_name, handler_name.replace(".py", ""))
|
|
88
|
+
module = importlib.import_module(module_path) # __import__(module_path)
|
|
89
|
+
for name, obj in inspect.getmembers(module):
|
|
90
|
+
# 找到Blueprint 的属性进行注册
|
|
91
|
+
if isinstance(obj, Blueprint):
|
|
92
|
+
# 如果有配置统一前缀则作为蓝图路径的统一前缀
|
|
93
|
+
if hasattr(obj, "url_prefix") and app.config.get("ROUTE_PREFIX", ""):
|
|
94
|
+
obj.url_prefix = f'{app.config.get("ROUTE_PREFIX")}{obj.url_prefix}'
|
|
95
|
+
# 加载完成后 注册蓝图到应用
|
|
96
|
+
app.register_blueprint(obj)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def setup_query_runner():
|
|
100
|
+
"""
|
|
101
|
+
注入数据查询执行器
|
|
102
|
+
:return:
|
|
103
|
+
"""
|
|
104
|
+
from redash.query_runner import import_query_runners
|
|
105
|
+
from redash import settings as redash_settings
|
|
106
|
+
import_query_runners(redash_settings.QUERY_RUNNERS)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def setup_sql_alchemy(app):
|
|
110
|
+
"""
|
|
111
|
+
配置SQLAlchemy
|
|
112
|
+
:param app:
|
|
113
|
+
:return:
|
|
114
|
+
"""
|
|
115
|
+
db.init_app(app)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def setup_login_manager(app):
|
|
119
|
+
login_manager = LoginManager(app)
|
|
120
|
+
setattr(app, "login_manager", login_manager)
|
|
121
|
+
|
|
122
|
+
@login_manager.request_loader
|
|
123
|
+
def request_loader(request):
|
|
124
|
+
# # 使用token访问的用户
|
|
125
|
+
token = request.headers.get("Authorization", "").replace("Bearer ", "")
|
|
126
|
+
if token:
|
|
127
|
+
user = get_token_user(token)
|
|
128
|
+
if user:
|
|
129
|
+
return user
|
|
130
|
+
apikey = request.headers.get("API-Key")
|
|
131
|
+
if apikey:
|
|
132
|
+
# 使用AK访问的接口用户
|
|
133
|
+
user = get_api_user(apikey)
|
|
134
|
+
if user:
|
|
135
|
+
return user
|
|
136
|
+
# 无任何用户信息返回 匿名用户
|
|
137
|
+
return AnonymousUser()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def setup_swagger(app):
|
|
141
|
+
"""
|
|
142
|
+
配置Swagger
|
|
143
|
+
:param app:
|
|
144
|
+
:return:
|
|
145
|
+
"""
|
|
146
|
+
SWAGGER_URL = app.config.get("SWAGGER_URL", "") # 访问 Swagger UI 的 URL
|
|
147
|
+
# API_URL = 'http://127.0.0.1:5001/static/swagger.json' # Swagger 规范的路径(本地 JSON 文件)
|
|
148
|
+
API_URL = app.config.get("SWAGGER_API_URL", "") # 接口
|
|
149
|
+
# 创建 Swagger UI 蓝图
|
|
150
|
+
swagger_ui_blueprint = get_swaggerui_blueprint(
|
|
151
|
+
SWAGGER_URL, # Swagger UI 访问路径
|
|
152
|
+
app.config.get("OUTSIDE_SCREEN_IP") + API_URL, # Swagger 文件路径
|
|
153
|
+
config={ # Swagger UI 配置参数
|
|
154
|
+
'app_name': "Flask-Swagger-UI 示例"
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL)
|
|
158
|
+
|
|
159
|
+
@app.route(API_URL, methods=['GET'])
|
|
160
|
+
def swagger_spec():
|
|
161
|
+
from lesscode_flask import __version__
|
|
162
|
+
swag = generate_openapi_spec(app)
|
|
163
|
+
swag['info']['title'] = app.config.get("SWAGGER_NAME", "")
|
|
164
|
+
swag['info']['description'] = app.config.get("SWAGGER_DESCRIPTION", "")
|
|
165
|
+
swag['info']['version'] = app.config.get("SWAGGER_VERSION", __version__)
|
|
166
|
+
return swag
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def setup_redis(app):
|
|
170
|
+
redis_conn_list = app.config.get("DATA_SOURCE", [])
|
|
171
|
+
|
|
172
|
+
for r in redis_conn_list:
|
|
173
|
+
if r.get("type") == "redis":
|
|
174
|
+
conn = redis.Redis(host=r.get("host"), port=r.get("port"), db=r.get("db"), password=r.get("password"),
|
|
175
|
+
decode_responses=True)
|
|
176
|
+
if not hasattr(current_app, "redis_conn_dict"):
|
|
177
|
+
current_app.redis_conn_dict = {}
|
|
178
|
+
if getattr(current_app, "redis_conn_dict").get(r.get("conn_name")):
|
|
179
|
+
raise Exception("Connection {} is repetitive".format(r.get("conn_name")))
|
|
180
|
+
else:
|
|
181
|
+
redis_conn_dict = getattr(current_app, "redis_conn_dict")
|
|
182
|
+
redis_conn_dict.update({
|
|
183
|
+
r.get("conn_name"): conn
|
|
184
|
+
})
|
|
185
|
+
setattr(current_app, "redis_conn_dict", redis_conn_dict)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import datetime
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
import traceback
|
|
9
|
+
|
|
10
|
+
from threading import Thread
|
|
11
|
+
|
|
12
|
+
# 装饰器
|
|
13
|
+
from lesscode_utils.json_utils import JSONEncoder
|
|
14
|
+
|
|
15
|
+
from lesscode_flask.utils.helpers import app_config
|
|
16
|
+
from lesscode_flask.utils.redis.redis_helper import RedisHelper
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def Cache(ex=3600 * 12, cache_key=None):
|
|
20
|
+
def cache_func(func):
|
|
21
|
+
# 默认key生成方法:str(item)
|
|
22
|
+
@functools.wraps(func)
|
|
23
|
+
def cache_wrapper(*args, **params):
|
|
24
|
+
try:
|
|
25
|
+
data = deal_cache(func, ex, cache_key, *args, **params)
|
|
26
|
+
except Exception as e:
|
|
27
|
+
logging.error(traceback.format_exc())
|
|
28
|
+
data = func(*args, **params)
|
|
29
|
+
return data
|
|
30
|
+
|
|
31
|
+
return cache_wrapper
|
|
32
|
+
|
|
33
|
+
return cache_func
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def deal_cache(func, ex, cache_key, *args, **params):
|
|
37
|
+
# 获取缓存查询key
|
|
38
|
+
signature = inspect.signature(func)
|
|
39
|
+
params = dict(sorted(params.items(), key=lambda x: x[0]))
|
|
40
|
+
func_name = str(func).split(" ")[1]
|
|
41
|
+
if not cache_key:
|
|
42
|
+
cache_key = format_insert_key(signature, func_name, args, params)
|
|
43
|
+
logging.info("redis_key:{}".format(cache_key))
|
|
44
|
+
value = query_cache(cache_key, params, ex, func=func, args=args)
|
|
45
|
+
if value is not False:
|
|
46
|
+
data = value
|
|
47
|
+
else:
|
|
48
|
+
start = datetime.datetime.now()
|
|
49
|
+
logging.info("[组件:{}]数据开始计算!".format(func_name))
|
|
50
|
+
# copy_params = copy.deepcopy(params)
|
|
51
|
+
# data = func(*args, **copy_params)
|
|
52
|
+
data = func(*args, **params)
|
|
53
|
+
# 插入缓存表
|
|
54
|
+
insert_cache(data, ex, cache_key)
|
|
55
|
+
logging.info("[组件:{}]数据缓存已刷新!用时{}".format(func_name, datetime.datetime.now() - start))
|
|
56
|
+
|
|
57
|
+
return data
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_redis_conn_name():
|
|
61
|
+
try:
|
|
62
|
+
conn_name = app_config.get("REDIS_CACHE_KEY", "redis")
|
|
63
|
+
except:
|
|
64
|
+
raise Exception("Redis connection is missing")
|
|
65
|
+
return conn_name
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def query_cache(cache_key, params=None, ex=3600 * 12, func=None, args=None, conn_name=None):
|
|
69
|
+
if conn_name is None:
|
|
70
|
+
conn_name = get_redis_conn_name()
|
|
71
|
+
|
|
72
|
+
if app_config.get("CACHE_ENABLE"):
|
|
73
|
+
ttl = RedisHelper(conn_name).get_connection().ttl(cache_key)
|
|
74
|
+
|
|
75
|
+
if ttl and ex >= 900 and 0 < ttl <= ex - 900:
|
|
76
|
+
Thread(target=request_interface, kwargs={
|
|
77
|
+
"func": func,
|
|
78
|
+
"params": params,
|
|
79
|
+
"args": args,
|
|
80
|
+
"ex": ex,
|
|
81
|
+
"cache_key": cache_key
|
|
82
|
+
}).start()
|
|
83
|
+
data = RedisHelper(conn_name).sync_get(cache_key)
|
|
84
|
+
if data:
|
|
85
|
+
value = json.loads(data)
|
|
86
|
+
return value
|
|
87
|
+
else:
|
|
88
|
+
logging.info("str_select_key为".format(cache_key))
|
|
89
|
+
return False
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def request_interface(func, params, args, ex, cache_key):
|
|
94
|
+
# copy_params = copy.deepcopy(params)
|
|
95
|
+
data = func(*args, **params)
|
|
96
|
+
insert_cache(data, ex, cache_key)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def format_insert_key(signature, func_name, args, params):
|
|
100
|
+
_args = []
|
|
101
|
+
param_keys = list(signature.parameters.keys())
|
|
102
|
+
if param_keys and args:
|
|
103
|
+
if param_keys[0] == "self":
|
|
104
|
+
_args = copy.deepcopy(args[1:])
|
|
105
|
+
else:
|
|
106
|
+
_args = copy.deepcopy(args)
|
|
107
|
+
if isinstance(_args, tuple):
|
|
108
|
+
_args = list(_args)
|
|
109
|
+
for k in params:
|
|
110
|
+
if k != "self":
|
|
111
|
+
_args.append(json.dumps(params[k]))
|
|
112
|
+
str_insert_key = "&".join([str(x) for x in _args])
|
|
113
|
+
str_insert_key = app_config.get("ROUTE_PREFIX") + "#" + func_name + "#" + str_insert_key
|
|
114
|
+
return str_insert_key
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def insert_cache(data, ex, cache_key, conn_name=None):
|
|
118
|
+
# 大于512kb不缓存
|
|
119
|
+
if sys.getsizeof(data) <= 512 * 1024:
|
|
120
|
+
if conn_name is None:
|
|
121
|
+
conn_name = get_redis_conn_name()
|
|
122
|
+
if app_config.get("CACHE_ENABLE"):
|
|
123
|
+
try:
|
|
124
|
+
RedisHelper(conn_name).sync_set(cache_key, JSONEncoder().encode(data), ex=ex)
|
|
125
|
+
except:
|
|
126
|
+
logging.error(traceback.format_exc())
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 装饰器来标识请求类型
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def request_type(content_type="json"):
|
|
6
|
+
"""
|
|
7
|
+
:param content_type: form-data,json,urlencoded
|
|
8
|
+
:return:
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def decorator(f):
|
|
12
|
+
@wraps(f)
|
|
13
|
+
def decorated_function(*args, **kwargs):
|
|
14
|
+
return f(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
decorated_function._request_type = content_type
|
|
17
|
+
return decorated_function
|
|
18
|
+
|
|
19
|
+
return decorator
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
import uuid
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
|
|
6
|
+
from flask import send_file
|
|
7
|
+
|
|
8
|
+
from lesscode_flask.setting import BaseConfig
|
|
9
|
+
from lesscode_flask.utils.helpers import app_config
|
|
10
|
+
from lesscode_flask.utils.oss.ks3_oss import Ks3Oss
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def judge_file_name(parent_path, file_name, extension, index=1):
|
|
14
|
+
while os.path.isfile(f'{parent_path}/{file_name}.{extension}'):
|
|
15
|
+
if index > 1:
|
|
16
|
+
file_name = file_name.replace(f'({index - 1})', f'({index})')
|
|
17
|
+
else:
|
|
18
|
+
file_name = f'{file_name}({index})'
|
|
19
|
+
|
|
20
|
+
index = index + 1
|
|
21
|
+
return f'{parent_path}/{file_name}.{extension}', f'{file_name}.{extension}'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def format_to_table_download(table_head_list=None, table_body_list=None):
|
|
25
|
+
data_index_list = [one['dataIndex'] for one in table_head_list]
|
|
26
|
+
table_head_map_dict = {one['dataIndex']: one['title'] for one in table_head_list}
|
|
27
|
+
sta_map_dict = {}
|
|
28
|
+
for one in table_body_list:
|
|
29
|
+
for tag in data_index_list:
|
|
30
|
+
if tag not in sta_map_dict:
|
|
31
|
+
sta_map_dict[tag] = [one[tag]]
|
|
32
|
+
else:
|
|
33
|
+
sta_map_dict[tag].append(one[tag])
|
|
34
|
+
sta_map_dict = {table_head_map_dict[k]: v for k, v in sta_map_dict.items()}
|
|
35
|
+
return sta_map_dict
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def export(table_head_list=None, table_body_list=None, file_name=None, extension="xlsx", export_path=None,
|
|
39
|
+
is_upload_ks3=False, bucket_name=None):
|
|
40
|
+
"""
|
|
41
|
+
:param is_upload_ks3: 是否上传至ks3
|
|
42
|
+
:param table_head_list: [
|
|
43
|
+
{
|
|
44
|
+
"title": "企业名称",
|
|
45
|
+
"dataIndex": "name"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"title": "省",
|
|
49
|
+
"dataIndex": "reg_province"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
:param table_body_list: [
|
|
53
|
+
{
|
|
54
|
+
"name":"丽丽"
|
|
55
|
+
"reg_province":"北京市"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
]
|
|
59
|
+
:param file_name:导出文件的名称
|
|
60
|
+
:param extension:扩展名
|
|
61
|
+
:param export_path: 导出目录 默认是静态目录下的download
|
|
62
|
+
:return:
|
|
63
|
+
"""
|
|
64
|
+
data = format_to_table_download(table_head_list, table_body_list)
|
|
65
|
+
try:
|
|
66
|
+
pandas = importlib.import_module("pandas")
|
|
67
|
+
except ImportError:
|
|
68
|
+
raise Exception(f"pandas is not exist,run:pip install pandas==2.2.2")
|
|
69
|
+
|
|
70
|
+
df = pandas.DataFrame(data=data)
|
|
71
|
+
# 定义下载目录,如果没有就新建
|
|
72
|
+
if not export_path:
|
|
73
|
+
export_path = f'{BaseConfig.STATIC_PATH}/download'
|
|
74
|
+
if not os.path.exists(export_path):
|
|
75
|
+
os.mkdir(export_path)
|
|
76
|
+
# 判断文件的名称如果存在则加(n)
|
|
77
|
+
file_path, file_name = judge_file_name(export_path, file_name, extension)
|
|
78
|
+
|
|
79
|
+
df.to_excel(file_path, index=False)
|
|
80
|
+
|
|
81
|
+
file_name = quote(file_name)
|
|
82
|
+
|
|
83
|
+
if is_upload_ks3:
|
|
84
|
+
ks = Ks3Oss()
|
|
85
|
+
if not bucket_name:
|
|
86
|
+
bucket_name = app_config.get("KS3_CONNECT_CONFIG", {}).get("bucket_name")
|
|
87
|
+
response = ks.save(key=file_name, bucket_name=bucket_name, policy="public-read", filename=file_path,
|
|
88
|
+
content_type="filename")
|
|
89
|
+
else:
|
|
90
|
+
response = send_file(
|
|
91
|
+
file_path,
|
|
92
|
+
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
93
|
+
as_attachment=False,
|
|
94
|
+
download_name=file_name
|
|
95
|
+
)
|
|
96
|
+
# 下载完成删除文件
|
|
97
|
+
os.remove(file_path)
|
|
98
|
+
return response
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
class app_config:
|
|
2
|
+
|
|
3
|
+
@staticmethod
|
|
4
|
+
def get(key, default=None):
|
|
5
|
+
"""
|
|
6
|
+
获取配置
|
|
7
|
+
:param key: 配置key
|
|
8
|
+
:param default: 默认值
|
|
9
|
+
:return:
|
|
10
|
+
"""
|
|
11
|
+
from flask import current_app
|
|
12
|
+
return current_app.config.get(key, default)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def serialize_result_to_dict(result):
|
|
16
|
+
"""
|
|
17
|
+
结果对象序列化为字典
|
|
18
|
+
:param result:
|
|
19
|
+
:return:
|
|
20
|
+
"""
|
|
21
|
+
if isinstance(result, list):
|
|
22
|
+
return [serialize_result_to_dict(r) for r in result]
|
|
23
|
+
if not hasattr(result, "__dict__"):
|
|
24
|
+
return result
|
|
25
|
+
return {k: v for k, v in result.__dict__.items() if not k.startswith('_')}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def generate_uuid():
|
|
29
|
+
"""
|
|
30
|
+
生成UUID
|
|
31
|
+
:return:
|
|
32
|
+
"""
|
|
33
|
+
import uuid
|
|
34
|
+
return uuid.uuid4().hex.replace("-", "")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_start_port():
|
|
38
|
+
"""
|
|
39
|
+
获取启动端口
|
|
40
|
+
:return:
|
|
41
|
+
"""
|
|
42
|
+
import sys
|
|
43
|
+
arg_list = sys.argv
|
|
44
|
+
for a in arg_list:
|
|
45
|
+
if "--port" in a:
|
|
46
|
+
return a.split("=")[1]
|
|
47
|
+
return "5000"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def parameter_validation(obj: dict):
|
|
51
|
+
"""
|
|
52
|
+
验证参数对象中非None的键值
|
|
53
|
+
:return:
|
|
54
|
+
"""
|
|
55
|
+
return {k: v for k, v in obj.items() if v}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def parse_boolean(s):
|
|
59
|
+
"""
|
|
60
|
+
将字符串转换为布尔值。
|
|
61
|
+
:param s: 待转换的字符串
|
|
62
|
+
:return:
|
|
63
|
+
"""
|
|
64
|
+
s = s.strip().lower()
|
|
65
|
+
if s in ("yes", "true", "on", "1"):
|
|
66
|
+
return True
|
|
67
|
+
# elif s in ("no", "false", "off", "0", "none"):
|
|
68
|
+
# return False
|
|
69
|
+
else:
|
|
70
|
+
# 其余不在判断全返False
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def inject_args(req, func, view_args={}):
|
|
75
|
+
import inspect
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
实现参数自动注入
|
|
79
|
+
:param req: 请求对象
|
|
80
|
+
:param func: 请求处理函数
|
|
81
|
+
:param view_args: 路径上获取的参数
|
|
82
|
+
:return:
|
|
83
|
+
"""
|
|
84
|
+
jsons = {}
|
|
85
|
+
args = req.args
|
|
86
|
+
form = req.form
|
|
87
|
+
files = req.files
|
|
88
|
+
if req.mimetype == 'application/json' and req.json is not None:
|
|
89
|
+
jsons = req.json
|
|
90
|
+
# 合并args、form和json参数字典
|
|
91
|
+
arguments = dict(**args, **form, **jsons, **files, **view_args)
|
|
92
|
+
# 获取处理方法的 参数签名
|
|
93
|
+
parameters = inspect.signature(func).parameters.items()
|
|
94
|
+
# 获取所有参数名称
|
|
95
|
+
parameter_names = [parameter_name for parameter_name, parameter in parameters]
|
|
96
|
+
kwargs = {}
|
|
97
|
+
# 检查传入的参数中 哪些不在参数列表中,单独存储
|
|
98
|
+
for key in arguments.keys():
|
|
99
|
+
if key not in parameter_names:
|
|
100
|
+
kwargs[key] = arguments[key]
|
|
101
|
+
params_dict = {}
|
|
102
|
+
# parameterName 参数名称, parameter 参数对象
|
|
103
|
+
for parameter_name, parameter in parameters:
|
|
104
|
+
# 依据参数名称,获取请求参数值
|
|
105
|
+
argument_value = arguments.get(parameter_name)
|
|
106
|
+
# 兼容**kwargs 参数
|
|
107
|
+
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
|
|
108
|
+
argument_value = kwargs
|
|
109
|
+
if argument_value:
|
|
110
|
+
# 获取形参类型
|
|
111
|
+
parameter_type = parameter.annotation
|
|
112
|
+
# 形参类型为空,尝试获取形参默认值类型
|
|
113
|
+
if parameter_type is inspect.Parameter.empty:
|
|
114
|
+
parameter_type = type(parameter.default)
|
|
115
|
+
if parameter_type == int:
|
|
116
|
+
params_dict[parameter_name] = int(argument_value)
|
|
117
|
+
elif parameter_type == float:
|
|
118
|
+
params_dict[parameter_name] = float(argument_value)
|
|
119
|
+
elif parameter_type == bool:
|
|
120
|
+
params_dict[parameter_name] = parse_boolean(argument_value)
|
|
121
|
+
else:
|
|
122
|
+
# 其余都按str处理
|
|
123
|
+
params_dict[parameter_name] = argument_value
|
|
124
|
+
return params_dict
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def mustache_render(template, **kwargs):
|
|
128
|
+
"""
|
|
129
|
+
模板参数渲染
|
|
130
|
+
:param template: 模板
|
|
131
|
+
:param kwargs:参数
|
|
132
|
+
:return:
|
|
133
|
+
"""
|
|
134
|
+
from jinja2 import Template
|
|
135
|
+
# 创建模板对象
|
|
136
|
+
template = Template(template)
|
|
137
|
+
# 渲染模板
|
|
138
|
+
return template.render(**kwargs)
|
|
139
|
+
|