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,210 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from functools import partial
|
|
3
|
+
from numbers import Number
|
|
4
|
+
|
|
5
|
+
import pystache
|
|
6
|
+
from dateutil.parser import parse
|
|
7
|
+
from funcy import distinct
|
|
8
|
+
|
|
9
|
+
from lesscode_flask.utils.helpers import mustache_render
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _pluck_name_and_value(default_column, row):
|
|
13
|
+
row = {k.lower(): v for k, v in row.items()}
|
|
14
|
+
name_column = "name" if "name" in row.keys() else default_column.lower()
|
|
15
|
+
value_column = "value" if "value" in row.keys() else default_column.lower()
|
|
16
|
+
|
|
17
|
+
return {"name": row[name_column], "value": str(row[value_column])}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# def _load_result(query_id, org):
|
|
21
|
+
# from redash import models
|
|
22
|
+
#
|
|
23
|
+
# query = models.Query.get_by_id_and_org(query_id, org)
|
|
24
|
+
#
|
|
25
|
+
# if query.data_source:
|
|
26
|
+
# query_result = models.QueryResult.get_by_id_and_org(query.latest_query_data_id, org)
|
|
27
|
+
# return query_result.data
|
|
28
|
+
# else:
|
|
29
|
+
# raise QueryDetachedFromDataSourceError(query_id)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def dropdown_values(query_id, org):
|
|
33
|
+
data = [] # load_result(query_id, org)
|
|
34
|
+
first_column = data["columns"][0]["name"]
|
|
35
|
+
pluck = partial(_pluck_name_and_value, first_column)
|
|
36
|
+
return list(map(pluck, data["rows"]))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def join_parameter_list_values(parameters, schema):
|
|
40
|
+
updated_parameters = {}
|
|
41
|
+
for key, value in parameters.items():
|
|
42
|
+
if isinstance(value, list):
|
|
43
|
+
definition = next((definition for definition in schema if definition["name"] == key), {})
|
|
44
|
+
multi_values_options = definition.get("multiValuesOptions", {})
|
|
45
|
+
separator = str(multi_values_options.get("separator", ","))
|
|
46
|
+
prefix = str(multi_values_options.get("prefix", ""))
|
|
47
|
+
suffix = str(multi_values_options.get("suffix", ""))
|
|
48
|
+
updated_parameters[key] = separator.join([prefix + v + suffix for v in value])
|
|
49
|
+
else:
|
|
50
|
+
updated_parameters[key] = value
|
|
51
|
+
return updated_parameters
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _collect_key_names(nodes):
|
|
55
|
+
keys = []
|
|
56
|
+
for node in nodes._parse_tree:
|
|
57
|
+
if isinstance(node, pystache.parser._EscapeNode):
|
|
58
|
+
keys.append(node.key)
|
|
59
|
+
elif isinstance(node, pystache.parser._SectionNode):
|
|
60
|
+
keys.append(node.key)
|
|
61
|
+
keys.extend(_collect_key_names(node.parsed))
|
|
62
|
+
|
|
63
|
+
return distinct(keys)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _collect_query_parameters(query):
|
|
67
|
+
nodes = pystache.parse(query)
|
|
68
|
+
keys = _collect_key_names(nodes)
|
|
69
|
+
return keys
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _parameter_names(parameter_values):
|
|
73
|
+
names = []
|
|
74
|
+
for key, value in parameter_values.items():
|
|
75
|
+
if isinstance(value, dict):
|
|
76
|
+
for inner_key in value.keys():
|
|
77
|
+
names.append("{}.{}".format(key, inner_key))
|
|
78
|
+
else:
|
|
79
|
+
names.append(key)
|
|
80
|
+
|
|
81
|
+
return names
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _is_number(string):
|
|
85
|
+
if isinstance(string, Number):
|
|
86
|
+
return True
|
|
87
|
+
else:
|
|
88
|
+
float(string)
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _is_regex_pattern(value, regex):
|
|
93
|
+
try:
|
|
94
|
+
if re.compile(regex).fullmatch(value):
|
|
95
|
+
return True
|
|
96
|
+
else:
|
|
97
|
+
return False
|
|
98
|
+
except re.error:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _is_date(string):
|
|
103
|
+
parse(string)
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _is_date_range(obj):
|
|
108
|
+
return _is_date(obj["start"]) and _is_date(obj["end"])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _is_value_within_options(value, dropdown_options, allow_list=False):
|
|
112
|
+
if isinstance(value, list):
|
|
113
|
+
return allow_list and set(map(str, value)).issubset(set(dropdown_options))
|
|
114
|
+
return str(value) in dropdown_options
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ParameterizedQuery:
|
|
118
|
+
def __init__(self, template, schema=None, org=None):
|
|
119
|
+
self.schema = schema or []
|
|
120
|
+
self.template = template
|
|
121
|
+
self.query = template
|
|
122
|
+
self.parameters = {}
|
|
123
|
+
|
|
124
|
+
def apply(self, parameters):
|
|
125
|
+
invalid_parameter_names = [key for (key, value) in parameters.items() if not self._valid(key, value)]
|
|
126
|
+
if invalid_parameter_names:
|
|
127
|
+
raise InvalidParameterError(invalid_parameter_names)
|
|
128
|
+
else:
|
|
129
|
+
self.parameters.update(parameters)
|
|
130
|
+
self.query = mustache_render(self.template, **join_parameter_list_values(parameters, self.schema))
|
|
131
|
+
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def _valid(self, name, value):
|
|
135
|
+
if not self.schema:
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
definition = next(
|
|
139
|
+
(definition for definition in self.schema if definition["name"] == name),
|
|
140
|
+
None,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if not definition:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
enum_options = definition.get("enumOptions")
|
|
147
|
+
query_id = definition.get("queryId")
|
|
148
|
+
regex = definition.get("regex")
|
|
149
|
+
allow_multiple_values = isinstance(definition.get("multiValuesOptions"), dict)
|
|
150
|
+
|
|
151
|
+
if isinstance(enum_options, str):
|
|
152
|
+
enum_options = enum_options.split("\n")
|
|
153
|
+
|
|
154
|
+
validators = {
|
|
155
|
+
"text": lambda value: isinstance(value, str),
|
|
156
|
+
"text-pattern": lambda value: _is_regex_pattern(value, regex),
|
|
157
|
+
"number": _is_number,
|
|
158
|
+
"enum": lambda value: _is_value_within_options(value, enum_options, allow_multiple_values),
|
|
159
|
+
"query": lambda value: _is_value_within_options(
|
|
160
|
+
value,
|
|
161
|
+
[v["value"] for v in dropdown_values(query_id, self.org)],
|
|
162
|
+
allow_multiple_values,
|
|
163
|
+
),
|
|
164
|
+
"date": _is_date,
|
|
165
|
+
"datetime-local": _is_date,
|
|
166
|
+
"datetime-with-seconds": _is_date,
|
|
167
|
+
"date-range": _is_date_range,
|
|
168
|
+
"datetime-range": _is_date_range,
|
|
169
|
+
"datetime-range-with-seconds": _is_date_range,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
validate = validators.get(definition["type"], lambda x: False)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
# multiple error types can be raised here; but we want to convert
|
|
176
|
+
# all except QueryDetached to InvalidParameterError in `apply`
|
|
177
|
+
return validate(value)
|
|
178
|
+
except QueryDetachedFromDataSourceError:
|
|
179
|
+
raise
|
|
180
|
+
except Exception:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def is_safe(self):
|
|
185
|
+
text_parameters = [param for param in self.schema if param["type"] == "text"]
|
|
186
|
+
return not any(text_parameters)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def missing_params(self):
|
|
190
|
+
query_parameters = set(_collect_query_parameters(self.template))
|
|
191
|
+
return set(query_parameters) - set(_parameter_names(self.parameters))
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def text(self):
|
|
195
|
+
return self.query
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class InvalidParameterError(Exception):
|
|
199
|
+
def __init__(self, parameters):
|
|
200
|
+
parameter_names = ", ".join(parameters)
|
|
201
|
+
message = "The following parameter values are incompatible with their definitions: {}".format(parameter_names)
|
|
202
|
+
super(InvalidParameterError, self).__init__(message)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class QueryDetachedFromDataSourceError(Exception):
|
|
206
|
+
def __init__(self, query_id):
|
|
207
|
+
self.query_id = query_id
|
|
208
|
+
super(QueryDetachedFromDataSourceError, self).__init__(
|
|
209
|
+
"This query is detached from any data source. Please select a different query."
|
|
210
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from flask import Response, abort
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ResponseResult(dict):
|
|
9
|
+
"""
|
|
10
|
+
ResponseResult 类用于统一包装数据返回格式
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, status_code=("00000", "请求成功"), data=""):
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
:param status_code:
|
|
17
|
+
:param data:
|
|
18
|
+
"""
|
|
19
|
+
super(ResponseResult, self).__init__()
|
|
20
|
+
# 业务请求状态编码
|
|
21
|
+
self["status"] = status_code[0]
|
|
22
|
+
# 返回状态码对应的说明信息
|
|
23
|
+
self["message"] = status_code[1]
|
|
24
|
+
# 返回数据对象 主对象 指定类型
|
|
25
|
+
self["data"] = data
|
|
26
|
+
# 时间戳
|
|
27
|
+
self["timestamp"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def make_response(message: str, data="", status_code="00000", http_code=200):
|
|
31
|
+
data = json.dumps(ResponseResult(status_code=(status_code, message), data=data))
|
|
32
|
+
respone = Response(data)
|
|
33
|
+
respone.content_type = "application/json"
|
|
34
|
+
respone.status_code = http_code
|
|
35
|
+
return respone
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def success(data,message: str=None):
|
|
39
|
+
"""
|
|
40
|
+
成功返回结果
|
|
41
|
+
:param data:
|
|
42
|
+
:param message:
|
|
43
|
+
:return:
|
|
44
|
+
"""
|
|
45
|
+
if message:
|
|
46
|
+
return ResponseResult(data=data, status_code=("00000", message))
|
|
47
|
+
return ResponseResult(data=data)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def fail(message: str, data="", status_code="99999", http_code=200):
|
|
51
|
+
"""
|
|
52
|
+
失败返回结果
|
|
53
|
+
:param message:
|
|
54
|
+
:param data:
|
|
55
|
+
:param status_code:
|
|
56
|
+
:param http_code:
|
|
57
|
+
:return:
|
|
58
|
+
"""
|
|
59
|
+
respone = ResponseResult.make_response(message, data, status_code, http_code)
|
|
60
|
+
abort(respone)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from functools import reduce
|
|
2
|
+
|
|
3
|
+
from flask_login import AnonymousUserMixin, UserMixin
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PermissionsCheckMixin:
|
|
7
|
+
def has_permission(self, permission):
|
|
8
|
+
return self.has_permissions((permission,))
|
|
9
|
+
|
|
10
|
+
def has_permissions(self, permissions):
|
|
11
|
+
has_permissions = reduce(
|
|
12
|
+
lambda a, b: a and b,
|
|
13
|
+
[permission in self.permissions for permission in permissions],
|
|
14
|
+
True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return has_permissions
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class User(UserMixin, PermissionsCheckMixin):
|
|
21
|
+
"""
|
|
22
|
+
用户对象类
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, id, username: str = None, display_name: str = None, phone_no: str = None, email: str = None,
|
|
26
|
+
org_id: str = None,
|
|
27
|
+
account_status: str = None, permissions=None):
|
|
28
|
+
# '账号id',
|
|
29
|
+
self.id = id
|
|
30
|
+
# 用户名
|
|
31
|
+
self.username = username
|
|
32
|
+
# '显示名',
|
|
33
|
+
self.display_name = display_name
|
|
34
|
+
# 手机号,
|
|
35
|
+
self.phone_no = phone_no
|
|
36
|
+
# 邮箱
|
|
37
|
+
self.email = email
|
|
38
|
+
# 组织机构id',
|
|
39
|
+
self.org_id = org_id
|
|
40
|
+
# '1正常(激活);2未激活(管理员新增,首次登录需要改密码); 3锁定(登录错误次数超限,锁定时长可配置); 4休眠(长期未登录(字段,时长可配置),定时) 5禁用-账号失效;
|
|
41
|
+
self.account_status = account_status
|
|
42
|
+
# 权限集合
|
|
43
|
+
self.permissions = permissions
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def is_api_user():
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def is_anonymous_user():
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def __str__(self):
|
|
54
|
+
return (f"User(id={self.id},username={self.username},phone_no={self.phone_no},"
|
|
55
|
+
f"display_name={self.display_name},email={self.email},org_id={self.org_id},"
|
|
56
|
+
f"account_status={self.account_status},permissions={self.permissions})")
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
return (f"User(id={self.id},username={self.username},phone_no={self.phone_no},"
|
|
60
|
+
f"display_name={self.display_name},email={self.email},org_id={self.org_id},"
|
|
61
|
+
f"account_status={self.account_status},permissions={self.permissions})")
|
|
62
|
+
|
|
63
|
+
def to_dict(self):
|
|
64
|
+
return {
|
|
65
|
+
"id": self.id,
|
|
66
|
+
"username": self.username,
|
|
67
|
+
"display_name": self.display_name,
|
|
68
|
+
"phone_no": self.phone_no,
|
|
69
|
+
"email": self.email if self.email else "",
|
|
70
|
+
"org_id": self.org_id if self.org_id else "",
|
|
71
|
+
"account_status": self.account_status,
|
|
72
|
+
"permissions": ",".join(self.permissions)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AnonymousUser(User):
|
|
77
|
+
"""
|
|
78
|
+
匿名用户
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, permissions=None):
|
|
82
|
+
super(AnonymousUser, self).__init__("AnonymousUserId", "AnonymousUser", "匿名用户", "-", "-", None, 1, permissions)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def is_api_user():
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def is_anonymous_user():
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ApiUser(User):
|
|
94
|
+
def __init__(self, id, username: str = None, display_name: str = None,
|
|
95
|
+
permissions=None):
|
|
96
|
+
super(ApiUser, self).__init__(id, username, display_name, "-", "-", None, 1, permissions)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def is_api_user():
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def to_obj(_user):
|
|
104
|
+
user = ApiUser(_user.get("id"))
|
|
105
|
+
for key, value in _user.items():
|
|
106
|
+
if isinstance(key, bytes):
|
|
107
|
+
key = key.decode('utf-8')
|
|
108
|
+
if isinstance(value, bytes):
|
|
109
|
+
value = value.decode('utf-8')
|
|
110
|
+
if key == "permissions":
|
|
111
|
+
user.permissions = value.split(",")
|
|
112
|
+
elif key == "org_id":
|
|
113
|
+
user.org_id = value if value else None
|
|
114
|
+
elif key == "account_status":
|
|
115
|
+
user.account_status = int(value)
|
|
116
|
+
else:
|
|
117
|
+
setattr(user, key, value)
|
|
118
|
+
return user
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from lesscode_flask.model.auth_client import AuthClient
|
|
2
|
+
from lesscode_flask.model.auth_permission import AuthPermission
|
|
3
|
+
from lesscode_flask.model.user import ApiUser, User
|
|
4
|
+
from lesscode_flask.service.auth_client_service import AuthClientService
|
|
5
|
+
from lesscode_flask.service.auth_permission_service import AuthPermissionService
|
|
6
|
+
from lesscode_flask.utils.helpers import app_config
|
|
7
|
+
from lesscode_flask.utils.redis.redis_helper import RedisHelper
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_token_user(token):
|
|
11
|
+
"""
|
|
12
|
+
使用API key 获取用户信息
|
|
13
|
+
:param apikey:
|
|
14
|
+
:return:
|
|
15
|
+
"""
|
|
16
|
+
token_cache_key = f"oauth2:token:{token}"
|
|
17
|
+
# 优先从缓存中获取
|
|
18
|
+
access_token = RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hgetall(token_cache_key)
|
|
19
|
+
# {
|
|
20
|
+
# 'client_id': 'eYC0lOd1XVBBPpdDntMwFcPg',
|
|
21
|
+
# 'token_type': 'Bearer',
|
|
22
|
+
# 'access_token': 'Rsh1zS9QVzMAefB5G7be04CefK5opjiOCBtBS8BYYi',
|
|
23
|
+
# 'refresh_token': 'la5jb14acszPARoDf1KtH24JjnswSYUsjH4NbsQxOsvpO4Dl',
|
|
24
|
+
# 'scope': 'profile',
|
|
25
|
+
# 'issued_at': 1645614957,
|
|
26
|
+
# 'expires_in': 3600,
|
|
27
|
+
# 'user_id': 21,
|
|
28
|
+
# 'clientId': "",
|
|
29
|
+
# "grant_type": "",
|
|
30
|
+
# "is_only_one": "1",
|
|
31
|
+
# "only_type": "pc"
|
|
32
|
+
# }
|
|
33
|
+
if access_token:
|
|
34
|
+
user_id = access_token.get("user_id")
|
|
35
|
+
clientId = access_token.get("client_id")
|
|
36
|
+
user_cache_key = f"oauth2:client_user_info:client_id:{clientId}:user_id:{user_id}"
|
|
37
|
+
user_dict = RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hgetall(user_cache_key)
|
|
38
|
+
if user_dict:
|
|
39
|
+
user = User(user_id, user_dict["username"], user_dict["display_name"], user_dict["permissions"])
|
|
40
|
+
return user
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_api_user(apikey):
|
|
45
|
+
"""
|
|
46
|
+
使用API key 获取用户信息
|
|
47
|
+
:param apikey:
|
|
48
|
+
:return:
|
|
49
|
+
"""
|
|
50
|
+
cache_key = f"oauth2:apikey_user_info:{apikey}"
|
|
51
|
+
# 优先从缓存中获取
|
|
52
|
+
user_dict = RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hgetall(cache_key)
|
|
53
|
+
if user_dict:
|
|
54
|
+
user = ApiUser.to_obj(user_dict)
|
|
55
|
+
return user
|
|
56
|
+
else:
|
|
57
|
+
# 库里查询
|
|
58
|
+
authClient = AuthClientService().get_one([AuthClient.client_id == apikey])
|
|
59
|
+
if authClient:
|
|
60
|
+
authPermission = AuthPermissionService().get_items([AuthPermission.client_id == authClient.id])
|
|
61
|
+
permissions = [permission.resource_id for permission in authPermission]
|
|
62
|
+
user = ApiUser(authClient.id, authClient.client_id, authClient.client_name, permissions)
|
|
63
|
+
RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hset(cache_key,
|
|
64
|
+
mapping=user.to_dict(),
|
|
65
|
+
time=authClient.token_expires_in)
|
|
66
|
+
return user
|
|
67
|
+
return None
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from flask_login import current_user
|
|
4
|
+
from lesscode_flask.db import db
|
|
5
|
+
from lesscode_flask.model.base_model import BaseModel
|
|
6
|
+
from lesscode_flask.utils.helpers import serialize_result_to_dict, parameter_validation
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseService:
|
|
12
|
+
|
|
13
|
+
def __init__(self, model):
|
|
14
|
+
self.model = model
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def add_item(item: BaseModel):
|
|
18
|
+
"""
|
|
19
|
+
添加数据
|
|
20
|
+
:param item: 待添加数据对象
|
|
21
|
+
:return:
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# try:
|
|
25
|
+
#
|
|
26
|
+
# except AttributeError:
|
|
27
|
+
# current_user = None
|
|
28
|
+
try:
|
|
29
|
+
if hasattr(item, "create_user_id"):
|
|
30
|
+
item.create_user_id = current_user.id
|
|
31
|
+
if hasattr(item, "create_user_name"):
|
|
32
|
+
item.create_user_name = current_user.display_name
|
|
33
|
+
except Exception as e:
|
|
34
|
+
if hasattr(item, "create_user_id"):
|
|
35
|
+
item.create_user_id = "AnonymousUserId"
|
|
36
|
+
if hasattr(item, "create_user_name"):
|
|
37
|
+
item.create_user_name = "匿名用户"
|
|
38
|
+
db.session.add(item)
|
|
39
|
+
db.session.commit()
|
|
40
|
+
return item.id
|
|
41
|
+
|
|
42
|
+
def add_items(self, items: list):
|
|
43
|
+
"""
|
|
44
|
+
添加数据
|
|
45
|
+
:param items: 待添加数据对象集合
|
|
46
|
+
:return:
|
|
47
|
+
"""
|
|
48
|
+
for item in items:
|
|
49
|
+
try:
|
|
50
|
+
if hasattr(item, "create_user_id"):
|
|
51
|
+
item.create_user_id = current_user.id
|
|
52
|
+
if hasattr(item, "create_user_name"):
|
|
53
|
+
item.create_user_name = current_user.display_name
|
|
54
|
+
except Exception as e:
|
|
55
|
+
if hasattr(item, "create_user_id"):
|
|
56
|
+
item.create_user_id = "AnonymousUserId"
|
|
57
|
+
if hasattr(item, "create_user_name"):
|
|
58
|
+
item.create_user_name = "匿名用户"
|
|
59
|
+
db.session.execute(
|
|
60
|
+
self.model.__table__.insert(),
|
|
61
|
+
items
|
|
62
|
+
)
|
|
63
|
+
db.session.commit()
|
|
64
|
+
return items
|
|
65
|
+
|
|
66
|
+
def update_item(self, id: str, item: dict):
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
if hasattr(self.model, "modify_user_id"):
|
|
70
|
+
item["modify_user_id"] = current_user.id
|
|
71
|
+
if hasattr(self.model, "modify_user_name"):
|
|
72
|
+
item["modify_user_name"] = current_user.display_name
|
|
73
|
+
except Exception as e:
|
|
74
|
+
if hasattr(item, "modify_user_id"):
|
|
75
|
+
item.create_user_id = "AnonymousUserId"
|
|
76
|
+
if hasattr(item, "modify_user_name"):
|
|
77
|
+
item.create_user_name = "匿名用户"
|
|
78
|
+
self.model.query.filter_by(id=id).update(parameter_validation(item))
|
|
79
|
+
db.session.commit()
|
|
80
|
+
return id
|
|
81
|
+
|
|
82
|
+
def get_item(self, id: str):
|
|
83
|
+
"""
|
|
84
|
+
获取单条信息
|
|
85
|
+
:param id:
|
|
86
|
+
:return:
|
|
87
|
+
"""
|
|
88
|
+
item = self.model.query.get(id)
|
|
89
|
+
return item
|
|
90
|
+
|
|
91
|
+
def get_one(self, filters: list):
|
|
92
|
+
"""
|
|
93
|
+
获取单条信息
|
|
94
|
+
:param filters:
|
|
95
|
+
:return:
|
|
96
|
+
"""
|
|
97
|
+
query = self.model.query
|
|
98
|
+
if filters:
|
|
99
|
+
query = query.filter(*filters)
|
|
100
|
+
item = query.one()
|
|
101
|
+
return item
|
|
102
|
+
|
|
103
|
+
def get_items(self, filters: list = None):
|
|
104
|
+
"""
|
|
105
|
+
获取列表信息
|
|
106
|
+
:param filters:
|
|
107
|
+
:return:
|
|
108
|
+
"""
|
|
109
|
+
query = self.model.query
|
|
110
|
+
if filters:
|
|
111
|
+
query = query.filter(*filters)
|
|
112
|
+
items = query.all()
|
|
113
|
+
return items
|
|
114
|
+
|
|
115
|
+
def delete_item(self, id: str):
|
|
116
|
+
self.model.query.filter_by(id=id).delete()
|
|
117
|
+
return id
|
|
118
|
+
|
|
119
|
+
def delete_items(self, filters: list):
|
|
120
|
+
if filters and len(filters) > 0:
|
|
121
|
+
self.model.query.filter(*filters).delete()
|
|
122
|
+
return id
|
|
123
|
+
|
|
124
|
+
def page(self, columns: [], filters: list = None, page_num: int = 1, page_size: int = 10):
|
|
125
|
+
query = self.model.query
|
|
126
|
+
if filters:
|
|
127
|
+
query = query.filter(*filters)
|
|
128
|
+
pagination = query.paginate(page=page_num, per_page=page_size)
|
|
129
|
+
# 获取当前页的数据
|
|
130
|
+
items = pagination.items
|
|
131
|
+
# 获取分页信息
|
|
132
|
+
total = pagination.total
|
|
133
|
+
has_prev = pagination.has_prev
|
|
134
|
+
has_next = pagination.has_next
|
|
135
|
+
result = {"columns": columns, "dataSource": serialize_result_to_dict(items), "total": total,
|
|
136
|
+
"has_prev": has_prev,
|
|
137
|
+
"has_next": has_next}
|
|
138
|
+
return result
|