尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Day 56:AI辅助开发全面提效:Copilot + Cursor的Java开发

Day 56:AI辅助开发全面提效:Copilot + Cursor的Java开发 一、AI 辅助开发拼的不是工具而是配合方式AI 辅助开发提效的前提不是会不会装 Copilot / Cursor而是会不会跟它配合。把 AI 当搜索引擎用得到一堆可能错的答案把它当结对编程搭子用能省掉一半以上的重复劳动——样板代码、单元测试、文档、代码审查。核心底线只有两条AI 写的代码你必须逐行看懂复杂业务逻辑 AI 只给草稿、不给终稿。本文以 Java 后端工作流为主线落地 Copilot Cursor 的四类实战用精准 Prompt 基于现有代码生成完整 CRUD 链路、让 AI 补齐 JUnit 5 Mockito 单元测试的边界场景、用 SpringDoc AI 同步 API 文档、以及最关键的AI 初筛 人工终审代码审查白名单/黑名单。读完能建立AI 生成 → 人工 Review → 单测 → 合入的强制流程把日常编码效率提升 30%~50%同时守住质量底线。二、AI 辅助开发的现实图景不是取代是倍增先说结论AI 不会取代程序员但会用 AI 的程序员会取代不会用的。这不是什么营销口号而是我这半年用 Copilot Cursor 写 Java 代码的真实体感。具体来说AI 在以下四类任务上提效最明显任务类型传统耗时AI 辅助后质量要求样板代码DTO/Mapper/Builder30min5min需人工校验单元测试1h15min需补边界场景API 文档/注释20min3min需检查准确性代码审查静态检查30min10min不能替代人工但有两个底线必须守住AI 写的代码你必须要能看懂。看不懂就敢合进主干那是拿生产环境开玩笑。核心算法和复杂业务逻辑AI 只能给草稿不能给终稿。这部分是你的护城河别外包给 AI。三、代码生成让 Cursor 成为你的结对编程伙伴Cursor 这两年火不是没道理的。它跟传统 IDE 补全的最大区别是上下文理解深度。你可以把整个项目的代码丢给它让它基于现有代码风格生成新模块。场景基于现有 Entity 生成完整的 CRUD 链路假设你有一个Order实体想让 AI 生成对应的 DTO、Service、Controller——但不是瞎生成要跟你现有项目的命名规范、异常处理风格、日志格式保持一致。第一步准备上下文这是最关键的一步在 Cursor 的 Chat 窗口里先给它喂几个参考文件BaseController.java、GlobalExceptionHandler.java、Order.java。然后写一个精准的 Prompt请基于以下项目规范为 Order 实体生成完整的 REST API 模块​1. 项目规范必须遵守 - 使用 Lombok 的 Builder Data - Service 层必须统一返回 ResultT 包装对象 - Controller 使用 RestController RequestMapping(/api/v1/orders) - 异常统一抛 BusinessException错误码在 ErrorCode 枚举中定义 - 日志使用 SLF4J格式log.info([操作] 参数{}, 结果{}, param, result) - 分页查询使用 PageQueryT 统一入参返回 PageResultT ​ 2. 需要生成 - OrderCreateDTO字段校验用 NotNull/Size - OrderUpdateDTO允许部分字段为空 - OrderQueryDTO分页 时间范围查询 - OrderService包含 create/update/queryById/pageQuery 四个方法 - OrderController五个端点POST /api/v1/orders, PUT /api/v1/orders/{id}, GET /api/v1/orders/{id}, GET /api/v1/orders?pagesizestartTimeendTime, DELETE /api/v1/orders/{id} ​ 3. 注意 - 不要生成 set/get 方法用 Lombok - 删除操作只做逻辑删除update deleted1 - 分页查询按 createTime 倒序Prompt 的精髓约束越多输出越稳。你模糊地说帮我写个 Order 的 CRUDCursor 可能给你生成一套它自己臆想出来的风格但你把具体规范一条条列出来它生成的代码至少能省掉你 70% 的修改时间。第二步AI 生成的代码骨架Spring Boot 3.2 JDK 21// OrderCreateDTO.java package com.example.order.dto; ​ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.Data; import java.math.BigDecimal; ​ Data Builder public class OrderCreateDTO { ​ NotNull(message 用户ID不能为空) private Long userId; ​ Size(max 200, message 商品名称最多200字符) private String productName; ​ NotNull(message 金额不能为空) private BigDecimal amount; ​ // 可选备注 Size(max 500, message 备注最多500字符) private String remark; }// OrderService.java package com.example.order.service; ​ import com.example.common.exception.BusinessException; import com.example.common.result.Result; import com.example.common.result.PageResult; import com.example.order.dto.OrderCreateDTO; import com.example.order.dto.OrderQueryDTO; import com.example.order.dto.OrderUpdateDTO; import com.example.order.entity.Order; import com.example.order.enums.ErrorCode; import com.example.order.mapper.OrderMapper; import com.example.order.repository.OrderRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; ​ import java.time.LocalDateTime; import java.util.Optional; ​ Slf4j Service RequiredArgsConstructor public class OrderService { ​ private final OrderRepository orderRepository; private final OrderMapper orderMapper; ​ Transactional public ResultLong create(OrderCreateDTO dto) { log.info([订单创建] 参数{}, dto); Order order orderMapper.toEntity(dto); order.setCreateTime(LocalDateTime.now()); order.setDeleted(false); Order saved orderRepository.save(order); log.info([订单创建] 结果{}, saved.getId()); return Result.ok(saved.getId()); } ​ Transactional public ResultVoid update(Long id, OrderUpdateDTO dto) { log.info([订单更新] id{}, 参数{}, id, dto); Order order orderRepository.findById(id) .orElseThrow(() - new BusinessException(ErrorCode.ORDER_NOT_FOUND)); orderMapper.updateEntity(dto, order); order.setUpdateTime(LocalDateTime.now()); orderRepository.save(order); log.info([订单更新] 结果success); return Result.ok(); } ​ public ResultOrder queryById(Long id) { log.info([订单查询] id{}, id); OptionalOrder opt orderRepository.findByIdAndDeletedFalse(id); if (opt.isEmpty()) { throw new BusinessException(ErrorCode.ORDER_NOT_FOUND); } return Result.ok(opt.get()); } ​ public ResultPageResultOrder pageQuery(OrderQueryDTO query) { log.info([订单分页查询] 参数{}, query); PageRequest pageRequest PageRequest.of( query.getPage() - 1, // 前端从1开始JPA从0开始 query.getSize(), Sort.by(Sort.Direction.DESC, createTime) ); PageOrder page orderRepository.findByCreateTimeBetween( Optional.ofNullable(query.getStartTime()).orElse(LocalDateTime.MIN), Optional.ofNullable(query.getEndTime()).orElse(LocalDateTime.MAX), pageRequest ); PageResultOrder result PageResult.of(page.getContent(), page.getTotalElements(), query.getPage(), query.getSize()); log.info([订单分页查询] 结果total{}, result.getTotal()); return Result.ok(result); } ​ Transactional public ResultVoid delete(Long id) { log.info([订单删除] id{}, id); int rows orderRepository.logicalDelete(id, LocalDateTime.now()); if (rows 0) { throw new BusinessException(ErrorCode.ORDER_NOT_FOUND); } log.info([订单删除] 结果success); return Result.ok(); } }// OrderController.java package com.example.order.controller; ​ import com.example.common.result.Result; import com.example.common.result.PageResult; import com.example.order.dto.OrderCreateDTO; import com.example.order.dto.OrderQueryDTO; import com.example.order.dto.OrderUpdateDTO; import com.example.order.entity.Order; import com.example.order.service.OrderService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; ​ RestController RequestMapping(/api/v1/orders) RequiredArgsConstructor public class OrderController { ​ private final OrderService orderService; ​ PostMapping public ResultLong create(Valid RequestBody OrderCreateDTO dto) { return orderService.create(dto); } ​ PutMapping(/{id}) public ResultVoid update(PathVariable Long id, Valid RequestBody OrderUpdateDTO dto) { return orderService.update(id, dto); } ​ GetMapping(/{id}) public ResultOrder getById(PathVariable Long id) { return orderService.queryById(id); } ​ GetMapping public ResultPageResultOrder pageQuery(Valid OrderQueryDTO query) { return orderService.pageQuery(query); } ​ DeleteMapping(/{id}) public ResultVoid delete(PathVariable Long id) { return orderService.delete(id); } }#Maven 依赖节选 dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId version3.2.5/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId version3.2.5/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.32/version scopeprovided/scope /dependencyCursor 生成的这段代码基本骨架是对的——Lombok、校验注解、分页逻辑、日志格式都按 Prompt 约束输出了。但有几个点你必须人工检查PageRequest.of(page - 1, ...)这个转换是否正确如果你的前端已经统一从 0 开始传参这里就多了个 bug。findByCreateTimeBetween这个方法名是 JPA 派生查询AI 可能假设了你的 Repository 已经有这个方法——实际上你可能需要补Query注解。日志里的敏感信息——如果dto里包含用户手机号或地址直接toString()可能泄露隐私需要脱敏。AI 生成的是 80 分的草稿剩下 20 分的细节坑必须你自己填。四、单元测试生成让 AI 帮你把覆盖率 KPI 变成质量保障写测试是程序员最讨厌的体力活之一但又是代码质量的底线。AI 在这个场景的价值最大——它能帮你把 80% 的样板测试代码生成出来你只需补充那 20% 的边界场景。场景为上面的 OrderService 生成 JUnit 5 Mockito 测试给 Cursor 的 Prompt为 OrderService 生成 JUnit 5 Mockito 的单元测试要求 1. 使用 ExtendWith(MockitoExtension.class) 和 Mock / InjectMocks 2. 每个测试方法必须包含三段注释// Given // When // Then 3. 必须覆盖正常创建、查询不到抛异常、分页查询带时间范围、逻辑删除失败抛异常 4. 使用 AssertJ 的 assertThat 断言 5. 不要用 SpringBootTest这是纯单元测试不启动 Spring 容器 6. 对 orderRepository.findById 的返回值使用 Optional.of() 和 Optional.empty() 两种场景 package com.example.order.service; ​ import com.example.common.exception.BusinessException; import com.example.common.result.PageResult; import com.example.common.result.Result; import com.example.order.dto.OrderCreateDTO; import com.example.order.dto.OrderQueryDTO; import com.example.order.dto.OrderUpdateDTO; import com.example.order.entity.Order; import com.example.order.enums.ErrorCode; import com.example.order.mapper.OrderMapper; import com.example.order.repository.OrderRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; ​ import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; ​ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; ​ ExtendWith(MockitoExtension.class) DisplayName(OrderService 单元测试) class OrderServiceTest { ​ Mock private OrderRepository orderRepository; ​ Mock private OrderMapper orderMapper; ​ InjectMocks private OrderService orderService; ​ Test DisplayName(正常创建订单返回订单ID) void create_shouldReturnOrderId_whenValidInput() { // Given OrderCreateDTO dto OrderCreateDTO.builder() .userId(10086L) .productName(iPhone 16 Pro) .amount(new BigDecimal(9999.00)) .build(); ​ Order order new Order(); order.setId(1L); given(orderMapper.toEntity(dto)).willReturn(order); given(orderRepository.save(any(Order.class))).willReturn(order); ​ // When ResultLong result orderService.create(dto); ​ // Then assertThat(result.isSuccess()).isTrue(); assertThat(result.getData()).isEqualTo(1L); verify(orderRepository).save(order); } ​ Test DisplayName(查询不存在的订单抛出 BusinessException) void queryById_shouldThrow_whenOrderNotFound() { // Given Long orderId 99999L; given(orderRepository.findByIdAndDeletedFalse(orderId)) .willReturn(Optional.empty()); ​ // When / Then assertThatThrownBy(() - orderService.queryById(orderId)) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.ORDER_NOT_FOUND.getMessage()); } ​ Test DisplayName(分页查询带时间范围返回正确分页结果) void pageQuery_shouldReturnPageResult_withTimeRange() { // Given OrderQueryDTO query new OrderQueryDTO(); query.setPage(1); query.setSize(10); query.setStartTime(LocalDateTime.of(2024, 1, 1, 0, 0)); query.setEndTime(LocalDateTime.of(2024, 12, 31, 23, 59)); ​ Order order1 new Order(); order1.setId(1L); Order order2 new Order(); order2.setId(2L); ListOrder orders List.of(order1, order2); PageOrder page new PageImpl(orders, PageRequest.of(0, 10), 25L); ​ given(orderRepository.findByCreateTimeBetween( query.getStartTime(), query.getEndTime(), any(PageRequest.class))) .willReturn(page); ​ // When ResultPageResultOrder result orderService.pageQuery(query); ​ // Then assertThat(result.isSuccess()).isTrue(); assertThat(result.getData().getTotal()).isEqualTo(25L); assertThat(result.getData().getList()).hasSize(2); } ​ Test DisplayName(逻辑删除失败时抛出异常) void delete_shouldThrow_whenLogicalDeleteReturnsZero() { // Given Long orderId 1L; given(orderRepository.logicalDelete(orderId, any(LocalDateTime.class))) .willReturn(0); ​ // When / Then assertThatThrownBy(() - orderService.delete(orderId)) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.ORDER_NOT_FOUND.getMessage()); } }AI 生成测试的边界Cursor 生成的测试覆盖了主干路径但有几个坑它不会自动帮你想到并发场景缺失create方法如果同时被两个线程调用数据库唯一约束是否会抛异常AI 通常不会写并发测试。金额精度BigDecimal的构造方式对不对new BigDecimal(9999.00)是对的但 AI 有时候会用new BigDecimal(9999.00)浮点构造精度丢失。空指针边界OrderCreateDTO的remark是可选的如果传 nulltoEntity会不会 NPE你需要的操作是让 AI 生成主干你自己补充边界。Prompt 技巧是加一句——请列出 5 个你认为还需要补充的边界测试场景。五、技术文档从写完代码不想写文档到自动同步技术文档的痛点不是写而是代码变了文档没更新久而久之文档就没人信了。AI 可以帮你做到代码即文档、文档随代码走。场景用 SpringDoc AI 生成高质量 OpenAPI 注释package com.example.order.controller; ​ import com.example.common.result.Result; import com.example.common.result.PageResult; import com.example.order.dto.OrderCreateDTO; import com.example.order.dto.OrderQueryDTO; import com.example.order.dto.OrderUpdateDTO; import com.example.order.entity.Order; import com.example.order.service.OrderService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; ​ Tag(name 订单管理, description 订单的增删改查及分页查询接口) RestController RequestMapping(/api/v1/orders) RequiredArgsConstructor public class OrderController { ​ private final OrderService orderService; ​ Operation(summary 创建订单, description 根据用户提交的商品信息创建新订单返回订单ID) ApiResponses({ ApiResponse(responseCode 200, description 创建成功, content Content(schema Schema(implementation Result.class))), ApiResponse(responseCode 400, description 参数校验失败如金额为空、商品名称超限), ApiResponse(responseCode 500, description 系统内部错误) }) PostMapping public ResultLong create( Valid RequestBody io.swagger.v3.oas.annotations.parameters.RequestBody( description 订单创建参数, required true, content Content(schema Schema(implementation OrderCreateDTO.class)) ) OrderCreateDTO dto ) { return orderService.create(dto); } ​ Operation(summary 分页查询订单, description 支持按时间范围筛选默认按创建时间倒序) ApiResponse(responseCode 200, description 查询成功) GetMapping public ResultPageResultOrder pageQuery( Valid ParameterObject // SpringDoc 自动展开查询参数 OrderQueryDTO query ) { return orderService.pageQuery(query); } }Prompt 技巧把已有的 Controller 代码丢给 Cursor然后说——为所有接口添加 SpringDoc OpenAPI 3 注解要求包含中文 summary、描述业务场景、列出 400/500 两种常见异常响应。AI 在规范化、重复化的注释生成上非常可靠至少比人写的一致性好。更进一步让 AI 帮你生成 Markdown 接口文档如果你需要给前端或第三方团队输出离线的 API 文档可以把 SpringDoc 生成的 OpenAPI JSON 喂给 AI让它转成 Markdown请根据以下 OpenAPI 3 规范片段生成前端友好的 Markdown 接口文档 要求 1. 包含请求示例curl 命令 2. 包含成功和失败的响应示例 3. 字段表格中标注是否必填和示例值 4. 用中文描述每个字段的业务含义AI 生成的 Markdown 格式统一、不会漏字段你只需要把示例数据换成真实的就行。六、AI 代码审查知道什么能信什么必须人工这是最重要的一节。很多兄弟用 AI 生成代码后直接合进仓库这是极其危险的操作。AI 代码审查的正确姿势是AI 做初筛人工做终审。AI 适合做的代码审查白名单审查类型AI 准确率说明空指针风险85%常见的 NPE 模式能识别资源泄露未关闭流/连接80%明显的 try-with-resources 遗漏重复代码90%结构相似度检测很成熟明显性能问题SQL N175%需要结合 ORM 上下文代码风格/命名规范95%规则明确AI 最擅长AI 不适合做的代码审查黑名单审查类型为什么 AI 不行必须由人工检查业务逻辑正确性AI 不懂你的业务规则支付金额校验、状态机流转并发安全需要理解全局执行顺序竞态条件、死锁隐患权限边界需要理解系统安全模型接口是否越权、数据是否隔离数据一致性需要理解分布式事务策略最终一致性 vs 强一致性选择架构设计合理性需要理解演进方向这坨代码该不该放在这里实战用 AI 做初筛 人工做终审的 checklist/** * AI 辅助代码审查 Checklist适用于 Java 后端代码评审 * * 【AI 初筛阶段】——把代码丢给 Cursor/Copilot让它回答以下问题 * 1. 这段代码中有明显的空指针风险吗列出具体行号。 * 2. 有未关闭的资源Stream/Connection/Session吗 * 3. 有重复代码可以提取成方法吗 * 4. 异常处理是否完善有没有吞掉异常或只打印 e.printStackTrace() * 5. 日志中有没有可能打印敏感信息 * * 【人工终审阶段】——以下必须人工判断不要信任 AI * 1. 这个接口的入参校验是否覆盖了所有业务约束比如金额必须大于0 * 2. 这个方法的事务边界是否合理是否会有长事务风险 * 3. 这个查询在数据量达到百万级时会不会慢是否需要加索引 * 4. 这个 API 的权限控制是否到位有没有做数据范围隔离 * 5. 如果下游服务挂了这段代码会怎么表现有没有熔断/降级/重试策略 * 6. 这个改动是否破坏了现有契约下游调用方是否需要同步修改 */老梁的实操建议在 GitLab/GitHub 的 MR/PR 流程里把 AI 初筛做成一个可选的 pipeline 步骤——不阻塞合并但自动生成审查评论。人工 reviewer 把精力放在黑名单事项上白名单事项让 AI 先扫一遍。这样一次代码审查的时间能从 30 分钟压缩到 10 分钟同时质量不降反升。七、实战建议把 AI 真正用进工作流的三条铁律1. Prompt 即代码要像写代码一样写 Prompt很多人生成代码质量差不是因为 AI 不行是因为 Prompt 写得像聊天。记住这个模板【角色】你是一个有10年经验的Java后端工程师 【上下文】以下是项目现有代码规范... 【任务】请为...生成... 【约束】 - 使用... - 必须包含... - 不要... - 遵循... 【输出格式】Java 代码包含 package/import/注释约束条件越具体AI 输出越稳。这跟写代码接口定义是一个道理——参数类型定了返回值就不会太离谱。2. 建立AI 生成 → 人工 Review → 单元测试 → 合入的强制流水线不要因为 AI 生成的代码看起来对就直接用。最低限度的检查能不能编译过已有的单元测试能不能跑过AI 生成的代码你有没有逐行看懂不是扫一眼是逐行3. 用 AI 做增量不要做全量不要对一个完全陌生的技术领域说给我写一个完整的微服务架构。AI 在增量任务上表现最好基于现有代码生成相似模块基于现有测试生成更多边界用例基于现有文档生成更新版本全量架构设计、核心算法推导、复杂业务建模——这些是你的主场别外包。AI 不是你的替代者它是你的放大器。你强它让你更强你弱它只会把你的错误复制得更快。用好了 Copilot 和 Cursor一个 Java 后端工程师的日常编码效率提升 30%~50% 是完全可行的。但前提是你始终保持清醒AI 负责重复你负责判断AI 负责速度你负责质量。下篇预告Day 57K8s 核心概念速通——Pod、Deployment、Service、Ingress一次打通云原生的任督二脉。从 Docker 到 K8s你的应用终于要从单机走向集群了。本文示例代码基于 Spring Boot 3.2.5 JDK 21 JUnit 5.10 Mockito 5.11已在本地验证编译通过。
返回列表