知食份子队——代码规范、冲刺任务与计划
大福食刻 α 冲刺:团队代码规范、冲刺任务与计划
| 项 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 福州大学 · 软件工程 · 2601_CS_SE_FZU 课程社区 |
| 这个作业要求在哪里 | 团队第四次作业——站立式会议 + α 冲刺 |
| 这个作业的目标 | 定团队代码规范,拆本次冲刺 39 项任务与 64 点七天计划,并说明如何驱使 AI 技术员参与交付 |
| 其他参考文献 | 阿里巴巴《Java 开发手册》终极版 v1.3.0;华为内部代码规范;《构建之法》第 2 章、第 13 章 |
这篇写三件事:代码写成什么样算不违规,这一周 64 点活怎么摊到 8 个人身上,AI 技术员怎么派活才不至于返工。三件事放在一起写,是因为它们互相牵着——规范定得含糊,任务就得返工;任务摊不匀,规范也没人守;AI 没边界,前面两样都会被带偏。
一、团队与分工
组里 8 个人,一人一岗,一岗一个 owner。分工是按交付链路切的,没有按模块切。
| 编码 | 岗位(交付链路位置) | 认领人 | 点数 | 这个岗不做什么 |
|---|---|---|---|---|
| A | 交付 owner:范围、验收口径、站会、文档、答辩 | 林青妍 | 10 | 不插手具体实现方案 |
| B | 架构 + 部署汇总:技术骨架、接口契约、六容器上线 | 张伟林 | 5 | 不替业务岗写业务代码 |
| C | 后端实现:13 个新端点 + 114 个端点实现 | 方韵希 | 14 | 不改视觉规范 |
| D | 数据:5 张新表、78 表落库、种子与自检 | 邱语堂 | 3 | 不擅自改表名、字段名、枚举 |
| E | 前端功能:8 页接线 + 31 页实现 + 去 localStorage | 郭洁颖 | 11 | 不改视觉规范 |
| F | UI 整合:视觉规范落到最终版 | 郑春天 | 6 | 不改业务逻辑 |
| G | QA-1 测试功能:用例、断言、AC 走查 | 杨雨薇 | 8 | 不改业务代码 |
| H | QA-2 测试验收 UI:三视口基线、偏差清单、验收签字 | 王一帆 | 7 | 不改布局代码 |
| — | 合计 | — | 64 | — |
当时讨论过按模块分,最后否掉了。按模块走,人人都要碰前后端,接口风格容易出四套,联调时互相等。按链路走,每段只有一个 owner,上下游交什么、怎么算过都写清楚,谁卡住了一眼能看出来。
验收岗(G、H)不碰代码,这是我们特意定的。又能写又能验,缺陷容易被自己人的视角漏过去。这次唯一一个 P0 权限漏洞,就是 G 岗的越权用例抓出来的——写代码的人一般不会去试"别人的 token 能不能删我的记录"。
配套走的是一条双线汇合的路子:C 的后端和 E 的前端各推各的,D4 起 C↔E 交叉互测,D5 汇合,然后 G 测功能、H 测 UI,两条线都过了才允许 K2 冻结。
二、代码规范
2.0 规范定在多大的规模上
规范按本周要交付的规模来定,下面这些数字都能用命令复现:
| 维度 | 数量 | 复现方式 |
|---|---|---|
| 数据库表 | 27 张(22 张基础 + 5 张主链) | init.sql 22 + 5 张主链 DDL |
| 后端端点 | 127 个(114 基础:业务 70 + admin 44;13 主链) | routes/index.js + 13 条新端点清单 |
| 前端页面 | 45 个 .vue(业务 39 + 管理端 6) | ls frontend/src/views frontend/src/views/admin |
| 测试文件 | 21 个(2 个断言集 + 19 个走查) | ls tests/*.mjs tests/*.cjs |
| 容器 | 6 个(MySQL / Redis / MinIO / matting / backend / frontend) | docker-compose.yml |
| 代码行数 | 26233 行(backend/src + frontend/src 的 js + vue) | find backend/src frontend/src \( -name "*.js" -o -name "*.vue" \) | xargs wc -l | tail -1 |
定了一条:文档和代码对不上时,以代码为准,再回头改文档。只有一类例外,是设计口径——表名、字段名、端点契约照既定设计来,因为代码本来就是照它写的。这两类冲突分开判,不要一刀切。
2.1 规范从哪来、怎么迁
作业要求参考阿里 Java 开发手册终极版 v1.3.0、华为内部代码规范这类材料。我们的做法是只搬判断标准,Java 专属的条款不搬:
| 来源 | 搬过来的 | 不采用的 | 为什么 |
|---|---|---|---|
| 阿里《Java 开发手册》v1.3.0 | 命名统一、分层清晰、异常统一处理、日志分级、注释规范 | HashMap 初始化容量、ThreadLocal、Java 包结构 | 本项目是 Node.js + Vue,不是 Java |
| 华为内部代码规范 | 提交可追溯、接口契约先冻结、变更要登记影响面 | 华为内部目录规范、构建流水线模板 | 与本项目仓库结构不匹配 |
| 本项目既有代码 | 11 个组件已全是 kebab-case、数据库列已全是 snake_case | 阿里手册默认的 camelCase | 跟随现状才能让新旧代码一致 |
团队规范一共 43 款,这里挑最常用的 40 条展开,按命名、后端、数据库、API、前端、UI、测试、注释、Git、安全、性能十一组排。每条都给判定口径或者反例,不写"要努力"这种话。
总的原则只有一条:规范得能立刻判对错,光写得好看没用。
2.2 命名规范(第 1—3 条)
第 1 条 函数、变量、常量一律 snake_case,例外只有三处
主流 JS 写 camelCase,这个项目从数据库列名到前端变量一路 snake_case。三层对齐之后中间不需要任何转换函数:avatar_object_key 是表列名,也是接口字段,也是组件里的变量,不会出现"接口给 snake、组件收 camel、忘了转就是 undefined"。
| 对象 | 规则 | 实例(真实代码) |
|---|---|---|
| 函数 | snake_case | create_app query_one assert_str list_canteens load_json rate_limit recalc_all |
| 变量 | snake_case | canteen_options dish_options success_open lit_text form pool |
| 类 | PascalCase | ApiError |
| 编译期常量、配置表 | UPPER_SNAKE_CASE | POINT_SORTS POINT_SORT_EXPR BIZ_BY_STATUS MSG_MAP MIN_SAMPLES UPLOAD_MAX_BYTES |
| 数据库表、列 | snake_case,表名单数 | user canteen review_like avatar_object_key last_login_at |
例外只有三处,都是库的约定,照原样用:Element Plus 的 props(v-model show-word-limit value-format)、Vue 官方 API(defineProps defineEmits onMounted useRouter)、第三方库返回的对象键(data.insertId err.code)。
// 不要
function getCanteenList() {}
const reviewList = [];
// 要
function list_canteens() {}
const review_list = [];
名字还得能读出意思,五条小要求:
- 布尔变量要能读出真假:
is_loginis_adminfzu_verifiedenabled。 - 函数名动词开头:
list_*查列表、get_*取单个、count_*数数量、create_*建、update_*改、delete_*/remove_*删、assert_*校验、parse_*转换、recalc_*重算。 - 禁止缩写到看不懂:
ptscssm只能在 SQL 别名里用,JS 变量里不许出现。 - 一个名字只表达一件事:
canteen.type是点位类型 0—3,subject_type是被评对象类型 0—2,跨实体传递时带上前缀。 - 拼音和英文不许混写:
getPingfen、cantingList这类直接打回。
第 2 条 文件、目录、路由名、CSS 类名怎么起
| 类型 | 规则 | 实例 |
|---|---|---|
| 目录 | 小写 kebab-case,按职责分层 | backend/src/services、backend/src/middleware、frontend/src/views/admin |
| 后端模块 | <域>.<层>.js | review.service.js、aggregation.service.js、admin.service.js |
| 后端工具 | 单个名词或动名组合,不带 .service | api-error.js、validate.js、dfa.js、dhash.js、fzu-cas.js |
| Vue 页面 | <名>-view.vue | checkin-view.vue、canteen-detail-view.vue、ai-view.vue |
| Vue 组件 | <名>.vue,kebab-case | review-card.vue、star-rating.vue、brand-logo.vue |
| 路由 name | kebab-case,与文件对应 | 'post-detail'、'journal-editor-edit'、'diet-plan' |
| 组件 emit 事件 | kebab-case | 'fav-changed'、'update:modelValue' |
| CSS 类名 | kebab-case,页面级加前缀 | .page-wrap .t-title .f-row .dish-chips .vis-seg |
| 测试 | *.test.mjs;走查 walkthrough-r<编号>-<主题>.test.mjs | api-integration.test.mjs、walkthrough-r52-fzu-cas.test.mjs |
| SQL | init.sql;迁移 migration/<日期>-<说明>.sql | migration/20261026-add-idx-review.sql |
两条禁令:test.js new.js temp.js aaa.js 我的代码.js 这类看名字猜不出用途的文件不许进仓库;代码文件一律英文名,文档可以用中文名。
组件文件名这条最容易错。Vue 官方风格指南推荐 PascalCase 文件名,仓库里 11 个组件统一 kebab-case(review-card.vue,不写 ReviewCard.vue)。Windows 文件系统大小写不敏感,混用会在跨平台时出现引入不到的怪问题,所以跟仓库走。
第 3 条 HTTP 双方向:读回来 snake,写进去 camel
同一个资源,出方向和进方向的名字不一样,这条最容易被"全项目 snake_case"误读。
| 方向 | 命名 | 实测实例 |
|---|---|---|
| 数据库 → API 响应 → 前端变量 | snake_case | avatar_object_key created_at rating_avg review_count |
| 前端表单 → 请求体 | camelCase | subjectType subjectId dimensionScores imageKeys isAnonymous avatarObjectKey listType |
实测:routes/index.js 里请求体取值 camelCase 16 处、snake_case 0 处;响应字段 snake_case 100%,没有一处驼峰转换。
四条规则:
- 响应字段永不转驼峰,前端拿到的就是数据库列名,中间不加映射层。
- 新增写接口的请求体沿用 camelCase,服务端在路由入口做一次映射,
services/里只认 snake 列名。 - 前端本地表单跟随请求体用 camelCase,不再引入新的 snake 字段。
- 同一资源不许两种写法并存,撞见
form.dish_names这种顺手统一掉。
这么定之前两边成本都量过:改全部请求体要同时动 45 个页面和 21 个测试文件,全改 camel 又要动 22 张表的列名。规则就是方向分明、新增不扩散。
2.3 后端规范(第 4—9 条)
第 4 条 目录职责,三条越界禁令
routes → services → db(pool / redis / minio)
↓ ↓
middleware utils(api-error / 校验 / dfa / dhash)
| 目录 | 职责 | 不该出现的东西 |
|---|---|---|
index.js | 起进程、起定时任务、优雅退出 | 业务逻辑 |
app.js | Express 装配:中间件顺序、静态代理、路由挂载、两个兜底 | SQL |
config.js | 环境变量 + 所有可调数值 | 业务分支 |
db/ | 连接池 / Redis / MinIO 客户端 | 业务 SQL(聚合口径除外) |
middleware/ | 横切:鉴权、错误、限流 | 具体业务判断 |
routes/index.js | 路径 → 校验 → 调 service → ok(res, data) | 复杂业务、复杂 SQL |
services/ | 业务规则 + 复杂 SQL + 聚合口径 + 事务 | req / res |
utils/ | 纯函数:无 IO、无全局状态、可单测 | 数据库访问、HTTP |
jobs/ | 定时兜底与补偿 | 被请求触发的逻辑 |
三条越界禁令:
routes里不写 SQL、不拼 WHERE、不做业务判断。services里不读写req/res。- 任何层不许绕过 service 直接调
db,一次性对账脚本除外,且必须在文件头写明用途。
路由薄一点没问题,取参、挂权限中间件、调 service、组装响应,本来就只有这四件事。
现状是 14 个 service + 7 个 utils + 3 个 middleware + 3 个 db + 1 个 jobs,routes/index.js 1010 行,业务 router 与 admin router 在同一个文件里,靠分区注释才读得下去。
第 5 条 路由四段式,一个字都别省
// routes/index.js:395
/**
* 发布评价(动态维度 + DFA 机审分流)
*/
router.post('/reviews', write_required(), rate_limit('review', N.REVIEW_PER_H, 3600), async (req, res, next) => {
try {
if (req.user.status !== 0) throw err.banned('当前状态不可发布');
const b = req.body;
const input = {
subjectType: Number(b.subjectType), subjectId: Number(b.subjectId),
dimensionScores: b.dimensionScores || {},
content: String(b.content || '').slice(0, 1000),
isAnonymous: !!b.isAnonymous,
};
ok(res, await review.create_review(req.user.id, input));
} catch (e) { next(e); }
});
四段必须齐:
/** ... */块注释:干什么、对应设计章节(打卡模块的签到约定)、参数与返回。- 中间件链:
auth_required()/write_required()/admin_required()/rate_limit(...),写在 handler 之前。 try { ... } catch (e) { next(e); },catch 体原样一行next(e),不在这里拼错误响应。- 出口只用
ok(res, data),不手写res.json({...})。
三条附注:write_required() 本身含登录校验,不用再挂 auth_required();业务前置判断直接 throw err.banned(...),不手写 res.status(403);请求体字段是 camelCase,进 service 之前映射成 DB 列名。
错法长这样:catch (e) { res.status(500).json({ msg: e.message }) }。绕过统一错误处理之后,业务码丢了,前端拿不到 code,SQL 报错原文也漏给了用户。
第 6 条 分层边界按复杂度切
| 场景 | 写在哪 | 依据 |
|---|---|---|
一次 SELECT / INSERT 能完事 | 路由里直接写 | routes/index.js 里有大量 query_one('SELECT id FROM user WHERE username = ?') |
| 多表 JOIN、子查询、聚合、排序表达式 | 必须落 service | content.service.js 的 POINT_SORT_EXPR、list_canteens |
| 需要事务(多写) | 必须落 service + get_conn() | review.service.js 用 beginTransaction() |
| 复用 2 次以上 | 必须落 service | 一刀切规则 |
| 有业务规则(样本 < 3 不进高分口碑) | 必须落 service | 规则不能散在路由里 |
判断口径很土但好用:一条 SELECT ... WHERE id = ? 可以留在路由;出现 JOIN、GROUP BY、beginTransaction,或者两个地方都要用,就下沉到 service。
三条不能破的边界:services/ 不许出现 req 或 res;utils/ 不许访问数据库;routes/ 不许出现 beginTransaction。
第 7 条 响应体永远三键,业务码只有九个
// 成功
{ "code": 0, "msg": "ok", "data": { ... } }
// 失败
{ "code": 40402, "msg": "重复操作:该记录已存在" }
| 工厂函数 | HTTP | 业务码 | 默认文案 | 什么时候用 |
|---|---|---|---|---|
| —(成功) | 200 | 0 | ok | 一切正常返回 |
err.invalid() | 422 | 40001 | 参数不合法 | 参数校验失败、格式不对 |
err.unauthorized() | 401 | 40101 | 请先登录 | 没 token / token 失效 |
err.forbidden() | 403 | 40301 | 无权操作 | 角色不够、非本人、非管理员 |
err.banned() | 403 | 40302 | 账号当前状态不可进行该操作 | 禁言 / 封禁 |
err.not_found() | 404 | 40401 | 内容不存在 | 资源不存在、路由未命中 |
err.conflict() | 409 | 40402 | 操作冲突 | 唯一键冲突、重复提交、状态非法迁移 |
err.too_many() | 429 | 42901 | 手速太快啦,休息一下 | 触发限流 |
| —(未知异常) | 500 | 50001 | 服务器开小差了,请稍后重试 | 兜底,console.error 留痕 |
err.unavailable() | 503 | 50301 | 服务暂不可用,请稍后再试 | 依赖(AI / 抠图 / CAS)不可用 |
九个业务码只有一个来源,就是 backend/src/utils/api-error.js。代码里没有的码,文档里也不会写。AI 降级不抛业务码,而是在成功体里回 degraded: true,降级算可用状态,不当错误处理。
四条纪律:
- 业务代码里不写裸
res.status(4xx).json(...),合法例外只有app.js的/img/*代理和middleware/error.js本身,这两处都不在业务流程里。 - 业务码五位、HTTP 三位,两个都要给对:409 配 40402,配成 40001 就是错的。
msg是给用户看的话,不带技术细节:写「用户名已被注册」,不写ER_DUP_ENTRY: Duplicate entry 'a' for key 'username'。- 新增业务概念才加码,不为"区分得更细"私自扩号段。
MySQL 重复键已经自动映射,业务代码不用自己判断:
// ER_DUP_ENTRY → 409 + 40402
if (errObj && errObj.code === 'ER_DUP_ENTRY') {
return res.status(409).json({ code: 40402, msg: '重复操作:该记录已存在' });
}
第 8 条 参数校验用 assert 家族,鉴权用四个中间件
assert(/^[a-zA-Z0-9_]{3,32}$/.test(username || ''), '用户名需 3-32 位字母/数字/下划线');
assert(typeof password === 'string' && password.length >= 6, '密码至少 6 位');
const score = assert_star(req.body.score, '评分'); // 1-5 整数,返回 number
const content = assert_str(req.body.content, '评价内容', 500); // 非空 + 上限 500,返回 trim 后字符串
const page = page_int(req.query.page, 1); // 非法值退回默认
校验顺序固定:身份 → 权限 → 参数格式 → 业务规则 → 落库。这样"没登录"不会被报成"参数错误",用户不至于困惑。
assert 的第二参必须是用户看得懂的中文,并且带上具体范围:写「评分需为 1-5 的整数」,不写「参数错误」。
四个中间件,别自己在路由体里写判断:
router.get('/canteens', auth_optional(), ...) // 公开,有 token 就挂 req.user
router.post('/reviews', auth_required(), ...) // 必须登录
router.post('/reviews', auth_required(), write_required(), ...) // 登录 + 没禁言/没封禁
router.get('/admin/stats', auth_required(), admin_required(), ...) // 管理员
三条纪律:路由体里不写 if (!req.user) throw err.unauthorized(),用 auth_required();写操作必须挂 write_required(),否则被禁言的用户还能发评价;admin 端点必须挂 admin_required(),光靠前端隐藏菜单不算权限控制。
账号状态 status:0 正常 / 1 禁言 / 2 封禁。muted_until 到期懒恢复,下次写入时把 status 改回 0,不靠定时任务清。
第 9 条 限流、事务、日志、常量,四件小事
限流:rate_limit(action, max, window_sec),项目里的真实写法是这两个。
router.post('/reviews', rate_limit('review', N.REVIEW_PER_H, 3600), ...)
router.get('/auth/captcha', rate_limit('captcha', 30, 60), ...)
Redis key 固定 rl:{action}:{userId}:{bucket},bucket = Math.floor(Date.now()/1000/window_sec);未登录用 req.ip 当身份;Redis 挂了要降级放行,不能因为限流组件故障把全站写操作停掉;阈值必须来自 config.numbers,不在路由里写数字。
事务:
const conn = await get_conn();
try {
await conn.beginTransaction();
// ... 多表写
await conn.commit();
} catch (e) {
await conn.rollback();
throw e;
} finally {
conn.release(); // 缺了就是连接泄漏
}
事务里只做数据库操作,不调 AI、不传文件;范围尽量小;rollback 之后必须把原错误抛出去,不要换成新错误吞掉。
日志:分四级,由 utils/logger.js 统一出口。log.error 记未捕获异常和依赖不可用;log.warn 记降级、重试、配置缺失;log.info 记启动信息和版本标记;log.debug 只用于调试。一律带 [模块] 前缀。禁止打密码、token、ai_config.apiKey、完整手机号学号。25 处业务打印已从 console.log 换成 logger,console.log 只在 logger 内部保留 1 处出口。
常量:任何有业务含义的数字都不许出现在业务代码里。
// 不要
if (samples < 3) { ... }
if (fileSize > 5 * 1024 * 1024) { ... }
// 要
const N = config.numbers;
if (samples < N.MIN_SAMPLES) { ... }
if (fileSize > N.UPLOAD_MAX_BYTES) { ... }
| 常量 | 值 | 含义 |
|---|---|---|
MIN_SAMPLES | 3 | 高分口碑门槛,样本少于 3 不进榜 |
DIM_CACHE_TTL_MS | 60000 | 维度配置内存缓存 60 秒 |
UPLOAD_MAX_BYTES | 5MB | 单图上限,multer 与 media service 共用 |
DHASH_HAMMING | 5 | 上传判重汉明距离阈值 |
REVIEW_PER_H | 10 | 发布/修改评价 次/时/人 |
VOTE_PER_H | 60 | 投票 次/时/人 |
UPLOAD_PER_H | 30 | 上传 张/时/人 |
AI_PER_H | 20 | AI 提问 次/时/人 |
POINT_SUBMIT_PER_H | 5 | 点位提交 次/时/人 |
新增常量加进 config.numbers 并写清单位(_MS / _BYTES / _PER_H),一处定义,不许两处各写一份。
2.4 数据库规范(第 10—13 条)
第 10 条 建表按模板来,十二条不能省
CREATE TABLE IF NOT EXISTS `user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(32) NOT NULL UNIQUE COMMENT '登录名',
`password` VARCHAR(72) NOT NULL COMMENT 'bcryptjs',
`avatar` TINYINT NOT NULL DEFAULT 0 COMMENT '预设头像编号',
`avatar_object_key` VARCHAR(255) NULL COMMENT '自定义头像 MinIO objectKey',
`role` TINYINT NOT NULL DEFAULT 0 COMMENT '0 学生 / 1 管理员',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0 正常 / 1 禁言 / 2 封禁',
`muted_until` DATETIME NULL COMMENT '禁言到期时间',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
| # | 规定 |
|---|---|
| 1 | CREATE TABLE IF NOT EXISTS,脚本能重复执行 |
| 2 | 表名、列名一律反引号包裹(user 是 MySQL 保留字,不包就报错) |
| 3 | 表名单数、snake_case:user canteen station dish review review_like |
| 4 | 主键统一 id,BIGINT(用户/评价)或 INT(配置类小表)+ AUTO_INCREMENT |
| 5 | 每一列必须有 COMMENT,枚举列写清每个值的含义('0 正常 / 1 禁言 / 2 封禁') |
| 6 | 时间一律 DATETIME,不用 TIMESTAMP(避免时区隐式转换);created_at 默认 CURRENT_TIMESTAMP |
| 7 | 金额用 DECIMAL(6,1)(人均消费),经纬度 DECIMAL(10,6),不用 FLOAT / DOUBLE |
| 8 | 布尔语义用 TINYINT(1),注释写清 0/1 的含义('0 未认证 / 1 已绑定') |
| 9 | 图片只存 objectKey(VARCHAR(255)),不存 URL、不存 base64 |
| 10 | 软删除用 status 字段(0/1/2),不做物理删除;enabled 用于开关 |
| 11 | 引擎 InnoDB、字符集 utf8mb4,与库级一致 |
| 12 | 新增列只加在末尾,不改列顺序 |
另有一条设计口径:表名一律照既定表名,不自创。checkin 改 check_in,diet_profile 改 dietary_profile,这类改名不是风格问题,前端字段和已有调用会一起错。
第 11 条 类型对照:什么业务用什么类型
| 业务含义 | 用什么 | 别用什么 |
|---|---|---|
| 主键 | BIGINT / INT + AUTO_INCREMENT | UUID 字符串 |
| 枚举 / 状态 | TINYINT + COMMENT 写清值 | ENUM(改值要 DDL)、VARCHAR |
| 布尔 | TINYINT(1) | BOOLEAN(其实是 TINYINT 别名,写法不统一) |
| 金额 / 均分 | DECIMAL(6,1) / DECIMAL(3,2) | FLOAT / DOUBLE(精度漂移) |
| 坐标 | DECIMAL(10,6) | 字符串(无法计算距离) |
| 时间 | DATETIME | TIMESTAMP(时区坑)、VARCHAR |
| 多值标签 | JSON | 逗号分隔字符串(无法索引与校验) |
| 长文本 | TEXT | VARCHAR(5000)(超长截断风险) |
| 图片 | VARCHAR(255) objectKey | URL / base64 |
第 12 条 索引四条规则,JSON 列三条纪律
- 唯一键先想清楚语义:
usernameUNIQUE、fzu_uidUNIQUE、review_like(user_id, review_id)联合唯一防重复点赞。 - 所有外键列建索引:
review.dish_id、review.user_id、station.canteen_id这几类。 - 列表页的排序 + 筛选组合建联合索引:"按 status 筛 + 按 created_at 排"就建
KEY (status, created_at)。 - 联合索引遵循最左前缀:
KEY (status, created_at)能用上单查status,用不上只查created_at;区分度高的列放左边。
实测:22 张表共 22 个主键 + 19 条具名索引(其中 3 条 UNIQUE KEY)+ 6 处列级 UNIQUE,合计 47 条索引类声明,外键约束 0 条。外键完整性由应用层保证,不建外键是为了分库与迁移时不用先拆约束。新增表对照这个比例自查:一张业务表通常 1 个主键 + 2—3 个索引。
JSON 列只存两类东西:canteen.tags(标签数组)和 review_summary.dim_sums(各维度分值和)。按维度排序时的写法:
(CAST(JSON_UNQUOTE(JSON_EXTRACT(pts.dim_sums, '$.taste')) AS DECIMAL(10, 2))
/ NULLIF(pts.review_count, 0)) DESC
三条纪律:读出来的值必须防御解析(mysql2 有时给数组、有时给字符串,统一用 parse_tags() 这类函数兜住);JSON 列不放需要频繁筛选的字段,筛选走正经列;写入前必须 JSON.stringify,INSERT ... SET ? 不会自动序列化。
第 13 条 迁移、种子、软删与聚合口径
迁移有两个必须知道的坑。
第一,中文数据不进 init.sql。docker-entrypoint-initdb.d 通道按 latin1 解析,中文会变乱码,所有中文数据一律由 seed/seed.js 参数化插入(query('INSERT ... VALUES (?)', [name]))。
第二,迁移脚本必须可重复执行。IF NOT EXISTS 只保护建表,不保护加列,加列要写成这样:
SET @exist := (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'dafu' AND table_name = 'user' AND column_name = 'last_login_at');
SET @sql := IF(@exist = 0,
'ALTER TABLE `user` ADD COLUMN `last_login_at` DATETIME NULL COMMENT ''最近登录时间''', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
种子数据的三个要求:可重复执行(先 DELETE 或 INSERT IGNORE);覆盖到列表页有数据;覆盖到"重复提交"能被测出来,比如同一用户同一点位同一天有多条记录。
写库还有四条:批量写用多值 INSERT,不在循环里一条条插;大数据量重算走 jobs/,不在请求链路里同步跑全表聚合;用户、评价这类有状态机的表用 status 软删,只需要记"删没删"的表用 deleted_at(DATETIME NULL),不用 is_deleted TINYINT,时间戳自带删除时间;severe_allergy 这类档案表不能物理删,历史评价会变悬空指针。
聚合口径:所有均分统一读 review_summary(subject_type 0 菜品 / 1 档口 / 2 食堂 + subject_id),对已发布评价做普通平均,无加权、无衰减;发评价、改评价、删评价、下架时增量更新 dim_sums 与 rating_avg;jobs 每日 05:00 加启动时全量 recalc_all() 兜底,防停机漂移。任何地方要用均分都读摘要表,不许自己 AVG(),一次性对账脚本除外。
2.5 API 设计(第 14—17 条)
第 14 条 URL 六条规则
/api/auth/register 资源前缀 + 动作
/api/canteens 复数名词
/api/canteens/:id 路径参数用 :id
/api/reviews/:id/vote 动作类子资源用动词
/api/me/reviews 当前用户资源走 /me
/api/admin/stats 管理端统一 /admin
/api/points 旧接口别名(与 /canteens 同实现)
- 全部挂
/api,文档里写的路径省略了这个前缀。 - 名词复数:
/canteens/dishes/reviews/posts。 - 动作类子资源才用动词:
/vote/like/appeal/reply/recalc。 - 层级最多两层:
/reviews/:id/reply可以,/canteens/:id/stations/:sid/dishes/:did/reviews不行。 - 路径全小写 + 连字符:
/points-submit/diet-profile/pet-community。 - 旧接口改名要留别名,
/api/points与/api/canteens用同一份实现,注释写清"旧口径保留兼容"。
第 15 条 方法语义与幂等
| 方法 | 语义 | 幂等 | 项目实例 |
|---|---|---|---|
| GET | 查询,不改数据 | 是 | 列表、详情、统计 |
| POST | 创建 / 动作 | 否,必须防重 | 发评价、点赞、AI 提问 |
| PUT | 全量更新 | 是 | 改资料、改评价 |
| PATCH | 部分更新 | 是 | 本项目 0 处使用,统一用 PUT |
| DELETE | 删除 / 取消 | 是 | 删评价、取消收藏、解绑 |
POST 防重三条路径,按业务选一条:
- 唯一键:
review_like(user_id, review_id),靠ER_DUP_ENTRY自动映射 409。 - 业务前置查询:同用户同点位同日期已有打卡,主动
throw err.conflict(),并在data里带上已有记录 id。 - token / 幂等键:AI 请求加 requestId,Redis 记 60 秒。
第 16 条 分页排序筛选的口径,加时间和数值两个坑
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
page | 正整数 | 1 | 不传 = 不分页,返回全量数组(兼容旧接口);传了才返回分页对象 |
size | 正整数 | 10 | 每页条数,上限 50 |
sort | 枚举字符串 | comprehensive | 必须在白名单内,非法值回 422 |
type / status | 整数 | 不传 = 全部 | 不传即不过滤 |
分页返回结构固定四键:
{ "list": [], "total": 128, "page": 2, "size": 10 }
排序白名单必须显式校验:
const POINT_SORTS = ['comprehensive', 'taste', 'portion', 'price', 'queue', 'env', 'delivery', 'package'];
if (!POINT_SORTS.includes(sort)) throw err.invalid('sort 取值不合法');
这条不是洁癖:ORDER BY ${sort} 会把字符串拼进 SQL,不校验就是 SQL 注入。
时间坑:后端 timezone: '+08:00',返回带 Z 的 UTC ISO 字符串(2026-10-23T07:30:00.000Z);前端直接 new Date(dt),绝不剥掉 Z。剥掉会把 UTC 当本地时间,固定偏 8 小时。
数值坑:连接池必须开 decimalNumbers: true,不配的话 mysql2 返回字符串,前端 .toFixed() 直接崩;新增数值列时确认这个配置还生效,别为了"精度"把它关掉。均分统一 ROUND(x, 2) 保留两位;计数(review_count / like_count)返回 number;金额返回 number(元),不返回分。
第 17 条 新增端点的检查清单
- 路径符合 URL 六条规则,挂在
/api下 - 方法符合语义,POST 想清楚了怎么防重
- 路由块注释写清"干什么 + 对应设计章节 + 参数 + 返回"
- 挂了正确的中间件:
auth_required/write_required/admin_required/rate_limit - 参数用
assert家族校验,文案中文且带范围 - 出口只有
ok(res, data),错误只有next(e) - 抛错用
err.*工厂,HTTP 与业务码配对正确 - 复杂 SQL 落 service,简单查询才留在路由
- 阈值类数字取自
config.numbers -
frontend/src/api/index.js对应api_*域里加了方法 + 注释 - 跑过一遍
tests/api-integration.test.mjs,没把已有用例跑红
2.6 前端规范(第 18—21 条)
第 18 条 前端目录与 API 层
| 目录 | 放什么 | 命名 |
|---|---|---|
api/ | http.js(唯一拦截器)+ index.js(按域分组)+ admin.js | 对象叫 api_域名 |
router/ | 路由表 + 守卫 | index.js |
stores/ | Pinia,一个业务域一个 | auth.js |
components/ | 跨页面复用,无业务依赖 | kebab-case.vue |
views/ | 页面,一个路由一个 | kebab-case-view.vue |
views/admin/ | 管理端页面 | 同上 |
utils/ | 纯函数(格式化、压缩、地图) | snake_case.js |
styles/ | global.css 630 行,设计令牌全在这 | — |
接口一律走 api/index.js,按域分组,一行一个:
export const api_review = {
list: (params) => http.get('/reviews', { params }),
get: (id) => http.get(`/reviews/${id}`),
create: (data) => http.post('/reviews', data),
update: (id, data) => http.put(`/reviews/${id}`, data),
remove: (id) => http.delete(`/reviews/${id}`),
vote: (id, value) => http.post(`/reviews/${id}/vote`, { value }),
like: (id) => http.post(`/reviews/${id}/like`),
};
四条规则:只允许 http.js 一个 axios 实例,页面里不许 import axios;每行一个方法,不写业务逻辑;有特殊参数或返回结构时上方加一行注释(// 点赞/取消赞 toggle:返回 {liked, likeCount});方法名与后端语义一致(list / get / create / update / remove / vote / like)。
拦截器已经做了四件事,页面里不要再做一遍:附 Authorization: Bearer ${token}(token 存 localStorage.fk_token);code === 0 时直接返回 data,页面拿到的就是业务数据;非 0 时 ElMessage.warning(msg) 并 reject;401 时清 fk_token / fk_user 并跳 /login。页面里再写 if (res.code === 0) 是重复判断,再写 try/catch 弹 toast 会弹两次。
第 19 条 SFC 三段,script 内部六分区
<template>
<div class="page-wrap checkin">
<!-- 分块注释:顶部返回头 / 表单 / 操作按钮 -->
</div>
</template>
<script setup>
/**
* 一体化打卡页(需求 5.1.4 / AC10-AC13):
* 一次打卡联动 打卡日历 + 足迹点亮 + 成就进度 + 宠物投喂道具
*/
/* ---------------- 常量 ---------------- */
/* ---------------- 小工具 ---------------- */
/* ---------------- 状态 ---------------- */
/* ---------------- 生命周期 ---------------- */
/* ---------------- 方法 ---------------- */
/* ---------------- 计算属性 ---------------- */
</script>
<style scoped>
/* 只用 var(--xxx) 令牌 */
</style>
六分区顺序固定:常量 → 小工具 → 状态 → 生命周期 → 方法 → 计算属性。import 和 defineProps / defineEmits 放在最上面,六个分区从常量开始。找东西按顺序找,不用读全文。
第 20 条 状态、存储与路由守卫
| 数据 | 放哪 | 理由 |
|---|---|---|
| 登录态(token / user) | Pinia + localStorage | 刷新要保留,auth.js 已实现 |
| 跨页面共享的列表(维度配置) | Pinia | 避免每页重复请求 |
| 页面内临时状态(表单、开关、分页) | ref / reactive | 不需要全局 |
| 业务数据(打卡记录、收藏、成就) | 必须走后端 | 见下段 |
存储的归属定了死规矩:localStorage 只放 token 和用户缓存,业务数据一律走接口。现状是 18 个页面在用 localStorage,其中 14 个存的是业务数据,另 4 个属本地偏好(login 记住用户名 / search 搜索历史 / post-publish 发帖草稿 / wheel 转盘候选);再加 follows 页面的关注关系是页内 mock,业务数据不落库共 15 个页面,这是本期要清的债。判据只有一条:换台设备、清一次缓存,这个数据还要不要。
键名统一 fk_ 前缀 + 语义名(fk_token / fk_user),新增键要登记,避免多个页面各起一个名字。读写必须走封装:
function load_json(key, fallback) {
try {
const raw = localStorage.getItem(key);
return raw ? JSON.parse(raw) : fallback;
} catch { return fallback; }
}
直接 JSON.parse(localStorage.getItem(k)) 在键为空时抛 SyntaxError,整个 onMounted 就挂了。实测 9 个页面各自复制了一份 load_json(7 个还各有一份 save_json):achievements checkin cobuild footprint pet-community pet report settings tasks。新增页面一律从 utils/storage.js 导入,不再就地复制。
路由五条规则:
- 组件一律
() => import(...),45 个页面全量打进主包会拖慢首屏。 name用 kebab-case,与文件名对应(checkin-view.vue↔name: 'checkin')。- 需要登录挂
meta: { auth: true },管理端再挂meta: { admin: true },守卫统一处理,页面里不再判断。 - 路由表按业务分组,用
// ---- 找吃的 ----这类注释分隔。 - 路径冲突时具体的写前面:
/journal/edit必须在/journal/:id之前,否则edit会被当成 id。
第 21 条 组件契约、样式、三态与新增页面清单
const props = defineProps({
modelValue: { type: [String, null], default: null },
max: { type: Number, default: 9 },
});
const emit = defineEmits(['update:modelValue', 'fav-changed']);
组件四条:props 必须写 type 和 default,可选值用 [A, null] 显式声明可空;emit 用 kebab-case,v-model 类用 update:xxx;组件不许直接调 API,数据从 props 进、事件从 emit 出;components/ 里不许 import views/ 的东西,会造成循环依赖。
样式三条:每个 SFC 必须 <style scoped>(实测 56 个组件/页面全部 scoped,0 个例外);只用 var(--xxx) 令牌,禁止硬编码色值 / 圆角 / 阴影;不许 !important,不许覆盖 Element Plus 内部深层类名,要改就用 :root 覆盖 --el-* 变量。
/* 不要 */
.title { color: #C98A1E; border-radius: 20px; box-shadow: 0 4px 12px rgba(0,0,0,.2); }
/* 要 */
.title { color: var(--brand); border-radius: var(--radius-sm); box-shadow: var(--shadow-soft); }
三态要齐,依赖接口的区域缺一个不算完成:
<div v-if="loading" class="skeleton"><!-- 骨架屏 --></div>
<div v-else-if="!list.length" class="empty">还没有记录,去打个卡吧</div>
<div v-else><!-- 正常内容 --></div>
空态文案要说下一步该干什么,不写"暂无数据"。
新增页面的检查清单,顺序不能倒,先接口后页面:
- 文件名
kebab-case-view.vue,放views/(管理端放views/admin/) -
router/index.js注册路由,组件用() => import(...)懒加载 - 路由
name用 kebab-case 且与文件名对应 - 需要登录挂
meta: { auth: true },管理端再挂meta: { admin: true } - 调接口只用
api/index.js的api_*方法,页面里没有import axios -
script setup六分区齐全且顺序正确 - 依赖接口的区域三态齐全,空态文案说下一步
-
<style scoped>+ 只用var(--xxx)令牌,无硬编码色值、无!important - 375px 宽度下不横向滚动
最常见的漏项是第 3 条和第 7 条:路由 name 随手写 page1,三态只写了 loading 和正常态。这两处漏了不会报错,联调和答辩演示时会暴露。
2.7 UI 视觉(第 22—24 条)
第 22 条 色板锁死,不许新增近似色
| 令牌 | 值 | 用在哪 |
|---|---|---|
--brand | #C98A1E | 强调·琥珀,主按钮、选中态 |
--brand-dark | #A8720F | 悬停文字、深色得分 |
--brand-light | #F5E8CC | 标签底 |
--ink | #3A2E1A | 主文字 |
--ink-2 | #B0A18A | 次要文字 |
--ink-3 | #8A6A3A | 三级文字、标签文字 |
--paper | #FFFDF5 | 卡面 |
--page | #FAF4E3 | 页底 |
--line | #F0E6CE | 描边、分隔线 |
--star | #D89A2A | 星级、分数 |
--good / --warn / --bad | #3ba76a / #f59e4c / #e5484d | 语义色 |
--heat-1…--heat-5 | 绿→黄→橙→红 | 热度五档 |
色板里的值只有 styles/global.css 一处定义。页面里出现 #C98A1E 的近似色(比如 #C98A1F、#CA8B1E),按违规处理。
第 23 条 圆角、阴影、字重、间距
| 项 | 规定 |
|---|---|
| 圆角 | 容器 var(--radius) 12px;控件与按钮 var(--radius-sm) 8px;禁止胶囊 999px |
| 阴影 | 只有两种:--shadow-soft(卡面弥散 0 8px 40px rgba(140,110,60,.06))、--shadow-float(浮层) |
| 字重 | 标题 700—800,正文 400;不用 500(系统字体的 500 与 400 几乎没差别) |
| 模块间距 | 32px |
| 卡内边距 | 18px |
| 字体 | 系统栈,不引入网络字体 |
第 24 条 三条视觉禁令与三视口基线
| 禁令 | 具体 | 反例 |
|---|---|---|
| 去胶囊 | 所有圆角控制在 6—8px,按钮/标签不超过 12px | border-radius: 999px 满屏胶囊 |
| 去封闭描边卡 | 用无框卡 + 极淡弥散阴影区分层次 | 每个卡片都 border: 1px solid #eee |
| 去重投影 | 全站只有一种卡片阴影,不叠多层 | 卡片 + 悬浮按钮 + 弹层全带重影 |
四项零硬编码:颜色、字号、圆角、间距都用令牌,页面里出现 #ff6b35 这类色值就算违规。grep -rn "#[0-9a-fA-F]\{6\}" frontend/src/views 一条命令就能扫出来。
三视口基线:
| 视口 | 宽度 | 用途 |
|---|---|---|
| 手机 | 375px | 主战场,所有页面在这宽度下不横向滚动 |
| 平板 | 768px | 两列布局 |
| 桌面 | 1440px | 管理端为主 |
2.8 测试(第 25—27 条)
第 25 条 两类测试,两种命名
| 类型 | 文件命名 | 数量 | 跑法 |
|---|---|---|---|
| 接口断言集 | api-integration.test.mjs / e2e-verify.test.mjs | 2 | node tests/api-integration.test.mjs |
| 功能走查 | walkthrough-r<编号>-<主题>.test.mjs | 19 | 同上 |
走查编号规则:r + 轮次号 + 主题,比如 walkthrough-r51b-captcha.test.mjs、walkthrough-r52-fzu-cas.test.mjs。编号只增不改,一轮一个文件。21 个测试文件不许删,新断言并进已有文件。
第 26 条 用例统一走 case_it,断言分四类
await case_it('IT-07', '重复点赞返回 409 + 40402', async () => {
const first = await post('/api/reviews/1/like', undefined, USER_TOKEN);
assert(first.status === 200, `首次点赞应 200,实际 ${first.status}`);
const second = await post('/api/reviews/1/like', undefined, USER_TOKEN);
assert(second.status === 409, `重复点赞应 409,实际 ${second.status}`);
assert(second.json.code === 40402, `业务码应 40402,实际 ${second.json.code}`);
});
| 类型 | 断言什么 | 例 |
|---|---|---|
| 正常 | 200 + code === 0 + data 结构对 | 点赞成功返回 {liked: true, likeCount: 1} |
| 缺参 | 422 + code === 40001 | 不发 content → 422 |
| 越权 | 401 / 403 + 对应码 | 无 token → 401;改别人评价 → 403 |
| 重复 | 409 + code === 40402,且带上已有记录 id | 同点位同日打卡 → 409 + data.checkinId |
每个写端点至少覆盖前三类,新端点写 4 条。断言要够狠:只断 status === 200 不算测过,至少断到关键字段的值。写 assert(data.likeCount === 1),不写 assert(data)。case_it 的 id 要能追到 AC 编号。
第 27 条 幂等与清理
/** 测试账号(随机后缀保证可重复执行) */
const TEST_USER = `it_${Date.now().toString(36)}`;
const TEST_PWD = 'it123456';
四条要求:账号随机化,每次跑用新用户名(it_ + 时间戳 base36);用例后自清理,造的评价删掉、改的配置改回;文件头写清前置条件(比如"前置:docker compose up -d --build 且服务健康");一处失败不影响全局,case_it 包住每个用例,最后统一汇总 pass / fail。
2.9 注释(第 28—30 条)
第 28 条 文件头写清三件事
/**
* MySQL 连接池(mysql2/promise,手写 SQL 为唯一真相源)
*/
必含三件事:这个文件是什么 → 为什么这么设计 → 有什么坑。带坑的注释才算好注释:
/**
* 数值常量表(编码口径冻结,测试期可调)
*/
decimalNumbers: true, // DECIMAL 返回 number(默认字符串会导致前端 .toFixed 崩溃)
第 29 条 函数必须有 JSDoc,业务函数挂设计依据
/**
* 执行单行查询,无结果返回 null
* @param {string} sql - 参数化 SQL
* @param {Array} params - 参数列表
* @returns {Promise<Object|null>}
*/
async function query_one(sql, params = []) { ... }
必填四项:开头一句说明、@param(每个参数带类型和含义)、@returns(含 null / undefined 的可能)、有副作用必须写(比如"会把 status 改回 0")。
每个业务函数都要挂设计依据,格式是括号里写模块名和约定名:
/**
* 点位列表:type 筛选 + 维度排序 + 可选分页
*/
答辩被问"这个规则哪来的",答得上来自哪条约定。没有依据的业务规则,先补约定再写代码。
第 30 条 TODO 与分区注释
TODO 格式固定为 TODO: 做什么,实测 30 处全部是这个格式。
五条纪律:只写 TODO:,不用 FIXME / XXX / HACK(统一才能 grep 统计);写清要做什么,不写"以后优化"这种空话;能写接口名就写接口名(TODO: 待 POST /api/checkin 落地后替换);TODO 是债,每个都要能回答"不做的后果是什么";做完必须删掉,不留"已完成"的 TODO。
长文件必须切段,格式是七连字符加空格:
/* ---------------- 认证 ---------------- */
/* ---------------- 常量 ---------------- */
1010 行的 routes/index.js 靠这个才读得下去。
2.10 Git 与提交(第 31—33 条)
第 31 条 分支
| 分支 | 用途 |
|---|---|
main | 可运行、可演示 |
feat/<简述> | 新功能,比如 feat/checkin-api |
fix/<简述> | 缺陷修复,比如 fix/captcha-expire |
第 32 条 提交信息
格式:
<类型>: <做了什么>
<为什么>(可选,一两句)
| 类型 | 用于 |
|---|---|
feat | 新功能 |
fix | 修缺陷 |
refactor | 重构(行为不变) |
docs | 只改文档 |
style | 格式(不改逻辑) |
test | 测试 |
chore | 构建、依赖、配置 |
也能写成 type(scope): 说明,scope 用模块名(checkin / pet / db / ui / deploy)。要求:一条提交只做一件事;标题不超 50 字;不提交 .env、node_modules、dist、日志、__pycache__。
反面例子就在自己仓库里。华为云文档仓 14 次提交,有 3 次叫「过期版本」「多余内容」「过期文件」,还有 4 次是裸中文。这种提交没法回溯,等于没写。
第 33 条 提交前四条命令,评审 12 条
# 1. 六个容器都健康(本项目唯一可靠的"能起"判据)
docker compose ps
curl -s http://localhost:3000/api/health # 期望 {"code":0,...,"data":{"status":"up"}}
# 2. 前端能构建
cd frontend && npm install && npm run build && cd ..
# 3. 接口断言全绿
node tests/api-integration.test.mjs
# 4. 没有遗留调试输出
grep -rn "console.log" backend/src frontend/src
注意:不要在宿主机直跑 node backend/src/index.js。config.js 的默认值是 127.0.0.1:3306,而 compose 里 mysql / redis / minio 都没有映射到宿主端口,直跑连不上库。后端只在容器里跑。
评审一人改、一人看,不搞集体过。改动类型和对应关注点:
| 改动类型 | 谁看 | 只看这几件事 |
|---|---|---|
| 后端接口 / service | C + A | 四段式齐不齐、错误码配对、复杂 SQL 是否落 service、事务有没有 release() |
| 数据库 DDL | D + C | IF NOT EXISTS、每列 COMMENT、索引是否覆盖热点查询、中文是否走 seed |
| 前端页面 | E / H + F | 三态齐全、只用令牌、是否 import axios、路由懒加载 |
| 视觉调整 | F | 是否新增了近似色、圆角是否破 6—8px、是否加了 !important |
| 测试用例 | G | 断言够不够狠、账号是否随机化、有没有自清理 |
| 部署与文档 | B | 命令能不能照抄跑通、数字口径与代码是否一致 |
评审 12 条,任一项不过就退回:
| # | 检查项 |
|---|---|
| 1 | 功能与任务描述一致,没有夹带无关改动 |
| 2 | 分层清晰:路由不写 SQL,service 不碰 req / res |
| 3 | 外部输入全部校验,枚举有白名单 |
| 4 | 错误码用既有九码表,无自造码、无裸字符串抛出 |
| 5 | 多表写有事务;失败可回滚;幂等考虑到位 |
| 6 | SQL 全部参数化,无 SELECT * |
| 7 | 无明文密钥 / 口令 / token;日志无敏感信息 |
| 8 | 写操作有成功 / 失败反馈;危险操作有二次确认 |
| 9 | 有对应测试,且能跑过 |
| 10 | 三态齐备,375px 视口不破版 |
| 11 | 命名符合规范,无临时文件、无 console.log 残留 |
| 12 | 文档同步:接口 / 字段 / 图表 / README 已更新 |
三条评审纪律:先看 diff 的范围再看内容,一次提交跨 3 个以上模块先打回拆开;提问题要给替代写法,"这里不对"不算评审,"这里应改成 throw err.conflict(...)"才算;不评论风格偏好,有分歧回到条款,条款没写的先补条款。
2.11 安全(第 34—39 条)
第 34 条 SQL 只能是参数化的
query('SELECT * FROM user WHERE id = ?', [id])
排序字段这类没法参数化的地方,必须走白名单(第 16 条的 POINT_SORTS 就是例子)。
第 35 条 密码只存 bcrypt hash,cost 固定 10
bcrypt.hash(password, 10);
登录时用 bcrypt.compare,不许自己写加盐算法。
第 36 条 JWT 密钥来自环境变量,有效期 7 天
jwt_secret 有开发默认值,生产必须覆盖。
第 37 条 敏感字段不返回前端
password、ai_config.apiKey 永远不出现在响应里;ai_config.apiKey 落库前加密(utils/secret-box.js)。
第 38 条 上传必校验
大小走 UPLOAD_MAX_BYTES,类型走图片白名单,重复图用 dHash 判重(DHASH_HAMMING 阈值 5)。
第 39 条 调试通道生产必须关
config.debug = process.env.DEBUG === 'true';
只在它为 true 时才下发验证码明文 debugCode。
2.12 性能(第 40 条)
第 40 条 列表、聚合、缓存与超时
| 场景 | 要求 |
|---|---|
| 列表查询 | 必须分页或 LIMIT;不许 SELECT * 全表捞 |
| 聚合计算 | 读 review_summary 摘要表,不现场 AVG() |
| 维度配置 | 内存缓存 60 秒(DIM_CACHE_TTL_MS) |
| 前端路由 | 全部懒加载(45 页) |
| 图片 | 上传前前端压缩(utils/compress.js);展示用 objectKey 走 CDN/Nginx,不 base64 塞 JSON |
| 请求超时 | axios 15s;AI 60s(AI_TIMEOUT_MS);抠图 30s;CAS 10s |
| 静态资源 | Nginx 直连 MinIO(生产),/img/* 走 Node 代理仅限本地开发 |
| 缓存头 | /img/* 已设 Cache-Control: public, max-age=86400 |
2.13 四道门禁:规范怎么强制
| 门禁 | 卡什么 | 怎么卡 | 不过怎么办 |
|---|---|---|---|
| 提交信息 | 提交说明是否符合 type(scope): 说明 | 合并前人工核对;git log --oneline 一眼能看出裸中文 | 重写提交说明 |
| PR 检查项 | 分层是否越界、响应是否裸写、是否补了测试 | PR 模板 4 个勾选项,评审者逐项勾 | 打回,不进 main |
| 字段回源 | AI 或人提到的字段 / 端点 / 依赖是否存在 | grep 回 init.sql、routes/index.js、package.json | 查不到即驳回 |
| 硬编码扫描 | 页面里的色值、魔法数、console.log、localStorage 残留 | grep -rn "#[0-9a-fA-F]\{6\}" frontend/src/views 一类命令 | 改回令牌 / 删除 |
格式类条款不单独编号,统一交给工具,评审时不吵空格。现行七条:缩进 4 空格(后端 / 前端 / 测试统一);JS 用单引号、HTML 属性用双引号、模板字符串用反引号;分号必须写;行宽建议 120 列;多行结构用 es5 尾逗号;if / for / while 必须带花括号,即使单行;相等判断一律 === / !==,== null 判空除外。仓库现状是 4 空格,改成 2 会让全仓 diff 爆炸。
还有一笔欠账要说清楚:本期仓库里没有 ESLint / Prettier / husky,风格只能靠人工 review 和 grep 拦,没有自动 --fix。这笔挂的是 P0,β 第一件事就是补 eslint.config.js / .prettierrc / commitlint.config.js。我们不写"我们有 lint",因为确实没有。
2.14 有意识偏离备案
| 偏离 | 为什么不照原文 | 什么时候还 |
|---|---|---|
| 全项目 snake_case(阿里默认 camelCase) | 数据库列已是 snake_case,全链路一个风格可省掉映射层 | 不回退;这是本项目约定 |
| Vue 组件用 kebab-case(官方推荐 PascalCase) | 仓库里 11 个组件已全是 kebab-case | 不回退 |
check_in.photos 用 JSON 列,不建从表 | 一期只做打卡主链,从表会增加一张表和一组事务 | β 评估是否拆 check_in_image |
| 无 ESLint / Prettier / husky | 全量格式化会产生上万行无意义 diff,淹没真实改动 | β 第一件事 |
| 统一 logger(已落地) | 25 处业务打印已换成 log.info/warn/error,console.log 只在 logger 内部 1 处 | 已完成 |
| 15 个页面业务数据尚未全部落库 | 随 5 张新表 + 13 个端点逐页替换,本周目标清零 | 本周 K1 验收 |
规范清单实际是 43 款:35 款已落地,3 款部分落地,5 条待落地。上面展开的 40 条是按日常使用频率挑的,已落地和待落地的都在里面;没展开的 3 条多是工具链和遗留清理,等排到再处理。条款与代码冲突时,以代码为准,然后回改文档;反过来做,规范第一天就没人遵守。
三、AI 工具:产品侧 AI 助手 + 团队侧 AI 技术员
作业要求各小组引入 AI 技术员(设计师、程序员、测试员等),并说明怎么驱使 AI 帮团队干活。这一节想写的是具体怎么用、怎么不出事,不是"我们用了 AI"这句结论。
AI 工具分两边。产品侧的 AI 助手(对话 / 推荐)本周从 0 接通:ai_provider / ai_model / ai_platform_model / ai_conversation 四张表、/ai/ask 与历史端点、/ai 页与后台供应商配置;模型不可用时回 degraded: true,不抛业务码;密钥走 AES-256-GCM 密文入库。接通与降级验收挂在 W-08,不另开任务。团队侧才是 A1—A8 的日常留痕,下面讲怎么派活。
3.1 八个 AI 技术员,按服务岗编号 A1—A8
| 编号 | 扮演 | 服务岗 | 一周主要干什么 | 产出形态 |
|---|---|---|---|---|
| A1 | AI 设计师(需求侧) | A 交付 owner | AC 对照表骨架、缺陷三层归因初稿、文档骨架 | 表格 / 清单 |
| A2 | AI 架构师(契约侧) | B 架构 + 部署 | 环境变量模板、端点契约清单初稿、镜像与 CI 片段 | 模板 / 片段 |
| A3 | AI 程序员(后端) | C 后端实现 | 按 DDL 生成 service 骨架、SQL 写法校对、重复代码扫描 | 函数骨架 / 差异表 |
| A4 | AI 程序员(数据) | D 数据 | 枚举字典、迁移脚本草稿、索引建议 | 对照表 / 脚本 |
| A5 | AI 程序员(前端) | E 前端功能 | 改动点清单(localStorage 位置、mock 残留)、接线骨架 | 位置表 / 骨架 |
| A6 | AI 设计师(视觉侧) | F UI 整合 | 找魔法色值、比对令牌表、生成对照截图清单 | 硬编码清单 |
| A7 | AI 测试员 | G 测试功能 | 用例草案、边界值枚举、断言骨架 | 用例集 |
| A8 | AI 测试员(验收侧) | H 测试验收 UI | 术语扫描、视口回归脚本、三视口基线草案 | 扫描报告 / 脚本 |
提示词编号说明一下:A1—A8 每岗有 3 条基线提示词(P-A1—P-A3、P-B1—P-B3……P-H1—P-H3),每条带预期输出、常见坑和验收标准。每日记录里 P-A4 往后的编号,是当天照同一模板临时加的派活序号,不是另外存了一份提示词。用的时候以五条纪律加对应基线模板为准。
3.2 派活只派能被 grep 验证的
一条派活单写四句话:
- 做什么——具体到"扫出全部硬编码色值",别写"优化一下样式"。
- 在哪做——给文件路径和表名:
init.sql、frontend/src/styles/global.css。 - 什么算完成——给一条能跑的检查命令,或者一个能数的口径,比如"列出 37 处,每处带行号"。
- 不许做什么——点明禁区,比如"不要给建议,只给位置"。
| 反例(这么派会翻车) | 正例(这么派能落地) |
|---|---|
| "帮我写一下打卡接口" | "照 check_in 表生成 service 函数签名,只要签名,不要实现,字段必须与 DDL 完全一致" |
| "检查一下代码质量" | "扫 frontend/src 里所有 #rrggbb 形式的硬编码色值,输出 文件:行号:色值 三列,不要给建议" |
| "帮我写测试" | "给 POST /me/diet-plan 写 4 条用例骨架(正常 / 缺参 / 越权 / 重复),断言字段写清楚预期值,不要写'正常显示'" |
| "审查一下这个 PR" | "列出本 PR 里所有引用数据库字段的地方,逐个回 init.sql 核对字段是否存在,存在 / 不存在两列输出" |
3.3 三条红线:这些活不派给 AI
| 不派 | 为什么 | 改由谁做 |
|---|---|---|
| 完成度判定(这条 AC 算不算达成) | AI 会把"有页面"当"达成" | 人按可数判据判 |
| 方案选型(该用 A 表还是 B 表) | AI 答得太像真的,但业务取舍要人拍板 | 人拍板,AI 只做对比 |
| 写"它什么情况下会报错" | AI 写的预期结果多是"正常显示"这类废话 | 人写预期值,AI 出骨架 |
这三条其实是一回事:AI 擅长把已知的东西铺开,判断对不对它做不了。
3.4 五条使用纪律
- 人审:AI 产出的每一行代码,提交的人得能解释;答不上来就作废。
- 标注:AI 参与多的提交,在 commit 正文注明
Co-authored-by: AI-A3 C。 - 核事实:AI 提到的接口、字段、依赖、配置项都回仓库核对,查不到就是幻觉,直接驳回。
- 补测试:AI 生成的接口和组件,由提交人补断言或用例,只交代码不交测试算没完成。
- 不投喂:真实密钥、口令、用户数据不进 prompt。
另外还有三条:
- 不无脑重构。AI 说要重构的,先看 diff 多大——大 diff 会把真实改动淹掉。
- AI 不决定架构。表结构、端点契约、模块划分由人拍板,AI 只出对比方案。
- 删除类命令要先说影响面。
rm/down -v/DROP之前,先讲清它删的是哪一块盘。
3.5 留痕:每天记录 + 提交信息 + Issue
| 留痕位 | 放哪 | 记什么 |
|---|---|---|
| 当日 AI 工作 | 当天的工作记录 | A1—A8 各自的任务、产出、采纳情况、人工改动 |
| 提交信息 | commit 正文 | Co-authored-by: AI-A3 C |
| 产出与结果 | Issue / 测试报告 | AI 产出与人工改动的差异 |
还有一条硬规矩:说不出人工改了什么,就说明没验,不算用过 AI。这条也顺带管住了"随手贴 AI 输出就提交"。
AI 技术员带来的是产能,判断力它给不了。一周的铺开工作能从 3 天压到 1 天,但"这份表对不对""这个 AC 算不算达成",还是得人来回答。
四、39 项冲刺 Backlog
先把账说清:39 项任务,编号 W-01—W-39,每项一个 Issue(#W-xx)。全部从 0 做起,做完一项关一项,没有"已完成待修正"这种状态。
计点的共 22 项,合计 64 点。优先级分三档:P0 = 本期必须闭环(W-02 / W-04 / W-06—W-13 / W-16—W-20 / W-23—W-35 / W-37 / W-39);P1 = 本期闭环但有降级方案(W-01 / W-03 / W-05 / W-14 / W-15 / W-21 / W-22 / W-36 / W-38);P2 进 β 池,不占本期点数。
4.1 数据库 · 78 张表(27 计点 + 51 照图施工,主责 D,协 B)
| 编号 | 任务 | 交付物 | 负责/协作 | 点 | 安排 | 验收判据 | Issue |
|---|---|---|---|---|---|---|---|
| W-01 | 22 张表建表(表名 / 列名 / 类型 / 默认值 / 注释) | DDL + 注释 | D / B | — | D2 | 22 表可建,字段与设计一致 | #W-01 |
| W-02 | 新建 5 张表:check_in / dietary_profile / achievement / user_achievement / pet(含 uk_check_in_dup) | DDL + 迁移脚本 | D / B | 1 | D2 | 5 表可建,唯一键齐,K0 前定稿 | #W-02 |
| W-03 | 27 张表字段注释 + 枚举字典(打卡来源/成就类型/宠物阶段) | 注释 + 字典 | D | — | D3 | 每表每列有 COMMENT,三类枚举有字典 | #W-03 |
| W-04 | 迁移脚本 sql/migration/00x_*.sql,全 IF NOT EXISTS | 迁移脚本 | D | — | D3 | 空库连跑两遍不报错 | #W-04 |
| W-05 | 27 张表外键 / 索引建齐(对齐 106 外键中属本周的部分) | 外键 / 索引清单 | D / B | — | D3 | 本周外键逐条落地 | #W-05 |
| W-06 | 演示数据:打卡 30 条(跨 7 天含重复日)/ 成就 12 条 / 宠物初始态 / 待办 5 条 | 种子 SQL | D | 2 | D4 | 连跑两遍结果一致 | #W-06 |
| W-07 | 数据自检六条 + 冻结快照 + 线上数据核对 | 六条结果 + 快照 | D | — | D5—D7 | 6/6 通过,快照带 commit SHA | #W-07 |
4.2 后端 · 127 个端点(主责 C,协 G)
| 编号 | 任务 | 交付物 | 负责/协作 | 点 | 安排 | 验收判据 | Issue |
|---|---|---|---|---|---|---|---|
| W-08 | 114 个端点实现(含 AI 助手对话 / 推荐 / 降级) | 路由 + service + 两态截图 | C / G | — | D2—D7 | 114 端点全通;AI 停供应商回 degraded: true,不抛业务码;正常 / 降级两态截图归档 | #W-08 |
| W-09 | 打卡 4 端点(POST /checkin 409 幂等 + data.checkinId / GET /checkin/calendar / GET /checkin/stats / DELETE /checkin/:id) | 路由 + service + 断言 | C / G | 5 | D2—D3 | 四端点全通,16 条断言跑绿 | #W-09 |
| W-10 | 成就 3 端点(列表 / 进度 / 领取) | 路由 + service | C / G | 2 | D3 | 进度 = 当前/目标,目标存绝对数,12 条断言绿 | #W-10 |
| W-11 | 宠物 3 端点(状态 / 喂食 / 社区),喂食阈值 3 天 | 路由 + 状态机 | C / G | 3 | D4 | >= 3 边界正确,三态矩阵断言绿 | #W-11 |
| W-12 | 饮食档案 2 端点 GET / PUT /me/diet-profile | 路由 + service | C / G | 1 | D2 | 与档案页同一字段口径,四类断言齐 | #W-12 |
| W-13 | 饮食计划 1 端点 POST /me/diet-plan(1/3/7 天) | 路由 + 规则引擎 | C / G | 3 | D5 | 三档可生成,按忌口/食堂/评分,无档案有降级 | #W-13 |
| W-14 | 14 service + 7 utils 分层约束 | PR 检查项 | C | — | D2—D7 | 新代码无 routes 越界,service 不碰 req/res | #W-14 |
| W-15 | 接口文档 diff:13 条新端点补进文档,114 → 127 | diff 表 | C | — | D7 | 13 条逐条补入,总数 127 | #W-15 |
4.3 前端 · 45 个页面(主责 E)
| 编号 | 任务 | 交付物 | 负责/协作 | 点 | 安排 | 验收判据 | Issue |
|---|---|---|---|---|---|---|---|
| W-16 | 39 个业务页里另外 31 页实现 | 页面 + 三态 | E | — | D2—D7 | 31 页关键路径可打开,无白屏 | #W-16 |
| W-17 | 打卡页接线,替换 localStorage,日历聚合 + 今日状态 | 页面 + 去本地存储 | E | 3 | D2 | 8 处 localStorage 清零,F5 数据还在,三态齐 | #W-17 |
| W-18 | 足迹页 + 手账详情 + me-view 统计卡片 | 三处统一走 /checkin/stats | E | 3 | D3 | 三处天数一致,手账 F5 不丢 id | #W-18 |
| W-19 | 成就页 / 宠物页 / 宠物社区去 mock | 三页接真接口 | E | 3 | D4 | 无 mockData,三态齐 | #W-19 |
| W-20 | 档案页 + 三处统一读(档案/打卡/计划共用一份 profile) | 公共 store | E | 2 | D5 | 改一处三处同步 | #W-20 |
| W-21 | 另 7 页登记入下阶段 | 7 页清单 + 理由 | E / A | — | D3 | 每页有"为什么这周不做"的具体原因 | #W-21 |
4.4 UI 一致性 · 39 个业务页(主责 F 落地 + H 验收)
| 编号 | 任务 | 交付物 | 负责/协作 | 点 | 安排 | 验收判据 | Issue |
|---|---|---|---|---|---|---|---|
| W-22 | 视觉令牌 + 组件库 + 三态样式清点 | 令牌清单 | F | — | D2 | 令牌使用率有数,缺口页有名单 | #W-22 |
| W-23 | 15 个缺口页接入令牌(颜色/字号/圆角/间距四项零硬编码) | 逐页替换 | F | 6 | D2—D5 | 15/15 清零,grep 无色值 | #W-23 |
| W-24 | 三视口基线定义(375/768/1440)+ 术语表 | 基线图 + 术语表 | H | 2 | D2 | 三套基线齐,D2 18:00 冻结 | #W-24 |
| W-25 | 7 个关键页 × 3 视口走查 | 首轮偏差清单 | H | 2 | D4 | 7×3 全覆盖,每条有编号/期望/实际 | #W-25 |
| W-26 | 偏差清单定稿 + 整改后复验 | 定稿清单 + 复验记录 | H / F | 3 | D6 | 每条写到"改成什么",复验闭环 | #W-26 |
| W-27 | 最终基线截图 + UI 验收结论(签字) | 21 张截图 + 签字 | H | — | D7 | 21 张归档,结论三档 | #W-27 |
4.5 测试 · 25 条 AC(主责 G)
| 编号 | 任务 | 交付物 | 负责/协作 | 点 | 安排 | 验收判据 | Issue |
|---|---|---|---|---|---|---|---|
| W-28 | 21 个测试文件编写 | 测试文件 | G | — | D2—D7 | 21 个文件可跑,新断言并入 | #W-28 |
| W-29 | 25 条 AC → 40 条用例映射表 | 映射表 | G / A | 1 | D2 | 25 条全覆盖,每条 id 可追 AC | #W-29 |
| W-30 | 13 个新端点四类断言(正常/缺参/越权/重复) | 52 条断言 | G / C | 1 | D2—D3 | 13×4 全绿 | #W-30 |
| W-31 | 边界异常 + AC 逐条走查(AC01—AC25) | 走查表 + 缺陷归因 | G | 4 | D3—D6 | 25 条逐条走查,缺陷有三层归因 | #W-31 |
| W-32 | 全量回归 + 大扫荡(含 31 个未改页面) | 回归报告 | G | 2 | D6 | 一条命令跑绿,45 页手工点检,先自证会红 | #W-32 |
| W-33 | 25 行验收报告(AC / 判据 / 结果 / 证据链接) | 验收报告 | G | — | D7 | 25 行齐,证据链接可打开 | #W-33 |
4.6 部署 · 文档 · 验收(主责 B,协 A)
| 编号 | 任务 | 交付物 | 负责/协作 | 点 | 安排 | 验收判据 | Issue |
|---|---|---|---|---|---|---|---|
| W-34 | 6 容器编排确认(MySQL/Redis/MinIO/matting/backend/frontend) | 冷启动记录 | B | — | D2 | 六容器冷启动无隐式依赖 | #W-34 |
| W-35 | 契约冻结清单——114 + 13 = 127 端点逐条列签名 | 契约清单 | B | — | D2 | 127 条签名齐,D2 18:00 冻结 | #W-35 |
| W-36 | 三份文档口径对齐(25 AC / 78 表 / 106 外键)+ 78→27 对照表 | 对照表 | B / A | 2 | D3 | 三套数同页可对,计点 27 / 交付 78 两列 | #W-36 |
| W-37 | 镜像构建 + 上云 + 三条校验(首页非空/主链点通/重启不丢) | 公网 URL + 三截图 | B | 3 | D5—D6 | 三条全过 | #W-37 |
| W-38 | 线上巡检 + 回滚预案(上一版镜像留着) | 巡检表 + 回滚三步 | B | — | D7 | 回滚不含 -v,上一版镜像保留 | #W-38 |
| W-39 | 25 条 AC 判据 + 答辩材料 + 冲刺总结 | 判据表 + PPT + 总结 | A | 10 | D5—D7 | 25 条有判定,PPT 15 页,11 篇随笔齐 | #W-39 |
4.7 优先级
| 优先级 | 编号 | 说明 |
|---|---|---|
| P0 | W-02、W-04、W-06—W-13、W-16—W-20、W-23—W-35、W-37、W-39 | 本期必须闭环;任一不过,K0—K3 至少有一个卡口失守 |
| P1 | W-01、W-03、W-05、W-14、W-15、W-21、W-22、W-36、W-38 | 本期闭环,但有降级方案;卡口日可临时让路 |
| P2 | β 池 | 不在本期 39 项内,不占 64 点 |
写 Backlog 时我们立了三条规矩:
- 编号唯一:
W-xx冲刺内、T-xx修正类、β-xx下阶段池,三套编号不许重号。 - 一项一主:负责岗只填一个编码,协作栏可以多人。
- 加一项就减一项,只置换不叠加——否则总量失控,燃尽图必崩。
五、七天排期
| 天 | 日期 | 各岗主要交付 | 卡口(当天必须完成) |
|---|---|---|---|
| D1 | 2026-10-23(周五) | 冲刺启动会:范围冻结、代码规范发布、39 项任务分配、AI 技术员部署;不写代码、不结算点数 | 任务分配与 Issue 建齐;K0 定于 D2 18:00 |
| D2 | 2026-10-24(周六) | D:5 表 DDL;H:三视口基线;G:AC 映射 + 断言;C:打卡 3 端点 + 档案端点 + AI 助手降级复现;E:打卡页;F:令牌首日 | K0 契约冻结 18:00:5 表 DDL 定稿 + 127 端点签名齐 |
| D3 | 2026-10-25(周日) | C:打卡收口 + 成就 3 端点;E:足迹/手账/me-view;B:口径对照;F:令牌;G:AC 走查首日 | 成就进度口径定死(当前/目标,目标存绝对数) |
| D4 | 2026-10-26(周一) | D:种子数据;C:宠物 3 端点;E:三页去 mock;H:7 页×3 视口走查;F:令牌 | 宠物三态矩阵断言通过;C↔E 交叉互测六条有结论 |
| D5 | 2026-10-27(周二) | C:饮食计划端点;E:档案统一读;G:AC 走查;B:上云;F:令牌收尾;A:AC 判据 | K1 数据不丢 18:00:7 页 + 手账详情逐个 F5,数据还在 |
| D6 | 2026-10-28(周三) | H:偏差清单定稿;G:全量回归 + 走查收尾 + AI 两态截图归档;B:上云第三校验;A:答辩材料 | K2 双签冻结 18:00:代码 + 数据双冻,打 tag v1.0-alpha |
| D7 | 2026-10-29(周四) | A:25 条 AC 终判 + 答辩 + 总结;B:线上巡检;H:UI 验收签字 | K3 公网四链路 12:00:登录/浏览/打卡/AI 全通 |
排期上有三点考虑:
- 顺序串行,工期并行。后段岗位的前半段活(规范、骨架、设计)D2 就开工,不等前段交完。
- 卡口都写日期。写不出日期,就等于没有卡口。
- 冻结前一天留缓冲。D6 冻结,D5 之前就得有能演示的版本,首次联调不能放到 D6。
D1 记 0 点,是因为那天只干三件事:把范围定死、把规范发下去、把 39 项任务分到人。不开工也不结算,免得会开完了、活还没开始,燃尽图先掉 6 点,看着像有进度。原计划放在 D1 的建表、基线、用例映射并进了 D2,打卡首段拆到 D2—D3,W-36 口径对照从 D2 挪到 D3;D2 由 11 点调成 13 点,D3 由 9 点调成 12 点,D6 由 10 点调成 11 点,总量还是 64。
六、点数与产能核算
1 点 ≈ 0.5 人日
总产能 = 8 人 × 7 天 = 56 人日 ≈ 112 点
本周投入 = 64 点 = 32 人日(占名义产能 57%)
各岗点数 = 该岗全部 Backlog 项点数之和
校验:Σ 各岗点数 = 总点数(差额必须为 0)
点数是什么?它是任务的工作量估算(story point),只用来排期、按岗分配、画燃尽图,以及每天结算关了几个点。组内贡献度和个人评分另有口径,点数只能作为参考,不能直接当贡献度百分比。
6.1 按岗
| 岗位 | 点数 | 计算式(列出该岗的 Backlog 编号) | 占比 |
|---|---|---|---|
| A | 10 | W-39(10) | 15.6% |
| B | 5 | W-36(2) + W-37(3) | 7.8% |
| C | 14 | W-09(5) + W-10(2) + W-11(3) + W-12(1) + W-13(3) | 21.9% |
| D | 3 | W-02(1) + W-06(2) | 4.7% |
| E | 11 | W-17(3) + W-18(3) + W-19(3) + W-20(2) | 17.2% |
| F | 6 | W-23(6) | 9.4% |
| G | 8 | W-29(1) + W-30(1) + W-31(4) + W-32(2) | 12.5% |
| H | 7 | W-24(2) + W-25(2) + W-26(3) | 10.9% |
| 合计 | 64 | — | 100% |
均衡性也看过:C 岗 7 人日最重(13 个端点加每天回归),D 岗 1.5 人日最轻。这个不均是故意留的。D 的 5 张表字段、索引、外键在设计阶段已经一表一卡定稿,D 的活是照图施工、写注释、出脚本,不用重新设计。D 岗 7 人日上限里有 5.5 人日余量,留着兜底,DDL 反复改或者迁移脚本跑不完时从这里调人,不额外接功能开发。
6.2 按天
| 天 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 合计 |
|---|---|---|---|---|---|---|---|---|
| 当日关闭点数 | 0 | 13 | 12 | 12 | 12 | 11 | 4 | 64 |
| 计划剩余 | 64 | 51 | 39 | 27 | 15 | 4 | 0 | — |
当天关掉的每一项都能追到上面的 Backlog:
| 天 | 关闭的任务 | 点 |
|---|---|---|
| D1 | 启动会,不关闭点数 | 0 |
| D2 | W-02 建表 1 · W-24 视口基线 2 · W-29 用例映射 1 · W-09 打卡首段 3 · W-17 打卡页 3 · W-12 档案端点 1 · W-30 断言 1 · W-23 令牌首日 1 | 13 |
| D3 | W-09 余量 2 · W-10 成就端点 2 · W-18 足迹页 3 · W-36 口径对齐 2 · W-23 令牌 2 · W-31 走查首日 1 | 12 |
| D4 | W-06 种子数据 2 · W-11 宠物端点 3 · W-19 三页去 mock 3 · W-25 视口走查 2 · W-23 令牌 2 | 12 |
| D5 | W-13 计划端点 3 · W-20 档案统一读 2 · W-31 走查 2 · W-37 上云 2 · W-23 令牌 1 · W-39 判据 2 | 12 |
| D6 | W-26 偏差清单 3 · W-32 全量回归 2 · W-37 上云 1 · W-31 走查收尾 1 · W-39 材料 4 | 11 |
| D7 | W-39 答辩与总结 4 | 4 |
按岗 10+5+14+3+11+6+8+7 = 64,按天 0+13+12+12+12+11+4 = 64,两边都对上了。✓
6.3 为什么 1 点 = 0.5 人日
8 人 × 7 天 = 56 人日。本周投入 64 点,折 32 人日,只用掉 57% 的产能,还剩 24 人日。留出来的余量看着多,其实是被挤出来的:
- 差距核对里点名的 **9 个"无库表核心类"**(有页面、无库表、无核心类承接)会反复卡人;
- 每天站会 + 日志 + 证据收集,8 个人各 30 分钟 ≈ 每天 4 人时;
- D1 启动会、答辩材料、PPT、评审表都要占时间,且都挤在头尾两天。
真要按 100% 产能排,一定崩。
七、燃尽计划
| 天 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 |
|---|---|---|---|---|---|---|---|---|
| 计划剩余 | 64 | 64 | 51 | 39 | 27 | 15 | 4 | 0 |
| 实际剩余 | 64 | 64 |

D1 当天实际剩余还是 64。启动会没关点,所以数字没动——我们刻意不把开会折算成点数,免得燃尽图一开始就掉 6 点。从 D2 起,每天站会只结算昨天说好的点数和关掉的点数,少 1 点第二天补上,偏差不跨天累计。
理想线从 64 直线降到 0,实际线每天补一个点。D1 是平的,这在启动会那天正常;要是 D2—D3 还平着走,才说明任务粒度切得太粗。
八、五个风险与应对
| 编号 | 风险 | 概率 | 影响 | 处置(谁在什么时候做) |
|---|---|---|---|---|
| RK-1 | C 岗单人单日过载(D2 一天 13 点里有 4 点是 C 的) | 高 | 整条打卡链断 | W-09 拆 D2/D3 两天;D2 先给前端一个"能调的空壳",E 不空等 |
| RK-2 | 5 张表 DDL 改一次,前后端一起返工 | 中 | K0 失守 | D2 18:00 前必须冻结;冻结后改字段要 A 签字并通知 C/E/G |
| RK-3 | 二期 51 张表落库把 D 拖住 | 中 | 迁移脚本跑不完 | 六域迁移脚本直接复用,不重新设计;跑不通就退回 27 张主链,二期脚本降级为"可执行未执行"并写明 |
| RK-4 | UI 令牌替换制造大 diff,淹没真实改动 | 高 | 回归看不出问题 | 令牌改动单独提交,不与功能改动混在同一个 commit |
| RK-5 | D2—D7 日志凑不齐四件证据 | 高 | 每天扣分 | 证据清单进每天站会最后 1 分钟;G 岗兼"证据管家",每天 17:30 点名 |
规范、任务、点数和卡口从这篇发布起就不再改了。中途真需要变,在当天的工作记录里写清改了什么、为什么,不悄悄改表。
相关推荐
业绩冲刺销售计划现代商务ppt模板.rar
象征冲刺的职场男士预备起跑姿势商务大图封面,适合于冲销售业绩目标工作计划,销售冲刺工作计划的ppt模板。
leetcode 2024春招冲刺百题计划——动态规划+数论
不打算充钱第一次用java写,有点不熟悉。。。还是用c+stl爽。没写完,不定期更新。会尽快的,先发出来吧,万一有人需要呢先更数论和动态规划。
蓝桥杯赛前三天冲刺指南:心态调整、三日计划与实战技巧
算法竞赛是检验编程与逻辑思维能力的有效方式,其核心在于将基础数据结构与算法知识应用于解决复杂问题。理解搜索、动态规划、贪心等核心算法原理,是提升解题能力的关键。这些技术不仅能锻炼思维,更在软件开发、人工智能等领域有广泛应用。针对蓝桥杯这类赛事,高效的赛前冲刺策略至关重要。本文聚焦于赛前最后三天的黄金窗口,提供了一套从全真模拟、精准复习到状态调整的完整计划,并融入了**动态规划**和**代码模板**等高频考点与实用技巧,旨在帮助选手将已有知识体系转化为稳定的赛场得分能力,实现最佳临场发挥。
迭代=冲刺?
本文作者:特邀敏捷教练梁堃前段时间在和一个小伙伴聊天的时候,他问了阿甲这样一个问题:Scrum guide中把固定时间盒周期称为sprint(冲刺),但是为什么大家在平时都愿意把它称为迭代(iteration)呢?从阿甲接触敏捷至今,对于sprint和iteration这两个词也是经常混着用,但关于这两个词之间的关系和区别,还真的没有思考过。正好阿甲也想趁这个机会和小伙伴们一起把这个概念理清楚,所...
Sprint-Planner:用于生成示例冲刺计划的小型 Google App 脚本
冲刺计划者 用于生成示例冲刺计划的小型 Google App 脚本


