本文以「用户管理系统」为实战案例,演示如何从零搭建、部署及测试一个功能完备、架构合理的 Spring Boot 企业级应用,涵盖:多环境配置、实体/DTO/VO 分离、分页 & 动态查询、全局统一异常、日志系统、接口文档、数据库控制台、安全认证、单元测试、集成测试、Docker 化及 CI/CD。
📑 目录
- 技术栈与架构
- 项目初始化
- 多环境配置
- 实体/DTO/VO 设计
- 数据访问层(Repository)
- 业务层(Service)
- 控制层(Controller)
- 全局异常处理
- 日志系统配置
- 接口文档(Swagger / OpenAPI)
- 数据库控制台(H2)
- 安全认证(Spring Security + JWT)
- 前端展示(Thymeleaf + Bootstrap)
- 单元测试与集成测试
- Docker 化部署
- CI/CD(GitHub Actions)
- 常见问题 & FAQ
- 下一步
技术栈与架构
- 后端框架:Spring Boot 3.x
- 持久层:Spring Data JPA + Hibernate
- 数据库:H2(测试),MySQL(生产)
- 安全:Spring Security + JWT
- 接口文档:SpringDoc OpenAPI
- 模板引擎:Thymeleaf
- 前端样式:Bootstrap 5
- 日志:SLF4J + Logback
- 测试:JUnit 5 + Mockito + SpringBootTest
- 容器化:Docker + Docker Compose
- CI/CD:GitHub Actions
项目分层架构示意:
┌─ controller (接口层)
│
├─ service (服务层, 业务逻辑)
│
├─ repository (数据访问层)
│
├─ entity (JPA 实体)
│
├─ dto (数据传输对象)
│
├─ vo (视图对象)
│
├─ config (配置类)
│
└─ exception (全局异常处理)
项目初始化
- 前往 Spring Initializr,设置:
- Project: Maven
- Language: Java
- Spring Boot: 3.1+
- Packaging: Jar
- Java: 17(或 11)
- 添加依赖:
- Spring Web
- Spring Data JPA
- H2 Database
- MySQL Driver
- Spring Boot Actuator
- Spring Security
- springdoc-openapi-starter-webmvc-ui
- Lombok
- 下载并导入 IDE,开启 Lombok 插件。
多环境配置
使用 application.yml
+ Profile 切换:
spring:
profiles:
active: dev # 默认开发环境
---
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
---
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://localhost:3306/userdb?useSSL=false&serverTimezone=UTC
username: root
password: 你的密码
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: validate
启动生产环境:
java -jar user-demo.jar --spring.profiles.active=prod
实体/DTO/VO 设计
Entity: User.java
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor @AllArgsConstructor @Builder
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable=false, unique=true)
private String username;
@Column(nullable=false)
private String email;
@Column(nullable=false)
private String password; // 存储加密后密码
}
DTO: UserDTO.java
@Data
public class UserDTO {
private String username;
private String email;
}
VO: UserVO.java
@Data
public class UserVO {
private Long id;
private String username;
private String email;
private LocalDateTime createdAt;
}
Mapper: 使用 MapStruct
@Mapper(componentModel = "spring")
public interface UserMapper {
UserVO toVO(User user);
User toEntity(UserDTO dto);
}
数据访问层(Repository)
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByUsernameContainsAndEmailContains(String username, String email, Pageable pageable);
Optional<User> findByUsername(String username);
}
业务层(Service)
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repo;
private final PasswordEncoder encoder;
private final UserMapper mapper;
@Transactional
public UserVO createUser(UserDTO dto) {
if(repo.findByUsername(dto.getUsername()).isPresent()) {
throw new BusinessException("用户名已存在");
}
User entity = mapper.toEntity(dto);
entity.setPassword(encoder.encode(dto.getPassword()));
User saved = repo.save(entity);
return mapper.toVO(saved);
}
public Page<UserVO> listUsers(String username, String email, Pageable p) {
return repo.findByUsernameContainsAndEmailContains(username, email, p)
.map(mapper::toVO);
}
// 其他更新、删除方法略...
}
控制层(Controller)
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService svc;
@PostMapping
public ResponseEntity<UserVO> create(@Valid @RequestBody UserDTO dto) {
UserVO vo = svc.createUser(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(vo);
}
@GetMapping
public Page<UserVO> search(
@RequestParam(defaultValue="") String username,
@RequestParam(defaultValue="") String email,
@RequestParam(defaultValue="0") int page,
@RequestParam(defaultValue="10") int size
) {
Pageable p = PageRequest.of(page, size);
return svc.listUsers(username, email, p);
}
}
请求示例(curl):
curl -X POST http://localhost:8080/api/users -H "Content-Type: application/json" -d '{"username":"alice","email":"alice@example.com","password":"123456"}'
全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<?> handleBiz(BusinessException ex) {
return ResponseEntity.badRequest()
.body(Map.of("error", ex.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleSys(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "系统异常,请联系管理员"));
}
}
日志系统配置
logback-spring.xml
<configuration>
<springProfile name="dev">
<logger name="com.example" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<logger name="com.example" level="INFO"/>
</springProfile>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
接口文档(Swagger / OpenAPI)
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
访问:http://localhost:8080/swagger-ui.html
数据库控制台 (H2)
在 application.yml
启用:
spring:
h2:
console:
enabled: true
path: /h2-console
访问:http://localhost:8080/h2-console
(JDBC URL: jdbc:h2:mem:testdb
)
安全认证(Spring Security + JWT)
省略 JWT 生成和过滤器配置代码示例,核心思路:
- 用户登录
/api/auth/login
返回 JWT - 全局过滤器校验 Token
- 保护
/api/users/**
接口
前端展示(Thymeleaf + Bootstrap)
- 模板目录:
src/main/resources/templates
- 静态资源:
src/main/resources/static
示例模板 index.html
:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>...</head>
<body>
<div class="container">
<h1>用户列表</h1>
<table class="table">...</table>
</div>
</body>
</html>
单元测试 & 集成测试
单元测试:UserServiceTest.java
@SpringBootTest
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock UserRepository repo;
@InjectMocks UserService svc;
@Test void shouldCreateUser() {
UserDTO dto = new UserDTO("bob","bob@example.com","pwd");
when(repo.findByUsername("bob")).thenReturn(Optional.empty());
when(repo.save(any())).thenAnswer(i -> i.getArgument(0));
UserVO vo = svc.createUser(dto);
assertEquals("bob", vo.getUsername());
}
}
集成测试:UserControllerIT.java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class UserControllerIT {
@Autowired MockMvc mvc;
@Test void getUsers() throws Exception {
mvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
}
}
Docker 化部署
Dockerfile
FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/user-demo.jar app.jar
ENTRYPOINT ["java","-jar","app.jar"]
docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: userdb
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
CI/CD (GitHub Actions)
.github/workflows/ci.yml
示例:
name: Java CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
- name: Build with Maven
run: mvn clean package -DskipTests
- name: Run tests
run: mvn test
- name: Build Docker image
run: docker build -t user-demo .
常见问题 & FAQ
- 如何加密密码? 使用
BCryptPasswordEncoder
。 - Actuator 如何细粒度管理? 修改
management.endpoints.web.exposure.include/exclude
。 - 如何切换日志级别? 修改
logback-spring.xml
或动态日志端点。
下一步
- 接入 Redis 缓存,加速查询
- 整合消息队列(RabbitMQ / Kafka)
- 微服务拆分:Spring Cloud Gateway + Consul / Eureka
- 前后端分离:React / Vue + Rest API
⭐️ 如果觉得有帮助,欢迎点赞、收藏并分享给更多人