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>
This commit is contained in:
commit
273ebf4657
101 files changed
+8725
No files matched your search
@@ -0,0 +1,83 @@
|
||||
"""ORM 基类、命名约定与公共列。
|
||||
|
||||
命名约定按 PROJECT_STANDARDS 红线设置,使 Alembic autogenerate 产出的
|
||||
约束名自动符合规范(唯一键 `uk_表_字段`、索引 `idx_表_字段`),
|
||||
无需在每张表上手工指定 —— 手工指定迟早会漏。
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, MetaData, text
|
||||
from sqlalchemy.dialects.mysql import BIGINT, TINYINT
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
# 约束命名模板。`column_0_name` 取约束涉及的第一列,
|
||||
# 对单列唯一键/索引即为字段名,符合规范的 uk_表_字段 / idx_表_字段
|
||||
NAMING_CONVENTION = {
|
||||
"ix": "idx_%(table_name)s_%(column_0_name)s",
|
||||
"uq": "uk_%(table_name)s_%(column_0_name)s",
|
||||
"ck": "ck_%(table_name)s_%(constraint_name)s",
|
||||
"fk": "fk_%(table_name)s_%(column_0_name)s",
|
||||
"pk": "pk_%(table_name)s",
|
||||
}
|
||||
|
||||
# 主键与外键统一使用无符号 BIGINT(规范模板为 BIGINT UNSIGNED)。
|
||||
# 两边类型必须完全一致,否则 MySQL 拒绝建立外键约束。
|
||||
BIGINT_UNSIGNED = BIGINT(unsigned=True)
|
||||
|
||||
# 状态类字段统一用 TINYINT(0/1 或少量枚举值)
|
||||
TINYINT_TYPE = TINYINT
|
||||
|
||||
__all__ = ["BIGINT_UNSIGNED", "TINYINT_TYPE", "Base", "TimestampMixin", "mysql_table_args"]
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
"""全部 ORM 模型的基类。"""
|
||||
|
||||
metadata = MetaData(naming_convention=NAMING_CONVENTION)
|
||||
|
||||
|
||||
class TimestampMixin:
|
||||
"""创建时间 / 更新时间公共列。
|
||||
|
||||
取默认值交给数据库而非应用层:即使有人直接用 SQL 改数据,时间戳依然准确;
|
||||
应用层赋值则会被绕过。
|
||||
|
||||
`update_time` 必须用 `text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")`
|
||||
而非 `server_onupdate=func.current_timestamp()` —— 后者只是告诉 ORM
|
||||
「这个值由服务端维护」,**不会生成 ON UPDATE 子句**,结果是更新时间永远不刷新。
|
||||
"""
|
||||
|
||||
create_time: Mapped[datetime] = mapped_column(
|
||||
DateTime,
|
||||
nullable=False,
|
||||
server_default=text("CURRENT_TIMESTAMP"),
|
||||
comment="创建时间",
|
||||
)
|
||||
update_time: Mapped[datetime] = mapped_column(
|
||||
DateTime,
|
||||
nullable=False,
|
||||
server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
|
||||
def mysql_table_args(comment: str, *extra: object) -> tuple[object, ...]:
|
||||
"""构造 MySQL 表的公共参数,返回可直接赋给 `__table_args__` 的元组。
|
||||
|
||||
显式指定引擎、字符集与排序规则:compose 已把服务端排序规则覆盖为
|
||||
`utf8mb4_general_ci`,若某处按 MySQL 8.0 默认的 `utf8mb4_0900_ai_ci` 建表,
|
||||
跨表 JOIN 会报 `Illegal mix of collations`。
|
||||
|
||||
:param comment: 表注释(规范红线要求中文 COMMENT)
|
||||
:param extra: 额外的约束对象(UniqueConstraint / Index 等)
|
||||
"""
|
||||
return (
|
||||
*extra,
|
||||
{
|
||||
"mysql_engine": "InnoDB",
|
||||
"mysql_charset": "utf8mb4",
|
||||
"mysql_collate": "utf8mb4_general_ci",
|
||||
"comment": comment,
|
||||
},
|
||||
)
|
||||
Reference in new issue
Block a user