PayrollManagementSystem/backend/app/core/response.py
liuwenxin 273ebf4657 chore: 初始化项目结构与登录鉴权模块
后端 FastAPI 六层结构(api/services/repositories/models/schemas/core),
统一响应信封、全局异常、日志脱敏;6 张表经 Alembic 迁移建库,SQL 留存
db/migration;登录鉴权含图形验证码(Redis GETDEL 防重放)、JWT、登录限流、
登出黑名单(TTL=令牌剩余有效期)、登录日志。

前端 Vue3 + TS + Element Plus 骨架,含登录页、路由守卫、请求封装与 Vite 代理。

工程配置:uv 锁定 Python 3.12、ruff 检查与格式化、editorconfig、gitattributes。

测试 184 项(单元 155 / 集成 29)全绿,浏览器端到端 8 项通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-09 18:46:36 +08:00

63 lines
2.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""统一响应模型。
所有接口响应体形如 `{code, message, data}`(PROJECT_STANDARDS 第 3 节)。
分页响应字段为 `list/total/pageNum/pageSize`(同规范第 14 节)。
字段名采用驼峰(规范约定)而 Python 属性用蛇形(语言惯例),
两者通过 pydantic 的 alias 映射,序列化时输出驼峰。
"""
from pydantic import BaseModel, ConfigDict, Field
from app.core.constants import CODE_SUCCESS
class ApiResponse[T](BaseModel):
"""统一响应包装。泛型参数为 `data` 的类型。"""
code: int = Field(default=CODE_SUCCESS, description="状态码,与 HTTP 状态码一致")
message: str = Field(default="success", description="提示信息")
data: T | None = Field(default=None, description="业务数据")
def ok[T](data: T | None = None, message: str = "success") -> ApiResponse[T]:
"""构造成功响应。
统一出口便于日后调整成功码或附加字段时只改一处。
"""
return ApiResponse(code=CODE_SUCCESS, message=message, data=data)
class PageResult[T](BaseModel):
"""分页结果。
Python 侧使用蛇形属性名,序列化后输出规范要求的驼峰字段名。
"""
model_config = ConfigDict(populate_by_name=True)
records: list[T] = Field(
default_factory=list, alias="list", serialization_alias="list", description="当前页数据"
)
total: int = Field(default=0, description="总记录数")
page_num: int = Field(
default=1, alias="pageNum", serialization_alias="pageNum", description="当前页码"
)
page_size: int = Field(
default=10, alias="pageSize", serialization_alias="pageSize", description="每页条数"
)
class PageQuery(BaseModel):
"""分页查询入参基类。业务查询参数继承本类扩展。"""
model_config = ConfigDict(populate_by_name=True)
page_num: int = Field(default=1, ge=1, alias="pageNum", description="页码,从 1 开始")
page_size: int = Field(default=10, ge=1, le=200, alias="pageSize", description="每页条数")
@property
def offset(self) -> int:
"""转换为 SQL OFFSET。"""
return (self.page_num - 1) * self.page_size