feat: 完善后端架构 - 标准化Controller层和Service层

 新功能:
- 创建了完整的Service层架构,包含所有业务实体的Service接口和实现类
- 新增8个标准化的Controller类,支持完整的CRUD操作
- 实现了统一的Request/Response模式和分页查询功能
- 创建了认证服务(AuthService)和令牌服务(TokenService)
- 添加了Redis配置和认证拦截器

🏗️ 架构优化:
- 移除Controller层所有try-catch块,使用全局异常处理机制
- 创建了专门的异常类(AuthException, TokenException, CaptchaException)
- 统一了API返回格式,完善了Result类的方法
- 实现了标准的分页查询和参数校验

📦 新增文件:
- 8个Controller类: Achievement, Comment, CommunityPost, Conversation, CozeApiCall, EmotionAnalysis, Reward, UserStats
- 12个Service接口和对应的实现类
- 标准化的DTO类(Request/Response)
- 异常处理类和拦截器
- 测试用例

🔧 重构优化:
- 重写了AuthController,移除所有业务逻辑到Service层
- 优化了MessageController,使用标准的Request/Response格式
- 更新了全局异常处理器,支持多种异常类型
- 完善了WebConfig配置,添加认证拦截器

📊 代码统计:
- 新增文件: 60+个
- 新增代码行数: 8000+行
- 重构代码行数: 1000+行
- 移除过时接口: 4个
This commit is contained in:
2025-07-24 07:38:40 +08:00
parent 880e0e3c88
commit 873b8e55da
67 changed files with 8619 additions and 850 deletions
@@ -0,0 +1,270 @@
package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.PageRequest;
import com.emotion.dto.response.BaseResponse;
import com.emotion.entity.UserStats;
import com.emotion.service.UserStatsService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户统计控制器
*
* @author emotion-museum
* @date 2025-07-23
*/
@RestController
@RequestMapping("/user-stats")
public class UserStatsController {
@Autowired
private UserStatsService userStatsService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 分页查询用户统计
*/
@GetMapping("/page")
public Result<PageResult<UserStatsResponse>> getPage(@Validated PageRequest request) {
IPage<UserStats> page = userStatsService.getPage(request);
List<UserStatsResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<UserStatsResponse> pageResult = new PageResult<>();
pageResult.setCurrent(page.getCurrent());
pageResult.setSize(page.getSize());
pageResult.setTotal(page.getTotal());
pageResult.setPages(page.getPages());
pageResult.setRecords(responses);
return Result.success(pageResult);
}
/**
* 根据用户ID获取统计信息
*/
@GetMapping("/user/{userId}")
public Result<UserStatsResponse> getByUserId(@PathVariable String userId) {
UserStats stats = userStatsService.getByUserId(userId);
if (stats == null) {
return Result.notFound("用户统计不存在");
}
return Result.success(convertToResponse(stats));
}
/**
* 根据用户ID和统计类型获取统计信息
*/
@GetMapping("/user/{userId}/type/{statsType}")
public Result<UserStatsResponse> getByUserIdAndStatsType(@PathVariable String userId, @PathVariable String statsType) {
UserStats stats = userStatsService.getByUserIdAndStatsType(userId, statsType);
if (stats == null) {
return Result.notFound("用户统计不存在");
}
return Result.success(convertToResponse(stats));
}
/**
* 根据统计类型查询统计信息
*/
@GetMapping("/type/{statsType}")
public Result<List<UserStatsResponse>> getByStatsType(@PathVariable String statsType) {
List<UserStats> statsList = userStatsService.getByStatsType(statsType);
List<UserStatsResponse> responses = statsList.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 查询用户的所有统计类型
*/
@GetMapping("/user/{userId}/all")
public Result<List<UserStatsResponse>> getAllStatsByUserId(@PathVariable String userId) {
List<UserStats> statsList = userStatsService.getAllStatsByUserId(userId);
List<UserStatsResponse> responses = statsList.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 查询排名前N的用户统计
*/
@GetMapping("/type/{statsType}/top")
public Result<List<UserStatsResponse>> getTopUsersByStatsType(@PathVariable String statsType, @RequestParam(defaultValue = "10") Integer limit) {
List<UserStats> statsList = userStatsService.getTopUsersByStatsType(statsType, limit);
List<UserStatsResponse> responses = statsList.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 查询用户在指定统计类型中的排名
*/
@GetMapping("/user/{userId}/type/{statsType}/rank")
public Result<Long> getUserRankByStatsType(@PathVariable String userId, @PathVariable String statsType) {
Long rank = userStatsService.getUserRankByStatsType(userId, statsType);
return Result.success(rank);
}
/**
* 创建或更新用户统计
*/
@PostMapping
public Result<UserStatsResponse> createOrUpdate(@RequestBody @Validated UserStatsCreateRequest request) {
UserStats stats = userStatsService.createOrUpdateUserStats(
request.getUserId(),
request.getStatsType(),
request.getValue(),
request.getPeriod()
);
return Result.success(convertToResponse(stats));
}
/**
* 更新用户统计值
*/
@PutMapping("/user/{userId}/type/{statsType}")
public Result<Void> updateStatsValue(@PathVariable String userId, @PathVariable String statsType, @RequestParam Double value) {
boolean updated = userStatsService.updateStatsValue(userId, statsType, value);
if (!updated) {
return Result.error("更新失败");
}
return Result.success();
}
/**
* 增加用户统计值
*/
@PutMapping("/user/{userId}/type/{statsType}/increment")
public Result<Void> incrementStatsValue(@PathVariable String userId, @PathVariable String statsType, @RequestParam Double increment) {
boolean updated = userStatsService.incrementStatsValue(userId, statsType, increment);
if (!updated) {
return Result.error("增加失败");
}
return Result.success();
}
/**
* 重新计算用户统计
*/
@PutMapping("/user/{userId}/recalculate")
public Result<Void> recalculateUserStats(@PathVariable String userId) {
boolean recalculated = userStatsService.recalculateUserStats(userId);
if (!recalculated) {
return Result.error("重新计算失败");
}
return Result.success();
}
/**
* 重新计算所有用户统计
*/
@PutMapping("/recalculate-all")
public Result<Void> recalculateAllUserStats() {
boolean recalculated = userStatsService.recalculateAllUserStats();
if (!recalculated) {
return Result.error("重新计算失败");
}
return Result.success();
}
/**
* 查询平均统计值
*/
@GetMapping("/type/{statsType}/avg")
public Result<Double> getAvgValueByStatsType(@PathVariable String statsType) {
Double avgValue = userStatsService.getAvgValueByStatsType(statsType);
return Result.success(avgValue);
}
/**
* 查询最大统计值
*/
@GetMapping("/type/{statsType}/max")
public Result<Double> getMaxValueByStatsType(@PathVariable String statsType) {
Double maxValue = userStatsService.getMaxValueByStatsType(statsType);
return Result.success(maxValue);
}
/**
* 查询最小统计值
*/
@GetMapping("/type/{statsType}/min")
public Result<Double> getMinValueByStatsType(@PathVariable String statsType) {
Double minValue = userStatsService.getMinValueByStatsType(statsType);
return Result.success(minValue);
}
/**
* 删除过期的统计数据
*/
@DeleteMapping("/expired")
public Result<Void> deleteExpiredStats(@RequestParam(defaultValue = "30") Integer days) {
boolean deleted = userStatsService.deleteExpiredStats(days);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 转换为响应对象
*/
private UserStatsResponse convertToResponse(UserStats stats) {
UserStatsResponse response = new UserStatsResponse();
BeanUtils.copyProperties(stats, response);
response.setId(stats.getId());
if (stats.getCreateTime() != null) {
response.setCreateTime(stats.getCreateTime().format(DATE_TIME_FORMATTER));
}
if (stats.getUpdateTime() != null) {
response.setUpdateTime(stats.getUpdateTime().format(DATE_TIME_FORMATTER));
}
return response;
}
/**
* 用户统计创建请求
*/
@lombok.Data
public static class UserStatsCreateRequest {
@NotBlank(message = "用户ID不能为空")
private String userId;
@NotBlank(message = "统计类型不能为空")
private String statsType;
@NotNull(message = "统计值不能为空")
private Double value;
private String period;
}
/**
* 用户统计响应类
*/
@lombok.Data
@lombok.EqualsAndHashCode(callSuper = true)
public static class UserStatsResponse extends BaseResponse {
private String userId;
private String statsType;
private Double value;
private String period;
}
}