AI代码生成实战:用Cursor/Copilot让开发效率提升3倍
AI代码生成实战:用Cursor/Copilot让开发效率提升3倍
那个让我无地自容的周五下午
2026年2月,项目组接到一个需求:为电商平台新增一个积分兑换模块,包括积分计算、兑换规则引擎、库存扣减、消息通知四个子系统。
我和同事小明同时接到任务,各做其中两个模块,周一上午联调。
周一早上九点,小明说他周五下午做完了,周末把另外两个模块的测试用例也顺手写了。
我的两个模块到周日晚上才写完,测试用例一行没有。
差距:同样的工作量,他用了不到10小时,我用了将近40小时。
小明用的是Cursor。我用的是IDEA标配。
那天之后,我花了两周时间系统研究了Cursor和GitHub Copilot的高阶用法,效率翻了将近3倍。今天把这些实战心得全部分享出来。
第一章:AI编程工具全景对比
1.1 主流工具横向评测
截止2026年6月,主流AI编程工具评测数据:
| 工具 | 代码补全质量 | 多文件理解 | Java支持 | 月费 | 特色 |
|---|---|---|---|---|---|
| Cursor Pro | ★★★★★ | ★★★★★ | ★★★★★ | $20 | @Codebase全局理解 |
| GitHub Copilot | ★★★★☆ | ★★★★☆ | ★★★★★ | $10/$19 | IDE集成最佳 |
| Claude Code | ★★★★★ | ★★★★★ | ★★★★☆ | $20起 | 终端操作能力强 |
| 通义灵码 | ★★★★☆ | ★★★☆☆ | ★★★★★ | 免费/企业版 | 国内合规,中文最佳 |
| JetBrains AI | ★★★★☆ | ★★★★☆ | ★★★★★ | $8.33/月 | IDEA原生集成 |
| Tabnine | ★★★☆☆ | ★★★☆☆ | ★★★★☆ | $12 | 本地推理,数据不出境 |
radar
title AI编程工具能力对比
axis 代码补全, 多文件理解, 重构能力, 测试生成, 文档生成, 中文支持
Cursor Pro: 95, 92, 88, 85, 82, 75
GitHub Copilot: 88, 82, 80, 80, 78, 68
通义灵码: 85, 78, 75, 78, 80, 95
JetBrains AI: 85, 80, 85, 82, 85, 701.2 选型建议
- 个人开发者:Cursor Pro(综合能力最强)
- 企业团队(国内):通义灵码企业版(合规+中文)
- 已深度使用JetBrains:JetBrains AI Assistant(无缝集成)
- GitHub重度用户:GitHub Copilot(PR Review功能出色)
- 对数据安全极度敏感:Tabnine自部署版
第二章:Cursor实战技巧——高阶用法详解
2.1 Cursor的核心功能架构
2.2 @Codebase的正确使用姿势
@Codebase 是 Cursor 最强大的功能,它会对整个代码仓库建立向量索引,让你可以用自然语言问关于整个项目的问题。
错误用法(大多数人的用法):
// 太宽泛,AI不知道从哪里找
@Codebase 帮我加一个用户管理功能正确用法(具体+有上下文):
@Codebase 参考 UserService 和 OrderService 的实现风格,
在 member 模块下新增一个 PointsService,
实现积分发放、扣减、查询余额、查询明细四个方法。
积分变动要写操作日志(参考 OrderService 的 AuditLog 实现)。
数据库操作使用 MyBatis-Plus,异常处理参考项目统一规范。效果对比:
- 宽泛提示词:生成通用代码,需要大量修改(30-50%可用)
- 精确提示词:生成符合项目风格的代码(80-95%可用)
2.3 高效Composer使用技巧
Composer(多文件编辑模式)是Cursor的杀手级功能,一次提示可以修改多个文件。
适合用Composer的场景:
- 新增一个完整的功能模块(Controller+Service+Repository+Entity)
- 全局重构(比如统一异常处理)
- 添加切面(日志、监控、权限)
- 数据库迁移(Entity + Migration文件同步修改)
Composer提示词模板(Java Spring Boot项目):
请在项目中新增一个完整的【商品评论】功能,要求:
1. 数据库层:
- 新建 ProductReview 实体类(参考 @File src/main/java/entity/Order.java 的风格)
- 新建 Flyway迁移文件 V20260227__create_product_review_table.sql
- 主键用雪花算法,时间字段用 created_at/updated_at,软删除用 deleted_at
2. Repository层:
- 新建 ProductReviewRepository(继承 BaseMapper,参考现有Repository)
- 包含:按商品ID分页查询、按用户ID查询、统计商品平均评分
3. Service层:
- 新建 ProductReviewService 接口和 ProductReviewServiceImpl
- 实现:发布评论(需要验证用户是否购买过该商品)、删除评论(软删除)、查询列表
4. Controller层:
- 新建 ProductReviewController
- RESTful接口:POST /api/reviews, DELETE /api/reviews/{id}, GET /api/products/{id}/reviews
- 用 @Valid 做参数校验,参考 @File src/main/java/controller/OrderController.java
5. DTO层:
- CreateReviewRequest(评论内容、评分1-5)
- ReviewResponse(包含用户昵称、头像)
使用项目现有的:统一返回格式(R类)、全局异常处理、JWT认证体系第三章:让AI写出生产级Java代码的提示技巧
3.1 提示词工程的核心原则
普通提示词 vs 生产级提示词的差距:
3.2 生产级Java代码提示词模板
## 任务
实现 [具体功能描述]
## 技术栈
- Spring Boot 3.x
- Java 17(使用新特性:Record、Sealed Class、Pattern Matching等)
- MyBatis-Plus 3.5.x
- Redis(Spring Data Redis)
- [其他依赖]
## 代码要求
1. 异常处理:所有checked exception转为unchecked,统一用 BusinessException(code, message)
2. 事务:Service方法需要的地方加@Transactional,注意只读查询用readOnly=true
3. 日志:用SLF4J + Lombok @Slf4j,关键操作记录入参和结果,禁止打印密码/token
4. 参数校验:使用Bean Validation注解,Controller层加@Valid
5. 分页:统一使用 Page<T> 返回格式
6. 缓存:热点数据加Redis缓存,过期时间 [X] 分钟,缓存key格式:服务名:业务:id
7. 并发安全:涉及库存/积分等共享资源,使用乐观锁或Redis分布式锁
8. 代码风格:遵循阿里巴巴Java开发手册
## 返回格式
请按顺序生成:
1. [Entity/Model类]
2. [Repository/Mapper接口](如需自定义SQL,附上对应的XML)
3. [Service接口]
4. [ServiceImpl实现类]
5. [Controller类]
6. [相关DTO]
每个类前注明文件路径(如:// src/main/java/com/example/service/UserService.java)3.3 具体场景的提示词案例
场景1:实现分布式限流
/*
* 提示词示例(给AI的完整输入):
*
* 使用Redis + Lua脚本实现滑动窗口限流,要求:
* 1. 支持注解驱动 @RateLimit(key="user:api", limit=10, window=60, unit=SECONDS)
* 2. key支持SpEL表达式,可引用方法参数(如 key="'user:' + #userId")
* 3. 限流维度:支持IP、用户ID、接口三种维度
* 4. 触发限流时返回 429 状态码和Retry-After响应头
* 5. 限流数据记录到监控指标(Micrometer)
* 6. 支持白名单(配置文件可配置豁免IP)
*
* 请生成:
* - @RateLimit注解定义
* - RateLimitAspect切面实现
* - RedisRateLimiter核心实现(含Lua脚本)
* - 白名单配置类
* - 全局异常处理中对RateLimitException的处理
*/
// AI生成的完整实现示例:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/**
* 限流key(支持SpEL表达式)
* 示例:key="'user:api:' + #userId"
*/
String key();
/** 时间窗口内最大请求数 */
int limit() default 100;
/** 时间窗口大小 */
long window() default 60;
/** 时间单位 */
TimeUnit unit() default TimeUnit.SECONDS;
/** 限流维度 */
LimitDimension dimension() default LimitDimension.API;
}
enum LimitDimension {
IP, // 按IP限流
USER, // 按用户ID限流
API // 按接口限流(全局)
}@Slf4j
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitAspect {
private final RedisRateLimiter rateLimiter;
private final RateLimitWhiteList whiteList;
private final ExpressionParser expressionParser = new SpelExpressionParser();
private final MeterRegistry meterRegistry;
public RateLimitAspect(RedisRateLimiter rateLimiter,
RateLimitWhiteList whiteList,
MeterRegistry meterRegistry) {
this.rateLimiter = rateLimiter;
this.whiteList = whiteList;
this.meterRegistry = meterRegistry;
}
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
// 获取当前请求上下文
HttpServletRequest request = getCurrentRequest();
String clientIp = getClientIp(request);
// 白名单检查
if (whiteList.isWhiteListed(clientIp)) {
return joinPoint.proceed();
}
// 构建限流Key
String resolvedKey = resolveKey(rateLimit, joinPoint);
String finalKey = buildFinalKey(rateLimit, resolvedKey, clientIp);
// 执行限流检查
boolean allowed = rateLimiter.isAllowed(finalKey, rateLimit.limit(),
rateLimit.window(), rateLimit.unit());
// 记录监控指标
meterRegistry.counter("rate_limit.requests",
"key", resolvedKey,
"result", allowed ? "allowed" : "rejected")
.increment();
if (!allowed) {
long retryAfterSeconds = rateLimit.unit().toSeconds(rateLimit.window());
log.warn("限流触发,key: {},IP: {}", finalKey, clientIp);
throw new RateLimitException(
"请求过于频繁,请 " + retryAfterSeconds + " 秒后重试",
retryAfterSeconds
);
}
return joinPoint.proceed();
}
private String resolveKey(RateLimit rateLimit, ProceedingJoinPoint joinPoint) {
String keyExpression = rateLimit.key();
if (!keyExpression.contains("#")) {
return keyExpression; // 静态key,直接返回
}
// 解析SpEL表达式
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] paramNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
return expressionParser.parseExpression(keyExpression).getValue(context, String.class);
}
private String buildFinalKey(RateLimit rateLimit, String resolvedKey, String clientIp) {
return switch (rateLimit.dimension()) {
case IP -> "rate_limit:ip:" + clientIp + ":" + resolvedKey;
case USER -> {
String userId = getCurrentUserId();
yield "rate_limit:user:" + userId + ":" + resolvedKey;
}
case API -> "rate_limit:api:" + resolvedKey;
};
}
private String getCurrentUserId() {
// 从SecurityContext或ThreadLocal获取
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth != null ? auth.getName() : "anonymous";
}
private HttpServletRequest getCurrentRequest() {
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}@Component
public class RedisRateLimiter {
private final StringRedisTemplate redisTemplate;
// 滑动窗口限流 Lua 脚本
private static final String SLIDING_WINDOW_SCRIPT = """
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local expire_time = now - window * 1000
-- 移除窗口外的请求记录
redis.call('ZREMRANGEBYSCORE', key, '-inf', expire_time)
-- 统计窗口内的请求数
local count = redis.call('ZCARD', key)
if count < limit then
-- 添加当前请求记录
redis.call('ZADD', key, now, now .. '-' .. math.random(1000000))
redis.call('PEXPIRE', key, window * 1000)
return 1 -- 允许
else
return 0 -- 拒绝
end
""";
private final DefaultRedisScript<Long> script;
public RedisRateLimiter(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
this.script = new DefaultRedisScript<>(SLIDING_WINDOW_SCRIPT, Long.class);
}
public boolean isAllowed(String key, int limit, long window, TimeUnit unit) {
long windowMillis = unit.toMillis(window);
long now = System.currentTimeMillis();
Long result = redisTemplate.execute(
script,
List.of(key),
String.valueOf(limit),
String.valueOf(windowMillis),
String.valueOf(now)
);
return Long.valueOf(1L).equals(result);
}
}第四章:Spring Boot项目的AI开发工作流
4.1 标准AI辅助开发流程
4.2 需求拆解提示词
/*
* 使用AI快速拆解需求,生成开发任务列表
*
* 提示词模板:
* "你是一名经验丰富的Java技术负责人。请将以下需求拆解为开发任务:
*
* [粘贴需求文档]
*
* 请输出:
* 1. 数据库表设计(表名、字段、索引)
* 2. API接口列表(HTTP方法、路径、参数、返回值)
* 3. 开发任务拆解(按Service方法粒度,估算工时)
* 4. 技术风险点(并发、性能、安全)
* 5. 依赖关系图(哪些任务必须先完成)"
*/
// 示例:AI生成的任务拆解结果(实际使用中AI会自动生成这些)
@Data
public class DevelopmentTask {
private String taskId;
private String title;
private String description;
private TaskType type; // ENTITY, REPOSITORY, SERVICE, CONTROLLER, DTO
private List<String> dependencies; // 依赖的taskId
private int estimatedHours;
private Priority priority;
// 任务执行状态跟踪
private TaskStatus status = TaskStatus.TODO;
private String generatedByAI; // AI生成的代码
private String reviewNotes; // 人工Review意见
}4.3 完整的需求→代码工作流示例
/**
* 演示:用Cursor完成一个完整的业务功能
*
* 需求:实现商品收藏功能
* 传统开发时间:约4小时
* AI辅助开发时间:约40分钟
*/
// Step 1: 告诉Cursor生成实体(约2分钟)
// 提示词:"参考Order实体,生成ProductFavorite实体,字段:userId、productId、创建时间"
@Data
@TableName("product_favorite")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductFavorite {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@NotNull
private Long userId;
@NotNull
private Long productId;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
// 软删除
@TableLogic
private Integer deleted;
}
// Step 2: 生成Repository(约1分钟)
// 提示词:"基于上面的实体,生成ProductFavoriteMapper,包括:判断是否已收藏、按用户分页查询"
@Mapper
public interface ProductFavoriteMapper extends BaseMapper<ProductFavorite> {
/**
* 检查是否已收藏
*/
@Select("SELECT COUNT(1) FROM product_favorite WHERE user_id = #{userId} AND product_id = #{productId} AND deleted = 0")
int countByUserIdAndProductId(@Param("userId") Long userId, @Param("productId") Long productId);
/**
* 按用户分页查询收藏(关联商品信息)
*/
@Select("""
SELECT pf.*, p.name as product_name, p.main_image, p.price
FROM product_favorite pf
LEFT JOIN product p ON pf.product_id = p.id
WHERE pf.user_id = #{userId} AND pf.deleted = 0
ORDER BY pf.created_at DESC
""")
Page<FavoriteVO> selectFavoritesByUserId(Page<FavoriteVO> page, @Param("userId") Long userId);
}
// Step 3: 生成Service(约5分钟,含业务逻辑)
// 提示词:"实现收藏Service,要求:防重复收藏、收藏数限制(最多500个)、取消收藏、查询列表"
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductFavoriteServiceImpl implements ProductFavoriteService {
private final ProductFavoriteMapper favoriteMapper;
private final ProductMapper productMapper;
private final RedisTemplate<String, String> redisTemplate;
private static final int MAX_FAVORITES_PER_USER = 500;
private static final String CACHE_KEY_FAVORITE_COUNT = "user:favorite:count:";
@Override
@Transactional
public void addFavorite(Long userId, Long productId) {
// 1. 验证商品存在
Product product = productMapper.selectById(productId);
if (product == null || product.getDeleted() == 1) {
throw new BusinessException(ErrorCode.PRODUCT_NOT_FOUND, "商品不存在");
}
// 2. 防重复收藏(先查缓存再查DB)
if (isFavorited(userId, productId)) {
throw new BusinessException(ErrorCode.ALREADY_FAVORITED, "已经收藏过该商品");
}
// 3. 收藏数上限检查
long favoriteCount = getFavoriteCount(userId);
if (favoriteCount >= MAX_FAVORITES_PER_USER) {
throw new BusinessException(ErrorCode.FAVORITE_LIMIT_EXCEEDED,
"收藏数量已达上限(" + MAX_FAVORITES_PER_USER + "个)");
}
// 4. 保存收藏记录
ProductFavorite favorite = ProductFavorite.builder()
.userId(userId)
.productId(productId)
.build();
favoriteMapper.insert(favorite);
// 5. 更新缓存
invalidateFavoriteCache(userId, productId);
log.info("用户{}收藏商品{}成功", userId, productId);
}
@Override
@Transactional
public void removeFavorite(Long userId, Long productId) {
LambdaUpdateWrapper<ProductFavorite> wrapper = new LambdaUpdateWrapper<ProductFavorite>()
.eq(ProductFavorite::getUserId, userId)
.eq(ProductFavorite::getProductId, productId);
int affected = favoriteMapper.delete(wrapper);
if (affected == 0) {
throw new BusinessException(ErrorCode.NOT_FAVORITED, "该商品未收藏");
}
invalidateFavoriteCache(userId, productId);
log.info("用户{}取消收藏商品{}成功", userId, productId);
}
@Override
public boolean isFavorited(Long userId, Long productId) {
// 先查Redis缓存
String cacheKey = "user:favorite:" + userId + ":" + productId;
String cached = (String) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return "1".equals(cached);
}
// 查数据库
boolean favorited = favoriteMapper.countByUserIdAndProductId(userId, productId) > 0;
// 写入缓存,TTL 10分钟
redisTemplate.opsForValue().set(cacheKey, favorited ? "1" : "0",
Duration.ofMinutes(10));
return favorited;
}
@Override
public Page<FavoriteVO> getFavoriteList(Long userId, int pageNum, int pageSize) {
Page<FavoriteVO> page = new Page<>(pageNum, pageSize);
return favoriteMapper.selectFavoritesByUserId(page, userId);
}
private long getFavoriteCount(Long userId) {
String cacheKey = CACHE_KEY_FAVORITE_COUNT + userId;
String cached = (String) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return Long.parseLong(cached);
}
long count = favoriteMapper.selectCount(
new LambdaQueryWrapper<ProductFavorite>().eq(ProductFavorite::getUserId, userId));
redisTemplate.opsForValue().set(cacheKey, String.valueOf(count), Duration.ofMinutes(5));
return count;
}
private void invalidateFavoriteCache(Long userId, Long productId) {
redisTemplate.delete("user:favorite:" + userId + ":" + productId);
redisTemplate.delete(CACHE_KEY_FAVORITE_COUNT + userId);
}
}第五章:AI生成代码的质量把控
5.1 AI代码审查清单
checklist
title AI生成代码Review清单
section 安全性
SQL注入防护(参数化查询)
XSS防护(输出转义)
CSRF防护(POST接口)
敏感数据不入日志
接口权限验证
section 健壮性
空指针处理(Optional或@NonNull)
边界条件处理(空列表、0值)
超时设置(HTTP调用、数据库查询)
事务回滚正确配置
section 性能
N+1查询问题
缺少索引的查询
大对象频繁创建
未关闭的资源(IO、连接)
section 规范
方法命名符合规范
注释完整(复杂逻辑必须有注释)
日志级别合理
错误码统一使用5.2 自动化代码质量检查
/**
* 集成AI的代码Review工具
* 在CI/CD流程中自动触发
*/
@Service
public class AICodeReviewService {
private final ChatClient chatClient;
/**
* 对PR中的Java文件进行AI Review
*/
public CodeReviewReport reviewJavaFile(String filePath, String fileContent,
String diffContent) {
String prompt = String.format("""
请对以下Java代码进行专业的Code Review:
## 文件路径
%s
## 变更内容(diff格式)
```diff
%s
```
## 完整文件内容
```java
%s
```
请从以下维度给出Review意见:
### 1. 安全问题(HIGH/MEDIUM/LOW)
- 是否有SQL注入风险?
- 是否有未验证的用户输入?
- 敏感信息是否有泄露风险?
### 2. 性能问题
- 是否有N+1查询?
- 是否有不必要的全表扫描?
- 缓存使用是否合理?
### 3. 代码质量
- 是否符合单一职责原则?
- 异常处理是否完整?
- 方法复杂度是否过高(>10的圈复杂度)?
### 4. 建议优化项
- 具体指出第几行有问题
- 给出修改建议(如可能,给出修改后的代码片段)
输出格式:JSON,包含 issues 数组,每个issue包含:
severity(CRITICAL/HIGH/MEDIUM/LOW/INFO)、line、description、suggestion
""", filePath, diffContent, fileContent);
String reviewJson = chatClient.prompt()
.system("你是一名专注于Java安全和性能的高级工程师,Code Review风格严格但建设性。")
.user(prompt)
.call()
.content();
return parseReviewReport(reviewJson, filePath);
}
private CodeReviewReport parseReviewReport(String json, String filePath) {
// 解析AI返回的JSON格式Review结果
// 实际项目中使用Jackson解析
return CodeReviewReport.builder()
.filePath(filePath)
.reviewedAt(Instant.now())
.issues(List.of()) // 从json解析
.build();
}
}第六章:单元测试生成
6.1 让AI生成全面的测试用例
/**
* AI驱动的单元测试生成
*
* 对 ProductFavoriteServiceImpl 生成的测试示例
*/
@ExtendWith(MockitoExtension.class)
class ProductFavoriteServiceImplTest {
@Mock
private ProductFavoriteMapper favoriteMapper;
@Mock
private ProductMapper productMapper;
@Mock
private RedisTemplate<String, String> redisTemplate;
@Mock
private ValueOperations<String, String> valueOperations;
@InjectMocks
private ProductFavoriteServiceImpl favoriteService;
@BeforeEach
void setUp() {
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
}
// ==================== addFavorite 测试 ====================
@Test
@DisplayName("正常收藏商品")
void addFavorite_Success() {
// Given
Long userId = 1001L;
Long productId = 2001L;
Product product = new Product();
product.setId(productId);
product.setDeleted(0);
when(productMapper.selectById(productId)).thenReturn(product);
when(valueOperations.get(anyString())).thenReturn(null);
when(favoriteMapper.countByUserIdAndProductId(userId, productId)).thenReturn(0);
when(favoriteMapper.selectCount(any())).thenReturn(5L);
when(favoriteMapper.insert(any())).thenReturn(1);
// When
assertDoesNotThrow(() -> favoriteService.addFavorite(userId, productId));
// Then
verify(favoriteMapper, times(1)).insert(argThat(favorite ->
favorite.getUserId().equals(userId) &&
favorite.getProductId().equals(productId)
));
}
@Test
@DisplayName("商品不存在时抛出异常")
void addFavorite_ProductNotFound() {
// Given
when(productMapper.selectById(anyLong())).thenReturn(null);
// When & Then
BusinessException exception = assertThrows(BusinessException.class,
() -> favoriteService.addFavorite(1001L, 9999L));
assertThat(exception.getCode()).isEqualTo(ErrorCode.PRODUCT_NOT_FOUND);
verify(favoriteMapper, never()).insert(any());
}
@Test
@DisplayName("重复收藏时抛出异常")
void addFavorite_AlreadyFavorited() {
// Given
Long userId = 1001L;
Long productId = 2001L;
Product product = buildValidProduct(productId);
when(productMapper.selectById(productId)).thenReturn(product);
when(valueOperations.get(anyString())).thenReturn("1"); // 缓存命中,已收藏
// When & Then
BusinessException exception = assertThrows(BusinessException.class,
() -> favoriteService.addFavorite(userId, productId));
assertThat(exception.getCode()).isEqualTo(ErrorCode.ALREADY_FAVORITED);
}
@Test
@DisplayName("收藏数达到上限时抛出异常")
void addFavorite_LimitExceeded() {
// Given
Long userId = 1001L;
Long productId = 2001L;
Product product = buildValidProduct(productId);
when(productMapper.selectById(productId)).thenReturn(product);
when(valueOperations.get("user:favorite:" + userId + ":" + productId)).thenReturn("0");
when(favoriteMapper.countByUserIdAndProductId(userId, productId)).thenReturn(0);
when(valueOperations.get("user:favorite:count:" + userId)).thenReturn("500");
// When & Then
BusinessException exception = assertThrows(BusinessException.class,
() -> favoriteService.addFavorite(userId, productId));
assertThat(exception.getCode()).isEqualTo(ErrorCode.FAVORITE_LIMIT_EXCEEDED);
}
// ==================== 边界条件测试 ====================
@Test
@DisplayName("userId为null时的处理")
void addFavorite_NullUserId() {
assertThrows(Exception.class, () -> favoriteService.addFavorite(null, 2001L));
}
@ParameterizedTest
@ValueSource(longs = {0L, -1L, Long.MAX_VALUE})
@DisplayName("边界userId值测试")
void addFavorite_BoundaryUserIds(Long userId) {
// 确保不会因为边界值导致系统崩溃
when(productMapper.selectById(anyLong())).thenReturn(null);
assertThrows(BusinessException.class, () -> favoriteService.addFavorite(userId, 2001L));
}
private Product buildValidProduct(Long productId) {
Product p = new Product();
p.setId(productId);
p.setDeleted(0);
p.setName("测试商品");
return p;
}
}第七章:重构辅助
7.1 AI辅助重构的正确姿势
/**
* 重构场景示例:将"上帝类"拆分为职责单一的服务
*
* 使用Cursor的步骤:
* 1. 打开臃肿的Service文件(可能3000行+)
* 2. 使用Cursor Chat,提示词:
* "分析这个Service类的所有方法,按照单一职责原则建议如何拆分?
* 给出拆分方案、新类的命名、方法归属,以及需要注意的循环依赖风险"
* 3. 确认拆分方案后,使用Composer逐步拆分
*/
// 重构前:一个包含200个方法的OrderService
// 重构后由AI建议拆分为:
// 1. OrderCreateService - 订单创建
// 2. OrderPaymentService - 支付处理
// 3. OrderFulfillmentService - 履约管理
// 4. OrderQueryService - 查询统计
// 5. OrderCancelService - 取消/退款
/**
* 重构提示词模板:
*
* 现有代码存在以下问题:[描述问题]
*
* 请帮我重构,目标:
* 1. 拆分为职责单一的类
* 2. 消除重复代码(DRY原则)
* 3. 减少方法的圈复杂度(每个方法<10)
* 4. 保持原有功能不变(提供重构前后的测试对比)
*
* 限制:
* - 不能修改公开API接口(其他团队在使用)
* - 不能引入新的外部依赖
* - 数据库事务边界保持不变
*/
// AI生成的重构方案示例:
// 重构前(复杂的if-else)
public BigDecimal calculateDiscount_before(Order order) {
BigDecimal discount = BigDecimal.ZERO;
if (order.getUserLevel() == 1) {
if (order.getTotalAmount().compareTo(new BigDecimal("100")) > 0) {
discount = order.getTotalAmount().multiply(new BigDecimal("0.05"));
}
} else if (order.getUserLevel() == 2) {
if (order.getTotalAmount().compareTo(new BigDecimal("200")) > 0) {
discount = order.getTotalAmount().multiply(new BigDecimal("0.08"));
}
} else if (order.getUserLevel() == 3) {
discount = order.getTotalAmount().multiply(new BigDecimal("0.10"));
}
// ... 更多if-else
return discount;
}
// 重构后(策略模式,AI自动生成)
public interface DiscountStrategy {
BigDecimal calculate(Order order);
boolean supports(int userLevel);
}@Component
public class SilverMemberDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal calculate(Order order) {
if (order.getTotalAmount().compareTo(new BigDecimal("100")) > 0) {
return order.getTotalAmount().multiply(new BigDecimal("0.05"));
}
return BigDecimal.ZERO;
}
@Override
public boolean supports(int userLevel) {
return userLevel == 1;
}
}@Component
public class DiscountCalculator {
private final List<DiscountStrategy> strategies;
public DiscountCalculator(List<DiscountStrategy> strategies) {
this.strategies = strategies;
}
public BigDecimal calculateDiscount(Order order) {
return strategies.stream()
.filter(s -> s.supports(order.getUserLevel()))
.findFirst()
.map(s -> s.calculate(order))
.orElse(BigDecimal.ZERO);
}
}第八章:文档生成
8.1 JavaDoc自动生成
/**
* 使用AI生成JavaDoc的提示词模板:
*
* "请为以下Java代码生成完整的JavaDoc注释:
* 1. 类注释:说明类的职责、主要用法
* 2. 方法注释:@param说明每个参数的含义和约束、@return说明返回值、@throws说明可能抛出的异常
* 3. 复杂业务逻辑处加内联注释
* 4. 注释用中文,专业术语可保留英文
*
* [粘贴代码]"
*/
// AI生成的完整JavaDoc示例:
/**
* 订单支付服务
*
* <p>负责处理订单的支付流程,包括:
* <ul>
* <li>支付预处理(金额验证、库存锁定)</li>
* <li>调用支付网关(微信/支付宝/银行卡)</li>
* <li>支付结果处理(更新订单状态、触发履约)</li>
* <li>支付异常处理(超时、失败、退款)</li>
* </ul>
*
* <p>使用示例:
* <pre>{@code
* PaymentResult result = paymentService.pay(
* PaymentRequest.builder()
* .orderId(12345L)
* .channel(PaymentChannel.WECHAT)
* .build()
* );
* }</pre>
*
* @author 张工程师
* @since 2026-02-01
* @see OrderService
* @see PaymentGatewayClient
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderPaymentService {
/**
* 发起支付请求
*
* <p>执行流程:
* <ol>
* <li>验证订单状态(必须为PENDING_PAYMENT)</li>
* <li>验证支付金额(防止篡改)</li>
* <li>锁定商品库存</li>
* <li>调用支付网关创建支付单</li>
* <li>返回支付跳转URL或二维码</li>
* </ol>
*
* @param request 支付请求,包含订单ID、支付渠道、客户端类型等信息
* @return 支付结果,包含支付跳转URL(H5/小程序)或二维码URL(PC扫码)
* @throws OrderNotFoundException 当订单不存在时抛出
* @throws OrderStatusException 当订单状态不允许支付时抛出(例如已取消)
* @throws PaymentGatewayException 当支付网关调用失败时抛出
* @throws StockInsufficientException 当库存不足时抛出
*/
public PaymentResult pay(PaymentRequest request) {
// 实现省略
return null;
}
}第九章:团队AI开发规范
9.1 建立团队AI辅助开发规范
9.2 团队共享Prompt库建设
/**
* 团队共享Prompt库示例
* 存储在项目根目录 .cursor/prompts/ 下
*
* 这样团队所有成员使用同样质量的提示词
*/
// .cursor/prompts/create-service.md
/*
# 创建Service
## 使用场景
当需要创建一个新的业务Service时使用。
## 变量
- SERVICE_NAME: Service名称(如:Order、User、Product)
- MODULE: 所属模块(如:order、user)
## 提示词
创建 {SERVICE_NAME}Service,要求:
1. **接口定义**({SERVICE_NAME}Service.java)
- 方法签名清晰,每个方法写完整JavaDoc
- 入参用Request DTO,出参用Response DTO
2. **实现类**({SERVICE_NAME}ServiceImpl.java)
- 继承接口,添加@Service和@Slf4j
- 所有写操作添加@Transactional
- 查询操作添加@Transactional(readOnly=true)
- 关键操作记录日志(入参+结果)
- 统一使用项目的BusinessException和ErrorCode
3. **数据访问**
- 使用MyBatis-Plus,复杂查询写XML
- 分页统一使用Page<T>
参考项目中已有的OrderService实现风格。
*/第十章:FAQ
FAQ
Q1:AI生成的代码出bug了怎么办?
A:AI生成的代码和人写的代码一样,都需要测试覆盖。实践建议:
- AI生成代码后,立刻让AI再生成对应的单元测试
- 复杂逻辑做Code Review(最好让另一个人或再次问AI做第二遍Review)
- 建立分阶段上线机制,先灰度再全量
Q2:用了AI编程工具后,初级工程师的技术成长会不会受影响?
A:这是个好问题。确实存在"用了AI就不动脑"的风险。我的建议:
- AI是助手,要理解AI生成的每一行代码的含义
- 不懂的地方,让AI解释,而不是直接copy
- 定期进行无AI编码练习,保持基本功
Q3:如何衡量AI编程带来的实际效率提升?
A:建立数据:
- 记录功能开发时间(AI辅助前后对比)
- 统计Bug率变化
- 统计代码Review反馈数量
- 统计测试覆盖率
我实测的数据:新功能开发效率提升约2.8倍,Bug密度降低约30%(因为AI生成时会考虑边界条件),测试覆盖率从40%提升到75%(因为生成测试太方便了)。
Q4:公司的代码能不能发给AI工具?
A:需要确认公司政策:
- GitHub Copilot企业版:代码不会用于训练
- Cursor:可配置不使用代码训练
- 通义灵码企业版:私有化部署,数据完全在内网
- 最安全方案:购买私有化部署的AI编程助手,或使用本地模型
总结
AI编程工具带来的效率革命是真实的,但要真正提升3倍效率,需要:
- 掌握高质量提示词技巧(80%的效率提升来自这里)
- 建立AI辅助开发工作流(不是偶尔问一问,而是全程贯通)
- 重视代码质量把控(AI生成快,但需要人来把关)
- 建立团队规范(让整个团队都用好AI,而不是你一个人卷)
