Skip to content

Commit f150849

Browse files
committed
Optimize common
1 parent 8adcb49 commit f150849

File tree

22 files changed

+291
-286
lines changed

22 files changed

+291
-286
lines changed

backend/app/admin/api/v1/sys/token.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from backend.app.admin.schema.token import GetTokenDetail, KickOutToken
1010
from backend.common.enums import StatusType
1111
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
12-
from backend.common.security.jwt import DependsJwtAuth, jwt_decode, superuser_verify
12+
from backend.common.security.jwt import DependsJwtAuth, jwt_decode, revoke_token, superuser_verify
1313
from backend.common.security.permission import RequestPermission
1414
from backend.common.security.rbac import DependsRBAC
1515
from backend.core.conf import settings
@@ -78,7 +78,9 @@ def append_token_detail():
7878
DependsRBAC,
7979
],
8080
)
81-
async def kick_out(request: Request, pk: Annotated[int, Path(...)], session_uuid: KickOutToken) -> ResponseModel:
81+
async def kick_out(
82+
request: Request, pk: Annotated[int, Path(..., description='用户 ID')], session_uuid: KickOutToken
83+
) -> ResponseModel:
8284
superuser_verify(request)
83-
await redis_client.delete(f'{settings.TOKEN_REDIS_PREFIX}:{pk}:{session_uuid}')
85+
await revoke_token(pk)
8486
return response_base.success()

backend/app/admin/crud/crud_user.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ async def get_list(
193193
)
194194
.order_by(desc(self.model.join_time))
195195
)
196-
196+
197197
filters = []
198198
if dept:
199199
filters.append(self.model.dept_id == dept)
@@ -203,7 +203,7 @@ async def get_list(
203203
filters.append(self.model.phone.like(f'%{phone}%'))
204204
if status is not None:
205205
filters.append(self.model.status == status)
206-
206+
207207
if filters:
208208
stmt = stmt.where(and_(*filters))
209209

@@ -315,18 +315,18 @@ async def get_with_relation(
315315
selectinload(Role.rules),
316316
),
317317
)
318-
318+
319319
filters = []
320320
if user_id:
321321
filters.append(self.model.id == user_id)
322322
if username:
323323
filters.append(self.model.username == username)
324-
324+
325325
if filters:
326326
stmt = stmt.where(and_(*filters))
327-
327+
328328
user = await db.execute(stmt)
329-
329+
330330
return user.scalars().first()
331331

332332

backend/app/admin/model/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from datetime import datetime
66
from typing import TYPE_CHECKING
77

8-
from sqlalchemy import VARBINARY, Boolean, DateTime, ForeignKey, String, Index
8+
from sqlalchemy import VARBINARY, Boolean, DateTime, ForeignKey, String
99
from sqlalchemy.dialects.postgresql import BYTEA, INTEGER
1010
from sqlalchemy.orm import Mapped, mapped_column, relationship
1111

backend/app/admin/service/user_service.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,7 @@ async def update_multi_login(*, request: Request, pk: int) -> int:
301301
if pk == user_id:
302302
if not latest_multi_login:
303303
key_prefix = f'{settings.TOKEN_REDIS_PREFIX}:{pk}'
304-
await redis_client.delete_prefix(
305-
key_prefix, exclude=f'{key_prefix}:{token_payload.session_uuid}'
306-
)
304+
await redis_client.delete_prefix(key_prefix, exclude=f'{key_prefix}:{token_payload.session_uuid}')
307305
refresh_token = request.cookies.get(settings.COOKIE_REFRESH_TOKEN_KEY)
308306
if refresh_token:
309307
key_prefix = f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{pk}'

backend/common/enums.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,38 @@
22
# -*- coding: utf-8 -*-
33
from enum import Enum
44
from enum import IntEnum as SourceIntEnum
5-
from typing import Type
5+
from typing import Any, Type, TypeVar
6+
7+
T = TypeVar('T', bound=Enum)
68

79

810
class _EnumBase:
11+
"""枚举基类,提供通用方法"""
12+
913
@classmethod
10-
def get_member_keys(cls: Type[Enum]) -> list[str]:
14+
def get_member_keys(cls: Type[T]) -> list[str]:
15+
"""获取枚举成员名称列表"""
1116
return [name for name in cls.__members__.keys()]
1217

1318
@classmethod
14-
def get_member_values(cls: Type[Enum]) -> list:
19+
def get_member_values(cls: Type[T]) -> list:
20+
"""获取枚举成员值列表"""
1521
return [item.value for item in cls.__members__.values()]
1622

23+
@classmethod
24+
def get_member_dict(cls: Type[T]) -> dict[str, Any]:
25+
"""获取枚举成员字典"""
26+
return {name: item.value for name, item in cls.__members__.items()}
27+
1728

1829
class IntEnum(_EnumBase, SourceIntEnum):
19-
"""整型枚举"""
30+
"""整型枚举基类"""
2031

2132
pass
2233

2334

2435
class StrEnum(_EnumBase, str, Enum):
25-
"""字符串枚举"""
36+
"""字符串枚举基类"""
2637

2738
pass
2839

@@ -56,7 +67,7 @@ class RoleDataRuleExpressionType(IntEnum):
5667

5768

5869
class MethodType(StrEnum):
59-
"""请求方法"""
70+
"""HTTP 请求方法"""
6071

6172
GET = 'GET'
6273
POST = 'POST'
@@ -67,7 +78,7 @@ class MethodType(StrEnum):
6778

6879

6980
class LoginLogStatusType(IntEnum):
70-
"""登陆日志状态"""
81+
"""登录日志状态"""
7182

7283
fail = 0
7384
success = 1
@@ -176,7 +187,7 @@ class GenModelMySQLColumnType(StrEnum):
176187

177188

178189
class GenModelPostgreSQLColumnType(StrEnum):
179-
"""代码生成模型列类型(PostgreSQL),仅作为数据保留,并未实施"""
190+
"""代码生成模型列类型(PostgreSQL)"""
180191

181192
# Python 类型映射
182193
BIGINT = 'int'

backend/common/exception/exception_handler.py

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from fastapi import FastAPI, Request
44
from fastapi.exceptions import RequestValidationError
55
from pydantic import ValidationError
6-
from pydantic.errors import PydanticUserError
76
from starlette.exceptions import HTTPException
87
from starlette.middleware.cors import CORSMiddleware
98
from uvicorn.protocols.http.h11_impl import STATUS_PHRASES
@@ -12,44 +11,41 @@
1211
from backend.common.response.response_code import CustomResponseCode, StandardResponseCode
1312
from backend.common.response.response_schema import response_base
1413
from backend.common.schema import (
15-
CUSTOM_USAGE_ERROR_MESSAGES,
1614
CUSTOM_VALIDATION_ERROR_MESSAGES,
1715
)
1816
from backend.core.conf import settings
1917
from backend.utils.serializers import MsgSpecJSONResponse
2018
from backend.utils.trace_id import get_request_trace_id
2119

2220

23-
def _get_exception_code(status_code: int):
21+
def _get_exception_code(status_code: int) -> int:
2422
"""
25-
获取返回状态码, OpenAPI, Uvicorn... 可用状态码基于 RFC 定义, 详细代码见下方链接
23+
获取返回状态码可用状态码基于 RFC 定义
2624
27-
`python 状态码标准支持 <https://github.com/python/cpython/blob/6e3cc72afeaee2532b4327776501eb8234ac787b/Lib/http
28-
/__init__.py#L7>`__
25+
`python 状态码标准支持 <https://github.com/python/cpython/blob/6e3cc72afeaee2532b4327776501eb8234ac787b/Lib/http/__init__.py#L7>`__
2926
3027
`IANA 状态码注册表 <https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml>`__
3128
32-
:param status_code:
29+
:param status_code: HTTP 状态码
3330
:return:
3431
"""
3532
try:
3633
STATUS_PHRASES[status_code]
34+
return status_code
3735
except Exception:
38-
code = StandardResponseCode.HTTP_400
39-
else:
40-
code = status_code
41-
return code
36+
return StandardResponseCode.HTTP_400
4237

4338

44-
async def _validation_exception_handler(request: Request, e: RequestValidationError | ValidationError):
39+
async def _validation_exception_handler(request: Request, exc: RequestValidationError | ValidationError):
4540
"""
4641
数据验证异常处理
4742
48-
:param e:
43+
:param request: 请求对象
44+
:param exc: 验证异常
4945
:return:
5046
"""
5147
errors = []
52-
for error in e.errors():
48+
for error in exc.errors():
5349
custom_message = CUSTOM_VALIDATION_ERROR_MESSAGES.get(error['type'])
5450
if custom_message:
5551
ctx = error.get('ctx')
@@ -87,10 +83,10 @@ def register_exception(app: FastAPI):
8783
@app.exception_handler(HTTPException)
8884
async def http_exception_handler(request: Request, exc: HTTPException):
8985
"""
90-
全局HTTP异常处理
86+
全局 HTTP 异常处理
9187
92-
:param request:
93-
:param exc:
88+
:param request: FastAPI 请求对象
89+
:param exc: HTTP 异常
9490
:return:
9591
"""
9692
if settings.ENVIRONMENT == 'dev':
@@ -113,53 +109,32 @@ async def http_exception_handler(request: Request, exc: HTTPException):
113109
@app.exception_handler(RequestValidationError)
114110
async def fastapi_validation_exception_handler(request: Request, exc: RequestValidationError):
115111
"""
116-
fastapi 数据验证异常处理
112+
FastAPI 数据验证异常处理
117113
118-
:param request:
119-
:param exc:
114+
:param request: FastAPI 请求对象
115+
:param exc: 验证异常
120116
:return:
121117
"""
122118
return await _validation_exception_handler(request, exc)
123119

124120
@app.exception_handler(ValidationError)
125121
async def pydantic_validation_exception_handler(request: Request, exc: ValidationError):
126122
"""
127-
pydantic 数据验证异常处理
123+
Pydantic 数据验证异常处理
128124
129-
:param request:
130-
:param exc:
125+
:param request: 请求对象
126+
:param exc: 验证异常
131127
:return:
132128
"""
133129
return await _validation_exception_handler(request, exc)
134130

135-
@app.exception_handler(PydanticUserError)
136-
async def pydantic_user_error_handler(request: Request, exc: PydanticUserError):
137-
"""
138-
Pydantic 用户异常处理
139-
140-
:param request:
141-
:param exc:
142-
:return:
143-
"""
144-
content = {
145-
'code': StandardResponseCode.HTTP_500,
146-
'msg': CUSTOM_USAGE_ERROR_MESSAGES.get(exc.code),
147-
'data': None,
148-
}
149-
request.state.__request_pydantic_user_error__ = content
150-
content.update(trace_id=get_request_trace_id(request))
151-
return MsgSpecJSONResponse(
152-
status_code=StandardResponseCode.HTTP_500,
153-
content=content,
154-
)
155-
156131
@app.exception_handler(AssertionError)
157132
async def assertion_error_handler(request: Request, exc: AssertionError):
158133
"""
159134
断言错误处理
160135
161-
:param request:
162-
:param exc:
136+
:param request: FastAPI 请求对象
137+
:param exc: 断言错误
163138
:return:
164139
"""
165140
if settings.ENVIRONMENT == 'dev':
@@ -183,8 +158,8 @@ async def custom_exception_handler(request: Request, exc: BaseExceptionMixin):
183158
"""
184159
全局自定义异常处理
185160
186-
:param request:
187-
:param exc:
161+
:param request: FastAPI 请求对象
162+
:param exc: 自定义异常
188163
:return:
189164
"""
190165
content = {
@@ -205,8 +180,8 @@ async def all_unknown_exception_handler(request: Request, exc: Exception):
205180
"""
206181
全局未知异常处理
207182
208-
:param request:
209-
:param exc:
183+
:param request: FastAPI 请求对象
184+
:param exc: 未知异常
210185
:return:
211186
"""
212187
if settings.ENVIRONMENT == 'dev':
@@ -233,10 +208,11 @@ async def cors_custom_code_500_exception_handler(request, exc):
233208
跨域自定义 500 异常处理
234209
235210
`Related issue <https://github.com/encode/starlette/issues/1175>`_
211+
236212
`Solution <https://github.com/fastapi/fastapi/discussions/7847#discussioncomment-5144709>`_
237213
238-
:param request:
239-
:param exc:
214+
:param request: FastAPI 请求对象
215+
:param exc: 自定义异常
240216
:return:
241217
"""
242218
if isinstance(exc, BaseExceptionMixin):

backend/common/log.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
class InterceptHandler(logging.Handler):
1616
"""
17-
默认处理器,来自 loguru 文档示例
17+
日志拦截处理器,用于将标准库的日志重定向到 loguru
1818
19-
参见 https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
19+
参考:https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
2020
"""
2121

2222
def emit(self, record: logging.LogRecord):
@@ -35,10 +35,10 @@ def emit(self, record: logging.LogRecord):
3535
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
3636

3737

38-
def setup_logging():
38+
def setup_logging() -> None:
3939
"""
4040
设置日志处理器
41-
41+
4242
参考:
4343
- https://github.com/benoitc/gunicorn/issues/1572#issuecomment-638391953
4444
- https://github.com/pawamoy/pawamoy.github.io/issues/17
@@ -47,7 +47,7 @@ def setup_logging():
4747
logging.root.handlers = [InterceptHandler()]
4848
logging.root.setLevel(settings.LOG_STD_LEVEL)
4949

50-
# 移除所有日志处理器并传播到根日志记录器
50+
# 配置日志传播规则
5151
for name in logging.root.manager.loggerDict.keys():
5252
logging.getLogger(name).handlers = []
5353
if 'uvicorn.access' in name or 'watchfiles.main' in name:
@@ -65,10 +65,8 @@ def correlation_id_filter(record):
6565
record['correlation_id'] = cid[: settings.LOG_CID_UUID_LENGTH]
6666
return record
6767

68-
# 移除默认 loguru 日志记录器
69-
logger.remove()
70-
71-
# 设置 loguru 默认处理器
68+
# 配置 loguru 处理器
69+
logger.remove() # 移除默认处理器
7270
logger.configure(
7371
handlers=[
7472
{
@@ -81,7 +79,7 @@ def correlation_id_filter(record):
8179
)
8280

8381

84-
def set_custom_logfile():
82+
def set_custom_logfile() -> None:
8583
"""设置自定义日志文件"""
8684
log_path = path_conf.LOG_DIR
8785
if not os.path.exists(log_path):
@@ -91,7 +89,7 @@ def set_custom_logfile():
9189
log_access_file = os.path.join(log_path, settings.LOG_ACCESS_FILENAME)
9290
log_error_file = os.path.join(log_path, settings.LOG_ERROR_FILENAME)
9391

94-
# 设置 loguru 日志记录器默认配置
92+
# 日志文件通用配置
9593
# https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add
9694
log_config = {
9795
'format': settings.LOG_FILE_FORMAT,
@@ -122,4 +120,5 @@ def set_custom_logfile():
122120
)
123121

124122

123+
# 创建 logger 实例
125124
log = logger

0 commit comments

Comments
 (0)