三周前,我差点在生产环境搞出一个安全事故。
起因很简单:让Cursor写了一个文件上传接口,跑了一下,没报错,直接提交了。
结果被同事review的时候揪出来:
// AI生成的代码
String savePath = uploadDir + fileName; // fileName来自用户输入
File file = new File(savePath);
file.transferFrom(channel);
fileName是用户传进来的,如果传../../etc/passwd,文件就写到服务器根目录去了。
这是一个经典的路径穿越漏洞。
功能完全正常,测试全部通过,但任何一个懂安全的人看一眼就知道这是定时炸弹。
那天之后,我把过去3个月用Cursor踩过的坑,全部整理了一遍。
一共10个。每一个都是真实交过的学费。
第一组:认知层的坑
这组坑最普遍。几乎每个刚开始用AI编程工具的人都会踩。
坑1:把Cursor当"更好的补全"在用
我最开始的用法是这样的:
写几个字 → 等AI补全 → 不满意就删 → 重写
用了两周,感觉"也就那样",效率提升有限,还经常被AI的补全打断思路。
后来才明白:这个用法完全用错了方向。
Cursor的核心能力不是补全,是生成。
正确的用法是:
描述清楚需求 → AI生成完整代码块 → 我来review和调整
两种用法的效率差距,不是10%,是10倍。
一句话改法: 不要等AI补全,要主动用Ctrl+K描述需求,让AI一次生成完整的函数或模块。
坑2:Prompt写得太模糊,然后怪AI不好用
这是我听到最多的抱怨:
"AI写的代码太烂了,还不如自己写。"
我每次听到这个,都想问一句:你的Prompt写的是什么?
模糊Prompt:
写一个登录接口
AI生成的:
public String login(String username, String password) {
User user = userMapper.findByUsername(username);
if (user != null && user.getPassword().equals(password)) {
return "success";
}
return "fail";
}
密码明文比较,没有token,没有异常处理,没有日志。能跑,但没法用。
清楚的Prompt:
写一个用户登录接口,要求:
1. 用户名+密码登录,密码用BCrypt校验
2. 登录失败超过5次,锁定账户30分钟(用Redis存失败次数)
3. 登录成功返回JWT token,有效期7天
4. 记录登录日志:IP、User-Agent、时间、成功/失败
5. 异常统一用BusinessException抛出,返回Result<T>包装
6. 技术栈:Spring Boot 3, MyBatis-Plus, Redis
AI生成的: 80%可以直接用。
AI的质量上限,取决于你Prompt的质量上限。
一句话改法: 写Prompt之前,先想清楚如果让一个新同事来做这个功能,你会告诉他哪些信息。把这些信息全写进去。
坑3:以为AI"懂"你的项目
这个坑很隐蔽。
Cursor确实能读取你的项目文件,但它不知道你们团队的规范、你的代码风格、你的命名习惯。
结果就是:
// 你的项目规范
public class UserService implements IUserService { ... }
// 返回值统一用 Result<T>
// 异常统一用 BusinessException
// AI生成的(没有规范约束时)
public class UserServiceImpl implements UserService { ... }
// 直接返回实体类
// 用 RuntimeException 抛异常
每次生成完都要手动调整,改来改去,效率反而变低了。
解决方案是在项目根目录建一个 .cursorrules 文件:
# 项目规范
## 命名规范
- 接口以I开头:IUserService
- 实现类不加Impl后缀:UserService
- DTO类名以DTO结尾
## 代码规范
- 所有接口返回 Result<T>
- 异常统一用 BusinessException 抛出
- 日志用 @Slf4j,不用 System.out.println
- 常量抽到对应模块的 Constants 类
## 技术栈
- Spring Boot 3.x / JDK 17
- MyBatis-Plus 3.5.x
- Redis(缓存和分布式锁)
配置之后,AI生成的代码直接符合规范,review工作量少了一半。
一句话改法: 第一次用Cursor接入新项目,先花30分钟写好 .cursorrules,后面省的时间是10倍。
第二组:安全和质量的坑
这组坑最危险。踩了可能直接影响线上。
坑4:AI生成的代码能跑,但有安全漏洞
开头的故事就是这个坑。
我把它完整复盘一遍。
AI生成的文件上传接口(问题版本):
@PostMapping("/upload")
public Result<String> upload(@RequestParam MultipartFile file) {
String fileName = file.getOriginalFilename(); // 直接用用户传入的文件名
String savePath = uploadDir + fileName; // 路径直接拼接
file.transferTo(new File(savePath));
return Result.success(savePath);
}
三个问题:
getOriginalFilename()返回的是用户控制的值,可以是../../etc/passwd- 没有文件大小限制,可以上传几个G的文件把磁盘撑爆
- 没有文件类型校验,可以上传可执行文件
修复之后:
@PostMapping("/upload")
public Result<String> upload(@RequestParam MultipartFile file) {
// 1. 文件大小校验
if (file.getSize() > MAX_FILE_SIZE) {
throw new BusinessException("文件大小超过限制");
}
// 2. 文件类型校验
String contentType = file.getContentType();
if (!ALLOWED_TYPES.contains(contentType)) {
throw new BusinessException("不支持的文件类型");
}
// 3. 文件名用UUID重新生成,不用用户传入的
String ext = FilenameUtils.getExtension(file.getOriginalFilename());
String safeFileName = UUID.randomUUID() + "." + ext;
String savePath = uploadDir + safeFileName;
file.transferTo(new File(savePath));
return Result.success(safeFileName);
}
AI不会主动帮你考虑安全性,除非你在Prompt里明确要求。
一句话改法: 涉及文件操作、用户输入、权限校验的代码,Prompt里加一句"需要考虑安全性,列出可能的安全风险并处理"。
坑5:并发场景AI经常给你挖坑
AI写出来的代码,在单线程下完全没问题。一到并发,就出幺蛾子。
我上个月做消息通知系统时,AI生成了一个重试逻辑:
// AI生成的(有问题)
public void retryFailed() {
List<NotifyRecord> failedList = notifyRecordMapper.selectFailed();
for (NotifyRecord record : failedList) {
if (record.getRetryCount() < 3) {
doNotify(record);
record.setRetryCount(record.getRetryCount() + 1);
notifyRecordMapper.updateById(record);
}
}
}
问题:如果这个方法被多个线程同时执行(比如定时任务多实例部署),同一条记录会被重复发送。
修复后:
// 加分布式锁 + 数据库乐观锁
public void retryFailed() {
List<NotifyRecord> failedList = notifyRecordMapper.selectFailed();
for (NotifyRecord record : failedList) {
String lockKey = "notify:retry:" + record.getId();
// 抢锁,抢不到说明已有其他实例在处理
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);
if (Boolean.TRUE.equals(locked)) {
try {
doNotify(record);
notifyRecordMapper.incrementRetryCount(record.getId());
} finally {
redisTemplate.delete(lockKey);
}
}
}
}
一句话改法: 定时任务、库存扣减、余额变更、状态流转这类场景,Prompt里明确加上"需要考虑并发安全,使用分布式锁或乐观锁"。
坑6:AI不会主动帮你考虑性能
AI的目标是"功能正确",不是"性能最优"。
最常见的是N+1查询问题。
// AI生成的(N+1查询)
public List<OrderVO> getOrders(Long userId) {
List<Order> orders = orderMapper.selectByUserId(userId);
return orders.stream().map(order -> {
OrderVO vo = new OrderVO();
BeanUtils.copyProperties(order, vo);
// 每个订单都查一次用户信息
User user = userMapper.selectById(order.getUserId());
vo.setUserName(user.getName());
// 每个订单都查一次商品信息
Product product = productMapper.selectById(order.getProductId());
vo.setProductName(product.getName());
return vo;
}).collect(Collectors.toList());
}
100个订单 = 201次数据库查询。
一句话改法: 涉及列表查询,Prompt里加"注意避免N+1查询,使用批量查询"。或者review时重点看循环内部有没有数据库调用。
坑7:让AI写测试,但测试是假的
这个坑最难发现。
我让Cursor写单元测试,它生成了一大堆,覆盖率看起来很高。
但仔细看:
@Test
void testLogin_success() {
// Mock了所有依赖
when(userMapper.findByUsername("test")).thenReturn(mockUser);
when(passwordEncoder.matches(any(), any())).thenReturn(true);
when(jwtUtil.generateToken(any())).thenReturn("mock-token");
Result<String> result = userService.login("test", "123456");
// 只验证了返回值不为空
assertNotNull(result);
}
测试通过了,但什么都没有真正验证:
- 没有验证返回的token是不是预期格式
- 没有验证登录日志是否记录了
- 没有验证失败次数是否被重置
这种测试,写了等于没写,但覆盖率数字很好看。
一句话改法: 让AI写完测试后,追问一句:"这些测试有没有只验证了mock的返回值,而没有验证真实的业务逻辑?"让AI自己检查。
第三组:习惯层的坑
这组坑最隐蔽,也是从"会用"到"用好"之间最难跨越的鸿沟。
坑8:一次让AI写太多,代码直接失控
刚开始用Cursor的时候,我会这么写Prompt:
帮我实现整个用户模块:注册、登录、修改密码、找回密码、
注销账户、用户信息管理、头像上传、第三方登录
AI生成了一大坨代码,洋洋洒洒800行。
然后我发现:
- 注册和登录的密码校验逻辑写了两套
- 日志记录风格不一致,有的用中文有的用英文
- 异常处理方式三种混用
- 我根本没办法整体review,只能硬着头皮提交
正确做法:
第一步:帮我实现用户注册功能
[review完,满意了]
第二步:帮我实现用户登录,密码校验复用注册里的逻辑
[review完,满意了]
第三步:继续,修改密码功能
分步骤生成,每步都是可控的,每步都能review,最终质量好得多。
一句话改法: 单次Prompt生成的代码,不要超过100行。超过了就拆。
坑9:看不懂AI的代码,但直接用了
这个习惯很危险。
AI有时候会生成一些你没见过的写法:
// AI生成的
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> sendEmail(user)),
CompletableFuture.runAsync(() -> sendSms(user))
).join();
如果你不熟悉CompletableFuture,可能看了一眼觉得"能跑就行"就提交了。
但这里有个问题:.join() 会阻塞当前线程,如果email或者短信服务超时,整个请求就卡死了。正确做法是加超时控制。
你不理解这段代码,你就发现不了这个问题。
我现在遇到看不懂的写法,一定会先问清楚:
你这里用了CompletableFuture.allOf,能解释一下:
1. 这样写的原因是什么?
2. 有没有潜在的风险?
3. 有没有更简单的替代方案?
一句话改法: 设立一条铁律:看不懂的代码,不提交。
坑10:效率上去了,但停止了深度思考
这是最隐蔽的坑,也是我认为最致命的。
用了AI之后,写代码确实快了。但我发现自己开始懒得思考了。
以前设计一个接口,我会想:
- 这个接口的入参合理吗?
- 返回值够不够用?
- 将来扩展会不会有问题?
现在有时候是:
- 让AI生成一个接口
- 能跑了
- 提交
然后两周后,业务需求一变,发现接口设计有根本性的问题,改起来成本极高。
AI提升的是执行速度,但思考这件事,没有任何捷径。
效率高了,省出来的时间,应该用来思考更深的问题:
- 这个方案是最好的吗?
- 还有没有更简单的实现?
- 三个月后再看这段代码,我还能看懂吗?
一句话改法: 每天留出一段"无AI时间",专门用来想架构、想设计、想需求背后的问题。
写在最后
总结一下这10个坑:
认知层:
- 把Cursor当补全工具 → 要当成"程序员"用
- Prompt太模糊 → 像给新同事交代任务一样写清楚
- 没配
.cursorrules→ 花30分钟配好,省几十小时
安全和质量:
- 没考虑安全性 → Prompt里明确要求,代码里重点review
- 并发场景没处理 → 明确要求加锁,识别并发场景
- 没考虑性能 → review时重点看N+1和大数据量
- 测试是假的 → 让AI自我检查测试的有效性
习惯层:
- 一次生成太多 → 单次不超过100行,分步骤来
- 看不懂的代码直接用 → 看不懂就问,不提交
- 停止深度思考 → 保留"无AI时间"
AI让写代码变容易了,但让写好代码变得更考验人了。
以前代码写得慢,你有足够的时间思考。
现在代码生成很快,反而需要你更主动地去思考那些AI不会替你想的问题。
这10个坑,我全踩过。
希望你少踩几个。
💬 回复「工具」获取我完整的AI开发工具清单(含配置方法)
💬 回复「交流」加入AI开发者交流群,一起踩坑一起成长
后端AI实验室 不讲概念,只谈实战 代码开源,每周更新