feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为响应对象
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.emotion.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建日记评论请求DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Data
|
||||
public class DiaryCommentCreateRequest {
|
||||
|
||||
/**
|
||||
* 日记ID
|
||||
*/
|
||||
@NotBlank(message = "日记ID不能为空")
|
||||
private String diaryId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
@NotBlank(message = "评论内容不能为空")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 评论图片
|
||||
*/
|
||||
private List<String> images;
|
||||
|
||||
/**
|
||||
* 父评论ID (用于回复功能)
|
||||
*/
|
||||
private String parentCommentId;
|
||||
|
||||
/**
|
||||
* 是否匿名评论: 0-实名, 1-匿名
|
||||
*/
|
||||
@NotNull(message = "是否匿名不能为空")
|
||||
private Integer isAnonymous;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.emotion.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建日记请求DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Data
|
||||
public class DiaryPostCreateRequest {
|
||||
|
||||
/**
|
||||
* 日记标题
|
||||
*/
|
||||
@Size(max = 200, message = "日记标题长度不能超过200个字符")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日记内容
|
||||
*/
|
||||
@NotBlank(message = "日记内容不能为空")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 图片列表
|
||||
*/
|
||||
private List<String> images;
|
||||
|
||||
/**
|
||||
* 视频列表
|
||||
*/
|
||||
private List<String> videos;
|
||||
|
||||
/**
|
||||
* 发布地点
|
||||
*/
|
||||
@Size(max = 200, message = "发布地点长度不能超过200个字符")
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
private BigDecimal latitude;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
private BigDecimal longitude;
|
||||
|
||||
/**
|
||||
* 天气信息
|
||||
*/
|
||||
@Size(max = 50, message = "天气信息长度不能超过50个字符")
|
||||
private String weather;
|
||||
|
||||
/**
|
||||
* 心情状态
|
||||
*/
|
||||
@Size(max = 50, message = "心情状态长度不能超过50个字符")
|
||||
private String mood;
|
||||
|
||||
/**
|
||||
* 心情评分 (0-10)
|
||||
*/
|
||||
private BigDecimal moodScore;
|
||||
|
||||
/**
|
||||
* 标签列表
|
||||
*/
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 是否公开: 0-仅自己可见, 1-公开
|
||||
*/
|
||||
@NotNull(message = "是否公开不能为空")
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 是否匿名发布: 0-实名, 1-匿名
|
||||
*/
|
||||
@NotNull(message = "是否匿名不能为空")
|
||||
private Integer isAnonymous;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.emotion.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 更新日记请求DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Data
|
||||
public class DiaryPostUpdateRequest {
|
||||
|
||||
/**
|
||||
* 日记标题
|
||||
*/
|
||||
@Size(max = 200, message = "日记标题长度不能超过200个字符")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日记内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 图片列表
|
||||
*/
|
||||
private List<String> images;
|
||||
|
||||
/**
|
||||
* 视频列表
|
||||
*/
|
||||
private List<String> videos;
|
||||
|
||||
/**
|
||||
* 发布地点
|
||||
*/
|
||||
@Size(max = 200, message = "发布地点长度不能超过200个字符")
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
private BigDecimal latitude;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
private BigDecimal longitude;
|
||||
|
||||
/**
|
||||
* 天气信息
|
||||
*/
|
||||
@Size(max = 50, message = "天气信息长度不能超过50个字符")
|
||||
private String weather;
|
||||
|
||||
/**
|
||||
* 心情状态
|
||||
*/
|
||||
@Size(max = 50, message = "心情状态长度不能超过50个字符")
|
||||
private String mood;
|
||||
|
||||
/**
|
||||
* 心情评分 (0-10)
|
||||
*/
|
||||
private BigDecimal moodScore;
|
||||
|
||||
/**
|
||||
* 标签列表
|
||||
*/
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 是否公开: 0-仅自己可见, 1-公开
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 是否匿名发布: 0-实名, 1-匿名
|
||||
*/
|
||||
private Integer isAnonymous;
|
||||
|
||||
/**
|
||||
* 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除
|
||||
*/
|
||||
private String status;
|
||||
}
|
||||
@@ -32,6 +32,12 @@ public class RegisterRequest extends BaseRequest {
|
||||
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 确认密码
|
||||
*/
|
||||
@NotBlank(message = "确认密码不能为空")
|
||||
private String confirmPassword;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@@ -51,7 +57,7 @@ public class RegisterRequest extends BaseRequest {
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
@Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.emotion.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 用户个人资料更新请求类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-26
|
||||
*/
|
||||
@Data
|
||||
public class UserProfileUpdateRequest {
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@Size(max = 50, message = "昵称长度不能超过50个字符")
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@Email(message = "邮箱格式不正确")
|
||||
@Size(max = 100, message = "邮箱长度不能超过100个字符")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 头像URL
|
||||
*/
|
||||
@Size(max = 500, message = "头像URL长度不能超过500个字符")
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
private LocalDate birthDate;
|
||||
|
||||
/**
|
||||
* 所在地
|
||||
*/
|
||||
@Size(max = 100, message = "所在地长度不能超过100个字符")
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 个人简介
|
||||
*/
|
||||
@Size(max = 500, message = "个人简介长度不能超过500个字符")
|
||||
private String bio;
|
||||
}
|
||||
@@ -5,46 +5,69 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 用户更新请求类
|
||||
*
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UserUpdateRequest extends BaseRequest {
|
||||
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@Size(max = 50, message = "用户名长度不能超过50个字符")
|
||||
private String username;
|
||||
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@Size(max = 50, message = "昵称长度不能超过50个字符")
|
||||
private String nickname;
|
||||
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@Email(message = "邮箱格式不正确")
|
||||
@Size(max = 100, message = "邮箱长度不能超过100个字符")
|
||||
private String email;
|
||||
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
|
||||
/**
|
||||
* 头像
|
||||
* 头像URL
|
||||
*/
|
||||
@Size(max = 500, message = "头像URL长度不能超过500个字符")
|
||||
private String avatar;
|
||||
|
||||
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
private LocalDate birthDate;
|
||||
|
||||
/**
|
||||
* 所在地
|
||||
*/
|
||||
@Size(max = 100, message = "所在地长度不能超过100个字符")
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 个人简介
|
||||
*/
|
||||
@Size(max = 500, message = "个人简介长度不能超过500个字符")
|
||||
private String bio;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.emotion.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日记评论响应DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Data
|
||||
public class DiaryCommentResponse {
|
||||
|
||||
/**
|
||||
* 评论ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 日记ID
|
||||
*/
|
||||
private String diaryId;
|
||||
|
||||
/**
|
||||
* 评论用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 评论用户信息
|
||||
*/
|
||||
private UserInfoResponse userInfo;
|
||||
|
||||
/**
|
||||
* 父评论ID (用于回复功能)
|
||||
*/
|
||||
private String parentCommentId;
|
||||
|
||||
/**
|
||||
* 父评论信息
|
||||
*/
|
||||
private DiaryCommentResponse parentComment;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 评论图片
|
||||
*/
|
||||
private List<String> images;
|
||||
|
||||
/**
|
||||
* 评论类型: user-用户评论, ai-AI评论, system-系统评论
|
||||
*/
|
||||
private String commentType;
|
||||
|
||||
/**
|
||||
* AI评论来源 (如: emotion_analysis, sentiment_analysis)
|
||||
*/
|
||||
private String aiCommentSource;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 回复数
|
||||
*/
|
||||
private Integer replyCount;
|
||||
|
||||
/**
|
||||
* 是否匿名评论: 0-实名, 1-匿名
|
||||
*/
|
||||
private Integer isAnonymous;
|
||||
|
||||
/**
|
||||
* 是否置顶: 0-普通, 1-置顶
|
||||
*/
|
||||
private Integer isTop;
|
||||
|
||||
/**
|
||||
* 状态: published-已发布, hidden-隐藏, deleted-已删除
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
private String publishTime;
|
||||
|
||||
/**
|
||||
* 最后回复时间
|
||||
*/
|
||||
private String lastReplyTime;
|
||||
|
||||
/**
|
||||
* 情绪评分
|
||||
*/
|
||||
private BigDecimal emotionScore;
|
||||
|
||||
/**
|
||||
* 情感评分
|
||||
*/
|
||||
private BigDecimal sentimentScore;
|
||||
|
||||
/**
|
||||
* 扩展元数据
|
||||
*/
|
||||
private Object metadata;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private String createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private String updateTime;
|
||||
|
||||
/**
|
||||
* 是否已点赞
|
||||
*/
|
||||
private Boolean isLiked;
|
||||
|
||||
/**
|
||||
* 子评论列表
|
||||
*/
|
||||
private List<DiaryCommentResponse> replies;
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.emotion.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日记响应DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Data
|
||||
public class DiaryPostResponse {
|
||||
|
||||
/**
|
||||
* 日记ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
private UserInfoResponse userInfo;
|
||||
|
||||
/**
|
||||
* 日记标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日记内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 图片列表
|
||||
*/
|
||||
private List<String> images;
|
||||
|
||||
/**
|
||||
* 视频列表
|
||||
*/
|
||||
private List<String> videos;
|
||||
|
||||
/**
|
||||
* 发布地点
|
||||
*/
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
private BigDecimal latitude;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
private BigDecimal longitude;
|
||||
|
||||
/**
|
||||
* 天气信息
|
||||
*/
|
||||
private String weather;
|
||||
|
||||
/**
|
||||
* 心情状态
|
||||
*/
|
||||
private String mood;
|
||||
|
||||
/**
|
||||
* 心情评分 (0-10)
|
||||
*/
|
||||
private BigDecimal moodScore;
|
||||
|
||||
/**
|
||||
* 标签列表
|
||||
*/
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 是否公开: 0-仅自己可见, 1-公开
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 是否匿名发布: 0-实名, 1-匿名
|
||||
*/
|
||||
private Integer isAnonymous;
|
||||
|
||||
/**
|
||||
* 浏览数
|
||||
*/
|
||||
private Integer viewCount;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*/
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 分享数
|
||||
*/
|
||||
private Integer shareCount;
|
||||
|
||||
/**
|
||||
* AI评论内容
|
||||
*/
|
||||
private String aiComment;
|
||||
|
||||
/**
|
||||
* AI评论时间
|
||||
*/
|
||||
private String aiCommentTime;
|
||||
|
||||
/**
|
||||
* AI情绪分析结果
|
||||
*/
|
||||
private Object aiEmotionAnalysis;
|
||||
|
||||
/**
|
||||
* AI情感评分 (-1到1)
|
||||
*/
|
||||
private BigDecimal aiSentimentScore;
|
||||
|
||||
/**
|
||||
* AI提取的关键词
|
||||
*/
|
||||
private List<String> aiKeywords;
|
||||
|
||||
/**
|
||||
* AI建议
|
||||
*/
|
||||
private String aiSuggestions;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
private String publishTime;
|
||||
|
||||
/**
|
||||
* 最后评论时间
|
||||
*/
|
||||
private String lastCommentTime;
|
||||
|
||||
/**
|
||||
* 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 优先级 (用于置顶功能)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 是否精选: 0-普通, 1-精选
|
||||
*/
|
||||
private Integer featured;
|
||||
|
||||
/**
|
||||
* 扩展元数据
|
||||
*/
|
||||
private Object metadata;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private String createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private String updateTime;
|
||||
|
||||
/**
|
||||
* 是否已点赞
|
||||
*/
|
||||
private Boolean isLiked;
|
||||
|
||||
/**
|
||||
* 是否已收藏
|
||||
*/
|
||||
private Boolean isBookmarked;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.emotion.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.emotion.common.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日记评论实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("diary_comment")
|
||||
public class DiaryComment extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 日记ID
|
||||
*/
|
||||
@TableField("diary_id")
|
||||
private String diaryId;
|
||||
|
||||
/**
|
||||
* 评论用户ID
|
||||
*/
|
||||
@TableField("user_id")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 父评论ID (用于回复功能)
|
||||
*/
|
||||
@TableField("parent_comment_id")
|
||||
private String parentCommentId;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
@TableField("content")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 评论图片 (存储图片URL数组)
|
||||
*/
|
||||
@TableField("images")
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 评论类型: user-用户评论, ai-AI评论, system-系统评论
|
||||
*/
|
||||
@TableField("comment_type")
|
||||
private String commentType;
|
||||
|
||||
/**
|
||||
* AI评论来源 (如: emotion_analysis, sentiment_analysis)
|
||||
*/
|
||||
@TableField("ai_comment_source")
|
||||
private String aiCommentSource;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
@TableField("like_count")
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 回复数
|
||||
*/
|
||||
@TableField("reply_count")
|
||||
private Integer replyCount;
|
||||
|
||||
/**
|
||||
* 是否匿名评论: 0-实名, 1-匿名
|
||||
*/
|
||||
@TableField("is_anonymous")
|
||||
private Integer isAnonymous;
|
||||
|
||||
/**
|
||||
* 是否置顶: 0-普通, 1-置顶
|
||||
*/
|
||||
@TableField("is_top")
|
||||
private Integer isTop;
|
||||
|
||||
/**
|
||||
* 状态: published-已发布, hidden-隐藏, deleted-已删除
|
||||
*/
|
||||
@TableField("status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@TableField("publish_time")
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
/**
|
||||
* 最后回复时间
|
||||
*/
|
||||
@TableField("last_reply_time")
|
||||
private LocalDateTime lastReplyTime;
|
||||
|
||||
/**
|
||||
* 情绪评分
|
||||
*/
|
||||
@TableField("emotion_score")
|
||||
private BigDecimal emotionScore;
|
||||
|
||||
/**
|
||||
* 情感评分
|
||||
*/
|
||||
@TableField("sentiment_score")
|
||||
private BigDecimal sentimentScore;
|
||||
|
||||
/**
|
||||
* 扩展元数据
|
||||
*/
|
||||
@TableField("metadata")
|
||||
private String metadata;
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.emotion.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.emotion.common.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户日记实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("diary_post")
|
||||
public class DiaryPost extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@TableField("user_id")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 日记标题
|
||||
*/
|
||||
@TableField("title")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日记内容
|
||||
*/
|
||||
@TableField("content")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 图片列表 (存储图片URL数组)
|
||||
*/
|
||||
@TableField("images")
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 视频列表 (存储视频URL数组)
|
||||
*/
|
||||
@TableField("videos")
|
||||
private String videos;
|
||||
|
||||
/**
|
||||
* 发布地点
|
||||
*/
|
||||
@TableField("location")
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
@TableField("latitude")
|
||||
private BigDecimal latitude;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
@TableField("longitude")
|
||||
private BigDecimal longitude;
|
||||
|
||||
/**
|
||||
* 天气信息
|
||||
*/
|
||||
@TableField("weather")
|
||||
private String weather;
|
||||
|
||||
/**
|
||||
* 心情状态
|
||||
*/
|
||||
@TableField("mood")
|
||||
private String mood;
|
||||
|
||||
/**
|
||||
* 心情评分 (0-10)
|
||||
*/
|
||||
@TableField("mood_score")
|
||||
private BigDecimal moodScore;
|
||||
|
||||
/**
|
||||
* 标签列表
|
||||
*/
|
||||
@TableField("tags")
|
||||
private String tags;
|
||||
|
||||
/**
|
||||
* 是否公开: 0-仅自己可见, 1-公开
|
||||
*/
|
||||
@TableField("is_public")
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 是否匿名发布: 0-实名, 1-匿名
|
||||
*/
|
||||
@TableField("is_anonymous")
|
||||
private Integer isAnonymous;
|
||||
|
||||
/**
|
||||
* 浏览数
|
||||
*/
|
||||
@TableField("view_count")
|
||||
private Integer viewCount;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
@TableField("like_count")
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*/
|
||||
@TableField("comment_count")
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 分享数
|
||||
*/
|
||||
@TableField("share_count")
|
||||
private Integer shareCount;
|
||||
|
||||
/**
|
||||
* AI评论内容
|
||||
*/
|
||||
@TableField("ai_comment")
|
||||
private String aiComment;
|
||||
|
||||
/**
|
||||
* AI评论时间
|
||||
*/
|
||||
@TableField("ai_comment_time")
|
||||
private LocalDateTime aiCommentTime;
|
||||
|
||||
/**
|
||||
* AI情绪分析结果
|
||||
*/
|
||||
@TableField("ai_emotion_analysis")
|
||||
private String aiEmotionAnalysis;
|
||||
|
||||
/**
|
||||
* AI情感评分 (-1到1)
|
||||
*/
|
||||
@TableField("ai_sentiment_score")
|
||||
private BigDecimal aiSentimentScore;
|
||||
|
||||
/**
|
||||
* AI提取的关键词
|
||||
*/
|
||||
@TableField("ai_keywords")
|
||||
private String aiKeywords;
|
||||
|
||||
/**
|
||||
* AI建议
|
||||
*/
|
||||
@TableField("ai_suggestions")
|
||||
private String aiSuggestions;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@TableField("publish_time")
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
/**
|
||||
* 最后评论时间
|
||||
*/
|
||||
@TableField("last_comment_time")
|
||||
private LocalDateTime lastCommentTime;
|
||||
|
||||
/**
|
||||
* 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除
|
||||
*/
|
||||
@TableField("status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 优先级 (用于置顶功能)
|
||||
*/
|
||||
@TableField("priority")
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 是否精选: 0-普通, 1-精选
|
||||
*/
|
||||
@TableField("featured")
|
||||
private Integer featured;
|
||||
|
||||
/**
|
||||
* 扩展元数据
|
||||
*/
|
||||
@TableField("metadata")
|
||||
private String metadata;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.emotion.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.emotion.entity.DiaryComment;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 日记评论Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Mapper
|
||||
public interface DiaryCommentMapper extends BaseMapper<DiaryComment> {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.emotion.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.emotion.entity.DiaryPost;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户日记Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Mapper
|
||||
public interface DiaryPostMapper extends BaseMapper<DiaryPost> {
|
||||
}
|
||||
@@ -117,4 +117,13 @@ public interface AiChatService {
|
||||
* @return 包含情绪总结等信息的CompletableFuture
|
||||
*/
|
||||
java.util.concurrent.CompletableFuture<java.util.Map<String, Object>> generateEmotionSummaryAsync(String userId);
|
||||
|
||||
/**
|
||||
* 发送日记内容生成AI总结评论
|
||||
* @param conversationId 会话ID
|
||||
* @param userMessage 用户日记内容
|
||||
* @param userId 用户ID
|
||||
* @return AI评论内容
|
||||
*/
|
||||
String sendSummaryMessage(String conversationId, String userMessage, String userId);
|
||||
}
|
||||
|
||||
@@ -97,9 +97,33 @@ public interface AuthService {
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户名
|
||||
*
|
||||
*
|
||||
* @param token 访问令牌
|
||||
* @return 用户名
|
||||
*/
|
||||
String getUsernameFromToken(String token);
|
||||
|
||||
/**
|
||||
* 检查账号是否存在
|
||||
*
|
||||
* @param account 账号
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByAccount(String account);
|
||||
|
||||
/**
|
||||
* 检查邮箱是否存在
|
||||
*
|
||||
* @param email 邮箱
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
/**
|
||||
* 检查手机号是否存在
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByPhone(String phone);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.emotion.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.emotion.common.BasePageRequest;
|
||||
import com.emotion.entity.DiaryComment;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日记评论服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
public interface DiaryCommentService extends IService<DiaryComment> {
|
||||
|
||||
/**
|
||||
* 分页查询评论
|
||||
*/
|
||||
IPage<DiaryComment> getPage(BasePageRequest request);
|
||||
|
||||
/**
|
||||
* 根据日记ID分页查询评论
|
||||
*/
|
||||
IPage<DiaryComment> getPageByDiaryId(String diaryId, BasePageRequest request);
|
||||
|
||||
/**
|
||||
* 根据用户ID分页查询评论
|
||||
*/
|
||||
IPage<DiaryComment> getPageByUserId(String userId, BasePageRequest request);
|
||||
|
||||
/**
|
||||
* 根据父评论ID查询回复列表
|
||||
*/
|
||||
List<DiaryComment> getRepliesByParentId(String parentCommentId);
|
||||
|
||||
/**
|
||||
* 根据评论类型查询评论列表
|
||||
*/
|
||||
List<DiaryComment> getByCommentType(String commentType);
|
||||
|
||||
/**
|
||||
* 根据日记ID和评论类型查询评论列表
|
||||
*/
|
||||
List<DiaryComment> getByDiaryIdAndCommentType(String diaryId, String commentType);
|
||||
|
||||
/**
|
||||
* 增加点赞数
|
||||
*/
|
||||
boolean incrementLikeCount(String commentId);
|
||||
|
||||
/**
|
||||
* 减少点赞数
|
||||
*/
|
||||
boolean decrementLikeCount(String commentId);
|
||||
|
||||
/**
|
||||
* 增加回复数
|
||||
*/
|
||||
boolean incrementReplyCount(String commentId);
|
||||
|
||||
/**
|
||||
* 减少回复数
|
||||
*/
|
||||
boolean decrementReplyCount(String commentId);
|
||||
|
||||
/**
|
||||
* 更新最后回复时间
|
||||
*/
|
||||
boolean updateLastReplyTime(String commentId);
|
||||
|
||||
/**
|
||||
* 设置置顶状态
|
||||
*/
|
||||
boolean setTop(String commentId, Integer isTop);
|
||||
|
||||
/**
|
||||
* 统计日记评论数量
|
||||
*/
|
||||
Long countByDiaryId(String diaryId);
|
||||
|
||||
/**
|
||||
* 统计用户评论数量
|
||||
*/
|
||||
Long countByUserId(String userId);
|
||||
|
||||
/**
|
||||
* 统计指定类型的评论数量
|
||||
*/
|
||||
Long countByCommentType(String commentType);
|
||||
|
||||
/**
|
||||
* 统计回复数量
|
||||
*/
|
||||
Long countReplies(String parentCommentId);
|
||||
|
||||
/**
|
||||
* 创建评论
|
||||
*/
|
||||
DiaryComment createComment(String diaryId, String userId, String content, List<String> images,
|
||||
String parentCommentId, Integer isAnonymous);
|
||||
|
||||
/**
|
||||
* 创建AI评论
|
||||
*/
|
||||
DiaryComment createAiComment(String diaryId, String content, String aiCommentSource,
|
||||
BigDecimal emotionScore, BigDecimal sentimentScore);
|
||||
|
||||
/**
|
||||
* 更新评论
|
||||
*/
|
||||
boolean updateComment(String commentId, String content, List<String> images);
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
*/
|
||||
boolean deleteComment(String commentId);
|
||||
|
||||
/**
|
||||
* 软删除评论
|
||||
*/
|
||||
boolean softDeleteComment(String commentId);
|
||||
|
||||
/**
|
||||
* 恢复评论
|
||||
*/
|
||||
boolean restoreComment(String commentId);
|
||||
|
||||
/**
|
||||
* 获取评论树结构
|
||||
*/
|
||||
List<DiaryComment> getCommentTree(String diaryId);
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.emotion.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.emotion.common.BasePageRequest;
|
||||
import com.emotion.entity.DiaryPost;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户日记服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
public interface DiaryPostService extends IService<DiaryPost> {
|
||||
|
||||
/**
|
||||
* 分页查询日记
|
||||
*/
|
||||
IPage<DiaryPost> getPage(BasePageRequest request);
|
||||
|
||||
/**
|
||||
* 根据用户ID分页查询日记
|
||||
*/
|
||||
IPage<DiaryPost> getPageByUserId(String userId, BasePageRequest request);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询公开日记
|
||||
*/
|
||||
IPage<DiaryPost> getPublicPageByUserId(String userId, BasePageRequest request);
|
||||
|
||||
/**
|
||||
* 查询精选日记
|
||||
*/
|
||||
IPage<DiaryPost> getFeaturedPage(BasePageRequest request);
|
||||
|
||||
/**
|
||||
* 根据状态查询日记列表
|
||||
*/
|
||||
List<DiaryPost> getByStatus(String status);
|
||||
|
||||
/**
|
||||
* 根据用户ID和状态查询日记列表
|
||||
*/
|
||||
List<DiaryPost> getByUserIdAndStatus(String userId, String status);
|
||||
|
||||
/**
|
||||
* 根据心情状态查询日记列表
|
||||
*/
|
||||
List<DiaryPost> getByMood(String mood);
|
||||
|
||||
/**
|
||||
* 根据标签查询日记列表
|
||||
*/
|
||||
List<DiaryPost> getByTags(List<String> tags);
|
||||
|
||||
/**
|
||||
* 根据地点查询日记列表
|
||||
*/
|
||||
List<DiaryPost> getByLocation(String location);
|
||||
|
||||
/**
|
||||
* 增加浏览数
|
||||
*/
|
||||
boolean incrementViewCount(String diaryId);
|
||||
|
||||
/**
|
||||
* 增加点赞数
|
||||
*/
|
||||
boolean incrementLikeCount(String diaryId);
|
||||
|
||||
/**
|
||||
* 减少点赞数
|
||||
*/
|
||||
boolean decrementLikeCount(String diaryId);
|
||||
|
||||
/**
|
||||
* 增加评论数
|
||||
*/
|
||||
boolean incrementCommentCount(String diaryId);
|
||||
|
||||
/**
|
||||
* 减少评论数
|
||||
*/
|
||||
boolean decrementCommentCount(String diaryId);
|
||||
|
||||
/**
|
||||
* 增加分享数
|
||||
*/
|
||||
boolean incrementShareCount(String diaryId);
|
||||
|
||||
/**
|
||||
* 更新最后评论时间
|
||||
*/
|
||||
boolean updateLastCommentTime(String diaryId);
|
||||
|
||||
/**
|
||||
* 设置精选状态
|
||||
*/
|
||||
boolean setFeatured(String diaryId, Integer featured);
|
||||
|
||||
/**
|
||||
* 设置置顶优先级
|
||||
*/
|
||||
boolean setPriority(String diaryId, Integer priority);
|
||||
|
||||
/**
|
||||
* 统计用户日记数量
|
||||
*/
|
||||
Long countByUserId(String userId);
|
||||
|
||||
/**
|
||||
* 统计用户公开日记数量
|
||||
*/
|
||||
Long countPublicByUserId(String userId);
|
||||
|
||||
/**
|
||||
* 统计精选日记数量
|
||||
*/
|
||||
Long countFeatured();
|
||||
|
||||
/**
|
||||
* 统计指定状态的日记数量
|
||||
*/
|
||||
Long countByStatus(String status);
|
||||
|
||||
/**
|
||||
* 创建日记
|
||||
*/
|
||||
DiaryPost createDiaryPost(com.emotion.dto.request.DiaryPostCreateRequest request);
|
||||
|
||||
/**
|
||||
* 更新日记
|
||||
*/
|
||||
boolean updateDiaryPost(String diaryId, com.emotion.dto.request.DiaryPostUpdateRequest request);
|
||||
|
||||
/**
|
||||
* 删除日记
|
||||
*/
|
||||
boolean deleteDiaryPost(String diaryId);
|
||||
|
||||
/**
|
||||
* 软删除日记
|
||||
*/
|
||||
boolean softDeleteDiaryPost(String diaryId);
|
||||
|
||||
/**
|
||||
* 恢复日记
|
||||
*/
|
||||
boolean restoreDiaryPost(String diaryId);
|
||||
|
||||
/**
|
||||
* 添加AI评论
|
||||
*/
|
||||
boolean addAiComment(String diaryId, String aiComment, Object aiEmotionAnalysis,
|
||||
BigDecimal aiSentimentScore, List<String> aiKeywords, String aiSuggestions);
|
||||
|
||||
/**
|
||||
* 发表日记并生成AI评论
|
||||
* @param request 日记创建请求
|
||||
* @return 日记响应对象,包含AI评论
|
||||
*/
|
||||
com.emotion.dto.response.DiaryPostResponse publishDiaryWithAiComment(com.emotion.dto.request.DiaryPostCreateRequest request);
|
||||
}
|
||||
@@ -672,7 +672,7 @@ public class AiChatServiceImpl implements AiChatService {
|
||||
/**
|
||||
* 发送总结消息到Coze AI
|
||||
*/
|
||||
private String sendSummaryMessage(String conversationId, String userMessage, String userId) {
|
||||
public String sendSummaryMessage(String conversationId, String userMessage, String userId) {
|
||||
log.info("发送总结消息到Coze AI: conversationId={}, userId={}", conversationId, userId);
|
||||
|
||||
// 创建API调用记录(总结不需要messageId)
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.emotion.exception.CaptchaException;
|
||||
import com.emotion.exception.TokenException;
|
||||
import com.emotion.service.AuthService;
|
||||
import com.emotion.service.UserService;
|
||||
import com.emotion.util.JwtUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -33,10 +35,11 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 认证服务实现类
|
||||
*
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
@@ -49,6 +52,9 @@ public class AuthServiceImpl implements AuthService {
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
private static final String CAPTCHA_PREFIX = "captcha:";
|
||||
private static final String TOKEN_PREFIX = "token:";
|
||||
private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
|
||||
@@ -105,28 +111,53 @@ public class AuthServiceImpl implements AuthService {
|
||||
throw new CaptchaException("验证码错误或已过期");
|
||||
}
|
||||
|
||||
// 验证密码确认
|
||||
if (!request.getPassword().equals(request.getConfirmPassword())) {
|
||||
throw new BusinessException("密码与确认密码不一致");
|
||||
}
|
||||
|
||||
// 检查账号是否已存在
|
||||
if (userService.getByAccount(request.getAccount()) != null) {
|
||||
throw new BusinessException("账号已存在");
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
// 检查邮箱是否已存在(只有当邮箱不为空时才检查)
|
||||
if (StringUtils.hasText(request.getEmail()) && userService.getByEmail(request.getEmail()) != null) {
|
||||
throw new BusinessException("邮箱已被使用");
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在(只有当手机号不为空时才检查)
|
||||
if (StringUtils.hasText(request.getPhone()) && userService.getByPhone(request.getPhone()) != null) {
|
||||
throw new BusinessException("手机号已被使用");
|
||||
}
|
||||
|
||||
// 处理用户名:如果为空则使用账号
|
||||
String username = StringUtils.hasText(request.getUsername()) ? request.getUsername() : request.getAccount();
|
||||
|
||||
// 处理邮箱:如果为空字符串则设为null
|
||||
String email = StringUtils.hasText(request.getEmail()) ? request.getEmail() : null;
|
||||
|
||||
// 处理手机号:如果为空字符串则设为null
|
||||
String phone = StringUtils.hasText(request.getPhone()) ? request.getPhone() : null;
|
||||
|
||||
// 创建用户(密码在UserService中加密,这里不需要预先加密)
|
||||
User user = userService.createUser(
|
||||
request.getAccount(),
|
||||
StringUtils.hasText(request.getUsername()) ? request.getUsername() : request.getAccount(),
|
||||
username,
|
||||
request.getPassword(),
|
||||
request.getEmail(),
|
||||
request.getPhone()
|
||||
email,
|
||||
phone
|
||||
);
|
||||
|
||||
// 设置昵称
|
||||
// 设置昵称(如果昵称为空则使用用户名)
|
||||
if (StringUtils.hasText(request.getNickname())) {
|
||||
user.setNickname(request.getNickname());
|
||||
} else if (StringUtils.hasText(username)) {
|
||||
user.setNickname(username);
|
||||
}
|
||||
|
||||
// 如果有昵称变更,更新用户信息
|
||||
if (StringUtils.hasText(user.getNickname())) {
|
||||
userService.updateById(user);
|
||||
}
|
||||
|
||||
@@ -267,7 +298,18 @@ public class AuthServiceImpl implements AuthService {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return false;
|
||||
}
|
||||
return redisTemplate.hasKey(TOKEN_PREFIX + token);
|
||||
|
||||
try {
|
||||
// 首先尝试JWT验证
|
||||
if (jwtUtil.validateToken(token)) {
|
||||
// JWT验证成功,再检查Redis中是否存在(用于登出功能)
|
||||
return redisTemplate.hasKey(TOKEN_PREFIX + token);
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.warn("Token验证失败: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,28 +317,50 @@ public class AuthServiceImpl implements AuthService {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return null;
|
||||
}
|
||||
return (String) redisTemplate.opsForValue().get(TOKEN_PREFIX + token);
|
||||
|
||||
try {
|
||||
// 首先尝试从JWT中获取用户ID
|
||||
String userId = jwtUtil.getUserIdFromToken(token);
|
||||
if (userId != null) {
|
||||
// 验证Redis中是否存在该token(确保未被登出)
|
||||
if (redisTemplate.hasKey(TOKEN_PREFIX + token)) {
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.warn("从Token获取用户ID失败: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsernameFromToken(String token) {
|
||||
String userId = getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 直接从JWT中获取用户名
|
||||
return jwtUtil.getUsernameFromToken(token);
|
||||
} catch (Exception e) {
|
||||
log.warn("从Token获取用户名失败: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
User user = userService.getById(userId);
|
||||
return user != null ? user.getUsername() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成访问令牌
|
||||
*/
|
||||
private String generateAccessToken(User user) {
|
||||
String token = UUID.randomUUID().toString().replace("-", "");
|
||||
// 使用JWT生成token
|
||||
String token = jwtUtil.generateToken(user.getId(), user.getUsername());
|
||||
|
||||
// 同时在Redis中存储token信息,用于快速验证和登出功能
|
||||
redisTemplate.opsForValue().set(
|
||||
TOKEN_PREFIX + token,
|
||||
user.getId(),
|
||||
TOKEN_EXPIRE_HOURS,
|
||||
TOKEN_PREFIX + token,
|
||||
user.getId(),
|
||||
TOKEN_EXPIRE_HOURS,
|
||||
TimeUnit.HOURS
|
||||
);
|
||||
return token;
|
||||
@@ -306,11 +370,14 @@ public class AuthServiceImpl implements AuthService {
|
||||
* 生成刷新令牌
|
||||
*/
|
||||
private String generateRefreshToken(User user) {
|
||||
String refreshToken = UUID.randomUUID().toString().replace("-", "");
|
||||
// 使用JWT生成刷新token
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
|
||||
|
||||
// 在Redis中存储刷新token信息
|
||||
redisTemplate.opsForValue().set(
|
||||
REFRESH_TOKEN_PREFIX + refreshToken,
|
||||
user.getId(),
|
||||
REFRESH_TOKEN_EXPIRE_DAYS,
|
||||
REFRESH_TOKEN_PREFIX + refreshToken,
|
||||
user.getId(),
|
||||
REFRESH_TOKEN_EXPIRE_DAYS,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
return refreshToken;
|
||||
@@ -404,4 +471,31 @@ public class AuthServiceImpl implements AuthService {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByAccount(String account) {
|
||||
if (!StringUtils.hasText(account)) {
|
||||
return false;
|
||||
}
|
||||
User user = userService.getByAccount(account);
|
||||
return user != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByEmail(String email) {
|
||||
if (!StringUtils.hasText(email)) {
|
||||
return false;
|
||||
}
|
||||
User user = userService.getByEmail(email);
|
||||
return user != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByPhone(String phone) {
|
||||
if (!StringUtils.hasText(phone)) {
|
||||
return false;
|
||||
}
|
||||
User user = userService.getByPhone(phone);
|
||||
return user != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
package com.emotion.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.emotion.common.BasePageRequest;
|
||||
import com.emotion.entity.DiaryComment;
|
||||
import com.emotion.mapper.DiaryCommentMapper;
|
||||
import com.emotion.service.DiaryCommentService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 日记评论服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Service
|
||||
public class DiaryCommentServiceImpl extends ServiceImpl<DiaryCommentMapper, DiaryComment> implements DiaryCommentService {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public IPage<DiaryComment> getPage(BasePageRequest request) {
|
||||
Page<DiaryComment> page = new Page<>(request.getCurrent(), request.getSize());
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryComment::getIsTop)
|
||||
.orderByDesc(DiaryComment::getPublishTime);
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DiaryComment> getPageByDiaryId(String diaryId, BasePageRequest request) {
|
||||
Page<DiaryComment> page = new Page<>(request.getCurrent(), request.getSize());
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getDiaryId, diaryId)
|
||||
.eq(DiaryComment::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryComment::getIsTop)
|
||||
.orderByDesc(DiaryComment::getPublishTime);
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DiaryComment> getPageByUserId(String userId, BasePageRequest request) {
|
||||
Page<DiaryComment> page = new Page<>(request.getCurrent(), request.getSize());
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getUserId, userId)
|
||||
.eq(DiaryComment::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryComment::getPublishTime);
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryComment> getRepliesByParentId(String parentCommentId) {
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getParentCommentId, parentCommentId)
|
||||
.eq(DiaryComment::getIsDeleted, 0)
|
||||
.orderByAsc(DiaryComment::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryComment> getByCommentType(String commentType) {
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getCommentType, commentType)
|
||||
.eq(DiaryComment::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryComment::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryComment> getByDiaryIdAndCommentType(String diaryId, String commentType) {
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getDiaryId, diaryId)
|
||||
.eq(DiaryComment::getCommentType, commentType)
|
||||
.eq(DiaryComment::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryComment::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementLikeCount(String commentId) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.setSql("like_count = like_count + 1");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decrementLikeCount(String commentId) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.setSql("like_count = GREATEST(like_count - 1, 0)");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementReplyCount(String commentId) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.setSql("reply_count = reply_count + 1");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decrementReplyCount(String commentId) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.setSql("reply_count = GREATEST(reply_count - 1, 0)");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateLastReplyTime(String commentId) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.set(DiaryComment::getLastReplyTime, LocalDateTime.now());
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTop(String commentId, Integer isTop) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.set(DiaryComment::getIsTop, isTop);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByDiaryId(String diaryId) {
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getDiaryId, diaryId)
|
||||
.eq(DiaryComment::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByUserId(String userId) {
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getUserId, userId)
|
||||
.eq(DiaryComment::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByCommentType(String commentType) {
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getCommentType, commentType)
|
||||
.eq(DiaryComment::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countReplies(String parentCommentId) {
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getParentCommentId, parentCommentId)
|
||||
.eq(DiaryComment::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiaryComment createComment(String diaryId, String userId, String content, List<String> images,
|
||||
String parentCommentId, Integer isAnonymous) {
|
||||
DiaryComment comment = DiaryComment.builder()
|
||||
.diaryId(diaryId)
|
||||
.userId(userId)
|
||||
.content(content)
|
||||
.images(convertListToJson(images))
|
||||
.parentCommentId(parentCommentId)
|
||||
.commentType("user")
|
||||
.likeCount(0)
|
||||
.replyCount(0)
|
||||
.isAnonymous(isAnonymous)
|
||||
.isTop(0)
|
||||
.status("published")
|
||||
.publishTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
this.save(comment);
|
||||
|
||||
// 如果有父评论,更新父评论的回复数
|
||||
if (StringUtils.hasText(parentCommentId)) {
|
||||
this.incrementReplyCount(parentCommentId);
|
||||
this.updateLastReplyTime(parentCommentId);
|
||||
}
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiaryComment createAiComment(String diaryId, String content, String aiCommentSource,
|
||||
BigDecimal emotionScore, BigDecimal sentimentScore) {
|
||||
DiaryComment comment = DiaryComment.builder()
|
||||
.diaryId(diaryId)
|
||||
.userId("system") // AI评论使用system用户ID
|
||||
.content(content)
|
||||
.commentType("ai")
|
||||
.aiCommentSource(aiCommentSource)
|
||||
.likeCount(0)
|
||||
.replyCount(0)
|
||||
.isAnonymous(0)
|
||||
.isTop(0)
|
||||
.status("published")
|
||||
.publishTime(LocalDateTime.now())
|
||||
.emotionScore(emotionScore)
|
||||
.sentimentScore(sentimentScore)
|
||||
.build();
|
||||
|
||||
this.save(comment);
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateComment(String commentId, String content, List<String> images) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.set(StringUtils.hasText(content), DiaryComment::getContent, content)
|
||||
.set(images != null, DiaryComment::getImages, convertListToJson(images));
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteComment(String commentId) {
|
||||
return this.removeById(commentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean softDeleteComment(String commentId) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.set(DiaryComment::getIsDeleted, 1);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restoreComment(String commentId) {
|
||||
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryComment::getId, commentId)
|
||||
.set(DiaryComment::getIsDeleted, 0);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryComment> getCommentTree(String diaryId) {
|
||||
// 获取所有顶级评论(没有父评论的评论)
|
||||
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryComment::getDiaryId, diaryId)
|
||||
.isNull(DiaryComment::getParentCommentId)
|
||||
.eq(DiaryComment::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryComment::getIsTop)
|
||||
.orderByDesc(DiaryComment::getPublishTime);
|
||||
|
||||
List<DiaryComment> topComments = this.list(wrapper);
|
||||
|
||||
// 为每个顶级评论加载回复
|
||||
return topComments.stream()
|
||||
.peek(comment -> {
|
||||
List<DiaryComment> replies = getRepliesByParentId(comment.getId());
|
||||
// 这里可以递归加载更深层的回复,但为了性能考虑,通常只加载一层
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将List转换为JSON字符串
|
||||
*/
|
||||
private String convertListToJson(List<?> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(list);
|
||||
} catch (JsonProcessingException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
package com.emotion.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.emotion.common.BasePageRequest;
|
||||
import com.emotion.entity.DiaryPost;
|
||||
import com.emotion.mapper.DiaryPostMapper;
|
||||
import com.emotion.service.DiaryPostService;
|
||||
import com.emotion.service.DiaryCommentService;
|
||||
import com.emotion.service.AiChatService;
|
||||
import com.emotion.dto.request.DiaryPostCreateRequest;
|
||||
import com.emotion.dto.response.DiaryPostResponse;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户日记服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Service
|
||||
public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost> implements DiaryPostService {
|
||||
|
||||
@Autowired
|
||||
private AiChatService aiChatService;
|
||||
@Autowired
|
||||
private DiaryCommentService diaryCommentService;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public IPage<DiaryPost> getPage(BasePageRequest request) {
|
||||
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize());
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPriority)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DiaryPost> getPageByUserId(String userId, BasePageRequest request) {
|
||||
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize());
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getUserId, userId)
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPriority)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DiaryPost> getPublicPageByUserId(String userId, BasePageRequest request) {
|
||||
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize());
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getUserId, userId)
|
||||
.eq(DiaryPost::getIsPublic, 1)
|
||||
.eq(DiaryPost::getStatus, "published")
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPriority)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DiaryPost> getFeaturedPage(BasePageRequest request) {
|
||||
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize());
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getFeatured, 1)
|
||||
.eq(DiaryPost::getIsPublic, 1)
|
||||
.eq(DiaryPost::getStatus, "published")
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPriority)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryPost> getByStatus(String status) {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getStatus, status)
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryPost> getByUserIdAndStatus(String userId, String status) {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getUserId, userId)
|
||||
.eq(DiaryPost::getStatus, status)
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryPost> getByMood(String mood) {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getMood, mood)
|
||||
.eq(DiaryPost::getIsPublic, 1)
|
||||
.eq(DiaryPost::getStatus, "published")
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryPost> getByTags(List<String> tags) {
|
||||
// 这里需要根据实际需求实现标签查询逻辑
|
||||
// 由于tags字段是JSON格式,可能需要使用数据库的JSON查询功能
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getIsPublic, 1)
|
||||
.eq(DiaryPost::getStatus, "published")
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiaryPost> getByLocation(String location) {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(DiaryPost::getLocation, location)
|
||||
.eq(DiaryPost::getIsPublic, 1)
|
||||
.eq(DiaryPost::getStatus, "published")
|
||||
.eq(DiaryPost::getIsDeleted, 0)
|
||||
.orderByDesc(DiaryPost::getPublishTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementViewCount(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.setSql("view_count = view_count + 1");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementLikeCount(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.setSql("like_count = like_count + 1");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decrementLikeCount(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.setSql("like_count = GREATEST(like_count - 1, 0)");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementCommentCount(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.setSql("comment_count = comment_count + 1");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decrementCommentCount(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.setSql("comment_count = GREATEST(comment_count - 1, 0)");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementShareCount(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.setSql("share_count = share_count + 1");
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateLastCommentTime(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.set(DiaryPost::getLastCommentTime, LocalDateTime.now());
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setFeatured(String diaryId, Integer featured) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.set(DiaryPost::getFeatured, featured);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setPriority(String diaryId, Integer priority) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.set(DiaryPost::getPriority, priority);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByUserId(String userId) {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getUserId, userId)
|
||||
.eq(DiaryPost::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countPublicByUserId(String userId) {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getUserId, userId)
|
||||
.eq(DiaryPost::getIsPublic, 1)
|
||||
.eq(DiaryPost::getStatus, "published")
|
||||
.eq(DiaryPost::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countFeatured() {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getFeatured, 1)
|
||||
.eq(DiaryPost::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByStatus(String status) {
|
||||
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DiaryPost::getStatus, status)
|
||||
.eq(DiaryPost::getIsDeleted, 0);
|
||||
return this.count(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiaryPost createDiaryPost(com.emotion.dto.request.DiaryPostCreateRequest request) {
|
||||
// 处理标题:如果为空,则设置为null或生成默认标题
|
||||
String title = request.getTitle();
|
||||
if (title == null || title.trim().isEmpty()) {
|
||||
// 可以选择设置为null,或者生成一个默认标题
|
||||
// 这里我们设置为null,让数据库使用默认值
|
||||
title = null;
|
||||
}
|
||||
|
||||
DiaryPost diaryPost = DiaryPost.builder()
|
||||
.userId(request.getUserId())
|
||||
.title(title)
|
||||
.content(request.getContent())
|
||||
.images(convertListToJson(request.getImages()))
|
||||
.videos(convertListToJson(request.getVideos()))
|
||||
.location(request.getLocation())
|
||||
.weather(request.getWeather())
|
||||
.mood(request.getMood())
|
||||
.tags(convertListToJson(request.getTags()))
|
||||
.isPublic(request.getIsPublic())
|
||||
.isAnonymous(request.getIsAnonymous())
|
||||
.viewCount(0)
|
||||
.likeCount(0)
|
||||
.commentCount(0)
|
||||
.shareCount(0)
|
||||
.publishTime(LocalDateTime.now())
|
||||
.status("published")
|
||||
.priority(0)
|
||||
.featured(0)
|
||||
.build();
|
||||
|
||||
this.save(diaryPost);
|
||||
return diaryPost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDiaryPost(String diaryId, com.emotion.dto.request.DiaryPostUpdateRequest request) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.set(StringUtils.hasText(request.getTitle()), DiaryPost::getTitle, request.getTitle())
|
||||
.set(StringUtils.hasText(request.getContent()), DiaryPost::getContent, request.getContent())
|
||||
.set(request.getImages() != null, DiaryPost::getImages, convertListToJson(request.getImages()))
|
||||
.set(request.getVideos() != null, DiaryPost::getVideos, convertListToJson(request.getVideos()))
|
||||
.set(StringUtils.hasText(request.getLocation()), DiaryPost::getLocation, request.getLocation())
|
||||
.set(StringUtils.hasText(request.getWeather()), DiaryPost::getWeather, request.getWeather())
|
||||
.set(StringUtils.hasText(request.getMood()), DiaryPost::getMood, request.getMood())
|
||||
.set(request.getTags() != null, DiaryPost::getTags, convertListToJson(request.getTags()))
|
||||
.set(request.getIsPublic() != null, DiaryPost::getIsPublic, request.getIsPublic())
|
||||
.set(request.getIsAnonymous() != null, DiaryPost::getIsAnonymous, request.getIsAnonymous())
|
||||
.set(StringUtils.hasText(request.getStatus()), DiaryPost::getStatus, request.getStatus());
|
||||
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteDiaryPost(String diaryId) {
|
||||
return this.removeById(diaryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean softDeleteDiaryPost(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.set(DiaryPost::getIsDeleted, 1);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restoreDiaryPost(String diaryId) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.set(DiaryPost::getIsDeleted, 0);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAiComment(String diaryId, String aiComment, Object aiEmotionAnalysis,
|
||||
BigDecimal aiSentimentScore, List<String> aiKeywords, String aiSuggestions) {
|
||||
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DiaryPost::getId, diaryId)
|
||||
.set(DiaryPost::getAiComment, aiComment)
|
||||
.set(DiaryPost::getAiCommentTime, LocalDateTime.now())
|
||||
.set(DiaryPost::getAiEmotionAnalysis, convertObjectToJson(aiEmotionAnalysis))
|
||||
.set(DiaryPost::getAiSentimentScore, aiSentimentScore)
|
||||
.set(DiaryPost::getAiKeywords, convertListToJson(aiKeywords))
|
||||
.set(DiaryPost::getAiSuggestions, aiSuggestions);
|
||||
return this.update(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.emotion.dto.response.DiaryPostResponse publishDiaryWithAiComment(com.emotion.dto.request.DiaryPostCreateRequest request) {
|
||||
// 1. 保存日记
|
||||
DiaryPost diaryPost = createDiaryPost(request);
|
||||
// 2. 生成AI评论
|
||||
String aiComment = null;
|
||||
try {
|
||||
String conversationId = "diary-" + diaryPost.getId();
|
||||
aiComment = aiChatService.sendSummaryMessage(conversationId, diaryPost.getContent(), request.getUserId());
|
||||
} catch (Exception e) {
|
||||
aiComment = "开开:AI评论生成失败";
|
||||
}
|
||||
// 3. 写入AI评论到diary_comment表
|
||||
if (aiComment != null) {
|
||||
diaryCommentService.createAiComment(
|
||||
diaryPost.getId(),
|
||||
aiComment,
|
||||
"diary_ai_summary",
|
||||
null,
|
||||
null
|
||||
);
|
||||
addAiComment(
|
||||
diaryPost.getId(),
|
||||
aiComment,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
// 4. 返回日记详情(含AI评论)
|
||||
com.emotion.dto.response.DiaryPostResponse response = new com.emotion.dto.response.DiaryPostResponse();
|
||||
org.springframework.beans.BeanUtils.copyProperties(diaryPost, response);
|
||||
// 查询AI评论
|
||||
List<com.emotion.entity.DiaryComment> aiComments = diaryCommentService.getByDiaryIdAndCommentType(diaryPost.getId(), "ai");
|
||||
if (!aiComments.isEmpty()) {
|
||||
response.setAiComment(aiComments.get(0).getContent());
|
||||
}
|
||||
// 转换时间格式
|
||||
if (diaryPost.getPublishTime() != null) {
|
||||
response.setPublishTime(diaryPost.getPublishTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
if (diaryPost.getLastCommentTime() != null) {
|
||||
response.setLastCommentTime(diaryPost.getLastCommentTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
if (diaryPost.getAiCommentTime() != null) {
|
||||
response.setAiCommentTime(diaryPost.getAiCommentTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
if (diaryPost.getCreateTime() != null) {
|
||||
response.setCreateTime(diaryPost.getCreateTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
if (diaryPost.getUpdateTime() != null) {
|
||||
response.setUpdateTime(diaryPost.getUpdateTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
// 转换JSON字段
|
||||
try {
|
||||
if (diaryPost.getImages() != null) {
|
||||
response.setImages(objectMapper.readValue(diaryPost.getImages(), new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {}));
|
||||
}
|
||||
if (diaryPost.getVideos() != null) {
|
||||
response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {}));
|
||||
}
|
||||
if (diaryPost.getTags() != null) {
|
||||
response.setTags(objectMapper.readValue(diaryPost.getTags(), new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {}));
|
||||
}
|
||||
if (diaryPost.getAiKeywords() != null) {
|
||||
response.setAiKeywords(objectMapper.readValue(diaryPost.getAiKeywords(), new com.fasterxml.jackson.core.type.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 (com.fasterxml.jackson.core.JsonProcessingException e) {
|
||||
// 忽略JSON解析错误
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将List转换为JSON字符串
|
||||
*/
|
||||
private String convertListToJson(List<?> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(list);
|
||||
} catch (JsonProcessingException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Object转换为JSON字符串
|
||||
*/
|
||||
private String convertObjectToJson(Object obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(obj);
|
||||
} catch (JsonProcessingException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,26 +262,8 @@ public class WebSocketServiceImpl implements WebSocketService {
|
||||
userId
|
||||
);
|
||||
|
||||
// 构建AI回复消息(不分割,保持完整性)
|
||||
WebSocketMessage aiMessage = WebSocketMessage.builder()
|
||||
.messageId(UUID.randomUUID().toString())
|
||||
.conversationId(conversationId)
|
||||
.type(WebSocketMessage.MessageType.TEXT)
|
||||
.content(aiReply)
|
||||
.senderId("ai")
|
||||
.senderType(WebSocketMessage.SenderType.AI)
|
||||
.status(WebSocketMessage.MessageStatus.SENT)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// AI回复已经在sendChatMessageForWebSocket中保存了,这里不需要重复保存
|
||||
|
||||
// 发送AI回复
|
||||
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", aiMessage);
|
||||
|
||||
if (conversationId != null) {
|
||||
messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, aiMessage);
|
||||
}
|
||||
// 根据换行符分割AI回复并按顺序发送多条消息
|
||||
sendAiReplyInParts(userId, conversationId, aiReply);
|
||||
|
||||
// 更新会话的最后活跃时间和消息数量
|
||||
updateConversationActivity(conversationId);
|
||||
@@ -398,4 +380,158 @@ public class WebSocketServiceImpl implements WebSocketService {
|
||||
log.error("更新会话活跃状态失败: conversationId={}", conversationId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据换行符分割AI回复并按顺序发送多条消息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param conversationId 会话ID
|
||||
* @param aiReply AI回复内容
|
||||
*/
|
||||
private void sendAiReplyInParts(String userId, String conversationId, String aiReply) {
|
||||
try {
|
||||
log.info("开始处理AI回复消息: userId={}, conversationId={}, aiReply长度={}",
|
||||
userId, conversationId, aiReply != null ? aiReply.length() : 0);
|
||||
|
||||
if (aiReply == null || aiReply.trim().isEmpty()) {
|
||||
log.warn("AI回复内容为空,跳过发送");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否需要分割
|
||||
boolean needsSplit = aiReply.contains("\n\n") || aiReply.contains("\n");
|
||||
|
||||
if (!needsSplit) {
|
||||
// 不需要分割,直接发送完整消息
|
||||
log.info("AI回复无换行符,发送完整消息");
|
||||
sendSingleAiMessage(userId, conversationId, aiReply.trim());
|
||||
return;
|
||||
}
|
||||
|
||||
// 需要分割,按换行符分割并发送多条消息
|
||||
log.info("AI回复包含换行符,开始分割发送");
|
||||
String[] replyParts = splitAiReply(aiReply);
|
||||
|
||||
log.info("AI回复分割完成,共{}个部分", replyParts.length);
|
||||
|
||||
// 按顺序发送每个部分
|
||||
int sentCount = 0;
|
||||
for (int i = 0; i < replyParts.length; i++) {
|
||||
String part = replyParts[i].trim();
|
||||
|
||||
// 跳过空白部分
|
||||
if (part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 发送消息部分
|
||||
sendSingleAiMessage(userId, conversationId, part);
|
||||
sentCount++;
|
||||
|
||||
log.info("发送AI回复部分 {}/{}: 内容长度={}", sentCount, replyParts.length, part.length());
|
||||
|
||||
// 在多个部分之间添加短暂延迟,模拟自然对话节奏
|
||||
if (i < replyParts.length - 1) {
|
||||
try {
|
||||
Thread.sleep(500); // 延迟500毫秒
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("发送AI回复时被中断");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("AI回复发送完成: userId={}, conversationId={}, 实际发送{}条消息",
|
||||
userId, conversationId, sentCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("分割发送AI回复失败: userId={}, conversationId={}", userId, conversationId, e);
|
||||
|
||||
// 发送错误时,尝试发送完整的原始回复
|
||||
try {
|
||||
WebSocketMessage fallbackMessage = WebSocketMessage.builder()
|
||||
.messageId(UUID.randomUUID().toString())
|
||||
.conversationId(conversationId)
|
||||
.type(WebSocketMessage.MessageType.TEXT)
|
||||
.content(aiReply)
|
||||
.senderId("ai")
|
||||
.senderType(WebSocketMessage.SenderType.AI)
|
||||
.status(WebSocketMessage.MessageStatus.SENT)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", fallbackMessage);
|
||||
|
||||
if (conversationId != null) {
|
||||
messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, fallbackMessage);
|
||||
}
|
||||
|
||||
log.info("已发送完整AI回复作为备用方案");
|
||||
|
||||
} catch (Exception fallbackError) {
|
||||
log.error("发送备用AI回复也失败", fallbackError);
|
||||
sendErrorMessage(userId, "AI回复发送失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送单条AI消息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param conversationId 会话ID
|
||||
* @param content 消息内容
|
||||
*/
|
||||
private void sendSingleAiMessage(String userId, String conversationId, String content) {
|
||||
// 构建AI回复消息
|
||||
WebSocketMessage aiMessage = WebSocketMessage.builder()
|
||||
.messageId(UUID.randomUUID().toString())
|
||||
.conversationId(conversationId)
|
||||
.type(WebSocketMessage.MessageType.TEXT)
|
||||
.content(content)
|
||||
.senderId("ai")
|
||||
.senderType(WebSocketMessage.SenderType.AI)
|
||||
.status(WebSocketMessage.MessageStatus.SENT)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// 发送给用户私有队列
|
||||
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", aiMessage);
|
||||
|
||||
// 发送到会话公共频道
|
||||
if (conversationId != null) {
|
||||
messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, aiMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能分割AI回复内容
|
||||
*
|
||||
* @param aiReply AI回复内容
|
||||
* @return 分割后的内容数组
|
||||
*/
|
||||
private String[] splitAiReply(String aiReply) {
|
||||
if (aiReply == null || aiReply.trim().isEmpty()) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
// 首先尝试按双换行符分割(段落分割)
|
||||
if (aiReply.contains("\n\n")) {
|
||||
String[] parts = aiReply.split("\n\n");
|
||||
log.debug("按双换行符分割,得到{}个部分", parts.length);
|
||||
return parts;
|
||||
}
|
||||
|
||||
// 如果没有双换行符,按单换行符分割(行分割)
|
||||
if (aiReply.contains("\n")) {
|
||||
String[] parts = aiReply.split("\n");
|
||||
log.debug("按单换行符分割,得到{}个部分", parts.length);
|
||||
return parts;
|
||||
}
|
||||
|
||||
// 如果没有换行符,返回原始内容(这种情况不应该到达这里)
|
||||
log.debug("没有换行符,返回原始内容");
|
||||
return new String[]{aiReply};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +473,122 @@ CREATE TABLE reward (
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '奖励表';
|
||||
|
||||
-- ============================================================================
|
||||
-- 14. 访客用户表 (guest_user)
|
||||
-- 14. 用户统计表 (user_stats)
|
||||
-- ============================================================================
|
||||
CREATE TABLE user_stats (
|
||||
id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键
|
||||
user_id VARCHAR(64) UNIQUE COMMENT '用户ID', -- 用户ID
|
||||
total_conversations INT DEFAULT 0 COMMENT '总对话数', -- 总对话数
|
||||
total_messages INT DEFAULT 0 COMMENT '总消息数', -- 总消息数
|
||||
total_emotions_recorded INT DEFAULT 0 COMMENT '总情绪记录数', -- 总情绪记录数
|
||||
topics_completed INT DEFAULT 0 COMMENT '完成的课题数', -- 完成的课题数
|
||||
achievements_unlocked INT DEFAULT 0 COMMENT '解锁的成就数', -- 解锁的成就数
|
||||
total_points INT DEFAULT 0 COMMENT '总积分', -- 总积分
|
||||
consecutive_days INT DEFAULT 0 COMMENT '连续使用天数', -- 连续使用天数
|
||||
max_consecutive_days INT DEFAULT 0 COMMENT '最大连续天数', -- 最大连续天数
|
||||
locations_visited INT DEFAULT 0 COMMENT '访问的地点数', -- 访问的地点数
|
||||
posts_created INT DEFAULT 0 COMMENT '创建的帖子数', -- 创建的帖子数
|
||||
comments_made INT DEFAULT 0 COMMENT '评论数', -- 评论数
|
||||
likes_received INT DEFAULT 0 COMMENT '收到的点赞数', -- 收到的点赞数
|
||||
social_interactions INT DEFAULT 0 COMMENT '社交互动数', -- 社交互动数
|
||||
-- 日记相关统计
|
||||
diary_posts_created INT DEFAULT 0 COMMENT '创建的日记数', -- 创建的日记数
|
||||
diary_likes_received INT DEFAULT 0 COMMENT '日记收到的点赞数', -- 日记收到的点赞数
|
||||
diary_comments_received INT DEFAULT 0 COMMENT '日记收到的评论数', -- 日记收到的评论数
|
||||
diary_views_received INT DEFAULT 0 COMMENT '日记收到的浏览数', -- 日记收到的浏览数
|
||||
diary_shares_received INT DEFAULT 0 COMMENT '日记收到的分享数', -- 日记收到的分享数
|
||||
diary_comments_made INT DEFAULT 0 COMMENT '发表的日记评论数', -- 发表的日记评论数
|
||||
diary_likes_given INT DEFAULT 0 COMMENT '给他人日记的点赞数', -- 给他人日记的点赞数
|
||||
featured_diary_count INT DEFAULT 0 COMMENT '精选日记数量', -- 精选日记数量
|
||||
ai_comments_received INT DEFAULT 0 COMMENT '收到的AI评论数', -- 收到的AI评论数
|
||||
-- 公共字段
|
||||
create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间
|
||||
update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间
|
||||
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除
|
||||
remarks VARCHAR(500) COMMENT '备注' -- 备注
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户统计表';
|
||||
|
||||
-- ============================================================================
|
||||
-- 16. 用户日记表 (diary_post) - 类似朋友圈功能
|
||||
-- 关联说明: user_id 关联 user.id,通过代码逻辑维护关联关系
|
||||
-- ============================================================================
|
||||
CREATE TABLE diary_post (
|
||||
id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键
|
||||
user_id VARCHAR(64) COMMENT '用户ID (关联user.id)', -- 用户ID (关联user.id)
|
||||
title VARCHAR(200) COMMENT '日记标题', -- 日记标题
|
||||
content TEXT COMMENT '日记内容', -- 日记内容
|
||||
images JSON COMMENT '图片列表 (存储图片URL数组)', -- 图片列表 (存储图片URL数组)
|
||||
videos JSON COMMENT '视频列表 (存储视频URL数组)', -- 视频列表 (存储视频URL数组)
|
||||
location VARCHAR(200) COMMENT '发布地点', -- 发布地点
|
||||
latitude DECIMAL(10, 8) COMMENT '纬度', -- 纬度
|
||||
longitude DECIMAL(11, 8) COMMENT '经度', -- 经度
|
||||
weather VARCHAR(50) COMMENT '天气信息', -- 天气信息
|
||||
mood VARCHAR(50) COMMENT '心情状态', -- 心情状态
|
||||
mood_score DECIMAL(3, 2) COMMENT '心情评分 (0-10)', -- 心情评分 (0-10)
|
||||
tags JSON COMMENT '标签列表', -- 标签列表
|
||||
is_public TINYINT DEFAULT 1 COMMENT '是否公开: 0-仅自己可见, 1-公开', -- 是否公开: 0-仅自己可见, 1-公开
|
||||
is_anonymous TINYINT DEFAULT 0 COMMENT '是否匿名发布: 0-实名, 1-匿名', -- 是否匿名发布: 0-实名, 1-匿名
|
||||
view_count INT DEFAULT 0 COMMENT '浏览数', -- 浏览数
|
||||
like_count INT DEFAULT 0 COMMENT '点赞数', -- 点赞数
|
||||
comment_count INT DEFAULT 0 COMMENT '评论数', -- 评论数
|
||||
share_count INT DEFAULT 0 COMMENT '分享数', -- 分享数
|
||||
ai_comment TEXT COMMENT 'AI评论内容', -- AI评论内容
|
||||
ai_comment_time DATETIME COMMENT 'AI评论时间', -- AI评论时间
|
||||
ai_emotion_analysis JSON COMMENT 'AI情绪分析结果', -- AI情绪分析结果
|
||||
ai_sentiment_score DECIMAL(3, 2) COMMENT 'AI情感评分 (-1到1)', -- AI情感评分 (-1到1)
|
||||
ai_keywords JSON COMMENT 'AI提取的关键词', -- AI提取的关键词
|
||||
ai_suggestions TEXT COMMENT 'AI建议', -- AI建议
|
||||
publish_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间', -- 发布时间
|
||||
last_comment_time DATETIME COMMENT '最后评论时间', -- 最后评论时间
|
||||
status VARCHAR(20) DEFAULT 'published' COMMENT '状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除', -- 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除
|
||||
priority INT DEFAULT 0 COMMENT '优先级 (用于置顶功能)', -- 优先级 (用于置顶功能)
|
||||
featured TINYINT DEFAULT 0 COMMENT '是否精选: 0-普通, 1-精选', -- 是否精选: 0-普通, 1-精选
|
||||
metadata JSON COMMENT '扩展元数据', -- 扩展元数据
|
||||
-- 公共字段
|
||||
create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间
|
||||
update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间
|
||||
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除
|
||||
remarks VARCHAR(500) COMMENT '备注' -- 备注
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户日记表 - 类似朋友圈功能';
|
||||
|
||||
-- ============================================================================
|
||||
-- 17. 日记评论表 (diary_comment)
|
||||
-- 关联说明: diary_id 关联 diary_post.id,user_id 关联 user.id,通过代码逻辑维护关联关系
|
||||
-- ============================================================================
|
||||
CREATE TABLE diary_comment (
|
||||
id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键
|
||||
diary_id VARCHAR(64) COMMENT '日记ID (关联diary_post.id)', -- 日记ID (关联diary_post.id)
|
||||
user_id VARCHAR(64) COMMENT '评论用户ID (关联user.id)', -- 评论用户ID (关联user.id)
|
||||
parent_comment_id VARCHAR(64) COMMENT '父评论ID (用于回复功能)', -- 父评论ID (用于回复功能)
|
||||
content TEXT COMMENT '评论内容', -- 评论内容
|
||||
images JSON COMMENT '评论图片 (存储图片URL数组)', -- 评论图片 (存储图片URL数组)
|
||||
comment_type VARCHAR(20) DEFAULT 'user' COMMENT '评论类型: user-用户评论, ai-AI评论, system-系统评论', -- 评论类型: user-用户评论, ai-AI评论, system-系统评论
|
||||
ai_comment_source VARCHAR(50) COMMENT 'AI评论来源 (如: emotion_analysis, sentiment_analysis)', -- AI评论来源 (如: emotion_analysis, sentiment_analysis)
|
||||
like_count INT DEFAULT 0 COMMENT '点赞数', -- 点赞数
|
||||
reply_count INT DEFAULT 0 COMMENT '回复数', -- 回复数
|
||||
is_anonymous TINYINT DEFAULT 0 COMMENT '是否匿名评论: 0-实名, 1-匿名', -- 是否匿名评论: 0-实名, 1-匿名
|
||||
is_top TINYINT DEFAULT 0 COMMENT '是否置顶: 0-普通, 1-置顶', -- 是否置顶: 0-普通, 1-置顶
|
||||
status VARCHAR(20) DEFAULT 'published' COMMENT '状态: published-已发布, hidden-隐藏, deleted-已删除', -- 状态: published-已发布, hidden-隐藏, deleted-已删除
|
||||
publish_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间', -- 发布时间
|
||||
last_reply_time DATETIME COMMENT '最后回复时间', -- 最后回复时间
|
||||
emotion_score DECIMAL(3, 2) COMMENT '情绪评分', -- 情绪评分
|
||||
sentiment_score DECIMAL(3, 2) COMMENT '情感评分', -- 情感评分
|
||||
metadata JSON COMMENT '扩展元数据', -- 扩展元数据
|
||||
-- 公共字段
|
||||
create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间
|
||||
update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间
|
||||
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除
|
||||
remarks VARCHAR(500) COMMENT '备注' -- 备注
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '日记评论表';
|
||||
|
||||
-- ============================================================================
|
||||
-- 18. 访客用户表 (guest_user)
|
||||
-- ============================================================================
|
||||
CREATE TABLE guest_user (
|
||||
id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键
|
||||
@@ -496,34 +611,6 @@ CREATE TABLE guest_user (
|
||||
remarks VARCHAR(500) COMMENT '备注' -- 备注
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访客用户表';
|
||||
|
||||
-- ============================================================================
|
||||
-- 15. 用户统计表 (user_stats)
|
||||
-- ============================================================================
|
||||
CREATE TABLE user_stats (
|
||||
id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键
|
||||
user_id VARCHAR(64) UNIQUE COMMENT '用户ID', -- 用户ID
|
||||
total_conversations INT DEFAULT 0 COMMENT '总对话数', -- 总对话数
|
||||
total_messages INT DEFAULT 0 COMMENT '总消息数', -- 总消息数
|
||||
total_emotions_recorded INT DEFAULT 0 COMMENT '总情绪记录数', -- 总情绪记录数
|
||||
topics_completed INT DEFAULT 0 COMMENT '完成的课题数', -- 完成的课题数
|
||||
achievements_unlocked INT DEFAULT 0 COMMENT '解锁的成就数', -- 解锁的成就数
|
||||
total_points INT DEFAULT 0 COMMENT '总积分', -- 总积分
|
||||
consecutive_days INT DEFAULT 0 COMMENT '连续使用天数', -- 连续使用天数
|
||||
max_consecutive_days INT DEFAULT 0 COMMENT '最大连续天数', -- 最大连续天数
|
||||
locations_visited INT DEFAULT 0 COMMENT '访问的地点数', -- 访问的地点数
|
||||
posts_created INT DEFAULT 0 COMMENT '创建的帖子数', -- 创建的帖子数
|
||||
comments_made INT DEFAULT 0 COMMENT '评论数', -- 评论数
|
||||
likes_received INT DEFAULT 0 COMMENT '收到的点赞数', -- 收到的点赞数
|
||||
social_interactions INT DEFAULT 0 COMMENT '社交互动数', -- 社交互动数
|
||||
-- 公共字段
|
||||
create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间
|
||||
update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间
|
||||
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除
|
||||
remarks VARCHAR(500) COMMENT '备注' -- 备注
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户统计表';
|
||||
|
||||
-- ============================================================================
|
||||
-- 创建索引以提高查询性能
|
||||
-- 注意: MySQL的CREATE INDEX不支持IF NOT EXISTS
|
||||
@@ -813,6 +900,105 @@ CREATE INDEX idx_user_stats_update_time ON user_stats (update_time);
|
||||
|
||||
CREATE INDEX idx_user_stats_create_time ON user_stats (create_time);
|
||||
|
||||
-- user_stats表日记相关索引
|
||||
CREATE INDEX idx_user_stats_diary_posts_created ON user_stats (diary_posts_created);
|
||||
|
||||
CREATE INDEX idx_user_stats_diary_likes_received ON user_stats (diary_likes_received);
|
||||
|
||||
CREATE INDEX idx_user_stats_diary_comments_received ON user_stats (diary_comments_received);
|
||||
|
||||
CREATE INDEX idx_user_stats_diary_views_received ON user_stats (diary_views_received);
|
||||
|
||||
CREATE INDEX idx_user_stats_featured_diary_count ON user_stats (featured_diary_count);
|
||||
|
||||
CREATE INDEX idx_user_stats_ai_comments_received ON user_stats (ai_comments_received);
|
||||
|
||||
-- diary_post表索引
|
||||
CREATE INDEX idx_diary_post_user_id ON diary_post (user_id);
|
||||
|
||||
CREATE INDEX idx_diary_post_publish_time ON diary_post (publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_post_last_comment_time ON diary_post (last_comment_time);
|
||||
|
||||
CREATE INDEX idx_diary_post_status ON diary_post (status);
|
||||
|
||||
CREATE INDEX idx_diary_post_priority ON diary_post (priority);
|
||||
|
||||
CREATE INDEX idx_diary_post_featured ON diary_post (featured);
|
||||
|
||||
CREATE INDEX idx_diary_post_create_time ON diary_post (create_time);
|
||||
|
||||
-- diary_comment表索引
|
||||
CREATE INDEX idx_diary_comment_diary_id ON diary_comment (diary_id);
|
||||
|
||||
CREATE INDEX idx_diary_comment_user_id ON diary_comment (user_id);
|
||||
|
||||
CREATE INDEX idx_diary_comment_parent_comment_id ON diary_comment (parent_comment_id);
|
||||
|
||||
CREATE INDEX idx_diary_comment_publish_time ON diary_comment (publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_comment_last_reply_time ON diary_comment (last_reply_time);
|
||||
|
||||
CREATE INDEX idx_diary_comment_emotion_score ON diary_comment (emotion_score);
|
||||
|
||||
CREATE INDEX idx_diary_comment_sentiment_score ON diary_comment (sentiment_score);
|
||||
|
||||
CREATE INDEX idx_diary_comment_create_time ON diary_comment (create_time);
|
||||
|
||||
-- diary_post表复合索引和功能索引
|
||||
CREATE INDEX idx_diary_post_user_publish ON diary_post (user_id, publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_post_user_status ON diary_post (user_id, status);
|
||||
|
||||
CREATE INDEX idx_diary_post_public_publish ON diary_post (is_public, publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_post_featured_publish ON diary_post (featured, publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_post_mood_score ON diary_post (mood_score);
|
||||
|
||||
CREATE INDEX idx_diary_post_ai_sentiment ON diary_post (ai_sentiment_score);
|
||||
|
||||
CREATE INDEX idx_diary_post_location ON diary_post (latitude, longitude);
|
||||
|
||||
CREATE INDEX idx_diary_post_like_count ON diary_post (like_count);
|
||||
|
||||
CREATE INDEX idx_diary_post_comment_count ON diary_post (comment_count);
|
||||
|
||||
CREATE INDEX idx_diary_post_view_count ON diary_post (view_count);
|
||||
|
||||
CREATE INDEX idx_diary_post_create_by ON diary_post (create_by);
|
||||
|
||||
CREATE INDEX idx_diary_post_update_by ON diary_post (update_by);
|
||||
|
||||
CREATE INDEX idx_diary_post_is_deleted ON diary_post (is_deleted);
|
||||
|
||||
-- diary_comment表复合索引和功能索引
|
||||
CREATE INDEX idx_diary_comment_diary_publish ON diary_comment (diary_id, publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_comment_diary_type ON diary_comment (diary_id, comment_type);
|
||||
|
||||
CREATE INDEX idx_diary_comment_user_publish ON diary_comment (user_id, publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_comment_parent_publish ON diary_comment (parent_comment_id, publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_comment_type_publish ON diary_comment (comment_type, publish_time);
|
||||
|
||||
CREATE INDEX idx_diary_comment_status ON diary_comment (status);
|
||||
|
||||
CREATE INDEX idx_diary_comment_is_top ON diary_comment (is_top);
|
||||
|
||||
CREATE INDEX idx_diary_comment_like_count ON diary_comment (like_count);
|
||||
|
||||
CREATE INDEX idx_diary_comment_reply_count ON diary_comment (reply_count);
|
||||
|
||||
CREATE INDEX idx_diary_comment_ai_source ON diary_comment (ai_comment_source);
|
||||
|
||||
CREATE INDEX idx_diary_comment_create_by ON diary_comment (create_by);
|
||||
|
||||
CREATE INDEX idx_diary_comment_update_by ON diary_comment (update_by);
|
||||
|
||||
CREATE INDEX idx_diary_comment_is_deleted ON diary_comment (is_deleted);
|
||||
|
||||
-- guest_user表索引
|
||||
CREATE INDEX idx_guest_user_guest_user_id ON guest_user (guest_user_id);
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.emotion.controller;
|
||||
|
||||
import com.emotion.service.AuthService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* AuthController 测试类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-26
|
||||
*/
|
||||
@WebMvcTest(AuthController.class)
|
||||
public class AuthControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private AuthService authService;
|
||||
|
||||
@Test
|
||||
public void testCheckAccountExists() throws Exception {
|
||||
// 模拟账户存在的情况
|
||||
when(authService.existsByAccount("existingUser")).thenReturn(true);
|
||||
|
||||
mockMvc.perform(get("/auth/check-account")
|
||||
.param("account", "existingUser"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccountNotExists() throws Exception {
|
||||
// 模拟账户不存在的情况
|
||||
when(authService.existsByAccount("nonExistingUser")).thenReturn(false);
|
||||
|
||||
mockMvc.perform(get("/auth/check-account")
|
||||
.param("account", "nonExistingUser"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckEmailExists() throws Exception {
|
||||
// 模拟邮箱存在的情况
|
||||
when(authService.existsByEmail("existing@example.com")).thenReturn(true);
|
||||
|
||||
mockMvc.perform(get("/auth/check-email")
|
||||
.param("email", "existing@example.com"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckEmailNotExists() throws Exception {
|
||||
// 模拟邮箱不存在的情况
|
||||
when(authService.existsByEmail("nonexisting@example.com")).thenReturn(false);
|
||||
|
||||
mockMvc.perform(get("/auth/check-email")
|
||||
.param("email", "nonexisting@example.com"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckPhoneExists() throws Exception {
|
||||
// 模拟手机号存在的情况
|
||||
when(authService.existsByPhone("13800138000")).thenReturn(true);
|
||||
|
||||
mockMvc.perform(get("/auth/check-phone")
|
||||
.param("phone", "13800138000"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckPhoneNotExists() throws Exception {
|
||||
// 模拟手机号不存在的情况
|
||||
when(authService.existsByPhone("13900139000")).thenReturn(false);
|
||||
|
||||
mockMvc.perform(get("/auth/check-phone")
|
||||
.param("phone", "13900139000"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(false));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user