本文目录导读:

- 项目结构
- Maven配置 (pom.xml)
- 主应用程序
- 实体类设计
- 请求/响应DTO
- Repository层
- 安全配置
- 服务层
- 异步消息服务
- Redis缓存服务
- Controller层
- 异常处理
- 配置文件
- 测试类
- Docker Compose 配置
- 特性说明
我将为您设计一个完整的Java系统整合案例,展示现代Java企业级应用的核心技术栈。
项目结构
java-integration-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/com/example/integration/
│ │ │ ├── config/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── entity/
│ │ │ ├── dto/
│ │ │ ├── security/
│ │ │ └── exception/
│ │ └── resources/
│ └── test/
Maven配置 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>integration-demo</artifactId>
<version>1.0.0</version>
<name>System Integration Demo</name>
<properties>
<java.version>11</java.version>
<jjwt.version>0.11.5</jjwt.version>
<lombok.version>1.18.24</lombok.version>
</properties>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Swagger/OpenAPI -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
主应用程序
package com.example.integration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableCaching
@EnableAsync
@EnableScheduling
public class IntegrationDemoApplication {
public static void main(String[] args) {
SpringApplication.run(IntegrationDemoApplication.class, args);
}
}
实体类设计
package com.example.integration.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Data
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(name = "full_name")
private String fullName;
@Column(name = "phone")
private String phone;
@Column(name = "status")
private boolean enabled = true;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
@Column(name = "last_login_time")
private LocalDateTime lastLoginTime;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
package com.example.integration.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import javax.persistence.*;
@Data
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
@Column(name = "description")
private String description;
}
请求/响应DTO
package com.example.integration.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import javax.validation.constraints.*;
public class UserDTO {
@Data
public static 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;
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
@Size(max = 100, message = "姓名长度不能超过100")
private String fullName;
@Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
@Data
public static class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
@Data
public static class UserResponse {
private Long id;
private String username;
private String email;
private String fullName;
private String phone;
private boolean enabled;
private Set<String> roles;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static UserResponse fromEntity(User user) {
UserResponse response = new UserResponse();
response.setId(user.getId());
response.setUsername(user.getUsername());
response.setEmail(user.getEmail());
response.setFullName(user.getFullName());
response.setPhone(user.getPhone());
response.setEnabled(user.isEnabled());
response.setRoles(user.getRoles().stream()
.map(Role::getName)
.collect(Collectors.toSet()));
response.setCreatedAt(user.getCreatedAt());
response.setUpdatedAt(user.getUpdatedAt());
return response;
}
}
@Data
public static class AuthResponse {
private String token;
private String refreshToken;
private String tokenType = "Bearer";
private long expiresIn;
private UserResponse user;
}
}
Repository层
package com.example.integration.repository;
import com.example.integration.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.Optional;
@Repository
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);
@Query("SELECT u FROM User u WHERE u.enabled = true AND u.lastLoginTime < :before")
List<User> findInactiveUsers(@Param("before") LocalDateTime before);
@Modifying
@Query("UPDATE User u SET u.lastLoginTime = :now WHERE u.id = :id")
void updateLastLoginTime(@Param("id") Long id, @Param("now") LocalDateTime now);
}
安全配置
package com.example.integration.security;
import com.example.integration.security.jwt.JwtAuthenticationEntryPoint;
import com.example.integration.security.jwt.JwtAuthenticationFilter;
import com.example.integration.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/actuator/health", "/actuator/info").permitAll()
.antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**",
"/webjars/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
.antMatchers("/api/users/**").hasAnyRole("ADMIN")
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
服务层
package com.example.integration.service;
import com.example.integration.dto.UserDTO;
import com.example.integration.entity.User;
import com.example.integration.entity.Role;
import com.example.integration.exception.BadRequestException;
import com.example.integration.exception.ResourceNotFoundException;
import com.example.integration.repository.UserRepository;
import com.example.integration.repository.RoleRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Slf4j
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private RedisService redisService;
@Autowired
private RabbitMQProducer rabbitMQProducer;
@Transactional
public UserDTO.UserResponse register(UserDTO.RegisterRequest request) {
// 验证密码一致性
if (!request.getPassword().equals(request.getConfirmPassword())) {
throw new BadRequestException("两次输入的密码不一致");
}
// 检查用户名和邮箱唯一性
if (userRepository.existsByUsername(request.getUsername())) {
throw new BadRequestException("用户名已存在");
}
if (userRepository.existsByEmail(request.getEmail())) {
throw new BadRequestException("邮箱已被注册");
}
// 创建用户
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setFullName(request.getFullName());
user.setPhone(request.getPhone());
// 分配默认角色
Role userRole = roleRepository.findByName("ROLE_USER")
.orElseThrow(() -> new RuntimeException("默认角色不存在"));
user.setRoles(Set.of(userRole));
try {
user = userRepository.save(user);
log.info("新用户注册成功: {}", user.getUsername());
// 发送欢迎邮件(异步)
rabbitMQProducer.sendWelcomeEmail(user.getEmail(), user.getUsername());
return UserDTO.UserResponse.fromEntity(user);
} catch (DataIntegrityViolationException e) {
log.error("用户注册失败: {}", e.getMessage());
throw new BadRequestException("用户信息不符合要求");
}
}
@Transactional(readOnly = true)
public UserDTO.UserResponse getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在: " + id));
return UserDTO.UserResponse.fromEntity(user);
}
@Transactional(readOnly = true)
public Page<UserDTO.UserResponse> getAllUsers(Pageable pageable) {
return userRepository.findAll(pageable)
.map(UserDTO.UserResponse::fromEntity);
}
@Transactional
public UserDTO.UserResponse updateUser(Long id, UserDTO.RegisterRequest request) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在: " + id));
if (request.getFullName() != null) {
user.setFullName(request.getFullName());
}
if (request.getPhone() != null) {
user.setPhone(request.getPhone());
}
if (request.getPassword() != null && !request.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword()));
}
user = userRepository.save(user);
log.info("用户信息更新: {}", user.getUsername());
// 清理缓存
redisService.delete("user:" + id);
return UserDTO.UserResponse.fromEntity(user);
}
@Transactional
public void deleteUser(Long id) {
if (!userRepository.existsById(id)) {
throw new ResourceNotFoundException("用户不存在: " + id);
}
userRepository.deleteById(id);
redisService.delete("user:" + id);
log.info("用户已删除: {}", id);
}
}
异步消息服务
package com.example.integration.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class RabbitMQProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
@Async
public void sendWelcomeEmail(String email, String username) {
try {
// 发送邮件消息到队列
rabbitTemplate.convertAndSend(
RabbitMQConfig.EMAIL_EXCHANGE,
RabbitMQConfig.EMAIL_ROUTING_KEY,
new EmailMessage(email, username)
);
log.info("欢迎邮件消息已发送至队列");
} catch (Exception e) {
log.error("发送邮件消息失败: {}", e.getMessage());
}
}
@Data
public static class EmailMessage {
private String email;
private String username;
public EmailMessage(String email, String username) {
this.email = email;
this.username = username;
}
}
}
Redis缓存服务
package com.example.integration.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public void delete(String key) {
redisTemplate.delete(key);
}
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
public void increment(String key) {
redisTemplate.opsForValue().increment(key);
}
}
Controller层
package com.example.integration.controller;
import com.example.integration.dto.UserDTO;
import com.example.integration.service.UserService;
import com.example.integration.service.auth.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private AuthenticationService authenticationService;
@PostMapping("/auth/register")
public ResponseEntity<UserDTO.AuthResponse> register(@Valid @RequestBody UserDTO.RegisterRequest request) {
UserDTO.UserResponse user = userService.register(request);
UserDTO.AuthResponse authResponse = authenticationService.generateAuthResponse(user);
return ResponseEntity.ok(authResponse);
}
@PostMapping("/auth/login")
public ResponseEntity<UserDTO.AuthResponse> login(@Valid @RequestBody UserDTO.LoginRequest request) {
return ResponseEntity.ok(authenticationService.login(request));
}
@GetMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN') or authentication.principal.id == #id")
public ResponseEntity<UserDTO.UserResponse> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
@GetMapping("/users")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Page<UserDTO.UserResponse>> getAllUsers(
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
return ResponseEntity.ok(userService.getAllUsers(pageable));
}
@PutMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<UserDTO.UserResponse> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserDTO.RegisterRequest request) {
return ResponseEntity.ok(userService.updateUser(id, request));
}
@DeleteMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok().build();
}
}
异常处理
package com.example.integration.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.persistence.EntityNotFoundException;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"参数校验失败",
errors,
LocalDateTime.now()
);
return ResponseEntity.badRequest().body(errorResponse);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
null,
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequest(BadRequestException ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
ex.getMessage(),
null,
LocalDateTime.now()
);
return ResponseEntity.badRequest().body(errorResponse);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDenied(AccessDeniedException ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.FORBIDDEN.value(),
"没有权限访问此资源",
null,
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
}
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuthentication(AuthenticationException ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.UNAUTHORIZED.value(),
"认证失败: " + ex.getMessage(),
null,
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
log.error("系统异常", ex);
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"服务器内部错误,请稍后重试",
null,
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
@Data
public static class ErrorResponse {
private int status;
private String message;
private Map<String, String> errors;
private LocalDateTime timestamp;
public ErrorResponse(int status, String message, Map<String, String> errors, LocalDateTime timestamp) {
this.status = status;
this.message = message;
this.errors = errors;
this.timestamp = timestamp;
}
}
}
配置文件
# application.yml
spring:
application:
name: integration-demo
datasource:
url: jdbc:postgresql://localhost:5432/integration_db
username: postgres
password: root
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: none
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
show-sql: false
redis:
host: localhost
port: 6379
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
cache:
type: redis
redis:
time-to-live: 60000ms
cache-null-values: false
server:
port: 8080
servlet:
context-path: /api
tomcat:
max-threads: 200
min-spare-threads: 20
jwt:
secret: ${JWT_SECRET:yoursecretkeyyoursecretkeyyoursecretkeyyoursecretkey}
expiration: 86400000 # 24小时
refresh-expiration: 604800000 # 7天
logging:
level:
com.example.integration: DEBUG
org.springframework.security: WARN
org.hibernate.SQL: WARN
file:
name: logs/integration-app.log
max-size: 10MB
management:
endpoints:
web:
exposure:
include: health,info,metrics,beans,configprops
endpoint:
health:
show-details: always
springdoc:
swagger-ui:
path: /swagger-ui.html
api-docs:
path: /v3/api-docs
测试类
package com.example.integration.controller;
import com.example.integration.dto.UserDTO;
import com.example.integration.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void testRegister() throws Exception {
UserDTO.RegisterRequest request = new UserDTO.RegisterRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setPassword("password123");
request.setConfirmPassword("password123");
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.token").exists())
.andExpect(jsonPath("$.user.email").value("test@example.com"));
}
@Test
public void testLogin() throws Exception {
UserDTO.LoginRequest request = new UserDTO.LoginRequest();
request.setUsername("testuser");
request.setPassword("password123");
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.token").exists());
}
@Test
public void testGetAllUsers() throws Exception {
mockMvc.perform(get("/api/users")
.header("Authorization", "Bearer " + getToken()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
}
private String getToken() throws Exception {
// 登录获取token
UserDTO.LoginRequest request = new UserDTO.LoginRequest();
request.setUsername("admin");
request.setPassword("admin123");
String response = mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andReturn()
.getResponse()
.getContentAsString();
return objectMapper.readTree(response).get("token").asText();
}
}
Docker Compose 配置
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15
container_name: integration-postgres
environment:
POSTGRES_DB: integration_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: root
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- integration-network
redis:
image: redis:7-alpine
container_name: integration-redis
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- integration-network
rabbitmq:
image: rabbitmq:3-management
container_name: integration-rabbitmq
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
volumes:
- rabbitmq_data:/var/lib/rabbitmq
networks:
- integration-network
app:
build: .
container_name: integration-app
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/integration_db
SPRING_DATASOURCE_USERNAME: postgres
SPRING_DATASOURCE_PASSWORD: root
SPRING_REDIS_HOST: redis
SPRING_RABBITMQ_HOST: rabbitmq
depends_on:
- postgres
- redis
- rabbitmq
networks:
- integration-network
networks:
integration-network:
driver: bridge
volumes:
postgres_data:
redis_data:
rabbitmq_data:
特性说明
功能特性
- 用户认证授权:JWT + Spring Security
- 数据持久化:Spring Data JPA + PostgreSQL
- 缓存优化:Redis 分布式缓存
- 异步消息:RabbitMQ 消息队列
- 接口文档:Swagger/OpenAPI
- 监控管理:Actuator
- 参数校验:Bean Validation
- 统一异常处理:全局异常处理器
技术栈
- 开发语言:Java 11
- 框架:Spring Boot 2.7
- 持久化:JPA/Hibernate + PostgreSQL
- 缓存:Redis
- 消息队列:RabbitMQ
- API文档:Swagger 3.0
- 安全:Spring Security + JWT
- 测试:JUnit 5 + MockMvc
启动步骤
- 启动 Docker Compose:
docker-compose up -d - 运行应用:
mvn spring-boot:run - 访问 Swagger UI: http://localhost:8080/api/swagger-ui.html
优化建议
- 性能:添加 Nginx 反向代理和负载均衡
- 安全:启用 HTTPS,限制请求频率
- 监控:集成 Prometheus + Grafana
- 部署:使用 Kubernetes 进行编排
- CI/CD:添加自动化部署流水线
这个整合案例提供了完整的现代化Java企业级架构,可以根据具体需求进一步扩展功能。