背景: - 开发计划 A1 批次要求登录/刷新/登出、用户生命周期、路由与按钮权限、 权限三态用例;视觉已选定方案 B(主从分栏 + 分屏登录)。 改动: - packages/shared:新增 identity 契约(User/Me/Login/Refresh/Department/Role) 与 user:view 权限点;写接口 strict 校验。 - apps/api:新增 identity 模块与 Prisma expand 迁移(departments/users/roles/ user_roles/role_permissions/refresh_tokens);Argon2id、JWT access+refresh 旋转与 jti 吊销、登录失败锁定、首登强改、守卫链、/me、@Audit 上下文、幂等种子。 - apps/web:按方案 B 落地 /users、/users/new、/users/:id、分屏登录与 403; usePermission/<Can>、路由守卫、401 单飞刷新、停用会话兜底、Semi + token。 - e2e:新增 A1-QA-01 六类端到端用例(A1_E2E 门控)。 - openapi 与前端生成类型同步;.env.example 登记认证变量;实施报告入库。 影响面: - 新增运行时依赖 @nestjs/jwt、argon2(计划 A1-BE-02 已批准,版本精确锁定)。 - 迁移为 expand,不改既有表;本地需 prisma migrate 后重新 seed。 - 平台导航「用户与组织」由 /platform/users 调整为 /users。 回滚方式: - 代码整体回滚本提交;迁移为 expand,回滚代码不需删表; - 本地如需彻底回退按实施报告 §6 处理。 验证证据: - pnpm format:check / lint / typecheck 通过; - pnpm test:shared 95 / api 60 / web 55; - pnpm test:int:7 个(真实 PG/Redis); - A1_E2E=1 playwright test e2e/a1-identity.spec.ts:6/6; - 报告:docs/开发计划/灵犀_A1身份认证与用户管理_实施报告_V0.1.md
This commit is contained in:
1 parent
f0d218bd99
commit
4d9eb980af
120 files changed
+13112
-314
No files matched your search
@@ -47,6 +47,132 @@ model IdempotencyKey {
|
||||
@@map("idempotency_keys")
|
||||
}
|
||||
|
||||
/// 部门:两级组织(一级部门 → 小组),父子用 parent_id 关联(A1 identity)
|
||||
model Department {
|
||||
id String @id @db.Char(26)
|
||||
code String @unique @db.VarChar(64)
|
||||
name String @db.VarChar(64)
|
||||
parentId String? @map("parent_id") @db.Char(26)
|
||||
leaderUserId String? @map("leader_user_id") @db.Char(26)
|
||||
sortOrder Int @default(0) @map("sort_order")
|
||||
status String @default("ACTIVE") @db.VarChar(16)
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
||||
|
||||
parent Department? @relation("DepartmentTree", fields: [parentId], references: [id])
|
||||
children Department[] @relation("DepartmentTree")
|
||||
leader User? @relation("DepartmentLeader", fields: [leaderUserId], references: [id])
|
||||
users User[] @relation("UserDepartment")
|
||||
|
||||
@@unique([parentId, name])
|
||||
@@index([parentId, sortOrder])
|
||||
@@map("departments")
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
ACTIVE
|
||||
DISABLED
|
||||
}
|
||||
|
||||
/// 用户:登录账号唯一、手机号唯一;生命周期只停用不删除(PRD R-02)
|
||||
model User {
|
||||
id String @id @db.Char(26)
|
||||
account String @unique @db.VarChar(64)
|
||||
email String? @unique @db.VarChar(128)
|
||||
name String @db.VarChar(64)
|
||||
phone String @unique @db.VarChar(20)
|
||||
passwordHash String @map("password_hash") @db.VarChar(255)
|
||||
departmentId String @map("department_id") @db.Char(26)
|
||||
jobTitle String? @map("job_title") @db.VarChar(64)
|
||||
status UserStatus @default(ACTIVE)
|
||||
mustChangePassword Boolean @default(true) @map("must_change_password")
|
||||
lastLoginAt DateTime? @map("last_login_at") @db.Timestamptz(3)
|
||||
lastLoginIp String? @map("last_login_ip") @db.VarChar(64)
|
||||
disabledAt DateTime? @map("disabled_at") @db.Timestamptz(3)
|
||||
remark String? @db.VarChar(200)
|
||||
createdBy String? @map("created_by") @db.Char(26)
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
||||
|
||||
department Department @relation("UserDepartment", fields: [departmentId], references: [id])
|
||||
roles UserRole[]
|
||||
refreshTokens RefreshToken[]
|
||||
ledDepartments Department[] @relation("DepartmentLeader")
|
||||
|
||||
@@index([departmentId, status])
|
||||
@@index([status, lastLoginAt])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
enum RoleStatus {
|
||||
ACTIVE
|
||||
DISABLED
|
||||
}
|
||||
|
||||
/// 角色:A1 只做绑定多选,权限点配置属 A2(此处先存角色-权限点)
|
||||
model Role {
|
||||
id String @id @db.Char(26)
|
||||
code String @unique @db.VarChar(64)
|
||||
name String @db.VarChar(64)
|
||||
description String @default("") @db.VarChar(255)
|
||||
isSystem Boolean @default(false) @map("is_system")
|
||||
status RoleStatus @default(ACTIVE)
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
||||
|
||||
users UserRole[]
|
||||
permissions RolePermission[]
|
||||
|
||||
@@map("roles")
|
||||
}
|
||||
|
||||
/// 用户-角色绑定(一人多角色)
|
||||
model UserRole {
|
||||
userId String @map("user_id") @db.Char(26)
|
||||
roleId String @map("role_id") @db.Char(26)
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([userId, roleId])
|
||||
@@index([roleId])
|
||||
@@map("user_roles")
|
||||
}
|
||||
|
||||
/// 角色-权限点:权限点取 @lingxi/shared PERMISSION_CODES,每请求实时解析(不缓存旧权限)
|
||||
model RolePermission {
|
||||
roleId String @map("role_id") @db.Char(26)
|
||||
permissionCode String @map("permission_code") @db.VarChar(64)
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||
|
||||
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([roleId, permissionCode])
|
||||
@@index([permissionCode])
|
||||
@@map("role_permissions")
|
||||
}
|
||||
|
||||
/// 刷新令牌:jti 作主键、只存哈希;旋转时旧 token 立刻吊销并记录 replaced_by_id
|
||||
model RefreshToken {
|
||||
id String @id @db.Char(26)
|
||||
userId String @map("user_id") @db.Char(26)
|
||||
familyId String @map("family_id") @db.Char(26)
|
||||
tokenHash String @unique @map("token_hash") @db.VarChar(64)
|
||||
userAgent String? @map("user_agent") @db.VarChar(255)
|
||||
ip String? @db.VarChar(64)
|
||||
expiresAt DateTime @map("expires_at") @db.Timestamptz(3)
|
||||
revokedAt DateTime? @map("revoked_at") @db.Timestamptz(3)
|
||||
replacedById String? @map("replaced_by_id") @db.Char(26)
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId, expiresAt])
|
||||
@@index([familyId])
|
||||
@@map("refresh_tokens")
|
||||
}
|
||||
|
||||
/// 系统配置:阈值 / 上限类数字唯一存放处(PRD R-26),带版本与变更审计
|
||||
model SystemConfig {
|
||||
id String @id @db.Char(26)
|
||||
|
||||
Reference in new issue
Block a user