lesscode-flask 0.2.12__tar.gz → 0.2.14__tar.gz
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-0.2.12 → lesscode_flask-0.2.14}/PKG-INFO +2 -12
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/__init__.py +1 -1
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/app.py +8 -3
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/model/user.py +8 -4
- lesscode_flask-0.2.14/lesscode_flask/model/user_limit_policy.py +58 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/setting/__init__.py +19 -15
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/limit/limit_util.py +50 -0
- lesscode_flask-0.2.14/lesscode_flask/utils/limit/req/redis_rate_limiter.py +113 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/limit/req_count/redis_count_limiter.py +19 -9
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask.egg-info/PKG-INFO +2 -12
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask.egg-info/SOURCES.txt +1 -0
- lesscode_flask-0.2.12/lesscode_flask/utils/limit/req/redis_rate_limiter.py +0 -63
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/README.md +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/db/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/db/datasource.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/db/executor.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/export_data/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/export_data/data_download_handler.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/log/access_log_handler.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/model/access_log.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/model/base_model.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/model/parameterized_query.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/model/resource_param_template.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/model/response_result.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/service/access_log_service.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/service/base_service.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/service/resource_param_template_service.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/setup/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/signals.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/static/swagger.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/decorator/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/decorator/cache.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/decorator/sql_injection.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/decorator/swagger.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/dify_utils.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/file/file_exporter.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/file/file_utils.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/fs_util.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/helpers.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/json/NotSortJSONProvider.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/limit/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/limit/req/rate_limiter_handler.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/limit/req_count/count_limiter_handler.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/oss/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/oss/aliyun_oss.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/oss/ks3_oss.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/oss/minio_oss.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/redis/redis_helper.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/request/request.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/swagger/swagger_template.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/swagger/swagger_util.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/task/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/task/task_helper.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/thread/thread_utils.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/wsgi.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask.egg-info/dependency_links.txt +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask.egg-info/requires.txt +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask.egg-info/top_level.txt +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/query_runner/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/query_runner/clickhouse.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/query_runner/elasticsearch.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/query_runner/kingbase.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/query_runner/mysql.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/query_runner/pg.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/settings/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/settings/helpers.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/utils/__init__.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/redash/utils/requests_session.py +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/setup.cfg +0 -0
- {lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: lesscode-flask
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: lesscode-flask 是基于flask的web开发脚手架项目,该项目初衷为简化开发过程,让研发人员更加关注业务。
|
|
5
5
|
Home-page: https://lesscode-flask
|
|
6
6
|
Author: Chao.yy
|
|
@@ -26,16 +26,6 @@ Requires-Dist: gevent==25.5.1
|
|
|
26
26
|
Requires-Dist: openpyxl==3.1.5
|
|
27
27
|
Requires-Dist: iputil==0.3.1
|
|
28
28
|
Requires-Dist: phone==0.4.5
|
|
29
|
-
Dynamic: author
|
|
30
|
-
Dynamic: author-email
|
|
31
|
-
Dynamic: classifier
|
|
32
|
-
Dynamic: description
|
|
33
|
-
Dynamic: description-content-type
|
|
34
|
-
Dynamic: home-page
|
|
35
|
-
Dynamic: platform
|
|
36
|
-
Dynamic: requires-dist
|
|
37
|
-
Dynamic: requires-python
|
|
38
|
-
Dynamic: summary
|
|
39
29
|
|
|
40
30
|
# lesscode-flask
|
|
41
31
|
|
|
@@ -24,7 +24,7 @@ from lesscode_flask.utils.decorator.sql_injection import contains_sql_injection
|
|
|
24
24
|
from lesscode_flask.utils.helpers import inject_args, generate_uuid, app_config
|
|
25
25
|
from lesscode_flask.utils.json.NotSortJSONProvider import NotSortJSONProvider
|
|
26
26
|
from lesscode_flask.utils.limit import RedisRateLimiter
|
|
27
|
-
from lesscode_flask.utils.limit.limit_util import get_rate_limit_info, get_count_limit_info
|
|
27
|
+
from lesscode_flask.utils.limit.limit_util import get_rate_limit_info, get_count_limit_info, get_user_limit_policy
|
|
28
28
|
from lesscode_flask.utils.limit.req.rate_limiter_handler import RateLimitHandler
|
|
29
29
|
from lesscode_flask.utils.limit.req_count.count_limiter_handler import CountLimitHandler
|
|
30
30
|
from lesscode_flask.utils.limit.req_count.redis_count_limiter import RedisCountLimiter
|
|
@@ -69,14 +69,19 @@ class Lesscoder(Flask):
|
|
|
69
69
|
|
|
70
70
|
def preprocess_request(self) -> ft.ResponseReturnValue | None:
|
|
71
71
|
v = super(Lesscoder, self).preprocess_request()
|
|
72
|
+
user_limit_policy = None
|
|
72
73
|
if hasattr(self, 'countLimiter') and self.countLimiter:
|
|
73
|
-
|
|
74
|
+
if not user_limit_policy:
|
|
75
|
+
user_limit_policy = get_user_limit_policy()
|
|
76
|
+
allowed, remaining = self.countLimiter.is_allowed(user_limit_policy)
|
|
74
77
|
if not allowed:
|
|
75
78
|
countLimitHandler = self.config.get("COUNT_LIMIT_HANDLER", CountLimitHandler)
|
|
76
79
|
countLimitHandler(request,remaining).response_handler()
|
|
77
80
|
if hasattr(self, 'rateLimiter') and self.rateLimiter:
|
|
81
|
+
if not user_limit_policy:
|
|
82
|
+
user_limit_policy = get_user_limit_policy()
|
|
78
83
|
# 是否触发限流 延迟时间 超出数量
|
|
79
|
-
allowed,delay, excess = self.rateLimiter.is_limited(
|
|
84
|
+
allowed,delay, excess = self.rateLimiter.is_limited(user_limit_policy)
|
|
80
85
|
if not allowed :
|
|
81
86
|
rateLimitHandler = self.config.get("RATE_LIMIT_HANDLER", RateLimitHandler)
|
|
82
87
|
rateLimitHandler(request,delay, excess).response_handler()
|
|
@@ -41,7 +41,7 @@ class User(flask_login.UserMixin, PermissionsCheckMixin):
|
|
|
41
41
|
email: str = None,
|
|
42
42
|
type: int = 0, account_status: str = None, permissions=[], permissions_url=[], roleIds=[],
|
|
43
43
|
client_id: str = None,
|
|
44
|
-
sub: str = None):
|
|
44
|
+
sub: str = None,limit_policy: str =None):
|
|
45
45
|
# '账号id',
|
|
46
46
|
self.id = id
|
|
47
47
|
# 用户名
|
|
@@ -67,6 +67,8 @@ class User(flask_login.UserMixin, PermissionsCheckMixin):
|
|
|
67
67
|
self.permissions_url = permissions_url
|
|
68
68
|
# # 角色集合
|
|
69
69
|
self.roleIds = roleIds
|
|
70
|
+
# 限速策略
|
|
71
|
+
self.limit_policy = limit_policy
|
|
70
72
|
|
|
71
73
|
#
|
|
72
74
|
@property
|
|
@@ -124,7 +126,8 @@ class User(flask_login.UserMixin, PermissionsCheckMixin):
|
|
|
124
126
|
"sub": self.sub if self.sub else "",
|
|
125
127
|
"permissions": json.dumps(self.permissions),
|
|
126
128
|
"permissions_url": json.dumps(self.permissions_url),
|
|
127
|
-
"roleIds": json.dumps(self.roleIds)
|
|
129
|
+
"roleIds": json.dumps(self.roleIds),
|
|
130
|
+
"limit_policy": self.limit_policy
|
|
128
131
|
|
|
129
132
|
}
|
|
130
133
|
|
|
@@ -136,7 +139,7 @@ class User(flask_login.UserMixin, PermissionsCheckMixin):
|
|
|
136
139
|
display_name=user_data.get("display_name", ""), phone_no=user_data.get("phone_no", ""),
|
|
137
140
|
email=user_data.get("email", ""), type=user_data.get("type", 0),
|
|
138
141
|
account_status=user_data.get("account_status", 1),
|
|
139
|
-
client_id=user_data.get("client_id", ""), sub=user_data.get("sub", ""))
|
|
142
|
+
client_id=user_data.get("client_id", ""), sub=user_data.get("sub", ""),limit_policy =user_data.get("limit_policy"))
|
|
140
143
|
permissions = user_data.get("permissions", [])
|
|
141
144
|
if isinstance(permissions, str):
|
|
142
145
|
user.permissions = json.loads(permissions)
|
|
@@ -193,7 +196,8 @@ class User(flask_login.UserMixin, PermissionsCheckMixin):
|
|
|
193
196
|
phone_no=user_dict["phone_no"],
|
|
194
197
|
permissions=user_dict.get("permissions", []),
|
|
195
198
|
roleIds=user_dict["roleIds"],
|
|
196
|
-
client_id=user_dict["client_id"]
|
|
199
|
+
client_id=user_dict["client_id"],
|
|
200
|
+
limit_policy = user_dict.get("limit_policy", None),
|
|
197
201
|
)
|
|
198
202
|
return user
|
|
199
203
|
return User()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
import json
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UserLimitPolicy():
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
id: str= None,
|
|
11
|
+
policy_name: str= None,
|
|
12
|
+
client_id: str= None,
|
|
13
|
+
rate_limit_enable: int = 0,
|
|
14
|
+
rate_limit_window_sec: int = 10,
|
|
15
|
+
rate_limit_rate: int = 5,
|
|
16
|
+
rate_limit_burst: int = 0,
|
|
17
|
+
count_limit_enable: int = 0,
|
|
18
|
+
count_limit_window_sec: int = 43200,
|
|
19
|
+
count_limit_count: int = 500,
|
|
20
|
+
white_list: Union[str, list] = None,
|
|
21
|
+
alert_webhook_url: str = None,
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
初始化 AuthUserLimitPolicy 对象。
|
|
25
|
+
|
|
26
|
+
:param policy_name: 限流策略名称
|
|
27
|
+
:param client_id: 所属应用 ID
|
|
28
|
+
:param rate_limit_enable: 是否启用频率限流(默认 0)
|
|
29
|
+
:param rate_limit_window_sec: 频率窗口时间(秒,默认 1)
|
|
30
|
+
:param rate_limit_rate: 每窗口期内允许请求数(QPS,默认 1)
|
|
31
|
+
:param rate_limit_burst: 允许突发流量数(默认 0)
|
|
32
|
+
:param count_limit_enable: 是否启用计数量限流(默认 0)
|
|
33
|
+
:param count_limit_window_sec: 计数窗口时间(秒,默认 43200)
|
|
34
|
+
:param count_limit_count: 窗口期内最大访问量(默认 500)
|
|
35
|
+
:param white_list: 限流白名单路径列表(JSON 格式)
|
|
36
|
+
:param alert_webhook_url: 报警 webhook URL
|
|
37
|
+
"""
|
|
38
|
+
self.id = id
|
|
39
|
+
self.policy_name = policy_name
|
|
40
|
+
self.client_id = client_id
|
|
41
|
+
# 限流参数类型转换为int
|
|
42
|
+
self.rate_limit_enable = int(rate_limit_enable) if rate_limit_enable is not None else 0
|
|
43
|
+
self.rate_limit_window_sec = int(rate_limit_window_sec) if rate_limit_window_sec is not None else 1
|
|
44
|
+
self.rate_limit_rate = int(rate_limit_rate) if rate_limit_rate is not None else 1
|
|
45
|
+
self.rate_limit_burst = int(rate_limit_burst) if rate_limit_burst is not None else 0
|
|
46
|
+
self.count_limit_enable = int(count_limit_enable) if count_limit_enable is not None else 0
|
|
47
|
+
self.count_limit_window_sec = int(count_limit_window_sec) if count_limit_window_sec is not None else 43200
|
|
48
|
+
self.count_limit_count = int(count_limit_count) if count_limit_count is not None else 500
|
|
49
|
+
self.alert_webhook_url = alert_webhook_url
|
|
50
|
+
|
|
51
|
+
# 修改后的 white_list 处理逻辑
|
|
52
|
+
if isinstance(white_list, str):
|
|
53
|
+
self.white_list = json.loads(white_list) if white_list else []
|
|
54
|
+
elif isinstance(white_list, list):
|
|
55
|
+
self.white_list = white_list
|
|
56
|
+
else:
|
|
57
|
+
self.white_list = []
|
|
58
|
+
|
|
@@ -94,28 +94,32 @@ class BaseConfig:
|
|
|
94
94
|
RATE_LIMIT_ENABLE: bool = False
|
|
95
95
|
# 触发限流的处理函数
|
|
96
96
|
RATE_LIMIT_HANDLER = RateLimitHandler
|
|
97
|
-
# 限流频率窗口 单位是每秒
|
|
98
|
-
RATE_LIMIT_TIME_WINDOW = 1
|
|
99
|
-
# 限流频率
|
|
100
|
-
RATE_LIMIT_RATE = 1
|
|
101
|
-
# 限流频率允许的突发值 请求速率超过(rate + brust)的请求会被直接拒绝。
|
|
102
|
-
RATE_LIMIT_BURST = 0
|
|
97
|
+
# # 限流频率窗口 单位是每秒
|
|
98
|
+
# RATE_LIMIT_TIME_WINDOW = 1
|
|
99
|
+
# # 限流频率
|
|
100
|
+
# RATE_LIMIT_RATE = 1
|
|
101
|
+
# # 限流频率允许的突发值 请求速率超过(rate + brust)的请求会被直接拒绝。
|
|
102
|
+
# RATE_LIMIT_BURST = 0
|
|
103
103
|
|
|
104
104
|
# 是否启用限流计数验证
|
|
105
105
|
COUNT_LIMIT_ENABLE: bool = False
|
|
106
106
|
# 触发限流的处理函数
|
|
107
107
|
COUNT_LIMIT_HANDLER = CountLimitHandler
|
|
108
|
-
# 窗口期内最大访问量
|
|
109
|
-
COUNT_LIMIT_COUNT = 500
|
|
110
|
-
# 窗口期时长 单位秒
|
|
111
|
-
COUNT_LIMIT_TIME_WINDOW = 60*60*12
|
|
108
|
+
# # 窗口期内最大访问量
|
|
109
|
+
# COUNT_LIMIT_COUNT = 500
|
|
110
|
+
# # 窗口期时长 单位秒
|
|
111
|
+
# COUNT_LIMIT_TIME_WINDOW = 60*60*12
|
|
112
112
|
# 白名单
|
|
113
113
|
LIMIT_WHITE_LIST: list = ["/ike2b/user/WXOA/login", "/ike2b/detail/company/node/get_note",
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
"/ike2b/user/attention/attention_company_status",
|
|
115
|
+
"/ike2b/pay/wx_pay/sq_coin/get_payment_status",
|
|
116
|
+
"/toolEngine/targetedInvestmentPromotion/get_reach_chain",
|
|
117
|
+
"/ike2b/user/WXOA/bind", "/ike2b/select/park/park_list",
|
|
118
|
+
"/ike2b/pool/company/area_distribution/park_company_count_by_special_tag",
|
|
119
|
+
"/ike2b/pool/region/list/search_region_list_by_range",
|
|
120
|
+
"/ike2b/pool/park/list/search_park_list_by_range",
|
|
121
|
+
]
|
|
122
|
+
|
|
119
123
|
# 飞书 webhook URL 常量定义 用于向指定的飞书机器人发送消息的 webhook 地址
|
|
120
124
|
LIMIT_FS_WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/545140ef-8234-4167-9f0c-8ee72abae430"
|
|
121
125
|
|
|
@@ -3,7 +3,11 @@ import hashlib
|
|
|
3
3
|
from flask import request, current_app
|
|
4
4
|
|
|
5
5
|
from lesscode_flask.model.user import flask_login
|
|
6
|
+
from lesscode_flask.model.user_limit_policy import UserLimitPolicy
|
|
6
7
|
from lesscode_flask.utils.helpers import app_config
|
|
8
|
+
from lesscode_flask.utils.redis.redis_helper import RedisHelper
|
|
9
|
+
|
|
10
|
+
|
|
7
11
|
def limit_key_encode() -> str:
|
|
8
12
|
limit_key = None
|
|
9
13
|
# 白名单
|
|
@@ -25,6 +29,25 @@ def limit_key_encode() -> str:
|
|
|
25
29
|
limit_key = hashlib.md5(limit_key.encode('utf-8')).hexdigest()
|
|
26
30
|
return hashlib.md5(limit_key.encode('utf-8')).hexdigest()
|
|
27
31
|
|
|
32
|
+
def limit_key(request_url) -> str:
|
|
33
|
+
"""
|
|
34
|
+
构建访问计数信息的redis 缓存key
|
|
35
|
+
:param request_url:
|
|
36
|
+
:return:
|
|
37
|
+
"""
|
|
38
|
+
limit_key = None
|
|
39
|
+
# 1、获取当前用户
|
|
40
|
+
current_user = flask_login.current_user
|
|
41
|
+
if current_user and not current_user.is_anonymous_user:
|
|
42
|
+
user_id = current_user.id
|
|
43
|
+
# 拼接 key 用户id+url
|
|
44
|
+
limit_key = f"{user_id}:{request_url}"
|
|
45
|
+
# 如果键为空,使用客户端IP
|
|
46
|
+
if not limit_key:
|
|
47
|
+
limit_key = f"{request.remote_addr}{request_url}"
|
|
48
|
+
# 对键进行哈希处理,避免键过长
|
|
49
|
+
limit_key = hashlib.md5(limit_key.encode('utf-8')).hexdigest()
|
|
50
|
+
return limit_key
|
|
28
51
|
|
|
29
52
|
def get_rate_limit_info() -> str:
|
|
30
53
|
"""
|
|
@@ -47,5 +70,32 @@ def get_count_limit_info() -> str:
|
|
|
47
70
|
# 窗口期时长 单位秒
|
|
48
71
|
time_window = app_config.get("COUNT_LIMIT_TIME_WINDOW",3600)
|
|
49
72
|
limit_key = limit_key_encode()
|
|
73
|
+
user_limit_policy = get_user_limit_policy()
|
|
50
74
|
return limit_key, count, time_window
|
|
51
75
|
|
|
76
|
+
|
|
77
|
+
def get_user_limit_policy():
|
|
78
|
+
limit_policy_id = None # 初始化变量
|
|
79
|
+
user_limit_policy = None # 初始化变量
|
|
80
|
+
REDIS_OAUTH_KEY = app_config.get("REDIS_OAUTH_KEY", "redis")
|
|
81
|
+
|
|
82
|
+
# 1、获取当前用户
|
|
83
|
+
current_user = flask_login.current_user
|
|
84
|
+
if current_user and not current_user.is_anonymous_user:
|
|
85
|
+
limit_policy_id = current_user.limit_policy
|
|
86
|
+
# item["white_list"] = json.dumps(item.get("white_list", []))
|
|
87
|
+
if limit_policy_id:
|
|
88
|
+
key = f"limit_policy:{limit_policy_id}"
|
|
89
|
+
limit_policy_id = RedisHelper(REDIS_OAUTH_KEY).sync_hgetall(key)
|
|
90
|
+
user_limit_policy = UserLimitPolicy(**limit_policy_id)
|
|
91
|
+
|
|
92
|
+
if user_limit_policy:
|
|
93
|
+
return user_limit_policy
|
|
94
|
+
else:
|
|
95
|
+
# 如果没有策略就构建默认策略
|
|
96
|
+
REDIS_RATE_LIMIT_ENABLE = app_config.get("RATE_LIMIT_ENABLE", False)
|
|
97
|
+
REDIS_COUNT_LIMIT_ENABLE = app_config.get("COUNT_LIMIT_ENABLE", False)
|
|
98
|
+
LIMIT_WHITE_LIST = app_config.get("LIMIT_WHITE_LIST", False)
|
|
99
|
+
return UserLimitPolicy(rate_limit_enable=int(REDIS_RATE_LIMIT_ENABLE),
|
|
100
|
+
count_limit_enable=int(REDIS_COUNT_LIMIT_ENABLE),white_list=LIMIT_WHITE_LIST)
|
|
101
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from flask import request
|
|
5
|
+
|
|
6
|
+
from lesscode_flask.model.user_limit_policy import UserLimitPolicy
|
|
7
|
+
from lesscode_flask.utils.limit.limit_util import limit_key
|
|
8
|
+
from lesscode_flask.utils.redis.redis_helper import RedisHelper
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RedisRateLimiter:
|
|
14
|
+
"""
|
|
15
|
+
Redis限流实现
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, redis_key):
|
|
19
|
+
self.redis_key = redis_key
|
|
20
|
+
|
|
21
|
+
def is_limited(self,user_limit_policy: UserLimitPolicy) -> tuple:
|
|
22
|
+
"""
|
|
23
|
+
检查是否触发限流
|
|
24
|
+
:param user_limit_policy: 策略信息
|
|
25
|
+
:return: (allowed,delay, excess) 是否触发限流 延迟时间和超出数量
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
rate: float = user_limit_policy.rate_limit_rate
|
|
29
|
+
burst: float = user_limit_policy.rate_limit_burst
|
|
30
|
+
time_window: int = user_limit_policy.rate_limit_window_sec
|
|
31
|
+
rate_limit_enable = user_limit_policy.rate_limit_enable
|
|
32
|
+
limit_white_list = user_limit_policy.white_list
|
|
33
|
+
request_url = request.url_rule.rule
|
|
34
|
+
# 未启用限流 或者 在白名单中 直接返回允许
|
|
35
|
+
if rate_limit_enable == 0 or request_url in limit_white_list:
|
|
36
|
+
# 白名单访问 直接返回None
|
|
37
|
+
return True, 0, 0
|
|
38
|
+
# 构造Redis键
|
|
39
|
+
key = f"limit_req:{limit_key(request_url)}"
|
|
40
|
+
# 存储键的键
|
|
41
|
+
excess_key = f"{key}excess"
|
|
42
|
+
# 存储最近一次请求时间毫秒的键
|
|
43
|
+
last_key = f"{key}last"
|
|
44
|
+
# 当前时间的毫秒表示形式
|
|
45
|
+
now = time.time() * 1000 # 转换为毫秒
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# 获取上次记录的信息
|
|
49
|
+
excess = RedisHelper(self.redis_key).sync_get(excess_key)
|
|
50
|
+
last =RedisHelper(self.redis_key).sync_get(last_key)
|
|
51
|
+
|
|
52
|
+
if excess is not None and last is not None:
|
|
53
|
+
excess = float(excess)
|
|
54
|
+
# 获取最近一次请求的时间毫秒值
|
|
55
|
+
last = float(last)
|
|
56
|
+
# 计算当前与最近一次访问的时间差
|
|
57
|
+
elapsed = now - last
|
|
58
|
+
# 根据时间差计算当前的excess值
|
|
59
|
+
excess = max(excess - (rate * abs(elapsed) / 1000*time_window) + 1, 0)
|
|
60
|
+
else:
|
|
61
|
+
excess = 0
|
|
62
|
+
# 返回延迟时间和超出数量
|
|
63
|
+
delay = excess / rate
|
|
64
|
+
# 检查是否超出限制
|
|
65
|
+
if excess > burst+rate:
|
|
66
|
+
return False, delay, excess # 被拒绝
|
|
67
|
+
# 更新Redis中的值
|
|
68
|
+
RedisHelper(self.redis_key).sync_set(excess_key, excess)
|
|
69
|
+
RedisHelper(self.redis_key).sync_set(last_key, now)
|
|
70
|
+
return True,delay, excess
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Redis rate limiter error: {e}")
|
|
74
|
+
return True, 0,0
|
|
75
|
+
# if not key: # 没有key表示不验证
|
|
76
|
+
# return True, 0,0
|
|
77
|
+
# # 构造Redis键
|
|
78
|
+
# key = f"limit_req:{key}"
|
|
79
|
+
# # 存储键的键
|
|
80
|
+
# excess_key = f"{key}excess"
|
|
81
|
+
# # 存储最近一次请求时间毫秒的键
|
|
82
|
+
# last_key = f"{key}last"
|
|
83
|
+
# # 当前时间的毫秒表示形式
|
|
84
|
+
# now = time.time() * 1000 # 转换为毫秒
|
|
85
|
+
#
|
|
86
|
+
# try:
|
|
87
|
+
# # 获取上次记录的信息
|
|
88
|
+
# excess = RedisHelper(self.redis_key).sync_get(excess_key)
|
|
89
|
+
# last =RedisHelper(self.redis_key).sync_get(last_key)
|
|
90
|
+
#
|
|
91
|
+
# if excess is not None and last is not None:
|
|
92
|
+
# excess = float(excess)
|
|
93
|
+
# # 获取最近一次请求的时间毫秒值
|
|
94
|
+
# last = float(last)
|
|
95
|
+
# # 计算当前与最近一次访问的时间差
|
|
96
|
+
# elapsed = now - last
|
|
97
|
+
# # 根据时间差计算当前的excess值
|
|
98
|
+
# excess = max(excess - (rate * abs(elapsed) / 1000*time_window) + 1, 0)
|
|
99
|
+
# else:
|
|
100
|
+
# excess = 0
|
|
101
|
+
# # 返回延迟时间和超出数量
|
|
102
|
+
# delay = excess / rate
|
|
103
|
+
# # 检查是否超出限制
|
|
104
|
+
# if excess > burst+rate:
|
|
105
|
+
# return False, delay, excess # 被拒绝
|
|
106
|
+
# # 更新Redis中的值
|
|
107
|
+
# RedisHelper(self.redis_key).sync_set(excess_key, excess)
|
|
108
|
+
# RedisHelper(self.redis_key).sync_set(last_key, now)
|
|
109
|
+
# return True,delay, excess
|
|
110
|
+
#
|
|
111
|
+
# except Exception as e:
|
|
112
|
+
# logger.error(f"Redis rate limiter error: {e}")
|
|
113
|
+
# return True, 0,0
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
3
|
+
from flask import request
|
|
4
|
+
|
|
5
|
+
from lesscode_flask.model.user_limit_policy import UserLimitPolicy
|
|
6
|
+
from lesscode_flask.utils.limit.limit_util import limit_key
|
|
2
7
|
from lesscode_flask.utils.redis.redis_helper import RedisHelper
|
|
3
8
|
|
|
4
9
|
logger = logging.getLogger(__name__)
|
|
@@ -9,18 +14,23 @@ class RedisCountLimiter:
|
|
|
9
14
|
def __init__(self, redis_key):
|
|
10
15
|
self.redis_key = redis_key
|
|
11
16
|
|
|
12
|
-
def is_allowed(self,
|
|
17
|
+
def is_allowed(self, user_limit_policy: UserLimitPolicy):
|
|
13
18
|
"""
|
|
14
19
|
检查是否允许请求
|
|
15
|
-
:param key: 限流键
|
|
16
|
-
:param count: 时间窗口内的请求数量限制
|
|
17
|
-
:param time_window: 时间窗口(秒)
|
|
18
|
-
:param cost: 本次请求消耗的令牌数
|
|
19
20
|
:return: (allowed, remaining)
|
|
20
21
|
"""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
count = user_limit_policy.count_limit_count
|
|
23
|
+
time_window = user_limit_policy.count_limit_window_sec
|
|
24
|
+
count_limit_enable = user_limit_policy.count_limit_enable
|
|
25
|
+
limit_white_list = user_limit_policy.white_list
|
|
26
|
+
cost = 1 # 每次消耗一次数
|
|
27
|
+
request_url = request.url_rule.rule
|
|
28
|
+
# 未启用限流 或者 在白名单中 直接返回允许
|
|
29
|
+
if count_limit_enable == 0 or request_url in limit_white_list:
|
|
30
|
+
# 白名单访问 直接返回None
|
|
31
|
+
return True, 0, 0
|
|
32
|
+
# 限流键
|
|
33
|
+
key = f"limit_count:{limit_key(request_url)}"
|
|
24
34
|
|
|
25
35
|
try:
|
|
26
36
|
remaining = RedisHelper(self.redis_key).sync_get(key)
|
|
@@ -28,7 +38,7 @@ class RedisCountLimiter:
|
|
|
28
38
|
remaining = count-cost
|
|
29
39
|
RedisHelper(self.redis_key).sync_set(key,remaining,time_window)
|
|
30
40
|
else:
|
|
31
|
-
#
|
|
41
|
+
# 键存在,每次减少相应数量
|
|
32
42
|
remaining = RedisHelper(self.redis_key).sync_incrby(name=key,amount=0- cost)
|
|
33
43
|
if remaining < 0:
|
|
34
44
|
return False, remaining
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: lesscode-flask
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: lesscode-flask 是基于flask的web开发脚手架项目,该项目初衷为简化开发过程,让研发人员更加关注业务。
|
|
5
5
|
Home-page: https://lesscode-flask
|
|
6
6
|
Author: Chao.yy
|
|
@@ -26,16 +26,6 @@ Requires-Dist: gevent==25.5.1
|
|
|
26
26
|
Requires-Dist: openpyxl==3.1.5
|
|
27
27
|
Requires-Dist: iputil==0.3.1
|
|
28
28
|
Requires-Dist: phone==0.4.5
|
|
29
|
-
Dynamic: author
|
|
30
|
-
Dynamic: author-email
|
|
31
|
-
Dynamic: classifier
|
|
32
|
-
Dynamic: description
|
|
33
|
-
Dynamic: description-content-type
|
|
34
|
-
Dynamic: home-page
|
|
35
|
-
Dynamic: platform
|
|
36
|
-
Dynamic: requires-dist
|
|
37
|
-
Dynamic: requires-python
|
|
38
|
-
Dynamic: summary
|
|
39
29
|
|
|
40
30
|
# lesscode-flask
|
|
41
31
|
|
|
@@ -21,6 +21,7 @@ lesscode_flask/model/parameterized_query.py
|
|
|
21
21
|
lesscode_flask/model/resource_param_template.py
|
|
22
22
|
lesscode_flask/model/response_result.py
|
|
23
23
|
lesscode_flask/model/user.py
|
|
24
|
+
lesscode_flask/model/user_limit_policy.py
|
|
24
25
|
lesscode_flask/service/access_log_service.py
|
|
25
26
|
lesscode_flask/service/base_service.py
|
|
26
27
|
lesscode_flask/service/resource_param_template_service.py
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import logging
|
|
3
|
-
from lesscode_flask.utils.redis.redis_helper import RedisHelper
|
|
4
|
-
|
|
5
|
-
logger = logging.getLogger(__name__)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class RedisRateLimiter:
|
|
9
|
-
"""
|
|
10
|
-
Redis限流实现
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(self, redis_key):
|
|
14
|
-
self.redis_key = redis_key
|
|
15
|
-
|
|
16
|
-
def is_limited(self, key: str, rate: float, burst: float,time_window:int=1) -> tuple:
|
|
17
|
-
"""
|
|
18
|
-
检查是否触发限流
|
|
19
|
-
:param key: 限流键
|
|
20
|
-
:param rate: 每秒请求速率
|
|
21
|
-
:param burst: 突发请求数
|
|
22
|
-
:param time_window: 窗口期
|
|
23
|
-
:return: (allowed,delay, excess) 是否触发限流 延迟时间和超出数量
|
|
24
|
-
"""
|
|
25
|
-
if not key: # 没有key表示不验证
|
|
26
|
-
return True, 0,0
|
|
27
|
-
# 构造Redis键
|
|
28
|
-
key = f"limit_req:{key}"
|
|
29
|
-
# 存储键的键
|
|
30
|
-
excess_key = f"{key}excess"
|
|
31
|
-
# 存储最近一次请求时间毫秒的键
|
|
32
|
-
last_key = f"{key}last"
|
|
33
|
-
# 当前时间的毫秒表示形式
|
|
34
|
-
now = time.time() * 1000 # 转换为毫秒
|
|
35
|
-
|
|
36
|
-
try:
|
|
37
|
-
# 获取上次记录的信息
|
|
38
|
-
excess = RedisHelper(self.redis_key).sync_get(excess_key)
|
|
39
|
-
last =RedisHelper(self.redis_key).sync_get(last_key)
|
|
40
|
-
|
|
41
|
-
if excess is not None and last is not None:
|
|
42
|
-
excess = float(excess)
|
|
43
|
-
# 获取最近一次请求的时间毫秒值
|
|
44
|
-
last = float(last)
|
|
45
|
-
# 计算当前与最近一次访问的时间差
|
|
46
|
-
elapsed = now - last
|
|
47
|
-
# 根据时间差计算当前的excess值
|
|
48
|
-
excess = max(excess - (rate * abs(elapsed) / 1000*time_window) + 1, 0)
|
|
49
|
-
else:
|
|
50
|
-
excess = 0
|
|
51
|
-
# 返回延迟时间和超出数量
|
|
52
|
-
delay = excess / rate
|
|
53
|
-
# 检查是否超出限制
|
|
54
|
-
if excess > burst+rate:
|
|
55
|
-
return False, delay, excess # 被拒绝
|
|
56
|
-
# 更新Redis中的值
|
|
57
|
-
RedisHelper(self.redis_key).sync_set(excess_key, excess)
|
|
58
|
-
RedisHelper(self.redis_key).sync_set(last_key, now)
|
|
59
|
-
return True,delay, excess
|
|
60
|
-
|
|
61
|
-
except Exception as e:
|
|
62
|
-
logger.error(f"Redis rate limiter error: {e}")
|
|
63
|
-
return True, 0,0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/export_data/data_download_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/model/resource_param_template.py
RENAMED
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/service/access_log_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/decorator/sql_injection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/json/NotSortJSONProvider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/swagger/swagger_template.py
RENAMED
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask/utils/swagger/swagger_util.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.12 → lesscode_flask-0.2.14}/lesscode_flask.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|