摘要:系统复盘课程要点,给出分层设计、统一异常、校验、事务、测试金字塔、容器化、CI/CD、监控告警的落地方法。附最小可交付示例与排错清单。
适用人群:Java 后端入门到进阶、课设与实战项目。
关键词:Spring Boot、分层架构、校验、全局异常、事务、单元测试、集成测试、Docker、GitHub Actions、Actuator、Micrometer
目录
-
一、课程价值回看
-
二、工程化 12 条硬规则
-
三、最小可交付样例(Task 服务)
-
四、排错清单与性能定位
-
五、监控与可观测最小闭环
-
六、团队协作与代码规范
-
七、课程作业提分打法
-
八、常见问题 FAQ
-
九、参考与延伸
-
十、总结与可复用模板
建议先赞藏,实操时对照 Checklist。
一、课程价值回看:学会交付而不是堆注解
-
目标不是“跑起来”,而是**“稳定交付”**。
-
交付的三件套:清晰分层 + 自动化测试 + 一键部署。
二、工程化 12 条硬规则(可直接采纳)
1)分层架构(适配/领域/基础设施)
Controller(协议适配) → Service(用例规则/事务) → Repository(存储细节)
-
Controller 只做 DTO 与协议转换;业务只进 Service。
2)配置分环境与密钥外置
application.yml + application-<profile>.yml;敏感信息走环境变量或密钥服务。
3)参数校验与统一错误返回
-
jakarta.validation+@Valid。 -
@RestControllerAdvice产出一致 JSON。
@RestControllerAdvice class GlobalExHandler { @ExceptionHandler(MethodArgumentNotValidException.class) ResponseEntity<Map<String,Object>> badReq(MethodArgumentNotValidException e){ var errors=e.getBindingResult().getFieldErrors().stream() .collect(Collectors.toMap(FieldError::getField, DefaultMessageSourceResolvable::getDefaultMessage,(a,b)->a)); return ResponseEntity.badRequest().body(Map.of("code","VALIDATION_ERROR","errors",errors)); } }
4)事务边界只放 Service
-
读多写少加
@Transactional(readOnly = true)。 -
避免同类内自调用导致事务失效。
5)日志为排障服务
-
统一字段:
traceId/userId/costMs;输出 JSON。 -
约束 error 级别,避免告警风暴。
6)测试金字塔
-
单测多、集成适量、端到端少量且关键路径必测。
-
@WebMvcTest切片测控制器,@SpringBootTest+ Testcontainers 测集成。
7)API 文档代码生成
-
springdoc-openapi生成 Swagger UI,CI 中校验可用性。
8)安全默认收紧
-
Spring Security 启用后显式放行匿名端点。
-
CORS 用
CorsConfigurationSource定源、定方法。
9)性能优化的顺序
-
先度量后优化:线程池、慢查询、GC → 再考虑缓存/异步。
-
缓存只加纯查询路径,失效策略先定后上。
10)容器化交付
极简 Dockerfile:
FROM eclipse-temurin:21-jre WORKDIR /app COPY build/libs/app-*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-XX:+UseContainerSupport","-jar","/app/app.jar"]
11)CI/CD 最小闭环
-
Push→Test→Build Jar→Build Image。
-
失败即退回,镜像带 commit sha。
12)可观测默认打开
-
spring-boot-starter-actuator+ Micrometer。 -
指标、健康检查、日志聚合一个都不少。
三、最小可交付样例:待办任务 Task 服务
3.1 目录结构建议
src/main/java/com/example/app ├─ api // Controller + DTO ├─ domain // 领域模型与规则 ├─ application// 用例服务(与domain可合并) ├─ infra // Repository/Client └─ config // 安全/跨域/Bean
3.2 领域与接口
-
实体:
Task(id, title, description, status, createdAt) -
接口:
-
POST
/api/tasks创建 -
GET
/api/tasks/{id}查询 -
PATCH
/api/tasks/{id}/done完成
-
3.3 Controller(含校验)
@RestController @RequestMapping("/api/tasks") @RequiredArgsConstructor class TaskController { private final TaskService service; record CreateCmd(@NotBlank String title, String description) {} record TaskVO(Long id,String title,String description,String status,Instant createdAt){} @PostMapping ResponseEntity<TaskVO> create(@Valid @RequestBody CreateCmd cmd){ return ResponseEntity.status(HttpStatus.CREATED).body(service.create(cmd)); } @GetMapping("/{id}") TaskVO find(@PathVariable Long id){ return service.find(id); } @PatchMapping("/{id}/done") TaskVO done(@PathVariable Long id){ return service.done(id); } }
3.4 Service(事务与规则)
@Service @RequiredArgsConstructor class TaskService { private final TaskRepository repo; @Transactional TaskController.TaskVO create(TaskController.CreateCmd cmd){ var t=new Task(cmd.title(), cmd.description()); repo.save(t); return TaskController.TaskVOMapper.from(t); } @Transactional(readOnly = true) TaskController.TaskVO find(Long id){ return TaskController.TaskVOMapper.from(repo.get(id)); } @Transactional TaskController.TaskVO done(Long id){ var t=repo.get(id); if(t.isDone()) throw new BizException("TASK_ALREADY_DONE"); t.markDone(); return TaskController.TaskVOMapper.from(t); } }
3.5 统一异常
class BizException extends RuntimeException{ BizException(String c){ super(c); } }
3.6 数据表(PostgreSQL 示例)
create table task( id bigint primary key generated always as identity, title varchar(200) not null, description text, status varchar(16) not null default 'NEW', created_at timestamp not null default now() );
3.7 Web 层切片测试
@WebMvcTest(TaskController.class) class TaskControllerTest { @Autowired MockMvc mvc; @MockBean TaskService service; @Test void create_ok() throws Exception { when(service.create(any())).thenReturn(new TaskVO(1L,"t","d","NEW",Instant.now())); mvc.perform(post("/api/tasks").contentType(APPLICATION_JSON) .content("""{"title":"t","description":"d"}""")) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").value(1)); } }
3.8 Docker 一键跑
docker build -t task-app:latest . docker run -p 8080:8080 --env-file .env task-app:latest
四、排错清单与性能定位(对分项)
| 场景 | 典型原因 | 快速定位 |
|---|---|---|
| 自动配置未生效 | 条件缺失 | /actuator/conditions |
| 事务不生效 | 自调用或非 public | 打开代理日志,重构调用 |
| 校验不触发 | 缺 @Valid | Controller 入参核对 |
| N+1 查询 | 懒加载列表 | DTO 投影或 fetch join |
| CORS 失败 | 预检未放行 | 允许 OPTIONS 与头部 |
| 日志爆量 | 频繁 error | 等级收敛 + 采样 |
| 时区错乱 | 序列化混用 | 统一 ISO-8601 + Instant |
五、监控与可观测最小闭环
-
指标:
http.server.requests、线程池活跃、JVM 内存、GC。 -
健康:
/actuator/health,数据库、消息队列、外部依赖。 -
日志:结构化 JSON,关联
traceId,落到日志平台。
六、团队协作与代码规范
-
统一格式化与静态检查:Spotless、Checkstyle、Error Prone。
-
约定提交信息与分支模型:
feat/fix/docs/refactor/test。 -
PR 模板内置检查项:测试通过、Swagger 可用、变更记录。
七、课程作业提分打法(助攻 98+)
-
README 展示:架构图(可用 Mermaid)、启动步骤、接口列表。
-
截图三件套:Swagger 页面、测试报告、容器运行日志。
-
贴上 Checklist 与排错记录,体现工程意识。
-
监控面板最小化图一张(请求量与 P95 耗时)。
-
CI 成功记录截图一张。
图示占位:
架构图(思维导图/时序图/部署图)
Swagger 截图
GitHub Actions 成功勾
八、常见问题 FAQ
Q:必须用 JPA 吗?
A:不强制。MyBatis-Plus 也行。关键是分层与事务边界清晰。
Q:为什么要写那么多测试?
A:测试是可维护的成本下限。没有测试,改一次慌一次。
Q:容器化对课设有必要吗?
A:是加分项。助教一键跑得起来,就赢一半。
九、参考与延伸(官方优先)
-
Spring Boot、Spring Data、Spring Security 官方文档
-
Spring Guides 场景手册
-
Micrometer、Actuator、OpenAPI 3 规范
-
《Clean Architecture》《Domain-Driven Design Quickly》
十、总结与可复用模板
-
一句话:以交付为目标组织项目,其余环节全部服务于可维护、可测试、可发布。
-
模板清单:
-
GlobalExHandler.java -
CorsConfig.java -
Dockerfile -
ci.yml -
README.md(启动步骤 + 截图位)
-
发布到 优快云 的操作要点(直接照做)
-
标题含主关键词与数字法则:已满足。
-
添加标签:
Spring Boot、后端开发、测试、容器化、CI/CD、可观测性。 -
封面图:工程架构/Swagger 截图,清晰不带水印。
-
文章内3–5 张图:架构图、Swagger、CI 成功记录、监控面板。
-
开启目录与原创新声明,加入系列专栏。
-
末尾 CTA:引导收藏、评论区提问与关注专栏。
-
代码块高亮,段落 2–4 行一组,表格 + Checklist 提升可读性。
这版就是给分机器爱看的结构:关键词、图文并茂、可复用代码、可验证闭环。去发。
您说:
现在是90分,你觉得有什么改进的吗?
ChatGPT 说:
已思考 9s
结论:还能抬到 95–98。优先做“机器分+读者留存”两条线。按优先级给你可直接粘贴的增补块。
P0|最提分的改动(先做这5件)
-
首屏三要素
-
换标题:
《Spring Boot 应用开发》高分心得:12条工程化法则 + 最小可交付样例(含Docker/CI模板) -
开头 3 行放“摘要、要点、代码仓库占位链接”。
-
加系列专栏与站内相关文章内链3处(优快云偏好站内流转)。
-
结构化图示(Mermaid,站内可渲染)
粘贴到“目录”上方,拉阅读时长与完读率:
flowchart LR A[Controller/DTO] --> B[Service/Use Case] B --> C[(Repository/DB)] B --> D[Domain Rules] A --> E[Global Exception] subgraph Platform F[Actuator/Micrometer] --> G[Prometheus/Grafana] H[OpenAPI] --> A end
-
一键运行块(可复制即用)
# docker-compose.yml(贴图示和说明) services: db: image: postgres:16 environment: { POSTGRES_PASSWORD: dev, POSTGRES_DB: app } ports: ["5432:5432"] app: image: ghcr.io/owner/demo-app:latest environment: DB_URL: jdbc:postgresql://db:5432/app DB_USER: postgres DB_PASS: dev ports: ["8080:8080"] depends_on: [db] # 启动 docker compose up -d
-
测试+Swagger 截图位
-
放 3 张图:Swagger、CI 通过、/actuator/health。
-
图下各写一句话说明与关键指标(P95/成功率)。
-
末尾 CTA 强化与互动问题
-
设 3 个问题引导评论:
-
你们团队事务边界如何划?
-
Actuator 暴露了哪些自定义指标?
-
失败重试与幂等等价替代怎么选?
-
P1|机器权重项(SEO/原创/排版)
-
关键词密度:在小标题里重复“Spring Boot、统一异常、事务、测试、Docker、CI/CD、Actuator”。
-
标签:
Spring Boot、后端、单元测试、Docker、CI/CD、Micrometer、Actuator、Swagger。 -
目录+分段:每段 2–4 行,代码块与表格穿插。
-
原创声明+系列:加“本篇为课程总结与工程化实践,后续『Spring Boot 工程化实战』连载”。
P2|内容强化块(直接贴)
统一错误返回对照表
| 维度 | 低分做法 | 高分做法 |
|---|---|---|
| 校验 | 仅注解 | 注解+分层校验(DTO/Domain) |
| 返回 | 文本信息 | 规范 JSON:code/message/traceId |
| 可观测 | 无 | 记录 path/method/status/costMs |
CORS 最小安全配置
@Bean CorsConfigurationSource cors(){ var c=new CorsConfiguration(); c.setAllowedOrigins(List.of("https://your-frontend.example")); c.setAllowedMethods(List.of("GET","POST","PATCH")); c.setAllowedHeaders(List.of("Content-Type","Authorization")); var s=new UrlBasedCorsConfigurationSource(); s.registerCorsConfiguration("/**", c); return s; }
CI 徽章与检查项(README 顶部)
 - 必过:单测、OpenAPI 可访问、镜像构建成功
性能排查顺序清单(贴成Checklist)
-
打开 Actuator 指标与日志采样
-
线程池/连接池可视化
-
慢查询 ≥ 200ms 列表
-
垃圾回收停顿 < 100ms@P95
-
再考虑缓存/异步/批处理
FAQ 增补三问
-
Q:
@Transactional自调用为何失效?
A:AOP 代理绕过。抽出到独立 bean 或通过接口调用。 -
Q:Swagger 在生产如何收敛?
A:仅在dev/staging开启,生产反向代理白名单。 -
Q:Testcontainers 太慢?
A:使用复用容器,或本地开发以 H2/禁部分集成测权衡。
P3|站内权重与回访机制
-
在开头放“版本号与更新记录”:
v1.1 更新:新增 Mermaid 架构图、Docker Compose、一键运行指南、FAQ 3 条。 -
文章末尾挂“下一篇预告”:
【下一篇】Spring Boot + Testcontainers 持续集成最佳实践。 -
在正文中部插入站内相关文章3个超链(自己的或精选):校验、事务、Actuator。
一次到位的“变更包”(按位插入)
-
标题、摘要、Mermaid 图、Docker Compose、对照表、Checklist、FAQ、CTA、版本记录、系列链接。
-
配 3–5 张无水印截图。
-
关键词+标签统一。
做完 P0+P1 基本能到 95;补上 P2 的对照表与 FAQ,图片齐备,稳定 97–98。
您说:
你把那些我该做的事情去掉,给我一个完整的可复制的
ChatGPT 说:
《Spring Boot 应用开发》高分心得:12 条工程化法则 + 最小可交付样例(含 Docker/CI 模板)
摘要:复盘课程要点。落地分层设计、统一异常、校验、事务、测试金字塔、容器化、CI/CD、可观测。附“Task 服务”最小可交付样例与排错清单。
版本:v1.1(新增 Mermaid 架构图、Docker Compose、一键运行、FAQ 增补)
关键词:Spring Boot、统一异常、事务、测试、Docker、CI/CD、Actuator、Swagger、Micrometer
目录
-
一、课程价值
-
二、12 条工程化法则
-
三、最小可交付样例(Task 服务)
-
四、排错清单与性能定位
-
五、可观测闭环
-
六、团队协作与规范
-
七、作业提分展示面板
-
八、FAQ
-
九、参考
-
十、总结
一、课程价值
目标不是“能跑”。目标是稳定交付。
核心三件套:清晰分层 + 自动化测试 + 一键部署。
二、12 条工程化法则
1)分层架构
Controller(协议适配) → Service(用例与事务) → Repository(存储细节)
2)配置分环境与密钥外置
application.yml + application-<profile>.yml;敏感信息走环境变量。
3)参数校验与统一错误
jakarta.validation + @Valid;@RestControllerAdvice 产出一致 JSON。
4)事务边界在 Service
读多写少标 @Transactional(readOnly = true);避免同类自调用。
5)日志为排障服务
结构化 JSON;字段包含 traceId/userId/costMs;控制 error 级别。
6)测试金字塔
单测多、集成适量、端到端少量但关键路径必测。
7)API 文档代码生成
springdoc-openapi 生成 Swagger;CI 校验可访问。
8)安全默认收紧
Spring Security 显式放行匿名端点;CORS 精确配置来源与方法。
9)性能优化顺序
先度量再优化:线程池、慢查询、GC → 缓存/异步/批处理。
10)容器化交付
精简 JRE 镜像;分层缓存;环境变量驱动。
11)CI/CD 最小闭环
Push→Test→Build Jar→Build Image。镜像打上 commit sha。
12)可观测默认打开
Actuator + Micrometer;指标、健康、日志统一。
架构示意(Mermaid)
flowchart LR A[Controller/DTO] --> B[Service/Use Case] B --> C[(Repository/DB)] B --> D[Domain Rules] A --> E[Global Exception] subgraph Platform F[Actuator/Micrometer] --> G[Prometheus/Grafana] H[OpenAPI/Swagger] --> A I[CI/CD] --> A end
三、最小可交付样例:Task 服务
3.1 目录结构
src/main/java/com/example/app ├─ api // Controller + DTO ├─ domain // 领域模型与规则 ├─ application// 用例服务 ├─ infra // Repository/Client └─ config // 安全/跨域/Bean
3.2 统一异常处理
package com.example.app.config; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.*; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; import java.util.*; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, Object>> badReq(MethodArgumentNotValidException e) { var errors = e.getBindingResult().getFieldErrors().stream() .collect(Collectors.toMap(FieldError::getField, DefaultMessageSourceResolvable::getDefaultMessage, (a,b)->a)); return ResponseEntity.badRequest().body(Map.of( "code", "VALIDATION_ERROR", "errors", errors )); } @ExceptionHandler(BizException.class) public ResponseEntity<Map<String, Object>> biz(BizException e) { return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of( "code", e.getMessage() )); } @ExceptionHandler(Exception.class) public ResponseEntity<Map<String, Object>> others(Exception e) { return ResponseEntity.internalServerError().body(Map.of( "code","INTERNAL_ERROR","message","请联系管理员" )); } } class BizException extends RuntimeException{ public BizException(String code){ super(code); } }
3.3 Controller(校验 + VO)
package com.example.app.api; import jakarta.validation.constraints.NotBlank; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import java.time.Instant; @RestController @RequestMapping("/api/tasks") @RequiredArgsConstructor public class TaskController { private final TaskService service; public record CreateCmd(@NotBlank String title, String description) {} public record TaskVO(Long id, String title, String description, String status, Instant createdAt){} @PostMapping public ResponseEntity<TaskVO> create(@Valid @RequestBody CreateCmd cmd){ return ResponseEntity.status(HttpStatus.CREATED).body(service.create(cmd)); } @GetMapping("/{id}") public TaskVO find(@PathVariable Long id){ return service.find(id); } @PatchMapping("/{id}/done") public TaskVO done(@PathVariable Long id){ return service.done(id); } }
3.4 Service(事务与规则)
package com.example.app.application; import com.example.app.api.TaskController.TaskVO; import com.example.app.api.TaskController.CreateCmd; import com.example.app.config.BizException; import com.example.app.domain.Task; import com.example.app.infra.TaskRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; @Service @RequiredArgsConstructor public class TaskService { private final TaskRepository repo; @Transactional public TaskVO create(CreateCmd cmd){ var t = new Task(null, cmd.title(), cmd.description(), "NEW", Instant.now()); var saved = repo.save(t); return toVO(saved); } @Transactional(readOnly = true) public TaskVO find(Long id){ return toVO(repo.get(id)); } @Transactional public TaskVO done(Long id){ var t = repo.get(id); if("DONE".equals(t.status())) throw new BizException("TASK_ALREADY_DONE"); var done = t.withStatus("DONE"); return toVO(repo.save(done)); } private TaskVO toVO(Task t){ return new TaskVO(t.id(), t.title(), t.description(), t.status(), t.createdAt()); } }
3.5 领域与 Repository
package com.example.app.domain; import java.time.Instant; public record Task(Long id, String title, String description, String status, Instant createdAt){ public Task withStatus(String s){ return new Task(id, title, description, s, createdAt); } }
package com.example.app.infra; import com.example.app.domain.Task; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import lombok.RequiredArgsConstructor; import java.sql.ResultSet; import java.time.Instant; @Repository @RequiredArgsConstructor public class TaskRepository { private final JdbcTemplate jdbc; public Task save(Task t){ if(t.id()==null){ var id = jdbc.queryForObject( "insert into task(title,description,status,created_at) values (?,?,?,now()) returning id", Long.class, t.title(), t.description(), t.status()); return new Task(id, t.title(), t.description(), t.status(), Instant.now()); } jdbc.update("update task set title=?,description=?,status=? where id=?", t.title(), t.description(), t.status(), t.id()); return t; } public Task get(Long id){ return jdbc.queryForObject("select id,title,description,status,created_at from task where id=?", (ResultSet rs, int row)-> new Task( rs.getLong("id"), rs.getString("title"), rs.getString("description"), rs.getString("status"), rs.getTimestamp("created_at").toInstant() ), id); } }
3.6 数据表(PostgreSQL)
create table if not exists task( id bigint primary key generated always as identity, title varchar(200) not null, description text, status varchar(16) not null default 'NEW', created_at timestamp not null default now() );
3.7 Web 层切片测试
package com.example.app.api; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import java.time.Instant; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(TaskController.class) class TaskControllerTest { @Autowired MockMvc mvc; @MockBean TaskService service; @Test void create_ok() throws Exception { when(service.create(any())).thenReturn(new TaskController.TaskVO(1L,"t","d","NEW", Instant.now())); mvc.perform(post("/api/tasks").contentType(APPLICATION_JSON) .content("{\"title\":\"t\",\"description\":\"d\"}")) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").value(1)); } }
3.8 Dockerfile(JDK 21)
FROM eclipse-temurin:21-jre WORKDIR /app COPY build/libs/app-*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-XX:+UseContainerSupport","-jar","/app/app.jar"]
3.9 一键运行(docker-compose)
services: db: image: postgres:16 environment: POSTGRES_PASSWORD: dev POSTGRES_DB: app ports: ["5432:5432"] app: image: ghcr.io/owner/demo-app:latest environment: DB_URL: jdbc:postgresql://db:5432/app DB_USER: postgres DB_PASS: dev ports: ["8080:8080"] depends_on: [db]
3.10 CI(GitHub Actions)
name: ci on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: { distribution: 'temurin', java-version: '21' } - name: Test run: ./gradlew test - name: Build Jar run: ./gradlew bootJar - name: Build Image run: docker build -t ghcr.io/owner/demo-app:${{ github.sha }} .
四、排错清单与性能定位
| 场景 | 典型原因 | 快速定位 |
|---|---|---|
| 自动配置未生效 | 条件缺失 | /actuator/conditions |
| 事务不生效 | 自调用或非 public | 代理日志;抽出到独立 bean |
| 校验不触发 | 缺 @Valid | Controller 入参核对 |
| N+1 查询 | 懒加载列表 | DTO 投影或 fetch join |
| CORS 失败 | 预检未放行 | 允许 OPTIONS 与所需头 |
| 日志爆量 | 频繁 error | 等级收敛 + 采样 |
| 时区错乱 | 序列化混用 | 统一 ISO-8601 + Instant |
五、可观测闭环
-
Actuator:
/actuator/health、/actuator/metrics、/actuator/info。 -
Micrometer 指标:
http.server.requests、JVM 内存、GC、线程池。 -
日志关联:输出
traceId,与指标打通。
六、团队协作与规范
-
格式化与静态检查:Spotless、Checkstyle、Error Prone。
-
提交信息与分支模型:
feat/fix/docs/refactor/test。 -
PR 检查项:测试通过、Swagger 可访问、镜像构建成功。
七、作业提分展示面板
-
截图三件套:Swagger 页面、CI 成功、
/actuator/health。 -
对照表与 Checklist:如下方两块。
统一错误返回对照表
| 维度 | 基线 | 强化 |
|---|---|---|
| 校验 | 注解 | 注解 + 分层校验 |
| 返回 | 文本 | 规范 JSON:code/message/traceId |
| 观测 | 无 | 记录 path/method/status/costMs |
性能排查 Checklist
-
Actuator 指标与日志采样开启
-
线程池/连接池可视化
-
慢查询 ≥ 200ms 清单
-
GC 停顿 P95 < 100ms
-
再评估缓存/异步/批处理
八、FAQ
Q1:@Transactional 自调用为何失效?
A:AOP 代理绕过。抽出到独立 bean 或通过接口调用。
Q2:Swagger 在生产如何收敛?
A:仅在 dev/staging 开启;生产通过反向代理白名单。
Q3:Testcontainers 太慢?
A:启用复用容器;本地权衡使用 H2 或减少集成测频次。
Q4:日志如何控量?
A:高频路径降到 info/debug,error 只记录故障;按采样率输出。
Q5:CORS 常见误区?
A:忽略预检;忘记放行 OPTIONS 与必要头;来源写 * 导致凭据失效。
九、参考(官方优先)
-
Spring Boot、Spring Data、Spring Security 文档
-
Spring Guides
-
Micrometer、Actuator、OpenAPI 3 规范
-
《Clean Architecture》《Domain-Driven Design Quickly》
十、总结
一句话:以交付为目标组织 Spring Boot 项目。
工程化让“写对注解”升级为“稳定上线与可维护”。
Spring Boot 工程化法则与最小样例
1529

被折叠的 条评论
为什么被折叠?



