zq践地组——CNCC 2026 会议系统 · 项目随笔
CNCC 2026 会议系统 · 项目随笔
团队与成员
| 成员 | 姓名 | 学号 | 主要分工 |
|---|---|---|---|
| A | 林嘉祚 | 102400118 | 组长 · 架构 / 数据库 / 部署 / 文档整理 |
| B | 张祺 | 102400432 | 后端 · 身份认证与报名 |
| C | 陈时文 | 102400313 | 后端 · 论坛 / 议程 / 消息 |
| D | 甘羽潇 | 102400437 | 参会者端前端 |
| E | 梁鑫敏 | 102400438 | 管理端前端 / 多语言 |
| F | 兰秀楠 | 102400316 | LLM / 算法 / 测试 |
本文为课程项目复盘,记录实际做了什么、遇到过哪些问题、如何解决。接口细节以 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 · 兰秀楠 | 102400316 | LLM / 算法 / 测试 | 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 白名单,匿名能看论坛列表和详情,但看不了"我负责的论坛"。

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

图注: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);运行截图

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 关系

图注: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、role | role 用 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_at | UNIQUE(user_id, forum_id) 是防重复报名的最后一道防线;人数用 COUNT(*)、大会参与人数用 COUNT(DISTINCT user_id),同一个表两种口径 |
| messages | 大会 / 分论坛通知 | scope、forum_id、publisher_id、published_at | scope=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、人数不变;仪表盘数字与接口一致
。
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/participants200、GET /api/participants/export200(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 次重试)并让 appdepends_on: service_healthy后才稳定。公网机器上再启用nginx的publicprofile,把静态页面挂进容器。 - 线上验收:在
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′ 部署评分项,重点写部署链路是否闭环、配置是否分离。

图注:端口、容器名、数据卷与环境变量来源,源文件是 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 图) |
| F | LLM 不可用时页面仍能出结果且提示明确 | 通过 | 接口 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 test45 条全绿(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 冻结、
与
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有没有返回。

图注: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。 - 学到什么:契约里的字段名要在注解层写死,不能靠变量名"刚好对上";这类参数绑定错了不报错,只会静默用默认值,自测必须覆盖"改了参数、响应真的变"。

图注:?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 |
| 3 | CSV 导出在 Excel 里乱码 | 少了 UTF-8 BOM,Excel 默认按 GBK 解析 | C |
| 4 | 半开区间冲突判定 | [start, end) 与闭区间差一个端点,"刚好接上"算不算冲突 | F |
| 5 | LLM 降级 | 超时 / 非 JSON / 编造 forum_id,重试一次后降级为关键词匹配 | F |
| 6 | Docker 首次构建失败 | 多阶段构建拉不到基础镜像、Maven 依赖没缓存、健康检查 unhealthy | A |
| 7 | 重复报名 409 与事务原子性 | users + registrations 必须一起成功,唯一索引撞了要回滚 | B |
| 8 | 统计口径 | 大会人数是去重用户数而不是报名记录数 | B |
| 9 | 消息轮询游标 | since 用时间戳还是 id,时间戳精度不够会重复或漏消息 | C |
| 10 | 权限 403 的边界 | 分论坛主席发非本人论坛的消息、参会者改别人的报名 | B |
| 11 | 多语言缺 key 回退 | 切到德语时部分文案还是中文,回退 zh-CN 并打日志 | E |
| 12 | 前端重复提交 | 报名按钮连点产生两条记录 / 覆盖别人的选择 | D |
| 13 | Nginx 反代路径 | /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 的"人工改写 / 自设计部分"具体是:
RegistrationMapper的<script>动态 SQL 拼接——MyBatis 注解里写IN条件的动态片段,AI 给的版本跑不通,按表结构自己拼的foreach。StatsMapper三条统计 SQL——总数 / 按论坛 / 脱敏名单的口径对着契约 §5 定,COUNT(DISTINCT user_id)与COUNT(*)不混用。- 权限分支判断——
@PublicApi/@RequireRole的放行顺序、forum_chair只认chair_user_id、为空直接 403 不放宽。 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% |
| 人工改写 / 自设计部分 | 报名按钮按 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′ 里最容易拿分也最容易露馅的地方。写"我看了代码觉得没问题"基本等于没写。
我们的办法是四层,缺一层就会漏:
- 对着契约逐字段核对:接口改完把响应字段和 docs/api.md 的对象定义比对一遍。
aforum_id(5.6)、时间缺+08:00(5.10)都是在这一层揪出来的。 - 单元测试:全项目 45 条,
mvn -B test一次跑完,实测见 6.6。 - 打异常分支:把 200 / 401 / 403 / 404 / 409 / 422 各打一遍——匿名
/api/forums应 200、匿名/api/forums/mine应 401、参会者看/api/participants应 403、报名不存在的论坛应 404、重复报名应 409、page_size=200应 422。本次按契约实测 41 个请求,状态码全部对得上。 - 真页面点一遍:三类角色登录后逐个视图走查。页面表现和接口返回对不上的地方,基本都藏在这一层。
一个具体例子: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 先判断返回值是不是数组,取第一项再交给看板;改完用本地真实后端 + 种子数据重新打开看板,截图见

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 单元测试与证据
- 测试范围:
| 测试类 | 条数 | 覆盖点 |
|---|---|---|
ConflictDetectorTest | 8 | 端点相接、部分重叠、完全包含、零时长、跨天、多对组合、顺序无关、脏数据 |
RecommendationParserTest | 8 | 代码块 JSON、夹带文字、包装对象、非法 JSON、编造 id、重复 id、空理由、无关推荐、limit、字符串 id |
RecommendServiceTest | 5 | LLM 成功、坏 JSON 重试一次、超时降级、两次失败降级、报名人数回填 |
KeywordMatcherTest | 4 | 领域词抽取、视觉/多模态命中、无关查询返回空、limit |
ScheduleServiceTest | 5 | 冲突对与 conflicts_with 双向一致、相邻端点不冲突、空日程、未登录 401、人数回填 |
ConflictItemJsonTest | 1 | JSON 字段 a_forum_id / b_forum_id 不被 Jackson 改写 |
ForumServiceTest(C) | 5 | 分页参数、详情与议程排序、/forums/mine 权限、不存在论坛 404 |
MessageServiceTest(C) | 9 | global/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"。
- 测试报告截图:

- 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。本环境没有真实密钥时只保留降级实测图
。2026-11-04 用真实密钥在本地按同一份
origin/master代码起 compose 补测了一次正常路径:POST /api/llm/recommend返回degraded=false、3 条全部命中库内forum_id(2/4/9),耗时 1.48s,响应体见
。密钥只写本地
.env(已被.gitignore覆盖),测完即删,未入库、未进截图、未进日志。线上部署时没有配置密钥,所以线上仍是降级路径。
图注:

图注: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 次) |
| 契约与建表 / Spring Boot 骨架与统一响应 / 种子数据 / README 与架构图 / 博客与 PSP 文档,明细见下表 |
| B(张祺 102400432) | 8 次(8 条全在 master,作者 ZOEKOKF;官方要求每人 ≥3 次) |
| 后端协作规范与日志 / 422 errors 结构与公共 DTO / JWT 与注解式鉴权 / 报名四接口 / 注册即登录与当前用户 / 统计总览与脱敏名单 / 自测脚本与文档 / 时间偏移修复,明细见下表 |
| C(陈时文 102400313) | 13 次(按提交内容归属:本人身份 6 条 + local-verify 7 条;按 Git 作者名直接统计为 16 条,其中 3 条无法确定归属;官方要求每人 ≥3 次) |
| 论坛列表与详情 / 议程 / 消息 scope 与 since 游标 / 参会名单与 CSV / GET /api/forums/mine(master 内为 9544757)/ 单测与自测脚本;24 条接口自测全部通过(按文字结论记录,不上图) |
| D(甘羽潇 102400437) | 8 次(作者名 甘羽潇;官方要求每人 ≥3 次) |
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 次) |
| 管理端布局与路由 / 仪表盘与看板 / 参会者名单与 CSV / 通知发布 / 五语言与动态 i18n / 统一登录入口 / 契约请求层 / 与参会者端合并 |
| F(兰秀楠 102400316) | 6 次(5 次代码/测试 + 1 次 PSP 文档;官方要求每人 ≥3 次) |
| 提示词与推荐接口 / 四道校验与关键词降级 / 半开区间冲突检测与日程接口 / 单元测试 / 冲突字段契约修复 / PSP |
统计口径:本表按提交内容归属统计,六人均满足官方"每人 ≥3 次"。部分提交使用了本机 Git 账号名:B 显示为
ZOEKOKF,C 有 7 条显示为local-verify(按改动内容归属到本人);另有 6 条hid_*提交无法归属到具体成员,未计入任何人的次数。合并阶段使用了集成机账号local-verify,因此按 Git 作者名直接统计会得到不同数字(C 为 16 条,其中 3 条无法确定归属),本表保留按内容归属的团队口径。
A 的 5 条主提交(作者 102400118林嘉祚,合计 11 次)
| # | commit | 信息 |
|---|---|---|
| 1 | 9c91e72 | docs: 初版接口契约与建表脚本 |
| 2 | 2d494f9 | docs: 契约冻结 v1 |
| 3 | df4be00 | feat: Spring Boot 骨架、统一响应与本地容器化 |
| 4 | 3b51361 | feat(db): 种子数据 |
| 5 | 365c20b | docs: README 与 E-R / 部署架构图 |

图注:git log --author="102400118林嘉祚" 的完整输出,11 条提交,时间从 10:37 到 23:55;上表 5 条是主提交,其余 6 条是 E-R/部署图、PSP 模板与博客文档。
B 的 8 条提交(作者 ZOEKOKF)
| # | commit | 信息 |
|---|---|---|
| 1 | 42339a9 | docs(backend): 新增后端协作规范与统一日志配置 |
| 2 | 48b7824 | feat(common): 补齐 422 errors 结构、角色枚举与分页/论坛摘要 DTO |
| 3 | 5b8b4a0 | feat(auth): JWT 签发校验与注解式鉴权(@PublicApi/@RequireRole) |
| 4 | 85f2b6e | feat(registration): 我的报名 / 报名 / 整体替换 / 取消报名(契约 §4) |
| 5 | 3d64a79 | feat(user): 注册(注册即登录)、登录与当前用户(契约 §2) |
| 6 | bf94a35 | feat(stats): 大会总览与分论坛脱敏名单(契约 §5.1 / §5.2) |
| 7 | 5237cab | docs(backend): 同步 B 已完成事项与接口自测脚本 |
| 8 | 2f74816 | fix(config): 时间输出补上 +08:00 偏移量,对齐契约 1.4 |

图注:git log origin/master --author=ZOEKOKF 输出 8 条,作者名是 B 的账号名 ZOEKOKF,时间全部是 11 月 3 日。
D 的 8 条提交(作者 甘羽潇,按提交时间排序)
| # | commit | 时间(Asia/Shanghai) | 信息 | 新增行(含文档/图片) |
|---|---|---|---|---|
| 1 | 320ac85 | 11-03 10:57 | feat: 完成参会者首页和论坛浏览页面 | 254 |
| 2 | e51198b | 11-03 14:38 | feat: 完善参会者端报名交互与契约字段 | 106 |
| 3 | 387b3d8 | 11-03 14:41 | feat: 新增 API 请求层 | 107 |
| 4 | f381c1a | 11-03 14:56 | feat: 完善参会者日程与论坛详情交互 | 374 |
| 5 | f34e9a9 | 11-03 15:34 | feat: 完善参会者日程功能 | 419 |
| 6 | 98e4d4a | 11-03 16:05 | feat: 完善消息中心 | 67 |
| 7 | 2b4f95a | 11-03 16:37 | fix: 完善参会者登录与报名权限 | 245 |
| 8 | 9759c5a | 11-03 22:15 | docs: 补充 PSP 表格和运行截图 | 64 行文档 + 7 张 PNG |
D 的提交日志截图见
,命令为
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 的提交记录

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

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

图注:git log origin/master --author=梁鑫敏 输出 16 条,含 3 次 merge(其中两次是把自己的 feature/integration-login 与远端同步)、1 次 revert 和 1 次 PSP 文档提交;作者名 102400438 梁鑫敏。
7.2 运行截图
| 场景 | 截图位置 | 说明 |
|---|---|---|
| 首页论坛列表 |
| 接口返回形态:page_size=5、total=31、total_pages=7。页面卡片读 forum.intro,而列表接口按契约只返回 ForumSummary,实测页面会显示 undefined,所以这里放接口返回图 |
| 论坛详情 + 议程 |
| 详情弹窗渲染简介、时间、地点、主席与报名态;弹窗没有渲染议程,议程用 GET /api/forums/1/agenda 的实测返回佐证(4 条,时间带 +08:00) |
| 详情页(已报名态) |
| 同一详情页在已报名后的按钮文案与状态 |
| 报名接口实测(POST / GET / 重复提交) |
| demo_participant 登录后 POST /api/registrations({"forum_id":2})200、GET /api/registrations 200、重复提交 409,响应原样保留中文与 +08:00 |
| 报名页(多选 + 冲突提示) |
| 已选 3 场;已开始的论坛标"论坛已结束,无法取消报名" |
| 个人中心 / 个人日程 |
| 4 场,冲突标记来自 /api/schedule 的 conflicts_with |
| 消息中心(大会 + 分论坛分区) |
| 2 条,global 与 forum 两种 scope |
| 会议主席仪表盘 |
| 参会者 2 / 论坛 31 / 报名 6,数字来自 /api/stats/overview |
| 分论坛主席看板 |
| 本地 docker compose(MySQL + Spring Boot)+ db/seed.sql,以 demo_forum_chair 登录实测;显示 chair_user_id=3 负责的论坛 2「大模型与智能体」,不再出现 NaN。图为真实后端数据,不是 Mock |
| 秘书参会者表格 + CSV 导出 |
| 脱敏姓名、报名数;导出接口实测 321 字节、UTF-8 BOM |
| 管理端通知发布 |
| 大会 / 分论坛两级 scope |
| LLM 推荐(正常路径 / 降级提示) |
| 正常路径:本地配置真实模型密钥实测,degraded=false、3 条命中库内 forum_id、1.48s,图为接口响应体(不含请求头、不含密钥,密钥不入库);降级路径:不配密钥时 degraded=true,页面顶部给出关键词匹配理由。线上部署未配密钥,线上仍是降级 |
| 五语言切换 |
| 英文 / 日文界面;静态文案与 Mock 词条已翻译,角色名等数据字段仍是中文(3 节 E 段写了这个取舍) |
| Swagger / OpenAPI 页面 |
| 真实控制器已出现在分组里,不再是只有 /api/ping |
| 单元测试报告 |
| 45 条全绿,BUILD SUCCESS |
| Docker 容器运行状态 |
| 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′)
- 实现了什么:管理端与参会者端支持中 / 英 / 日等五种语言切换,静态文案走词典表,切换后立即生效,不需要刷新;论坛名、角色名等数据字段保持中文。
- 怎么验证:
、
是同一页面在英文、日文下的实测截图;3 节 E 段记录了"数据字段不翻译"的取舍,5.11 记录了切换后部分文案仍为中文时回退
zh-CN并打日志的处理。
9.2 日程冲突检测(10′)
- 实现了什么:
GET /api/schedule用半开区间[start, end)判断任意两场已报名论坛是否重叠,返回conflicts_with;个人日程页对冲突项打标提示。 - 怎么验证:
是 4 场报名、含冲突项的实测页面;6.6 的单元测试中有 14 条覆盖冲突判定(刚好相接不算冲突、包含关系、多场交叉),
mvn test全绿见
;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 min | 0 | 0 | 目录非空,clone 可能失败,预留处理时间 |
| 2 | 需求与接口契约 | 45 min | 650 | 1 | 八组接口、统一响应、错误码表 |
| 3 | 契约冻结评审 | 20 min | 0 | 0 | 按评审意见改契约,不新增功能 |
| 4 | 数据库设计 | 20 min | 190 | 1 | 四张表、唯一索引、外键与 CHECK |
| 5 | 骨架与容器化 | 45 min | 900 | 1 | Spring Boot 骨架 + Dockerfile/compose/nginx |
| 6 | 种子数据 | 30 min | 245 | 0 | 1 场大会 + 30 场专题 + 4 个账号 |
| 7 | 文档与图 | 25 min | 575 | 0 | README、E-R 图、部署图 |
| 合计 | — | 200 min(3 h 20 min) | 2,560 | 3 | 不超过 8 小时,全部压在一天内 |
时间记录
| 任务 | 实际起止 | 中断时间 | 实际工时 | 依据 / 备注 |
|---|---|---|---|---|
| 环境与仓库接管 | 09:10–09:20 | 未记录 | 10 min | clone 报目录非空,改 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.md | 1 | 664 | 669 | 9c91e72 新增 664;2d494f9 又改 73 行 |
| db/schema.sql | 1 | 193 | 195 | df4be00 里随外键修复改过 4 行 |
| db/seed.sql | 1 | 245 | 245 | 3b51361 |
backend/ | 15 | 601 | 601 | 11 个 Java 文件 + pom.xml、Dockerfile、.dockerignore、application.yml |
README.md + docs/diagrams/ | 4 | 575 | 576 | 365c20b 新增 575,README 原有 2 行被替换 |
| 根目录部署配置 | 4 | 244 | 244 | .env.example 19 + docker-compose.yml 73 + deploy.sh 93 + nginx/nginx.conf 59 |
| 合计 | 26 | 2,522 | 2,530 | 当前总行数不含已被覆盖的旧 README 行 |
口径说明:
backend/是git ls-tree HEAD backend的 15 个文件;df4be00的 21 个文件里另有 6 个在根目录。之前口头说的"19 个文件"对不上 git,以仓库为准。
缺陷记录
| # | 缺陷 | 发现阶段 | 类型 | 修复动作 | 耗时估算 | 根因 |
|---|---|---|---|---|---|---|
| 1 | git clone <url> . 报目标目录非空 | 环境准备(09:10 前后) | 环境 | 改用 git init + remote add + fetch + checkout master,原分工文档保留为未跟踪文件 | 10 min | clone 要求目标目录为空,. 里已有分工文档 |
| 2 | ERROR 3823:messages 建表中断,库里只有 3 张表 | 容器化建库验证(10:00–10:40 内) | 设计 | 最小复现后确认 CHECK 与 ON UPDATE CASCADE 冲突;外键改 ON UPDATE RESTRICT,保留 ON DELETE CASCADE 与 CHECK;down -v 重建 | 20 min | MySQL 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 min | mysql 客户端连接字符集不是 utf8mb4,属于显示层问题,不是数据损坏 |
缺陷修复耗时已经包含在对应任务的实际工时里,不重复加到 130 min 的合计中。
PSP1 / PSP2 汇总
工时
| 项 | 计划 | 实际 | 偏差 | 原因 |
|---|---|---|---|---|
| 环境与仓库接管 | 15 min | 10 min | −5 | 操作步骤少,直接换落地方式 |
| 需求与契约 + 数据库设计 | 65 min | 20 min | −45 | 契约和表结构用 AI 出初稿,人工主要做字段核对和约束取舍;原计划按从零手写估的 |
| 契约冻结评审 | 20 min | 20 min | 0 | 与计划一致,改动量 39+/34− |
| 骨架与容器化 | 45 min | 40 min | −5 | 其中约 30 min 花在 3823 和健康检查上,配置文件本身生成得快 |
| 种子数据 | 30 min | 20 min | −10 | 模板化数据,主要是跑两遍验证幂等 |
| 文档与图 | 25 min | 20 min | −5 | 图用 Markdown/HTML 模板改,没有从零画 |
| 合计 | 200 min | 130 min | −70 min(−35.0%) | 计划偏保守,AI 初稿把契约和骨架的耗时压下来了 |
规模与缺陷
| 指标 | 计划 | 实际 | 偏差 |
|---|---|---|---|
| 交付规模(LOC) | 2,560 | 2,530 | −30(−1.2%) |
| 缺陷数 | 3 | 4 | +1 |
| 缺陷密度 | 1.17 个/KLOC | 1.58 个/KLOC(4 ÷ 2.53) | +0.41 |
| 缺陷修复耗时 | — | 约 50 min,占实际工时 38.5% | — |
缺陷密度按当前总行数 2,530 算;4 条缺陷全部在当天发现并修完,没有遗留到联调阶段。
总结
这份 PSP 看下来,最不舒服的一点是:计划 3 小时 20 分,实际 2 小时出头,但省下来的时间不是因为我手快,而是因为契约和骨架大量用了 AI 初稿。真正花时间的是两个"没想到"——ERROR 3823 和健康检查误判,加起来 30 分钟,占了实际工时的近四分之一。
如果重来一次,我会做四件事:
- 建表脚本先在空库上跑通再提交。当时
messages的 CHECK 和外键组合只在空库初始化时才暴露,如果提交前用down -v重建一次,9c91e72就不会带着一个建不完的 schema 进仓库。 - 契约冻结前让 B~F 真的拿 Mock 对一遍。v1 冻结后
api.md又改了 73 行,说明"冻结"更多是我自己宣布的,不是全组核对过的。先让每个人照契约写一个最小请求,再宣布冻结,返工会少很多。 - 健康检查按"首次初始化"配,不按稳态配。这次是
start_period和retries救回来的,但本质上是把数据库启动当成了一个瞬时动作。以后所有带初始化脚本的容器都先留足首次启动窗口,再谈"快速失败"。 - 中文
?那次也提醒我:先HEX()看字节,再决定是修数据还是修客户端。第一反应去改种子数据,方向就错了。
组员 B(张祺 102400432)的 PSP
时间口径:取自本机 git 提交时间戳(
42339a911:36、5237cab13:15、2f7481620:18)与会话起止,取整到分钟。
基本信息
| 项 | 内容 |
|---|---|
| 成员 | B / 张祺 / 102400432 |
| 角色 | 后端 · 身份与报名(贡献 17),契约 §2 认证、§4 报名、§5.1/§5.2 统计 |
| 统计区间 | 2026-11-03 09:50 — 2026-11-03 20:25(同日两段:上午至午后、晚间返工) |
时间记录
| 阶段 | 预估时间 | 实际时间 | 起止时间 | 备注 |
|---|---|---|---|---|
| 读作业与分工、通读契约 v1、盘点 A 骨架偏差 | 40 | 50 | 09:50–10:40 | 骨架与契约有 7 处不一致(JWT 空壳、无角色校验、422 结构、时间无偏移量等),逐条列入待修项 |
写后端协作规范、统一日志、补 .gitignore | 45 | 56 | 10:40–11:36 | 额外补「待修项责任表」「变更登记」,方便 C 对照 |
| 公共层(统一响应/错误码/422/时间)+ JWT + 注解式鉴权 | 60 | 34 | 11:36–12:10 | 方案在规范阶段已定,编码比预估快;环境问题计入最后一段 |
| 认证 §2(注册即登录/登录/me)+ 报名 §4(列表/新增/整体替换/取消) | 70 | 40 | 12:10–12:50 | users + registrations 同事务;报名接口只认 Token 中的 sub |
| 统计 §5.1 / §5.2(去重人数、姓名打码、forum_chair 归属校验) | 40 | 15 | 12:50–13:05 | SQL 与权限分支复用上一模块,明显低于预估 |
| 冒烟脚本 + 按模块拆 6 次提交 + 推送 | 40 | 20 | 13:05–13:25 | 提交依次对齐 common/auth/registration/user/stats/文档 |
返工:时间输出补 +08:00(C 联调反馈) | 0 | 25 | 20:00–20:25 | 计划外缺陷;改 1 行格式串,重新打包并推送两条分支 |
| 合计 | 295 | 240 | — | 实际比计划少 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 条断言 |
| Markdown | 2 份 182 行 | docs/backend-conventions.md 147 行 + 本表 |
| SQL | 0 | 沿用 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 symbol | JDK 23+ 的 javac 不再自动执行 classpath 上的注解处理器,Lombok 未生成 getter 与构造器 | pom.xml 显式声明 annotationProcessorPaths(提交 5b8b4a0) |
| D2 | RegistrationController 返回 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:00 | JacksonConfig 输出格式串 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 min | 9 min | 契约里 page_size 与 page 的命名先对齐,避免后面返工 |
| 论坛列表(type / keyword / 分页) | 30 min | 11 min | AI 出首稿快,主要是把筛选条件按契约逐条核对 |
消息 scope 与 since 游标 | 35 min | 7 min | 游标用 base64url 编 `published_at |
| 秘书名单与 CSV 导出(UTF-8 BOM) | 20 min | 5 min | 加 BOM 后 Excel 才不乱码,改的是响应头 |
| 写工作日志 | 10 min | 4 min | 记录当天已做/未做,供晚上合并时对账 |
| 自测清单(24 条接口) | 15 min | 6 min | 用 PowerShell 脚本串起来跑,逐条比对状态码 |
| 等 B 的认证与统一出参合入 | 10 min | 12 min | 等待时间,不计入对外汇总;期间只能先按契约写 Mock |
| 合并 B 分支并适配 | 30 min | 55 min | 返工大头:Role 被改成常量、participantCount 从 Long 变 Integer、page_size / forum_id 绑定失效、422 缺 errors |
| 补 C 侧单测 | 40 min | 16 min | 分页参数、议程排序、/forums/mine 权限、since 非法值、越权 403 |
等 B 的时间格式修复(+08:00) | 10 min | 8 min | 等待时间,不计入对外汇总;期间先按旧格式自测,修复后复测一遍 |
补 GET /api/forums/mine | 40 min | 28 min | 契约漏项,评审前补的;要按 chair_user_id 只返回本人论坛 |
| PSP 整理与数字核对 | 75 min | 25 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/ | 8 | 318 | 列表、详情、议程、/forums/mine |
message/ | 8 | 375 | global / forum 两种 scope、since 游标 |
participant/ | 7 | 283 | 按论坛筛选、脱敏、CSV 导出 |
backend/src/test/java(C 侧) | 2 | 199 | ForumServiceTest、MessageServiceTest |
| 文档与自测脚本 | 3 | 194 | docs/selftest.ps1、接口说明与工作日志 |
| 合计 | 28 | 1,369 | 主代码 976 / 测试 199 / 文档 194 |
缺陷记录
| 缺陷簇 | 处数 | 发现阶段 | 类型 | 根因与修复 |
|---|---|---|---|---|
| 合并 B 分支后不兼容 | 9 处 | 合并当天 | 集成 | Role 从枚举被改成常量、participantCount 由 Long 变 Integer,逐处改回契约口径 |
| 参数绑定失效 | 4 处 | 合并当天 | 集成 | page_size、forum_id 的 @RequestParam 名字与契约不一致,改注解值并对契约 |
422 响应缺 errors | 11 处 | 自测阶段 | 契约 | 校验失败只返回 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
|
| 我负责的论坛 | 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.ps1 | 24 条接口自测全部通过(文字结论,不上图) |
| 单元测试 | 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:15 | 8 小时(D 自报) | 未记录(提交跨度 11 小时 18 分,不能当净工时) | D 自报实际 10.5 小时,无法用时间戳核实 |
代码规模
行数取
origin/master上frontend/的最终文件,不是 diff 新增行。
| 文件 | 行数 | 字节 | 说明 |
|---|---|---|---|
frontend/app.js | 908 | 41,121 | 论坛列表/详情、报名、日程、消息、推荐入口 |
frontend/api.js | 207 | 8,176 | 请求层与 Mock 开关 |
frontend/index.html | 213 | 12,125 | 参会者端页面骨架 |
frontend/styles.css | 161 | 24,452 | 参会者端样式 |
frontend/README.md | 26 | — | 目录内说明 |
| 代码合计 | 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 |
| 3 | myRegistrations 归并时可能抛 TypeError | `item.forum?.id | |
| 4 | 文件里保留两套旧渲染函数 | renderScheduleLegacy / renderMessagesLegacy 在改版后没有删除 | 技术债,未清 |
产出对照
| 产出 | 位置(文件 / 接口 / 页面) | 截图或检查证据 |
|---|---|---|
| 首页与论坛浏览 | frontend/index.html、frontend/app.js、frontend/styles.css;论坛列表与详情弹窗 |
|
| 登录与报名权限 | frontend/app.js;登录弹窗、报名按钮状态 |
node --check app.js 通过 |
| 我的日程 / 个人中心 | frontend/app.js;日程分组、冲突标记、个人中心 |
|
| 消息中心 | frontend/app.js、frontend/api.js;消息列表与详情弹窗 | 截图见 7.2;git diff --check 通过 |
| LLM 推荐 | frontend/app.js、frontend/api.js;推荐弹窗与降级提示 |
|
偏差说明
偏差最大的是"消息中心与智能推荐":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 min | 75 min | 提交 b3125cd,2,852 行新增,10 个文件。骨架与样式模板复用了参会者端的视觉基座,比预估快;耗时主要在对齐 docs/api.md 的字段映射 |
| Mock 业务数据 i18n 翻译链路修复 | 30 min | 65 min | 提交 602983c。切语言后发现论坛名、消息标题等业务内容仍是中文,新增 mockI18n() 让 Mock 数据也走语言包,缺 key 回退 zh-CN 并打日志。本阶段偏差最大 |
| hero 视觉对齐参会者端 + 参会者账号跳转 | 45 min | 55 min | 提交 3b6cc8b。把管理端 hero 卡片、配色、字号对齐参会者端;同时加"登录参会者端"跳转入口 |
| 参会者入口位置调整与移除 | 15 min | 20 min | 三次提交 64b9127 / 446413e / 3d165da。入口从登录页右上角 → 白色面板右上角 → 最终删掉:登录面板本身已区分角色,多一个入口反而混淆 |
| 五语言走查与收尾 | 30 min | 36 min | 逐个视图切五种语言走查,确认无缺 key 告警、CSV 导出文件名正常、未登录时角色守卫能拦住 |
| 按契约冻结 v1 写真实接口请求层 | 80 min | 63 min | 17: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 min | 37 min | 18: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/,界面证据用
notice-ja.png |
| 真实接口请求层(契约冻结 v1) | admin/api.js:request() 统一信封解析、401 清 token、CSV 单独处理、buildQuery 显式传参 | 无图;接口状态以博客 4.7 / 6.4 的实测结论为准 |
| Mock 业务数据(对齐 docs/api.md) | admin/mock-data.js | 有:
|
| 三种角色视图:会议主席 / 分论坛主席 / 秘书 | app.js 中 renderView 路由(dashboard / board / participants) | 有:
|
| 参会者名单 CSV 导出 | admin/app.js 中 downloadFile + csvCell | 无独立截图;导出接口实测 321 字节、UTF-8 BOM,见博客 4.4 / 7.2 |
| 通知消息与接口状态视图 | app.js 中 notice / api 视图 | 有:
|
| 契约缺口记录(后端未定义) | 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:20 | 0.5 h | 0.5 h | 冻结契约把原设计的 POST /schedule/conflicts 改成 GET /schedule,按契约实现避免返工 |
| LLM 推荐(提示词 / 解析 / 降级) | 11-03 12:20 ~ 12:55 | 1.5 h | 0.9 h | 有 Python 原型可迁移,快于预估;JSON 解析与四道校验占主要时间 |
| 冲突算法与日程接口 | 11-03 12:55 ~ 13:20 | 1.0 h | 0.4 h | 半开区间规则一次写对,边界用例留到测试阶段补 |
| 单元测试 | 11-03 13:20 ~ 13:45 | 1.0 h | 0.4 h | 首版 30 条;一条断言写错(“大模型”不是“大语言模型”的连续子串),修正后通过 |
| 集成联调与修复 | 11-03 21:40 ~ 23:50 | 1.0 h | 2.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 |
| 素材整理与 PSP | 11-04 09:00 ~ 09:30 | 0.5 h | 0.5 h | 补单条推荐、单测输出、git log 截图 |
| 合计 | — | 5.5 h | 4.9 h | — |
代码规模
口径:
git show --stat统计的个人提交新增行,不含合并提交、不重复计算 cherry-pick。
| 类别 | 新增行数 | 说明 |
|---|---|---|
| 主代码 | 1,075 | LLM 推荐 710 行 + 日程冲突 356 行 + 契约字段修复 9 行 |
| 单元测试 | 601 | 5 个测试类,45 条用例(含 30 条首版 + 1 条 JSON 字段回归) |
| 协作修复 | 4 | 补 C 分支 ForumRegistrationRow.userId 与 SQL 查询列 |
| 合计 | 1,680 | 提交 a51ea17 / 15dc948 / 7725942 / 417ae75 / db82f44 |
缺陷记录
| 编号 | 缺陷 | 发现方式 | 根因 | 处理 | 耗时 |
|---|---|---|---|---|---|
| F-BUG-1 | ForumRegistrationRow 缺 getUserId(),整个后端无法编译 | mvn compile 报“找不到符号” | C 分支组装名单时按 userId 分组,但实体和 SQL 都没带该列 | 实体补 userId 字段,SQL 补 r.user_id AS user_id | 0.2 h |
| F-BUG-2 | /api/schedule 冲突字段输出 aforum_id,契约要求 a_forum_id | 真实接口调用后逐字段核对 docs/api.md | aForumId 前两个字母大写,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/ | 有:
|
| 个人日程与冲突检测 | GET /api/schedule;backend/src/main/java/com/cncc/meeting/schedule/ | 有:
|
| 单元测试 45 条 | backend/src/test/java/com/cncc/meeting/{llm,schedule}/ | 有:
|
| 冲突字段契约修复 | schedule/dto/ConflictItem.java + ConflictItemJsonTest | 有:7.1 F 的提交记录
|
偏差说明
偏差最大的是“集成联调”:预估 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 分支的文件路径拼接,链接格式以仓库实际支持为准。
相关推荐
飞轮储能系统机侧PQ控制+网侧变流器恒压双闭环控制【附参考文献】
内容概要:本文针对含分布式电源的配电网在遭受自然灾害或外力破坏导致多条线路同时故障的复杂场景,提出并实现了一种将网络重构、孤岛划分与检修进度统筹考虑的多时段故障恢复优化模型。该模型以最小化系统网损和甩负荷惩罚为综合目标,采用混合整数二阶锥规划(MISOCP)方法,通过引入大常数松弛处理支路的投切状态,利用二阶锥松弛技术将非线性的潮流方程转化为凸优化问题,并创新性地应用虚拟流方法确保各供电孤岛内部的连通性。模型在IEEE 33节点系统上进行了仿真验证,能够逐时段生成最优的开关操作序列、负荷恢复策略以及分布式电源协同供电方案,有效提升了配电网在故障期间的韧性与供电恢复能力。文章还深入评述了模型的实现细节,并指出了在负荷分级、无功处理、辐射状约束精确性等方面的潜在改进方向。; 适合人群:电力系统自动化、智能电网、配电网规划与运行等相关领域的研究生、科研人员及从事配电网故障恢复与韧性提升工作的工程技术人员。; 使用场景及目标:①应对由极端天气或外力破坏引发的配电网多点并发故障;②在故障后通过协同优化网络重构与动态孤岛划分,最大化恢复关键负荷供电;③将故障线路的修复时序纳入优化框架,实现检修资源调度与供电恢复路径的联合决策,以提升系统整体恢复效率与韧性。; 阅读建议:本文理论深度较高,综合运用了电力系统分析、数学优化建模与凸松弛技术,建议读者具备扎实的电力系统潮流计算、优化理论和MATLAB/YALMIP建模基础。阅读时应重点关注虚拟流、二阶锥松弛等核心技术的设计原理与实现逻辑,结合文中详细的公式推导与算例结果,深入理解模型的构建过程与求解机制,为进一步研究考虑储能、柔性负荷及多能协同的主动配电网高级恢复策略奠定基础。
风光储协同并网发电系统仿真(Simulink仿真实现)
内容概要:本文围绕“风光储协同并网发电系统仿真(Simulink仿真实现)”展开,重点介绍了一套基于Simulink/MATLAB平台的电力系统仿真体系,涵盖风力发电、光伏发电、储能系统(如电池、飞轮、抽水蓄能)以及制氢系统(PEM电解槽)等多种能源形式的协同建模与控制策略。系统实现了并网与孤岛模式下的协调控制,采用了虚拟同步发电机(VSG)、下垂控制、模型预测控制(MPC)、双闭环控制等多种先进控制算法,并结合多时段故障恢复、网络重构与孤岛划分的优化模型,提升配电网在故障情况下的韧性与恢复能力。文中详细阐述了基于二阶锥松弛与虚拟流理论的混合整数二阶锥规划(MISOCP)建模方法,用于求解含分布式电源的配电网多时段故障恢复问题,确保供电连通性与辐射状结构。整体资源体系丰富,包含仿真模型、参考文献、建模报告及代码实现,适用于科研与教学实践。; 适合人群:具备电力系统、自动化、电气工程等相关背景,熟悉MATLAB/Simulink仿真环境,从事新能源、微电网、智能配电系统研究的研究生、科研人员及工程技术人员。; 使用场景及目标:① 构建风光储协同发电系统的动态仿真模型,研究其并网运行特性与稳定性;② 设计与验证微电网在故障情况下的孤岛划分、网络重构与多源协同恢复控制策略;③ 学习并应用二阶锥松弛、虚拟流、混合整数规划等高级建模技术解决电力系统优化问题。; 阅读建议:建议结合提供的参考文献与仿真模型逐步实践,重点关注模型的约束构建、目标函数设计与求解器接口实现;在复现过程中注意变量定义、参数设置与物理意义的一致性,建议拓展加入负荷分级、储能动态调度与通信延迟等实际因素以提升模型实用性。
基于人工神经网络的类噪声环境声音声学识别(Matlab代码实现)
内容概要:本文系统阐述了基于人工神经网络的类噪声环境声音声学识别方法,并提供了完整的Matlab代码实现。研究聚焦于构建和训练人工神经网络模型,以实现对在复杂类噪声环境中采集的声音信号进行高效识别与分类。内容涵盖数据预处理、特征提取、网络模型设计、训练优化等关键技术环节,重点探讨了如何提升模型在低信噪比条件下的鲁棒性与识别精度。该方法融合了现代信号处理技术与深度学习架构,为复杂声学环境下的音频分析提供了有效的解决方案。; 适合人群:具备一定信号处理和机器学习基础,从事音频识别、环境监测、智能安防或相关领域研究的研发人员及研究生。; 使用场景及目标:①应用于智能监控、工业设备故障诊断、城市环境声音感知等需在强噪声背景下识别特定声源的场景;②为科研人员提供一个可复现、可扩展的Matlab实现框架,用于进一步改进和优化声学识别算法。; 阅读建议:建议读者结合所提供的Matlab代码逐段研读,深入理解算法实现细节,重点关注特征工程与网络结构设计部分,并尝试在不同类型的噪声条件下调试模型,以全面评估其泛化能力与实际应用潜力。
政府科技管理者在推动科技成果转化时,如何精准识别高价值科技成果?.docx
政府科技管理者在推动科技成果转化时,如何精准识别高价值科技成果?
产业园区运营负责人如何实现科技成果与产业资源的精准匹配和高效配置?.docx
产业园区运营负责人如何实现科技成果与产业资源的精准匹配和高效配置?














































