# CLAUDE.md

本文件为 `C:\code\gitea\rdms\cn-rdms` 仓库下所有 Agent（含 Claude Code）的常驻工作指引；`AGENTS.md` 引用本文件。

## 工作方式

- 默认先给执行方案：目标、涉及模块、改动点、验证方式；不擅自动手。
- 用户只要分析或评审时，停在分析层；不要顺手开工。
- 描述仓库现状以**当前**代码、配置、文档可验证的事实为准；不要拿历史实现、过渡方案或已废弃模型解释当前状态。
- **输出极简**：先给结论、改动点、必要风险；用自然语言给判断和影响面，少贴代码片段；涉及代码用 `file_path:line_number` 引用；用户追问再展开。
- **下定论需要充足证据**。疑似 bug 时先判断是否稳定复现：跑了很久没动过的功能**首次**报错，优先怀疑运行时状态污染（devtools / IDE 热替换、ApplicationContext 残留、缓存、Redis / DB 连接、JVM 静态字段被旧 context 设过等），**不要凭单次堆栈就断言代码 bug，更不要直接甩修改方案**。先给"可能原因 + 最便宜的取证步骤"（多数场景是**冷重启 JVM**），用户确认能稳定复现，再讨论代码层面的修复。同款写法在仓库其它位置存在并不能反推"也是 bug"，长期能跑的代码突然失效 ≠ 代码本身错。
- **技术风险判断（性能 / N+1 / 索引缺失 / 架构缺陷 / 并发安全 / 内存泄漏 等）与"bug 判断"同等严格**：未读到实现层不下结论。不要凭 subagent 摘要、字段名、注释或印象"顺嘴提一句风险/瓶颈/可能问题"——那也是下结论，**且杀伤力更大**：用户会基于"风险提示"决定要不要立项整改。如果当前上下文没核实到实现，就明说"这部分未核实，需要打开 X 文件确认"，不要把猜测包装成"风险提示"塞出去。已识别教训：执行进度查询答完"已批量聚合无 N+1"后又凭印象抛"列表 N+1 风险"，被追问才收回。

## 本机环境

- JDK：必须使用 `JDK 17`，路径 `C:\Program Files\Java\jdk-17`。不要使用 JDK 8 / 11 / 其他版本。
- Maven：`C:\software\apache-maven-3.8.9`，命令优先用完整路径 `C:\software\apache-maven-3.8.9\bin\mvn.cmd`。不要假设 `mvn` 在 PATH。
- 执行任何 Maven / java 命令前，先确认当前 shell 的 `JAVA_HOME` 指向 JDK 17，且 `java -version` 输出 17；否则在该命令上下文中显式切换。
- MySQL 客户端：本机已装命令行客户端 `C:\software\mysql-8.4.9-winx64\bin\mysql.exe`，Agent 可直接连库跑 SQL（查数据、核对状态、验证库结构），无需另装工具。连接账密**不写进本文件**，以 `application-local.yaml` 的 `spring.datasource` 段为准（开发库 `192.168.1.22:13306`/`rdms_view`）；**演示库是另一套，账密以用户当场给的为准**。**读随便跑；写库（DDL/DML）务必先确认是哪套库**，开发库改动按本文件§数据与 SQL 出演示库补丁，绝不擅自改演示库。调用细节（PowerShell 必须逐参数加单引号否则 host 被拆、字符集、常用查询）见 [`docs/agent/MySQL客户端连库手册.md`](./docs/agent/MySQL客户端连库手册.md)。

## 仓库结构

多模块 Maven 单仓库，Java 17，Spring Boot 3.5.9，根模块打包 `pom`。

顶层模块：
1. `rdms-system` — 系统域（用户/组织/岗位/菜单/角色/权限）
2. `rdms-project` — RDMS 核心交付域（项目集/项目/产品/需求/任务/工单/执行）
3. `rdms-framework` — 共享框架与内部 starter
4. `rdms-gateway` — Spring Cloud Gateway 网关

每个业务模块按 `xxx-api` + `xxx-boot` 拆分：
- `*-api`：对外 RPC/Feign 接口、DTO、错误码、枚举、常量
- `*-boot`：启动类、controller、service、dal、convert、api 实现、模块级 framework 配置

主包/启动类：
- `rdms-system-boot`：`com.njcn.rdms.module.system.SystemServerApplication`
- `rdms-project-boot`：`com.njcn.rdms.module.project.ProjectServerApplication`
- `rdms-gateway`：`com.njcn.rdms.gateway.GatewayServerApplication`

`rdms-framework` 子模块：`rdms-common` 及一组 `rdms-spring-boot-starter-*`（`env`、`web`、`rpc`、`security`、`mybatis`、`redis`、`mq`、`websocket`、`excel`、`protection`、`test`、`biz-ip`）。

## 模块演进判断

新增能力时**先判断落点**：
- 落在现有 `rdms-system` 子域 → 沿用 `controller/admin|app`、`service`、`dal/dataobject`、`dal/mysql`、`convert` 的现有结构，跨模块暴露时在 `rdms-system-api` 补 API/DTO/错误码/枚举。
- 落在现有 `rdms-project` 子域 → 同理，跨模块走 `rdms-project-api`。
- 已具备独立服务边界（如未来的 `rdms-workflow`）→ 新建 `rdms-xxx` + `rdms-xxx-api` + `rdms-xxx-boot`，根 `pom.xml` 加聚合，包路径 / `spring.application.name` / `ApiConstants` / `RpcConstants` / `rdms.info.base-package` 保持一致。

不要：
- 把后续业务长期堆进 `rdms-system`。
- 为新增子域引入一套平行的 `application/domain/infrastructure/adapter` 分层。

## 分层职责

| 层 | 职责 | 红线 |
|---|---|---|
| `rdms-framework` | 基础能力 | 不承载业务语义；除非框架级缺陷或全局基础设施，不要把业务判断塞进来 |
| `rdms-gateway` | 入口、令牌校验、登录用户透传、路由、横切 | 不要在这里承载组织/成员/负责人/项目/产品/工作流状态/数据可见性 |
| Controller | HTTP 暴露、参数校验、权限注解、结果封装 | 不要编排复杂业务流程，不要直接操作多个 mapper；用 `ReqVO`/`RespVO`，不要直接暴露 DO |
| Service | 业务规则、事务、缓存、领域编排 | 已有领域优先扩展，不要为"整齐"平移；不要把规则散到 controller / mapper / util |
| DAL | DO + Mapper | Mapper 继承 `BaseMapperX<T>`；查询优先 `LambdaQueryWrapperX` 与默认方法封装；非必要不回退 XML；不承担领域校验 |
| Convert | 已有 `convert` 风格继续沿用，简单场景直接 `BeanUtils` | 不要强推全员 MapStruct，也不要反过来把已有 convert 全删 |

## 认证与跨模块调用

- 默认沿用 OAuth2 / Token / `LoginUser` / `login-user` 透传主链。**不要**另造 ThreadLocal / Session / 自定义 header。
- 业务逻辑落 `*-boot`；可复用契约落 `*-api`；可复用框架能力落 `rdms-framework`。跨模块/跨服务必须通过 `*-api` 定义契约，**不要直接依赖别人 `*-boot` 的 service 或 mapper（必须走 `*-api`）**；改跨模块 API 时，`*-boot` 实现与对应 `*-api` 契约同步更新。

### 鉴权：必须按"全域 / 对象域"分通道挂

系统有**两条互不交叉**的权限通道，挂错通道 = 永远 403。新增/修改接口前必须先判断它属于哪一域：

| 通道 | 适用场景 | 注解 / 实现 | 角色与菜单 |
|---|---|---|---|
| **全域 global** | 传统 RBAC 顶层菜单与"项目管理界面"——选择对象**之前**的所有动作（建项目、列项目、菜单/角色/用户管理等） | Controller 上 `@PreAuthorize("@ss.hasPermission('xxx')")`，由 `PermissionServiceImpl.hasAnyPermissions` 处理 | `system_role.scope_type='global'` + `system_menu.scope_type='global'` |
| **对象域 object** | 用户**已选择某个对象**（如某个项目/产品）后，对象内部的一切操作（任务、执行、工时、协办人、需求、成员维护等） | Service 上 `@CheckObjectPermission(objectType=..., objectId="#xxxId", permission="...")`，由 `ObjectPermissionAspect` → `ObjectPermissionService.checkPermission` 处理 | `system_role.scope_type='object'` + `system_menu.scope_type='object'` + `object_type` |

红线：

- **对象内接口绝不能挂 `@PreAuthorize("@ss.hasPermission(...)")`**。该注解走的链路在 `PermissionServiceImpl` 里强制按 GLOBAL 取角色（line 343-347）+ 强制按 GLOBAL 查菜单（line 92-94），对象域角色与对象域菜单都进不来，即使授权配置完全正确也必然 403。
- **对象域权限校验必须落在 Service 层**，默认写法是方法上挂 `@CheckObjectPermission`，原因：路径里 `objectId` 通常以 `#projectId`/`#productId` 等 SpEL 解析，Controller 的参数校验前置阶段不便复用；与同模块（`ProjectMemberServiceImpl` / `ProjectExecutionAssigneeServiceImpl` 等）保持一致。
- **第二种正规写法：属主放行 + 权限码兜底（方法体内裁决）**。任务、执行的创建 / 编辑 / 删除 / 删除预检采用"项目/执行/任务负责人身份命中直接放行，未命中回落对象域权限码"的口径，权限裁决在方法体内调 `ProjectObjectAuthorizationService`（入口见 `ProjectTaskServiceImpl.requireTaskEditDeletePermission` / `ProjectExecutionServiceImpl.requireExecutionEditDeletePermission`）。这些方法**故意不挂** `@CheckObjectPermission`——注解是前置拦截，会把"无权限码的负责人"挡死。**不要误判为漏鉴权把注解补回去**；盘点权限面时除了搜注解，还要搜 `ProjectObjectAuthorizationService` 的调用点。口径来源：`docs/superpowers/specs/2026-07-07-执行编辑删除权限口径-design.md`。
- **同一接口不要两条通道叠加**。要么全域，要么对象域；叠加只会让对象域用户被全域那条卡死。
- 对象内**读路径**（列表 / 详情 / 状态看板 / 聚合）同样要挂 `@CheckObjectPermission(objectType=PROJECT, permission=...PERMISSION_QUERY)`——查询同样扫库耗资源，必须按对象域鉴权。**Controller 方法层一律不挂权限注解**，新增读接口照此在 Service 层挂对象域权限；不要只在 Controller 留空，更不要误判"Controller 没注解 = 无鉴权"。

判定口诀：**URL 里有 `{projectId}` / `{productId}` 等对象 ID → 对象域；没有 → 全域**。

### 站内信通知事件（project 域统一入口）

业务动作要发站内信时，统一走 `framework/notify` 的 `NotifySendEvent`（`publishEvent(NotifySendEvent.of(userIds, templateCode, params))`），由全局唯一的 `NotifySendEventListener`（`@TransactionalEventListener(AFTER_COMMIT)`）做去重 / 兜底 / 调 `NotifyMessageSendApi`；不要绕过事件直接在业务 Service 里注入发送 API。

红线：

- **`NotifySendEvent` 只能在 `@Transactional` 的业务方法里 publish**。`@TransactionalEventListener` 的语义是"只在有事务的上下文里 publish 才会被消费"——在无事务方法里 publish，通知会**静默不发、不报错**。新业务接入通知前先确认触发点方法有事务；确实无事务的场景不要"顺手 publish"，要么补事务，要么找用户确认方案。
- 模板场景码登记进 `NotifyTemplateCodeConstants`，并保证 `system_notify_template` 已配同 code 模板（模板缺失时监听器只 `log.warn`，该场景通知发不出但不影响业务）。

## 接口语义(HTTP 动词)

本仓库 update 类接口默认按 RESTful 标准用 HTTP 动词区分语义,前后端必须按下表对齐,避免"前端没传字段"和"前端想清空"在后端无法区分的歧义。

| 动词 | 语义 | 字段处理规则 |
|---|---|---|
| **PUT** | 全资源替换 | 前端必须把表单完整状态回传(读到的非必填字段也要原样回传)。后端按字段值落库:**有值=更新,`null`=清空**。DO 字段加 `@TableField(updateStrategy = FieldStrategy.ALWAYS)` 跳过全局 `NOT_NULL` 让 `null` 真的写库 |
| **PATCH** | 部分字段更新 | **本仓库暂不引入 PATCH 接口**。如果有"只改一两个字段"的需求,用专门的子动作接口(参考 `assignees/{id}/inactive`、`status` 这种语义化路径),不要在 update 接口里靠旁路标记(如 `clearXxx: true`)模拟 PATCH |
| **DELETE** | 资源删除 | 软删走全局 `deleted` 列,不需要参数 body |

红线:

- **不要在 update 类接口的 ReqVO 里加 `clearXxx: Boolean` 这种旁路标记**来模拟 PATCH —— 等于承认接口是"伪 PATCH",会让所有非必填字段都需要类似标记,长期污染 API 设计。需要部分更新就拆子动作接口。
- 新增 update 接口时,必须在 API 文档对应章节明示"PUT 全字段回传"约定;DO 上对允许 null 的字段补 `FieldStrategy.ALWAYS` 注解,并加注释说明语义来源(指向本节)。
- 历史接口若是稀疏 PATCH 风格(传 null = 不动),保留现状但不要拓展;遇到清空诉求时按 PUT 方向重构。

## 用户可见错误文案（状态机动作 / 状态校验）

凡"状态机动作 / 状态校验失败"的业务异常,若 `message` 会被前端直接 toast 给用户:**不要把动作 / 状态的内部 code 塞进 message**(如"不支持动作【complete】")。给用户看中文展示名,技术诊断(原始 code / 入参 / 堆栈)由 `infra_api_access_log` 承载。

红线:

- service 注入 `StatusActionTextResolver`(`rdms-project-boot · service/status`),抛错前用 `actionName / statusName` 把 code 翻成中文名再填错误码占位;中文名权威源是 `rdms_object_status_transition.action_name` / `rdms_object_status_model.status_name`,**不在代码里硬编码 code→中文 映射**。
- 错误码文案用「{}」占位中文名(正面样板 `PERSONAL_ITEM_STATUS_ACTION_NOT_ALLOWED`),不要把 code 写死进文案。
- 新对象类型只要在状态机两张表配好 `status_name` / `action_name`,resolver 自动生效,无需改代码。

完整规范、落地清单与已知缺口(加班申请 `OvertimeApplicationServiceImpl` 待补)见 [`docs/architecture/用户可见错误文案规范.html`](./docs/architecture/用户可见错误文案规范.html)。来源 TD-012。

## 数据与 SQL

### 🔴 演示库同步补丁（上线前最高优先级，每次开发都要做）

项目已临近上线测试，**开发库与演示库两套数据库并存**：日常开发只改了开发库，演示库要靠 SQL 补丁手工同步。一旦补丁缺失或漏项，演示库就会因数据结构 / 字典 / 权限缺失导致功能异常。因此——

- **每次开发引起的任何数据库变动，都必须同步产出一个可直接运行的 SQL 补丁文件**，交给用户拿到演示库执行。覆盖范围：表结构（DDL）、数据字典（`system_dict_type` / `system_dict_data`）、菜单 / 角色 / 权限（`system_menu` 及权限相关表）、新功能初始化数据、索引等——**只要开发库动了，补丁就必须有**。
- **落地位置与命名**：`docs/sql/patches/`，命名 `YYYY-MM-DD-功能名-NN.sql`（每次变更一个独立文件；当天同一功能多次变更递增 `NN` 序号）。沿用 `docs/sql/**` 不主动提交 git 的惯例。
- **可直接执行**：纯 SQL、自包含，不依赖 IDE / 迁移工具；用户复制即可在演示库一次跑通。
- **幂等可重复**：演示库可能已有部分数据，补丁必须可重复执行不报错（`NOT EXISTS` / `IF NOT EXISTS` 守卫；雪花 id 表按下文「种子 SQL」规则显式取 id）。
- **只含增量**：仅本次变动，不 dump 全库、不夹带无关数据。
- 补丁写法严格遵守下文「种子 SQL」小节（雪花 id 取值、collation 1267 陷阱）。
- **收尾必做**：完成任何涉及 DB 的开发后，主动告诉用户「本次演示库补丁：`docs/sql/patches/xxx.sql`」并简述其内容，不要等用户来问。

- 新表 DO 复用现有 `BaseDO` / 审计字段风格，不要再引一套审计基类（除非该表本身明确不需要逻辑删除）。
- **不要假设运行时自动数据库迁移**：依赖新表/新字段/新索引时，必须同步补 SQL 脚本与文档。
- SQL 放在目标模块 `src/main/resources/sql/...`，可审阅、可单独执行。
- 缓存/日志/审计变更优先沿用既有机制，不要绕开登录上下文与审计字段填充。

### 种子 SQL（纯 SQL INSERT 雪花 ID 表）

`system_dict_type` / `system_dict_data` / `system_menu` 等历史表 id 由 MyBatis-Plus 雪花算法在 Java 层生成，DDL 无 `AUTO_INCREMENT`。纯 SQL 路径（字典种子、菜单种子、演示库补丁）写 INSERT 必须遵守三条红线，否则报错或撞库：

- **必须显式给 id**：用 `MAX+1` 取（`SET @new_id = (SELECT IFNULL(MAX(id),0)+1 FROM xxx);`），多条连续 INSERT **每条前重新取**，否则 `1364 - Field 'id' doesn't have a default value`。
- **必须幂等**：每条 INSERT 加 `NOT EXISTS` 守卫，可重复执行不重插。
- **防 collation 1267**：**不要**用用户变量存字符串再比较（`SET @t='xxx'` 后 `WHERE col=@t`），仓库历史表 collation 不统一会撞 `1267 Illegal mix of collations`；直接展开成字面值。

详细写法、示例与样板（`docs/sql/rdms_project_query_permission.sql`）见 [`docs/agent/种子SQL写法规范.md`](./docs/agent/种子SQL写法规范.md)。

## 注释与编码

- 关键字段/分支/约束/非直观实现补**简洁中文**注释；中文写入必须 UTF-8，不要用"改成英文"规避乱码。
- superpowers 产出的功能文档（设计/实施/联调）默认中文落地；代码标识、文件路径、接口路径、SQL、命令保持原样不意译。

## 文档输出格式

- 新写文档默认输出 **HTML 格式**（便于浏览器直接打开、自带样式）。
- 例外：`docs/superpowers/` 与 `docs/agent/` 下保持 **markdown**——前者是工作流约定，后者是专给 Agent 看的操作手册（Agent 读 md 更直接，不需要浏览器样式）。
- 历史已有的 markdown 文档不强制迁移；只有新写的按 HTML。

### 产出落点（每次生成前对照，避免散乱）

**红线：绝不把生成物放在仓库根或 `docs/` 根目录**——任何文档 / SQL / 草稿必须落到 `docs/` 下对应子目录（完整目录见 [`docs/README.md`](./docs/README.md) 场景表）。历史教训：工作台 API 文档被丢到仓库根 + docs 根导致散乱。

| 产出类型 | 落点 |
|---|---|
| 业务功能文档（设计 / 前端 API / 实现说明 / 联调） | `docs/domains/<域>/`（域=system/product/project），**平铺**、命名 `YYYY-MM-DD-功能-类型.html`，**不按功能建子目录** |
| superpowers 工作流产物（spec / plan） | `docs/superpowers/specs/` `plans/`（保持 markdown） |
| 跨模块约束 / 架构规范 | `docs/architecture/` |
| starter / 框架 API 说明 | `docs/modules/` |
| 专给 Agent 看的操作手册（从 CLAUDE.md 抽出的长篇 how-to / 命令 / 陷阱） | `docs/agent/`（CLAUDE.md 留红线 + 指针） |
| 调研 / 对标 / 可行性 | `docs/research/` |
| 负债台账 / 排查经验 | `docs/debt/` |
| 演示库同步补丁 | `docs/sql/patches/`（见本文件§数据与 SQL） |
| SQL 草稿 | `docs/sql/`（最终迁回模块 `src/main/resources/sql/`） |
| **过程草稿 / 待确认清单 / "看一眼"稿** | **`docs/temp/`，定稿后及时删** |

- 正文格式按上文「文档输出格式」（默认 HTML，`superpowers/` 与 `docs/agent/` 保持 md，历史 md 不强迁）。
- 落点对应的子目录有 `README.md` 时，新增文件**同步登记**进该 README 的文件表。

## 工作规则（执行前对照）

1. 优先做有边界的模块内改动，避免跨模块扩散。
2. **不要修改** `application-local.yaml` / `application-dev.yaml`，除非任务本身就是环境配置调整；改前先查 git 状态。
3. 新增共享能力优先扩展现有 `rdms-spring-boot-starter-*`，不要在业务服务里重复堆配置。
4. **未经用户明确同意，不执行任何 `mvn`、启动命令、脚本等会实际运行项目的命令。**

## Git 操作纪律

### 默认不引导分支管理（**首要**）

用户在本仓库长期固定在 `main` 上工作。开发流程中：

- **不要主动建议建 feature 分支**（`git checkout -b feat/xxx`、`git switch -c ...`）。
- **不要把"先切到 xxx 分支再操作"作为方案前置步骤**。
- 一切围绕 `main` 展开：直接在 `main` 上改、`main` 上提交、`main` 上推。
- 例外：用户明确要求建分支、或涉及多人协作 / PR 评审 / 大规模重构（此时仍只是"提一句作为可选"，不强推）。

**理由**：这次差点丢用户 3 天工作的事故，根因就是分支管理本身——某次操作意外把文件名当成分支名（建出 `用户行动清单.md` 分支），后续"切回 main + `git branch -D` 删怪分支"流程里就把未推送 commit `8bad989` 干掉了。**少走分支 = 少埋雷**。

### 破坏性 git 命令必须先核实

任何**会丢工作**的 git 命令——`branch -D`、`reset --hard`、`clean -fd`、`push --force` / `--force-with-lease`、`checkout` / `switch` 带未提交改动、`rebase` 在已推送分支上、直接动 `.git/` 内部文件（`refs/`、`HEAD`、`packed-refs`）——**给出建议前必须先核实**，不得凭"看起来安全"就甩命令：

1. **目标 ref 上是否有未推送 / 未合并 commit**：让用户跑 `git log --oneline -5 <ref>` 或 `git log <主线>..<ref>` 把输出贴回来。
2. **工作区是否干净**：`git status`。
3. **先挂救生圈**：建议用 `git tag backup-xxx <sha>` 锁定当前 SHA，**再执行**破坏性命令。
4. **明示翻车回滚路径**：例如"如果不对，`git reset --hard backup-xxx` 即可回到此处"。

## 验证默认动作

先定义验证方式，再做修改。默认静态验证：
- 调用链是否闭环、是否符合模块边界
- 配置项 / 接口契约 / 权限标识 / 路由 / 资源注册前后是否一致
- 改动是否控制在最小集合
- 文档 / SQL / 配置 / 接口说明是否需要同步更新

如果改动涉及 Spring 配置、序列化、安全、路由、RPC 契约、MyBatis 行为或跨模块 API，必须**显式区分**哪些是已静态检查、哪些尚未实际运行验证。

## 给后续我自己的提醒

- 仓库可能有未提交的本地改动，不要顺手覆盖与当前任务无关的编辑。
- `docs/` 是当前工作上下文的一部分，不是归档；架构级修改前先查阅 [`docs/README.md`](./docs/README.md)。
- 根 `pom.xml` 统一版本与依赖；版本调整改根 pom，不要散落到子模块。
- 推荐使用 `Glob` / `Grep` / `Read` 等专用工具，避免用 Bash 做文件搜索/读取/编辑。
