知食份子队——代码规范、冲刺任务与计划

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

大福食刻 α 冲刺:团队代码规范、冲刺任务与计划

项内容
这个作业属于哪个课程福州大学 · 软件工程 · 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不改视觉规范
FUI 整合:视觉规范落到最终版郑春天6不改业务逻辑
GQA-1 测试功能:用例、断言、AC 走查杨雨薇8不改业务代码
HQA-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_casecreate_app query_one assert_str list_canteens load_json rate_limit recalc_all
变量snake_casecanteen_options dish_options success_open lit_text form pool
类PascalCaseApiError
编译期常量、配置表UPPER_SNAKE_CASEPOINT_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_login is_admin fzu_verified enabled。
  • 函数名动词开头:list_* 查列表、get_* 取单个、count_* 数数量、create_* 建、update_* 改、delete_* / remove_* 删、assert_* 校验、parse_* 转换、recalc_* 重算。
  • 禁止缩写到看不懂:pts c s sm 只能在 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
后端模块<域>.<层>.jsreview.service.js、aggregation.service.js、admin.service.js
后端工具单个名词或动名组合,不带 .serviceapi-error.js、validate.js、dfa.js、dhash.js、fzu-cas.js
Vue 页面<名>-view.vuecheckin-view.vue、canteen-detail-view.vue、ai-view.vue
Vue 组件<名>.vue,kebab-casereview-card.vue、star-rating.vue、brand-logo.vue
路由 namekebab-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.mjsapi-integration.test.mjs、walkthrough-r52-fzu-cas.test.mjs
SQLinit.sql;迁移 migration/<日期>-<说明>.sqlmigration/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_caseavatar_object_key created_at rating_avg review_count
前端表单 → 请求体camelCasesubjectType subjectId dimensionScores imageKeys isAnonymous avatarObjectKey listType

实测:routes/index.js 里请求体取值 camelCase 16 处、snake_case 0 处;响应字段 snake_case 100%,没有一处驼峰转换。

四条规则:

  1. 响应字段永不转驼峰,前端拿到的就是数据库列名,中间不加映射层。
  2. 新增写接口的请求体沿用 camelCase,服务端在路由入口做一次映射,services/ 里只认 snake 列名。
  3. 前端本地表单跟随请求体用 camelCase,不再引入新的 snake 字段。
  4. 同一资源不许两种写法并存,撞见 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.jsExpress 装配:中间件顺序、静态代理、路由挂载、两个兜底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); }
});

四段必须齐:

  1. /** ... */ 块注释:干什么、对应设计章节(打卡模块的签到约定)、参数与返回。
  2. 中间件链:auth_required() / write_required() / admin_required() / rate_limit(...),写在 handler 之前。
  3. try { ... } catch (e) { next(e); },catch 体原样一行 next(e),不在这里拼错误响应。
  4. 出口只用 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、子查询、聚合、排序表达式必须落 servicecontent.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业务码默认文案什么时候用
—(成功)2000ok一切正常返回
err.invalid()42240001参数不合法参数校验失败、格式不对
err.unauthorized()40140101请先登录没 token / token 失效
err.forbidden()40340301无权操作角色不够、非本人、非管理员
err.banned()40340302账号当前状态不可进行该操作禁言 / 封禁
err.not_found()40440401内容不存在资源不存在、路由未命中
err.conflict()40940402操作冲突唯一键冲突、重复提交、状态非法迁移
err.too_many()42942901手速太快啦,休息一下触发限流
—(未知异常)50050001服务器开小差了,请稍后重试兜底,console.error 留痕
err.unavailable()50350301服务暂不可用,请稍后再试依赖(AI / 抠图 / CAS)不可用

九个业务码只有一个来源,就是 backend/src/utils/api-error.js。代码里没有的码,文档里也不会写。AI 降级不抛业务码,而是在成功体里回 degraded: true,降级算可用状态,不当错误处理。

四条纪律:

  1. 业务代码里不写裸 res.status(4xx).json(...),合法例外只有 app.js 的 /img/* 代理和 middleware/error.js 本身,这两处都不在业务流程里。
  2. 业务码五位、HTTP 三位,两个都要给对:409 配 40402,配成 40001 就是错的。
  3. msg 是给用户看的话,不带技术细节:写「用户名已被注册」,不写 ER_DUP_ENTRY: Duplicate entry 'a' for key 'username'。
  4. 新增业务概念才加码,不为"区分得更细"私自扩号段。

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_SAMPLES3高分口碑门槛,样本少于 3 不进榜
DIM_CACHE_TTL_MS60000维度配置内存缓存 60 秒
UPLOAD_MAX_BYTES5MB单图上限,multer 与 media service 共用
DHASH_HAMMING5上传判重汉明距离阈值
REVIEW_PER_H10发布/修改评价 次/时/人
VOTE_PER_H60投票 次/时/人
UPLOAD_PER_H30上传 张/时/人
AI_PER_H20AI 提问 次/时/人
POINT_SUBMIT_PER_H5点位提交 次/时/人

新增常量加进 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;
#规定
1CREATE 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_INCREMENTUUID 字符串
枚举 / 状态TINYINT + COMMENT 写清值ENUM(改值要 DDL)、VARCHAR
布尔TINYINT(1)BOOLEAN(其实是 TINYINT 别名,写法不统一)
金额 / 均分DECIMAL(6,1) / DECIMAL(3,2)FLOAT / DOUBLE(精度漂移)
坐标DECIMAL(10,6)字符串(无法计算距离)
时间DATETIMETIMESTAMP(时区坑)、VARCHAR
多值标签JSON逗号分隔字符串(无法索引与校验)
长文本TEXTVARCHAR(5000)(超长截断风险)
图片VARCHAR(255) objectKeyURL / base64

第 12 条 索引四条规则,JSON 列三条纪律

  1. 唯一键先想清楚语义:username UNIQUE、fzu_uid UNIQUE、review_like(user_id, review_id) 联合唯一防重复点赞。
  2. 所有外键列建索引:review.dish_id、review.user_id、station.canteen_id 这几类。
  3. 列表页的排序 + 筛选组合建联合索引:"按 status 筛 + 按 created_at 排"就建 KEY (status, created_at)。
  4. 联合索引遵循最左前缀: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 同实现)
  1. 全部挂 /api,文档里写的路径省略了这个前缀。
  2. 名词复数:/canteens /dishes /reviews /posts。
  3. 动作类子资源才用动词:/vote /like /appeal /reply /recalc。
  4. 层级最多两层:/reviews/:id/reply 可以,/canteens/:id/stations/:sid/dishes/:did/reviews 不行。
  5. 路径全小写 + 连字符:/points-submit /diet-profile /pet-community。
  6. 旧接口改名要留别名,/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 导入,不再就地复制。

路由五条规则:

  1. 组件一律 () => import(...),45 个页面全量打进主包会拖慢首屏。
  2. name 用 kebab-case,与文件名对应(checkin-view.vue ↔ name: 'checkin')。
  3. 需要登录挂 meta: { auth: true },管理端再挂 meta: { admin: true },守卫统一处理,页面里不再判断。
  4. 路由表按业务分组,用 // ---- 找吃的 ---- 这类注释分隔。
  5. 路径冲突时具体的写前面:/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,按钮/标签不超过 12pxborder-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.mjs2node tests/api-integration.test.mjs
功能走查walkthrough-r<编号>-<主题>.test.mjs19同上

走查编号规则: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 都没有映射到宿主端口,直跑连不上库。后端只在容器里跑。

评审一人改、一人看,不搞集体过。改动类型和对应关注点:

改动类型谁看只看这几件事
后端接口 / serviceC + A四段式齐不齐、错误码配对、复杂 SQL 是否落 service、事务有没有 release()
数据库 DDLD + CIF 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多表写有事务;失败可回滚;幂等考虑到位
6SQL 全部参数化,无 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

编号扮演服务岗一周主要干什么产出形态
A1AI 设计师(需求侧)A 交付 ownerAC 对照表骨架、缺陷三层归因初稿、文档骨架表格 / 清单
A2AI 架构师(契约侧)B 架构 + 部署环境变量模板、端点契约清单初稿、镜像与 CI 片段模板 / 片段
A3AI 程序员(后端)C 后端实现按 DDL 生成 service 骨架、SQL 写法校对、重复代码扫描函数骨架 / 差异表
A4AI 程序员(数据)D 数据枚举字典、迁移脚本草稿、索引建议对照表 / 脚本
A5AI 程序员(前端)E 前端功能改动点清单(localStorage 位置、mock 残留)、接线骨架位置表 / 骨架
A6AI 设计师(视觉侧)F UI 整合找魔法色值、比对令牌表、生成对照截图清单硬编码清单
A7AI 测试员G 测试功能用例草案、边界值枚举、断言骨架用例集
A8AI 测试员(验收侧)H 测试验收 UI术语扫描、视口回归脚本、三视口基线草案扫描报告 / 脚本

提示词编号说明一下:A1—A8 每岗有 3 条基线提示词(P-A1—P-A3、P-B1—P-B3……P-H1—P-H3),每条带预期输出、常见坑和验收标准。每日记录里 P-A4 往后的编号,是当天照同一模板临时加的派活序号,不是另外存了一份提示词。用的时候以五条纪律加对应基线模板为准。

3.2 派活只派能被 grep 验证的

一条派活单写四句话:

  1. 做什么——具体到"扫出全部硬编码色值",别写"优化一下样式"。
  2. 在哪做——给文件路径和表名:init.sql、frontend/src/styles/global.css。
  3. 什么算完成——给一条能跑的检查命令,或者一个能数的口径,比如"列出 37 处,每处带行号"。
  4. 不许做什么——点明禁区,比如"不要给建议,只给位置"。
反例(这么派会翻车)正例(这么派能落地)
"帮我写一下打卡接口""照 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 五条使用纪律

  1. 人审:AI 产出的每一行代码,提交的人得能解释;答不上来就作废。
  2. 标注:AI 参与多的提交,在 commit 正文注明 Co-authored-by: AI-A3 C。
  3. 核事实:AI 提到的接口、字段、依赖、配置项都回仓库核对,查不到就是幻觉,直接驳回。
  4. 补测试:AI 生成的接口和组件,由提交人补断言或用例,只交代码不交测试算没完成。
  5. 不投喂:真实密钥、口令、用户数据不进 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-0122 张表建表(表名 / 列名 / 类型 / 默认值 / 注释)DDL + 注释D / B—D222 表可建,字段与设计一致#W-01
W-02新建 5 张表:check_in / dietary_profile / achievement / user_achievement / pet(含 uk_check_in_dup)DDL + 迁移脚本D / B1D25 表可建,唯一键齐,K0 前定稿#W-02
W-0327 张表字段注释 + 枚举字典(打卡来源/成就类型/宠物阶段)注释 + 字典D—D3每表每列有 COMMENT,三类枚举有字典#W-03
W-04迁移脚本 sql/migration/00x_*.sql,全 IF NOT EXISTS迁移脚本D—D3空库连跑两遍不报错#W-04
W-0527 张表外键 / 索引建齐(对齐 106 外键中属本周的部分)外键 / 索引清单D / B—D3本周外键逐条落地#W-05
W-06演示数据:打卡 30 条(跨 7 天含重复日)/ 成就 12 条 / 宠物初始态 / 待办 5 条种子 SQLD2D4连跑两遍结果一致#W-06
W-07数据自检六条 + 冻结快照 + 线上数据核对六条结果 + 快照D—D5—D76/6 通过,快照带 commit SHA#W-07

4.2 后端 · 127 个端点(主责 C,协 G)

编号任务交付物负责/协作点安排验收判据Issue
W-08114 个端点实现(含 AI 助手对话 / 推荐 / 降级)路由 + service + 两态截图C / G—D2—D7114 端点全通;AI 停供应商回 degraded: true,不抛业务码;正常 / 降级两态截图归档#W-08
W-09打卡 4 端点(POST /checkin 409 幂等 + data.checkinId / GET /checkin/calendar / GET /checkin/stats / DELETE /checkin/:id)路由 + service + 断言C / G5D2—D3四端点全通,16 条断言跑绿#W-09
W-10成就 3 端点(列表 / 进度 / 领取)路由 + serviceC / G2D3进度 = 当前/目标,目标存绝对数,12 条断言绿#W-10
W-11宠物 3 端点(状态 / 喂食 / 社区),喂食阈值 3 天路由 + 状态机C / G3D4>= 3 边界正确,三态矩阵断言绿#W-11
W-12饮食档案 2 端点 GET / PUT /me/diet-profile路由 + serviceC / G1D2与档案页同一字段口径,四类断言齐#W-12
W-13饮食计划 1 端点 POST /me/diet-plan(1/3/7 天)路由 + 规则引擎C / G3D5三档可生成,按忌口/食堂/评分,无档案有降级#W-13
W-1414 service + 7 utils 分层约束PR 检查项C—D2—D7新代码无 routes 越界,service 不碰 req/res#W-14
W-15接口文档 diff:13 条新端点补进文档,114 → 127diff 表C—D713 条逐条补入,总数 127#W-15

4.3 前端 · 45 个页面(主责 E)

编号任务交付物负责/协作点安排验收判据Issue
W-1639 个业务页里另外 31 页实现页面 + 三态E—D2—D731 页关键路径可打开,无白屏#W-16
W-17打卡页接线,替换 localStorage,日历聚合 + 今日状态页面 + 去本地存储E3D28 处 localStorage 清零,F5 数据还在,三态齐#W-17
W-18足迹页 + 手账详情 + me-view 统计卡片三处统一走 /checkin/statsE3D3三处天数一致,手账 F5 不丢 id#W-18
W-19成就页 / 宠物页 / 宠物社区去 mock三页接真接口E3D4无 mockData,三态齐#W-19
W-20档案页 + 三处统一读(档案/打卡/计划共用一份 profile)公共 storeE2D5改一处三处同步#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-2315 个缺口页接入令牌(颜色/字号/圆角/间距四项零硬编码)逐页替换F6D2—D515/15 清零,grep 无色值#W-23
W-24三视口基线定义(375/768/1440)+ 术语表基线图 + 术语表H2D2三套基线齐,D2 18:00 冻结#W-24
W-257 个关键页 × 3 视口走查首轮偏差清单H2D47×3 全覆盖,每条有编号/期望/实际#W-25
W-26偏差清单定稿 + 整改后复验定稿清单 + 复验记录H / F3D6每条写到"改成什么",复验闭环#W-26
W-27最终基线截图 + UI 验收结论(签字)21 张截图 + 签字H—D721 张归档,结论三档#W-27

4.5 测试 · 25 条 AC(主责 G)

编号任务交付物负责/协作点安排验收判据Issue
W-2821 个测试文件编写测试文件G—D2—D721 个文件可跑,新断言并入#W-28
W-2925 条 AC → 40 条用例映射表映射表G / A1D225 条全覆盖,每条 id 可追 AC#W-29
W-3013 个新端点四类断言(正常/缺参/越权/重复)52 条断言G / C1D2—D313×4 全绿#W-30
W-31边界异常 + AC 逐条走查(AC01—AC25)走查表 + 缺陷归因G4D3—D625 条逐条走查,缺陷有三层归因#W-31
W-32全量回归 + 大扫荡(含 31 个未改页面)回归报告G2D6一条命令跑绿,45 页手工点检,先自证会红#W-32
W-3325 行验收报告(AC / 判据 / 结果 / 证据链接)验收报告G—D725 行齐,证据链接可打开#W-33

4.6 部署 · 文档 · 验收(主责 B,协 A)

编号任务交付物负责/协作点安排验收判据Issue
W-346 容器编排确认(MySQL/Redis/MinIO/matting/backend/frontend)冷启动记录B—D2六容器冷启动无隐式依赖#W-34
W-35契约冻结清单——114 + 13 = 127 端点逐条列签名契约清单B—D2127 条签名齐,D2 18:00 冻结#W-35
W-36三份文档口径对齐(25 AC / 78 表 / 106 外键)+ 78→27 对照表对照表B / A2D3三套数同页可对,计点 27 / 交付 78 两列#W-36
W-37镜像构建 + 上云 + 三条校验(首页非空/主链点通/重启不丢)公网 URL + 三截图B3D5—D6三条全过#W-37
W-38线上巡检 + 回滚预案(上一版镜像留着)巡检表 + 回滚三步B—D7回滚不含 -v,上一版镜像保留#W-38
W-3925 条 AC 判据 + 答辩材料 + 冲刺总结判据表 + PPT + 总结A10D5—D725 条有判定,PPT 15 页,11 篇随笔齐#W-39

4.7 优先级

优先级编号说明
P0W-02、W-04、W-06—W-13、W-16—W-20、W-23—W-35、W-37、W-39本期必须闭环;任一不过,K0—K3 至少有一个卡口失守
P1W-01、W-03、W-05、W-14、W-15、W-21、W-22、W-36、W-38本期闭环,但有降级方案;卡口日可临时让路
P2β 池不在本期 39 项内,不占 64 点

写 Backlog 时我们立了三条规矩:

  1. 编号唯一:W-xx 冲刺内、T-xx 修正类、β-xx 下阶段池,三套编号不许重号。
  2. 一项一主:负责岗只填一个编码,协作栏可以多人。
  3. 加一项就减一项,只置换不叠加——否则总量失控,燃尽图必崩。

五、七天排期

天日期各岗主要交付卡口(当天必须完成)
D12026-10-23(周五)冲刺启动会:范围冻结、代码规范发布、39 项任务分配、AI 技术员部署;不写代码、不结算点数任务分配与 Issue 建齐;K0 定于 D2 18:00
D22026-10-24(周六)D:5 表 DDL;H:三视口基线;G:AC 映射 + 断言;C:打卡 3 端点 + 档案端点 + AI 助手降级复现;E:打卡页;F:令牌首日K0 契约冻结 18:00:5 表 DDL 定稿 + 127 端点签名齐
D32026-10-25(周日)C:打卡收口 + 成就 3 端点;E:足迹/手账/me-view;B:口径对照;F:令牌;G:AC 走查首日成就进度口径定死(当前/目标,目标存绝对数)
D42026-10-26(周一)D:种子数据;C:宠物 3 端点;E:三页去 mock;H:7 页×3 视口走查;F:令牌宠物三态矩阵断言通过;C↔E 交叉互测六条有结论
D52026-10-27(周二)C:饮食计划端点;E:档案统一读;G:AC 走查;B:上云;F:令牌收尾;A:AC 判据K1 数据不丢 18:00:7 页 + 手账详情逐个 F5,数据还在
D62026-10-28(周三)H:偏差清单定稿;G:全量回归 + 走查收尾 + AI 两态截图归档;B:上云第三校验;A:答辩材料K2 双签冻结 18:00:代码 + 数据双冻,打 tag v1.0-alpha
D72026-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 编号)占比
A10W-39(10)15.6%
B5W-36(2) + W-37(3)7.8%
C14W-09(5) + W-10(2) + W-11(3) + W-12(1) + W-13(3)21.9%
D3W-02(1) + W-06(2)4.7%
E11W-17(3) + W-18(3) + W-19(3) + W-20(2)17.2%
F6W-23(6)9.4%
G8W-29(1) + W-30(1) + W-31(4) + W-32(2)12.5%
H7W-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 按天

天D1D2D3D4D5D6D7合计
当日关闭点数01312121211464
计划剩余645139271540—

当天关掉的每一项都能追到上面的 Backlog:

天关闭的任务点
D1启动会,不关闭点数0
D2W-02 建表 1 · W-24 视口基线 2 · W-29 用例映射 1 · W-09 打卡首段 3 · W-17 打卡页 3 · W-12 档案端点 1 · W-30 断言 1 · W-23 令牌首日 113
D3W-09 余量 2 · W-10 成就端点 2 · W-18 足迹页 3 · W-36 口径对齐 2 · W-23 令牌 2 · W-31 走查首日 112
D4W-06 种子数据 2 · W-11 宠物端点 3 · W-19 三页去 mock 3 · W-25 视口走查 2 · W-23 令牌 212
D5W-13 计划端点 3 · W-20 档案统一读 2 · W-31 走查 2 · W-37 上云 2 · W-23 令牌 1 · W-39 判据 212
D6W-26 偏差清单 3 · W-32 全量回归 2 · W-37 上云 1 · W-31 走查收尾 1 · W-39 材料 411
D7W-39 答辩与总结 44

按岗 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% 产能排,一定崩。


七、燃尽计划

天D0D1D2D3D4D5D6D7
计划剩余64645139271540
实际剩余6464

img

D1 当天实际剩余还是 64。启动会没关点,所以数字没动——我们刻意不把开会折算成点数,免得燃尽图一开始就掉 6 点。从 D2 起,每天站会只结算昨天说好的点数和关掉的点数,少 1 点第二天补上,偏差不跨天累计。

理想线从 64 直线降到 0,实际线每天补一个点。D1 是平的,这在启动会那天正常;要是 D2—D3 还平着走,才说明任务粒度切得太粗。


八、五个风险与应对

编号风险概率影响处置(谁在什么时候做)
RK-1C 岗单人单日过载(D2 一天 13 点里有 4 点是 C 的)高整条打卡链断W-09 拆 D2/D3 两天;D2 先给前端一个"能调的空壳",E 不空等
RK-25 张表 DDL 改一次,前后端一起返工中K0 失守D2 18:00 前必须冻结;冻结后改字段要 A 签字并通知 C/E/G
RK-3二期 51 张表落库把 D 拖住中迁移脚本跑不完六域迁移脚本直接复用,不重新设计;跑不通就退回 27 张主链,二期脚本降级为"可执行未执行"并写明
RK-4UI 令牌替换制造大 diff,淹没真实改动高回归看不出问题令牌改动单独提交,不与功能改动混在同一个 commit
RK-5D2—D7 日志凑不齐四件证据高每天扣分证据清单进每天站会最后 1 分钟;G 岗兼"证据管家",每天 17:30 点名

规范、任务、点数和卡口从这篇发布起就不再改了。中途真需要变,在当天的工作记录里写清改了什么、为什么,不悄悄改表。

...全文

相关推荐

业绩冲刺销售计划现代商务ppt模板.rar

象征冲刺的职场男士预备起跑姿势商务大图封面,适合于冲销售业绩目标工作计划,销售冲刺工作计划的ppt模板。

weixin_38744435

leetcode 2024春招冲刺百题计划——动态规划+数论

不打算充钱第一次用java写,有点不熟悉。。。还是用c+stl爽。没写完,不定期更新。会尽快的,先发出来吧,万一有人需要呢先更数论和动态规划。

云深沐子兮

蓝桥杯赛前三天冲刺指南:心态调整、三日计划与实战技巧

算法竞赛是检验编程与逻辑思维能力的有效方式,其核心在于将基础数据结构与算法知识应用于解决复杂问题。理解搜索、动态规划、贪心等核心算法原理,是提升解题能力的关键。这些技术不仅能锻炼思维,更在软件开发、人工智能等领域有广泛应用。针对蓝桥杯这类赛事,高效的赛前冲刺策略至关重要。本文聚焦于赛前最后三天的黄金窗口,提供了一套从全真模拟、精准复习到状态调整的完整计划,并融入了**动态规划**和**代码模板**等高频考点与实用技巧,旨在帮助选手将已有知识体系转化为稳定的赛场得分能力,实现最佳临场发挥。

weixin_34214500

迭代=冲刺?

本文作者:特邀敏捷教练梁堃前段时间在和一个小伙伴聊天的时候,他问了阿甲这样一个问题:Scrum guide中把固定时间盒周期称为sprint(冲刺),但是为什么大家在平时都愿意把它称为迭代(iteration)呢?从阿甲接触敏捷至今,对于sprint和iteration这两个词也是经常混着用,但关于这两个词之间的关系和区别,还真的没有思考过。正好阿甲也想趁这个机会和小伙伴们一起把这个概念理清楚,所...

CCGI_敏捷行动派

Sprint-Planner:用于生成示例冲刺计划的小型 Google App 脚本

冲刺计划者 用于生成示例冲刺计划的小型 Google App 脚本

小马甲不小
评论
收藏 已收藏
分享
返回顶部