rainycode 1.2.0__tar.gz → 1.4.0__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.
- {rainycode-1.2.0 → rainycode-1.4.0}/PKG-INFO +5 -3
- {rainycode-1.2.0 → rainycode-1.4.0}/common_base/aiorequests.py +3 -3
- {rainycode-1.2.0 → rainycode-1.4.0}/common_base/exception.py +2 -1
- {rainycode-1.2.0 → rainycode-1.4.0}/common_base/logging.py +2 -2
- {rainycode-1.2.0 → rainycode-1.4.0}/common_depends/auth_depend.py +7 -3
- rainycode-1.4.0/common_models/base_model.py +268 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/common_models/user_model.py +1 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/common_models/wechat_model.py +4 -1
- {rainycode-1.2.0 → rainycode-1.4.0}/common_servers/api/auth_api.py +2 -2
- {rainycode-1.2.0 → rainycode-1.4.0}/common_servers/api/wechat_api.py +3 -2
- {rainycode-1.2.0 → rainycode-1.4.0}/common_servers/router.py +1 -2
- {rainycode-1.2.0 → rainycode-1.4.0}/common_servers/schemas/auth_schema.py +2 -1
- {rainycode-1.2.0 → rainycode-1.4.0}/common_servers/service/auth_svc.py +32 -28
- {rainycode-1.2.0 → rainycode-1.4.0}/common_servers/service/wechat_svc.py +2 -1
- {rainycode-1.2.0 → rainycode-1.4.0}/common_utils/bcrypt_util.py +0 -1
- {rainycode-1.2.0 → rainycode-1.4.0}/common_utils/captcha_util.py +1 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/common_utils/ip_util.py +1 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/common_utils/jwt_util.py +3 -1
- {rainycode-1.2.0 → rainycode-1.4.0}/common_utils/snowflake_util.py +2 -2
- {rainycode-1.2.0 → rainycode-1.4.0}/common_utils/wechat_util.py +5 -3
- {rainycode-1.2.0 → rainycode-1.4.0}/core/databases/aiodb.py +56 -34
- {rainycode-1.2.0 → rainycode-1.4.0}/core/databases/aioredis.py +16 -15
- {rainycode-1.2.0 → rainycode-1.4.0}/core/middleware/http_middleware.py +4 -2
- {rainycode-1.2.0 → rainycode-1.4.0}/core/start.py +16 -6
- {rainycode-1.2.0 → rainycode-1.4.0}/pyproject.toml +9 -7
- {rainycode-1.2.0 → rainycode-1.4.0}/rainycode.egg-info/PKG-INFO +5 -3
- {rainycode-1.2.0 → rainycode-1.4.0}/rainycode.egg-info/SOURCES.txt +0 -1
- {rainycode-1.2.0 → rainycode-1.4.0}/rainycode.egg-info/requires.txt +4 -1
- rainycode-1.2.0/common_models/base_model.py +0 -200
- rainycode-1.2.0/common_servers/schemas/__init__.py +0 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/common_base/consts.py +0 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/common_base/response.py +0 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/common_models/__init__.py +0 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/core/base_config.py +0 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/rainycode.egg-info/dependency_links.txt +0 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/rainycode.egg-info/top_level.txt +0 -0
- {rainycode-1.2.0 → rainycode-1.4.0}/setup.cfg +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rainycode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: FastAPI base modules
|
|
5
|
-
Requires-Python: >=3.
|
|
6
|
-
Requires-Dist: build==1.4.0
|
|
5
|
+
Requires-Python: >=3.10
|
|
7
6
|
Requires-Dist: aerich==0.9.2
|
|
8
7
|
Requires-Dist: fastapi==0.133.1
|
|
9
8
|
Requires-Dist: aiohttp==3.13.3
|
|
@@ -18,3 +17,6 @@ Requires-Dist: pycryptodome==3.23.0
|
|
|
18
17
|
Requires-Dist: uvicorn[standard]==0.41.0
|
|
19
18
|
Requires-Dist: tortoise-orm[asyncmy]==0.25.4
|
|
20
19
|
Requires-Dist: pydantic-settings==2.13.1
|
|
20
|
+
Provides-Extra: release
|
|
21
|
+
Requires-Dist: build==1.4.0; extra == "release"
|
|
22
|
+
Requires-Dist: twine==6.1.0; extra == "release"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import ssl
|
|
3
|
-
import certifi
|
|
4
|
-
import aiohttp
|
|
5
3
|
|
|
4
|
+
import aiohttp
|
|
5
|
+
import certifi
|
|
6
6
|
|
|
7
7
|
# 模块级别 SSL 上下文,使用 certifi 证书,避免重复创建
|
|
8
8
|
_SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
|
|
@@ -10,7 +10,7 @@ _SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
|
|
|
10
10
|
|
|
11
11
|
def parse_url(url, params=None, suffix=None):
|
|
12
12
|
if params:
|
|
13
|
-
query = "&".join(["{}={
|
|
13
|
+
query = "&".join([f"{k}={params[k]}" for k in sorted(params.keys())])
|
|
14
14
|
if "?" in url:
|
|
15
15
|
url += "&" + query
|
|
16
16
|
else:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
from common_base.logging import logger
|
|
2
1
|
from typing import cast
|
|
2
|
+
|
|
3
|
+
from common_base.logging import logger
|
|
3
4
|
from common_base.response import ErrorReturn
|
|
4
5
|
from fastapi import HTTPException, Request, status
|
|
5
6
|
from fastapi.exceptions import RequestValidationError, ResponseValidationError
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
import sys
|
|
3
|
-
import logging
|
|
4
4
|
import traceback
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def logging_config(app_name: str):
|
|
8
8
|
# 日志目录
|
|
9
|
-
log_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../
|
|
9
|
+
log_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../logs"))
|
|
10
10
|
if not os.path.exists(log_dir):
|
|
11
11
|
os.mkdir(log_dir)
|
|
12
12
|
file_path = log_dir + '/' + app_name + '.log'
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
from common_base.consts import (
|
|
4
|
+
REDIS_PREFIX_BLOCKLIST,
|
|
5
|
+
REDIS_PREFIX_USER_INFO,
|
|
6
|
+
)
|
|
5
7
|
from common_base.exception import CustomException
|
|
6
8
|
from common_models.user_model import User
|
|
7
9
|
from common_utils.jwt_util import JwtUtil
|
|
8
10
|
from core.databases.aioredis import AioRedis
|
|
11
|
+
from fastapi import Depends, Request
|
|
12
|
+
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
|
|
9
13
|
|
|
10
14
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/auth_center/login")
|
|
11
15
|
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from common_utils.snowflake_util import get_snowflake_id
|
|
6
|
+
from core.databases.aiodb import MySQLUtils
|
|
7
|
+
from tortoise import BaseDBAsyncClient, Model, fields
|
|
8
|
+
from tortoise.expressions import Q
|
|
9
|
+
from tortoise.queryset import BulkCreateQuery, QuerySet, QuerySetSingle
|
|
10
|
+
from typing_extensions import Self
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseModel(MySQLUtils):
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
async def create(
|
|
17
|
+
cls,
|
|
18
|
+
using_db: BaseDBAsyncClient | None = None,
|
|
19
|
+
**kwargs: Any
|
|
20
|
+
) -> Self:
|
|
21
|
+
if not kwargs.get('id', 0) and 'id' in cls._meta.fields:
|
|
22
|
+
# uuid生成id
|
|
23
|
+
if kwargs.get('id_uuid', False):
|
|
24
|
+
kwargs['id'] = str(uuid.uuid4()).replace('-', '')
|
|
25
|
+
# 雪花算法生成id
|
|
26
|
+
if kwargs.get('id_snowflake', False):
|
|
27
|
+
kwargs['id'] = get_snowflake_id()
|
|
28
|
+
return await super().create(using_db=using_db, **kwargs)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get(
|
|
32
|
+
cls,
|
|
33
|
+
*args: Q,
|
|
34
|
+
using_db: BaseDBAsyncClient | None = None,
|
|
35
|
+
**kwargs: Any
|
|
36
|
+
) -> QuerySetSingle[Self]:
|
|
37
|
+
if 'deleted' in cls._meta.fields:
|
|
38
|
+
deleted = kwargs.get('deleted')
|
|
39
|
+
if deleted is None:
|
|
40
|
+
# 默认只查未删除的记录
|
|
41
|
+
kwargs['deleted'] = 0
|
|
42
|
+
elif deleted == -1:
|
|
43
|
+
# -1 表示不过滤 deleted 字段,未删除(0)和已删除(1)的记录都可查
|
|
44
|
+
kwargs.pop('deleted', None)
|
|
45
|
+
# 其余值(0/1)原样透传
|
|
46
|
+
return super().get(*args, using_db=using_db, **kwargs)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def filter(
|
|
50
|
+
cls,
|
|
51
|
+
*args: Q,
|
|
52
|
+
**kwargs: Any
|
|
53
|
+
) -> QuerySet[Self]:
|
|
54
|
+
if 'deleted' in cls._meta.fields:
|
|
55
|
+
deleted = kwargs.get('deleted')
|
|
56
|
+
if deleted is None:
|
|
57
|
+
# 默认只查未删除的记录
|
|
58
|
+
kwargs['deleted'] = 0
|
|
59
|
+
elif deleted == -1:
|
|
60
|
+
# -1 表示不过滤 deleted 字段,未删除(0)和已删除(1)的记录都可查
|
|
61
|
+
kwargs.pop('deleted', None)
|
|
62
|
+
# 其余值(0/1)原样透传
|
|
63
|
+
return cls._meta.manager.get_queryset().filter(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def bulk_create(
|
|
67
|
+
cls,
|
|
68
|
+
objects: Iterable[Self],
|
|
69
|
+
batch_size: int | None = None,
|
|
70
|
+
ignore_conflicts: bool = False,
|
|
71
|
+
update_fields: Iterable[str] | None = None,
|
|
72
|
+
on_conflict: Iterable[str] | None = None,
|
|
73
|
+
using_db: BaseDBAsyncClient | None = None,
|
|
74
|
+
**kwargs: Any
|
|
75
|
+
) -> "BulkCreateQuery[Self]":
|
|
76
|
+
if 'id' in cls._meta.fields:
|
|
77
|
+
for obj in objects:
|
|
78
|
+
if not getattr(obj, 'id', 0):
|
|
79
|
+
# uuid生成id
|
|
80
|
+
if kwargs.get('id_uuid', False):
|
|
81
|
+
obj.id = str(uuid.uuid4()).replace('-', '')
|
|
82
|
+
# 雪花算法生成id
|
|
83
|
+
if kwargs.get('id_snowflake', False):
|
|
84
|
+
obj.id = get_snowflake_id()
|
|
85
|
+
|
|
86
|
+
params = {
|
|
87
|
+
"objects": objects,
|
|
88
|
+
"batch_size": batch_size,
|
|
89
|
+
"ignore_conflicts": ignore_conflicts,
|
|
90
|
+
"update_fields": update_fields,
|
|
91
|
+
"on_conflict": on_conflict,
|
|
92
|
+
"using_db": using_db,
|
|
93
|
+
}
|
|
94
|
+
return super().bulk_create(**params)
|
|
95
|
+
|
|
96
|
+
async def save(
|
|
97
|
+
self,
|
|
98
|
+
using_db: BaseDBAsyncClient | None = None,
|
|
99
|
+
update_fields: Iterable[str] | None = None,
|
|
100
|
+
force_create: bool = False,
|
|
101
|
+
force_update: bool = False,
|
|
102
|
+
**kwargs: Any
|
|
103
|
+
) -> None:
|
|
104
|
+
if not getattr(self, 'id', 0) and kwargs.get('id_uuid', False) and 'id' in self._meta.fields:
|
|
105
|
+
setattr(self, 'id', str(uuid.uuid4()).replace('-', ''))
|
|
106
|
+
return await super().save(using_db=using_db, update_fields=update_fields, force_create=force_create,
|
|
107
|
+
force_update=force_update)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TimeModel(BaseModel):
|
|
111
|
+
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
|
112
|
+
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
113
|
+
|
|
114
|
+
class Meta:
|
|
115
|
+
abstract = True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# class CreateModel(BaseModel):
|
|
119
|
+
# create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
120
|
+
# creator = fields.CharField(max_length=50, null=True, description="创建人")
|
|
121
|
+
|
|
122
|
+
# class Meta:
|
|
123
|
+
# abstract = True
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# class UpdateModel(BaseModel):
|
|
127
|
+
# update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
|
128
|
+
# create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
129
|
+
# updater = fields.CharField(max_length=50, null=True, description="更新人")
|
|
130
|
+
# creator = fields.CharField(max_length=50, null=True, description="创建人")
|
|
131
|
+
|
|
132
|
+
# class Meta:
|
|
133
|
+
# abstract = True
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# class DeleteModel(BaseModel):
|
|
137
|
+
# deleted = fields.IntField(default=0, description="是否已删除, 0:未删除, 1:已删除")
|
|
138
|
+
# create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
|
139
|
+
# update_time = fields.DatetimeField(auto_now=True, description="更新时间")
|
|
140
|
+
# creator = fields.CharField(max_length=50, null=True, description="创建人")
|
|
141
|
+
# updater = fields.CharField(max_length=50, null=True, description="更新人")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class CreateRelatedMeta(type(Model)):
|
|
145
|
+
"""元类:更新外键字段,解决用户related_name重复问题"""
|
|
146
|
+
def __new__(cls, name: str, bases: tuple, attrs: dict):
|
|
147
|
+
if name == 'CreateModel':
|
|
148
|
+
return super().__new__(cls, name, bases, attrs)
|
|
149
|
+
|
|
150
|
+
creator_related_name = f"{name.lower()}_creator"
|
|
151
|
+
attrs["creator"] = fields.ForeignKeyField(
|
|
152
|
+
'main.User',
|
|
153
|
+
null=True,
|
|
154
|
+
related_name=creator_related_name,
|
|
155
|
+
db_constraint=False,
|
|
156
|
+
description="创建人"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
new_class = super().__new__(cls, name, bases, attrs)
|
|
160
|
+
return new_class
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class CreateModel(BaseModel, metaclass=CreateRelatedMeta):
|
|
164
|
+
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
165
|
+
creator = fields.ForeignKeyField(
|
|
166
|
+
'main.User',
|
|
167
|
+
null=True,
|
|
168
|
+
db_constraint=False,
|
|
169
|
+
description="创建人"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
class Meta:
|
|
173
|
+
abstract = True
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class UpdateRelatedMeta(type(Model)):
|
|
177
|
+
"""元类:更新外键字段,解决用户related_name重复问题"""
|
|
178
|
+
def __new__(cls, name: str, bases: tuple, attrs: dict):
|
|
179
|
+
if name == 'UpdateModel':
|
|
180
|
+
return super().__new__(cls, name, bases, attrs)
|
|
181
|
+
|
|
182
|
+
creator_related_name = f"{name.lower()}_creator"
|
|
183
|
+
attrs["creator"] = fields.ForeignKeyField(
|
|
184
|
+
'main.User',
|
|
185
|
+
null=True,
|
|
186
|
+
related_name=creator_related_name,
|
|
187
|
+
db_constraint=False,
|
|
188
|
+
description="创建人"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
updater_related_name = f"{name.lower()}_updater"
|
|
192
|
+
attrs["updater"] = fields.ForeignKeyField(
|
|
193
|
+
'main.User',
|
|
194
|
+
null=True,
|
|
195
|
+
related_name=updater_related_name,
|
|
196
|
+
db_constraint=False,
|
|
197
|
+
description="更新人"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
new_class = super().__new__(cls, name, bases, attrs)
|
|
201
|
+
return new_class
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class UpdateModel(BaseModel, metaclass=UpdateRelatedMeta):
|
|
205
|
+
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
|
206
|
+
updater = fields.ForeignKeyField(
|
|
207
|
+
'main.User',
|
|
208
|
+
null=True,
|
|
209
|
+
db_constraint=False,
|
|
210
|
+
description="更新人"
|
|
211
|
+
)
|
|
212
|
+
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
213
|
+
creator = fields.ForeignKeyField(
|
|
214
|
+
'main.User',
|
|
215
|
+
null=True,
|
|
216
|
+
db_constraint=False,
|
|
217
|
+
description="创建人"
|
|
218
|
+
)
|
|
219
|
+
class Meta:
|
|
220
|
+
abstract = True
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class DeleteRelatedMeta(type(Model)):
|
|
224
|
+
"""元类:更新外键字段,解决用户related_name重复问题"""
|
|
225
|
+
def __new__(cls, name: str, bases: tuple, attrs: dict):
|
|
226
|
+
if name == 'DeleteModel':
|
|
227
|
+
return super().__new__(cls, name, bases, attrs)
|
|
228
|
+
|
|
229
|
+
creator_related_name = f"{name.lower()}_creator"
|
|
230
|
+
attrs["creator"] = fields.ForeignKeyField(
|
|
231
|
+
'main.User',
|
|
232
|
+
null=True,
|
|
233
|
+
related_name=creator_related_name,
|
|
234
|
+
db_constraint=False,
|
|
235
|
+
description="创建人"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
updater_related_name = f"{name.lower()}_updater"
|
|
239
|
+
attrs["updater"] = fields.ForeignKeyField(
|
|
240
|
+
'main.User',
|
|
241
|
+
null=True,
|
|
242
|
+
related_name=updater_related_name,
|
|
243
|
+
db_constraint=False,
|
|
244
|
+
description="更新人"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
new_class = super().__new__(cls, name, bases, attrs)
|
|
248
|
+
return new_class
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class DeleteModel(BaseModel, metaclass=DeleteRelatedMeta):
|
|
252
|
+
deleted = fields.IntField(default=0, description="是否已删除, 0:未删除, 1:已删除")
|
|
253
|
+
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
|
254
|
+
updater = fields.ForeignKeyField(
|
|
255
|
+
'main.User',
|
|
256
|
+
null=True,
|
|
257
|
+
db_constraint=False,
|
|
258
|
+
description="更新人"
|
|
259
|
+
)
|
|
260
|
+
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
261
|
+
creator = fields.ForeignKeyField(
|
|
262
|
+
'main.User',
|
|
263
|
+
null=True,
|
|
264
|
+
db_constraint=False,
|
|
265
|
+
description="创建人"
|
|
266
|
+
)
|
|
267
|
+
class Meta:
|
|
268
|
+
abstract = True
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from common_models.user_model import User
|
|
2
|
-
from fastapi import APIRouter, Security, Body, Query, Request
|
|
3
1
|
from common_base.response import SuccessReturn
|
|
4
2
|
from common_depends.auth_depend import login_require
|
|
3
|
+
from common_models.user_model import User
|
|
5
4
|
from common_servers.schemas.auth_schema import *
|
|
6
5
|
from common_servers.service.auth_svc import AuthService
|
|
6
|
+
from fastapi import APIRouter, Body, Query, Request, Security
|
|
7
7
|
|
|
8
8
|
router = APIRouter(prefix="/auth")
|
|
9
9
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from urllib.parse import urlencode
|
|
3
|
-
|
|
4
|
-
from fastapi.responses import RedirectResponse
|
|
3
|
+
|
|
5
4
|
from common_base.consts import REDIS_PREFIX_OAUTH_STATE
|
|
6
5
|
from common_base.exception import CustomException
|
|
7
6
|
from common_servers.service.auth_svc import AuthService
|
|
8
7
|
from common_servers.service.wechat_svc import WechatService
|
|
9
8
|
from core.databases.aioredis import AioRedis
|
|
9
|
+
from fastapi import APIRouter, Query, Request, Response
|
|
10
|
+
from fastapi.responses import RedirectResponse
|
|
10
11
|
|
|
11
12
|
router = APIRouter(prefix="/wechat")
|
|
12
13
|
|
|
@@ -1,32 +1,36 @@
|
|
|
1
|
-
from datetime import datetime, timedelta
|
|
2
|
-
import uuid
|
|
3
1
|
import json
|
|
4
2
|
import re
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
5
|
from urllib.parse import urlencode
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
from common_base.consts import (
|
|
8
8
|
REDIS_PREFIX_BLOCKLIST,
|
|
9
|
-
|
|
9
|
+
REDIS_PREFIX_LOGIN_LOCK_IP,
|
|
10
|
+
REDIS_PREFIX_LOGIN_LOCK_USER,
|
|
10
11
|
REDIS_PREFIX_OAUTH_STATE,
|
|
12
|
+
REDIS_PREFIX_REFRESH_TOKEN,
|
|
11
13
|
REDIS_PREFIX_USER_INFO,
|
|
12
14
|
REDIS_PREFIX_WECHAT_CODE,
|
|
13
15
|
REDIS_PREFIX_WECHAT_OAUTH_CODE,
|
|
14
|
-
REDIS_PREFIX_LOGIN_LOCK_USER,
|
|
15
|
-
REDIS_PREFIX_LOGIN_LOCK_IP,
|
|
16
16
|
)
|
|
17
17
|
from common_base.exception import CustomException
|
|
18
18
|
from common_base.logging import logger
|
|
19
19
|
from common_models.user_model import User
|
|
20
|
-
from common_models.wechat_model import WechatApp, WechatUser
|
|
20
|
+
from common_models.wechat_model import AppTypeEnum, WechatApp, WechatUser
|
|
21
|
+
from common_servers.schemas.auth_schema import (
|
|
22
|
+
UserLogin,
|
|
23
|
+
UserRegister,
|
|
24
|
+
WechatLogin,
|
|
25
|
+
)
|
|
21
26
|
from common_utils.bcrypt_util import PwdUtil
|
|
22
27
|
from common_utils.jwt_util import JwtUtil
|
|
23
28
|
from common_utils.wechat_util import WechatUtil
|
|
29
|
+
from core.base_config import base_config
|
|
24
30
|
from core.databases.aioredis import AioRedis
|
|
25
|
-
from
|
|
26
|
-
from tortoise.transactions import atomic
|
|
31
|
+
from fastapi.responses import RedirectResponse
|
|
27
32
|
from tortoise.expressions import Q
|
|
28
|
-
from
|
|
29
|
-
|
|
33
|
+
from tortoise.transactions import atomic
|
|
30
34
|
|
|
31
35
|
# 登录限流配置
|
|
32
36
|
LOGIN_FAIL_LIMIT = 5 # 最大失败次数
|
|
@@ -219,14 +223,14 @@ class AuthService:
|
|
|
219
223
|
raise CustomException(msg="授权码已失效,请重新授权")
|
|
220
224
|
|
|
221
225
|
# 获取公众号配置
|
|
222
|
-
|
|
223
|
-
if not
|
|
226
|
+
wechat_app = await WechatApp.get_or_none(id=app_pk, app_type=AppTypeEnum.OFFICIAL_ACCOUNT)
|
|
227
|
+
if not wechat_app:
|
|
224
228
|
raise CustomException(msg="公众号配置不存在")
|
|
225
229
|
|
|
226
230
|
# 用 code 换取 access_token 和 openid
|
|
227
231
|
wx_res = await WechatUtil.get_oauth_access_token(
|
|
228
|
-
|
|
229
|
-
|
|
232
|
+
wechat_app.app_id,
|
|
233
|
+
wechat_app.app_secret,
|
|
230
234
|
code
|
|
231
235
|
)
|
|
232
236
|
openid = wx_res.get("openid")
|
|
@@ -239,7 +243,7 @@ class AuthService:
|
|
|
239
243
|
await AioRedis.set(code_key, "1", ex=300)
|
|
240
244
|
|
|
241
245
|
# 检查用户是否已存在
|
|
242
|
-
wechat_user = await WechatUser.get_or_none(openid=openid, wechat_app=
|
|
246
|
+
wechat_user = await WechatUser.get_or_none(openid=openid, wechat_app=wechat_app).select_related("user")
|
|
243
247
|
|
|
244
248
|
if wechat_user:
|
|
245
249
|
# 用户已存在,直接登录
|
|
@@ -257,7 +261,7 @@ class AuthService:
|
|
|
257
261
|
# 使用 _bind_or_register_wechat_user 创建或绑定用户
|
|
258
262
|
user = await cls._bind_or_register_wechat_user(
|
|
259
263
|
openid=openid,
|
|
260
|
-
|
|
264
|
+
wechat_app=wechat_app,
|
|
261
265
|
unionid=wx_res.get("unionid"),
|
|
262
266
|
nickname=user_info.get("nickname"),
|
|
263
267
|
avatar=user_info.get("headimgurl")
|
|
@@ -275,7 +279,7 @@ class AuthService:
|
|
|
275
279
|
api_prefix = f"/api/{base_config.app_name}" if base_config.app_name else ""
|
|
276
280
|
callback_url = f"{base_config.base_url}{api_prefix}/wechat/oauth/callback"
|
|
277
281
|
userinfo_auth_url = WechatUtil.build_oauth_url(
|
|
278
|
-
appid=
|
|
282
|
+
appid=wechat_app.app_id,
|
|
279
283
|
redirect_uri=callback_url,
|
|
280
284
|
scope="snsapi_userinfo",
|
|
281
285
|
state=new_state
|
|
@@ -321,10 +325,10 @@ class AuthService:
|
|
|
321
325
|
async def _authenticate_wechat_mini(cls, data: WechatLogin) -> User:
|
|
322
326
|
"""微信小程序认证"""
|
|
323
327
|
# 1. 获取小程序配置
|
|
324
|
-
|
|
328
|
+
wechat_app = await WechatApp.get_or_none(
|
|
325
329
|
app_id=data.app_id, app_type=AppTypeEnum.MINI_PROGRAM
|
|
326
330
|
)
|
|
327
|
-
if not
|
|
331
|
+
if not wechat_app:
|
|
328
332
|
raise CustomException(msg="无效的小程序配置")
|
|
329
333
|
|
|
330
334
|
# 2. 检查 code 是否已使用(防重放)
|
|
@@ -334,7 +338,7 @@ class AuthService:
|
|
|
334
338
|
|
|
335
339
|
# 3. code 换取 session_key 和 openid
|
|
336
340
|
wx_res = await WechatUtil.code2session(
|
|
337
|
-
|
|
341
|
+
wechat_app.app_id, wechat_app.app_secret, data.code
|
|
338
342
|
)
|
|
339
343
|
openid = wx_res.get("openid")
|
|
340
344
|
unionid = wx_res.get("unionid")
|
|
@@ -350,7 +354,7 @@ class AuthService:
|
|
|
350
354
|
phone = None
|
|
351
355
|
if data.encrypted_data and data.iv:
|
|
352
356
|
phone_info = WechatUtil.decrypt_data(
|
|
353
|
-
session_key, data.encrypted_data, data.iv,
|
|
357
|
+
session_key, data.encrypted_data, data.iv, wechat_app.app_id
|
|
354
358
|
)
|
|
355
359
|
phone = phone_info.get("phoneNumber")
|
|
356
360
|
# 校验手机号格式
|
|
@@ -360,7 +364,7 @@ class AuthService:
|
|
|
360
364
|
# 6. 使用 _bind_or_register_wechat_user 处理用户绑定/注册
|
|
361
365
|
return await cls._bind_or_register_wechat_user(
|
|
362
366
|
openid=openid,
|
|
363
|
-
|
|
367
|
+
wechat_app=wechat_app,
|
|
364
368
|
unionid=unionid,
|
|
365
369
|
phone=phone,
|
|
366
370
|
session_key=session_key,
|
|
@@ -430,7 +434,7 @@ class AuthService:
|
|
|
430
434
|
async def _bind_or_register_wechat_user(
|
|
431
435
|
cls,
|
|
432
436
|
openid: str,
|
|
433
|
-
|
|
437
|
+
wechat_app: WechatApp,
|
|
434
438
|
unionid: str | None = None,
|
|
435
439
|
phone: str | None = None,
|
|
436
440
|
nickname: str | None = None,
|
|
@@ -442,7 +446,7 @@ class AuthService:
|
|
|
442
446
|
|
|
443
447
|
Args:
|
|
444
448
|
openid: 微信 OpenID
|
|
445
|
-
|
|
449
|
+
wechat_app: 微信应用配置
|
|
446
450
|
unionid: 微信 UnionID(可选,用于跨应用关联)
|
|
447
451
|
phone: 手机号(可选,小程序一键登录获取)
|
|
448
452
|
nickname: 微信昵称
|
|
@@ -454,7 +458,7 @@ class AuthService:
|
|
|
454
458
|
"""
|
|
455
459
|
# 1. 检查 WechatUser 是否已存在
|
|
456
460
|
wechat_user = await WechatUser.get_or_none(
|
|
457
|
-
openid=openid, wechat_app=
|
|
461
|
+
openid=openid, wechat_app=wechat_app
|
|
458
462
|
).select_related("user")
|
|
459
463
|
|
|
460
464
|
if wechat_user:
|
|
@@ -480,7 +484,7 @@ class AuthService:
|
|
|
480
484
|
if not user and unionid:
|
|
481
485
|
other_wechat_user = (
|
|
482
486
|
await WechatUser.filter(unionid=unionid)
|
|
483
|
-
.exclude(wechat_app=
|
|
487
|
+
.exclude(wechat_app=wechat_app)
|
|
484
488
|
.first()
|
|
485
489
|
.select_related("user")
|
|
486
490
|
)
|
|
@@ -502,7 +506,7 @@ class AuthService:
|
|
|
502
506
|
# 4. 创建 WechatUser 关联
|
|
503
507
|
await WechatUser.create(
|
|
504
508
|
user=user,
|
|
505
|
-
wechat_app=
|
|
509
|
+
wechat_app=wechat_app,
|
|
506
510
|
openid=openid,
|
|
507
511
|
unionid=unionid,
|
|
508
512
|
session_key=session_key,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
1
5
|
from common_base import aiorequests
|
|
2
6
|
from common_base.exception import CustomException
|
|
3
7
|
from common_base.logging import logger
|
|
4
8
|
from Crypto.Cipher import AES
|
|
5
|
-
|
|
6
|
-
import base64
|
|
7
|
-
import json
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
class WechatUtil:
|
|
10
12
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from core.base_config import base_config
|
|
3
6
|
from fastapi import FastAPI
|
|
4
7
|
from fastapi.encoders import jsonable_encoder
|
|
5
8
|
from tortoise import Tortoise
|
|
6
|
-
from tortoise.models import Model
|
|
7
9
|
from tortoise.contrib.fastapi import RegisterTortoise
|
|
8
|
-
from tortoise.
|
|
9
|
-
from
|
|
10
|
-
from core.base_config import base_config
|
|
10
|
+
from tortoise.models import Model
|
|
11
|
+
from tortoise.queryset import QuerySet, QuerySetSingle
|
|
11
12
|
|
|
12
13
|
db_models = [
|
|
13
14
|
'aerich.models',
|
|
@@ -92,8 +93,8 @@ class MySQLUtils(Model):
|
|
|
92
93
|
@staticmethod
|
|
93
94
|
def json_encoder(
|
|
94
95
|
obj: Any,
|
|
95
|
-
include:
|
|
96
|
-
exclude:
|
|
96
|
+
include: list[str] | None = None,
|
|
97
|
+
exclude: list[str] | None = None,
|
|
97
98
|
**kwargs
|
|
98
99
|
):
|
|
99
100
|
return jsonable_encoder(
|
|
@@ -109,13 +110,13 @@ class MySQLUtils(Model):
|
|
|
109
110
|
async def model2dict(
|
|
110
111
|
cls,
|
|
111
112
|
model: Model,
|
|
112
|
-
include:
|
|
113
|
-
exclude:
|
|
114
|
-
alias:
|
|
115
|
-
fetch_related:
|
|
113
|
+
include: list[str] | None = None,
|
|
114
|
+
exclude: list[str] | None = None,
|
|
115
|
+
alias: list[tuple[str, str]] | None = None,
|
|
116
|
+
fetch_related: list[str] | None = None,
|
|
116
117
|
flat: bool = False, # 扁平化
|
|
117
118
|
**kwargs
|
|
118
|
-
) ->
|
|
119
|
+
) -> dict[str, Any]:
|
|
119
120
|
meta = getattr(cls, 'PydanticMeta', getattr(cls, 'Meta', None))
|
|
120
121
|
if meta:
|
|
121
122
|
_include = getattr(meta, 'include', None)
|
|
@@ -224,17 +225,24 @@ class MySQLUtils(Model):
|
|
|
224
225
|
async def get_data(
|
|
225
226
|
cls,
|
|
226
227
|
*args,
|
|
227
|
-
include:
|
|
228
|
-
exclude:
|
|
229
|
-
alias:
|
|
230
|
-
fetch_related:
|
|
228
|
+
include: list[str] | None = None, # 包含哪些字段,默认全部
|
|
229
|
+
exclude: list[str] | None = None, # 排除哪些字段,默认全部
|
|
230
|
+
alias: list[tuple[str, str]] | None = None, # 字段别名,如[('id', 'uid')]
|
|
231
|
+
fetch_related: list[str] | None = None, # 对应关系表
|
|
231
232
|
flat: bool = False, # 扁平化
|
|
232
233
|
deleted: bool | None = None,
|
|
233
234
|
queryset_model: QuerySetSingle | Model | None = None,
|
|
234
235
|
**kwargs
|
|
235
|
-
) ->
|
|
236
|
-
if 'deleted' in cls._meta.fields
|
|
237
|
-
kwargs
|
|
236
|
+
) -> dict[str, Any] | None:
|
|
237
|
+
if 'deleted' in cls._meta.fields:
|
|
238
|
+
deleted = kwargs.get('deleted')
|
|
239
|
+
if deleted is None:
|
|
240
|
+
# 默认只查未删除的记录
|
|
241
|
+
kwargs['deleted'] = 0
|
|
242
|
+
elif deleted == -1:
|
|
243
|
+
# -1 表示不过滤 deleted 字段,未删除(0)和已删除(1)的记录都可查
|
|
244
|
+
kwargs.pop('deleted', None)
|
|
245
|
+
# 其余值(0/1)原样透传
|
|
238
246
|
|
|
239
247
|
queryKwargs = {}
|
|
240
248
|
if fetch_related:
|
|
@@ -264,17 +272,24 @@ class MySQLUtils(Model):
|
|
|
264
272
|
async def get_or_none_data(
|
|
265
273
|
cls,
|
|
266
274
|
*args,
|
|
267
|
-
include:
|
|
268
|
-
exclude:
|
|
269
|
-
alias:
|
|
270
|
-
fetch_related:
|
|
275
|
+
include: list[str] | None = None, # 包含哪些字段,默认全部
|
|
276
|
+
exclude: list[str] | None = None, # 排除哪些字段,默认全部
|
|
277
|
+
alias: list[tuple[str, str]] | None = None, # 字段别名,如[('id', 'uid')]
|
|
278
|
+
fetch_related: list[str] | None = None, # 对应关系表
|
|
271
279
|
flat: bool = False, # 扁平化
|
|
272
280
|
deleted: bool | None = None,
|
|
273
281
|
queryset_model: QuerySetSingle | Model | None = None,
|
|
274
282
|
**kwargs
|
|
275
|
-
) ->
|
|
276
|
-
if 'deleted' in cls._meta.fields
|
|
277
|
-
kwargs
|
|
283
|
+
) -> dict[str, Any] | None:
|
|
284
|
+
if 'deleted' in cls._meta.fields:
|
|
285
|
+
deleted = kwargs.get('deleted')
|
|
286
|
+
if deleted is None:
|
|
287
|
+
# 默认只查未删除的记录
|
|
288
|
+
kwargs['deleted'] = 0
|
|
289
|
+
elif deleted == -1:
|
|
290
|
+
# -1 表示不过滤 deleted 字段,未删除(0)和已删除(1)的记录都可查
|
|
291
|
+
kwargs.pop('deleted', None)
|
|
292
|
+
# 其余值(0/1)原样透传
|
|
278
293
|
|
|
279
294
|
queryKwargs = {}
|
|
280
295
|
if fetch_related:
|
|
@@ -307,18 +322,25 @@ class MySQLUtils(Model):
|
|
|
307
322
|
page_no: int | None = 0,
|
|
308
323
|
page_size: int | None = 0,
|
|
309
324
|
nocount: bool = False,
|
|
310
|
-
include:
|
|
311
|
-
exclude:
|
|
312
|
-
alias:
|
|
313
|
-
fetch_related:
|
|
325
|
+
include: list[str] | None = None, # 包含哪些字段,默认全部
|
|
326
|
+
exclude: list[str] | None = None, # 排除哪些字段,默认全部
|
|
327
|
+
alias: list[tuple[str, str]] | None = None, # 字段别名,如[('id', 'uid')]
|
|
328
|
+
fetch_related: list[str] | None = None, # 对应关系表
|
|
314
329
|
flat: bool = False, # 扁平化
|
|
315
330
|
deleted: bool | None = None,
|
|
316
331
|
queryset: QuerySet | None = None,
|
|
317
|
-
order_by:
|
|
332
|
+
order_by: list[str] | None = None,
|
|
318
333
|
**kwargs
|
|
319
|
-
) ->
|
|
320
|
-
if 'deleted' in cls._meta.fields
|
|
321
|
-
kwargs
|
|
334
|
+
) -> dict[str, Any] | list[dict[str, Any]]:
|
|
335
|
+
if 'deleted' in cls._meta.fields:
|
|
336
|
+
deleted = kwargs.get('deleted')
|
|
337
|
+
if deleted is None:
|
|
338
|
+
# 默认只查未删除的记录
|
|
339
|
+
kwargs['deleted'] = 0
|
|
340
|
+
elif deleted == -1:
|
|
341
|
+
# -1 表示不过滤 deleted 字段,未删除(0)和已删除(1)的记录都可查
|
|
342
|
+
kwargs.pop('deleted', None)
|
|
343
|
+
# 其余值(0/1)原样透传
|
|
322
344
|
|
|
323
345
|
queryKwargs = {}
|
|
324
346
|
if fetch_related:
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import time
|
|
2
1
|
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
from collections.abc import Callable, Mapping
|
|
3
4
|
from contextlib import asynccontextmanager
|
|
4
|
-
|
|
5
|
-
from redis.asyncio import Redis
|
|
6
|
-
from redis import asyncio as aioredis
|
|
7
|
-
from redis.typing import KeyT, EncodableT, ExpiryT, ZScoreBoundT, AnyKeyT
|
|
5
|
+
|
|
8
6
|
from common_base.logging import log_exc
|
|
9
7
|
from core.base_config import base_config
|
|
8
|
+
from redis import asyncio as aioredis
|
|
9
|
+
from redis.asyncio import Redis
|
|
10
|
+
from redis.typing import AnyKeyT, EncodableT, ExpiryT, KeyT, ZScoreBoundT
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class AioRedis:
|
|
@@ -63,7 +64,7 @@ class AioRedis:
|
|
|
63
64
|
pass
|
|
64
65
|
|
|
65
66
|
@classmethod
|
|
66
|
-
async def get(cls, name: KeyT) ->
|
|
67
|
+
async def get(cls, name: KeyT) -> str | None:
|
|
67
68
|
redis = await cls.connect()
|
|
68
69
|
result = await redis.get(name)
|
|
69
70
|
return result
|
|
@@ -73,11 +74,11 @@ class AioRedis:
|
|
|
73
74
|
cls,
|
|
74
75
|
name: KeyT,
|
|
75
76
|
value: EncodableT,
|
|
76
|
-
ex:
|
|
77
|
-
px:
|
|
77
|
+
ex: ExpiryT | None = None, # 超时秒数
|
|
78
|
+
px: ExpiryT | None = None, # 超时毫秒
|
|
78
79
|
nx: bool = False,
|
|
79
80
|
xx: bool = False
|
|
80
|
-
) ->
|
|
81
|
+
) -> bool | None:
|
|
81
82
|
redis = await cls.connect()
|
|
82
83
|
result = await redis.set(name, value, ex=ex, px=px, nx=nx, xx=xx)
|
|
83
84
|
return result
|
|
@@ -86,7 +87,7 @@ class AioRedis:
|
|
|
86
87
|
async def exists(
|
|
87
88
|
cls,
|
|
88
89
|
*names: KeyT
|
|
89
|
-
) ->
|
|
90
|
+
) -> bool | None:
|
|
90
91
|
redis = await cls.connect()
|
|
91
92
|
result = await redis.exists(*names)
|
|
92
93
|
return result
|
|
@@ -102,7 +103,7 @@ class AioRedis:
|
|
|
102
103
|
incr: bool = False,
|
|
103
104
|
gt: bool = False,
|
|
104
105
|
lt: bool = False
|
|
105
|
-
) ->
|
|
106
|
+
) -> int | float:
|
|
106
107
|
redis = await cls.connect()
|
|
107
108
|
result = await redis.zadd(name, mapping, nx, xx, ch, incr, gt, lt)
|
|
108
109
|
return result
|
|
@@ -118,7 +119,7 @@ class AioRedis:
|
|
|
118
119
|
cls,
|
|
119
120
|
name: KeyT,
|
|
120
121
|
value: EncodableT
|
|
121
|
-
) ->
|
|
122
|
+
) -> float | None:
|
|
122
123
|
redis = await cls.connect()
|
|
123
124
|
result = await redis.zscore(name, value)
|
|
124
125
|
return result
|
|
@@ -135,10 +136,10 @@ class AioRedis:
|
|
|
135
136
|
name: KeyT,
|
|
136
137
|
max: ZScoreBoundT,
|
|
137
138
|
min: ZScoreBoundT,
|
|
138
|
-
start:
|
|
139
|
-
num:
|
|
139
|
+
start: int | None = None,
|
|
140
|
+
num: int | None = None,
|
|
140
141
|
withscores: bool = False,
|
|
141
|
-
score_cast_func:
|
|
142
|
+
score_cast_func: type | Callable = float,
|
|
142
143
|
):
|
|
143
144
|
redis = await cls.connect()
|
|
144
145
|
result = await redis.zrevrangebyscore(name, max, min, start, num, withscores, score_cast_func)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import time
|
|
2
|
+
|
|
3
|
+
from common_base.logging import logger
|
|
2
4
|
from fastapi import Request
|
|
3
5
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
4
|
-
from starlette.types import ASGIApp
|
|
5
6
|
from starlette.responses import Response
|
|
6
|
-
from
|
|
7
|
+
from starlette.types import ASGIApp
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class RequestHttpMiddleware(BaseHTTPMiddleware):
|
|
9
11
|
def __init__(self, app: ASGIApp) -> None:
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import AsyncGenerator
|
|
3
3
|
from contextlib import asynccontextmanager
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from common_base.exception import (
|
|
7
|
+
AllExceptionHandler,
|
|
8
|
+
CustomException,
|
|
9
|
+
CustomExceptionHandler,
|
|
10
|
+
HttpExceptionHandler,
|
|
11
|
+
RequestValidationHandle,
|
|
12
|
+
ResponseValidationHandle,
|
|
13
|
+
ValueExceptionHandler,
|
|
14
|
+
)
|
|
9
15
|
from common_base.logging import logger
|
|
10
16
|
from core.base_config import BaseConfig, base_config
|
|
11
17
|
from core.databases.aiodb import AioDb
|
|
12
18
|
from core.middleware.http_middleware import RequestHttpMiddleware
|
|
19
|
+
from fastapi import FastAPI, HTTPException
|
|
20
|
+
from fastapi.exceptions import RequestValidationError, ResponseValidationError
|
|
21
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
22
|
+
from starlette.staticfiles import StaticFiles
|
|
13
23
|
|
|
14
24
|
|
|
15
25
|
def start_server(config: BaseConfig):
|
|
@@ -4,12 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rainycode"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "FastAPI base modules"
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
10
|
|
|
11
11
|
dependencies = [
|
|
12
|
-
"build == 1.4.0",
|
|
13
12
|
"aerich == 0.9.2",
|
|
14
13
|
"fastapi == 0.133.1",
|
|
15
14
|
"aiohttp == 3.13.3",
|
|
@@ -26,9 +25,12 @@ dependencies = [
|
|
|
26
25
|
"pydantic-settings == 2.13.1",
|
|
27
26
|
]
|
|
28
27
|
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
release = [
|
|
30
|
+
"build == 1.4.0",
|
|
31
|
+
"twine == 6.1.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
29
34
|
[tool.setuptools.packages.find]
|
|
30
35
|
where = ["."]
|
|
31
|
-
include = ["core*", "common*"
|
|
32
|
-
|
|
33
|
-
[tool.setuptools.package-data]
|
|
34
|
-
utlis = ["py.typed"]
|
|
36
|
+
include = ["core*", "common*"]
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rainycode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: FastAPI base modules
|
|
5
|
-
Requires-Python: >=3.
|
|
6
|
-
Requires-Dist: build==1.4.0
|
|
5
|
+
Requires-Python: >=3.10
|
|
7
6
|
Requires-Dist: aerich==0.9.2
|
|
8
7
|
Requires-Dist: fastapi==0.133.1
|
|
9
8
|
Requires-Dist: aiohttp==3.13.3
|
|
@@ -18,3 +17,6 @@ Requires-Dist: pycryptodome==3.23.0
|
|
|
18
17
|
Requires-Dist: uvicorn[standard]==0.41.0
|
|
19
18
|
Requires-Dist: tortoise-orm[asyncmy]==0.25.4
|
|
20
19
|
Requires-Dist: pydantic-settings==2.13.1
|
|
20
|
+
Provides-Extra: release
|
|
21
|
+
Requires-Dist: build==1.4.0; extra == "release"
|
|
22
|
+
Requires-Dist: twine==6.1.0; extra == "release"
|
|
@@ -12,7 +12,6 @@ common_models/wechat_model.py
|
|
|
12
12
|
common_servers/router.py
|
|
13
13
|
common_servers/api/auth_api.py
|
|
14
14
|
common_servers/api/wechat_api.py
|
|
15
|
-
common_servers/schemas/__init__.py
|
|
16
15
|
common_servers/schemas/auth_schema.py
|
|
17
16
|
common_servers/service/auth_svc.py
|
|
18
17
|
common_servers/service/wechat_svc.py
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import uuid
|
|
2
|
-
from collections.abc import Iterable
|
|
3
|
-
from typing_extensions import Self
|
|
4
|
-
from typing import Optional, Any
|
|
5
|
-
from tortoise import fields, BaseDBAsyncClient
|
|
6
|
-
from tortoise.expressions import Q
|
|
7
|
-
from tortoise.queryset import BulkCreateQuery, QuerySet, QuerySetSingle
|
|
8
|
-
from common_utils.snowflake_util import get_snowflake_id
|
|
9
|
-
from core.databases.aiodb import MySQLUtils
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class BaseModel(MySQLUtils):
|
|
13
|
-
|
|
14
|
-
@classmethod
|
|
15
|
-
async def create(
|
|
16
|
-
cls,
|
|
17
|
-
using_db: Optional[BaseDBAsyncClient] = None,
|
|
18
|
-
**kwargs: Any
|
|
19
|
-
) -> Self:
|
|
20
|
-
if not kwargs.get('id', 0) and 'id' in cls._meta.fields:
|
|
21
|
-
# uuid生成id
|
|
22
|
-
if kwargs.get('id_uuid', False):
|
|
23
|
-
kwargs['id'] = str(uuid.uuid4()).replace('-', '')
|
|
24
|
-
# 雪花算法生成id
|
|
25
|
-
if kwargs.get('id_snowflake', False):
|
|
26
|
-
kwargs['id'] = get_snowflake_id()
|
|
27
|
-
return await super().create(using_db=using_db, **kwargs)
|
|
28
|
-
|
|
29
|
-
@classmethod
|
|
30
|
-
def get(
|
|
31
|
-
cls,
|
|
32
|
-
*args: Q,
|
|
33
|
-
using_db: BaseDBAsyncClient | None = None,
|
|
34
|
-
**kwargs: Any
|
|
35
|
-
) -> QuerySetSingle[Self]:
|
|
36
|
-
if 'deleted' in cls._meta.fields and kwargs.get('deleted', None) is None:
|
|
37
|
-
kwargs['deleted'] = False
|
|
38
|
-
return super().get(*args, using_db=using_db, **kwargs)
|
|
39
|
-
|
|
40
|
-
@classmethod
|
|
41
|
-
def filter(
|
|
42
|
-
cls,
|
|
43
|
-
*args: Q,
|
|
44
|
-
**kwargs: Any
|
|
45
|
-
) -> QuerySet[Self]:
|
|
46
|
-
if 'deleted' in cls._meta.fields and kwargs.get('deleted', None) is None:
|
|
47
|
-
kwargs['deleted'] = False
|
|
48
|
-
return cls._meta.manager.get_queryset().filter(*args, **kwargs)
|
|
49
|
-
|
|
50
|
-
@classmethod
|
|
51
|
-
def bulk_create(
|
|
52
|
-
cls,
|
|
53
|
-
objects: Iterable[Self],
|
|
54
|
-
batch_size: Optional[int] = None,
|
|
55
|
-
ignore_conflicts: bool = False,
|
|
56
|
-
update_fields: Optional[Iterable[str]] = None,
|
|
57
|
-
on_conflict: Optional[Iterable[str]] = None,
|
|
58
|
-
using_db: Optional[BaseDBAsyncClient] = None,
|
|
59
|
-
**kwargs: Any
|
|
60
|
-
) -> "BulkCreateQuery[Self]":
|
|
61
|
-
if 'id' in cls._meta.fields:
|
|
62
|
-
for obj in objects:
|
|
63
|
-
if not getattr(obj, 'id', 0):
|
|
64
|
-
# uuid生成id
|
|
65
|
-
if kwargs.get('id_uuid', False):
|
|
66
|
-
obj.id = str(uuid.uuid4()).replace('-', '')
|
|
67
|
-
# 雪花算法生成id
|
|
68
|
-
if kwargs.get('id_snowflake', False):
|
|
69
|
-
obj.id = get_snowflake_id()
|
|
70
|
-
|
|
71
|
-
params = {
|
|
72
|
-
"objects": objects,
|
|
73
|
-
"batch_size": batch_size,
|
|
74
|
-
"ignore_conflicts": ignore_conflicts,
|
|
75
|
-
"update_fields": update_fields,
|
|
76
|
-
"on_conflict": on_conflict,
|
|
77
|
-
"using_db": using_db,
|
|
78
|
-
}
|
|
79
|
-
return super().bulk_create(**params)
|
|
80
|
-
|
|
81
|
-
async def save(
|
|
82
|
-
self,
|
|
83
|
-
using_db: Optional[BaseDBAsyncClient] = None,
|
|
84
|
-
update_fields: Optional[Iterable[str]] = None,
|
|
85
|
-
force_create: bool = False,
|
|
86
|
-
force_update: bool = False,
|
|
87
|
-
**kwargs: Any
|
|
88
|
-
) -> None:
|
|
89
|
-
if not getattr(self, 'id', 0) and kwargs.get('id_uuid', False) and 'id' in self._meta.fields:
|
|
90
|
-
setattr(self, 'id', str(uuid.uuid4()).replace('-', ''))
|
|
91
|
-
return await super().save(using_db=using_db, update_fields=update_fields, force_create=force_create,
|
|
92
|
-
force_update=force_update)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class CreateModel(BaseModel):
|
|
96
|
-
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
97
|
-
creator = fields.CharField(max_length=50, null=True, description="创建人")
|
|
98
|
-
|
|
99
|
-
class Meta:
|
|
100
|
-
abstract = True
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class UpdateModel(BaseModel):
|
|
104
|
-
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
105
|
-
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
|
106
|
-
creator = fields.CharField(max_length=50, null=True, description="创建人")
|
|
107
|
-
updater = fields.CharField(max_length=50, null=True, description="更新人")
|
|
108
|
-
|
|
109
|
-
class Meta:
|
|
110
|
-
abstract = True
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class DeleteModel(BaseModel):
|
|
114
|
-
deleted = fields.BooleanField(default=False, description="是否已删除")
|
|
115
|
-
create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
|
116
|
-
update_time = fields.DatetimeField(auto_now=True, description="更新时间")
|
|
117
|
-
creator = fields.CharField(max_length=50, null=True, description="创建人")
|
|
118
|
-
updater = fields.CharField(max_length=50, null=True, description="更新人")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# class CreateRelatedMeta(type(Model)):
|
|
122
|
-
# """元类:更新外键字段,解决用户related_name重复问题"""
|
|
123
|
-
# def __new__(cls, name: str, bases: tuple, attrs: dict):
|
|
124
|
-
# if name == 'UpdateModel':
|
|
125
|
-
# return super().__new__(cls, name, bases, attrs)
|
|
126
|
-
|
|
127
|
-
# creator_related_name = f"{name.lower()}_creator"
|
|
128
|
-
# attrs["creator"] = fields.ForeignKeyField(
|
|
129
|
-
# 'main.SystemUserModel',
|
|
130
|
-
# null=True,
|
|
131
|
-
# related_name=creator_related_name,
|
|
132
|
-
# db_constraint=False,
|
|
133
|
-
# description="创建人"
|
|
134
|
-
# )
|
|
135
|
-
|
|
136
|
-
# new_class = super().__new__(cls, name, bases, attrs)
|
|
137
|
-
# return new_class
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
# class CreateModel(BaseModel, metaclass=CreateRelatedMeta):
|
|
141
|
-
# deleted = fields.BooleanField(default=False, description='是否已删除')
|
|
142
|
-
# create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
143
|
-
# creator = fields.ForeignKeyField(
|
|
144
|
-
# 'main.SystemUserModel',
|
|
145
|
-
# null=True,
|
|
146
|
-
# db_constraint=False,
|
|
147
|
-
# description="创建人"
|
|
148
|
-
# )
|
|
149
|
-
|
|
150
|
-
# class Meta:
|
|
151
|
-
# abstract = True
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
# class UpdateRelatedMeta(type(Model)):
|
|
155
|
-
# """元类:更新外键字段,解决用户related_name重复问题"""
|
|
156
|
-
# def __new__(cls, name: str, bases: tuple, attrs: dict):
|
|
157
|
-
# if name == 'UpdateModel':
|
|
158
|
-
# return super().__new__(cls, name, bases, attrs)
|
|
159
|
-
|
|
160
|
-
# creator_related_name = f"{name.lower()}_creator"
|
|
161
|
-
# attrs["creator"] = fields.ForeignKeyField(
|
|
162
|
-
# 'main.SystemUserModel',
|
|
163
|
-
# null=True,
|
|
164
|
-
# related_name=creator_related_name,
|
|
165
|
-
# db_constraint=False,
|
|
166
|
-
# description="创建人"
|
|
167
|
-
# )
|
|
168
|
-
|
|
169
|
-
# updater_related_name = f"{name.lower()}_updater"
|
|
170
|
-
# attrs["updater"] = fields.ForeignKeyField(
|
|
171
|
-
# 'main.SystemUserModel',
|
|
172
|
-
# null=True,
|
|
173
|
-
# related_name=updater_related_name,
|
|
174
|
-
# db_constraint=False,
|
|
175
|
-
# description="更新人"
|
|
176
|
-
# )
|
|
177
|
-
|
|
178
|
-
# new_class = super().__new__(cls, name, bases, attrs)
|
|
179
|
-
# return new_class
|
|
180
|
-
|
|
181
|
-
# class UpdateModel(BaseModel, metaclass=UpdateRelatedMeta):
|
|
182
|
-
# deleted = fields.BooleanField(default=False, description='是否已删除')
|
|
183
|
-
# create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
184
|
-
# update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
|
185
|
-
# creator = fields.ForeignKeyField(
|
|
186
|
-
# 'main.SystemUserModel',
|
|
187
|
-
# null=True,
|
|
188
|
-
# db_constraint=False,
|
|
189
|
-
# description="创建人"
|
|
190
|
-
# )
|
|
191
|
-
|
|
192
|
-
# updater = fields.ForeignKeyField(
|
|
193
|
-
# 'main.SystemUserModel',
|
|
194
|
-
# null=True,
|
|
195
|
-
# db_constraint=False,
|
|
196
|
-
# description="更新人"
|
|
197
|
-
# )
|
|
198
|
-
|
|
199
|
-
# class Meta:
|
|
200
|
-
# abstract = True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|