Spring Boot 微服务开发实战:OpenCode 智能助手全流程指南
Java 开发者长期受困于效率低下的编程工具——将 Spring Boot 与 OpenCode 结合,开发效率可以获得质的飞跃。在阅读完前 9 篇教程后,你已经全面掌握了 OpenCode 的功能。现在,让我们用一个完整的 Spring Boot 微服务项目,把所有知识串联起来——从项目初始化到 Docker 容器化部署,全程使用 OpenCode 辅助完成。
本章目标
我们将使用 OpenCode 构建一个用户订单管理微服务,包含以下技术栈:Spring Boot 3.3 + JDK 17,JPA 数据持久化(MySQL),RESTful API(CRUD + 分页查询),JWT 认证与权限控制,全局异常处理,以及 Docker 容器化部署。在整个过程中,你会看到如何逐步向 OpenCode 下达指令,并观察它如何辅助完成工作。
一、项目初始化
1.1 创建项目结构
在终端打开工作目录并启动 OpenCode,然后发出指令:
你:请帮我创建一个 Spring Boot 3.3 微服务项目,项目名为 user-order-service,采用 JDK 17 和 Maven 构建。要求生成标准的包结构,包含 controller、service、repository、model、dto、config、security 七个包。
OpenCode 随即生成如下目录结构:
user-order-service/
├── pom.xml
├── src/
│ └── main/
│ ├── java/com/example/userorderservice/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── repository/
│ │ ├── model/
│ │ ├── dto/
│ │ ├── config/
│ │ └── security/
│ └── resources/
│ └── application.yml
1.2 /init 生成 AGENTS.md
你:请运行 /init 为项目生成 AGENTS.md
OpenCode 会分析项目结构并生成 AGENTS.md:
# User Order Service
## 项目概述
Spring Boot 3.3 微服务,用于用户订单管理。
## 技术栈
- Java 17 + Maven
- Spring Boot 3.3.x
- Spring Data JPA + MySQL
- Spring Security + JWT
- Lombok
## 代码规范
- 遵循阿里巴巴 Java 开发手册
- 使用 Lombok 简化 POJO
- RESTful API 设计
- 统一响应格式
## 构建命令
- 编译:mvn clean compile
- 打包:mvn clean package -DskipTests=true
- 运行:java -jar target/user-order-service.jar
1.3 opencode.json 配置
{
"provider": "zai",
"model": "zai/glm-4-plus",
"small_model": "zai/glm-4-flash",
"permission": {
"allow": [
"edit src/**",
"write src/**",
"bash(mvn *)",
"bash(java *)",
"bash(mkdir *)"
]
}
}
💡 选用智谱 GLM-4 作为主力模型,对中国 Java 开发者来说,响应迅速且性价比出色。
1.4 Maven 依赖
你:请帮我生成 pom.xml,包含以下依赖:Spring Boot Web、Data JPA、Security、Validation,MySQL Driver,Lombok,JWT(jjwt 0.12.x),SpringDoc OpenAPI(Swagger 文档),JDK 17,Spring Boot 3.3.6。
OpenCode 生成的核心依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.6</version>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
二、数据模型与数据库
2.1 定义实体
你:请帮我创建 User 和 Order 两个 JPA 实体。
User:id(Long,自增),username,email,password,role(ADMIN/USER),
created_at,updated_at。一个 User 拥有多个 Order。
Order:id(Long,自增),user_id(外键关联 User),product_name,
quantity,price(BigDecimal),status(PENDING/CONFIRMED/SHIPPED/COMPLETED),
created_at,updated_at。
使用 Lombok,字段采用驼峰命名,表名使用 info_ 前缀。
OpenCode 生成的实体代码:
// User.java
@Entity
@Table(name = "info_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(nullable = false)
private String password;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private Role role;
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public enum Role {
ADMIN, USER
}
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
// Order.java
@Entity
@Table(name = "info_order")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(name = "product_name", nullable = false, length = 200)
private String productName;
@Column(nullable = false)
private Integer quantity;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private OrderStatus status;
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public enum OrderStatus {
PENDING, CONFIRMED, SHIPPED, COMPLETED
}
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
if (status == null) status = OrderStatus.PENDING;
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
2.2 Repository 接口
你:为 User 和 Order 创建 Repository 接口。
UserRepository 需要包含:findByUsername、findByEmail。
OrderRepository 需要包含:findByUserId(分页)、findByStatus、countByUserId。
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
public interface OrderRepository extends JpaRepository<Order, Long> {
Page<Order> findByUserId(Long userId, Pageable pageable);
List<Order> findByStatus(Order.OrderStatus status);
long countByUserId(Long userId);
}
2.3 数据库配置
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_order_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:root}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQLDialect
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
server:
port: 8080
jwt:
secret: ${JWT_SECRET:mySecretKeyForDevelopmentOnlyDoNotUseInProduction2026}
expiration: 86400000
💡 通过 ${DB_USERNAME:root} 的格式,既支持环境变量覆盖,也可以在开发时使用默认值。
三、RESTful API 开发
3.1 统一响应与 DTO
首先让 OpenCode 创建基础结构:
你:请创建统一的 API 响应封装 ApiResponse<T>,包含 code、message、data 字段。
然后创建以下 DTO:
- RegisterRequest:username、email、password
- LoginRequest:username、password
- LoginResponse:token、username、role
- OrderRequest:productName、quantity、price
- OrderResponse:id、productName、quantity、price、status、createdAt
所有 DTO 都加上 Validation 注解。
// ApiResponse.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(200, message, data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
}
// RegisterRequest.java
@Data
public class RegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度 3-50")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度 6-100")
private String password;
}
3.2 Service 层
你:创建 UserService 和 OrderService。
UserService:
- register:注册用户,密码加密
- login:验证密码,返回 JWT token
- findByUsername:查用户信息
OrderService:
- createOrder:创建订单
- getUserOrders:分页查询用户订单
- updateOrderStatus:更新订单状态
- getOrderById:查询订单详情
// UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Transactional
public User register(RegisterRequest request) {
if (userRepository.existsByUsername(request.getUsername())) {
throw new BusinessException(400, "用户名已存在");
}
if (userRepository.existsByEmail(request.getEmail())) {
throw new BusinessException(400, "邮箱已被注册");
}
User user = User.builder()
.username(request.getUsername())
.email(request.getEmail())
.password(passwordEncoder.encode(request.getPassword()))
.role(User.Role.USER)
.build();
return userRepository.save(user);
}
public LoginResponse login(LoginRequest request) {
User user = userRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new BusinessException(401, "用户名或密码错误"));
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new BusinessException(401, "用户名或密码错误");
}
String token = JwtUtil.generateToken(user);
return new LoginResponse(token, user.getUsername(), user.getRole().name());
}
}
// OrderService.java
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
@Transactional
public Order createOrder(Long userId, OrderRequest request) {
Order order = Order.builder()
.user(User.builder().id(userId).build())
.productName(request.getProductName())
.quantity(request.getQuantity())
.price(request.getPrice())
.status(Order.OrderStatus.PENDING)
.build();
return orderRepository.save(order);
}
public Page<Order> getUserOrders(Long userId, int page, int size) {
return orderRepository.findByUserId(userId, PageRequest.of(page, size, Sort.by("createdAt").descending()));
}
@Transactional
public Order updateOrderStatus(Long orderId, Order.OrderStatus status) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new BusinessException(404, "订单不存在"));
order.setStatus(status);
return orderRepository.save(order);
}
}
3.3 Controller 层
你:创建 AuthController 和 OrderController。
AuthController:POST /api/auth/register、POST /api/auth/login
OrderController:POST /api/orders、GET /api/orders、PUT /api/orders/{id}/status、GET /api/orders/{id}
所有接口返回 ApiResponse<T>,并加上 Swagger 注解。
OrderController 的接口需要登录后才能访问。
// AuthController.java
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Tag(name = "认证管理")
public class AuthController {
private final UserService userService;
@PostMapping("/register")
@Operation(summary = "用户注册")
public ApiResponse<User> register(@Valid @RequestBody RegisterRequest request) {
return ApiResponse.success(userService.register(request));
}
@PostMapping("/login")
@Operation(summary = "用户登录")
public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
return ApiResponse.success(userService.login(request));
}
}
// OrderController.java
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
@Tag(name = "订单管理")
public class OrderController {
private final OrderService orderService;
@PostMapping
@Operation(summary = "创建订单")
public ApiResponse<Order> createOrder(
@RequestAttribute("userId") Long userId,
@Valid @RequestBody OrderRequest request) {
return ApiResponse.success("订单创建成功", orderService.createOrder(userId, request));
}
@GetMapping
@Operation(summary = "查询我的订单")
public ApiResponse<Page<Order>> getMyOrders(
@RequestAttribute("userId") Long userId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return ApiResponse.success(orderService.getUserOrders(userId, page, size));
}
@PutMapping("/{id}/status")
@Operation(summary = "更新订单状态")
public ApiResponse<Order> updateStatus(
@PathVariable Long id,
@RequestParam Order.OrderStatus status) {
return ApiResponse.success("状态更新成功", orderService.updateOrderStatus(id, status));
}
@GetMapping("/{id}")
@Operation(summary = "查询订单详情")
public ApiResponse<Order> getOrder(@PathVariable Long id) {
return ApiResponse.success(orderService.getOrderById(id));
}
}
3.4 全局异常处理
你:创建全局异常处理器,处理:
- BusinessException(业务异常)
- MethodArgumentNotValidException(参数校验异常)
- AccessDeniedException(权限异常)
- 其他未捕获异常
所有异常统一返回 ApiResponse 格式。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusiness(BusinessException e) {
return ResponseEntity.status(e.getCode())
.body(ApiResponse.error(e.getCode(), e.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidation(
MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors()
.forEach(err -> errors.put(err.getField(), err.getDefaultMessage()));
return ResponseEntity.badRequest()
.body(ApiResponse.error(400, "参数校验失败"));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleOther(Exception e) {
return ResponseEntity.status(500)
.body(ApiResponse.error(500, "服务器内部错误"));
}
}
四、安全认证
4.1 JWT 工具类
你:创建 JwtUtil 工具类,包含:
- generateToken:根据 User 信息生成 JWT
- validateToken:验证 token 有效性
- getUsernameFromToken:从 token 中提取用户名
- getUserIdFromToken:从 token 中提取用户 ID
使用 jjwt 0.12.x API。
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
public String generateToken(User user) {
return Jwts.builder()
.subject(user.getUsername())
.claim("userId", user.getId())
.claim("role", user.getRole().name())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
public String getUsernameFromToken(String token) {
return parseToken(token).getSubject();
}
public Long getUserIdFromToken(String token) {
return parseToken(token).get("userId", Long.class);
}
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (Exception e) {
return false;
}
}
}
4.2 Security 配置
你:创建 Spring Security 配置,要求:
- /api/auth/** 放行(注册登录不需要认证)
- /swagger-ui/**、/v3/api-docs/** 放行
- 其他所有接口需要 JWT 认证
- 禁用 CSRF(前后端分离)
- 禁用 Session(无状态)
- 添加 JWT 过滤器
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4.3 JWT 过滤器
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.validateToken(token)) {
Long userId = jwtUtil.getUserIdFromToken(token);
String username = jwtUtil.getUsernameFromToken(token);
request.setAttribute("userId", userId);
request.setAttribute("username", username);
List<SimpleGrantedAuthority> authorities = List.of(
new SimpleGrantedAuthority("ROLE_USER"));
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(request, response);
}
}
五、测试
5.1 让 OpenCode 生成测试
你:为 UserService 和 OrderController 编写单元测试。
UserService 测试使用 Mock 用户仓库。
OrderController 用 MockMvc 测试 API 接口。
使用 JUnit 5 + Mockito。
OpenCode 生成的测试代码:
// UserServiceTest.java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserService userService;
@Test
void register_success() {
RegisterRequest request = new RegisterRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setPassword("123456");
when(userRepository.existsByUsername("testuser")).thenReturn(false);
when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
when(passwordEncoder.encode("123456")).thenReturn("encoded_password");
when(userRepository.save(any(User.class))).thenAnswer(inv -> inv.getArgument(0));
User result = userService.register(request);
assertThat(result.getUsername()).isEqualTo("testuser");
assertThat(result.getRole()).isEqualTo(User.Role.USER);
verify(userRepository).save(any(User.class));
}
@Test
void register_duplicateUsername_throwsException() {
RegisterRequest request = new RegisterRequest();
request.setUsername("existing");
request.setEmail("new@example.com");
request.setPassword("123456");
when(userRepository.existsByUsername("existing")).thenReturn(true);
assertThatThrownBy(() -> userService.register(request))
.isInstanceOf(BusinessException.class)
.hasMessageContaining("用户名已存在");
}
@Test
void login_success() {
User user = User.builder()
.id(1L).username("testuser")
.password("encoded_pwd").role(User.Role.USER).build();
when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(user));
when(passwordEncoder.matches("123456", "encoded_pwd")).thenReturn(true);
LoginResponse response = userService.login(
new LoginRequest("testuser", "123456"));
assertThat(response.getUsername()).isEqualTo("testuser");
assertThat(response.getToken()).isNotNull();
}
}
5.2 运行测试并分析结果
你:运行 mvn test,看看测试结果
OpenCode:
✅ 运行 mvn test
📊 结果:3 tests passed, 0 failed
全部测试通过!
如果测试失败的场景:
OpenCode:
❌ UserServiceTest.login_success 测试失败
原因:NullPointerException at UserService.java:45
分析:JwtUtil 未注入,因为它是静态方法调用
修复方案:将 JwtUtil 注入 UserService,改为实例方法
你:请修复这个问题
OpenCode:
✅ 已修改 UserService,注入 JwtUtil
✅ 重新运行测试 → 全部通过
六、Docker 容器化
6.1 Dockerfile
你:请为项目创建 Dockerfile,使用多阶段构建:
第一阶段:Maven 构建
第二阶段:JRE 17 运行
最终镜像尽量小。
# 构建阶段
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests=true -B
# 运行阶段
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
ENV JAVA_OPTS="-Xmx256m -Xms128m"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
6.2 docker-compose.yml
你:创建 docker-compose.yml,包含三个服务:
1. user-order-service:Spring Boot 应用
2. mysql:MySQL 8 数据库
3. redis:Redis 缓存(预留)
使用环境变量管理敏感信息,数据持久化。
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- DB_HOST=mysql
- DB_PORT=3306
- DB_NAME=user_order_db
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- JWT_SECRET=${JWT_SECRET}
depends_on:
mysql:
condition: service_healthy
restart: unless-stopped
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
- MYSQL_DATABASE=user_order_db
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
mysql_data:
redis_data:
6.3 启动服务
你:帮我启动 docker-compose,检查服务是否正常运行
OpenCode:
✅ 创建 .env 文件(DB_USERNAME=root, DB_PASSWORD=xxx, JWT_SECRET=xxx)
✅ 运行 docker-compose up -d --build
✅ 等待 MySQL 健康检查通过
✅ 检查 app 日志 → Started UserOrderServiceApplication in 8.2s
✅ 测试 curl http://localhost:8080/api/auth/login → 200 OK
七、调试实战
7.1 编译错误
你:运行 mvn compile,看看有没有编译错误
OpenCode:
❌ 编译错误:找不到符号 BusinessException
分析:BusinessException 类尚未创建
修复:创建 BusinessException 自定义异常类
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
7.2 Bean 注入失败
OpenCode:
❌ 启动失败:Parameter 0 of constructor in UserService required a bean of type
'PasswordEncoder' that could not be found.
分析:SecurityConfig 中定义了 passwordEncoder() Bean,
但 OpenCode 之前用了 @RequiredArgsConstructor 注入,
可能存在循环依赖或 Bean 加载顺序问题。
修复:确认 SecurityConfig 在 UserService 之前加载,
检查 @Configuration 注解是否正确。
你:帮我修复这个 Bean 注入问题
OpenCode:
✅ 检查发现 SecurityConfig 缺少 @Configuration 注解(已添加)
✅ 添加后重新编译 → 成功
✅ 启动应用 → 正常运行
7.3 数据库连接失败
OpenCode:
❌ 启动失败:Communications link failure
分析:MySQL 容器未启动或连接配置错误
检查步骤:
1. docker-compose ps → mysql 状态是否 Up
2. application.yml 中数据库地址是否正确
3. MySQL 密码是否匹配
修复:application.yml 中 DB_HOST 应为 mysql(Docker 网络中的服务名),
而不是 localhost
你:修改 application.yml,让它在 Docker 环境中使用 mysql 作为主机名
spring:
datasource:
url: jdbc:mysql://${DB_HOST:mysql}:${DB_PORT:3306}/${DB_NAME:user_order_db}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
八、Git 工作流
8.1 生成 .gitignore
你:帮我生成 Java Spring Boot 项目的 .gitignore
# Maven
target/
# IDE
.idea/
*.iml
.vscode/
.settings/
.classpath
.project
# OS
.DS_Store
Thumbs.db
# Env
.env
*.log
# Claude Code
.claude/settings.json
8.2 提交代码
你:请为当前所有改动生成规范的 commit message
OpenCode:
建议的 commit message:
feat(user-order): 初始化 Spring Boot 用户订单微服务
- 搭建 Spring Boot 3.3 + JDK 17 项目结构
- 实现 User/Order JPA 实体与 Repository
- 完成 RESTful API(认证 + 订单 CRUD)
- 添加 JWT 认证与 Spring Security 配置
- 实现全局异常处理与统一响应格式
- 添加 Swagger/OpenAPI 文档
- 编写单元测试(UserService + OrderController)
- 添加 Docker 多阶段构建与 docker-compose 配置
Co-Authored-By: OpenCode <opencode@ai>
8.3 Plan 模式代码审查
你:切换到 Plan 模式,帮我对整个项目做一次代码审查
OpenCode(Plan 模式):
📋 代码审查报告
✅ 优点:
- 项目结构清晰,分层合理
- 统一响应格式规范
- Lombok 使用恰当
- 异常处理完善
⚠️ 改进建议:
1. Order 实体的 @ManyToOne 应添加 @JsonIgnoreProperties 避免 JSON 序列化循环引用
2. application.yml 中的 JWT secret 不应硬编码,建议使用环境变量
3. 缺少分页参数的合法性校验(page 不能为负数)
4. 建议为 OrderService 添加 @Transactional(readOnly = true) 到查询方法
5. 全局异常处理器应记录异常日志
是否要我自动修复这些问题?
九、总结
全流程回顾
| 阶段 | OpenCode 做了什么 | 你的角色 |
|---|---|---|
| 项目初始化 | 生成目录、AGENTS.md、pom.xml | 指定技术栈 |
| 数据模型 | 生成 Entity、Repository | 描述字段需求 |
| API 开发 | 生成 Controller、Service、DTO | 定义接口规范 |
| 安全认证 | 生成 JWT、Security 配置 | 指定认证方案 |
| 测试 | 生成测试代码、运行测试 | 检查覆盖率 |
| 容器化 | 生成 Dockerfile、docker-compose | 指定服务依赖 |
| 调试 | 分析错误、定位原因、修复代码 | 确认修复方向 |
| 代码审查 | Plan 模式审查,给出改进建议 | 决定是否采纳 |
Java 项目使用 OpenCode 的技巧
💡 指令要具体:不要说“帮我写个接口”,而要说“创建 POST /api/orders,接收 OrderRequest DTO,返回 ApiResponse,需要 JWT 认证”。
💡 分步推进:不要一次性让 OpenCode 写完整个项目,按模块推进——先 Model,再 Repository,然后 Service,最后 Controller。
💡 善用 Plan 模式:面对复杂功能,先用 Plan 模式让 OpenCode 提供方案,确认后再用 Build 模式实施。
💡 让 OpenCode 帮你调试:遇到编译错误、运行时异常时,将错误日志直接提供给 OpenCode,它通常能快速定位并修复。
⚠️ 注意 Lombok 兼容性:确保 IDE 安装了 Lombok 插件,否则编译可能通过但 IDE 会报红。
⚠️ JPA 延迟加载:@ManyToOne(fetch = FetchType.LAZY) 在序列化时可能触发 LazyInitializationException,注意在 Service 层处理好关联数据的加载。