Compare commits

..

No commits in common. "1790fb30a809eeec589d0be58bb367df49de75bf" and "aac8946727d3909dc947f6939da7e0bdf8452774" have entirely different histories.

13 changed files with 1 additions and 443 deletions

View File

@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-10-10

View File

@ -1,87 +0,0 @@
## Context
Phase 0 后端已具备:Prisma 6 + MySQL(14 张 `sys_` 表)、登录/刷新/登出(JWT access 2h + refresh 7d 旋转)、多租户/软删/data_scope 过滤(Prisma `$extends` + AsyncLocalStorage 上下文)、10 个域 CRUD、pino 日志、Swagger。`@RequirePermissions` / `@Public` 装饰器与 `PermissionsGuard` 已定义但未接线;`sys_audit_log` / `sys_login_log` 表已存在但无写入点;无 seed 脚本。动机见 proposal.md。
关键现状约束:
- 权限/角色/资源/组织/租户用 `code` 关联,用户用 `id`(见 shared/data-model)。
- `PrismaService` 对 create 不注入 `tenant_code`(避免 `$extends` 误注入 where),故写审计/登录日志需显式传 `tenantCode`。
- 无 Redis,仅 MySQL;权限集载入方案受此约束。
## Goals / Non-Goals
**Goals:**
- 三级鉴权落地:`@Public` / 无标记(仅登录)/ `@RequirePermissions`(登录+权限码)。
- 权限码数据驱动:登录时从 role_resource 聚合 `type ∈ {button, api}` 的 `resource.code`。
- `super_admin` 角色绕过权限码校验、data_scope 看全量。
- 审计日志、登录日志自动落库(异步、不阻塞业务)。
- 幂等 seed 脚本,一键初始化完整可登录的租户。
**Non-Goals:**
- 不做权限变更的即时生效(如每次请求查库、强制下线)。权限变更在下次登录/刷新后生效。
- 不做「在线会话管理 / 踢人下线」的 UI(SESSION 域后置)。
- 不改 DB schema、不改 shared 三层主 spec、不动前端。
## Decisions
### D1. 权限集与超管标志存 Redis(而非 JWT / 每请求查库)
登录成功时聚合权限码(`user_role → role_resource → resource`,取 `type ∈ {button, api}` 的 code)与 `super` 标志,写入 Redis;`PermissionsGuard` 每请求从 Redis 读取做集合判断。
- **key**:`{REDIS_KEY_PREFIX}perm:{tenantCode}:{userId}`;**value**:`{ super: boolean, permissions: string[] }`
- **TTL** 随会话(默认 7d,与 refresh 一致),刷新时续期,登出时删除。
- **备选**:JWT payload 承载(token 随资源数线性膨胀、权限变更延迟 ≤2h);每请求查库聚合(无缓存层、每请求多次 JOIN)。均放弃。
- **取舍**:引入 Redis 依赖,换来 token 最小、权限实时、多实例共享一致。
### D2. 权限实时失效(授权变更删缓存)
修改 `role_resource` / `user_role`(授权变更)后,删除该租户全部用户的权限缓存(SCAN `{prefix}perm:{tenantCode}:*` 后 DEL);下一次请求守卫重新聚合并回填。
- **简化**:租户级失效,不精确到受影响用户;脚手架规模可接受。
- 移除某人的 `super_admin` 角色同样经此路径即时生效(无需等 token 过期)。
### D3. 权限码语义:一个 code = 一个权限点,按钮与接口分用
`resource.code` 全局唯一,细粒度 `domain:action`。约定:
- **api 资源**:每个受控接口一个,code 即接口权限码(如 `user:create`),`@RequirePermissions` 引用它。
- **button 资源**:仅用于「纯前端按钮」(无独立受控接口的 UI 动作,如 `user:export`)。前端按钮若触发受控接口,直接复用该接口的 code 判定可见性,不重复登记 button 资源。
- **menu 资源**:导航菜单,code 不进入接口权限集。
- 避免「创建用户」按钮与「POST /users」接口争抢同一 code 的唯一键冲突。
### D4. 审计写入:`@AuditLog(module, action)` 装饰器 + 拦截器,异步快照
装饰器 `SetMetadata` 记录 `module`/`action`;拦截器在 `next.handle()` 的 `tap` 中:
- 请求进入时快照 `tenantCode`/`userId`/`username`/`ip`/`userAgent`/`method`/`path`/脱敏后的 `body` 与起始时间(避免异步阶段 AsyncLocalStorage 失效丢租户上下文);
- 完成后异步 fire-and-forget 写 `sys_audit_log`;失败仅记告警日志,不影响响应。
- 入参复用 BodyLoggingInterceptor 的递归脱敏(password/token/secret 等 → `***`)。
- 成功 `status=success`,异常 `status=fail` + `error_message`。
### D5. 登录日志写入:auth.service 内联记录
登录成功/失败在 `auth.service.login` 的对应分支直接写 `sys_login_log`(成功 status=success;失败 status=fail + message 原因:密码错误/账号停用/锁定/租户不存在)。同步写失败仅告警,不改变登录结果。
### D6. seed 数据建模与幂等
`prisma/seed.ts` 用裸 `PrismaClient`(不经 `$extends`),全部按唯一键 upsert,可重复执行:
- 租户 `touyun`;根组织 `touyun`。
- 角色 `super_admin`(`data_scope=all`)、可选 `admin`(`data_scope=all`)。
- 用户 `touyun`(`bcryptjs` 哈希 `Touyun@123`),绑定 `super_admin`。
- 资源树:菜单(工作台 + 各域导航)、api(每个受控接口一个,code=`domain:action`)、button(纯前端按钮)。
- `role_resource`:`super_admin` 关联全部资源 code。
- 密码哈希用 `bcryptjs`,与运行时代码同库。
### D7. 权限码域清单(domain:action)
`user` / `role` / `resource` / `org` / `dict` / `config` / `tenant` / `audit` / `login-log`;动作 `list`/`create`/`update`/`delete`/`reset-pwd`/`assign`/`assign-resource` 等,按各域实际接口对齐。
### D8. Redis 接入方式
客户端用 `ioredis`;连接与 key 前缀均来自 env:`REDIS_HOST`/`REDIS_PORT`/`REDIS_DB`/`REDIS_PASSWORD`(默认 127.0.0.1:6379)、`REDIS_KEY_PREFIX`(默认 `sysmgr:`)。多 fork 项目共用一个 Redis 实例时按前缀隔离,互不干扰。
## Risks / Trade-offs
- [新增 Redis 依赖] → Redis 已在 Docker 常驻,key 前缀隔离多项目;若某 fork 项目无 Redis,需自行提供回退(点查)能力,属后续可选。
- [权限缓存与 DB 短暂不一致(删 key 窗口)] → 删 key 后下一次请求即回填,窗口毫秒级。
- [异步审计可能丢日志(D4)] → 进程崩溃窗口极小;失败告警日志兜底,审计定位问题足够。
- [审计 params 含敏感数据] → 复用脱敏,落库前过滤 password/token/secret 等。
- [seed 覆盖既有同名数据] → upsert 语义「更新已有、补齐缺失」;生产慎跑,仅初始化用。
- [接口漏挂 `@RequirePermissions` 导致裸奔] → 默认仅登录(宽松),收口只覆盖核心管理域;后续可升级为「默认拒绝」需全量盘点。
## Migration Plan
- 无 DB schema 变更;表已存在。
- 部署步骤:① 合并代码 ② 配置 `REDIS_HOST` / `REDIS_KEY_PREFIX` 等 env(指向常驻 Redis)③ `pnpm prisma:seed`(幂等,可重复)④ 重启服务。
- 回滚:`git revert`;seed 数据无需回滚(幂等);Redis 缓存可整体清空(登录时自动回填)。
## Open Questions
- 权限变更后是否需强制失效既有 access token(属 SESSION 域「在线会话/踢人」,后置)。

View File

@ -1,35 +0,0 @@
## Why
Phase 0 后端已实现认证、10 个域 CRUD、多租户/软删/data_scope 过滤,但有三处「声明未生效」:① `@RequirePermissions` 已定义却零接口使用,任何登录用户都能增删改任意资源;② `sys_audit_log` / `sys_login_log` 只有查询接口、无任何写入点;③ 无 seed 脚本,新 fork 无法一键初始化。这三点是「脚手架」区别于普通 CRUD 的核心价值,fork 出去即是安全漏洞 + 排障盲区。本次收口。
## What Changes
- 三级鉴权真正落地:`@Public` 不登录 → 无标记仅登录 → `@RequirePermissions('domain:action')` 登录+权限码;无标记接口默认仅登录。
- 权限码数据驱动:权限码 = 资源表 `api`/`button` 型资源的 `code`(细粒度 `domain:action`),登录时聚合进请求上下文,守卫做集合判断。
- 超管绕过:角色 `code=super_admin` 绕过权限码校验、data_scope 看全量。
- 审计日志落库:`@AuditLog(domain, action)` 装饰器 + 拦截器自动记录写操作(增/删/改)与登录/登出,异步写入,失败不阻塞业务。
- 登录日志落库:登录成功与失败均记录(失败附原因),含 IP/UA。
- 新增 seed 脚本:一键初始化租户 `touyun`、用户 `touyun`(密码 `Touyun@123`)、`super_admin` 角色、全量资源树(菜单+按钮+api)并全授 super_admin。
## Capabilities
### New Capabilities
- `backend/auth`: 三级鉴权执行语义、super_admin 绕过、登录时权限集入上下文
- `backend/res`: 资源表作为 RBAC 授权锚点的运行时语义(权限码 = 资源 code,细粒度 `domain:action`)
- `backend/audit`: 审计日志写入(写操作 + 登录/登出,装饰器+拦截器,异步)
- `backend/login`: 登录日志写入(成功+失败,IP/UA/失败原因)
### Modified Capabilities
(无 —— 本变更不修改 shared 三层主 spec,均为后端新能力)
## Impact
- 后端 `sources/sysmgr-nest`:
- 新增 Redis 依赖(ioredis)+ RedisService,权限集存 Redis(key 前缀经 `REDIS_KEY_PREFIX` 隔离多 fork 项目)
- `auth/`:permissions.guard 从 Redis 读权限集与 super 标志做判断;auth.service 登录/刷新时聚合权限码写 Redis
- `resources/ users/ roles/ orgs/ dicts/ configs/ tenants/`:写接口挂 `@RequirePermissions` + `@AuditLog`;授权变更后删权限缓存
- 新增审计/登录日志写入服务(统一拦截器)
- `prisma/seed.ts` + package.json seed 脚本
- 无 DB schema 变更(表已存在);无 shared spec 变更。
- 新增基础设施依赖:Redis(Docker 常驻,key 前缀隔离多项目)。
- 前端不受影响:权限码由资源表下发,前端菜单/按钮后续按此渲染。

View File

@ -1,38 +0,0 @@
## Purpose
定义操作日志(审计)的自动落库行为:写操作与登录/登出由装饰器与拦截器统一记录,异步写入不阻塞业务。
## ADDED Requirements
### Requirement: 写操作自动审计
系统 SHALL 对标注 `@AuditLog(module, action)` 的写接口(增/删/改)在请求完成后异步写入 sys_audit_log,记录操作人、模块、动作、请求方法、路径、入参、结果状态、IP、UA 与耗时。
#### Scenario: 写操作成功
- **WHEN** 标注 `@AuditLog` 的写接口执行成功
- **THEN** 系统写入一条 audit_log,status=success,含 module/action/method/path/user_id/username/ip/user_agent/duration_ms
#### Scenario: 写操作失败
- **WHEN** 标注 `@AuditLog` 的写接口执行失败
- **THEN** 系统写入一条 audit_log,status=fail,并记录 error_message
#### Scenario: 查询不审计
- **WHEN** 查询类(读)接口执行
- **THEN** 系统不写入审计日志
### Requirement: 登录登出审计
系统 SHALL 记录登录成功与登出事件到 audit_log(module=auth,action=login/logout)。
#### Scenario: 登录成功审计
- **WHEN** 用户登录成功
- **THEN** 系统写入 audit_log(module=auth, action=login, status=success)
#### Scenario: 登出审计
- **WHEN** 用户登出
- **THEN** 系统写入 audit_log(module=auth, action=logout)
### Requirement: 审计异步不阻塞
审计日志 SHALL 异步写入;写入失败仅告警,不得影响业务请求结果。
#### Scenario: 审计写入失败
- **WHEN** 审计日志写库失败
- **THEN** 系统记录告警日志,业务请求照常返回成功结果

View File

@ -1,42 +0,0 @@
## Purpose
定义后端三级鉴权的执行语义与超级管理员绕过规则,作为所有接口访问控制的统一契约。
## ADDED Requirements
### Requirement: 三级鉴权
系统 SHALL 对每个接口按三级放行:标注 `@Public` 的接口无需登录即可访问;未标注任何权限装饰器的接口仅校验有效登录态;标注 `@RequirePermissions` 的接口要求登录态且当前用户拥有对应权限码。
#### Scenario: 公开接口匿名访问
- **WHEN** 请求标注 `@Public` 的接口(如登录、刷新 token、接口文档)
- **THEN** 系统无需 token 即放行
#### Scenario: 未标注接口仅校验登录
- **WHEN** 未登录请求未标注权限装饰器的接口
- **THEN** 系统返回 401 未登录
#### Scenario: 未标注接口登录后放行
- **WHEN** 已登录用户请求未标注权限装饰器的接口
- **THEN** 系统放行
#### Scenario: 需权限接口无权限码
- **WHEN** 已登录用户请求 `@RequirePermissions('user:create')` 的接口,但其权限集不含 `user:create`
- **THEN** 系统返回 403 无权限
### Requirement: 权限集入上下文
登录成功 SHALL 将用户的权限码集合(其全部角色经资源关联聚合得到的 api/button 资源 code)载入请求上下文,供权限守卫校验。
#### Scenario: 登录聚合权限
- **WHEN** 用户登录成功
- **THEN** 系统按用户全部角色的 role_resource 关联聚合出权限码集合并载入上下文
### Requirement: 超级管理员绕过
拥有 `code=super_admin` 角色的用户 SHALL 绕过权限码校验;其列表查询的数据权限按「全部」处理,不受 data_scope 过滤。
#### Scenario: 超管访问任意需权限接口
- **WHEN** 拥有 `super_admin` 角色的用户请求任意 `@RequirePermissions` 接口
- **THEN** 系统无需校验其是否拥有该权限码即放行
#### Scenario: 超管查看全量数据
- **WHEN** 超管执行列表查询
- **THEN** 系统不做 data_scope 部门过滤,返回全量数据

View File

@ -1,23 +0,0 @@
## Purpose
定义登录日志的自动落库行为:每次登录成功与失败均记录,失败附原因与来源信息。
## ADDED Requirements
### Requirement: 登录成功记录
系统 SHALL 在登录成功时写入 sys_login_log,记录 username、ip、user_agent、status=success。
#### Scenario: 登录成功
- **WHEN** 用户登录成功
- **THEN** 系统写入 login_log(username/ip/user_agent/status=success)
### Requirement: 登录失败记录
系统 SHALL 在登录失败时写入 sys_login_log,记录 username、ip、user_agent、status=fail 与失败原因 message。
#### Scenario: 密码错误
- **WHEN** 用户以错误密码登录失败
- **THEN** 系统写入 login_log,status=fail,message 说明密码错误
#### Scenario: 账号停用或锁定
- **WHEN** 停用或锁定账号尝试登录失败
- **THEN** 系统写入 login_log,status=fail,message 说明账号状态原因

View File

@ -1,30 +0,0 @@
## Purpose
定义资源表作为 RBAC 授权锚点的运行时语义:权限码即资源 code,采用细粒度 domain:action 命名。
## ADDED Requirements
### Requirement: 权限码即资源 code
系统 SHALL 以资源表的 `code` 作为接口与按钮的权限标识;`@RequirePermissions` 的参数 SHALL 为该 code 字符串。
#### Scenario: 接口声明权限码
- **WHEN** 接口声明 `@RequirePermissions('user:create')`
- **THEN** 系统校验当前用户权限集是否包含字符串 `user:create`
### Requirement: 权限码格式
权限码 SHALL 采用细粒度 `domain:action` 格式(domain 为域,action 为动作,如 `user:list`、`user:create`、`role:assign`)。
#### Scenario: 细粒度权限码
- **WHEN** 为「创建用户」接口登记权限码
- **THEN** 该接口资源 code 为 `user:create`
### Requirement: 权限码聚合来源
用户权限集 SHALL 由其全部角色的 role_resource 关联(role_code + resource_code),取其中 `type ∈ {button, api}` 的 resource.code 并集聚合得到。
#### Scenario: 多角色权限并集
- **WHEN** 用户拥有两个角色,各自关联了部分资源
- **THEN** 该用户权限集为两角色资源 code 的并集
#### Scenario: 菜单不计入接口权限集
- **WHEN** 用户角色关联了 `type=menu` 的资源
- **THEN** 该 menu 的 code 不进入接口权限集(菜单用于导航渲染,不用于接口鉴权)

View File

@ -1,44 +0,0 @@
## 1. Redis 接入与权限模型(auth 域)
- [x] 1.1 引入 ioredis 依赖,封装 RedisService(get/set/del/按前缀 SCAN 删除),连接用 env(REDIS_HOST/PORT/DB/PASSWORD、REDIS_KEY_PREFIX,默认 127.0.0.1:6379、`sysmgr:`)
- [x] 1.2 auth.service 登录/刷新时聚合权限码集(user_role → role_resource → resource,取 `type ∈ {button, api}` 的 code)与 `super` 标志(是否绑定 `code=super_admin`),写入 Redis `{prefix}perm:{tenantCode}:{userId}`(TTL 随会话,刷新续期);登出删除
- [x] 1.3 jwt-auth.guard 校验通过后将 userId/tenantCode/username 装入 `request.user`(JWT 不放权限集)
- [x] 1.4 permissions.guard 从 Redis 读 `{super, permissions}`:`super=true` 直接放行;否则校验权限集是否包含全部所需权限码,缺一返回 403
- [x] 1.5 data_scope 过滤对 `super=true` 按「全部」处理(resolveDataScope 或各列表查询入口统一短路)
- [x] 1.6 授权变更失效:roles 模块改 role_resource / user_role 后删除该租户权限缓存(SCAN `{prefix}perm:{tenantCode}:*`)
## 2. 权限码挂接口
- [x] 2.1 定义各域权限码常量清单(`domain:action`):tenant/user/role/resource/org/dict/config/audit/login-log 的 list/create/update/delete 等
- [x] 2.2 tenants、users、roles、resources、orgs、dicts、configs 各 controller 的全部受控接口挂 `@RequirePermissions(...)`(写操作强制;list 按 domain:list 挂)
- [x] 2.3 audit-logs、login-logs 查询接口挂 `audit:list` / `login-log:list`
- [x] 2.4 登录/刷新/登出及 `/docs` 保持 `@Public`,无需权限码
## 3. 审计日志落库
- [x] 3.1 新增 `@AuditLog(module, action)` 装饰器(SetMetadata 记录 module/action)
- [x] 3.2 新增 AuditLogInterceptor:请求进入时快照 tenantCode/userId/username/ip/userAgent/method/path/脱敏 body 与起始时间;完成后异步 fire-and-forget 写入;异常时记告警不阻塞
- [x] 3.3 新增 AuditLogService:写 sys_audit_log(显式传 tenantCode,复用 BodyLogging 脱敏)
- [x] 3.4 注册 AuditLogInterceptor(全局);给各写接口 controller 挂 `@AuditLog(domain, action)`
- [x] 3.5 登录成功/登出写 audit_log(module=auth,action=login/logout)
## 4. 登录日志落库
- [x] 4.1 新增 LoginLogService:写 sys_login_log(username/ip/user_agent/status/message)
- [x] 4.2 auth.service.login 成功分支写 status=success;失败分支写 status=fail + message(密码错误/账号停用/锁定/租户不存在),并附 IP/UA
## 5. seed 脚本
- [x] 5.1 新增 prisma/seed.ts:裸 PrismaClient,按唯一键 upsert,可重复执行
- [x] 5.2 生成租户 `touyun`、根组织、角色 `super_admin`(data_scope=all)、用户 `touyun`(bcryptjs 哈希 `Touyun@123`)并绑定 super_admin
- [x] 5.3 生成全量资源树:菜单(工作台 + 各域导航)、api(每个受控接口一个,code=`domain:action`,含 method/api_path)、button(纯前端按钮)
- [x] 5.4 `role_resource` 将 super_admin 关联全部资源 code
- [x] 5.5 package.json 增加 seed 脚本(prisma db seed 配置)并跑通
## 6. 验证
- [x] 6.1 跑 seed 后以 `touyun` / `Touyun@123` 登录成功,Redis 中生成 `{prefix}perm:touyun:{userId}` 缓存(含 permissions/super)
- [x] 6.2 超管访问受控接口放行、列表查询全量;新建一个无权限的普通用户访问受控接口返回 403
- [x] 6.3 修改某角色授权(role_resource)后,该租户权限缓存被删除,受影响用户下一次请求按新权限生效
- [x] 6.4 写操作后 sys_audit_log 有记录(status/params 脱敏);登录成功/失败后 sys_login_log 有记录(失败含原因)
- [x] 6.5 回归:登录/刷新/登出、现有 CRUD、日志输出均正常;测试后停止服务

View File

@ -1,40 +0,0 @@
# Audit Log Specification
## Purpose
定义操作日志(审计)的自动落库行为:写操作与登录/登出由装饰器与拦截器统一记录,异步写入不阻塞业务。
## Requirements
### Requirement: 写操作自动审计
系统 SHALL 对标注 `@AuditLog(module, action)` 的写接口(增/删/改)在请求完成后异步写入 sys_audit_log,记录操作人、模块、动作、请求方法、路径、入参、结果状态、IP、UA 与耗时。
#### Scenario: 写操作成功
- **WHEN** 标注 `@AuditLog` 的写接口执行成功
- **THEN** 系统写入一条 audit_log,status=success,含 module/action/method/path/user_id/username/ip/user_agent/duration_ms
#### Scenario: 写操作失败
- **WHEN** 标注 `@AuditLog` 的写接口执行失败
- **THEN** 系统写入一条 audit_log,status=fail,并记录 error_message
#### Scenario: 查询不审计
- **WHEN** 查询类(读)接口执行
- **THEN** 系统不写入审计日志
### Requirement: 登录登出审计
系统 SHALL 记录登录成功与登出事件到 audit_log(module=auth,action=login/logout)。
#### Scenario: 登录成功审计
- **WHEN** 用户登录成功
- **THEN** 系统写入 audit_log(module=auth, action=login, status=success)
#### Scenario: 登出审计
- **WHEN** 用户登出
- **THEN** 系统写入 audit_log(module=auth, action=logout)
### Requirement: 审计异步不阻塞
审计日志 SHALL 异步写入;写入失败仅告警,不得影响业务请求结果。
#### Scenario: 审计写入失败
- **WHEN** 审计日志写库失败
- **THEN** 系统记录告警日志,业务请求照常返回成功结果

View File

@ -1,44 +0,0 @@
# Auth Specification
## Purpose
定义后端三级鉴权的执行语义与超级管理员绕过规则,作为所有接口访问控制的统一契约。
## Requirements
### Requirement: 三级鉴权
系统 SHALL 对每个接口按三级放行:标注 `@Public` 的接口无需登录即可访问;未标注任何权限装饰器的接口仅校验有效登录态;标注 `@RequirePermissions` 的接口要求登录态且当前用户拥有对应权限码。
#### Scenario: 公开接口匿名访问
- **WHEN** 请求标注 `@Public` 的接口(如登录、刷新 token、接口文档)
- **THEN** 系统无需 token 即放行
#### Scenario: 未标注接口仅校验登录
- **WHEN** 未登录请求未标注权限装饰器的接口
- **THEN** 系统返回 401 未登录
#### Scenario: 未标注接口登录后放行
- **WHEN** 已登录用户请求未标注权限装饰器的接口
- **THEN** 系统放行
#### Scenario: 需权限接口无权限码
- **WHEN** 已登录用户请求 `@RequirePermissions('user:create')` 的接口,但其权限集不含 `user:create`
- **THEN** 系统返回 403 无权限
### Requirement: 权限集入上下文
登录成功 SHALL 将用户的权限码集合(其全部角色经资源关联聚合得到的 api/button 资源 code)载入请求上下文,供权限守卫校验。
#### Scenario: 登录聚合权限
- **WHEN** 用户登录成功
- **THEN** 系统按用户全部角色的 role_resource 关联聚合出权限码集合并载入上下文
### Requirement: 超级管理员绕过
拥有 `code=super_admin` 角色的用户 SHALL 绕过权限码校验;其列表查询的数据权限按「全部」处理,不受 data_scope 过滤。
#### Scenario: 超管访问任意需权限接口
- **WHEN** 拥有 `super_admin` 角色的用户请求任意 `@RequirePermissions` 接口
- **THEN** 系统无需校验其是否拥有该权限码即放行
#### Scenario: 超管查看全量数据
- **WHEN** 超管执行列表查询
- **THEN** 系统不做 data_scope 部门过滤,返回全量数据

View File

@ -1,25 +0,0 @@
# Login Log Specification
## Purpose
定义登录日志的自动落库行为:每次登录成功与失败均记录,失败附原因与来源信息。
## Requirements
### Requirement: 登录成功记录
系统 SHALL 在登录成功时写入 sys_login_log,记录 username、ip、user_agent、status=success。
#### Scenario: 登录成功
- **WHEN** 用户登录成功
- **THEN** 系统写入 login_log(username/ip/user_agent/status=success)
### Requirement: 登录失败记录
系统 SHALL 在登录失败时写入 sys_login_log,记录 username、ip、user_agent、status=fail 与失败原因 message。
#### Scenario: 密码错误
- **WHEN** 用户以错误密码登录失败
- **THEN** 系统写入 login_log,status=fail,message 说明密码错误
#### Scenario: 账号停用或锁定
- **WHEN** 停用或锁定账号尝试登录失败
- **THEN** 系统写入 login_log,status=fail,message 说明账号状态原因

View File

@ -1,32 +0,0 @@
# Resource Authorization Specification
## Purpose
定义资源表作为 RBAC 授权锚点的运行时语义:权限码即资源 code,采用细粒度 domain:action 命名。
## Requirements
### Requirement: 权限码即资源 code
系统 SHALL 以资源表的 `code` 作为接口与按钮的权限标识;`@RequirePermissions` 的参数 SHALL 为该 code 字符串。
#### Scenario: 接口声明权限码
- **WHEN** 接口声明 `@RequirePermissions('user:create')`
- **THEN** 系统校验当前用户权限集是否包含字符串 `user:create`
### Requirement: 权限码格式
权限码 SHALL 采用细粒度 `domain:action` 格式(domain 为域,action 为动作,如 `user:list`、`user:create`、`role:assign`)。
#### Scenario: 细粒度权限码
- **WHEN** 为「创建用户」接口登记权限码
- **THEN** 该接口资源 code 为 `user:create`
### Requirement: 权限码聚合来源
用户权限集 SHALL 由其全部角色的 role_resource 关联(role_code + resource_code),取其中 `type ∈ {button, api}` 的 resource.code 并集聚合得到。
#### Scenario: 多角色权限并集
- **WHEN** 用户拥有两个角色,各自关联了部分资源
- **THEN** 该用户权限集为两角色资源 code 的并集
#### Scenario: 菜单不计入接口权限集
- **WHEN** 用户角色关联了 `type=menu` 的资源
- **THEN** 该 menu 的 code 不进入接口权限集(菜单用于导航渲染,不用于接口鉴权)

@ -1 +1 @@
Subproject commit ce2aa35a24ab351433ae7bb98aba2cab38c7e676
Subproject commit c680fb9dc7b5b39c03e029f72ffc2776d2c731d5