Cursor + Spring Boot实战:从零写一个RESTful API

一、项目需求

搭建一个用户管理系统的后端 API:

复制代码
┌──────────────────────────────────────────────────┐
│               用户管理 RESTful API                │
├────────┬──────────┬─────────────────────────────┤
│ 接口    │  方法    │           说明                │
├────────┼──────────┼─────────────────────────────┤
│/users  │ GET      │ 获取用户列表(分页)            │
│/users  │ POST     │ 创建用户                      │
│/users/{id}│ GET   │ 获取单个用户                   │
│/users/{id}│ PUT   │ 更新用户                      │
│/users/{id}│ DELETE│ 删除用户                      │
└────────┴──────────┴─────────────────────────────┘

技术栈:

  • Spring Boot 3.2 + Java 17
  • MyBatis-Plus(简化数据库操作)
  • MySQL 8.0
  • Lombok

二、Cursor 新建项目

2.1 创建 Spring Boot 项目

在 Cursor 中打开终端,输入:

bash 复制代码
# 方式一:用 Spring Initializr(推荐在 Cursor Terminal 中操作)
# 访问 https://start.spring.io/ 下载项目,然后在 Cursor 中打开

# 方式二:或者直接让 Cursor 生成
# 打开 Cursor 的 Composer (Ctrl+I / Cmd+I),输入:

Cursor Prompt:

复制代码
帮我创建一个 Spring Boot 3.2 项目,要求:
1. Java 17
2. 依赖:spring-boot-starter-web, mybatis-plus-spring-boot3-starter, mysql-connector-j, lombok
3. groupId: com.example, artifactId: user-management
4. 在 application.yml 中配置 MySQL 数据源(用户名root,密码123456,数据库user_db)
5. 创建数据库建表SQL:users表(id, username, email, phone, create_time, update_time, is_deleted逻辑删除)

Cursor 会自动帮你生成项目结构和配置文件。

2.2 项目结构

生成后项目结构应该是:

复制代码
user-management/
├── src/main/java/com/example/usermanagement/
│   ├── UserManagementApplication.java
│   ├── entity/
│   │   └── User.java
│   ├── mapper/
│   │   └── UserMapper.java
│   ├── service/
│   │   ├── UserService.java
│   │   └── impl/
│   │       └── UserServiceImpl.java
│   ├── controller/
│   │   └── UserController.java
│   ├── dto/
│   │   ├── UserCreateDTO.java
│   │   ├── UserUpdateDTO.java
│   │   └── UserVO.java
│   ├── common/
│   │   ├── Result.java
│   │   └── PageResult.java
│   └── config/
│       └── MybatisPlusConfig.java
├── src/main/resources/
│   ├── application.yml
│   └── db/
│       └── schema.sql
└── pom.xml

三、Cursor 对话实录:搭框架

3.1 让 Cursor 生成实体类和 Mapper

Prompt(在 Cursor Chat 中输入):

复制代码
基于上面的 users 表结构,帮我生成以下文件:

1. User.java (entity) - 使用 MyBatis-Plus 注解 @TableName, @TableId, @TableField
2. UserMapper.java - 继承 BaseMapper<User>
3. MybatisPlusConfig.java - 配置分页插件和逻辑删除

注意:
- 使用 @TableLogic 实现逻辑删除
- id 使用雪花算法 @TableId(type = IdType.ASSIGN_ID)
- create_time 和 update_time 使用 @TableField(fill = FieldFill.INSERT/UPDATE)
- 代码风格要简洁,用 Lombok

3.2 Cursor 生成的实体类

java 复制代码
package com.example.usermanagement.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("users")
public class User {

    /** 主键ID(雪花算法) */
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    /** 用户名 */
    private String username;

    /** 邮箱 */
    private String email;

    /** 手机号 */
    private String phone;

    /** 创建时间(自动填充) */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /** 更新时间(自动填充) */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /** 逻辑删除(0=正常,1=已删除) */
    @TableLogic
    private Integer isDeleted;
}

四、Cursor 对话实录:写业务代码

4.1 Service 层

Prompt:

复制代码
帮我生成 UserService 和 UserServiceImpl:

要求:
1. 分页查询用户列表(支持按用户名模糊搜索)
2. 根据 ID 获取用户详情
3. 创建用户(校验用户名不能重复,邮箱格式校验)
4. 更新用户(只更新非空字段)
5. 删除用户(逻辑删除)

返回值统一用 Result<T> 包装,分页用 PageResult<T>
DTO 和 VO 要和 Entity 分开
java 复制代码
// Service 接口
public interface UserService {
    /** 分页查询 */
    PageResult<UserVO> listUsers(int pageNum, int pageSize, String username);
    /** 获取详情 */
    UserVO getUserById(Long id);
    /** 创建用户 */
    Long createUser(UserCreateDTO dto);
    /** 更新用户 */
    void updateUser(Long id, UserUpdateDTO dto);
    /** 删除用户 */
    void deleteUser(Long id);
}
java 复制代码
// Service 实现
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    @Override
    public PageResult<UserVO> listUsers(int pageNum, int pageSize, String username) {
        Page<User> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(username != null && !username.isBlank(), User::getUsername, username);
        wrapper.orderByDesc(User::getCreateTime);

        userMapper.selectPage(page, wrapper);

        List<UserVO> voList = page.getRecords().stream()
                .map(this::toVO)
                .collect(Collectors.toList());

        return new PageResult<>(voList, page.getTotal());
    }

    @Override
    public UserVO getUserById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }
        return toVO(user);
    }

    @Override
    @Transactional
    public Long createUser(UserCreateDTO dto) {
        // 校验用户名唯一
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, dto.getUsername());
        if (userMapper.selectCount(wrapper) > 0) {
            throw new RuntimeException("用户名已存在");
        }

        User user = new User();
        user.setUsername(dto.getUsername());
        user.setEmail(dto.getEmail());
        user.setPhone(dto.getPhone());
        userMapper.insert(user);
        return user.getId();
    }

    @Override
    @Transactional
    public void updateUser(Long id, UserUpdateDTO dto) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }

        // 只更新非空字段
        if (dto.getUsername() != null) user.setUsername(dto.getUsername());
        if (dto.getEmail() != null) user.setEmail(dto.getEmail());
        if (dto.getPhone() != null) user.setPhone(dto.getPhone());

        userMapper.updateById(user);
    }

    @Override
    public void deleteUser(Long id) {
        userMapper.deleteById(id); // 逻辑删除
    }

    private UserVO toVO(User user) {
        return UserVO.builder()
                .id(user.getId())
                .username(user.getUsername())
                .email(user.getEmail())
                .phone(user.getPhone())
                .createTime(user.getCreateTime())
                .build();
    }
}

4.2 Controller 层

Prompt:

复制代码
帮我生成 UserController,遵循 RESTful 风格:
- GET    /api/users?page=1&size=10&username=xxx
- POST   /api/users
- GET    /api/users/{id}
- PUT    /api/users/{id}
- DELETE /api/users/{id}

用 @Valid 做参数校验,统一异常处理
java 复制代码
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Tag(name = "用户管理")
public class UserController {

    private final UserService userService;

    @GetMapping
    @Operation(summary = "分页查询用户列表")
    public Result<PageResult<UserVO>> listUsers(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String username) {
        return Result.success(userService.listUsers(page, size, username));
    }

    @GetMapping("/{id}")
    @Operation(summary = "获取用户详情")
    public Result<UserVO> getUser(@PathVariable Long id) {
        return Result.success(userService.getUserById(id));
    }

    @PostMapping
    @Operation(summary = "创建用户")
    public Result<Long> createUser(@Valid @RequestBody UserCreateDTO dto) {
        return Result.success(userService.createUser(dto));
    }

    @PutMapping("/{id}")
    @Operation(summary = "更新用户")
    public Result<Void> updateUser(@PathVariable Long id,
                                     @Valid @RequestBody UserUpdateDTO dto) {
        userService.updateUser(id, dto);
        return Result.success();
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "删除用户")
    public Result<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return Result.success();
    }
}

五、Cursor 对话实录:写测试

Prompt:

复制代码
帮我写 UserController 的集成测试类,使用 @SpringBootTest + MockMvc:
1. 测试创建用户(正常 + 用户名重复)
2. 测试分页查询
3. 测试获取用户详情(存在 + 不存在)
4. 测试更新用户
5. 测试删除用户
java 复制代码
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void createUser_success() throws Exception {
        UserCreateDTO dto = new UserCreateDTO();
        dto.setUsername("testuser");
        dto.setEmail("test@example.com");

        mockMvc.perform(post("/api/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(dto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data").isNumber());
    }
}

六、接口测试

用 Apifox / Postman / cURL 测试:

bash 复制代码
# 1. 创建用户
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"张三","email":"zhangsan@example.com","phone":"13800138000"}'

# 2. 查询用户列表
curl http://localhost:8080/api/users?page=1\&size=10

# 3. 获取用户详情
curl http://localhost:8080/api/users/1

# 4. 更新用户
curl -X PUT http://localhost:8080/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"email":"newemail@example.com"}'

# 5. 删除用户
curl -X DELETE http://localhost:8080/api/users/1

七、Cursor 使用技巧总结

7.1 Prompt 编写技巧

复制代码
┌─────────────────────────────────────────────────────────┐
│  ✅ 好的 Prompt                           │  ❌ 差的 Prompt            │
├───────────────────────────────┬──────────────────────────┤
│ "用 MyBatis-Plus 写,雪花算法ID" │ "写一个用户管理"           │
│ "返回 Result<T> 包装"           │ "给我代码"                │
│ "校验用户名唯一,邮箱格式"       │ "加上校验"                │
│ "只更新非空字段"                │ "写 update 方法"          │
│ "分页查询,按创建时间倒序"       │ "查询所有用户"            │
└───────────────────────────────┴──────────────────────────┘

7.2 Cursor Debug 技巧

当代码有 bug 时,不要直接让 Cursor "帮我修"。正确做法:

复制代码
Step 1: 先自己看报错信息,理解错误原因
Step 2: 在 Cursor Chat 中粘贴报错日志
Step 3: 告诉 Cursor 你已经做了什么排查
Step 4: 让 Cursor 给出修复建议,而不是直接给完整代码

示例 Prompt:
"用户创建时报错 DuplicateKeyException,我已经检查了数据库确实有唯一索引。
但我的代码里有校验用户名唯一性,为什么还会报这个错?
我的校验逻辑是:[粘贴代码]。帮我看看问题在哪。"

7.3 效率对比

复制代码
┌─────────────────────────────────────────────────────┐
│         传统开发 vs Cursor 开发(本项目管理API)        │
├──────────────┬──────────────┬───────────────────────┤
│     步骤      │  传统方式     │  Cursor 辅助            │
├──────────────┼──────────────┼───────────────────────┤
│ 创建项目      │  10分钟       │  2分钟(自动生成)        │
│ 写实体类      │  15分钟       │  1分钟(Prompt生成)     │
│ 写 Mapper     │  5分钟        │  30秒                   │
│ 写 Service    │  40分钟       │  5分钟(Prompt+微调)    │
│ 写 Controller │  20分钟       │  3分钟                  │
│ 写 DTO/VO     │  15分钟       │  2分钟                  │
│ 写异常处理    │  15分钟       │  3分钟                  │
│ 写测试        │  30分钟       │  5分钟                  │
│ 调试Bug      │  30分钟       │  10分钟                 │
├──────────────┼──────────────┼───────────────────────┤
│ 总计          │  ~3小时       │  ~30分钟               │
└──────────────┴──────────────┴───────────────────────┘

八、踩坑记录

踩坑1:MyBatis-Plus 分页不生效

忘记配置分页插件,分页查询返回的是全部数据。

java 复制代码
// ❌ 忘记加这个配置
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

踩坑2:逻辑删除和查询冲突

配置了 @TableLogic 后,selectById 会自动加 is_deleted=0 条件。如果需要查包含已删除的数据,需要手写 SQL。

踩坑3:Cursor 生成的 import 可能缺失

Cursor 有时不会自动添加所有 import,特别是自定义的类。编译报错后让 Cursor "帮我修复 import" 即可。


🎉 总结 :Cursor 最大的价值不是"帮你写代码",而是把你的想法快速变成可运行的代码,让你把精力放在架构设计和业务逻辑上。用对 Prompt,效率可以提升 5-10 倍。

如果觉得有帮助,点赞 + 收藏支持一下!有问题欢迎评论区讨论 💬

相关推荐
码云之上2 小时前
聊聊如何设计一个高效、稳定的 Node.js 接入层
前端·后端·node.js
IT_陈寒3 小时前
Vite项目build后路由404了?你可能漏了这个小配置
前端·人工智能·后端
宸津-代码粉碎机3 小时前
Spring AI企业级实战|从RAG优化到Agent多工具调度
java·大数据·人工智能·后端·python·spring
吴佳浩3 小时前
AI Infra 的真相:Go 没输,rust也不是取代
后端·rust·go
喵个咪4 小时前
实时游戏网络协议深度对比:KCP vs WebRTC vs WebSocket
后端·websocket·webrtc
普通网友4 小时前
springboot之集成Elasticsearch
spring boot·后端·elasticsearch
QuZero4 小时前
Guava Cache Deep Dive
java·后端·算法·guava
invicinble4 小时前
关于flowable流程引擎技术栈相关
spring boot
leeyi4 小时前
SSE 实时推流 —— Token 怎么一个个蹦出来
后端·agent