双11狂欢大促

盲盒赢 定制羽绒服

开启惊喜盲盒,抽取你的专属好礼

— 惊喜奖品抢先看 —

zq践地组——CNCC 2026 会议系统 · 项目随笔

zq践地组 zq践地组 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要 + 加入社区

CNCC 2026 会议系统 · 项目随笔

团队与成员

成员姓名学号主要分工
A林嘉祚102400118组长 · 架构 / 数据库 / 部署 / 文档整理
B张祺102400432后端 · 身份认证与报名
C陈时文102400313后端 · 论坛 / 议程 / 消息
D甘羽潇102400437参会者端前端
E梁鑫敏102400438管理端前端 / 多语言
F兰秀楠102400316LLM / 算法 / 测试

本文为课程项目复盘,记录实际做了什么、遇到过哪些问题、如何解决。接口细节以 docs/api.md 和 db/schema.sql 为准;文中数字以提交记录与实测结果为准,未记录的项直接注明。


0. 项目概览与评分项对应

0.1 项目概览

CNCC 2026 会议系统面向大会论坛与专题论坛的会议场景,提供参会者注册报名、个人日程与冲突提示、消息通知,以及会议主席、分论坛主席、秘书使用的管理端(统计、名单导出、通知发布),并附带 LLM 智能推荐与五种界面语言切换。技术栈为 Spring Boot 3 + MySQL 8 + 原生静态前端,本地与服务器均用 Docker Compose 启动。

  • 评分构成:随笔 28′ + 项目与附加合计封顶 72′。
  • 部署情况:项目已部署到华为云 ECS(Docker Compose 三容器:应用 / MySQL / Nginx),验收时 /、/api/ping 与静态资源均实测返回 200;该实例为按需计费,验收后已释放,实测记录与截图见第 7 节。
  • 提交次数:官方要求每人 ≥3 次,本组实际提交次数与提交日志截图见 7.1。

六人分工与贡献比例:

成员学号职责贡献比例
A · 林嘉祚102400118组长:架构 / 数据库 / 部署 / 文档与集成18
B · 张祺102400432后端:身份与报名17
C · 陈时文102400313后端:会议与消息16
D · 甘羽潇102400437前端:参会者端16
E · 梁鑫敏102400438前端:管理端与多语言16
F · 兰秀楠102400316LLM / 算法 / 测试17

0.2 评分项与本文对应

评分项对应本文
随笔:分工(2′)第 3 节
随笔:commit 截图(6′)7.1
随笔:运行截图(2′)7.2
随笔:困难与解决(8′)第 5 节
随笔:PSP(2′)六份 PSP 见文末《附:六名组员的 PSP》,A–F 每名组员一个表格
随笔第二部分:选题展示被提问、重答(2′)2.1
随笔第二部分:选题后的新思考与行动(6′)2.2
项目:GUI(10′)7.2
项目:基础功能(20′)4.7
项目:LLM(8′)第 3 节 F 段 / 6.6 / 7.2
项目:AI Coding(14′)第 6 节
项目:数据库(10′)4.2
项目:用户体验(4′)7.2
项目:部署(6′)4.6
附加:多语言(10′)7.2 与第 3 节 E 段
附加:冲突检测(10′)4.7、第 5 节与 6.6

1. 项目简介与在线地址

1.1 一句话摘要

CNCC 2026 会议系统是一个面向大会论坛与专题论坛的会议服务网站:参会者注册时勾选论坛完成报名,在个人日程里看到时间冲突,在消息中心收到大会与分论坛通知;会议主席、分论坛主席、秘书在管理端看统计、导名单、发通知。我们用 Spring Boot 3 + MySQL 8 + 纯静态前端,一天内跑通"注册选论坛 → 报名落库 → 后台人数变化 → 日程冲突提示"这条主链路,另加 LLM 推荐与五种语言的界面切换。

1.2 项目是什么

CNCC 2026 是大会论坛 + 167 个专题论坛的会议场景,两类论坛的数据结构一致,所以我们用同一张 forums 表 + type 字段来装,没有拆成两套。

系统面向四类角色:普通参会者、会议主席、分论坛主席、秘书。核心链路是"注册时勾选论坛 → 报名落库 → 后台人数实时变化 → 参会者在个人日程里看到冲突提示"。

选这个题,是因为会议报名天然带着权限、时间和统计三条线,够我们在一天里把契约、鉴权、幂等和前后端联调都走一遍,又不用依赖真实的第三方服务。砍掉的东西也写在这里:复杂图表(总览只保留三个数字)、消息已读状态(只做增量拉取,不做已读回执)、头像与富文本编辑、议程的单独增删改(议程随论坛整体下发)。这些不是做不了,是会把主链路的联调时间挤掉。

1.3 地址

项目内容
CodeArts 仓库地址https://codehub.devcloud.cn-north-4.huaweicloud.com/c248a00d79754d65985c02444972bbe8/cncc2026_meeting.git
线上访问地址(验收时有效,实例已释放)http://123.60.223.83/ —— 统一登录入口;参会者端 http://123.60.223.83/frontend/index.html%EF%BC%8C%E7%AE%A1%E7%90%86%E7%AB%AF http://123.60.223.83/admin/index.html
线上部署形态华为云 ECS t6.large.2(2 vCPU / 4 GiB,EulerOS 3.0,华北-北京四,公网 EIP 123.60.223.83),Docker Compose 三容器(app / mysql / nginx),restart: unless-stopped
部署时效说明该实例为按需计费,验收期后已释放,地址仅在验收时有效;验收时 /、/api/ping、静态资源均实测 200(见 4.5 / 4.7)
演示账号(四类角色)demo_participant(参会者)/ demo_chair(会议主席)/ demo_forum_chair(分论坛主席)/ demo_secretary(秘书),密码统一 demo123456(以 db/seed.sql 为准)
本地启动方式cp .env.example .env 填好 MySQL 密码 → ./deploy.sh(= mvn package → docker compose up -d --build);没有 JDK 时直接 docker compose up -d --build 走容器内构建。前置:Docker Desktop(含 compose v2)

提醒:本机 localhost 不算线上地址。上面的 123.60.223.83 是按需实例,验收后已释放,现在打不开是正常的;复现请按本地启动方式或重新买一台机器跑 docker compose --profile public up -d --build。


2. 随笔第二部分(8′)

这一节是选题展示之后的补充:2.1 写上次展示被追问、这次怎么重新回答,2.2 写选题之后实际做了什么、下一阶段还打算做什么。

2.1 重新回答:三个被追问的问题(2′)

这三问来自我上一次项目展示时老师和同学的追问,原话没有逐字留存,按当时的笔记整理。放到 CNCC 2026 这个项目里,用现在的实际做法重新回答一遍。

上次展示时被问到的问题现在会怎么答
3 名学生抢 1 个名额,算不算高并发?严格说这不算高并发。3 个并发请求只能暴露"先查后插"这类竞态,属于并发正确性验证,不是性能测试;高并发要看数百到数千同时请求下的吞吐、P95 延迟、错误率和资源占用。而且这个系统本身没有"名额"字段,报名不限量,真实的并发风险是同一用户重复报名——我们用 registrations 表的 UNIQUE(user_id, forum_id) 唯一索引兜底,重复提交返回 409,不会产生脏数据。这类用例应该命名为"并发正确性-重复报名",不宜标成"高并发"。
是否做边界测试?做了算法与输入边界,没做压力边界。具体覆盖:时间区间的端点相接、完全包含、零时长、跨天;LLM 返回代码块包裹的 JSON、前后夹带解释、编造不存在的 forum_id、重复 id、空理由、非法 JSON、超时;参数与权限侧的 page < 1、page_size 越界、非法 type、消息 limit 越界、非法 since 游标、未登录访问日程 401。F 模块 31 条,全项目 45 条,全部通过。没做的是 100/500 并发压测,留到下一阶段。
测试报告是自动生成的吗?是自动生成的:mvn test 由 Maven Surefire 在 backend/target/surefire-reports/ 下产出每个测试类的 TXT 与 XML,含用例数、失败数和耗时,命令行同时打印 Tests run: 45, Failures: 0...;博客里的截图来自这次真实执行输出,不是手画的示意图。但仓库没有把原始输出文件一并提交(没有 tools/test-output-f.txt),也没有接 CI 归档,所以"自动生成"指的是 Surefire 的本地产物,不是一套持续集成的报告系统。
  • 为什么想重新回答:这三问当时都答得比较笼统,现在项目里有了具体代码和测试结果,可以把边界、口径和没做的部分分开讲清楚。
  • 口径说明:凡是写了"已做"的,都能在仓库里指到测试代码或配置;没做的(并发压测、CI 归档)就写没做,不拿计划当结果。

2.2 选题后的新思考、新想法与具体行动(6′)

本节记录选题之后的新想法,以及每条想法对应的具体动作。

行动 1:契约冻结,把"边写边改"堵死

  • 想法:展示时被追问"接口会不会边写边改、前后端怎么对得上",当时只能说"群里说一声",这不是机制。
  • 具体动作:把 docs/api.md 冻结成 v1(2d494f9),补 §1.3 字段命名口径(分工文档用词 → 接口字段 → 数据库字段),把统一响应、错误码、JWT claim 写到别人能照着写 Mock 的程度。
  • 谁来做:A(林嘉祚)。
  • 什么时候做:选题展示后第二天上午,在 B~F 各自开工之前。
  • 怎么算做完:B~F 不再问"这个字段叫什么";冻结后的改动只以 commit 体现(例如 4ae3368 补取消报名的时间限制)。

行动 2:时间格式带时区,自测加断言

  • 想法:联调时接口返回 2026-11-03T09:00:00,没有 +08:00,前端只能按本地时间猜。
  • 具体动作:JacksonConfig 的输出 pattern 从 yyyy-MM-dd'T'HH:mm:ss 改成带 XXX(2f74816);B 把"带时区断言"写进自测清单。
  • 谁来做:B(张祺)改代码,C 负责反馈与复测。
  • 什么时候做:集成点 3 联调当天。
  • 怎么算做完:GET /api/forums/1/agenda 的每条 start_time 都以 +08:00 结尾(实测 2026-11-03T09:00:00+08:00,见 7.2 的议程接口截图)。

行动 3:把"取消报名的时间边界"写进契约

  • 想法:报名人数是实时算的,论坛开始后取消,主席的名单和签到表就对不上——这正是 2.1 里"是否做边界测试"没答完整的那一块。
  • 具体动作:docs/api.md §4 给 DELETE /api/registrations/{forumId} 补"论坛已开始 → 409"规则(4ae3368);前端把已开始论坛的取消按钮置灰,文案改成"论坛已结束,无法取消报名"。
  • 谁来做:A 改契约,D 改前端。
  • 什么时候做:集成点 3 之后、集中截图之前。
  • 怎么算做完:打开已开始的论坛详情,按钮不可点且文案正确(见 7.2 报名页截图)。

三条的结果:行动 1、2 已落地并能在提交记录里查到;行动 3 只落了契约和前端两半,后端 DELETE 的时间校验截至本文仍未合入 master,这是本轮最明确的一处半成品。

下一阶段的具体行动(尚未开始)

2.1 里没做完的并发压测和报告归档,列成计划,不写成已完成。

行动负责人时间验收标准
把"3 名学生抢 1 个名额"类用例改标为"并发正确性",另建高并发性能用例集F下一阶段第 1 周"高并发"标签下至少 100、500 并发两档,指标含 P95、错误率、降级率
LLM 接口加 Mock 压测:100/500 并发,记录线程池、超时与降级比例F下一阶段第 2 周压测报告给出 P95 与错误率,明确线程池上限与限流阈值
AI 生成边界用例 + 人工评审后进 CI,CI 自动归档 Surefire 报告F + A下一阶段第 2 周CI 每次合入自动跑 31 条 F 测试并保留报告;AI 生成用例至少 1 名人工 review

3. 六人分工

本节由各人描述自己负责的部分、交付物与协作接口。

名单(2026-11-03 版):林嘉祚 102400118(A)/ 张祺 102400432(B)/ 陈时文 102400313(C)/ 甘羽潇 102400437(D)/ 梁鑫敏 102400438(E)/ 兰秀楠 102400316(F)。

A · 组长:架构 / 数据库 / 部署 / 文档与集成

林嘉祚 102400118 · 职责:组长(架构 / 数据库 / 部署 / 文档与集成)· 贡献比例 18

交付物:docs/api.md(v1 契约,八组接口,统一 {code, message, data},每个响应带 X-Request-Id,错误码表 401/403/404/409/422/500)、db/schema.sql(users / forums / registrations / messages 四张表)、Spring Boot 3 + MyBatis-Plus 骨架、Docker + Nginx 配置、博客与文档整理。

我最先干的事不是写代码,而是把契约写到别人能照着 Mock 的程度。冻结 v1(2d494f9)之后,B~F 才能各自开工。四张表的取舍有三处:议程直接存 forums.agenda 的 JSON,不单开第五张表;个人日程靠 registrations JOIN forums 实时算,不做物化表;冲突检测用半开区间 [start, end) 在应用层算,数据库只存时间。骨架用 Spring Boot 3 + MyBatis-Plus,ddl-auto: none,字段一律以建表脚本为准,不让 ORM 反向改表。代价是契约一改,前后端都得跟着返工,所以每次改动都留在 commit 和群记录里,方便回头对齐。

B · 后端:身份与报名

张祺 102400432 · 职责:后端(身份与报名)· 贡献比例 17

交付物:backend/src/main/java/com/cncc/meeting/auth/(JwtUtil 按契约 1.2.1 签发 sub/role/iat/exp,AuthInterceptor 先判 @PublicApi 再验 Token,@RequireRole 不符返回 403)、user/(POST /api/auth/register 注册即登录、POST /api/auth/login、GET /api/auth/me)、registration/(GET/POST/PUT /api/registrations 与 DELETE /api/registrations/{forumId},接口不接收 user_id,一律取 Token 的 sub)、stats/(GET /api/stats/overview 大会总览、GET /api/stats/forums/{forumId} 分论坛统计与脱敏名单)、backend/scripts/smoke-test.ps1(§2 / §4 / §5.1 / §5.2 的接口自测)。

我这块的核心是"注册和报名必须是一件事":注册时勾选的论坛要和 users 一起落库,所以整个注册放在一个 @Transactional 里,先校验 forum_ids 不重复(重复 → 422)、论坛都存在(缺失 → 404),再写用户、批量写报名、签发 Token 一并返回,注册完就是登录态。账号重复没有只靠"先查后插",users.username 的唯一索引兜底,撞了转 409。报名四个接口都不收 user_id,只认 JWT 里的 sub,否则等于让人替别人报名;整体替换是事务内先删后插,返回 added / removed,前端不用自己算差集。统计口径上,大会总览的"参与人数"是 COUNT(DISTINCT user_id) 而不是 COUNT(*),一个人报三个论坛只算一个人;分论坛名单按 chair_user_id 判权,forum_chair 看别人论坛直接 403,姓名保留首字、其余打 *。踩得最深的是本地 JDK 25 编不过 Lombok,以及时间输出漏了 +08:00,分别写在 5.9 和 5.10。

C · 后端:会议与消息

陈时文 102400313 · 职责:后端(会议与消息)· 贡献比例 16

交付物:backend/src/main/java/com/cncc/meeting/forum/(分页与 type/keyword 筛选的列表、含主席信息与议程排序的详情、GET /api/forums/{id}/agenda、补缺口的 GET /api/forums/mine)、message/(POST /api/messages 的 global/forum 两种 scope、GET /api/messages?since= 游标增量拉取)、participant/(按论坛筛选、姓名脱敏、带 UTF-8 BOM 的 GET /api/participants/export)、docs/selftest.ps1(24 条接口自测)。

论坛和消息这两块我踩得最深的是"参数名"和"谁能看"。列表这边 page_size 一开始怎么传都不生效,接口永远返回 10 条;消息这边拉取没有做全量,而是用 since 游标按秒增量取,因为全量每次轮询都把整张表重传一遍,前端还得自己去重。代价是游标语义要自己维护,since 传了非法值不能静默忽略,得返回 422。名单导出给 Excel 打开时加 UTF-8 BOM,不然中文全是乱码。权限这块我没有另起一套,等 B 的 JWT 合进来后统一走 @PublicApi 白名单,匿名能看论坛列表和详情,但看不了"我负责的论坛"。

图1

图注:GET /api/messages?limit=10 共 7 条,next_cursor 指向下一页,items 里同时有 global 与 forum 两种 scope,是 since 游标增量拉取的返回形态。

图2

图注:GET /api/participants/export 返回 181 字节、UTF-8 BOM,Excel 打开后中文表头与两行记录正常。

D · 前端:参会者端

甘羽潇 102400437 · 职责:前端(参会者端)· 贡献比例 16

口径说明:D 的 7 张运行截图和一份 PSP 都在 9759c5a 里,但没有交回文字自述。以下由 A 依据这 8 条提交、origin/master 的 frontend/ 代码和截图整理;涉及"为什么这么做"的取舍按代码形态与提交顺序归纳,不替 D 编造原话。

交付物:frontend/index.html(213 行)、frontend/app.js(908 行)、frontend/api.js(207 行)、frontend/styles.css(161 行)、frontend/README.md(26 行)。8 条提交合计新增 1,546 行前端代码(另 26 行 README、64 行 PSP);运行截图

图3

~ 07-recommendation.png 共 7 张。

我负责参会者端。登录不单独做页面:统一入口 login.js 按 role 分流,参会者进来后把 token 写 localStorage,刷新时用 GET /api/auth/me 恢复身份;未登录点"报名"是弹登录框,不把用户甩回首页。

论坛列表一次拉全量(GET /api/forums?page=1&page_size=100,演示数据 31 场),类型 tab 加关键词在前端过滤 name / topic / intro / chair / location 五个字段。这个取舍是为演示规模服务的:31 场全量拉比做后端分页交互省事;真到 167 场要改回分页,否则首屏会变重。

详情先用手上的列表摘要立即出弹窗,再请求 GET /api/forums/{id} 补全,避免点开先白屏。报名与取消分三条路径:单场 POST /api/registrations、批量 PUT /api/registrations(整体替换)、取消 DELETE /api/registrations/{forumId}。按钮状态按 start_time 和 end_time 算:已报名且论坛已开始 → 置灰,文案是"论坛已开始/已结束,无法取消报名";未开始才显示"取消报名"。为什么卡在开坛时刻:报名人数是由 registrations 实时聚合的,论坛开始后取消会让主席手上的名单和签到表对不上,所以前端先挡一层,后端契约再给 409。前端置灰只是体验层,真正的兜底是后端校验——这一点 4.8 写明了,DELETE 的时间校验截至本文仍未合入 master。

个人日程读 GET /api/schedule,拿 items 和 conflicts,按日期分组标"待开始 / 进行中 / 已结束",冲突项按 a_forum_id / b_forum_id 打标记。消息用 GET /api/messages?since= 游标增量拉,localStorage 存 cursor 和已读 id,global 与 forum 分区展示。推荐入口是 POST /api/llm/recommend,输入上限 500 字;degraded=true 时页面顶部明说"智能推荐暂时不可用,以下为关键词匹配结果",推荐卡片可以直接进详情或报名,报名前先做本地冲突检查。

两个取舍值得写下来。一是为什么用原生 JS 单页:frontend/ 里没有 package.json,也没有构建产物,Nginx 直接挂静态目录就能用,助教拿到仓库用 python -m http.server 就能打开;代价是没有组件化和类型检查,app.js 写到 908 行后靠模块级状态变量和命名维持,renderScheduleLegacy / renderMessagesLegacy 两段旧渲染函数还留在文件里没清,是明确的技术债。二是接口失败怎么降级:loadForums 失败回退 4 条内置 mockForums,详情请求失败保留列表摘要,消息失败显示"消息暂时无法加载",日程失败回退本地报名集合——降级只为页面不白屏,错误仍 console.error,不吞掉。

踩得最实的一个坑是字段:列表接口按 ForumSummary 返回,不带 intro,而列表卡片读的是 forum.intro,接真实接口后卡片会显示 undefined(7.2 首页那行因此改用接口返回截图)。这是照着 Mock 字段写死、没和契约逐字段对齐的典型;收尾时没有改源码,作为已知缺陷留在 4.8。

E · 前端:管理端 + 多语言

梁鑫敏 102400438 · 职责:前端(管理端 + 多语言)· 贡献比例 16

交付物:admin/(index.html + app.js 的 renderView 路由与三种角色守卫、api.js 统一请求层、mock-data.js、styles.css、i18n/ 下 zh-CN / zh-TW / en / de / ja 五套资源包)、根目录 index.html + login.js(统一登录入口,按 role 分流到管理端或参会者端)、仪表盘 / 我的论坛看板 / 参会者名单与 CSV 导出 / 通知发布四个管理视图。

我负责管理端和五语言,做的是一份结构 + 五份资源包:t(key) 和 data-i18n 挂在同一套页面上,切语言即时重绘,缺 key 回退 zh-CN 并在控制台告警。放弃"五套页面 / 全量翻译数据"是因为复制五套等于五倍维护成本,而且后端只返回一份中文数据,翻译只能在展示层做;代价是非中文界面里地点、主席姓名这类开放字段仍会露出中文,只能等后端补多语言字段再收口。管理端的可见性没有交给菜单藏起来,而是 ADMIN_ROLES 加 renderView 按角色只渲染该看的视图:会议主席看大会总览,分论坛主席只看 chair_user_id 是自己的论坛,秘书拿参会者名单和 CSV。最花时间的是 i18n 只做了静态文案、动态 Mock 数据没走翻译,以及管理员登录后被静默弹回登录页,两条分别写在 5.11 和 5.12。

F · LLM + 算法 + 测试

兰秀楠 102400316 · 职责:LLM / 算法 / 测试(贡献比例 17)

交付物:backend/src/main/java/com/cncc/meeting/llm/(提示词 RecommendPrompt、解析与四道校验 RecommendationParser、重试与降级 RecommendService、关键词兜底 KeywordMatcher)、POST /api/llm/recommend(失败也返回 200 + degraded)、backend/src/main/java/com/cncc/meeting/schedule/(半开区间冲突检测 ConflictDetector、GET /api/schedule)、backend/src/test/java/com/cncc/meeting/ 下的单元测试。

我这块最怕的是模型不按格式吐数据:要它只返回 [{forum_id, reason}],它时不时加一段解释、套个代码块,甚至编一个清单里没有的 id。所以拿到文本不能直接用,得先剥代码块、取出 JSON 数组,再过四道校验——论坛 id 必须在库里真实存在、不能重复、理由不能太短、还得和用户问的话沾边。JSON 解析或校验失败会带上"只输出 JSON"的修复指令重试一次;网络、密钥、超时这类错误不重试,直接退到关键词匹配,用 degraded=true 让前端明确提示,保证 LLM 异常时接口仍返回 200,而参数校验失败按契约返回 422。最难的一个取舍是严格校验和召回:为了保证推荐 100% 落在真实论坛上,我宁可丢掉可疑结果,代价是一些表述不同但合理的论坛被过滤掉,最后靠关键词降级兜住。冲突这块按 [start, end) 两两比较,前一个结束等于后一个开始不算冲突,这条端点规则写进了注释和单测。

贡献比例汇总

A 林嘉祚 18 / B 张祺 17 / C 陈时文 16 / D 甘羽潇 16 / E 梁鑫敏 16 / F 兰秀楠 17,合计 100。比例为组内分工约定的初值,最终以各组员实际交付复核。


4. 开发过程:按集成点推进

这一段不是流水账。每个集成点写三件事:当时各人在干什么 → 汇合时哪里对不上 → 怎么收口。
集成点按实际推进顺序记录,时间以 commit 记录为准。
4.2 数据库设计、4.6 部署架构、4.7 验收结果是评分项要求横向展开的说明,不属于三个集成点,位置按文档结构编排。

4.1 阶段 0:契约与表先行(A)

  • 做了什么:先定 {code, message, data} 统一响应和错误码表,再写 users / forums / registrations / messages 四张表,然后出 Mock 和 /api/ping,让另外五个人不用等后端就能开工。
  • 关键决定:"个人日程不建表",运行时由 registrations JOIN forums 算出来;"议程不建第五张表",存 forums.agenda 的 JSON;"冲突不落库",按半开区间在应用层两两比较。三条决定都没有被后来推翻,代价写在 4.2.3。
  • 契约冻结时间点:2d494f9(11 月 3 日上午,v1 冻结);冻结后只做过两次规则补充——4ae3368 补"论坛已开始取消报名返回 409"、2f74816 把时间输出补上 +08:00,没有新增接口。

4.2 数据库设计

这一节对应 10′ 建表评分项,不能只写"用了四张表"。每张表要说清楚它接住了哪条需求,以及为什么这么建。

4.2.1 E-R 关系

图10

图注:docs/diagrams/er.md 的渲染结果(源文件是可读的文本,图和说明同源)。四张表:users、forums、registrations、messages。

  • 关系描述:users 1—N registrations N—1 forums(一个用户可以报多个论坛,一个论坛有多条报名,uk_registrations_user_forum 保证同一对只出现一次);messages 不直接连线到用户,靠 scope + forum_id 指向"大会"或"某个分论坛",可见范围在查询时按角色过滤。
  • 个人日程:不建表。GET /api/schedule 是 registrations JOIN forums 的实时查询,按 start_time 排序;冲突由应用层算,不落库。代价是每次查日程都要联表、没有缓存,数据量上来要先加索引再加聚合缓存,而不是改查询逻辑。

4.2.2 表与需求的对应

表接住的需求关键字段为什么这么设计
users注册登录 + 四角色username(唯一)、name、password_hash、rolerole 用 ENUM('participant','chair','forum_chair','secretary') 而不拆角色表:角色数量固定、没有"一个人多角色"的需求,拆表只会多一次 join;JWT 里也只放一个 role
forums大会论坛与专题论坛type、name、topic、chair_user_id、start_time、end_time、agenda两类论坛共用一张表,靠 type 区分,列表筛选就是 WHERE type = ?;好处是详情/议程/统计/推荐只写一套,限制是"会议论坛特有的字段"只能都塞进同一张表
registrations报名关联、人数统计、消息可见范围user_id、forum_id、created_atUNIQUE(user_id, forum_id) 是防重复报名的最后一道防线;人数用 COUNT(*)、大会参与人数用 COUNT(DISTINCT user_id),同一个表两种口径
messages大会 / 分论坛通知scope、forum_id、publisher_id、published_atscope=global 时 forum_id 必须为 NULL、scope=forum 时必须非空,由 CHECK 约束保证;游标用 (published_at, id),所以索引也按这两列建
(不建表)个人日程个人日程查询、冲突提示—派生数据,见 4.2.1;目前没有后悔,因为报名量小、查询只按单个用户

4.2.3 索引、唯一约束与事务的取舍

  • 关键索引(全部在 db/schema.sql 里,可对照):
    • uk_registrations_user_forum (user_id, forum_id):防重复报名,重复提交由它兜底返回 409;
    • idx_forums_type_start (type, start_time):GET /forums 的"按类型筛选 + 按时间排序"走这条;
    • idx_messages_scope_published (scope, published_at, id):大会通知倒序与 since 增量拉取;
    • idx_registrations_forum_created (forum_id, created_at):分论坛人数统计、按论坛筛名单、CSV 导出。
  • 唯一索引救过一次:重复账号注册、重复报名同一论坛,实测都返回 409,而不是插进两条脏数据。应用层的"先查后插"在并发下仍有窗口,唯一索引是最后一道防线。
  • 事务:注册要把 users + registrations 一次写完,实现在一个 @Transactional 里;forum_ids 里有不存在的论坛时整笔回滚(返回 404),不会留下"有用户没报名"的孤儿。我们只在重复账号、论坛缺失两条失败路径上验证过返回码,没有专门做并发回滚压测。
  • 统计不建缓存表:人数每次实时算。理由是报名量小、口径要能随报名立刻变化;数据量到十万级、或总览接口被频繁调用时,这个决定会失效,届时应先加覆盖索引再考虑缓存,而不是把 COUNT 换成定时任务。
  • 边界:唯一性、外键、枚举取值、end_time > start_time、scope 与 forum_id 的一致性交给数据库(UNIQUE / FOREIGN KEY / ENUM / CHECK);"论坛是否已开始不能取消""越权看别人论坛""分页上限 100"这类业务规则交给应用层,数据库不重复实现。

4.3 集成点 1:注册 → 选论坛 → 报名 → 后台见人数

  • 涉及:D 报名页 × B 报名接口 × E 仪表盘 × B 统计接口
  • 汇合前的状态:B 的认证与报名分支按契约 v1 写完,D 的报名页按同一份契约接 Mock,E 的仪表盘先读 mock-data.js,三方都在等"真实接口接上"这一刻。
  • 实际遇到的问题:合并 B 分支时炸出一串不兼容——Role 枚举被改成了常量类、participantCount 从 Long 变成 Integer(前端按数字格式化,Long 会被序列化成字符串)、page_size 与 forum_id 的 @RequestParam 名字和契约不一致导致绑定失效、422 响应缺 errors 数组。报名页这边还有个语义差:E/D 按"差集"自己算增删,而 B 的 PUT /api/registrations 是整体替换并返回 added / removed。
  • 收口方式:一律改实现、不改契约(契约已冻结)。B 逐处把常量类改回枚举、Integer 改回 Long、@RequestParam 对齐 page_size / forum_id,并按公共结构补 422 的 errors;A 在契约 §4 补一句"PUT 返回 added / removed,前端不要自己算差集",D 改为直接读返回。
  • 结果验证:POST /api/auth/register 勾 2 个论坛返回 200,随后 GET /api/stats/overview 的报名数随之变化(实测 6);重复报名同一论坛 409、人数不变;仪表盘数字与接口一致

    图11

    。

4.4 集成点 2:三类角色登录后各页面都是真实数据

  • 涉及:C 消息与秘书导出 × B 权限分支 × E 秘书表格/通知表单 × D 消息中心 × F 推荐接真实数据
  • 汇合前的状态:C 的论坛、消息、名单三块按契约写完,E 的秘书表格与通知表单还在用 Mock,D 的消息中心刚接上真实 GET /api/messages。
  • 实际遇到的问题:F 从 C 的分支切出来时 mvn compile 直接失败,ForumRegistrationRow 找不到 getUserId()(DTO 和查询列都漏了 user_id);同一份冲突结果里 ConflictItem 被 Jackson 输出成 aforum_id / bforum_id,与契约的 a_forum_id / b_forum_id 对不上;E 的名单页还发现,分论坛主席的看板把 /api/forums/mine 返回的数组当单个论坛对象取 .id,拼出 /api/stats/forums/NaN 直接 422。
  • 收口方式:C 补查询列与 DTO 字段,F 用 @JsonProperty("a_forum_id") 把命名钉死并加 ConflictItemJsonTest;权限边界统一走 @RequireRole,参会者发通知 403、分论坛主席看别家统计 403;E 的看板 NaN 缺陷在收尾时定位并修在 admin/api.js,见 6.4。
  • 结果验证:秘书 GET /api/participants 200、GET /api/participants/export 200(321 字节、UTF-8 BOM);参会者调这两条 403;消息接口按角色只返回该看的 2 条(1 条大会 + 1 条本论坛),since 非法值 422。

4.5 集成点 3:全链路联调 → 修 bug → 部署

  • 涉及:F 冲突算法 × D 日程页 × E 五语言补齐 × B/C 异常分支收口
  • 联调时暴露的问题:F 从 C 的分支切出来时 mvn compile 直接失败,ForumRegistrationRow 找不到 getUserId()(DTO 和查询列都漏了 user_id);接上真实接口后,/api/schedule 的冲突字段被 Jackson 输出成 aforum_id,和契约的 a_forum_id 对不上。B 侧接口时间一开始只输出到秒、没有 +08:00,前端按本地时间解析会差 8 小时,统一在 JacksonConfig 补 XXX 后才对齐契约 1.4(详见 5.10)。几处都修完才敢部署。
  • 部署过程:mvn package 第一次卡在本机 JDK 25 与 Lombok 的兼容(5.9),换 17 才过;docker compose up -d --build 第一次是 app 先于 MySQL 就绪启动、连不上库,给 MySQL 加 healthcheck(start_period: 60s + 30 次重试)并让 app depends_on: service_healthy 后才稳定。公网机器上再启用 nginx 的 public profile,把静态页面挂进容器。
  • 线上验收:在 123.60.223.83 上起 mysql + app + nginx(docker compose --profile public up -d --build),实测统一入口 /、参会者端 /frontend/index.html、管理端 /admin/index.html、/api/ping、静态资源均返回 200,登录后四类角色的页面能打开。该实例是按需计费,验收期后已释放,地址只在验收时有效。

4.6 部署架构

对应 6′ 部署评分项,重点写部署链路是否闭环、配置是否分离。

图12

图注:端口、容器名、数据卷与环境变量来源,源文件是 docs/diagrams/deploy.md。

  • 前端:frontend/ 与 admin/ 是纯静态页面(无 npm 构建步骤);部署时把静态目录整理进 frontend/dist,由 Nginx 直接托管,统一入口 / 跳到登录页,再按角色分流到 /frontend/ 或 /admin/。
  • Nginx:静态托管 + /api 反代。proxy_pass http://cncc_backend;(不带 URI),所以 /api/... 前缀原样保留,后端 Controller 路径本身含 /api;location / 用 try_files $uri $uri/ /index.html 做回退。
  • 后端:多阶段构建,最终以 cncc2026-meeting.jar 启动,暴露 8080,健康检查打 /api/ping。
  • MySQL:mysql:8,utf8mb4 + Asia/Shanghai,db/schema.sql 挂到 /docker-entrypoint-initdb.d/01-schema.sql 自动初始化(只在数据卷为空时执行),数据卷名 mysql-data(compose 项目名 cncc2026-meeting,实际卷为 cncc2026-meeting_mysql-data)。
  • 配置分离:数据库密码等走环境变量,.env 不随代码发布,只保留 .env.example;MYSQL_ROOT_PASSWORD / MYSQL_PASSWORD 用 ${VAR:?} 语法,没填会直接拒绝启动。
  • 一键启动:./deploy.sh = mvn package → docker compose up -d --build;第一次跑卡在本机 JDK 25 与 Lombok 的兼容,换 JDK 17 后过。

线上状态:mysql + app + nginx 三容器跑在 ECS t6.large.2 上,公网 EIP 123.60.223.83,验收时 /、两个子入口与 /api/ping 均实测 200。实例按需计费,验收后已释放,上面的地址现在打不开属于预期。

4.7 验收结果

逐条回填分工文档的验收口径。没过的就写没过,"有一条没过、我们怎么处理"比全绿更可信。

角色验收口径(分工文档原文)是否通过证据
A契约无歧义通过45 条单测 + 一轮接口实测,字段名与状态码都能和 docs/api.md 对上(6.4、6.6)
A表能直接建通过空数据卷起 MySQL,db/schema.sql 自动建四张表;导入 db/seed.sql 后 31 论坛 / 4 用户 / 0 报名
A任何人拉代码能跑起 Mock通过干净 worktree(origin/master)+ .env + docker compose up -d --build,/api/ping 返回 {"code":0,"message":"ok","data":"pong"}
A线上地址可访问通过(实测 200,实例已释放)公网 ECS 上 mysql + app + nginx 三容器起齐,/、/frontend/index.html、/admin/index.html、/api/ping、静态资源验收时均 200;实例按需计费,验收后释放
A博客第一、二部分齐备通过1.x 地址与账号、2.1 / 2.2、4.2 / 4.3 / 4.4 / 4.6 / 4.8、6.6、7.1~7.3、第 8 节已回填;D 段由 A 依据提交记录与代码整理,并在段首说明
B注册→多选论坛→报名成功→后台人数 +1通过POST /api/auth/register 200(forum_ids=[1,2]);GET /api/stats/overview 报名数随之为 6
B重复报名被拒通过POST /api/registrations 同论坛第二次 409;forum_ids 重复 422
B三种角色越权访问均返回 403通过参会者 GET /api/participants 403、参会者 GET /api/stats/overview 403、分论坛主席看别家论坛 403、无权限发大会通知 403
C未登录能浏览论坛与议程通过匿名 GET /api/forums 200、GET /api/forums/1 200、GET /api/forums/1/agenda 200
C参会者只看到自己报名论坛的消息通过参会者 GET /api/messages 只返回大会消息 + 自己报名的论坛消息(实测 2 条);MessageServiceTest 9 条覆盖权限分支
C秘书导出的 CSV 能被 Excel 正常打开且人数与统计接口一致部分接口返回 321 字节、UTF-8 BOM 已实测;Excel 实际打开只按 3 节的截图(181 字节演示数据)验证过,本轮没有重开 Excel
D注册后不改库也能完成"选论坛→报名→看日程→收消息"闭环通过用 demo_participant 走完整链路:列表→详情→报名→日程→消息,全程未改库(截图见 7.2)
D冲突提示与 F 的算法结果一致通过页面冲突标记直接来自 /api/schedule 的 conflicts_with,不是前端另算
E三类角色登录后菜单不串通过会议主席 / 分论坛主席 / 秘书分别登录,导航只出现各自视图(7.2 三张管理端图)
E切换五种语言后导航与核心文案全部变化部分静态文案与 Mock 词条已翻(英文 / 日文实测);地点、主席姓名等数据字段仍是中文
E新增一种语言只需加资源文件、不改逻辑代码通过admin/i18n/ 下五套资源包同构,切语言只换资源与 t(),无角色/视图分支改动
F推荐结果 100% 命中真实论坛 id通过降级路径实测 3 条全命中库内 id;正常路径于 2026-11-04 在本地配真实密钥实测,degraded=false,返回 3 条、forum_id 2/4/9 均为库内论坛(7.2 图)
FLLM 不可用时页面仍能出结果且提示明确通过接口 degraded=true + 页面顶部提示"智能推荐暂不可用,以下为关键词匹配结果"(7.2 图)
F端点相接判定为不冲突通过ConflictDetectorTest 8 条 + ScheduleServiceTest 相邻端点用例全绿

这一轮只有两行不是"通过":C 的 Excel 打开只按 3 节的演示数据验证过、E 的五语言在开放字段上仍露中文;两处都在证据列写明了缺哪一半。线上地址是实测 200 通过的,但实例按需计费、验收后已释放,现在点开打不开属于正常,不是造假。

4.8 代码冻结与收尾

冻结不是一句话,按实际动作写:

  • 停功能的时点:11 月 3 日下午契约冻结 v1(2d494f9)之后不再加接口;晚上只允许"修 bug + 补文档"。冻结后又放行过两次规则补充——4ae3368 补"论坛已开始取消报名 → 409"、2f74816 把时间输出补上 +08:00,都没新增接口。
  • 最后一遍回归:A 做整体回归,全组各自回归自己的模块。可复核的结果是:mvn test 45 条全绿(BUILD SUCCESS,3:42)、41 个接口请求的状态码逐个核对(含 422 / 403 / 404 / 409 分支)、每人一份提交日志截图。
  • 截图收齐:参会者端 13 张(D 的 7 张 + A 补拍的 6 张)、管理端 6 张、接口返回 3 张、测试与部署 6 张、提交日志 6 张,共 34 张,每张都核过 PNG 头与文件大小;分论坛主席看板在修掉 NaN 后本地实测补了真图。
  • 文档收齐:README 补启动/部署步骤、docs/api.md 冻结、

    图10

    与 deploy.png 补齐、PSP 各人完成、博客成稿。
  • 没做完的一件:后端 DELETE /api/registrations/{forumId} 的"论坛已开始返回 409"时间校验,截至本文仍未在 master 实现——契约写了、前端按钮置灰了、后端校验没合入。这是收尾时明确留下的未完成项,不把未实现写成已支持。
  • 收尾顺序:先冻结契约 → 再跑回归 → 再截图 → 最后写 PSP 与博客,避免"边写文档边改代码"导致图文对不上。

5. 困难与解决

统一五段式:现象 → 排查 → 根因 → 解决 → 学到什么。
每个问题配 1 张截图或 1 段日志。5.15.4 是 A 这边真实踩到的四个,5.55.6 是 F 的两个,5.75.8 是 C 的两个,5.95.10 是 B 的两个,5.11~5.12 是 E 的两个。

5.1 空目录里 git clone 直接失败

  • 现象:项目目录里已经有分工文档,执行 git clone <url> . 直接报 destination path '.' already exists and is not an empty directory,仓库没拉下来。
  • 排查:先 ls 看目录,确认占位的是全组要用的分工文档,不是构建残留。考虑过把文档挪走再 clone,但那是大家正在用的文件;于是换个思路,验证已有目录能不能直接接管远端仓库。查 git clone 的行为说明,它对目标目录的要求就是必须是空的,问题不在网络或权限。
  • 根因:clone 会在目标路径创建仓库,路径里已有文件时它不覆盖、直接拒绝;命令里的 . 把当前目录当成了新建仓库的位置。
  • 解决:改成先初始化再拉远端:
git init
git remote add origin <repo-url>
git fetch origin
git checkout master

原分工文档没删,留在本地工作区,未纳入版本库。

  • 学到什么:clone 不是唯一的落地方式,init + remote add + fetch 可以在不破坏现有文件的前提下接管仓库。遇到"目录不空"先想清楚哪些文件要保留,别上来就清空目录。

本节不再挂终端原图:结论可由仓库当前的 git remote -v、git status 与那份未跟踪的分工文档直接复核。

5.2 schema.sql 在 messages 建表处中断

  • 现象:空库执行 db/schema.sql,前三张表建完后在 messages 处报 ERROR 3823: Column 'forum_id' cannot be used in a check constraint needed in a foreign key constraint 'fk_messages_forum' referential action,SHOW TABLES 只有 3 张,第四张没建出来。
  • 排查:把 messages 的 DDL 单独抠出来,在空库上只跑这一段,稳定复现。然后逐项拆约束:去掉 CHECK 能过,去掉 ON UPDATE CASCADE 也能过,两个同时存在就报 3823。到这一步才确定是约束组合冲突,不是 SQL 语法写错。
  • 根因:MySQL 8 不允许"带引用动作的外键"和"基于该列的 CHECK 约束"同时存在;ON UPDATE CASCADE 撞上了 CHECK (scope = 'forum' 时 forum_id 非空)。
  • 解决:把该外键改成 ON UPDATE RESTRICT,保留 ON DELETE CASCADE 和 CHECK。执行 docker compose down -v 重建空库后 4 张表齐全,SHOW CREATE TABLE messages 能看到 CHECK 和两条外键都在。
  • 学到什么:建表脚本要以"空库一把过"为准,不能只在已经建好的库上改;DDL 报错先做最小复现,再逐个拆约束组合;为什么选 RESTRICT 要写进 schema 注释,否则下一次还会有人顺手改回 CASCADE。

本节不再挂 DDL 输出原图:约束现状可直接在 db/schema.sql 的 messages 建表段核对,fk_messages_forum 现为 ON UPDATE RESTRICT。

5.3 首次 docker compose up -d 被判 mysql unhealthy

  • 现象:第一次启动报 dependency failed to start: container cncc2026-mysql is unhealthy,app 没起来,mysql 容器本身还在初始化。
  • 排查:看 health 日志(docker inspect --format '{{json .State.Health}}' cncc2026-mysql),里面有两次 mysqladmin ping 失败,随后容器就被标成 unhealthy。对照 MySQL 首次启动流程,初始化脚本执行期间跑的是临时实例,它不监听 3306;默认的健康检查窗口在初始化结束前就耗完了。
  • 根因:健康检查没覆盖首次建库这段最长耗时。depends_on 用的是 condition: service_healthy,mysql 一被误判,app 就直接放弃启动。
  • 解决:healthcheck 加 start_period: 60s,retries 调到 30;然后 docker compose down -v 全新建库,一次成功。重跑一次有时也能过,但那只是碰巧躲过了初始化窗口,不算修好。
  • 学到什么:健康检查要按"最慢的一次启动"来配,不是按稳态来配;depends_on 的 healthy 条件会把数据库的误判放大成整条链路起不来;判断容器就绪要看 health 日志,不能只看 up -d 有没有返回。

图13

图注:mysql 与 app 两个容器均为 healthy,端口映射 3307->3306(本机 3306 被占用,所以 .env 里换成了 3307)。

5.4 查表时中文全是问号

  • 现象:SELECT name FROM forums LIMIT 1; 返回 ?,第一反应是种子数据写坏了,或者建表时字符集没配对。
  • 排查:先不改数据,用 SELECT HEX(name) FROM forums LIMIT 1; 看实际存储的字节,得到 E5A4A7E4BC9AE8AEBAE59D9B,这是合法的 UTF-8(对应"大会论坛")。字节没问题,说明坏的是显示这一层,不是库里的内容。
  • 根因:mysql 客户端的连接字符集没设成 utf8mb4,终端按错误字符集渲染,把中文显示成了问号。
  • 解决:查询时加 --default-character-set=utf8mb4 就正常。导入 seed.sql 时改用 docker compose cp seed.sql mysql:/tmp/seed.sql 把文件拷进容器再执行,绕开 PowerShell 管道带来的编码问题。
  • 学到什么:看到 ? 先分清是"存坏了"还是"显示坏了",HEX() 是最快的判据;跨平台跑 SQL 要把客户端字符集和管道编码一起考虑,别只盯着库的 utf8mb4 配置。

本节不再挂查询原图:判据是 HEX(name) 返回 E5A4A7E4BC9AE8AEBAE59D9B,属合法 UTF-8,问题出在客户端字符集而不是库内数据。

5.5 F:从 C 的分支切出来,后端编译不过

  • 现象:准备在 C 的代码上写测试,切过去先跑 mvn compile,直接失败,一行都起不来。
[ERROR] .../participant/service/ParticipantService.java:[95,56] 找不到符号
  符号:   方法 getUserId()
  位置:   类 com.cncc.meeting.participant.dto.ForumRegistrationRow
  • 排查:先看报错的第 95 行,是 Collectors.groupingBy(ForumRegistrationRow::getUserId);再翻 DTO,只有 forumId / forumName / registeredAt;最后回到 ParticipantMapper.selectByUserIds,发现 SQL 里也没查 r.user_id。三处对不上,说明不是某个文件写错,而是这条组装链路从头到尾就没带 userId。
  • 根因:C 组装参会名单时要按 userId 分组,但 DTO 字段和查询列都漏了;提交前没跑编译,所以分支 push 上去看起来是好的。
  • 解决:DTO 补 private Long userId;,SQL 改成 SELECT r.user_id AS user_id, ...,重新 mvn compile 通过,顺手提醒了 C。
  • 学到什么:分支能 push 不代表能编译。跨分支集成第一步应该是全量编译,而不是先写新代码;DTO、Mapper SQL、Service 三处要一起对。

5.6 F:冲突字段被 Jackson 改成了 aforum_id

  • 现象:/api/schedule 返回 200,冲突对数也对,但字段名是 aforum_id / bforum_id,契约里写的是 a_forum_id / b_forum_id。
{"aforum_id":2,"bforum_id":3,"aforum_name":"大模型与智能体",...}
  • 排查:先用 curl 打真实接口看原始 JSON,确认不是前端转出来的;再对照 docs/api.md 里的 ConflictItem;最后写了一个最小的 Jackson 序列化测试,看到 getAForumId() 被拆成 AForumId,snake_case 对连续大写不会插下划线。
  • 根因:aForumId 的 a 和 F 边界不是 Jackson 认的"小写转大写",所以只得到 aforum_id。
  • 解决:四个字段加 @JsonProperty("a_forum_id") 这类显式名称,并新增 ConflictItemJsonTest 把字段名锁住;master 上单独提了修复。
  • 学到什么:契约字段名不能只靠全局命名策略,aForumId 这种边界必须显式注解;单测全过不等于接口返回对,最后一定要打真实接口核对 JSON。

5.7 C:page_size 传了等于没传

  • 现象:GET /api/forums?page_size=5,返回还是 10 条,响应里的 page_size 也还是 10:
{"code":0,"message":"ok","data":{"page":1,"page_size":10,"total":31,"total_pages":4,...}}
  • 排查:先怀疑前端把参数弄丢了,用 curl 直接打接口,结果一样,说明请求确实带过去了;再试 ?pageSize=5,这次生效,后端认的是 Java 参数名;回头对照 docs/api.md,契约写的是 page_size。
  • 根因:@RequestParam int pageSize 没写显式 name,Spring 按编译保留的 Java 参数名绑定;契约要求 snake_case,所以 page_size 被忽略、静默用了默认值。
  • 解决:4 处改成 @RequestParam(name = "page_size", defaultValue = "10"),修后 ?page_size=5 返回 5 条、page_size 是 5、total_pages 从 4 变成 7。
  • 学到什么:契约里的字段名要在注解层写死,不能靠变量名"刚好对上";这类参数绑定错了不报错,只会静默用默认值,自测必须覆盖"改了参数、响应真的变"。

图14

图注:?page_size=5 返回 5 条,total=31、total_pages=7,证明参数名修复后分页生效。

5.8 C:匿名访问论坛被拦成 401

  • 现象:不带 Token 请求 GET /api/forums,返回 401:
{"code":401,"message":"未登录或登录已过期","data":null,"timestamp":1793680936055}
  • 排查:先确认 ForumController.list() 上确实标了 @PublicApi;再顺着请求看拦截器,preHandle 里是无条件抛 401,根本没读这个注解。也就是说注解写了但没人认。
  • 根因:拦截器只有"默认拒绝",没有"注解白名单"这一层;@PublicApi 当时只是个标记,不参与放行判断。
  • 解决:等 B 的 JWT 合入(5a2cb70)后,把 preHandle 改成先判 @PublicApi 再验 Token;复测匿名 /api/forums 返回 200,匿名 /api/forums/mine 仍是 401。
  • 学到什么:公开接口的白名单必须真的读注解,不能按 URL 前缀猜;"注释里写了公开"不等于"运行时公开",联调时要用不带 Token 的请求逐条验。

5.9 B:本地 JDK 25 下 Lombok 不生成代码

  • 现象:本地 mvn compile 直接失败,报错不是语法错,而是 Lombok 本该生成的成员找不到,比如:
[ERROR] .../common/BizException.java:[14,24] 找不到符号
  符号: 方法 getMessage()

第 14 行是 super(errorCode.getMessage()),getMessage() 应该由 ErrorCode 上的 @Getter 生成;同一轮还报了一批 DTO 的 getter / @RequiredArgsConstructor 构造器缺失。

  • 排查:先怀疑 import 漏了,逐个确认 lombok.Getter、lombok.RequiredArgsConstructor 都在;把失败类排在一起看,发现共同点是全都依赖 Lombok,才转向编译链。确认本地 java -version 是 25,而 pom.xml 的 java.version 是 17;查 javac 的变更说明,JDK 23 起不再隐式执行 classpath 上的注解处理器,对上了。
  • 根因:Lombok 只作为普通依赖放在 dependencies 里,没进 annotationProcessorPaths;旧版 javac 会自动扫 classpath 上的处理器,JDK 23+ 不扫了,注解等于没跑。
  • 解决:在 maven-compiler-plugin 里显式声明 annotationProcessorPaths,指向 org.projectlombok:lombok:${lombok.version},编译目标仍按 java.version=17;改完 mvn clean compile 通过。修复随鉴权那次提交一起进 master(5b8b4a0)。
  • 学到什么:"找不到符号"要先分清是代码写错,还是生成代码没生成;本地 JDK 高于项目 target 时,注解处理器必须显式声明,不能指望它自动从 classpath 扫出来。

5.10 B:接口时间漏了 +08:00

  • 现象:接口返回的时间是 2026-11-03T09:00:00,尾部没有偏移量;前端按 ISO 8601 解析,没有偏移量就按本地时间处理,换到 UTC 环境会整体差 8 小时,和契约 1.4 的示例也对不上。
  • 排查:先 curl 打真实接口看原始 JSON,排除前端格式化的问题;再翻 JacksonConfig,OUTPUT 的 pattern 是 yyyy-MM-dd'T'HH:mm:ss,虽然代码里调了 value.atOffset(CN_OFFSET),但 pattern 没有 XXX 占位符;最后对照契约 1.4 的示例,确认要求带偏移量。
  • 根因:LocalDateTime 本身不带时区,偏移量是在序列化时补进去的;pattern 不写 XXX,补上的 offset 不会出现在字符串里,读回来就只剩"本地时间"这一层语义。
  • 解决:pattern 改成 yyyy-MM-dd'T'HH:mm:ssXXX,统一输出 2026-11-03T09:00:00+08:00;入参同时兼容带偏移量的 ISO 8601 和不带偏移量的本地时间。改动很小,单独提了一次(2f74816)。
  • 学到什么:时间字段要同时看"类型"和"输出 pattern"两件事,atOffset 不等于输出里一定有偏移量;契约里写死的字符串格式最好有用例锁住,否则全局配置一改没人发现。

5.11 E:切了语言,一半界面还是中文

  • 现象:点语言下拉切到英文 / 日文后,导航、标题、表头、按钮都变成了目标语言,但"各论坛报名人数"里的论坛名、通知列表里的标题和正文仍是简体中文,五语言看起来只覆盖了一半。
  • 排查:按语言逐个视图走查,确认只有"来自数据层"的文本没变;打开控制台,看到 missing key 告警,说明渲染时确实去查了 key,但语言包里没有对应词条,于是回退到了 zh-CN;再看 applyStaticI18n(),它只遍历 [data-i18n] 静态节点,论坛名、通知标题是 Mock 数据直接拼进模板的,压根没走翻译函数。
  • 根因:多语言只做在"静态 HTML 文案"这一层。Mock / 接口返回的业务字段是数据,不经过 data-i18n,切语言时不会重绘成目标语言;mock.forum.{id}.name 这类 key 规则也没有提前约定。
  • 解决:在 app.js 增加 mockI18n(key, fallback),先查当前语言包、再回退 zh-CN、最后回退原始值;五个语言包补齐 22 个论坛的 mock.forum.{1..22}.name/.topic 与 4 条通知的 mock.msg.{1..4}.title/.body,论坛列表、仪表盘、看板、通知预览与"最近发布"五处统一改走这个函数(602983c)。
  • 学到什么:多语言不是"把中文文案换成英文",凡是会进入模板的文本都要有稳定 key。判断标准很简单:切语言后如果某一类文字没变,它一定没走 t();来自数据层的字符串要预留"翻译 key + 兜底值"两段信息。

5.12 E:管理员登录成功,页面却瞬间被弹回登录页

  • 现象:用 chair 登录后能看到管理端界面闪一下,随后自动跳回根目录登录页;参会者账号同样行为。刷新、换浏览器都复现,登录接口本身没有报"账号或密码错误"。
  • 排查:用 Playwright 抓控制台和网络,发现所有请求都打到了 /api/* 而不是本地 Mock;在页面里打印 localStorage,确认 cncc2026-token 登录后确实写入,但随后被 setToken(null) 清空,代码侧的失败分支是 ApiError(401, "unauthorized");直接请求 admin/api.js 和磁盘内容比对,发现浏览器加载的是旧版本(USE_MOCK = false),而磁盘上是 true;换成带 Cache-Control: no-store 的本地服务重载,问题立刻消失。
  • 根因:三个问题叠在一起。第一,api.js 的 Mock 开关在远端版本里是 false,本地没起后端,/api/auth/me 必然 404;第二,boot() 把 401 统一处理成"清 token + location.replace("../index.html")",页面表现为"登录成功又被踢出",没有把真实原因暴露出来;第三,python -m http.server 不返回 Cache-Control,Chrome 对 HTML / JS 用了启发式缓存,脚本改回 USE_MOCK = true 也没生效。
  • 解决:把 USE_MOCK 恢复为 true 并保留 sessionStorage 的开关透传;给页面脚本和跳转 URL 加 ?v=20261103 版本号强制换缓存(cdd939b);本地验证改用带 no-store 头的静态服务;同时把 ADMIN_ROLES 与角色分流写进统一登录页,避免非管理角色被错误地放进管理端又被守卫弹回。
  • 学到什么:前端"失败后静默跳转"是最难查的一类问题,因为界面表现(回到登录页)和真实原因(接口 404)完全错位。以后会让 catch 分支至少 console.warn 出接口路径和状态码;另外,纯静态部署必须显式配置 HTML / JS 的缓存策略,否则"改了没生效"会浪费大量排查时间。

5.13 其他候选问题

以下问题在排查中出现过,但没有形成完整的"现象 → 根因"记录,列出备查。

#候选问题说明归属
1契约改动导致返工冻结后又改字段/语义,前端 Mock 和后端实现两头改A
2时区与字符集MySQL 连接串缺 serverTimezone=Asia/Shanghai / characterEncoding=utf8,时间差 8 小时或中文乱码A
3CSV 导出在 Excel 里乱码少了 UTF-8 BOM,Excel 默认按 GBK 解析C
4半开区间冲突判定[start, end) 与闭区间差一个端点,"刚好接上"算不算冲突F
5LLM 降级超时 / 非 JSON / 编造 forum_id,重试一次后降级为关键词匹配F
6Docker 首次构建失败多阶段构建拉不到基础镜像、Maven 依赖没缓存、健康检查 unhealthyA
7重复报名 409 与事务原子性users + registrations 必须一起成功,唯一索引撞了要回滚B
8统计口径大会人数是去重用户数而不是报名记录数B
9消息轮询游标since 用时间戳还是 id,时间戳精度不够会重复或漏消息C
10权限 403 的边界分论坛主席发非本人论坛的消息、参会者改别人的报名B
11多语言缺 key 回退切到德语时部分文案还是中文,回退 zh-CN 并打日志E
12前端重复提交报名按钮连点产生两条记录 / 覆盖别人的选择D
13Nginx 反代路径/api 前缀被吃掉或重复,前端 404 但后端日志正常A

6. AI Coding(14′)

这一节记录 AI 的使用方式、验证方法与出问题的地方。
每一节附 prompt 原文、生成代码、改动点与测试结果。

6.1 我们怎么用 AI

  • 工具:B/C/E/F 四人用的是 Trae CN(TraeCode)IDE 内置 Agent 模式,模型统一为 DeepSeek-V4.1-Flash;E 另外用 Playwright 做页面走查、抓控制台与截图取证。A 用的是 Trae CN 客户端(多轮,未导出会话),D 没有交回用量记录。没有用网页版模型或 CLI 批量生成。
  • 用在哪些环节:起工程骨架、写 Controller / Service / Mapper 首稿、写单元测试骨架、查编译与运行报错、把中文文案翻成五套资源包、按契约补 Mock 数据。
  • 坚持手写的环节:契约字段名与错误码的最终核对、SQL 的统计口径(COUNT(DISTINCT user_id) 和 COUNT(*) 不能混)、测试的预期值(端点相接算不算冲突这类判断)、语言包里的会议术语。
  • 团队约定:AI 生成的接口代码必须对着 docs/api.md 逐字段核对才能提交;提交前 mvn test 必须全绿,异常分支要用 curl 打一遍(401/403/404/409/422 各至少一条);字段名不接受"看起来对",以契约里的字符串为准。

6.2 AI 使用量化

本节量化 AI 参与程度,并给出每个数字的来源与口径。

项内容
模型 / 工具名称Trae CN(TraeCode)IDE 内置 Agent;A 用 Trae CN 客户端;模型统一 DeepSeek-V4.1-Flash。E 另配 Playwright 做页面走查与截图。D 未提供
版本号模型 DeepSeek-V4.1-Flash;IDE / 客户端版本未记录
使用人数5 人实际使用(A/B/C/E/F);D 未提供,未计入
调用规模各人自报口径不同:B 约 60 次工具调用、C 约 60 次工具调用、E 约 70 次指令、F 约 60 轮对话;A 是多轮使用但没有导出会话、无法逐轮计数,只能按提交行数侧写
AI 生成代码占比两种口径,比较时别混:①按“个人提交新增代码行”(剔除 PSP / 博客等文档);②按“最终文件行数”(含人工改写,E 用的就是这个)。逐人:F 98%(1,650 / 1,680,口径①)、B 约 95%(自报;仓库实测 Java 新增 1,635 行,比例未按实测重算)、C 91%(890 / 976 主代码,口径①)、E 约 70%(2,330 / 3,343,口径②)、A 约 85%(口径①,估算非日志)
人工改写 / 自设计部分逐人见下方小节。共同点是“判断类”内容人工定:契约字段与错误码、统计口径、测试预期值、命名词表;AI 主要负责首稿与重复性拼装

我们只在部分环节用 AI:骨架与首稿、单测骨架、报错排查、语言包翻译、Mock 数据生成。契约的最终措辞、统计口径、测试断言和术语校对是人工定的——这几处出错整块功能都会错,不敢交给模型。D 的用量没有记录,这一栏空着,不估算。

F 的用量(兰秀楠自报,2026-11-04)

项内容
模型 / 工具名称Trae CN(TraeCode)IDE 智能体
版本号模型 DeepSeek-V4.1-Flash;IDE 版本未记录
使用人数1 人(F 本人)
调用规模约 60 轮对话 / 工具调用,含约 40 次终端命令、10 余次代码生成与修改
AI 生成代码占比约 98%。口径:个人提交代码新增 1,680 行(不含 PSP 文档),AI 生成并保留约 1,650 行,人工手写 / 修改约 30 行
逻辑自设计 / 人工改写部分逻辑由本人定、代码由 AI 落(人工手写 / 修改约 30 行,按个人提交新增 1,680 行口径)。F 自述里点名的部分:提示词结构(RecommendPrompt 的角色、只返回 [{forum_id, reason}]、禁止编造清单外 id);四道校验(论坛 id 真实存在、不重复、理由不能太短、与用户问题相关);降级与重试策略(解析或校验失败带修复指令重试一次,网络 / 密钥 / 超时不重试,直接关键词匹配并标 degraded=true);单测断言(端点相接不算冲突、conflicts_with 双向一致等预期值)。对应 RecommendationParser、RecommendService、ConflictDetector、ScheduleService 与测试类。

C 的用量(陈时文自报,2026-11-04)

项内容
模型 / 工具名称Trae CN(IDE 内置 Agent 模式)
版本号模型 DeepSeek-V4.1-Flash;IDE 版本未记录
使用人数1 人(C 本人)
调用规模约 60 次工具调用(Shell 约 25、文件读写约 20、其余约 15;C 注明按 A 侧记录核对后改准)
AI 生成代码占比91%。口径:主代码 976 行,AI 首稿 890 行(91.2%),手工改写 86 行(8.8%);测试与脚本 393 行由 AI 出骨架、断言预期值手填
纯手写部分PSP 工时表、自测用例的预期值、合并后 9 处类型 / 角色适配

B 的用量(张祺自报,2026-11-04)

项内容
模型 / 工具名称Trae CN(TraeCode)IDE 智能体
版本号模型 DeepSeek-V4.1-Flash;IDE 版本未记录
使用人数1 人(B 本人)
调用规模约 60 次工具调用(文件读写约 45、编译约 8、git 约 7)
AI 生成代码占比约 95%(B 自报,按个人提交的新增行数口径)。B 自报新增约 1,450 行;按仓库实测,8 条提交的 .java 新增合计 1,635 行(不含 smoke-test.ps1 与文档),行数口径以仓库实际为准,比例未按实测重算
人工改写 / 自设计部分约 5%(按新增 Java 行数口径):AI 生成后由本人改写、或本人先定逻辑再让 AI 落代码的部分,具体四条见下方

B 的"人工改写 / 自设计部分"具体是:

  1. RegistrationMapper 的 <script> 动态 SQL 拼接——MyBatis 注解里写 IN 条件的动态片段,AI 给的版本跑不通,按表结构自己拼的 foreach。
  2. StatsMapper 三条统计 SQL——总数 / 按论坛 / 脱敏名单的口径对着契约 §5 定,COUNT(DISTINCT user_id) 与 COUNT(*) 不混用。
  3. 权限分支判断——@PublicApi / @RequireRole 的放行顺序、forum_chair 只认 chair_user_id、为空直接 403 不放宽。
  4. scripts/smoke-test.ps1 的断言顺序与 PSP 时间口径。

db/schema.sql、db/seed.sql 是 A 手写,不计入 B 的产出。

E 的用量(梁鑫敏自报,2026-11-04)

项内容
模型 / 工具名称TRAE IDE(TraeCN)内置 AI Agent(Agent 模式),配合 Playwright 做页面走查、控制台取证与截图
版本号模型 DeepSeek-V4.1-Flash;IDE 版本未记录
使用人数1 人(E 本人)
调用规模约 70 次指令(约 45 次直接产生文件改动,其余为查错、截图、git 操作)
AI 生成代码占比约 70%。口径:分母取最终提交行数,不取 diff;admin/ 3,140 行(按仓库实际:app.js 992 + api.js 489 + 五个语言包 1,042 + mock-data.js 174 + styles.css 296 + index.html 147)+ 根目录统一登录 203 行 = 3,343 行;AI 首次产出、未被整体重写约 2,330 行,2,330 / 3,343 ≈ 69.7%
人工改写 / 自设计部分admin/mock-data.js(174 行,22 论坛 / 31 参会者 / 101 条报名逐条构造)、admin/styles.css(296 行,参照参会者端视觉手工调整)、五个语言包的术语校对(约 180 行人工改写,重点校对德语 / 日语的会议术语)

五个语言包各 190 个 key,其中论坛名 / 主题 22 组、通知标题 / 正文 4 组。

A 的用量(按仓库行数估算,2026-11-04)

项内容
模型 / 工具名称Trae CN 客户端(与组员 IDE Agent 同模型)
版本号模型 DeepSeek-V4.1-Flash;客户端版本未记录
使用人数1 人(A 本人)
调用规模多轮(契约、建表、骨架、部署脚本、文档整理各一段),没有导出会话记录,无法逐轮计数
AI 生成代码占比约 85%。口径:个人提交新增代码行 1,439(.java 406 + .sql 441 + .html 226 + .yml 111 + .xml 103 + .sh 93 + .conf 59,来自 git log origin/master --author=林嘉祚 --numstat),不含 2,243 行文档;估算 AI 首稿保留约 1,220 行。这是估算,不是日志统计
人工改写 / 自设计部分契约 v1 八组接口的字段与错误码表(docs/api.md);四张表的结构 / 索引 / CHECK 约束(db/schema.sql);健康检查的 start_period: 60s 与 depends_on: service_healthy;Nginx proxy_pass 不带 URI 的取舍(否则 /api 前缀会被吃掉)

D 的用量(未提供记录,按仓库实测估算)

项内容
模型 / 工具名称未提供(D 未交回会话记录,无法确认是否使用 AI、用的什么工具)
版本号未提供
使用人数1 人(D 本人)
调用规模未提供
AI 生成代码占比未提供原始记录。仓库实测:frontend/ 四个文件最终 1,489 行(app.js 908 + api.js 207 + index.html 213 + styles.css 161);8 条提交累计新增前端代码 1,546 行(另 README 26 行、PSP 64 行,来自 git log origin/master --author=甘羽潇 --numstat)。按纯手写原生 JS 单页、逐屏调样式的交付形态,估算 AI 生成并保留约 60%70%,即约 9301,080 行。这是估算,不是日志统计;D 未提供用量,所以不能当精确值。
人工改写 / 自设计部分报名按钮按 start_time / end_time 置灰与文案、日程按日期分组与冲突标记、消息 since 游标的本地存储、四处接口失败降级路径、renderScheduleLegacy / renderMessagesLegacy 两段旧渲染函数

6.3 Prompt 使用记录

以下为实际使用过的提示词原文。

记录 1(F · 推荐接口的系统提示词,代码原文)

  • 场景:把库里 31 个论坛注入提示词,要求模型只输出 [{forum_id, reason}],不许编造清单外的 id。
  • Prompt 原文:
你是 CNCC 2026 会议系统的分论坛推荐助手。
你只能从下面给出的论坛清单中选择论坛,禁止编造清单之外的论坛或 id。

论坛清单(JSON 数组,字段:id 编号、name 名称、topic 议题、chair 主席):
[%s]

输出要求:
1. 只输出一个 JSON 数组,不要输出任何解释文字,不要使用 Markdown 代码块;
2. 数组元素格式为 {"forum_id": 整数, "reason": "推荐理由"};
3. reason 用中文写 20~60 字,必须结合用户描述与论坛议题,不能写空话;
4. 最多返回 %d 条,按相关度从高到低排序;
5. 若没有相关论坛,输出空数组 [],不要硬凑。
  • AI 给出的结果:RecommendPrompt 拼提示词、RecommendService 调模型、RecommendationParser 剥代码块并解析 JSON。
  • 我们怎么改的:在解析后补了 id 存在性、去重、理由长度、相关性四道校验;limit 越界先在入口挡掉。
  • 是否采纳:部分采纳,另加了失败重试与降级。

记录 2(F · 解析失败后的修复指令,代码原文)

  • 场景:第一次输出不是合法 JSON 时,用固定话术再问一次。
  • Prompt 原文:
上次输出无法解析。请严格只输出 JSON 数组,不要任何解释和代码块标记。
  • AI 给出的结果:RecommendService 第二次调用模型,仍失败就转关键词降级。
  • 我们怎么改的:把重试限定为"解析/校验失败"一次,网络、密钥、超时直接降级,不浪费第二次调用。
  • 是否采纳:全采纳。

记录 3(F · 联调时用的用户输入)

  • 场景:打真实接口验证推荐结果能不能落在真实论坛上。
  • Prompt 原文:
用户的研究方向和参会需求:我对大语言模型和软件工程感兴趣
  • AI 给出的结果:返回带 reason 的推荐数组,全部是库内论坛 id。
  • 我们怎么改的:没改,用来核对 degraded=false 与理由长度。
  • 是否采纳:全采纳。

6.4 生成代码怎么验证

这一节是 14′ 里最容易拿分也最容易露馅的地方。写"我看了代码觉得没问题"基本等于没写。

我们的办法是四层,缺一层就会漏:

  1. 对着契约逐字段核对:接口改完把响应字段和 docs/api.md 的对象定义比对一遍。aforum_id(5.6)、时间缺 +08:00(5.10)都是在这一层揪出来的。
  2. 单元测试:全项目 45 条,mvn -B test 一次跑完,实测见 6.6。
  3. 打异常分支:把 200 / 401 / 403 / 404 / 409 / 422 各打一遍——匿名 /api/forums 应 200、匿名 /api/forums/mine 应 401、参会者看 /api/participants 应 403、报名不存在的论坛应 404、重复报名应 409、page_size=200 应 422。本次按契约实测 41 个请求,状态码全部对得上。
  4. 真页面点一遍:三类角色登录后逐个视图走查。页面表现和接口返回对不上的地方,基本都藏在这一层。

一个具体例子:E 的看板(AI 生成)把 GET /api/forums/mine 的返回值当单个论坛对象用,直接取 .id 去调 /api/stats/forums/{id};但该接口实际返回的是 ForumSummary 数组,array.id 是 undefined,拼出来的 URL 就是 /api/stats/forums/NaN,被后端以 422 拒掉。我们用 demo_forum_chair 登录点进看板,页面显示"加载失败 · HTTP 422"。这是"单测全过、页面一点就挂"的典型:单测只测 Service,没人验证页面拿到的数据形状。修法选了不改契约的那条——admin/api.js 的 myForum 先判断返回值是不是数组,取第一项再交给看板;改完用本地真实后端 + 种子数据重新打开看板,截图见

图15

。

6.5 AI 犯的错(至少写 2 个)

每条按"它写了什么 → 为什么是错的 → 我们怎么发现的"记录。

错误 1:page_size 传了等于没传(C 侧)

  • AI 写了什么:public Result<PageResult<ForumSummary>> list(@RequestParam int pageSize, ...),方法体里直接用 pageSize 拼 LIMIT。
  • 错在哪:Spring 按编译保留的 Java 参数名绑定,契约要求 page_size;前端传 page_size=5 被静默忽略,永远用默认 10,不报错也不警告。
  • 怎么发现的:自测时"改了参数、响应没变",curl 直打也复现;换成 pageSize=5 才生效,回头对照契约才确定是命名问题。
  • 怎么改的:4 处改成 @RequestParam(name = "page_size", defaultValue = "10"),docs/selftest.ps1 补一条"参数变了响应必须变"的断言。

错误 2:冲突字段被序列化成 aforum_id(F 侧)

  • AI 写了什么:private Long aForumId;,靠全局 snake_case 策略输出。
  • 错在哪:Jackson 不把 aF 当作"小写转大写"的边界,输出成 aforum_id / bforum_id,与契约的 a_forum_id / b_forum_id 不符。
  • 怎么发现的:curl 打 /api/schedule 看原始 JSON,字段名对不上;补一个最小序列化测试稳定复现。
  • 怎么改的:四个字段加 @JsonProperty 显式命名,新增 ConflictItemJsonTest 把字段名锁死。

错误 3:时间输出没有偏移量(B 侧)

  • AI 写了什么:DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"),配合 value.atOffset(CN_OFFSET) 以为就带时区了。
  • 错在哪:atOffset 只把 LocalDateTime 换成 OffsetDateTime,偏移量能不能出现在字符串里由 pattern 决定,串里没有 XXX 就只剩本地时间。
  • 怎么发现的:C 联调反馈时间没有 +08:00;对照契约 §1.4 和 JacksonConfig 的 pattern 复现。
  • 怎么改的:pattern 末尾加 XXX,同步自测清单加"带时区断言"。

错误 4:Mock 数据没走 t()(E 侧)

  • AI 写了什么:切语言时只遍历 [data-i18n] 静态节点重绘,论坛名、通知标题直接拼字符串进模板。
  • 错在哪:静态文案翻了,数据层的文字没翻,五语言看起来只覆盖一半;缺 key 时静默回退 zh-CN,不切语言根本发现不了。
  • 怎么发现的:按语言逐个视图走查 + 控制台 missing key 告警。
  • 怎么改的:加 mockI18n(key, fallback),五个语言包补 mock.forum.* / mock.msg.* 词条,列表、仪表盘、看板、通知预览与"最近发布"五处统一走该函数。

四条的共同点:AI 都能把代码写到"能跑",但契约字段、命名边界、参数名这些"字符串层面的约定"它不会主动去对。所以我们的核对清单里,字段名和错误码是唯一不接受"看起来对"的部分。

6.6 单元测试与证据

  • 测试范围:
测试类条数覆盖点
ConflictDetectorTest8端点相接、部分重叠、完全包含、零时长、跨天、多对组合、顺序无关、脏数据
RecommendationParserTest8代码块 JSON、夹带文字、包装对象、非法 JSON、编造 id、重复 id、空理由、无关推荐、limit、字符串 id
RecommendServiceTest5LLM 成功、坏 JSON 重试一次、超时降级、两次失败降级、报名人数回填
KeywordMatcherTest4领域词抽取、视觉/多模态命中、无关查询返回空、limit
ScheduleServiceTest5冲突对与 conflicts_with 双向一致、相邻端点不冲突、空日程、未登录 401、人数回填
ConflictItemJsonTest1JSON 字段 a_forum_id / b_forum_id 不被 Jackson 改写
ForumServiceTest(C)5分页参数、详情与议程排序、/forums/mine 权限、不存在论坛 404
MessageServiceTest(C)9global/forum 两种 scope、since 游标、非法游标 422、论坛主席越权 403
  • 测试数量与通过情况:本次在临时 worktree(origin/master)里用 maven:3.9-eclipse-temurin-17 容器实跑 mvn -B test,结果 Tests run: 45, Failures: 0, Errors: 0, Skipped: 0,BUILD SUCCESS,总耗时 3:42。其中 F 侧 31 条、C 侧 14 条。上一版按成员自报记录,本次一律以实跑为准。
  • 怎么跑:
# 本机有 JDK 17
cd backend && mvn -B test
# 没有 JDK 时用容器跑(本次就是这种方式)
docker run --rm -v <repo>/backend:/build -w /build maven:3.9-eclipse-temurin-17 mvn -B test

报告在 backend/target/surefire-reports/。只跑 F 模块用 mvn test -Dtest="KeywordMatcherTest,RecommendationParserTest,RecommendServiceTest,ConflictDetectorTest,ConflictItemJsonTest,ScheduleServiceTest"。

  • 测试报告截图:

图16

  • LLM 降级策略(无密钥也能复现):POST /api/llm/recommend 先走模型——RecommendPrompt 把库里论坛清单注入提示词,LlmClient 按 OpenAI 兼容协议调用,RecommendationParser 再做四道校验(只认清单内 id、理由非空、去重、limit)。模型超时、返回非法 JSON、编造 id 时重试一次;仍失败就落到 KeywordMatcher,接口仍返回 200,degraded=true,页面顶部明示"智能推荐暂时不可用,以下为关键词匹配结果"。复现路径:不设 LLM_API_KEY(或把 LLM_BASE_URL 指到不可达地址)→ 登录后 POST /api/llm/recommend → 响应里可见 degraded=true。本环境没有真实密钥时只保留降级实测图

    图17

    。2026-11-04 用真实密钥在本地按同一份 origin/master 代码起 compose 补测了一次正常路径:POST /api/llm/recommend 返回 degraded=false、3 条全部命中库内 forum_id(2/4/9),耗时 1.48s,响应体见

    图18

    。密钥只写本地 .env(已被 .gitignore 覆盖),测完即删,未入库、未进截图、未进日志。线上部署时没有配置密钥,所以线上仍是降级路径。

图注:

图18

为接口响应体本身(不含请求头、不含密钥),本地配置真实模型密钥实测,密钥不入库。

图注:mvn -B test 在 maven 容器内实跑,8 个测试类 45 条全部通过,BUILD SUCCESS,总耗时 3:42;图里的时间戳是容器时钟(UTC)。

  • 覆盖边界:这 45 条都是单元测试,Service 层的 Mapper 用 Mockito 打桩,没有起 MySQL 的集成测试;真实接口的 200/4xx 由 curl 另测(见 6.4)。
  • 测试代码位置:backend/src/test/java/com/cncc/meeting/
@Test @DisplayName("一个论坛恰好在另一个结束时开始,不算冲突(半开区间规则)")
void adjacentEndpointsAreNotConflict() {
    Forum a = forum(17, "多模态大模型", "2026-10-23T13:30:00", "2026-10-23T16:00:00");
    Forum b = forum(18, "AI Agent 与智能体系统", "2026-10-23T16:00:00", "2026-10-23T17:00:00");

    assertThat(ConflictDetector.overlaps(
            a.getStartTime(), a.getEndTime(), b.getStartTime(), b.getEndTime())).isFalse();
    assertThat(ConflictDetector.detect(List.of(a, b))).isEmpty();
}

7. 提交日志与运行截图

commit 官方要求每人 ≥3 次;本组实际提交次数与日志截图见下表。

7.1 提交记录

成员commit 次数截图位置主要提交内容
A(林嘉祚 102400118)11 次(主提交 5 + 文档与集成 6;官方要求每人 ≥3 次)

图19

契约与建表 / Spring Boot 骨架与统一响应 / 种子数据 / README 与架构图 / 博客与 PSP 文档,明细见下表
B(张祺 102400432)8 次(8 条全在 master,作者 ZOEKOKF;官方要求每人 ≥3 次)

图20

后端协作规范与日志 / 422 errors 结构与公共 DTO / JWT 与注解式鉴权 / 报名四接口 / 注册即登录与当前用户 / 统计总览与脱敏名单 / 自测脚本与文档 / 时间偏移修复,明细见下表
C(陈时文 102400313)13 次(按提交内容归属:本人身份 6 条 + local-verify 7 条;按 Git 作者名直接统计为 16 条,其中 3 条无法确定归属;官方要求每人 ≥3 次)

图21

论坛列表与详情 / 议程 / 消息 scope 与 since 游标 / 参会名单与 CSV / GET /api/forums/mine(master 内为 9544757)/ 单测与自测脚本;24 条接口自测全部通过(按文字结论记录,不上图)
D(甘羽潇 102400437)8 次(作者名 甘羽潇;官方要求每人 ≥3 次)

图22

:git log origin/master --author="甘羽潇" --date=format:'%Y-%m-%d %H:%M' --pretty=format:'%h %ad %an %s',实际输出作者名为"甘羽潇"
参会者首页与论坛浏览 / 报名交互与契约字段 / API 请求层 / 日程与详情交互 / 日程功能 / 消息中心 / 登录与报名权限 / PSP 与运行截图,明细见下表
E(梁鑫敏 102400438)16 次(11 次代码 / 修复 + 1 次 revert + 1 次 PSP 文档 + 3 次 merge;官方要求每人 ≥3 次)

图23

管理端布局与路由 / 仪表盘与看板 / 参会者名单与 CSV / 通知发布 / 五语言与动态 i18n / 统一登录入口 / 契约请求层 / 与参会者端合并
F(兰秀楠 102400316)6 次(5 次代码/测试 + 1 次 PSP 文档;官方要求每人 ≥3 次)

图24

提示词与推荐接口 / 四道校验与关键词降级 / 半开区间冲突检测与日程接口 / 单元测试 / 冲突字段契约修复 / PSP

统计口径:本表按提交内容归属统计,六人均满足官方"每人 ≥3 次"。部分提交使用了本机 Git 账号名:B 显示为 ZOEKOKF,C 有 7 条显示为 local-verify(按改动内容归属到本人);另有 6 条 hid_* 提交无法归属到具体成员,未计入任何人的次数。合并阶段使用了集成机账号 local-verify,因此按 Git 作者名直接统计会得到不同数字(C 为 16 条,其中 3 条无法确定归属),本表保留按内容归属的团队口径。

A 的 5 条主提交(作者 102400118林嘉祚,合计 11 次)

#commit信息
19c91e72docs: 初版接口契约与建表脚本
22d494f9docs: 契约冻结 v1
3df4be00feat: Spring Boot 骨架、统一响应与本地容器化
43b51361feat(db): 种子数据
5365c20bdocs: README 与 E-R / 部署架构图

图19

图注:git log --author="102400118林嘉祚" 的完整输出,11 条提交,时间从 10:37 到 23:55;上表 5 条是主提交,其余 6 条是 E-R/部署图、PSP 模板与博客文档。

B 的 8 条提交(作者 ZOEKOKF)

#commit信息
142339a9docs(backend): 新增后端协作规范与统一日志配置
248b7824feat(common): 补齐 422 errors 结构、角色枚举与分页/论坛摘要 DTO
35b8b4a0feat(auth): JWT 签发校验与注解式鉴权(@PublicApi/@RequireRole)
485f2b6efeat(registration): 我的报名 / 报名 / 整体替换 / 取消报名(契约 §4)
53d64a79feat(user): 注册(注册即登录)、登录与当前用户(契约 §2)
6bf94a35feat(stats): 大会总览与分论坛脱敏名单(契约 §5.1 / §5.2)
75237cabdocs(backend): 同步 B 已完成事项与接口自测脚本
82f74816fix(config): 时间输出补上 +08:00 偏移量,对齐契约 1.4

图20

图注:git log origin/master --author=ZOEKOKF 输出 8 条,作者名是 B 的账号名 ZOEKOKF,时间全部是 11 月 3 日。

D 的 8 条提交(作者 甘羽潇,按提交时间排序)

#commit时间(Asia/Shanghai)信息新增行(含文档/图片)
1320ac8511-03 10:57feat: 完成参会者首页和论坛浏览页面254
2e51198b11-03 14:38feat: 完善参会者端报名交互与契约字段106
3387b3d811-03 14:41feat: 新增 API 请求层107
4f381c1a11-03 14:56feat: 完善参会者日程与论坛详情交互374
5f34e9a911-03 15:34feat: 完善参会者日程功能419
698e4d4a11-03 16:05feat: 完善消息中心67
72b4f95a11-03 16:37fix: 完善参会者登录与报名权限245
89759c5a11-03 22:15docs: 补充 PSP 表格和运行截图64 行文档 + 7 张 PNG

D 的提交日志截图见

图22

,命令为 git log origin/master --author="甘羽潇" --date=format:'%Y-%m-%d %H:%M' --pretty=format:'%h %ad %an %s';按作者过滤,实际输出作者名就是"甘羽潇",原样显示,没有改写。上表可由同一条命令直接复核;代码新增合计 1,546 行(另 README 26 行、PSP 64 行),最终 frontend/ 四个文件共 1,489 行。

C / F / E 的提交记录

图21

图注:git log --author="陈时文" 输出 6 条,时间集中在 12:08–12:18;另外 7 条以 local-verify 身份提交(分支修复与合并),按内容归属计入 C 的 13 次。

图24

图注:F 的 6 条提交,含 30 条单测、冲突字段修复(169a03d)与 PSP;作者名 102400316_兰秀楠。

图23

图注:git log origin/master --author=梁鑫敏 输出 16 条,含 3 次 merge(其中两次是把自己的 feature/integration-login 与远端同步)、1 次 revert 和 1 次 PSP 文档提交;作者名 102400438 梁鑫敏。

7.2 运行截图

场景截图位置说明
首页论坛列表

图14

接口返回形态:page_size=5、total=31、total_pages=7。页面卡片读 forum.intro,而列表接口按契约只返回 ForumSummary,实测页面会显示 undefined,所以这里放接口返回图
论坛详情 + 议程

图25

、

图26

详情弹窗渲染简介、时间、地点、主席与报名态;弹窗没有渲染议程,议程用 GET /api/forums/1/agenda 的实测返回佐证(4 条,时间带 +08:00)
详情页(已报名态)

图27

同一详情页在已报名后的按钮文案与状态
报名接口实测(POST / GET / 重复提交)

图28

demo_participant 登录后 POST /api/registrations({"forum_id":2})200、GET /api/registrations 200、重复提交 409,响应原样保留中文与 +08:00
报名页(多选 + 冲突提示)

图29

已选 3 场;已开始的论坛标"论坛已结束,无法取消报名"
个人中心 / 个人日程

图30

4 场,冲突标记来自 /api/schedule 的 conflicts_with
消息中心(大会 + 分论坛分区)

图31

2 条,global 与 forum 两种 scope
会议主席仪表盘

图11

参会者 2 / 论坛 31 / 报名 6,数字来自 /api/stats/overview
分论坛主席看板

图15

本地 docker compose(MySQL + Spring Boot)+ db/seed.sql,以 demo_forum_chair 登录实测;显示 chair_user_id=3 负责的论坛 2「大模型与智能体」,不再出现 NaN。图为真实后端数据,不是 Mock
秘书参会者表格 + CSV 导出

图32

脱敏姓名、报名数;导出接口实测 321 字节、UTF-8 BOM
管理端通知发布

图33

大会 / 分论坛两级 scope
LLM 推荐(正常路径 / 降级提示)

图18

、

图17

正常路径:本地配置真实模型密钥实测,degraded=false、3 条命中库内 forum_id、1.48s,图为接口响应体(不含请求头、不含密钥,密钥不入库);降级路径:不配密钥时 degraded=true,页面顶部给出关键词匹配理由。线上部署未配密钥,线上仍是降级
五语言切换

图34

、

图35

英文 / 日文界面;静态文案与 Mock 词条已翻译,角色名等数据字段仍是中文(3 节 E 段写了这个取舍)
Swagger / OpenAPI 页面

图36

真实控制器已出现在分组里,不再是只有 /api/ping
单元测试报告

图16

45 条全绿,BUILD SUCCESS
Docker 容器运行状态

图13

app 8080、mysql 3307→3306,两者 healthy

截图口径:本次在临时工作副本(origin/master 代码)里起 docker compose(MySQL 8 + Spring Boot),静态页面用本地 Node 服务托管并代理 /api,浏览器为无头 Edge。除注明"接口返回"或"降级"的图外,页面显示的都是真实接口数据。参会者端与管理端现在默认连真实接口,只有显式加 ?mock=1 才走内置假数据;截图时没有改代码里的开关。

图里的论坛时间都是 11 月 3 日、当前日期已是 4 日,所以详情与日程页普遍显示"已结束",这是种子数据的时间导致的,不是页面错误。

7.3 协作过程证据

master 是保护分支,我们实际走 feature 分支 + 合并请求。这类记录比 git log 更能说明分工是真的、契约是怎么收敛的。
分支名、MR 作者名一律按第 3 节名单填。

证据位置 / 截图说明
CodeArts 分支列表不挂图;用 git branch -r 可复现远程共 19 个分支:feat/backen/b、feat/backen/c、feature/a-foundation、feature/a-docs-fix、feature/d-participant-frontend、feature/e-admin-i18n、feature/f-llm、feature/integration-login、feature/integration-c-f、fix/f-schedule-json*、docs/psp-b、docs/psp-c、docs/f-psp、release-v1 等,分支名能看出归属
合并请求记录不挂 MR 页面截图;以 git log --merges --oneline 为准origin/master 上共 15 条 merge 提交,覆盖三个集成点:5a2cb70(B 认证+统一出参并入 C)、26d2018(后端全量并入集成分支)、4416477(B 的时间格式修复)、9b87e70(release-v1 进 master)、95772eb(fix/master-schedule-json)、f80d144(feature/a-docs-fix)、873d868(docs/f-psp)。MR 页面没有导出,不拿截图冒充
契约变更公告以三次提交留痕:2d494f9(v1 冻结)、4ae3368(取消报名 409)、2f74816(+08:00)群公告不作为证据;能复核的是这三次提交本身
代码评审评论不挂图,也没有导出记录MR 讨论当时没有留存,这里不补造"LGTM";评审结论只按合并提交与当前代码判断
master 保护分支规则不挂设置页截图,只保留结论直接 git push origin master 被服务端 pre-receive hook 拒绝,所以所有提交都走 feature 分支 + 合并;报错原文没有保存,设置页也不作为证据

这一节只写能复现的:分支与 merge 提交可以用 git branch -r 和 git log --merges --oneline 当场复核;MR 讨论、群公告、保护分支设置页这三项没有留存,不拿文字或截图冒充。如果评审需要,分支与合并历史可以现场跑命令演示。


8. 复盘与反思(可选,但写得好很加分)

如果还有一天,先改什么。 第一件是补上后端"论坛已开始不许取消报名"的时间校验——契约和前端都做了,就差这一处,属于典型的"文档先行、实现掉队"。第二件是把参会者端列表卡片的字段和 ForumSummary 对齐:页面读 forum.intro,列表接口不返回这个字段,真实数据下会显示 undefined,这比少一个字段更伤观感。

明知故犯的技术债。 议程塞在 forums.agenda 的 JSON 里,代价是不能按讲者检索、不能分页;个人日程靠联表实时算,没有缓存;统计每次 COUNT,没到十万级不会动。这三条都在 4.2.3 写了"什么情况下会失效",不是不知道,是这一天的量级不值得为它们付复杂度。

分工的问题。 后端两条分支并行改同一批 DTO(Role、participantCount、page_size),合并时才炸出 24 处不兼容、返工 55 分钟;根因是契约只约定了字段名,没约定共享类的类型与构造方式。下次会让两个人先把共享类型写进契约示例,再各自开工。另一个问题是前端两人各做一端、各自 mock,接口一接就暴露字段形态差异,说明"先 Mock 后联调"省下的时间会在联调时还回去。

做得最差的一处。 线上实例是按需计费、验收后释放的,博客和 README 里没提前写明"地址有时效",读者点开打不开容易误判;这是表达问题,不是部署问题,已经补在 1.3 和 4.7。


9. 附加功能

9.1 多语言界面(10′)

  • 实现了什么:管理端与参会者端支持中 / 英 / 日等五种语言切换,静态文案走词典表,切换后立即生效,不需要刷新;论坛名、角色名等数据字段保持中文。
  • 怎么验证:

    图34

    、

    图35

    是同一页面在英文、日文下的实测截图;3 节 E 段记录了"数据字段不翻译"的取舍,5.11 记录了切换后部分文案仍为中文时回退 zh-CN 并打日志的处理。

9.2 日程冲突检测(10′)

  • 实现了什么:GET /api/schedule 用半开区间 [start, end) 判断任意两场已报名论坛是否重叠,返回 conflicts_with;个人日程页对冲突项打标提示。
  • 怎么验证:

    图30

    是 4 场报名、含冲突项的实测页面;6.6 的单元测试中有 14 条覆盖冲突判定(刚好相接不算冲突、包含关系、多场交叉),mvn test 全绿见

    图16

    ;5.13 第 4 条记录了端点边界的取舍。

附:六名组员的 PSP

以下为六名组员各自的 PSP 表,按 A–F 排列。

组员 A(林嘉祚 102400118)的 PSP

统计范围只覆盖 2026-11-03 当天的基础设施交付;博客整理、后续联调不计入这份表。
实际起止时间依据 git 提交时间戳与当天操作记录推算,不是掐表计时;推算的地方都标了。

基本信息

项内容
成员A / 林嘉祚 / 102400118
角色组长 · 架构 / 数据库 / 部署 / 博客整理(贡献 18)
统计区间2026-11-03 09:10 ~ 11:20

PSP0 计划

#任务计划工时计划规模(LOC)计划缺陷数说明
1环境与仓库接管15 min00目录非空,clone 可能失败,预留处理时间
2需求与接口契约45 min6501八组接口、统一响应、错误码表
3契约冻结评审20 min00按评审意见改契约,不新增功能
4数据库设计20 min1901四张表、唯一索引、外键与 CHECK
5骨架与容器化45 min9001Spring Boot 骨架 + Dockerfile/compose/nginx
6种子数据30 min24501 场大会 + 30 场专题 + 4 个账号
7文档与图25 min5750README、E-R 图、部署图
合计—200 min(3 h 20 min)2,5603不超过 8 小时,全部压在一天内

时间记录

任务实际起止中断时间实际工时依据 / 备注
环境与仓库接管09:10–09:20未记录10 minclone 报目录非空,改 init + remote add + fetch + checkout master;依据操作记录推算
需求与接口契约 + 数据库设计09:20–09:40未记录20 min两项合并推进,提交 9c91e72(api 664 行 + schema 193 行);依据提交时间戳推算,无法再精确拆分钟
契约冻结评审09:40–10:00未记录20 min补关键待确认项,提交 2d494f9(39+/34−);依据提交时间戳推算
骨架与容器化10:00–10:40未记录40 min提交 df4be00(21 文件);期间遇到 ERROR 3823 与首次启动健康检查误判,排查耗时已含在这 40 分钟里
种子数据10:40–11:00未记录20 min提交 3b51361(db/seed.sql 245 行);两次执行验证幂等,发现中文显示 ?
文档与图11:00–11:20未记录20 min提交 365c20b(README + 3 个图文件,575 行新增)
合计09:10–11:20未记录130 min(2 h 10 min)按时间块累加,不含当天之后的联调与博客整理

中断时间没有可靠记录,不写 0 冒充精确值。上面每一段的起止都来自提交时间戳或当天操作顺序,分钟级拆分是推算。

规模估算

以 git show --stat 和 HEAD 里的实际文件为准:

产出文件数提交新增行当前总行数说明
docs/api.md16646699c91e72 新增 664;2d494f9 又改 73 行
db/schema.sql1193195df4be00 里随外键修复改过 4 行
db/seed.sql12452453b51361
backend/1560160111 个 Java 文件 + pom.xml、Dockerfile、.dockerignore、application.yml
README.md + docs/diagrams/4575576365c20b 新增 575,README 原有 2 行被替换
根目录部署配置4244244.env.example 19 + docker-compose.yml 73 + deploy.sh 93 + nginx/nginx.conf 59
合计262,5222,530当前总行数不含已被覆盖的旧 README 行

口径说明:backend/ 是 git ls-tree HEAD backend 的 15 个文件;df4be00 的 21 个文件里另有 6 个在根目录。之前口头说的"19 个文件"对不上 git,以仓库为准。

缺陷记录

#缺陷发现阶段类型修复动作耗时估算根因
1git clone <url> . 报目标目录非空环境准备(09:10 前后)环境改用 git init + remote add + fetch + checkout master,原分工文档保留为未跟踪文件10 minclone 要求目标目录为空,. 里已有分工文档
2ERROR 3823:messages 建表中断,库里只有 3 张表容器化建库验证(10:00–10:40 内)设计最小复现后确认 CHECK 与 ON UPDATE CASCADE 冲突;外键改 ON UPDATE RESTRICT,保留 ON DELETE CASCADE 与 CHECK;down -v 重建20 minMySQL 8 不允许带引用动作的外键与基于该列的 CHECK 同时存在
3首次 docker compose up -d 报 container cncc2026-mysql is unhealthy首次启动验证(10:00–10:40 内)配置healthcheck 加 start_period: 60s、retries: 30;down -v 全新建库复验10 min初始化期临时实例不监听 3306,默认健康检查窗口太短,depends_on 把误判放大成 app 起不来
4查表中文显示为 ?种子数据验证(10:40–11:00 内)工具用 SELECT HEX(name) 确认字节合法;查询加 --default-character-set=utf8mb4;导入改用 docker compose cp 拷进容器10 minmysql 客户端连接字符集不是 utf8mb4,属于显示层问题,不是数据损坏

缺陷修复耗时已经包含在对应任务的实际工时里,不重复加到 130 min 的合计中。

PSP1 / PSP2 汇总

工时

项计划实际偏差原因
环境与仓库接管15 min10 min−5操作步骤少,直接换落地方式
需求与契约 + 数据库设计65 min20 min−45契约和表结构用 AI 出初稿,人工主要做字段核对和约束取舍;原计划按从零手写估的
契约冻结评审20 min20 min0与计划一致,改动量 39+/34−
骨架与容器化45 min40 min−5其中约 30 min 花在 3823 和健康检查上,配置文件本身生成得快
种子数据30 min20 min−10模板化数据,主要是跑两遍验证幂等
文档与图25 min20 min−5图用 Markdown/HTML 模板改,没有从零画
合计200 min130 min−70 min(−35.0%)计划偏保守,AI 初稿把契约和骨架的耗时压下来了

规模与缺陷

指标计划实际偏差
交付规模(LOC)2,5602,530−30(−1.2%)
缺陷数34+1
缺陷密度1.17 个/KLOC1.58 个/KLOC(4 ÷ 2.53)+0.41
缺陷修复耗时—约 50 min,占实际工时 38.5%—

缺陷密度按当前总行数 2,530 算;4 条缺陷全部在当天发现并修完,没有遗留到联调阶段。

总结

这份 PSP 看下来,最不舒服的一点是:计划 3 小时 20 分,实际 2 小时出头,但省下来的时间不是因为我手快,而是因为契约和骨架大量用了 AI 初稿。真正花时间的是两个"没想到"——ERROR 3823 和健康检查误判,加起来 30 分钟,占了实际工时的近四分之一。

如果重来一次,我会做四件事:

  1. 建表脚本先在空库上跑通再提交。当时 messages 的 CHECK 和外键组合只在空库初始化时才暴露,如果提交前用 down -v 重建一次,9c91e72 就不会带着一个建不完的 schema 进仓库。
  2. 契约冻结前让 B~F 真的拿 Mock 对一遍。v1 冻结后 api.md 又改了 73 行,说明"冻结"更多是我自己宣布的,不是全组核对过的。先让每个人照契约写一个最小请求,再宣布冻结,返工会少很多。
  3. 健康检查按"首次初始化"配,不按稳态配。这次是 start_period 和 retries 救回来的,但本质上是把数据库启动当成了一个瞬时动作。以后所有带初始化脚本的容器都先留足首次启动窗口,再谈"快速失败"。
  4. 中文 ? 那次也提醒我:先 HEX() 看字节,再决定是修数据还是修客户端。第一反应去改种子数据,方向就错了。

组员 B(张祺 102400432)的 PSP

时间口径:取自本机 git 提交时间戳(42339a9 11:36、5237cab 13:15、2f74816 20:18)与会话起止,取整到分钟。

基本信息

项内容
成员B / 张祺 / 102400432
角色后端 · 身份与报名(贡献 17),契约 §2 认证、§4 报名、§5.1/§5.2 统计
统计区间2026-11-03 09:50 — 2026-11-03 20:25(同日两段:上午至午后、晚间返工)

时间记录

阶段预估时间实际时间起止时间备注
读作业与分工、通读契约 v1、盘点 A 骨架偏差405009:50–10:40骨架与契约有 7 处不一致(JWT 空壳、无角色校验、422 结构、时间无偏移量等),逐条列入待修项
写后端协作规范、统一日志、补 .gitignore455610:40–11:36额外补「待修项责任表」「变更登记」,方便 C 对照
公共层(统一响应/错误码/422/时间)+ JWT + 注解式鉴权603411:36–12:10方案在规范阶段已定,编码比预估快;环境问题计入最后一段
认证 §2(注册即登录/登录/me)+ 报名 §4(列表/新增/整体替换/取消)704012:10–12:50users + registrations 同事务;报名接口只认 Token 中的 sub
统计 §5.1 / §5.2(去重人数、姓名打码、forum_chair 归属校验)401512:50–13:05SQL 与权限分支复用上一模块,明显低于预估
冒烟脚本 + 按模块拆 6 次提交 + 推送402013:05–13:25提交依次对齐 common/auth/registration/user/stats/文档
返工:时间输出补 +08:00(C 联调反馈)02520:00–20:25计划外缺陷;改 1 行格式串,重新打包并推送两条分支
合计295240—实际比计划少 55 分钟(−19%)

代码规模与缺陷数

项数值说明
Java(本人新增/重写)45 文件 ≈1,450 行覆盖 common/auth/config/user/registration/stats/mapper 七个包
Java(后端总量)52 文件 1,613 行含 A 骨架约 163 行
PowerShell 自测脚本1 文件 176 行backend/scripts/smoke-test.ps1,约 25 条断言
Markdown2 份 182 行docs/backend-conventions.md 147 行 + 本表
SQL0沿用 A 的 db/schema.sql、db/seed.sql
缺陷数3(开发期 2 / 联调期 1)全部已修复并合入主分支,无遗留

口径说明:b3503e8 把冒烟脚本扩到 34 项断言并连库跑通,上表"约 25 条"是填 PSP 时的早期口径;最终证据以 34 项为准。

编号现象(原文)根因修复
D1[ERROR] .../BizException.java:[14,24] cannot find symbol,location: variable errorCode of type com.cncc.meeting.common.ErrorCode;AuthInterceptor.java:[65,13] cannot find symbolJDK 23+ 的 javac 不再自动执行 classpath 上的注解处理器,Lombok 未生成 getter 与构造器pom.xml 显式声明 annotationProcessorPaths(提交 5b8b4a0)
D2RegistrationController 返回 Result<Map<String,Object>> 编译失败:不兼容的类型:推断类型不符合上界Map.of 把值推断为 List<RegistrationItem>,与 Object 值类型不兼容新增 RegistrationListResult DTO(提交 85f2b6e)
D3接口时间返回 2026-11-03T14:55:20,契约 1.4 要求 2026-11-03T09:00:00+08:00JacksonConfig 输出格式串 yyyy-MM-dd'T'HH:mm:ss 不含偏移量字段格式串改为 yyyy-MM-dd'T'HH:mm:ssXXX(提交 2f74816)

产出对照

产出位置(文件 / 接口 / 页面)是否有截图或测试证据
后端协作规范docs/backend-conventions.md有,文件内 §10 待修项状态表
统一日志backend/src/main/resources/logback-spring.xml有,配置项与规范 §7 一致
认证 3 接口POST /api/auth/register、POST /api/auth/login、GET /api/auth/me有:mvn -DskipTests package BUILD SUCCESS;冒烟脚本 34 项断言通过
报名 4 接口GET/POST/PUT /api/registrations、DELETE /api/registrations/{forum_id}有:脚本覆盖 added_count/removed_count/deleted、409/404/422
统计 2 接口GET /api/stats/overview、GET /api/stats/forums/{id}有:脚本覆盖 403/404、名单含 name_masked 且不含 username
自测脚本backend/scripts/smoke-test.ps1有:本机 MySQL 8.0 连库执行,PASS=34 / FAIL=0

提交记录(feat/backen/b)

2f74816 fix(config): 时间输出补上 +08:00 偏移量,对齐契约 1.4
5237cab docs(backend): 同步 B 已完成事项与接口自测脚本
bf94a35 feat(stats): 大会总览与分论坛脱敏名单(契约 §5.1 / §5.2)
3d64a79 feat(user): 注册(注册即登录)、登录与当前用户(契约 §2)
85f2b6e feat(registration): 我的报名/报名/整体替换/取消报名(契约 §4)
5b8b4a0 feat(auth): JWT 签发校验与注解式鉴权(@PublicApi/@RequireRole)
48b7824 feat(common): 补齐 422 errors 结构、角色枚举与分页/论坛摘要 DTO
42339a9 docs(backend): 新增后端协作规范与统一日志配置

偏差说明

偏得最多的是「统计接口」阶段(计划 40 → 实际 15 分钟):overview 与 forums/{id} 的 SQL 和上一个模块的权限分支几乎同构,属于计划时重复计价,高估了 25 分钟。反向偏差是联调期返工(计划 0 → 实际 25 分钟)——时间格式是跨模块共享配置,自测清单里没覆盖,属于明显的计划漏洞。

组员 C(陈时文 102400313)的 PSP

分支与提交数、规模行数均按提交记录复核(feat/backen/c 分支);工时来自本人当天记录,等待他人的时间单列并标注。

基本信息

项内容
成员C / 陈时文 / 102400313
角色后端 · 论坛与议程 / 消息中心 / 秘书名单与导出(贡献 16)
统计区间2026-11-03 11:36 ~ 23:45
分支feat/backen/c(分支共 31 条提交,本人身份 13 条:原始 6 + 后续修复 / 文档 7)

时间记录

阶段预估时间实际时间备注
读契约、抽共享 DTO(ForumSummary / 分页)15 min9 min契约里 page_size 与 page 的命名先对齐,避免后面返工
论坛列表(type / keyword / 分页)30 min11 minAI 出首稿快,主要是把筛选条件按契约逐条核对
消息 scope 与 since 游标35 min7 min游标用 base64url 编 `published_at
秘书名单与 CSV 导出(UTF-8 BOM)20 min5 min加 BOM 后 Excel 才不乱码,改的是响应头
写工作日志10 min4 min记录当天已做/未做,供晚上合并时对账
自测清单(24 条接口)15 min6 min用 PowerShell 脚本串起来跑,逐条比对状态码
等 B 的认证与统一出参合入10 min12 min等待时间,不计入对外汇总;期间只能先按契约写 Mock
合并 B 分支并适配30 min55 min返工大头:Role 被改成常量、participantCount 从 Long 变 Integer、page_size / forum_id 绑定失效、422 缺 errors
补 C 侧单测40 min16 min分页参数、议程排序、/forums/mine 权限、since 非法值、越权 403
等 B 的时间格式修复(+08:00)10 min8 min等待时间,不计入对外汇总;期间先按旧格式自测,修复后复测一遍
补 GET /api/forums/mine40 min28 min契约漏项,评审前补的;要按 chair_user_id 只返回本人论坛
PSP 整理与数字核对75 min25 min提交数、规模行数对着 git show --stat 一条条核
合计330 min(5 h 30 min)180 min(3 h)12 段明细逐分钟累加为 186 min(含等待 20 min);对外汇总按不等待的口径取 180 min,差 6 min 为分段取整

等待他人的两段(12 + 8 = 20 min)按要求不计入实际工时汇总;表中保留是为了说明"为什么有些活被切成两半"。

规模估算

以 git show --stat 与分支最终文件为准:

模块文件数新增行说明
forum/8318列表、详情、议程、/forums/mine
message/8375global / forum 两种 scope、since 游标
participant/7283按论坛筛选、脱敏、CSV 导出
backend/src/test/java(C 侧)2199ForumServiceTest、MessageServiceTest
文档与自测脚本3194docs/selftest.ps1、接口说明与工作日志
合计281,369主代码 976 / 测试 199 / 文档 194

缺陷记录

缺陷簇处数发现阶段类型根因与修复
合并 B 分支后不兼容9 处合并当天集成Role 从枚举被改成常量、participantCount 由 Long 变 Integer,逐处改回契约口径
参数绑定失效4 处合并当天集成page_size、forum_id 的 @RequestParam 名字与契约不一致,改注解值并对契约
422 响应缺 errors11 处自测阶段契约校验失败只返回 message,按 B 的公共 422 结构补 data.errors[].field/reason
合计24 处(3 簇)——全部在提交前修完,未遗留到联调

返工 55 min(合并 B 分支并适配),占实际工时 180 min 的 **31%**;最大偏差是这段,预估 30 min、实际 55 min,超 **83%**。

产出对照

产出位置(文件 / 接口 / 页面)是否有截图或测试证据
论坛列表 / 详情 / 议程GET /api/forums、GET /api/forums/{id}、GET /api/forums/{id}/agenda有:接口实测 200,议程 4 条且时间带 +08:00

图26

我负责的论坛GET /api/forums/mine有:分论坛主席与参会者各测一次,均 200
消息发布与增量拉取POST /api/messages、GET /api/messages?since=有:global / forum 两种 scope 各 200;越权 403;非法游标 422
秘书名单与导出GET /api/participants、GET /api/participants/export有:名单 200(按论坛过滤 200);导出 200、321 字节、UTF-8 BOM
接口自测docs/selftest.ps124 条接口自测全部通过(文字结论,不上图)
单元测试ForumServiceTest、MessageServiceTest实测 45 条全绿(C 14 + F 31),mvn test BUILD SUCCESS 3:42

偏差说明

偏得最厉害的是"合并 B 分支并适配":预估 30 min,实际 55 min,超 83%,返工 55 min 占实际工时 31%。原因是两条后端分支并行开发同一批 DTO,字段类型和参数名在合并时才暴露冲突,靠"逐处对契约"收口;下一次会让两边先在契约上把共享类型的定义写死,再各自开工。

组员 D(甘羽潇 102400437)的 PSP

口径说明:本表的工时与规模依据 git 时间戳和文件行数推算,不是本人掐表;数据来自 git log origin/master --author=甘羽潇(8 条提交)与 git show --numstat。原 PSP 里的“预估 / 实际”是自报值,保留在备注列作对照;无法由提交时间戳推出的净工时写“未记录”。

基本信息

项内容
成员D / 甘羽潇 / 102400437
角色前端 · 参会者端(贡献 16)
统计区间2026-11-03 10:57 ~ 22:15(提交时间戳跨度 11 小时 18 分,不等于净工时)
提交数8 条(320ac85、e51198b、387b3d8、f381c1a、f34e9a9、98e4d4a、2b4f95a、9759c5a)

时间记录

阶段划分沿用 D 原 PSP 的四个阶段,提交按主题近似归组。"实际(推算)"用相邻提交的时间戳间隔,间隔里包含未提交的工作和中断,不是净工时;无法拆分的地方不硬填。

阶段对应提交(时间)预估(D 自报)实际(推算)备注
首页与论坛浏览320ac85(10:57)2 小时未记录(首条提交之前没有时间戳)新增 254 行(含 frontend/README.md 26 行);D 自报实际 2.5 小时
报名交互与登录权限e51198b(14:38)、2b4f95a(16:37)2 小时未记录(10:57→14:38 的 3 小时 41 分、16:05→16:37 的 32 分都含中断)两条提交新增 106 + 245 行;包含多选报名、整体替换、未登录拦截、已开始不可取消
日程与个人中心f381c1a(14:56)、f34e9a9(15:34)1.5 小时15 分 + 38 分(相邻提交间隔,含提交前的未提交工作)两条提交新增 374 + 419 行;按日期分组、已结束状态、冲突标记、个人中心
消息中心与智能推荐387b3d8(14:41)、98e4d4a(16:05)2.5 小时3 分 + 31 分(相邻提交间隔,含提交前的未提交工作)两条提交新增 107 + 67 行;api.js 请求层、since 游标、推荐入口与降级
PSP 与运行截图9759c5a(22:15)未记录未记录(与 16:37 的最后代码提交相隔 5 小时 38 分,含截图与写 PSP)64 行文档 + 7 张 PNG
合计10:57 ~ 22:158 小时(D 自报)未记录(提交跨度 11 小时 18 分,不能当净工时)D 自报实际 10.5 小时,无法用时间戳核实

代码规模

行数取 origin/master 上 frontend/ 的最终文件,不是 diff 新增行。

文件行数字节说明
frontend/app.js90841,121论坛列表/详情、报名、日程、消息、推荐入口
frontend/api.js2078,176请求层与 Mock 开关
frontend/index.html21312,125参会者端页面骨架
frontend/styles.css16124,452参会者端样式
frontend/README.md26—目录内说明
代码合计1,489—四个代码文件;含 README 为 1,515 行

8 条提交累计新增 1,546 行前端代码(另 README 26 行、PSP 64 行);新增行多于最终行数,是因为中途有替换和删除。

缺陷记录

只列能在代码 / 提交里核对的,不补写没发生过的 bug。

#现象根因状态
1参会者列表卡片显示 undefined卡片读 forum.intro,而 GET /api/forums 按契约返回 ForumSummary,不含 intro未修,记在博客 4.8 / 8
2已开始论坛的"取消报名"只在前端置灰后端 DELETE /api/registrations/{forumId} 的"已开始 → 409"时间校验尚未合入 master前端已挡,后端未修,记在 4.8
3myRegistrations 归并时可能抛 TypeError`item.forum?.id
4文件里保留两套旧渲染函数renderScheduleLegacy / renderMessagesLegacy 在改版后没有删除技术债,未清

产出对照

产出位置(文件 / 接口 / 页面)截图或检查证据
首页与论坛浏览frontend/index.html、frontend/app.js、frontend/styles.css;论坛列表与详情弹窗

图3

、

图4

、

图5

登录与报名权限frontend/app.js;登录弹窗、报名按钮状态

图6

;node --check app.js 通过
我的日程 / 个人中心frontend/app.js;日程分组、冲突标记、个人中心

图7

、

图8

消息中心frontend/app.js、frontend/api.js;消息列表与详情弹窗截图见 7.2;git diff --check 通过
LLM 推荐frontend/app.js、frontend/api.js;推荐弹窗与降级提示

图9

偏差说明

偏差最大的是"消息中心与智能推荐":D 自报预估 2.5 小时、实际 3 小时。从提交看,14:41 的 API 请求层和 16:05 的消息中心之间插进了 374 / 419 行两次日程改动,说明原计划里的"日程与个人中心"实际被拆成两轮返工,不是一次写完。

第二个偏差在收尾:最后一条代码提交是 16:37,PSP 与截图提交是 22:15,中间空了 5 小时 38 分。这段时间用于运行截图和写 PSP,但没有分批提交,时间戳无法再细分,因此表里写"未记录"。

合计口径上,D 自报 10.5 小时,与提交跨度 11 小时 18 分接近,但净工时无法核实;本表不把跨度当工时,也不把自报值当实测。

组员 E(梁鑫敏 102400438)的 PSP

数据口径:预估为事后按任务量估算,实际时间依据 git 提交时间戳与当天操作顺序推算。

基本信息

项内容
成员E / 梁鑫敏 / 102400438
角色前端 · 管理端 + 多语言 + 双前端合并(贡献 16)
统计区间2026-11-03 12:30 ~ 19:20(当天全部完成,中间晚餐与等后端约 60 min 中断)

时间记录

阶段预估时间实际时间备注
管理端骨架与五语言包:登录页、路由、三种角色守卫、admin/ 目录整体落地(index.html、app.js、api.js、mock-data.js、styles.css + 5 个语言包)120 min75 min提交 b3125cd,2,852 行新增,10 个文件。骨架与样式模板复用了参会者端的视觉基座,比预估快;耗时主要在对齐 docs/api.md 的字段映射
Mock 业务数据 i18n 翻译链路修复30 min65 min提交 602983c。切语言后发现论坛名、消息标题等业务内容仍是中文,新增 mockI18n() 让 Mock 数据也走语言包,缺 key 回退 zh-CN 并打日志。本阶段偏差最大
hero 视觉对齐参会者端 + 参会者账号跳转45 min55 min提交 3b6cc8b。把管理端 hero 卡片、配色、字号对齐参会者端;同时加"登录参会者端"跳转入口
参会者入口位置调整与移除15 min20 min三次提交 64b9127 / 446413e / 3d165da。入口从登录页右上角 → 白色面板右上角 → 最终删掉:登录面板本身已区分角色,多一个入口反而混淆
五语言走查与收尾30 min36 min逐个视图切五种语言走查,确认无缺 key 告警、CSV 导出文件名正常、未登录时角色守卫能拦住
按契约冻结 v1 写真实接口请求层80 min63 min17:40–18:43,提交 d2daf48,8 个文件 140+/49−。request() 统一 /api 前缀、Bearer 头、解析 {code,message,data} 信封、401 自动清 token;export 按 text/csv 单独处理;接口状态页新增"契约章节"列,并记录契约缺口 GET /api/forums/mine
双前端分支合并:feature/e-admin-i18n + feature/d-participant-frontend 合二为一30 min37 min18:43–19:20。两分支各自改过 README.md 与 .gitignore,先统一 README 为系统总述;合并后单分支同时包含 admin/(3,182 行)与 frontend/(353 行),共享同一份 docs/api.md 契约与统一响应处理,避免两套前端各自维护接口层
合计320 min(5 h 20 min)370 min(6 h 10 min)实际比预估多 50 min(+15.6%):i18n 链路超 35 min、分支合并是计划外新增 37 min,被骨架阶段提前完成的 45 min 抵掉了一部分

产出对照

产出位置(文件 / 接口 / 页面)是否有截图或测试证据
管理端单页应用(登录 + 主框架)admin/index.html、admin/app.js、admin/styles.css无图;统一登录入口在根 index.html + login.js,代码可复核
五种语言界面(简中、繁中、英、德、日)admin/i18n/zh-CN.js、zh-TW.js、en.js、de.js、ja.js无整组切换图;语言包在 admin/i18n/,界面证据用

图34

、notice-ja.png
真实接口请求层(契约冻结 v1)admin/api.js:request() 统一信封解析、401 清 token、CSV 单独处理、buildQuery 显式传参无图;接口状态以博客 4.7 / 6.4 的实测结论为准
Mock 业务数据(对齐 docs/api.md)admin/mock-data.js有:

图11

三种角色视图:会议主席 / 分论坛主席 / 秘书app.js 中 renderView 路由(dashboard / board / participants)有:

图15

(分论坛主席看板,本地真后端 + 种子数据)、

图32

参会者名单 CSV 导出admin/app.js 中 downloadFile + csvCell无独立截图;导出接口实测 321 字节、UTF-8 BOM,见博客 4.4 / 7.2
通知消息与接口状态视图app.js 中 notice / api 视图有:

图33

契约缺口记录(后端未定义)GET /api/forums/mine 已在 master 实现;admin/api.js 对数组返回值做归一化,避免拼出 NaN无图;以代码注释与 6.4 的复现结论为准
合并后的统一前端交付单分支同时包含 admin/ 与 frontend/,README 统一为系统总述无截图;交付形态以当前分支目录为准

偏差说明

估时偏得最厉害的是 Mock 数据 i18n 翻译链路(预估 30 min / 实际 65 min)。一开始只把静态文案做了 data-i18n,切语言时发现论坛名、消息标题这些来自 Mock 的动态内容完全没走翻译,等于"五语言"只覆盖了一半界面。返工后加了 mockI18n(key, fallback),动态内容按当前语言回查表、缺 key 回退 zh-CN。教训是:多语言不能只做静态文案,凡是来自数据层的文本都要预留翻译 key。

第二处偏差是双前端分支合并,这是计划外新增的 37 min。两分支各自改过 README.md 和 .gitignore,合并时还发现两套前端各写了一份接口模型——参会者端直连、管理端有完整信封解析,最终以管理端的 api.js 为准统一。如果一开始就按"单分支交付、共享接口层"来拆任务,这段返工可以省掉。

参会者入口的位置来回改了三次才决定删除,本质是一开始没和 D 端统一"登录入口"策略——两个端各走各的登录页即可,不需要互相跳转。先对齐策略能省约 10 min。

组员 F(兰秀楠 102400316)的 PSP

基本信息

项内容
成员F / 兰秀楠 / 102400316
角色LLM / 算法 / 测试(贡献 17)
统计区间2026-11-03 ~ 2026-11-04

时间记录

阶段起止时间预估时间实际时间备注
读契约与接口对齐11-03 11:50 ~ 12:200.5 h0.5 h冻结契约把原设计的 POST /schedule/conflicts 改成 GET /schedule,按契约实现避免返工
LLM 推荐(提示词 / 解析 / 降级)11-03 12:20 ~ 12:551.5 h0.9 h有 Python 原型可迁移,快于预估;JSON 解析与四道校验占主要时间
冲突算法与日程接口11-03 12:55 ~ 13:201.0 h0.4 h半开区间规则一次写对,边界用例留到测试阶段补
单元测试11-03 13:20 ~ 13:451.0 h0.4 h首版 30 条;一条断言写错(“大模型”不是“大语言模型”的连续子串),修正后通过
集成联调与修复11-03 21:40 ~ 23:501.0 h2.2 h环境无 JDK/Maven/MySQL,工具链 0.5 h;C 分支编译失败修复 0.2 h;B/C 公共类冲突对齐 0.5 h;运行期发现 aforum_id 并修复 0.5 h;截图 0.5 h
素材整理与 PSP11-04 09:00 ~ 09:300.5 h0.5 h补单条推荐、单测输出、git log 截图
合计—5.5 h4.9 h—

代码规模

口径:git show --stat 统计的个人提交新增行,不含合并提交、不重复计算 cherry-pick。

类别新增行数说明
主代码1,075LLM 推荐 710 行 + 日程冲突 356 行 + 契约字段修复 9 行
单元测试6015 个测试类,45 条用例(含 30 条首版 + 1 条 JSON 字段回归)
协作修复4补 C 分支 ForumRegistrationRow.userId 与 SQL 查询列
合计1,680提交 a51ea17 / 15dc948 / 7725942 / 417ae75 / db82f44

缺陷记录

编号缺陷发现方式根因处理耗时
F-BUG-1ForumRegistrationRow 缺 getUserId(),整个后端无法编译mvn compile 报“找不到符号”C 分支组装名单时按 userId 分组,但实体和 SQL 都没带该列实体补 userId 字段,SQL 补 r.user_id AS user_id0.2 h
F-BUG-2/api/schedule 冲突字段输出 aforum_id,契约要求 a_forum_id真实接口调用后逐字段核对 docs/api.mdaForumId 前两个字母大写,Jackson snake_case 不在 A/F 间插下划线四个字段加 @JsonProperty,并补序列化回归测试0.5 h
F-BUG-3单测断言“包含大模型”失败mvn test 失败“大语言模型”中不连续包含“大模型”,是测试用例写错而非实现错修正断言为完整领域词0.1 h

缺陷数:产品代码 2 个,测试用例 1 个,合计 3 个,均已修复并回归。

产出对照

产出位置(文件 / 接口 / 页面)是否有截图或测试证据
LLM 智能推荐接口POST /api/llm/recommend;backend/src/main/java/com/cncc/meeting/llm/有:

图17

图18

个人日程与冲突检测GET /api/schedule;backend/src/main/java/com/cncc/meeting/schedule/有:

图30

单元测试 45 条backend/src/test/java/com/cncc/meeting/{llm,schedule}/有:

图16

冲突字段契约修复schedule/dto/ConflictItem.java + ConflictItemJsonTest有:7.1 F 的提交记录

图24

偏差说明

偏差最大的是“集成联调”:预估 1.0 h,实际 2.2 h。主要原因是本机没有 JDK、Maven、MySQL,工具链和本地库各占了约 0.5 h;另外 C 分支基线本身编译失败,需要先补字段才能继续,这属于跨人协作问题,无法在编码阶段预估。

附录

  • 接口契约:docs/api.md
  • 建表脚本:db/schema.sql
  • 各人 PSP:见《附:六名组员的 PSP》,A–F 六份表格已在上文内联

说明:文中 CodeArts 文件链接按仓库 master 分支的文件路径拼接,链接格式以仓库实际支持为准。

...全文
YOLO26算法港口码头防撞设施目标检测+训练好的模型+7194张数据集+pyqt可视化界面.zip 附训练好的 YOLO 权重与 PyQt 可视化检测界面,页面底部含可视化效果预览,毕设/课设开箱即用。 【数据集概况】 · 检测类别(中文):[松动的板(Loose plate), 缺失的板(Missing plate), 橡胶裂纹(Rubber crack)] · 训练集:6295 张 · 验证集:600 张 · 测试集:299 张 · 总计:7194 张 该数据集聚焦于港口码头区域的防撞设施,旨在通过精确识别松动的板、缺失的板及橡胶裂纹等关键缺陷,为设施的日常巡检与维护提供智能化支持。其价值在于提升港口运营的安全性与效率,确保大型船舶靠泊时设施结构的完整性。 该数据集的训练集、验证集与测试集划分比例科学合理,分别为6295张、600张和299张,总计7194张。这种分布能够有效保障模型在训练阶段获得充分学习,在验证阶段进行准确评估,并在测试阶段实现... 【训练曲线与评估图】 【模型训练配置】 参数 | 值 模型 | yolo26n 训练轮数 | 100 epochs 输入尺寸 | 640x640 批次大小 | 24 优化器 | auto 初始学习率 | 0.01 训练设备 【关键指标汇总】 训练了 72 个 epoch,最终轮指标: 指标 | 数值 mAP50 | **0.5451** mAP50-95 | 0.2472 Precision | 0.5841 Recall | 0.5221 train/box_loss | 0.7732 train/cls_loss | 0.5844 val/box_loss | 1.8959 val/cls_loss | 1.4294 【训练过程分析】 训练开始时,模型在前 20 轮迅速下降 box_loss 和 cls_loss,这表明算法能够快速适应数据集。b... 使用场景:毕业设计、课程设计、深度学习入门、工业场景落地验证。 立即下载

相关推荐

飞轮储能系统机侧PQ控制+网侧变流器恒压双闭环控制【附参考文献】

内容概要:本文针对含分布式电源的配电网在遭受自然灾害或外力破坏导致多条线路同时故障的复杂场景,提出并实现了一种将网络重构、孤岛划分与检修进度统筹考虑的多时段故障恢复优化模型。该模型以最小化系统网损和甩负荷惩罚为综合目标,采用混合整数二阶锥规划(MISOCP)方法,通过引入大常数松弛处理支路的投切状态,利用二阶锥松弛技术将非线性的潮流方程转化为凸优化问题,并创新性地应用虚拟流方法确保各供电孤岛内部的连通性。模型在IEEE 33节点系统上进行了仿真验证,能够逐时段生成最优的开关操作序列、负荷恢复策略以及分布式电源协同供电方案,有效提升了配电网在故障期间的韧性与供电恢复能力。文章还深入评述了模型的实现细节,并指出了在负荷分级、无功处理、辐射状约束精确性等方面的潜在改进方向。; 适合人群:电力系统自动化、智能电网、配电网规划与运行等相关领域的研究生、科研人员及从事配电网故障恢复与韧性提升工作的工程技术人员。; 使用场景及目标:①应对由极端天气或外力破坏引发的配电网多点并发故障;②在故障后通过协同优化网络重构与动态孤岛划分,最大化恢复关键负荷供电;③将故障线路的修复时序纳入优化框架,实现检修资源调度与供电恢复路径的联合决策,以提升系统整体恢复效率与韧性。; 阅读建议:本文理论深度较高,综合运用了电力系统分析、数学优化建模与凸松弛技术,建议读者具备扎实的电力系统潮流计算、优化理论和MATLAB/YALMIP建模基础。阅读时应重点关注虚拟流、二阶锥松弛等核心技术的设计原理与实现逻辑,结合文中详细的公式推导与算例结果,深入理解模型的构建过程与求解机制,为进一步研究考虑储能、柔性负荷及多能协同的主动配电网高级恢复策略奠定基础。

wytm

风光储协同并网发电系统仿真(Simulink仿真实现)

内容概要:本文围绕“风光储协同并网发电系统仿真(Simulink仿真实现)”展开,重点介绍了一套基于Simulink/MATLAB平台的电力系统仿真体系,涵盖风力发电、光伏发电、储能系统(如电池、飞轮、抽水蓄能)以及制氢系统(PEM电解槽)等多种能源形式的协同建模与控制策略。系统实现了并网与孤岛模式下的协调控制,采用了虚拟同步发电机(VSG)、下垂控制、模型预测控制(MPC)、双闭环控制等多种先进控制算法,并结合多时段故障恢复、网络重构与孤岛划分的优化模型,提升配电网在故障情况下的韧性与恢复能力。文中详细阐述了基于二阶锥松弛与虚拟流理论的混合整数二阶锥规划(MISOCP)建模方法,用于求解含分布式电源的配电网多时段故障恢复问题,确保供电连通性与辐射状结构。整体资源体系丰富,包含仿真模型、参考文献、建模报告及代码实现,适用于科研与教学实践。; 适合人群:具备电力系统、自动化、电气工程等相关背景,熟悉MATLAB/Simulink仿真环境,从事新能源、微电网、智能配电系统研究的研究生、科研人员及工程技术人员。; 使用场景及目标:① 构建风光储协同发电系统的动态仿真模型,研究其并网运行特性与稳定性;② 设计与验证微电网在故障情况下的孤岛划分、网络重构与多源协同恢复控制策略;③ 学习并应用二阶锥松弛、虚拟流、混合整数规划等高级建模技术解决电力系统优化问题。; 阅读建议:建议结合提供的参考文献与仿真模型逐步实践,重点关注模型的约束构建、目标函数设计与求解器接口实现;在复现过程中注意变量定义、参数设置与物理意义的一致性,建议拓展加入负荷分级、储能动态调度与通信延迟等实际因素以提升模型实用性。

wytm

基于人工神经网络的类噪声环境声音声学识别(Matlab代码实现)

内容概要:本文系统阐述了基于人工神经网络的类噪声环境声音声学识别方法,并提供了完整的Matlab代码实现。研究聚焦于构建和训练人工神经网络模型,以实现对在复杂类噪声环境中采集的声音信号进行高效识别与分类。内容涵盖数据预处理、特征提取、网络模型设计、训练优化等关键技术环节,重点探讨了如何提升模型在低信噪比条件下的鲁棒性与识别精度。该方法融合了现代信号处理技术与深度学习架构,为复杂声学环境下的音频分析提供了有效的解决方案。; 适合人群:具备一定信号处理和机器学习基础,从事音频识别、环境监测、智能安防或相关领域研究的研发人员及研究生。; 使用场景及目标:①应用于智能监控、工业设备故障诊断、城市环境声音感知等需在强噪声背景下识别特定声源的场景;②为科研人员提供一个可复现、可扩展的Matlab实现框架,用于进一步改进和优化声学识别算法。; 阅读建议:建议读者结合所提供的Matlab代码逐段研读,深入理解算法实现细节,重点关注特征工程与网络结构设计部分,并尝试在不同类型的噪声条件下调试模型,以全面评估其泛化能力与实际应用潜力。

大龙不吃小鱼干

政府科技管理者在推动科技成果转化时,如何精准识别高价值科技成果?.docx

政府科技管理者在推动科技成果转化时,如何精准识别高价值科技成果?

BIZKEEN

产业园区运营负责人如何实现科技成果与产业资源的精准匹配和高效配置?.docx

产业园区运营负责人如何实现科技成果与产业资源的精准匹配和高效配置?

BIZKEEN
评论
收藏 已收藏
分享
返回顶部