AI 在一个真实项目里翻过的坑,我记了 265 行

56 阅读7分钟

一、第三篇只讲了 4 个坑,其实有 20 多个

第三篇里我说过,AI 在一个项目里会翻各种车,举了 4 个例子(@TableLogic 不生效、UI 白线盲调、计划偏差、"做了 A 忘了 B")。

但那是挑出来的 4 个。真实情况是——这个项目做下来,我专门开了一份文档叫《阶段1-经验总结》,265 行,20+ 个坑,按数据库、后端、前端、流程四类归档。

今天把没讲过的全抖出来。这些坑用 AI 写代码的人,大概率会一个一个踩过去。

二、数据库类:AI 翻车最重的灾区

2.1 BCrypt 密码 hash 不能手写

AI 做了什么:往 DDL 的初始化数据里手工拼了一个 BCrypt hash。

发生了什么:拼出来的 hash 少了 1 个字符,登录时 BCryptPasswordEncoder.matches() 直接报 Encoded password does not look like BCrypt,验证失败,系统进不去。

怎么修的:hash 必须用 BCryptPasswordEncoder.encode() 生成,保证是完整的 60 字符。DDL 里备注"请上线前修改"。

这个坑的根源:AI 觉得"生成一个 hash"很简单,手拼一下就行。但它不知道 BCrypt 的格式有多严格——少一个字符就整体作废。这种"看起来能跑、其实不能用"的坑,AI 特别爱踩。

2.2 字段精度必须对照投标文档

AI 做了什么:按"标准习惯"建表,折标煤系数 DECIMAL(10,4)、产能 DECIMAL(16,4)、email VARCHAR(100)。

发生了什么:投标文档要求的是 DECIMAL(10,6)、产能字段要 VARCHAR(64)(因为产能带单位)、email 要 VARCHAR(128)。精度不够、类型不对、值域还少了一个(1 良好/0 异常 vs 文档的 1 正常/2 异常/3 估算)。

怎么修的:投标文档是最权威的数据源,DDL 每个字段的类型、长度、注释、默认值逐字段对照,不能凭记忆写。

根源:AI 不知道"这个项目的字段标准在投标文档里"。它只会按通用习惯猜,而定制项目恰恰每个字段都有来处。

2.3 关联表不加唯一约束

AI 做了什么:sys_role_menu 只建了普通索引。

发生了什么:角色和菜单可能重复绑定,数据脏了都不知道。

怎么修的:N:N 关联表必须建联合唯一索引 UNIQUE INDEX (role_id, menu_id)。

2.4 多份 DDL 权威脚本互相打架

AI 做了什么:另起炉灶,在别处重写了一份独立的建表脚本。

发生了什么:两份脚本 sys_menu 表定义不一致(缺列、默认值不同、列名不同),还和已执行过的脚本初始化数据主键冲突(id=1 重复插入)。

怎么修的:DDL 只能有一份权威脚本,增量变更走独立编号文件(02-xxx.sql、03-xxx.sql),不重复建表。

根源:AI 觉得"重写一份更干净",但它不知道这个项目已经有一份执行过的 DDL。"自以为更优"的重写,在存量的世界里是灾难。

三、后端框架类:标准做法在这不标准

3.1 MyBatis-Plus 的 update(null, wrapper) 直接抛异常

AI 做了什么:sysUserDao.update(null, new UpdateWrapper<>())——标准的省事写法。

发生了什么:MyBatis-Plus 3.0.5 是老版本,内部模板会对 entity 做 OGNL 字段检查,entity 为 null 直接抛 OgnlException: source is null。

怎么修的:先 new SysUserEntity(),setXxx() 后再传进 update。升级 MyBatis-Plus 版本是高风险的,不要碰。

根源:AI 学的是"最新版"的写法,但这个项目是老版本。AI 不知道你的框架停在哪个版本,就会用最潮的 API 写最老的项目。

3.2 拦截器的 /error 二次触发

AI 做了什么:在 WebConfig.excludePathPatterns("/login") 排除了登录接口,以为就安全了。

发生了什么:登录失败抛 500 异常 → Tomcat 内部 forward 到 /error → /error 不在排除列表 → 二次触发 TokenException,把真正的错误掩盖了。

怎么修的:拦截器内部硬编码白名单,/login 和 /error 都放行。excludePathPatterns 只作辅助,硬编码才是底线。

根源:AI 不知道 Spring 的异常链路里还有个隐藏的 /error 转发。它只想到了"登录要放行",没想到"错误页也要放行"。

3.3 Jackson 数字强转 Long 必炸

AI 做了什么:从 JWT 的 claims 里 (Long) claims.get("userId") 直接强转。

发生了什么:JSON 数字默认解析成 Integer,强转抛 ClassCastException: Integer cannot be cast to Long。

怎么修的:一律 Long.valueOf(obj.toString()) 或 Number.longValue()。

3.4 Token 拦截器启用太早

AI 做了什么:表还没建、登录接口还没调通,就把拦截器启用了。

发生了什么:所有请求都被拦截,系统直接进不去。

怎么修的:阶段启动顺序钉死:DDL 建表 → 登录调通 → 拦截器启用。

四、前端类:样式和状态的坑最隐蔽

4.1 Promise 错误路径不 reject,按钮永远转圈

AI 做了什么:HTTP 封装里响应 code != 0 时只弹了 $message.error(),没 reject()。

发生了什么:调用方的 Promise 永远 pending,.then() 和 .catch() 都不触发。登录页 loading 转圈永远停不下来,按钮一直禁用。

怎么修的:非成功 code 必须 reject,$postForm、$get、$formData 全都要统一处理。调用方在 .catch() 里重置 UI。

这个坑在阶段 1 出现,阶段 1 只修了 $postJson 一个方法——这就是上一篇说的"做了 A 忘了 B":同类的方法还有三个,AI 只改了一个。

4.2 token header 传 null,序列化成字符串 "null"

AI 做了什么:未登录时 config.headers['token'] = null。

发生了什么:axios 把 null 序列化成字符串 "null",后端收到非空的 "null" 就当真去解析 JWT,报的是 token失效 而不是 token不能为空。

怎么修的:设置 header 前先判断有值才设,不设 null/undefined。

4.3 /deep/ 用在全局 SCSS,被静默丢弃

AI 做了什么:全局样式文件 page-common.scss 里大量写 /deep/ .el-table。

发生了什么:/deep/ 是 Vue scoped style 的穿透语法,在全局 SCSS 里会被 sass-loader 静默丢弃——不报错,就是没用。Element UI 的深色覆盖全部失效,表格、按钮、分页全是白底。

怎么修的:全局 SCSS 禁止 /deep/,覆盖 Element UI 用纯全局选择器 + !important。

根源:AI 知道 /deep/ 能穿透,但不知道它有使用范围。"静默失败"比"报错"更可怕——报错你会查,静默失败你会以为成功。

4.4 样式入口找错了文件

AI 做了什么:在 main.js 里 import '@/styles/index.scss'。

发生了什么:@ 别名对 SCSS 不生效,直接 404。项目的实际全局样式入口是 App.vue 的 <style> 块引入的 static/css/main.scss。

怎么修的:新增全局样式前,先追踪项目的实际样式加载链路,别在 main.js 里 import SCSS。

4.5 前端速查

坑一句话修正
<keep-alive> 缓存组件菜单切换不刷新数据 → 管理后台去掉缓存,或走 activated() 刷新
操作列 fixed="right"独立 DOM 层行高不一致 → 列数可控就不用 fixed
滚动条还是浅色深色主题覆盖要包含 ::-webkit-scrollbar-thumb

五、流程类:工具和规矩的坑

坑一句话修正
直接 svn commit83 个缺失文件没登记 → 提交前 svn status 审核,! 用 svn rm 登记
632 行 DDL 一次写入参数截断 → 用 heredoc 分段追加
"显然要改"就直接改违反"先确认再改" → 方案 → 确认 → 执行,即使是显然的修改

六、这些坑的共性:AI 按标准做法输出,你的项目不标准

看完全部 20 个坑,你会发现一个规律:没有一个是"AI 不会写代码",全是"AI 不知道你的项目和标准不一样"。

  • 数据库的坑 → 它不知道字段标准在投标文档里、不知道 DDL 只能有一份权威
  • 后端的坑 → 它不知道你的框架是 3.0.5、不知道有个隐藏的 /error
  • 前端的坑 → 它不知道全局样式入口在哪、不知道 /deep/ 在全局会失效
  • 流程的坑 → 它不知道 SVN 要审状态、不知道改之前要先确认

AI 的失败,永远源于它对"这个项目的上下文"一无所知。 这不是它的错,是你还没告诉它。

所以这个项目我做的第一件事就是写 CLAUDE.md(上一篇讲过),第二件事就是把 AI 踩过的坑沉淀成《经验总结》——每做完一个阶段,让 AI 把踩坑记录归档,避免下个阶段重复踩。

现在这份文档有 265 行。下次让 AI 做类似的事,我会先让它读它。

上一篇:写文档这件最烦的事,AI 顺手替我干了

下一篇预告:用 AI 做了半年项目,我把省下的时间算了笔账