mcpField
S
spring-boot

spring-boot

github.com/piomin开发1.4k89 次安装1 周前更新
代码仓库

关于 spring-boot

面向开发者的 Spring Boot 4.x 开发技能,适用于构建面向生产的服务。它提供清晰架构、安全、测试、数据访问和部署方面的指导,并包含具体规范与代码模式。

主要功能

  1. 01分层架构与构造器注入
  2. 02REST 校验和 Problem Details 错误
  3. 03安全及 OAuth2/JWT 指引
  4. 04Flyway 迁移与数据访问模式
  5. 05单元和集成测试流程
  6. 06Actuator 健康检查配置

适用场景

  1. 01实现前设计 Spring Boot 服务
  2. 02构建带校验的 REST 端点和错误处理
  3. 03添加安全机制和数据库迁移
  4. 04部署前验证测试与健康检查

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/piomin/claude-ai-spring-boot --skill spring-boot

安装后在 Claude Code 中输入/spring-boot确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
cloud.md
data.md
security.md
testing.md
ui-contract.md
web.md

详细介绍

Spring Boot 技能

专注于整洁架构和可用于生产的代码,进行企业级 Spring Boot 4.x 开发。

核心工作流

  1. 分析 - 理解需求,确定服务边界、API 和数据模型
  2. 设计 - 规划架构,在编码前确认设计
  3. 实现 - 使用构造器注入和分层架构构建;对于关系型数据库,与实体一同编写 Flyway 迁移(ddl-auto: validate)
  4. 安全 - 添加 Spring Security、OAuth2 和方法级安全;确认测试通过
  5. 测试 - 编写单元测试和集成测试;运行 ./mvnw clean verify 并确认全部通过,且集成测试实际运行(未跳过)
  6. 部署 - 通过 Actuator 配置健康检查;验证 /actuator/health 返回 UP,且 /actuator/info 显示 build.version

快速入门模板

实体
@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    private String name;

    @DecimalMin("0.0")
    private BigDecimal price;

    // Getters/Setters (no Lombok)
}
Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByNameContainingIgnoreCase(String name);
}
Service
@Service
@Transactional(readOnly = true)
public class ProductService {
    private final ProductRepository repo;

    public ProductService(ProductRepository repo) {
        this.repo = repo;
    }

    public List<ProductResponse> search(String name) {
        return repo.findByNameContainingIgnoreCase(name).stream()
            .map(ProductResponse::from)
            .toList();
    }

    @Transactional
    public ProductResponse create(ProductRequest request) {
        var product = new Product();
        product.setName(request.name());
        product.setPrice(request.price());
        return ProductResponse.from(repo.save(product));
    }
}
REST Controller
@RestController
@RequestMapping("/api/v1/products")
@Validated
public class ProductController {
    private final ProductService service;

    public ProductController(ProductService service) {
        this.service = service;
    }

    @GetMapping
    public List<ProductResponse> search(@RequestParam(defaultValue = "") String name) {
        return service.search(name);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ProductResponse create(@Valid @RequestBody ProductRequest request) {
        return service.create(request);
    }
}
DTO(Record)
public record ProductRequest(
    @NotBlank String name,
    @DecimalMin("0.0") BigDecimal price
) {}

public record ProductResponse(Long id, String name, BigDecimal price) {
    public static ProductResponse from(Product product) {
        return new ProductResponse(product.getId(), product.getName(), product.getPrice());
    }
}
全局异常处理器(Problem Details)
@RestControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {

    public record FieldMessage(String field, String code, String message) {}

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
                                                                  HttpStatusCode status, WebRequest request) {
        ex.getBody().setProperty("errors", ex.getFieldErrors().stream()
                .map(e -> new FieldMessage(e.getField(), code(e.getCode()), e.getDefaultMessage()))
                .toList());
        return super.handleMethodArgumentNotValid(ex, headers, status, request);
    }

    private static String code(String constraint) {
        return switch (constraint) {
            case "NotBlank", "NotNull" -> "required";
            case null, default -> "invalid";
        };
    }

    @ExceptionHandler
    ProblemDetail handleOptimisticLock(ObjectOptimisticLockingFailureException ex) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, "Record was changed by someone else; reload and retry");
    }

    @ExceptionHandler
    ProblemDetail handleUnexpected(Exception ex) {
        logger.error("Unexpected error", ex);
        return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected server error");
    }
}

每个错误都使用 RFC 9457 Problem Details,并在 errors: [{field, code, message}] 中包含字段错误。完整处理器(唯一约束、无效排序、文档化错误代码):references/web.md。

测试切片
@WebMvcTest(ProductController.class)
class ProductControllerTest {
    @Autowired MockMvc mockMvc;
    @MockitoBean ProductService service;

    @Test
    void createProduct_validRequest_returns201() throws Exception {
        var response = new ProductResponse(1L, "Widget", new BigDecimal("10.00"));
        when(service.create(any())).thenReturn(response);

        mockMvc.perform(post("/api/v1/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"name":"Widget","price":10.0}
                    """))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("Widget"));
    }
}

参考指南

根据上下文加载详细模式:

主题参考加载时机
Web/RESTreferences/web.mdController、分页列表、校验、PUT 和 version、包含 errors[] 的 Problem Details(RFC 9457)
为 UI 构建的 APIreferences/ui-contract.md仅当 UI 或生成的客户端使用 OpenAPI 契约时(参见 conventions):/api、springdoc 3 契约导出、错误代码、筛选器、关系、Keycloak、“连接 UI”
数据访问references/data.mdJPA、Repository、事务、查询、Flyway 迁移
安全references/security.mdSpring Security 6、OAuth2、JWT、身份验证
云/配置references/cloud.md配置服务器、服务发现、弹性、虚拟线程、HTTP 接口、GraalVM 原生映像、OTLP 跟踪
测试references/testing.md单元测试、集成测试、切片测试、Testcontainers @ServiceConnection

约束

必须做到
  • 使用构造器注入(不使用字段注入)
  • 对所有请求体使用 @Valid
  • 对多步骤写入使用 @Transactional
  • 对读取操作使用 @Transactional(readOnly = true)
  • 使用 @ConfigurationProperties 实现类型安全的配置
  • 使用 @RestControllerAdvice 进行全局异常处理
  • 将密钥外置(使用环境变量,而非 properties 文件)
  • 持久化到关系型数据库时使用 Flyway 迁移和 ddl-auto: validate(否则跳过 Flyway)
  • 错误使用 Problem Details;列表端点返回 PagedModel
  • 放行 /actuator/health/**,确保存活和就绪探针在安全机制后正常工作
  • 仅当 API 是为 UI 构建时(参见 conventions 中的“为 UI 构建 REST API”):遵循 references/ui-contract.md
必须避免
  • 字段注入(在字段上使用 @Autowired)
  • 跳过端点输入校验
  • 混用阻塞式和响应式代码
  • 将密钥存储在 application.properties 中
  • 使用已弃用的 Spring Boot 2.x/3.x 模式
  • 对 URL、凭据和环境值进行硬编码
  • 在测试之外使用 ddl-auto: update/create,或修改已应用的迁移
  • 从 Controller 返回 Page/PageImpl,或在应用程序类上添加 @EnableSpringDataWebSupport(@WebMvcTest 会重复注册该配置)
  • 在 Controller 方法上添加 @ApiResponse(springdoc 3 随后会丢弃成功响应;应使用 OpenApiCustomizer)
  • 在兜底处理器中返回异常的消息
Spring Boot 4 / Spring Data 4 注意事项
  • PropertyReferenceException 和 TypeInformation 已移至 org.springframework.data.core
  • @MockBean/@SpyBean 已移除:请使用 @MockitoBean/@MockitoSpyBean
  • @WebMvcTest 不会创建仓库:控制器注入的每个仓库都需要在测试中配置一个 @MockitoBean
  • 使用 ./mvnw clean verify 构建:由 IDE 编译到 target 中的类可能会掩盖 javac 错误

架构模式

项目结构:

src/main/java/pl/piomin/services/
├── controller/     # REST endpoints
├── service/        # Business logic
├── repository/     # Data access
├── model/          # Entities
├── dto/            # Request/Response DTOs
├── config/         # Configuration
└── exception/      # Custom exceptions + handler

分层:

  • 控制器 → 服务 → 仓库
  • 控制器处理 HTTP 和验证
  • 服务处理业务逻辑和事务
  • 仓库处理数据持久化

整洁架构原则:

  • 领域模型独立于框架
  • 以用例为驱动进行设计
  • 依赖倒置(接口)
  • 明确各层之间的边界

常用注解

注解用途
@RestControllerREST 控制器(组合了 @Controller + @ResponseBody)
@Service业务逻辑组件
@Repository数据访问组件
@Transactional事务管理
@Valid触发验证
@ConfigurationProperties将属性绑定到类
@EnableMethodSecurity启用方法安全

响应式 WebFlux 端点

@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{id}")
    public Mono<ResponseEntity<OrderDto>> getOrder(@PathVariable UUID id) {
        return orderService.findById(id)
                .map(ResponseEntity::ok)
                .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<OrderDto> createOrder(@Valid @RequestBody CreateOrderRequest request) {
        return orderService.create(request);
    }
}

Spring Security JWT

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/actuator/health/**").permitAll()  // + "/actuator/info" when built for a UI
                        .anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
                .build();
    }
}

知识库

Spring Boot 4.x、Java 21+、Spring WebFlux、Project Reactor、Spring Data JPA、Spring Security、OAuth2/JWT、Hibernate、R2DBC、Spring Cloud、Resilience4j、Micrometer、JUnit 5、Testcontainers、Mockito、Maven、Paketo Buildpacks、Skaffold

评论

支持 Markdown