- 在 ProjectMapper 中新增 updateProgressRateById 方法,支持单独更新项目进度 - 在 ProjectService 中新增 recalcProgress 接口,用于重新计算项目进度 - 实现 ProjectServiceImpl 的进度重计算逻辑,通过根任务平均进度更新项目进度 - 新增 ProjectTaskMapper 的 selectRootTaskAvgProgressByProjectId 查询方法 - 在任务创建、更新、删除、状态变更等操作后触发项目进度重计算 - 添加进度归一化处理,确保数值精度为两位小数 - 更新 CLAUDE.md 文档,加强技术风险判断要求
176 lines
15 KiB
Markdown
176 lines
15 KiB
Markdown
# 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;否则在该命令上下文中显式切换。
|
||
|
||
## 仓库结构
|
||
|
||
多模块 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` 分层。
|
||
- 让外部模块直接依赖 `*-boot` 的 service 或 mapper(必须走 `*-api`)。
|
||
|
||
## 分层职责
|
||
|
||
| 层 | 职责 | 红线 |
|
||
|---|---|---|
|
||
| `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`**;改跨模块 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` / `ProjectExecutionServiceImpl` / `ProjectExecutionAssigneeServiceImpl` / `ProjectTaskServiceImpl`)保持一致。
|
||
- **同一接口不要两条通道叠加**。要么全域,要么对象域;叠加只会让对象域用户被全域那条卡死。
|
||
- 列表/详情这类对象内**读路径**目前未挂 `@CheckObjectPermission`(属已识别负债,台账 TD-001),新增读接口暂沿用现状即可,不要顺手改造,等独立立项。
|
||
|
||
判定口诀:**URL 里有 `{projectId}` / `{productId}` 等对象 ID → 对象域;没有 → 全域**。
|
||
|
||
## 接口语义(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 方向重构。
|
||
|
||
## 数据与 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,否则 MySQL 报 `1364 - Field 'id' doesn't have a default value`。
|
||
|
||
- **id 取值**:`SET @new_id = (SELECT IFNULL(MAX(id), 0) + 1 FROM xxx_table);` 然后 INSERT `SELECT @new_id, ...`。雪花 ID 单调递增,`MAX+1` 落在已用区间之后,不会与未来 Java 生成的新雪花 ID 冲突。
|
||
- **多条连续 INSERT**:每条 INSERT **前重新取** `MAX+1`——不要用 `base+1 / +2 / +3` 一次性算多个。配合 `NOT EXISTS` 守卫,部分已存在场景(半路重跑)才不会出现两条共用一个 id。
|
||
- **collation 1267 陷阱**:仓库历史表 collation 不统一(如 `system_dict_data` 是 `utf8mb4_unicode_ci`,新表 `rdms_task_worklog` 是 `utf8mb4_0900_ai_ci`)。**不要**用 `SET @t = 'xxx'` 存字符串再 `WHERE col = @t`——用户变量自带连接级 collation,与列 collation 撞会报 `1267 Illegal mix of collations (utf8mb4_unicode_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,IMPLICIT) for operation '='`。**对策**:直接展开成字面值,MySQL 字面值会按列 collation 隐式解析,不冲突。
|
||
|
||
样板参考:`docs/sql/rdms_task_worklog.sql:47-50`(菜单种子)+ `docs/sql/rdms_worklog_difficulty_seed.sql`(字典种子)。
|
||
|
||
## 注释与编码
|
||
|
||
- 关键字段/分支/约束/非直观实现补**简洁中文**注释;中文写入必须 UTF-8,不要用"改成英文"规避乱码。
|
||
- superpowers 产出的功能文档(设计/实施/联调)默认中文落地;代码标识、文件路径、接口路径、SQL、命令保持原样不意译。
|
||
|
||
## 文档输出格式
|
||
|
||
- 新写文档默认输出 **HTML 格式**(便于浏览器直接打开、自带样式)。
|
||
- 例外:`docs/superpowers/` 下保持 markdown(工作流约定)。
|
||
- 历史已有的 markdown 文档不强制迁移;只有新写的按 HTML。
|
||
|
||
## 工作规则(执行前对照)
|
||
|
||
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 做文件搜索/读取/编辑。
|