按 2026-11-02 需求补齐计划交付后端能力: - 角色授权:权限目录化,接口统一准入校验,未登记默认拒绝; 管理员判断改为按数据库角色重新判定,防止权限码持有者绕过 - 系统监控:单进程采集资源、请求与异常指标,分钟级聚合, 阈值配置化;敏感配置增加 repr 保护,Redis 密码 URL 转义 - 试用期计提:按回款日判定并写入快照,年度结清与调整单联动; 人员模型补试用期、离职日期与工资版本历史字段 - 计提模板:公式骨架受控,页面维护名称与参数绑定 - T+ 采集:自动化配置表与进度查询 内置角色禁停用保护对齐模块注释;核算规则版本提为常量并 补充批量核算失败堆栈日志。 影响范围:后端全部模块、数据库迁移 V22-V28 验证:uv run ruff check 通过;922 个单元测试全部通过; 集成测试因本机 MySQL 数据卷被其他容器锁定暂未运行
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""角色管理的请求/响应模型。
|
||
|
||
`RoleOut` 原位于 `schemas/user.py`,因角色管理成为独立模块而迁入本文件 ——
|
||
用户表单的角色下拉与角色管理列表共用同一响应模型。
|
||
"""
|
||
|
||
import re
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||
|
||
from app.core.response import PageQuery
|
||
|
||
# 角色编码规则:小写字母开头,后接小写字母/数字/下划线,长度 2–32(与列宽一致)。
|
||
# 不允许大写与中文 —— 编码会出现在授权判断(`ROLE_ADMIN` 常量按编码比对)与
|
||
# 操作日志里,保持单一风格避免大小写/全角混用造成对照困难
|
||
ROLE_CODE_PATTERN = re.compile(r"^[a-z][a-z0-9_]{1,31}$")
|
||
|
||
|
||
class RoleOut(BaseModel):
|
||
"""角色基础响应(用户表单下拉与角色列表共用)。"""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: int
|
||
role_code: str
|
||
role_name: str
|
||
description: str | None = None
|
||
sort_order: int = 0
|
||
status: int = 1
|
||
|
||
|
||
class RoleManageOut(RoleOut):
|
||
"""角色管理列表项:额外携带绑定用户数,供前端提示删除保护的原因。"""
|
||
|
||
user_count: int = 0
|
||
permission_count: int = 0
|
||
|
||
|
||
class RoleCreateIn(BaseModel):
|
||
"""新增角色。"""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
role_code: str = Field(min_length=2, max_length=32, description="角色编码,创建后不可修改")
|
||
role_name: str = Field(min_length=1, max_length=64, description="角色名称")
|
||
description: str | None = Field(default=None, max_length=255)
|
||
sort_order: int = Field(default=0, ge=0, description="显示排序,越小越靠前")
|
||
permission_codes: list[str] | None = Field(default=None, max_length=300)
|
||
reason: str | None = Field(default=None, min_length=1, max_length=500)
|
||
|
||
@field_validator("role_code")
|
||
@classmethod
|
||
def _check_code(cls, value: str) -> str:
|
||
if not ROLE_CODE_PATTERN.match(value):
|
||
raise ValueError("角色编码须以小写字母开头,仅可含小写字母、数字与下划线")
|
||
return value
|
||
|
||
|
||
class RoleUpdateIn(BaseModel):
|
||
"""编辑角色。
|
||
|
||
**不含 role_code** —— 编码是授权判定的锚点,修改会让既有授权语义静默改变。
|
||
前端表单全量提交,故为全量替换语义。
|
||
"""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
role_name: str = Field(min_length=1, max_length=64, description="角色名称")
|
||
description: str | None = Field(default=None, max_length=255)
|
||
sort_order: int = Field(default=0, ge=0, description="显示排序,越小越靠前")
|
||
|
||
|
||
class RoleStatusIn(BaseModel):
|
||
"""启用/禁用角色。"""
|
||
|
||
status: int = Field(ge=0, le=1, description="1启用 0禁用")
|
||
|
||
|
||
class RoleQuery(PageQuery):
|
||
"""角色列表查询条件。"""
|
||
|
||
keyword: str | None = Field(default=None, max_length=64, description="编码或名称,模糊匹配")
|
||
status: int | None = Field(default=None, ge=0, le=1, description="状态筛选")
|