feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置
This commit is contained in:
@@ -0,0 +1,764 @@
|
||||
# 项目结构示例
|
||||
|
||||
## 1. 目录结构
|
||||
|
||||
```
|
||||
src/main/java/com/emotionmuseum/
|
||||
├── EmotionMuseumApplication.java # 启动类
|
||||
├── config/ # 配置类
|
||||
│ ├── AsyncConfig.java # 异步配置
|
||||
│ ├── MybatisPlusConfig.java # MyBatis-Plus配置
|
||||
│ ├── RedisConfig.java # Redis配置
|
||||
│ ├── WebSocketConfig.java # WebSocket配置
|
||||
│ ├── SecurityConfig.java # Spring Security配置
|
||||
│ ├── JwtConfig.java # JWT配置
|
||||
│ └── SwaggerConfig.java # API文档配置
|
||||
├── controller/ # 控制器层
|
||||
│ ├── UserController.java # 用户控制器
|
||||
│ ├── ChatController.java # 聊天控制器
|
||||
│ └── AuthController.java # 认证控制器
|
||||
├── service/ # 服务层
|
||||
│ ├── UserService.java # 用户服务接口
|
||||
│ ├── ChatService.java # 聊天服务接口
|
||||
│ ├── AuthService.java # 认证服务接口
|
||||
│ └── impl/ # 服务实现
|
||||
│ ├── UserServiceImpl.java
|
||||
│ ├── ChatServiceImpl.java
|
||||
│ └── AuthServiceImpl.java
|
||||
├── mapper/ # 数据访问层
|
||||
│ ├── UserMapper.java
|
||||
│ ├── ChatMapper.java
|
||||
│ └── MessageMapper.java
|
||||
├── entity/ # 实体类
|
||||
│ ├── User.java
|
||||
│ ├── Chat.java
|
||||
│ ├── Message.java
|
||||
│ └── BaseEntity.java
|
||||
├── dto/ # 数据传输对象
|
||||
│ ├── request/ # 请求对象
|
||||
│ │ ├── CreateUserRequest.java
|
||||
│ │ ├── LoginRequest.java
|
||||
│ │ ├── ChatRequest.java
|
||||
│ │ └── PageRequest.java
|
||||
│ └── response/ # 响应对象
|
||||
│ ├── UserResponse.java
|
||||
│ ├── LoginResponse.java
|
||||
│ ├── ChatResponse.java
|
||||
│ └── PageResult.java
|
||||
├── common/ # 公共组件
|
||||
│ ├── base/ # 基础类
|
||||
│ │ ├── BaseEntity.java
|
||||
│ │ └── BaseService.java
|
||||
│ ├── exception/ # 异常处理
|
||||
│ │ ├── GlobalExceptionHandler.java
|
||||
│ │ ├── BusinessException.java
|
||||
│ │ └── UserNotFoundException.java
|
||||
│ ├── result/ # 统一返回结果
|
||||
│ │ ├── Result.java
|
||||
│ │ └── ResultCode.java
|
||||
│ ├── security/ # 安全相关
|
||||
│ │ ├── JwtAuthenticationFilter.java # JWT认证过滤器
|
||||
│ │ ├── JwtTokenProvider.java # JWT令牌提供者
|
||||
│ │ ├── UserDetailsServiceImpl.java # 用户详情服务实现
|
||||
│ │ ├── AuthenticationEntryPointImpl.java # 认证失败处理
|
||||
│ │ └── AccessDeniedHandlerImpl.java # 访问拒绝处理
|
||||
│ └── util/ # 工具类
|
||||
│ ├── JwtUtil.java
|
||||
│ ├── RedisUtil.java
|
||||
│ └── DateUtil.java
|
||||
├── websocket/ # WebSocket相关
|
||||
│ ├── WebSocketHandler.java
|
||||
│ ├── ChatWebSocketHandler.java
|
||||
│ └── dto/
|
||||
│ ├── ChatMessage.java
|
||||
│ └── WebSocketMessage.java
|
||||
└── ai/ # AI相关
|
||||
├── CozeClient.java # Coze客户端
|
||||
├── ChatClient.java # 聊天客户端
|
||||
└── config/
|
||||
└── CozeConfig.java # Coze配置
|
||||
```
|
||||
|
||||
## 3. 核心代码示例
|
||||
|
||||
### 3.1 启动类
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableAsync
|
||||
@EnableCaching
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@MapperScan("com.emotionmuseum.mapper")
|
||||
public class EmotionMuseumApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EmotionMuseumApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## 2. 核心代码示例
|
||||
|
||||
### 2.1 启动类
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableAsync
|
||||
@EnableCaching
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@MapperScan("com.emotionmuseum.mapper")
|
||||
public class EmotionMuseumApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EmotionMuseumApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 BaseEntity
|
||||
```java
|
||||
@Data
|
||||
@MappedSuperclass
|
||||
public abstract class BaseEntity {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 统一返回结果
|
||||
```java
|
||||
@Data
|
||||
public class Result<T> {
|
||||
private Integer code;
|
||||
private String message;
|
||||
private T data;
|
||||
private Long timestamp;
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(ResultCode.SUCCESS.getCode());
|
||||
result.setMessage(ResultCode.SUCCESS.getMessage());
|
||||
result.setData(data);
|
||||
result.setTimestamp(System.currentTimeMillis());
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(String message) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(ResultCode.ERROR.getCode());
|
||||
result.setMessage(message);
|
||||
result.setTimestamp(System.currentTimeMillis());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 全局异常处理
|
||||
```java
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> handleBusinessException(BusinessException e) {
|
||||
log.warn("业务异常: {}", e.getMessage());
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
|
||||
String message = e.getBindingResult().getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.collect(Collectors.joining(", "));
|
||||
log.warn("参数验证失败: {}", message);
|
||||
return Result.error(message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<Void> handleException(Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return Result.error("系统异常,请稍后重试");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 异步配置
|
||||
```java
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig implements AsyncConfigurer {
|
||||
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(500);
|
||||
executor.setThreadNamePrefix("async-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new SimpleAsyncUncaughtExceptionHandler();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 Coze客户端
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CozeClient {
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Value("${coze.api.url}")
|
||||
private String cozeApiUrl;
|
||||
|
||||
@Value("${coze.api.key}")
|
||||
private String cozeApiKey;
|
||||
|
||||
public String chat(String message, String sessionId) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer " + cozeApiKey);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("message", message);
|
||||
requestBody.put("session_id", sessionId);
|
||||
|
||||
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
ResponseEntity<Map> response = restTemplate.postForEntity(
|
||||
cozeApiUrl + "/chat", request, Map.class);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
|
||||
return (String) response.getBody().get("response");
|
||||
}
|
||||
|
||||
throw new BusinessException("调用Coze API失败");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("调用Coze API异常", e);
|
||||
throw new BusinessException("AI服务暂时不可用");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.7 WebSocket配置
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new ChatWebSocketHandler(), "/ws/chat")
|
||||
.setAllowedOrigins("*");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.8 Spring Security配置
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtAuthFilter;
|
||||
|
||||
@Autowired
|
||||
private AuthenticationEntryPointImpl unauthorizedHandler;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/api/auth/**").permitAll()
|
||||
.requestMatchers("/api/public/**").permitAll()
|
||||
.requestMatchers("/ws/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.9 JWT认证过滤器
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
try {
|
||||
String jwt = getJwtFromRequest(request);
|
||||
|
||||
if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
|
||||
String username = jwtTokenProvider.getUsernameFromToken(jwt);
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("无法设置用户认证: {}", e.getMessage());
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private String getJwtFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.10 聊天控制器
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/chat")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class ChatController {
|
||||
|
||||
@Autowired
|
||||
private ChatService chatService;
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
@PostMapping("/send")
|
||||
public Result<ChatResponse> sendMessage(@Valid @RequestBody ChatRequest request) {
|
||||
// Controller层只负责:参数校验、调用Service、返回结果
|
||||
ChatResponse response = chatService.sendMessage(request);
|
||||
return Result.success(response);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
@GetMapping("/history")
|
||||
public Result<PageResult<ChatResponse>> getChatHistory(
|
||||
@Valid PageRequest pageRequest,
|
||||
@RequestParam(required = false) String sessionId) {
|
||||
// Controller层只负责:参数校验、调用Service、返回结果
|
||||
PageResult<ChatResponse> result = chatService.getChatHistory(pageRequest, sessionId);
|
||||
return Result.success(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.11 聊天服务实现
|
||||
```java
|
||||
@Service
|
||||
@Transactional
|
||||
@Slf4j
|
||||
public class ChatServiceImpl implements ChatService {
|
||||
|
||||
@Autowired
|
||||
private ChatMapper chatMapper;
|
||||
|
||||
@Autowired
|
||||
private CozeClient cozeClient;
|
||||
|
||||
@Override
|
||||
public ChatResponse sendMessage(ChatRequest request) {
|
||||
// 1. 业务参数校验
|
||||
validateChatRequest(request);
|
||||
|
||||
// 2. 业务逻辑:调用AI服务
|
||||
String aiResponse = cozeClient.chat(request.getMessage(), request.getSessionId());
|
||||
|
||||
// 3. 业务逻辑:保存聊天记录
|
||||
Chat chat = new Chat();
|
||||
chat.setUserId(getCurrentUserId());
|
||||
chat.setMessage(request.getMessage());
|
||||
chat.setResponse(aiResponse);
|
||||
chat.setSessionId(request.getSessionId());
|
||||
chat.setCreateTime(LocalDateTime.now());
|
||||
|
||||
chatMapper.insert(chat);
|
||||
|
||||
// 4. 返回结果转换
|
||||
return convertToChatResponse(chat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ChatResponse> getChatHistory(PageRequest pageRequest, String sessionId) {
|
||||
// 1. 业务逻辑:查询聊天历史
|
||||
Page<Chat> page = new Page<>(pageRequest.getPageNum(), pageRequest.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<Chat> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Chat::getUserId, getCurrentUserId());
|
||||
if (StringUtils.hasText(sessionId)) {
|
||||
wrapper.eq(Chat::getSessionId, sessionId);
|
||||
}
|
||||
wrapper.orderByDesc(Chat::getCreateTime);
|
||||
|
||||
Page<Chat> chatPage = chatMapper.selectPage(page, wrapper);
|
||||
|
||||
// 2. 数据转换
|
||||
List<ChatResponse> responses = chatPage.getRecords().stream()
|
||||
.map(this::convertToChatResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 3. 返回分页结果
|
||||
return new PageResult<>(responses, chatPage.getTotal(), pageRequest.getPageNum(), pageRequest.getPageSize());
|
||||
}
|
||||
|
||||
// 私有方法:业务校验
|
||||
private void validateChatRequest(ChatRequest request) {
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("请求参数不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(request.getMessage())) {
|
||||
throw new IllegalArgumentException("消息内容不能为空");
|
||||
}
|
||||
if (request.getMessage().length() > 1000) {
|
||||
throw new IllegalArgumentException("消息内容长度不能超过1000字符");
|
||||
}
|
||||
}
|
||||
|
||||
// 私有方法:获取当前用户ID
|
||||
private Long getCurrentUserId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
|
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||
// 根据用户名获取用户ID的逻辑
|
||||
return getUserService().getUserIdByUsername(userDetails.getUsername());
|
||||
}
|
||||
throw new UnauthorizedException("用户未登录");
|
||||
}
|
||||
|
||||
// 私有方法:数据转换
|
||||
private ChatResponse convertToChatResponse(Chat chat) {
|
||||
ChatResponse response = new ChatResponse();
|
||||
response.setId(chat.getId());
|
||||
response.setMessage(chat.getMessage());
|
||||
response.setResponse(chat.getResponse());
|
||||
response.setSessionId(chat.getSessionId());
|
||||
response.setCreateTime(chat.getCreateTime());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 配置文件示例
|
||||
|
||||
### 3.1 application.yml
|
||||
```yaml
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
application:
|
||||
name: emotion-museum
|
||||
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: password
|
||||
hikari:
|
||||
maximum-pool-size: 20
|
||||
minimum-idle: 5
|
||||
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
database: 0
|
||||
timeout: 3000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 20
|
||||
max-idle: 10
|
||||
min-idle: 5
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
|
||||
# JWT配置
|
||||
jwt:
|
||||
secret: your-secret-key-here-must-be-very-long-and-secure
|
||||
expiration: 86400000 # 24小时
|
||||
|
||||
coze:
|
||||
api:
|
||||
url: https://api.coze.com
|
||||
key: your-coze-api-key
|
||||
|
||||
# Log4j2配置
|
||||
logging:
|
||||
config: classpath:log4j2-spring.xml
|
||||
```
|
||||
|
||||
### 3.2 application-dev.yml
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/emotion_museum_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
|
||||
# 开发环境JWT配置
|
||||
jwt:
|
||||
secret: dev-secret-key-not-for-production
|
||||
expiration: 86400000
|
||||
```
|
||||
|
||||
### 3.3 log4j2-spring.xml
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN" monitorInterval="30">
|
||||
<Properties>
|
||||
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
|
||||
<Property name="LOG_FILE_PATH">logs</Property>
|
||||
</Properties>
|
||||
|
||||
<Appenders>
|
||||
<!-- 控制台输出 -->
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</Console>
|
||||
|
||||
<!-- 文件输出 -->
|
||||
<RollingFile name="FileAppender" fileName="${LOG_FILE_PATH}/app.log"
|
||||
filePattern="${LOG_FILE_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy />
|
||||
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||
</Policies>
|
||||
<DefaultRolloverStrategy max="10"/>
|
||||
</RollingFile>
|
||||
|
||||
<!-- 错误日志文件 -->
|
||||
<RollingFile name="ErrorFileAppender" fileName="${LOG_FILE_PATH}/error.log"
|
||||
filePattern="${LOG_FILE_PATH}/error-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy />
|
||||
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||
</Policies>
|
||||
<DefaultRolloverStrategy max="10"/>
|
||||
</RollingFile>
|
||||
|
||||
<!-- 安全日志文件 -->
|
||||
<RollingFile name="SecurityFileAppender" fileName="${LOG_FILE_PATH}/security.log"
|
||||
filePattern="${LOG_FILE_PATH}/security-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy />
|
||||
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||
</Policies>
|
||||
<DefaultRolloverStrategy max="10"/>
|
||||
</RollingFile>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<!-- 应用日志 -->
|
||||
<Logger name="com.emotionmuseum" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="FileAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 安全日志 -->
|
||||
<Logger name="com.emotionmuseum.common.security" level="info" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="SecurityFileAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 错误日志 -->
|
||||
<Logger name="com.emotionmuseum" level="error" additivity="false">
|
||||
<AppenderRef ref="ErrorFileAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- Spring Security日志 -->
|
||||
<Logger name="org.springframework.security" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="SecurityFileAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 根日志器 -->
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="FileAppender"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
```
|
||||
|
||||
## 4. Maven依赖
|
||||
|
||||
### 4.1 pom.xml核心依赖
|
||||
```xml
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<spring-boot.version>3.2.0</spring-boot.version>
|
||||
<spring-ai.version>0.8.0</spring-ai.version>
|
||||
<mybatis-plus.version>3.5.4</mybatis-plus.version>
|
||||
<mysql.version>8.0.33</mysql.version>
|
||||
<redis.version>3.2.0</redis.version>
|
||||
<spring-security.version>6.1.0</spring-security.version>
|
||||
<log4j2.version>2.20.0</log4j2.version>
|
||||
<jwt.version>0.11.5</jwt.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Log4j2 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring AI -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</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>
|
||||
```
|
||||
|
||||
这个项目结构示例提供了一个完整的SpringBoot单体后端服务的基础框架,包含了您要求的所有技术组件:Spring AI、WebSocket、Redis、MySQL、MyBatis-Plus、Spring Security、JWT认证、Log4j2日志、异步处理、全局异常处理、统一返回结果等。该框架具有以下特点:
|
||||
|
||||
1. **安全性**: 集成Spring Security和JWT,提供完整的认证授权机制
|
||||
2. **可观测性**: 使用Log4j2提供结构化日志记录和日志分级管理
|
||||
3. **高性能**: 支持异步处理、缓存和数据库优化
|
||||
4. **可扩展性**: 模块化设计,易于扩展和维护
|
||||
5. **标准化**: 统一的代码规范和异常处理机制
|
||||
6. **分层清晰**: 严格遵循分层架构,Controller层只负责请求处理,所有业务逻辑都在Service层实现
|
||||
Reference in New Issue
Block a user