feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置

This commit is contained in:
2025-07-27 10:05:59 +08:00
parent 6903ac1c0d
commit cc886cd4d5
126 changed files with 21179 additions and 15734 deletions
@@ -111,6 +111,33 @@ public class AuthController {
return Result.success(username);
}
/**
* 检查账号是否存在
*/
@GetMapping("/check-account")
public Result<Boolean> checkAccount(@RequestParam String account) {
boolean exists = authService.existsByAccount(account);
return Result.success(exists);
}
/**
* 检查邮箱是否存在
*/
@GetMapping("/check-email")
public Result<Boolean> checkEmail(@RequestParam String email) {
boolean exists = authService.existsByEmail(email);
return Result.success(exists);
}
/**
* 检查手机号是否存在
*/
@GetMapping("/check-phone")
public Result<Boolean> checkPhone(@RequestParam String phone) {
boolean exists = authService.existsByPhone(phone);
return Result.success(exists);
}
/**
* 从请求中提取访问令牌
*/
@@ -0,0 +1,332 @@
package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.BasePageRequest;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.DiaryCommentCreateRequest;
import com.emotion.dto.response.DiaryCommentResponse;
import com.emotion.entity.DiaryComment;
import com.emotion.service.DiaryCommentService;
import com.emotion.service.DiaryPostService;
import com.emotion.service.UserService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Valid;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* 日记评论控制器
*
* @author emotion-museum
* @date 2025-07-23
*/
@RestController
@RequestMapping("/diary-comment")
public class DiaryCommentController {
@Autowired
private DiaryCommentService diaryCommentService;
@Autowired
private DiaryPostService diaryPostService;
@Autowired
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 分页查询评论
*/
@GetMapping("/page")
public Result<PageResult<DiaryCommentResponse>> getPage(@Validated BasePageRequest request) {
IPage<DiaryComment> page = diaryCommentService.getPage(request);
List<DiaryCommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryCommentResponse> 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("/diary/{diaryId}/page")
public Result<PageResult<DiaryCommentResponse>> getPageByDiaryId(@PathVariable String diaryId,
@Validated BasePageRequest request) {
IPage<DiaryComment> page = diaryCommentService.getPageByDiaryId(diaryId, request);
List<DiaryCommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryCommentResponse> 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}/page")
public Result<PageResult<DiaryCommentResponse>> getPageByUserId(@PathVariable String userId,
@Validated BasePageRequest request) {
IPage<DiaryComment> page = diaryCommentService.getPageByUserId(userId, request);
List<DiaryCommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryCommentResponse> 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);
}
/**
* 获取评论树结构
*/
@GetMapping("/diary/{diaryId}/tree")
public Result<List<DiaryCommentResponse>> getCommentTree(@PathVariable String diaryId) {
List<DiaryComment> comments = diaryCommentService.getCommentTree(diaryId);
List<DiaryCommentResponse> responses = comments.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 根据ID获取评论详情
*/
@GetMapping("/{id}")
public Result<DiaryCommentResponse> getById(@PathVariable String id) {
DiaryComment comment = diaryCommentService.getById(id);
if (comment == null) {
return Result.notFound("评论不存在");
}
return Result.success(convertToResponse(comment));
}
/**
* 创建评论
*/
@PostMapping
public Result<DiaryCommentResponse> create(@Valid @RequestBody DiaryCommentCreateRequest request) {
DiaryComment comment = diaryCommentService.createComment(
request.getDiaryId(),
request.getUserId(),
request.getContent(),
request.getImages(),
request.getParentCommentId(),
request.getIsAnonymous()
);
// 更新日记的评论数
diaryPostService.incrementCommentCount(request.getDiaryId());
diaryPostService.updateLastCommentTime(request.getDiaryId());
return Result.success(convertToResponse(comment));
}
/**
* 更新评论
*/
@PutMapping("/{id}")
public Result<DiaryCommentResponse> update(@PathVariable String id, @Valid @RequestBody DiaryCommentCreateRequest request) {
boolean updated = diaryCommentService.updateComment(id, request.getContent(), request.getImages());
if (!updated) {
return Result.error("更新失败");
}
DiaryComment updatedComment = diaryCommentService.getById(id);
return Result.success(convertToResponse(updatedComment));
}
/**
* 删除评论
*/
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable String id) {
DiaryComment comment = diaryCommentService.getById(id);
if (comment == null) {
return Result.error("评论不存在");
}
boolean deleted = diaryCommentService.deleteComment(id);
if (!deleted) {
return Result.error("删除失败");
}
// 更新日记的评论数
diaryPostService.decrementCommentCount(comment.getDiaryId());
return Result.success();
}
/**
* 软删除评论
*/
@DeleteMapping("/{id}/soft")
public Result<Void> softDelete(@PathVariable String id) {
DiaryComment comment = diaryCommentService.getById(id);
if (comment == null) {
return Result.error("评论不存在");
}
boolean deleted = diaryCommentService.softDeleteComment(id);
if (!deleted) {
return Result.error("删除失败");
}
// 更新日记的评论数
diaryPostService.decrementCommentCount(comment.getDiaryId());
return Result.success();
}
/**
* 恢复评论
*/
@PutMapping("/{id}/restore")
public Result<Void> restore(@PathVariable String id) {
DiaryComment comment = diaryCommentService.getById(id);
if (comment == null) {
return Result.error("评论不存在");
}
boolean restored = diaryCommentService.restoreComment(id);
if (!restored) {
return Result.error("恢复失败");
}
// 更新日记的评论数
diaryPostService.incrementCommentCount(comment.getDiaryId());
return Result.success();
}
/**
* 点赞评论
*/
@PostMapping("/{id}/like")
public Result<Void> like(@PathVariable String id) {
boolean liked = diaryCommentService.incrementLikeCount(id);
if (!liked) {
return Result.error("点赞失败");
}
return Result.success();
}
/**
* 取消点赞评论
*/
@DeleteMapping("/{id}/like")
public Result<Void> unlike(@PathVariable String id) {
boolean unliked = diaryCommentService.decrementLikeCount(id);
if (!unliked) {
return Result.error("取消点赞失败");
}
return Result.success();
}
/**
* 设置置顶状态
*/
@PutMapping("/{id}/top/{isTop}")
public Result<Void> setTop(@PathVariable String id, @PathVariable Integer isTop) {
boolean set = diaryCommentService.setTop(id, isTop);
if (!set) {
return Result.error("设置置顶状态失败");
}
return Result.success();
}
/**
* 统计日记评论数量
*/
@GetMapping("/diary/{diaryId}/count")
public Result<Long> countByDiaryId(@PathVariable String diaryId) {
Long count = diaryCommentService.countByDiaryId(diaryId);
return Result.success(count);
}
/**
* 统计用户评论数量
*/
@GetMapping("/user/{userId}/count")
public Result<Long> countByUserId(@PathVariable String userId) {
Long count = diaryCommentService.countByUserId(userId);
return Result.success(count);
}
/**
* 统计回复数量
*/
@GetMapping("/parent/{parentCommentId}/replies/count")
public Result<Long> countReplies(@PathVariable String parentCommentId) {
Long count = diaryCommentService.countReplies(parentCommentId);
return Result.success(count);
}
/**
* 转换实体为响应DTO
*/
private DiaryCommentResponse convertToResponse(DiaryComment comment) {
DiaryCommentResponse response = new DiaryCommentResponse();
BeanUtils.copyProperties(comment, response);
// 转换时间格式
if (comment.getPublishTime() != null) {
response.setPublishTime(comment.getPublishTime().format(DATE_TIME_FORMATTER));
}
if (comment.getLastReplyTime() != null) {
response.setLastReplyTime(comment.getLastReplyTime().format(DATE_TIME_FORMATTER));
}
if (comment.getCreateTime() != null) {
response.setCreateTime(comment.getCreateTime().format(DATE_TIME_FORMATTER));
}
if (comment.getUpdateTime() != null) {
response.setUpdateTime(comment.getUpdateTime().format(DATE_TIME_FORMATTER));
}
// 转换JSON字段
try {
if (comment.getImages() != null) {
response.setImages(objectMapper.readValue(comment.getImages(), new TypeReference<List<String>>() {}));
}
if (comment.getMetadata() != null) {
response.setMetadata(objectMapper.readValue(comment.getMetadata(), Object.class));
}
} catch (JsonProcessingException e) {
// 忽略JSON解析错误
}
return response;
}
}
@@ -0,0 +1,359 @@
package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.BasePageRequest;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.DiaryPostCreateRequest;
import com.emotion.dto.request.DiaryPostUpdateRequest;
import com.emotion.dto.response.DiaryPostResponse;
import com.emotion.entity.DiaryPost;
import com.emotion.service.DiaryPostService;
import com.emotion.service.DiaryCommentService;
import com.emotion.service.UserService;
import com.emotion.service.AiChatService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Valid;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
import com.emotion.entity.DiaryComment;
/**
* 用户日记控制器
*
* @author emotion-museum
* @date 2025-07-23
*/
@RestController
@RequestMapping("/diary-post")
public class DiaryPostController {
@Autowired
private DiaryPostService diaryPostService;
@Autowired
private DiaryCommentService diaryCommentService;
@Autowired
private UserService userService;
@Autowired
private AiChatService aiChatService;
@Autowired
private ObjectMapper objectMapper;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 分页查询日记
*/
@GetMapping("/page")
public Result<PageResult<DiaryPostResponse>> getPage(@Validated BasePageRequest request) {
IPage<DiaryPost> page = diaryPostService.getPage(request);
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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}/page")
public Result<PageResult<DiaryPostResponse>> getPageByUserId(@PathVariable String userId,
@Validated BasePageRequest request) {
IPage<DiaryPost> page = diaryPostService.getPageByUserId(userId, request);
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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/page")
public Result<PageResult<DiaryPostResponse>> getPublicPageByUserId(@PathVariable String userId,
@Validated BasePageRequest request) {
IPage<DiaryPost> page = diaryPostService.getPublicPageByUserId(userId, request);
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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);
}
/**
* 查询精选日记
*/
@GetMapping("/featured/page")
public Result<PageResult<DiaryPostResponse>> getFeaturedPage(@Validated BasePageRequest request) {
IPage<DiaryPost> page = diaryPostService.getFeaturedPage(request);
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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("/{id}")
public Result<DiaryPostResponse> getById(@PathVariable String id) {
DiaryPost diaryPost = diaryPostService.getById(id);
if (diaryPost == null) {
return Result.notFound("日记不存在");
}
// 增加浏览数
diaryPostService.incrementViewCount(id);
return Result.success(convertToResponse(diaryPost));
}
/**
* 创建日记
*/
@PostMapping
public Result<DiaryPostResponse> create(@Valid @RequestBody DiaryPostCreateRequest request) {
DiaryPost diaryPost = diaryPostService.createDiaryPost(request);
return Result.success(convertToResponse(diaryPost));
}
/**
* 发表日记并生成AI评论
*/
@PostMapping("/publish")
public Result<DiaryPostResponse> publish(@Valid @RequestBody DiaryPostCreateRequest request) {
return Result.success(diaryPostService.publishDiaryWithAiComment(request));
}
/**
* 更新日记
*/
@PutMapping("/{id}")
public Result<DiaryPostResponse> update(@PathVariable String id, @Valid @RequestBody DiaryPostUpdateRequest request) {
boolean updated = diaryPostService.updateDiaryPost(id, request);
if (!updated) {
return Result.error("更新失败");
}
DiaryPost updatedDiaryPost = diaryPostService.getById(id);
return Result.success(convertToResponse(updatedDiaryPost));
}
/**
* 删除日记
*/
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable String id) {
boolean deleted = diaryPostService.deleteDiaryPost(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 软删除日记
*/
@DeleteMapping("/{id}/soft")
public Result<Void> softDelete(@PathVariable String id) {
boolean deleted = diaryPostService.softDeleteDiaryPost(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 恢复日记
*/
@PutMapping("/{id}/restore")
public Result<Void> restore(@PathVariable String id) {
boolean restored = diaryPostService.restoreDiaryPost(id);
if (!restored) {
return Result.error("恢复失败");
}
return Result.success();
}
/**
* 点赞日记
*/
@PostMapping("/{id}/like")
public Result<Void> like(@PathVariable String id) {
boolean liked = diaryPostService.incrementLikeCount(id);
if (!liked) {
return Result.error("点赞失败");
}
return Result.success();
}
/**
* 取消点赞日记
*/
@DeleteMapping("/{id}/like")
public Result<Void> unlike(@PathVariable String id) {
boolean unliked = diaryPostService.decrementLikeCount(id);
if (!unliked) {
return Result.error("取消点赞失败");
}
return Result.success();
}
/**
* 分享日记
*/
@PostMapping("/{id}/share")
public Result<Void> share(@PathVariable String id) {
boolean shared = diaryPostService.incrementShareCount(id);
if (!shared) {
return Result.error("分享失败");
}
return Result.success();
}
/**
* 设置精选状态
*/
@PutMapping("/{id}/featured/{featured}")
public Result<Void> setFeatured(@PathVariable String id, @PathVariable Integer featured) {
boolean set = diaryPostService.setFeatured(id, featured);
if (!set) {
return Result.error("设置精选状态失败");
}
return Result.success();
}
/**
* 设置置顶优先级
*/
@PutMapping("/{id}/priority/{priority}")
public Result<Void> setPriority(@PathVariable String id, @PathVariable Integer priority) {
boolean set = diaryPostService.setPriority(id, priority);
if (!set) {
return Result.error("设置优先级失败");
}
return Result.success();
}
/**
* 统计用户日记数量
*/
@GetMapping("/user/{userId}/count")
public Result<Long> countByUserId(@PathVariable String userId) {
Long count = diaryPostService.countByUserId(userId);
return Result.success(count);
}
/**
* 统计用户公开日记数量
*/
@GetMapping("/user/{userId}/public/count")
public Result<Long> countPublicByUserId(@PathVariable String userId) {
Long count = diaryPostService.countPublicByUserId(userId);
return Result.success(count);
}
/**
* 统计精选日记数量
*/
@GetMapping("/featured/count")
public Result<Long> countFeatured() {
Long count = diaryPostService.countFeatured();
return Result.success(count);
}
/**
* 转换实体为响应DTO
*/
private DiaryPostResponse convertToResponse(DiaryPost diaryPost) {
DiaryPostResponse response = new DiaryPostResponse();
BeanUtils.copyProperties(diaryPost, response);
// 转换时间格式
if (diaryPost.getPublishTime() != null) {
response.setPublishTime(diaryPost.getPublishTime().format(DATE_TIME_FORMATTER));
}
if (diaryPost.getLastCommentTime() != null) {
response.setLastCommentTime(diaryPost.getLastCommentTime().format(DATE_TIME_FORMATTER));
}
if (diaryPost.getAiCommentTime() != null) {
response.setAiCommentTime(diaryPost.getAiCommentTime().format(DATE_TIME_FORMATTER));
}
if (diaryPost.getCreateTime() != null) {
response.setCreateTime(diaryPost.getCreateTime().format(DATE_TIME_FORMATTER));
}
if (diaryPost.getUpdateTime() != null) {
response.setUpdateTime(diaryPost.getUpdateTime().format(DATE_TIME_FORMATTER));
}
// 转换JSON字段
try {
if (diaryPost.getImages() != null) {
response.setImages(objectMapper.readValue(diaryPost.getImages(), new TypeReference<List<String>>() {}));
}
if (diaryPost.getVideos() != null) {
response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new TypeReference<List<String>>() {}));
}
if (diaryPost.getTags() != null) {
response.setTags(objectMapper.readValue(diaryPost.getTags(), new TypeReference<List<String>>() {}));
}
if (diaryPost.getAiKeywords() != null) {
response.setAiKeywords(objectMapper.readValue(diaryPost.getAiKeywords(), new TypeReference<List<String>>() {}));
}
if (diaryPost.getAiEmotionAnalysis() != null) {
response.setAiEmotionAnalysis(objectMapper.readValue(diaryPost.getAiEmotionAnalysis(), Object.class));
}
if (diaryPost.getMetadata() != null) {
response.setMetadata(objectMapper.readValue(diaryPost.getMetadata(), Object.class));
}
} catch (JsonProcessingException e) {
// 忽略JSON解析错误
}
return response;
}
}
@@ -6,14 +6,18 @@ import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.UserCreateRequest;
import com.emotion.dto.request.UserUpdateRequest;
import com.emotion.dto.request.UserProfileUpdateRequest;
import com.emotion.dto.response.UserResponse;
import com.emotion.entity.User;
import com.emotion.service.UserService;
import com.emotion.util.UserContextHolder;
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.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.time.format.DateTimeFormatter;
import java.util.List;
@@ -132,6 +136,67 @@ public class UserController {
return Result.success(count);
}
/**
* 获取当前用户个人资料
*/
@GetMapping("/profile")
public Result<UserResponse> getCurrentUserProfile(HttpServletRequest request) {
String currentUserId = getCurrentUserId(request);
if (currentUserId == null) {
return Result.unauthorized("用户未登录");
}
User user = userService.getById(currentUserId);
if (user == null) {
return Result.notFound("用户不存在");
}
return Result.success(convertToResponse(user));
}
/**
* 更新当前用户个人资料
*/
@PutMapping("/profile")
public Result<UserResponse> updateCurrentUserProfile(@Valid @RequestBody UserProfileUpdateRequest request,
HttpServletRequest httpRequest) {
String currentUserId = getCurrentUserId(httpRequest);
if (currentUserId == null) {
return Result.unauthorized("用户未登录");
}
User user = new User();
BeanUtils.copyProperties(request, user);
user.setId(currentUserId);
boolean updated = userService.updateById(user);
if (!updated) {
return Result.error("更新失败");
}
User updatedUser = userService.getById(currentUserId);
return Result.success(convertToResponse(updatedUser));
}
/**
* 获取当前用户ID
* 从JWT拦截器设置的请求属性中获取用户ID
*/
private String getCurrentUserId(HttpServletRequest request) {
// 优先从UserContextHolder获取(线程本地存储)
String userId = UserContextHolder.getCurrentUserId();
if (userId != null) {
return userId;
}
// 如果UserContextHolder中没有,从请求属性中获取(兼容性)
Object userIdAttr = request.getAttribute("userId");
if (userIdAttr != null) {
return userIdAttr.toString();
}
return null;
}
/**
* 转换为响应对象
*/