# 项目结构示例 ## 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 { private Integer code; private String message; private T data; private Long timestamp; public static Result success(T data) { Result result = new Result<>(); result.setCode(ResultCode.SUCCESS.getCode()); result.setMessage(ResultCode.SUCCESS.getMessage()); result.setData(data); result.setTimestamp(System.currentTimeMillis()); return result; } public static Result error(String message) { Result 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 handleBusinessException(BusinessException e) { log.warn("业务异常: {}", e.getMessage()); return Result.error(e.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public Result 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 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 requestBody = new HashMap<>(); requestBody.put("message", message); requestBody.put("session_id", sessionId); HttpEntity> request = new HttpEntity<>(requestBody, headers); ResponseEntity 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 sendMessage(@Valid @RequestBody ChatRequest request) { // Controller层只负责:参数校验、调用Service、返回结果 ChatResponse response = chatService.sendMessage(request); return Result.success(response); } @PreAuthorize("hasRole('USER')") @GetMapping("/history") public Result> getChatHistory( @Valid PageRequest pageRequest, @RequestParam(required = false) String sessionId) { // Controller层只负责:参数校验、调用Service、返回结果 PageResult 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 getChatHistory(PageRequest pageRequest, String sessionId) { // 1. 业务逻辑:查询聊天历史 Page page = new Page<>(pageRequest.getPageNum(), pageRequest.getPageSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Chat::getUserId, getCurrentUserId()); if (StringUtils.hasText(sessionId)) { wrapper.eq(Chat::getSessionId, sessionId); } wrapper.orderByDesc(Chat::getCreateTime); Page chatPage = chatMapper.selectPage(page, wrapper); // 2. 数据转换 List 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 %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n logs ``` ## 4. Maven依赖 ### 4.1 pom.xml核心依赖 ```xml 21 3.2.0 0.8.0 3.5.4 8.0.33 3.2.0 6.1.0 2.20.0 0.11.5 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-security io.jsonwebtoken jjwt-api ${jwt.version} io.jsonwebtoken jjwt-impl ${jwt.version} runtime io.jsonwebtoken jjwt-jackson ${jwt.version} runtime org.springframework.boot spring-boot-starter-log4j2 org.springframework.ai spring-ai-openai-spring-boot-starter org.springframework.boot spring-boot-starter-websocket com.baomidou mybatis-plus-boot-starter ${mybatis-plus.version} mysql mysql-connector-java ${mysql.version} org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-validation org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test ``` 这个项目结构示例提供了一个完整的SpringBoot单体后端服务的基础框架,包含了您要求的所有技术组件:Spring AI、WebSocket、Redis、MySQL、MyBatis-Plus、Spring Security、JWT认证、Log4j2日志、异步处理、全局异常处理、统一返回结果等。该框架具有以下特点: 1. **安全性**: 集成Spring Security和JWT,提供完整的认证授权机制 2. **可观测性**: 使用Log4j2提供结构化日志记录和日志分级管理 3. **高性能**: 支持异步处理、缓存和数据库优化 4. **可扩展性**: 模块化设计,易于扩展和维护 5. **标准化**: 统一的代码规范和异常处理机制 6. **分层清晰**: 严格遵循分层架构,Controller层只负责请求处理,所有业务逻辑都在Service层实现