给接手本仓库的 AI 编码助手(Claude Code、Codex 等)看的约定。命令以 .github/workflows/ci.yml 和 frontend/package.json 为准;两边不一致时,以它们为准并顺手修正本文件。
| 目录 | 内容 |
|---|---|
| 根目录 | 后端:Spring Boot 3.5、Java 21、Maven(自带 mvnw),包名 com.arthur.labops |
frontend/ |
前端:Next.js 16,由 vinext 构建运行;React 19、TypeScript;Node >=22.13.0 |
src/main/resources/db/migration/ |
Flyway 迁移脚本 |
benchmark/ |
并发压测脚本(Python)与结果,RESULTS.md 是对外引用的数据来源 |
scripts/、根目录 *.ps1 |
Windows 本地起栈、生产 profile 起停与验证脚本 |
docs/ |
concurrency-notes.md 并发设计说明,screenshots/ 是 README 用图 |
.github/screenshots/ |
README 截图的页面清单(shots.mjs)与截图脚本,由 readme-screenshots.yml 运行 |
CI(ci.yml)对每个指向 main 的 PR 跑以下全部检查。
后端(在仓库根目录下)
- 构建与测试:
./mvnw -B --no-transfer-progress verify(Windows 用mvnw.cmd) - 没有单独的 lint 步骤。
- 默认 profile 走 H2、进程内锁和本地定时器,不需要 MySQL、Redis、RabbitMQ。
- MySQL 并发回归(
RefreshTokenMysqlConcurrencyTest、PaymentRequestMysqlConcurrencyTest、MysqlCriticalConcurrencyRegressionTest)用 Testcontainers,本机没有 Docker 时会被跳过;CI 会检查它们真的跑过、没有 skipped。 - MySQL 趋势查询计划回归(
DashboardTrendIndexMysqlTest)同样用 Testcontainers;CI 必须确认已执行,防止索引缺失被跳过的测试掩盖。 RabbitReservationExpiryOrderingIntegrationTest要连127.0.0.1:5672,连不上整类跳过;CI 用 RabbitMQ service 让它真正执行。- CI 另有「后端 · 无 Docker」job,关掉 Docker 再跑一遍
verify:新加的 Testcontainers 用例在没有 Docker 时必须跳过,不能报错。
前端(在 frontend/ 下)
- 安装:
npm ci - lint:
npm run lint - 类型检查:
npm run typecheck(tsc --noEmit;lint 和测试的 TS 转译都不做类型检查,类型错误只有这一步拦得住) - 测试:
npm test,先vinext build生产构建,再跑tests/**/*.test.mjs。CI 跑在 UTC,断言渲染出的日期或时分时,测试数据用new Date(年, 月, 日, 时, 分)按本地时间构造,不要写死带时区的字符串;本地可用TZ=UTC npm test复查。 - 本地开发:
npm run dev,后端地址见frontend/.env.example(默认http://127.0.0.1:18080)。
README 截图
- 截图只来自真实运行的服务,不用假后端或手工拼图。改了界面后先推送自己的分支,再对该分支手动运行「README 截图」workflow(
gh workflow run readme-screenshots.yml --ref <分支>),它会起栈、按角色截图并提交回该分支,不能在 main 上运行。截图提交由 workflow 令牌推送,不会触发 CI,之后再推一次自己的提交即可让 CI 覆盖最新状态。 - 要拍的页面和等待的选择器写在
.github/screenshots/shots.mjs;frontend/tests/readme-screenshots.test.mjs检查类名和data-*属性仍存在、清单与 README 引用的图一致;rendered-html.test.mjs用完整登录选择器查询真实渲染的表单,覆盖autocomplete的属性值。改界面时如果它们失败,同步改shots.mjs。
提交信息检查(在仓库根目录下)
- 测试:
node --test .github/scripts/check-commit-messages.test.mjs - 自查本分支的提交:
node .github/scripts/check-commit-messages.mjs origin/main HEAD
- commit 和 PR 只署作者本人。不加
Co-Authored-By、会话链接(claude.ai/code/session…)、Generated with ...之类的署名行。CI 的「提交信息署名检查」会拦下带这些字样的提交。 - PR 描述用中文。
- 改完必须先在本地跑通所改部分的 lint 和测试(见上方命令),全部通过再提交。
- PR 描述里的截图放在单独的孤儿分支
pr-assets/<PR 主题>(只含图片),用raw.githubusercontent.com/.../pr-assets/<主题>/...引用,不提交进功能分支。 - 不要删除任何
pr-assets/*分支,PR 合并后也不删:已合并 PR 的描述仍引用这些图,删了截图就永久挂掉。清理分支时只删功能分支(feat/*、fix/*、chore/*、docs/*、tmp/*等)。
- 修 bug 必须补回归测试:修复前测试失败,修复后通过。
- review 发现问题时,不只修这一处,还要防止同类问题再出现。优先改代码结构,让错误写不出来;其次补测试或 CI 检查,让错误被自动发现。两者都拦不住时,才写进文档。
- 数据库结构变更只加新的 Flyway 迁移,版本号接着最大的往下排,不改已经合入的迁移。
- 状态变更涉及设备和预约两张表时,加锁顺序固定为 Equipment → Reservation,批量锁按主键排序,避免 ABBA 死锁。细节见
docs/concurrency-notes.md。 - 并发相关的修复,H2 上测不出来的要补 MySQL Testcontainers 回归,并在
ci.yml里加「确认已执行」的检查,防止被静默跳过。
- 颜色、圆角、阴影用
app/globals.css里:root的变量,不另起一套。 - 图标用
app/components/icons.tsx的Icon,缺的图标加进那个文件,不用 Unicode 字符充当图标。设备没有照片字段,按类别用categoryIcon显示图标。 - 角色导航以
app/lib/roles.ts的navItemsForRole为准;tests/rendered-html.test.mjs里有一份对照,改导航时两处一起改。 - 筛选项(下拉选项、标签列表)不要从当前页、已筛选的列表数据里推导:换页或加筛选后选项会消失。由后端单独接口提供,例如设备类别用
GET /api/equipment/categories。测试要覆盖「筛选后」和「数据在第一页之外」两种情况。 - 设备名录这类只用来装饰行的数据,不要和页面自己的数据放进同一个
Promise.all:名录失败会连带已经成功的行一起丢掉。用page.tsx的settle单独等待,失败时行回退到编号并单独提示;测试要覆盖「主数据成功、名录失败」。 - 给
.solid-button、.ghost-button这类通用类加页面专属尺寸时,选择器要带上父级(如.login-card .login-submit):单个类名写在通用样式前面会按源码顺序被覆盖。jsdom 的getComputedStyle不算优先级,测不出这种覆盖,要断言样式时用tests/css-cascade.mjs的cascadedValue。 - 菜单、下拉层等浮层不要渲染在
overflow滚动容器里(表格外层的.table-scroll会裁掉纵向溢出),用 portal 挂到body再按触发按钮定位,参照EquipmentView的MoreMenu。测试至少覆盖一行、两行和末行三种情况。
- 大文件用局部编辑,不要整文件重写。
benchmark/RESULTS.md里的数字只来自benchmark/下脚本的实际运行结果,不手改、不估算。