rainycode 1.1.9__tar.gz → 1.3.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.1.9 → rainycode-1.3.0}/PKG-INFO +5 -3
- {rainycode-1.1.9 → rainycode-1.3.0}/common_base/aiorequests.py +3 -3
- {rainycode-1.1.9 → rainycode-1.3.0}/common_base/exception.py +2 -1
- {rainycode-1.1.9 → rainycode-1.3.0}/common_base/logging.py +2 -2
- {rainycode-1.1.9 → rainycode-1.3.0}/common_depends/auth_depend.py +7 -3
- {rainycode-1.1.9 → rainycode-1.3.0}/common_models/base_model.py +21 -12
- {rainycode-1.1.9 → rainycode-1.3.0}/common_models/user_model.py +1 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/common_models/wechat_model.py +4 -1
- {rainycode-1.1.9 → rainycode-1.3.0}/common_servers/api/auth_api.py +2 -2
- {rainycode-1.1.9 → rainycode-1.3.0}/common_servers/api/wechat_api.py +3 -2
- {rainycode-1.1.9 → rainycode-1.3.0}/common_servers/router.py +1 -2
- {rainycode-1.1.9 → rainycode-1.3.0}/common_servers/schemas/auth_schema.py +2 -1
- {rainycode-1.1.9 → rainycode-1.3.0}/common_servers/service/auth_svc.py +32 -28
- {rainycode-1.1.9 → rainycode-1.3.0}/common_servers/service/wechat_svc.py +2 -1
- {rainycode-1.1.9 → rainycode-1.3.0}/common_utils/bcrypt_util.py +0 -1
- {rainycode-1.1.9 → rainycode-1.3.0}/common_utils/captcha_util.py +1 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/common_utils/ip_util.py +1 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/common_utils/jwt_util.py +3 -1
- {rainycode-1.1.9 → rainycode-1.3.0}/common_utils/snowflake_util.py +2 -2
- {rainycode-1.1.9 → rainycode-1.3.0}/common_utils/wechat_util.py +5 -3
- {rainycode-1.1.9 → rainycode-1.3.0}/core/databases/aiodb.py +29 -28
- {rainycode-1.1.9 → rainycode-1.3.0}/core/databases/aioredis.py +17 -16
- {rainycode-1.1.9 → rainycode-1.3.0}/core/middleware/http_middleware.py +4 -2
- {rainycode-1.1.9 → rainycode-1.3.0}/core/start.py +16 -6
- {rainycode-1.1.9 → rainycode-1.3.0}/pyproject.toml +9 -7
- {rainycode-1.1.9 → rainycode-1.3.0}/rainycode.egg-info/PKG-INFO +5 -3
- {rainycode-1.1.9 → rainycode-1.3.0}/rainycode.egg-info/SOURCES.txt +0 -1
- {rainycode-1.1.9 → rainycode-1.3.0}/rainycode.egg-info/requires.txt +4 -1
- rainycode-1.1.9/common_servers/schemas/__init__.py +0 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/common_base/consts.py +0 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/common_base/response.py +0 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/common_models/__init__.py +0 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/core/base_config.py +0 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/rainycode.egg-info/dependency_links.txt +0 -0
- {rainycode-1.1.9 → rainycode-1.3.0}/rainycode.egg-info/top_level.txt +0 -0
- {rainycode-1.1.9 → rainycode-1.3.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.3.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
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from collections.abc import Iterable
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
from tortoise import fields, BaseDBAsyncClient
|
|
6
|
-
from tortoise.expressions import Q
|
|
7
|
-
from tortoise.queryset import BulkCreateQuery, QuerySet, QuerySetSingle
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
8
5
|
from common_utils.snowflake_util import get_snowflake_id
|
|
9
6
|
from core.databases.aiodb import MySQLUtils
|
|
7
|
+
from tortoise import BaseDBAsyncClient, fields
|
|
8
|
+
from tortoise.expressions import Q
|
|
9
|
+
from tortoise.queryset import BulkCreateQuery, QuerySet, QuerySetSingle
|
|
10
|
+
from typing_extensions import Self
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class BaseModel(MySQLUtils):
|
|
@@ -14,7 +15,7 @@ class BaseModel(MySQLUtils):
|
|
|
14
15
|
@classmethod
|
|
15
16
|
async def create(
|
|
16
17
|
cls,
|
|
17
|
-
using_db:
|
|
18
|
+
using_db: BaseDBAsyncClient | None = None,
|
|
18
19
|
**kwargs: Any
|
|
19
20
|
) -> Self:
|
|
20
21
|
if not kwargs.get('id', 0) and 'id' in cls._meta.fields:
|
|
@@ -51,11 +52,11 @@ class BaseModel(MySQLUtils):
|
|
|
51
52
|
def bulk_create(
|
|
52
53
|
cls,
|
|
53
54
|
objects: Iterable[Self],
|
|
54
|
-
batch_size:
|
|
55
|
+
batch_size: int | None = None,
|
|
55
56
|
ignore_conflicts: bool = False,
|
|
56
|
-
update_fields:
|
|
57
|
-
on_conflict:
|
|
58
|
-
using_db:
|
|
57
|
+
update_fields: Iterable[str] | None = None,
|
|
58
|
+
on_conflict: Iterable[str] | None = None,
|
|
59
|
+
using_db: BaseDBAsyncClient | None = None,
|
|
59
60
|
**kwargs: Any
|
|
60
61
|
) -> "BulkCreateQuery[Self]":
|
|
61
62
|
if 'id' in cls._meta.fields:
|
|
@@ -80,8 +81,8 @@ class BaseModel(MySQLUtils):
|
|
|
80
81
|
|
|
81
82
|
async def save(
|
|
82
83
|
self,
|
|
83
|
-
using_db:
|
|
84
|
-
update_fields:
|
|
84
|
+
using_db: BaseDBAsyncClient | None = None,
|
|
85
|
+
update_fields: Iterable[str] | None = None,
|
|
85
86
|
force_create: bool = False,
|
|
86
87
|
force_update: bool = False,
|
|
87
88
|
**kwargs: Any
|
|
@@ -92,6 +93,14 @@ class BaseModel(MySQLUtils):
|
|
|
92
93
|
force_update=force_update)
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
class TimeModel(BaseModel):
|
|
97
|
+
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
98
|
+
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
|
99
|
+
|
|
100
|
+
class Meta:
|
|
101
|
+
abstract = True
|
|
102
|
+
|
|
103
|
+
|
|
95
104
|
class CreateModel(BaseModel):
|
|
96
105
|
create_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
|
|
97
106
|
creator = fields.CharField(max_length=50, null=True, description="创建人")
|
|
@@ -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,15 +225,15 @@ 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
|
+
) -> dict[str, Any] | None:
|
|
236
237
|
if 'deleted' in cls._meta.fields and deleted is None:
|
|
237
238
|
kwargs['deleted'] = False
|
|
238
239
|
|
|
@@ -264,15 +265,15 @@ class MySQLUtils(Model):
|
|
|
264
265
|
async def get_or_none_data(
|
|
265
266
|
cls,
|
|
266
267
|
*args,
|
|
267
|
-
include:
|
|
268
|
-
exclude:
|
|
269
|
-
alias:
|
|
270
|
-
fetch_related:
|
|
268
|
+
include: list[str] | None = None, # 包含哪些字段,默认全部
|
|
269
|
+
exclude: list[str] | None = None, # 排除哪些字段,默认全部
|
|
270
|
+
alias: list[tuple[str, str]] | None = None, # 字段别名,如[('id', 'uid')]
|
|
271
|
+
fetch_related: list[str] | None = None, # 对应关系表
|
|
271
272
|
flat: bool = False, # 扁平化
|
|
272
273
|
deleted: bool | None = None,
|
|
273
274
|
queryset_model: QuerySetSingle | Model | None = None,
|
|
274
275
|
**kwargs
|
|
275
|
-
) ->
|
|
276
|
+
) -> dict[str, Any] | None:
|
|
276
277
|
if 'deleted' in cls._meta.fields and deleted is None:
|
|
277
278
|
kwargs['deleted'] = False
|
|
278
279
|
|
|
@@ -307,16 +308,16 @@ class MySQLUtils(Model):
|
|
|
307
308
|
page_no: int | None = 0,
|
|
308
309
|
page_size: int | None = 0,
|
|
309
310
|
nocount: bool = False,
|
|
310
|
-
include:
|
|
311
|
-
exclude:
|
|
312
|
-
alias:
|
|
313
|
-
fetch_related:
|
|
311
|
+
include: list[str] | None = None, # 包含哪些字段,默认全部
|
|
312
|
+
exclude: list[str] | None = None, # 排除哪些字段,默认全部
|
|
313
|
+
alias: list[tuple[str, str]] | None = None, # 字段别名,如[('id', 'uid')]
|
|
314
|
+
fetch_related: list[str] | None = None, # 对应关系表
|
|
314
315
|
flat: bool = False, # 扁平化
|
|
315
316
|
deleted: bool | None = None,
|
|
316
317
|
queryset: QuerySet | None = None,
|
|
317
|
-
order_by:
|
|
318
|
+
order_by: list[str] | None = None,
|
|
318
319
|
**kwargs
|
|
319
|
-
) ->
|
|
320
|
+
) -> dict[str, Any] | list[dict[str, Any]]:
|
|
320
321
|
if 'deleted' in cls._meta.fields and deleted is None:
|
|
321
322
|
kwargs['deleted'] = False
|
|
322
323
|
|
|
@@ -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:
|
|
@@ -33,7 +34,7 @@ class AioRedis:
|
|
|
33
34
|
)
|
|
34
35
|
cls.__redis_pool = Redis(connection_pool=pool)
|
|
35
36
|
# 测试连接是否有效
|
|
36
|
-
cls.__redis_pool.ping()
|
|
37
|
+
await cls.__redis_pool.ping() # pyright: ignore[reportGeneralTypeIssues]
|
|
37
38
|
except Exception as e:
|
|
38
39
|
log_exc(e)
|
|
39
40
|
raise
|
|
@@ -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.3.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.3.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|