feat: 新增协作邀请功能与标签管理
Some checks failed
test/timeline-server/pipeline/head Something is wrong with the build of this commit
Some checks failed
test/timeline-server/pipeline/head Something is wrong with the build of this commit
新增故事协作邀请功能,包括邀请状态字段和相关接口 添加标签管理功能,支持时间线节点的标签分类 实现智能填充服务,从图片EXIF提取时间和地点信息 优化Docker镜像使用Alpine基础镜像减少体积 新增批量操作功能,包括排序、删除和时间修改 扩展通知系统支持协作邀请相关消息 添加评论和提醒功能相关实体和服务接口
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
package com.timeline.story.controller;
|
||||
|
||||
import com.timeline.common.response.ResponseEntity;
|
||||
import com.timeline.common.utils.UserContextUtils;
|
||||
import com.timeline.story.service.AnalyticsService;
|
||||
import com.timeline.story.vo.TimelineAnalyticsVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* AnalyticsController - 数据分析控制器
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供时间线数据分析的 REST API 接口。
|
||||
*
|
||||
* API 列表:
|
||||
* - GET /story/analytics/overview: 获取总体统计
|
||||
* - GET /story/analytics/story/{storyId}: 获取故事统计
|
||||
* - GET /story/analytics/monthly-trend: 获取月度趋势
|
||||
* - GET /story/analytics/top-locations: 获取热门地点
|
||||
* - GET /story/analytics/top-tags: 获取热门标签
|
||||
* - GET /story/analytics/yearly-report: 获取年度报告
|
||||
* - GET /story/analytics/activity: 获取活跃度统计
|
||||
* - GET /story/analytics/export: 导出统计数据
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/story/analytics")
|
||||
public class AnalyticsController {
|
||||
|
||||
@Autowired
|
||||
private AnalyticsService analyticsService;
|
||||
|
||||
/**
|
||||
* 获取用户时间线总体统计
|
||||
*
|
||||
* @return 统计数据
|
||||
*/
|
||||
@GetMapping("/overview")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo> getOverview() {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取用户总体统计: userId={}", userId);
|
||||
|
||||
TimelineAnalyticsVo stats = analyticsService.getOverallStats(userId);
|
||||
return com.timeline.common.response.ResponseEntity.success(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取故事统计数据
|
||||
*
|
||||
* @param storyInstanceId 故事ID
|
||||
* @return 统计数据
|
||||
*/
|
||||
@GetMapping("/story/{storyInstanceId}")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo> getStoryStats(
|
||||
@PathVariable String storyInstanceId) {
|
||||
log.info("获取故事统计: storyId={}", storyInstanceId);
|
||||
|
||||
TimelineAnalyticsVo stats = analyticsService.getStoryStats(storyInstanceId);
|
||||
return com.timeline.common.response.ResponseEntity.success(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取月度趋势数据
|
||||
*
|
||||
* @param year 年份(可选,默认当前年)
|
||||
* @return 月度趋势
|
||||
*/
|
||||
@GetMapping("/monthly-trend")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo> getMonthlyTrend(
|
||||
@RequestParam(required = false) Integer year) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
|
||||
if (year == null) {
|
||||
year = java.time.Year.now().getValue();
|
||||
}
|
||||
|
||||
log.info("获取月度趋势: userId={}, year={}", userId, year);
|
||||
|
||||
TimelineAnalyticsVo stats = analyticsService.getMonthlyTrend(userId, year);
|
||||
return com.timeline.common.response.ResponseEntity.success(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热门地点
|
||||
*
|
||||
* @param limit 返回数量(默认10)
|
||||
* @return 热门地点列表
|
||||
*/
|
||||
@GetMapping("/top-locations")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo> getTopLocations(
|
||||
@RequestParam(defaultValue = "10") int limit) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取热门地点: userId={}, limit={}", userId, limit);
|
||||
|
||||
TimelineAnalyticsVo stats = analyticsService.getTopLocations(userId, limit);
|
||||
return com.timeline.common.response.ResponseEntity.success(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热门标签
|
||||
*
|
||||
* @param limit 返回数量(默认10)
|
||||
* @return 热门标签列表
|
||||
*/
|
||||
@GetMapping("/top-tags")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo> getTopTags(
|
||||
@RequestParam(defaultValue = "10") int limit) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取热门标签: userId={}, limit={}", userId, limit);
|
||||
|
||||
TimelineAnalyticsVo stats = analyticsService.getTopTags(userId, limit);
|
||||
return com.timeline.common.response.ResponseEntity.success(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年度报告
|
||||
*
|
||||
* @param year 年份(可选,默认去年)
|
||||
* @return 年度报告
|
||||
*/
|
||||
@GetMapping("/yearly-report")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo.YearlyReport> getYearlyReport(
|
||||
@RequestParam(required = false) Integer year) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
|
||||
if (year == null) {
|
||||
year = java.time.Year.now().getValue() - 1;
|
||||
}
|
||||
|
||||
log.info("获取年度报告: userId={}, year={}", userId, year);
|
||||
|
||||
TimelineAnalyticsVo.YearlyReport report = analyticsService.generateYearlyReport(userId, year);
|
||||
return com.timeline.common.response.ResponseEntity.success(report);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活跃度统计
|
||||
*
|
||||
* @return 活跃度数据
|
||||
*/
|
||||
@GetMapping("/activity")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo> getActivity() {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取活跃度统计: userId={}", userId);
|
||||
|
||||
TimelineAnalyticsVo stats = analyticsService.getActivityStats(userId);
|
||||
return com.timeline.common.response.ResponseEntity.success(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间分布
|
||||
*
|
||||
* @return 时间分布数据
|
||||
*/
|
||||
@GetMapping("/time-distribution")
|
||||
public com.timeline.common.response.ResponseEntity<TimelineAnalyticsVo> getTimeDistribution() {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取时间分布: userId={}", userId);
|
||||
|
||||
TimelineAnalyticsVo stats = analyticsService.getTimeDistribution(userId);
|
||||
return com.timeline.common.response.ResponseEntity.success(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出统计数据
|
||||
*
|
||||
* @param format 导出格式 (json/csv)
|
||||
* @param response HTTP 响应
|
||||
*/
|
||||
@GetMapping("/export")
|
||||
public void exportStats(
|
||||
@RequestParam(defaultValue = "json") String format,
|
||||
HttpServletResponse response) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("导出统计数据: userId={}, format={}", userId, format);
|
||||
|
||||
try {
|
||||
byte[] data = analyticsService.exportStats(userId, format);
|
||||
|
||||
String fileName = "timeline_stats_" + java.time.LocalDate.now() + "." + format;
|
||||
response.setContentType(format.equals("csv") ? "text/csv" : "application/json");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
|
||||
response.getOutputStream().write(data);
|
||||
response.getOutputStream().flush();
|
||||
} catch (Exception e) {
|
||||
log.error("导出统计数据失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.timeline.story.controller;
|
||||
|
||||
import com.timeline.common.response.ResponseEntity;
|
||||
import com.timeline.common.utils.UserContextUtils;
|
||||
import com.timeline.story.entity.StoryComment;
|
||||
import com.timeline.story.service.CommentService;
|
||||
import com.timeline.story.vo.CommentVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CommentController - 评论控制器
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供评论相关的 REST API 接口。
|
||||
*
|
||||
* API 列表:
|
||||
* - POST /story/comment: 发表评论
|
||||
* - POST /story/comment/{parentId}/reply: 回复评论
|
||||
* - DELETE /story/comment/{commentId}: 删除评论
|
||||
* - GET /story/comment/{commentId}: 获取评论详情
|
||||
* - GET /story/comment/list/{storyItemId}: 获取节点的评论列表
|
||||
* - GET /story/comment/{commentId}/replies: 获取评论的回复列表
|
||||
* - POST /story/comment/{commentId}/like: 点赞评论
|
||||
* - DELETE /story/comment/{commentId}/like: 取消点赞
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/story/comment")
|
||||
public class CommentController {
|
||||
|
||||
@Autowired
|
||||
private CommentService commentService;
|
||||
|
||||
/**
|
||||
* 发表评论
|
||||
*
|
||||
* @param commentVo 评论内容
|
||||
* @return 创建的评论
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<CommentVo> createComment(@RequestBody CommentVo commentVo) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("用户 {} 发表评论: storyItemId={}", userId, commentVo.getStoryItemId());
|
||||
|
||||
StoryComment comment = commentService.createComment(commentVo);
|
||||
return ResponseEntity.success(convertToVo(comment));
|
||||
}
|
||||
|
||||
/**
|
||||
* 回复评论
|
||||
*
|
||||
* @param parentId 父评论ID
|
||||
* @param commentVo 回复内容
|
||||
* @return 创建的回复
|
||||
*/
|
||||
@PostMapping("/{parentId}/reply")
|
||||
public ResponseEntity<CommentVo> replyComment(
|
||||
@PathVariable String parentId,
|
||||
@RequestBody CommentVo commentVo) {
|
||||
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("用户 {} 回复评论: parentId={}", userId, parentId);
|
||||
|
||||
StoryComment comment = commentService.replyComment(parentId, commentVo);
|
||||
return ResponseEntity.success(convertToVo(comment));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/{commentInstanceId}")
|
||||
public ResponseEntity<String> deleteComment(@PathVariable String commentInstanceId) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("用户 {} 删除评论: {}", userId, commentInstanceId);
|
||||
|
||||
commentService.deleteComment(commentInstanceId);
|
||||
return ResponseEntity.success("评论已删除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评论详情
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @return 评论信息
|
||||
*/
|
||||
@GetMapping("/{commentInstanceId}")
|
||||
public ResponseEntity<CommentVo> getComment(@PathVariable String commentInstanceId) {
|
||||
StoryComment comment = commentService.getCommentById(commentInstanceId);
|
||||
return ResponseEntity.success(convertToVo(comment));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的评论列表
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 评论列表
|
||||
*/
|
||||
@GetMapping("/list/{storyItemId}")
|
||||
public ResponseEntity<List<CommentVo>> getComments(
|
||||
@PathVariable String storyItemId,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
|
||||
log.info("获取节点评论列表: storyItemId={}", storyItemId);
|
||||
|
||||
List<CommentVo> comments = commentService.getCommentsByStoryItem(storyItemId, pageNum, pageSize);
|
||||
return ResponseEntity.success(comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评论的回复列表
|
||||
*
|
||||
* @param parentId 父评论ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 回复列表
|
||||
*/
|
||||
@GetMapping("/{parentId}/replies")
|
||||
public ResponseEntity<List<CommentVo>> getReplies(
|
||||
@PathVariable String parentId,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "5") int pageSize) {
|
||||
|
||||
log.info("获取评论回复列表: parentId={}", parentId);
|
||||
|
||||
List<CommentVo> replies = commentService.getRepliesByComment(parentId, pageNum, pageSize);
|
||||
return ResponseEntity.success(replies);
|
||||
}
|
||||
|
||||
/**
|
||||
* 点赞评论
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/{commentInstanceId}/like")
|
||||
public ResponseEntity<String> likeComment(@PathVariable String commentInstanceId) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("用户 {} 点赞评论: {}", userId, commentInstanceId);
|
||||
|
||||
commentService.likeComment(commentInstanceId, userId);
|
||||
return ResponseEntity.success("点赞成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消点赞
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/{commentInstanceId}/like")
|
||||
public ResponseEntity<String> unlikeComment(@PathVariable String commentInstanceId) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("用户 {} 取消点赞评论: {}", userId, commentInstanceId);
|
||||
|
||||
commentService.unlikeComment(commentInstanceId, userId);
|
||||
return ResponseEntity.success("取消点赞成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的评论列表
|
||||
*
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 评论列表
|
||||
*/
|
||||
@GetMapping("/my")
|
||||
public ResponseEntity<List<CommentVo>> getMyComments(
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
List<CommentVo> comments = commentService.getCommentsByUser(userId, pageNum, pageSize);
|
||||
return ResponseEntity.success(comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private CommentVo convertToVo(StoryComment comment) {
|
||||
if (comment == null) return null;
|
||||
|
||||
CommentVo vo = new CommentVo();
|
||||
vo.setInstanceId(comment.getInstanceId());
|
||||
vo.setStoryItemId(comment.getStoryItemId());
|
||||
vo.setUserId(comment.getUserId());
|
||||
vo.setParentId(comment.getParentId());
|
||||
vo.setReplyToUserId(comment.getReplyToUserId());
|
||||
vo.setContent(comment.getContent());
|
||||
vo.setLikeCount(comment.getLikeCount());
|
||||
vo.setCreateTime(comment.getCreateTime());
|
||||
vo.setUpdateTime(comment.getUpdateTime());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.timeline.story.controller;
|
||||
|
||||
import com.timeline.common.response.ResponseEntity;
|
||||
import com.timeline.common.utils.UserContextUtils;
|
||||
import com.timeline.story.entity.Notification;
|
||||
import com.timeline.story.service.NotificationService;
|
||||
import com.timeline.story.vo.NotificationVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* NotificationController - 消息通知控制器
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供消息通知相关的 REST API 接口。
|
||||
*
|
||||
* API 列表:
|
||||
* - GET /story/notification/list: 获取通知列表
|
||||
* - GET /story/notification/unread: 获取未读通知列表
|
||||
* - GET /story/notification/unread-count: 获取未读数量
|
||||
* - PUT /story/notification/{notificationId}/read: 标记已读
|
||||
* - PUT /story/notification/read-all: 全部标记已读
|
||||
* - DELETE /story/notification/{notificationId}: 删除通知
|
||||
* - DELETE /story/notification/clear: 清空所有通知
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/story/notification")
|
||||
public class NotificationController {
|
||||
|
||||
@Autowired
|
||||
private NotificationService notificationService;
|
||||
|
||||
/**
|
||||
* 获取通知列表
|
||||
*
|
||||
* @param type 消息类型(可选)
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 通知列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<List<NotificationVo>> getNotifications(
|
||||
@RequestParam(required = false) String type,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取用户通知列表: userId={}, type={}", userId, type);
|
||||
|
||||
List<NotificationVo> notifications = notificationService.getNotifications(userId, type, pageNum, pageSize);
|
||||
return ResponseEntity.success(notifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读通知列表
|
||||
*
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 未读通知列表
|
||||
*/
|
||||
@GetMapping("/unread")
|
||||
public ResponseEntity<List<NotificationVo>> getUnreadNotifications(
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取用户未读通知: userId={}", userId);
|
||||
|
||||
List<NotificationVo> notifications = notificationService.getUnreadNotifications(userId, pageNum, pageSize);
|
||||
return ResponseEntity.success(notifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读通知数量
|
||||
*
|
||||
* @return 未读数量
|
||||
*/
|
||||
@GetMapping("/unread-count")
|
||||
public ResponseEntity<Map<String, Object>> getUnreadCount() {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
|
||||
int totalCount = notificationService.getUnreadCount(userId);
|
||||
Map<String, Integer> countByType = notificationService.getUnreadCountByType(userId);
|
||||
|
||||
return ResponseEntity.success(Map.of(
|
||||
"total", totalCount,
|
||||
"byType", countByType
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记单条通知为已读
|
||||
*
|
||||
* @param notificationInstanceId 通知ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/{notificationInstanceId}/read")
|
||||
public ResponseEntity<String> markAsRead(@PathVariable String notificationInstanceId) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("标记通知已读: userId={}, notificationId={}", userId, notificationInstanceId);
|
||||
|
||||
notificationService.markAsRead(notificationInstanceId, userId);
|
||||
return ResponseEntity.success("已标记为已读");
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记所有通知为已读
|
||||
*
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/read-all")
|
||||
public ResponseEntity<String> markAllAsRead() {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("标记所有通知已读: userId={}", userId);
|
||||
|
||||
notificationService.markAllAsRead(userId);
|
||||
return ResponseEntity.success("已全部标记为已读");
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型标记所有通知为已读
|
||||
*
|
||||
* @param type 消息类型
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/read-all/{type}")
|
||||
public ResponseEntity<String> markAllAsReadByType(@PathVariable String type) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("按类型标记通知已读: userId={}, type={}", userId, type);
|
||||
|
||||
notificationService.markAllAsReadByType(userId, type);
|
||||
return ResponseEntity.success("已标记为已读");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除通知
|
||||
*
|
||||
* @param notificationInstanceId 通知ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/{notificationInstanceId}")
|
||||
public ResponseEntity<String> deleteNotification(@PathVariable String notificationInstanceId) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("删除通知: userId={}, notificationId={}", userId, notificationInstanceId);
|
||||
|
||||
notificationService.deleteNotification(notificationInstanceId, userId);
|
||||
return ResponseEntity.success("通知已删除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有通知
|
||||
*
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/clear")
|
||||
public ResponseEntity<String> clearAllNotifications() {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("清空所有通知: userId={}", userId);
|
||||
|
||||
notificationService.clearAllNotifications(userId);
|
||||
return ResponseEntity.success("通知已清空");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.timeline.story.controller;
|
||||
|
||||
import com.timeline.common.response.ResponseEntity;
|
||||
import com.timeline.common.utils.UserContextUtils;
|
||||
import com.timeline.story.entity.Reminder;
|
||||
import com.timeline.story.service.ReminderService;
|
||||
import com.timeline.story.vo.ReminderVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ReminderController - 提醒控制器
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供提醒管理的 REST API 接口。
|
||||
*
|
||||
* API 列表:
|
||||
* - POST /story/reminder: 创建提醒
|
||||
* - PUT /story/reminder/{reminderId}: 更新提醒
|
||||
* - DELETE /story/reminder/{reminderId}: 删除提醒
|
||||
* - GET /story/reminder/list: 获取提醒列表
|
||||
* - PUT /story/reminder/{reminderId}/toggle: 启用/禁用提醒
|
||||
* - POST /story/reminder/memory: 生成回忆提醒
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/story/reminder")
|
||||
public class ReminderController {
|
||||
|
||||
@Autowired
|
||||
private ReminderService reminderService;
|
||||
|
||||
/**
|
||||
* 创建提醒
|
||||
*
|
||||
* @param reminderVo 提醒信息
|
||||
* @return 创建的提醒
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<ReminderVo> createReminder(@RequestBody ReminderVo reminderVo) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("创建提醒: userId={}, type={}", userId, reminderVo.getType());
|
||||
|
||||
Reminder reminder = reminderService.createReminder(reminderVo);
|
||||
return ResponseEntity.success(convertToVo(reminder));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新提醒
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
* @param reminderVo 提醒信息
|
||||
* @return 更新后的提醒
|
||||
*/
|
||||
@PutMapping("/{reminderInstanceId}")
|
||||
public ResponseEntity<ReminderVo> updateReminder(
|
||||
@PathVariable String reminderInstanceId,
|
||||
@RequestBody ReminderVo reminderVo) {
|
||||
|
||||
log.info("更新提醒: reminderId={}", reminderInstanceId);
|
||||
|
||||
Reminder reminder = reminderService.updateReminder(reminderInstanceId, reminderVo);
|
||||
return ResponseEntity.success(convertToVo(reminder));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除提醒
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/{reminderInstanceId}")
|
||||
public ResponseEntity<String> deleteReminder(@PathVariable String reminderInstanceId) {
|
||||
log.info("删除提醒: reminderId={}", reminderInstanceId);
|
||||
|
||||
reminderService.deleteReminder(reminderInstanceId);
|
||||
return ResponseEntity.success("提醒已删除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提醒详情
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
* @return 提醒信息
|
||||
*/
|
||||
@GetMapping("/{reminderInstanceId}")
|
||||
public ResponseEntity<ReminderVo> getReminder(@PathVariable String reminderInstanceId) {
|
||||
Reminder reminder = reminderService.getReminderById(reminderInstanceId);
|
||||
return ResponseEntity.success(convertToVo(reminder));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的提醒列表
|
||||
*
|
||||
* @param type 提醒类型(可选)
|
||||
* @return 提醒列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<List<ReminderVo>> getReminders(
|
||||
@RequestParam(required = false) String type) {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("获取提醒列表: userId={}, type={}", userId, type);
|
||||
|
||||
List<Reminder> reminders = reminderService.getRemindersByUser(userId, type);
|
||||
List<ReminderVo> voList = reminders.stream()
|
||||
.map(this::convertToVo)
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.success(voList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用提醒
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
* @param enabled 是否启用
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/{reminderInstanceId}/toggle")
|
||||
public ResponseEntity<String> toggleReminder(
|
||||
@PathVariable String reminderInstanceId,
|
||||
@RequestParam boolean enabled) {
|
||||
|
||||
log.info("切换提醒状态: reminderId={}, enabled={}", reminderInstanceId, enabled);
|
||||
|
||||
reminderService.toggleReminder(reminderInstanceId, enabled);
|
||||
return ResponseEntity.success(enabled ? "提醒已启用" : "提醒已禁用");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成回忆提醒
|
||||
*
|
||||
* 功能描述:
|
||||
* 检查用户是否有"去年的今天"的记录,
|
||||
* 如果有则生成回忆提醒。
|
||||
*
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/memory")
|
||||
public ResponseEntity<String> generateMemoryReminders() {
|
||||
String userId = UserContextUtils.getCurrentUserId();
|
||||
log.info("生成回忆提醒: userId={}", userId);
|
||||
|
||||
reminderService.generateMemoryReminders(userId);
|
||||
return ResponseEntity.success("回忆提醒已生成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private ReminderVo convertToVo(Reminder reminder) {
|
||||
if (reminder == null) return null;
|
||||
|
||||
ReminderVo vo = new ReminderVo();
|
||||
vo.setInstanceId(reminder.getInstanceId());
|
||||
vo.setType(reminder.getType());
|
||||
vo.setTitle(reminder.getTitle());
|
||||
vo.setContent(reminder.getContent());
|
||||
vo.setStoryInstanceId(reminder.getStoryInstanceId());
|
||||
vo.setStoryItemId(reminder.getStoryItemId());
|
||||
vo.setRemindTime(reminder.getRemindTime());
|
||||
vo.setRepeatType(reminder.getRepeatType());
|
||||
vo.setIsSent(reminder.getIsSent() == 1);
|
||||
vo.setSentTime(reminder.getSentTime());
|
||||
vo.setIsEnabled(reminder.getIsEnabled() == 1);
|
||||
vo.setCreateTime(reminder.getCreateTime());
|
||||
|
||||
// 设置类型描述
|
||||
vo.setTypeDesc(getTypeDesc(reminder.getType()));
|
||||
vo.setRepeatTypeDesc(getRepeatTypeDesc(reminder.getRepeatType()));
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型描述
|
||||
*/
|
||||
private String getTypeDesc(String type) {
|
||||
return switch (type) {
|
||||
case "RECORD" -> "记录提醒";
|
||||
case "MEMORY" -> "回忆提醒";
|
||||
case "ANNIVERSARY" -> "纪念日提醒";
|
||||
case "CUSTOM" -> "自定义提醒";
|
||||
default -> "未知类型";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取重复类型描述
|
||||
*/
|
||||
private String getRepeatTypeDesc(String repeatType) {
|
||||
return switch (repeatType) {
|
||||
case "NONE" -> "不重复";
|
||||
case "DAILY" -> "每天";
|
||||
case "WEEKLY" -> "每周";
|
||||
case "MONTHLY" -> "每月";
|
||||
case "YEARLY" -> "每年";
|
||||
default -> "未知";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.timeline.story.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.timeline.common.response.ResponseEntity;
|
||||
import com.timeline.common.response.ResponseEnum;
|
||||
import com.timeline.story.entity.StoryItem;
|
||||
import com.timeline.story.service.StoryItemService;
|
||||
import com.timeline.story.service.StoryService;
|
||||
@@ -98,4 +99,74 @@ public class StoryItemController {
|
||||
Map result = storyItemService.searchItems(keyword, pageNum, pageSize);
|
||||
return ResponseEntity.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新时间线节点排序
|
||||
*
|
||||
* 功能描述:
|
||||
* 接收前端拖拽排序后的节点顺序,批量更新各节点的 sortOrder 字段。
|
||||
* 用于保存用户手动调整的时间线节点顺序。
|
||||
*
|
||||
* @param request 包含 items 数组,每个元素有 instanceId 和 sortOrder
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/order")
|
||||
public ResponseEntity<String> updateItemsOrder(@RequestBody Map<String, List<Map<String, Object>>> request) {
|
||||
List<Map<String, Object>> items = request.get("items");
|
||||
if (items == null || items.isEmpty()) {
|
||||
return ResponseEntity.error("排序数据不能为空");
|
||||
}
|
||||
log.info("批量更新 StoryItem 排序,共 {} 项", items.size());
|
||||
storyItemService.updateItemsOrder(items);
|
||||
return ResponseEntity.success("排序更新成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除时间线节点
|
||||
*
|
||||
* 功能描述:
|
||||
* 根据节点ID列表批量软删除时间线节点。
|
||||
* 软删除仅标记 is_delete 字段,数据可恢复。
|
||||
*
|
||||
* @param request 包含 instanceIds 数组
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/batch-delete")
|
||||
public ResponseEntity<String> batchDeleteItems(@RequestBody Map<String, List<String>> request) {
|
||||
List<String> instanceIds = request.get("instanceIds");
|
||||
if (instanceIds == null || instanceIds.isEmpty()) {
|
||||
return ResponseEntity.error(ResponseEnum.BAD_REQUEST, "删除列表不能为空");
|
||||
}
|
||||
log.info("批量删除 StoryItem,共 {} 项", instanceIds.size());
|
||||
storyItemService.batchDeleteItems(instanceIds);
|
||||
return ResponseEntity.success("批量删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改时间线节点时间
|
||||
*
|
||||
* 功能描述:
|
||||
* 批量修改多个节点的 storyItemTime 字段。
|
||||
* 用于批量调整节点的时间信息。
|
||||
*
|
||||
* @param request 包含 instanceIds 数组和 storyItemTime 字符串
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/batch-time")
|
||||
public ResponseEntity<String> batchUpdateItemTime(@RequestBody Map<String, Object> request) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> instanceIds = (List<String>) request.get("instanceIds");
|
||||
String storyItemTime = (String) request.get("storyItemTime");
|
||||
|
||||
if (instanceIds == null || instanceIds.isEmpty()) {
|
||||
return ResponseEntity.error(ResponseEnum.BAD_REQUEST, "修改列表不能为空");
|
||||
}
|
||||
if (storyItemTime == null || storyItemTime.isEmpty()) {
|
||||
return ResponseEntity.error(ResponseEnum.BAD_REQUEST, "时间不能为空");
|
||||
}
|
||||
|
||||
log.info("批量修改 StoryItem 时间,共 {} 项,新时间: {}", instanceIds.size(), storyItemTime);
|
||||
storyItemService.batchUpdateItemTime(instanceIds, storyItemTime);
|
||||
return ResponseEntity.success("批量修改时间成功");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.util.List;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/story/permission")
|
||||
@Slf4j
|
||||
@@ -34,6 +33,7 @@ public class StoryPermissionController {
|
||||
storyPermissionService.updatePermission(permissionVo);
|
||||
return ResponseEntity.success("权限更新成功");
|
||||
}
|
||||
|
||||
@PostMapping("/authorize")
|
||||
public ResponseEntity<String> authorizePermission(@RequestBody StoryPermissionVo permissionVo) {
|
||||
log.info("授权权限: {}", permissionVo);
|
||||
@@ -76,7 +76,29 @@ public class StoryPermissionController {
|
||||
@RequestParam Integer requiredPermissionType) {
|
||||
log.info("检查用户权限: storyInstanceId={}, userId={}, requiredPermissionType={}",
|
||||
storyInstanceId, userId, requiredPermissionType);
|
||||
boolean hasPermission = storyPermissionService.checkUserPermission(storyInstanceId, userId, requiredPermissionType);
|
||||
boolean hasPermission = storyPermissionService.checkUserPermission(storyInstanceId, userId,
|
||||
requiredPermissionType);
|
||||
return ResponseEntity.success(hasPermission);
|
||||
}
|
||||
|
||||
@PostMapping("/invite")
|
||||
public ResponseEntity<String> inviteUser(@RequestBody StoryPermissionVo permissionVo) {
|
||||
log.info("邀请用户协作: {}", permissionVo);
|
||||
storyPermissionService.inviteUser(permissionVo);
|
||||
return ResponseEntity.success("邀请发送成功");
|
||||
}
|
||||
|
||||
@PutMapping("/invite/{inviteId}/accept")
|
||||
public ResponseEntity<String> acceptInvite(@PathVariable String inviteId) {
|
||||
log.info("接受邀请: {}", inviteId);
|
||||
storyPermissionService.acceptInvite(inviteId);
|
||||
return ResponseEntity.success("邀请已接受");
|
||||
}
|
||||
|
||||
@PutMapping("/invite/{inviteId}/reject")
|
||||
public ResponseEntity<String> rejectInvite(@PathVariable String inviteId) {
|
||||
log.info("拒绝邀请: {}", inviteId);
|
||||
storyPermissionService.rejectInvite(inviteId);
|
||||
return ResponseEntity.success("邀请已拒绝");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,4 +33,18 @@ public interface StoryItemMapper {
|
||||
// StoryItemShareVo selectByShareIdWithAuthor(String shareId);
|
||||
|
||||
List<StoryItemVo> searchItems(@Param("keyword") String keyword);
|
||||
|
||||
/**
|
||||
* 更新节点排序值
|
||||
*
|
||||
* @param params 包含 instanceId, sortOrder, updateId, updateTime
|
||||
*/
|
||||
void updateOrder(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 更新节点时间
|
||||
*
|
||||
* @param params 包含 instanceId, storyItemTime, updateId, updateTime
|
||||
*/
|
||||
void updateItemTime(Map<String, Object> params);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,20 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface StoryPermissionMapper {
|
||||
void insert(StoryPermission permission);
|
||||
|
||||
void update(StoryPermission permission);
|
||||
|
||||
void updateInviteStatus(String permissionId, Integer inviteStatus);
|
||||
|
||||
void deleteByPermissionId(String permissionId);
|
||||
|
||||
StoryPermission selectByPermissionId(String permissionId);
|
||||
|
||||
List<StoryPermission> selectByStoryInstanceId(String storyInstanceId);
|
||||
|
||||
List<StoryPermission> selectByUserId(String userId);
|
||||
|
||||
StoryPermission selectByStoryAndUser(String storyInstanceId, String userId);
|
||||
|
||||
List<StoryPermission> selectByStoryAndPermissionType(String storyInstanceId, Integer permissionType);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Notification - 消息通知实体类
|
||||
*
|
||||
* 功能描述:
|
||||
* 定义系统消息通知的数据结构。
|
||||
* 支持多种消息类型:评论、点赞、@提及、邀请、系统通知。
|
||||
*
|
||||
* 消息类型说明:
|
||||
* - COMMENT: 评论通知
|
||||
* - LIKE: 点赞通知
|
||||
* - MENTION: @提及通知
|
||||
* - INVITE: 协作邀请通知
|
||||
* - SYSTEM: 系统通知
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class Notification {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 消息唯一标识
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 接收用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
* COMMENT/LIKE/MENTION/INVITE/SYSTEM
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 消息标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 关联ID(评论ID/节点ID等)
|
||||
*/
|
||||
private String relatedId;
|
||||
|
||||
/**
|
||||
* 关联类型
|
||||
* STORY_ITEM/COMMENT/STORY
|
||||
*/
|
||||
private String relatedType;
|
||||
|
||||
/**
|
||||
* 发送者ID
|
||||
*/
|
||||
private String senderId;
|
||||
|
||||
/**
|
||||
* 发送者名称
|
||||
*/
|
||||
private String senderName;
|
||||
|
||||
/**
|
||||
* 发送者头像
|
||||
*/
|
||||
private String senderAvatar;
|
||||
|
||||
/**
|
||||
* 是否已读
|
||||
*/
|
||||
private Integer isRead;
|
||||
|
||||
/**
|
||||
* 阅读时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime readTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reminder - 提醒实体类
|
||||
*
|
||||
* 功能描述:
|
||||
* 定义用户提醒的数据结构,支持多种提醒类型。
|
||||
*
|
||||
* 提醒类型:
|
||||
* - RECORD: 记录提醒(每日记录提醒)
|
||||
* - MEMORY: 回忆提醒(去年的今天)
|
||||
* - ANNIVERSARY: 纪念日提醒
|
||||
* - CUSTOM: 自定义提醒
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class Reminder {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 提醒唯一标识
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 提醒类型
|
||||
* RECORD/MEMORY/ANNIVERSARY/CUSTOM
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 提醒标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 提醒内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 关联的故事ID(可选)
|
||||
*/
|
||||
private String storyInstanceId;
|
||||
|
||||
/**
|
||||
* 关联的节点ID(可选)
|
||||
*/
|
||||
private String storyItemId;
|
||||
|
||||
/**
|
||||
* 提醒时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime remindTime;
|
||||
|
||||
/**
|
||||
* 重复类型
|
||||
* NONE/DAILY/WEEKLY/MONTHLY/YEARLY
|
||||
*/
|
||||
private String repeatType;
|
||||
|
||||
/**
|
||||
* 是否已发送
|
||||
*/
|
||||
private Integer isSent;
|
||||
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime sentTime;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Integer isEnabled;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* StoryComment - 评论实体类
|
||||
*
|
||||
* 功能描述:
|
||||
* 定义时间线节点评论的数据结构。
|
||||
* 支持多级评论(回复功能)。
|
||||
*
|
||||
* 字段说明:
|
||||
* - instanceId: 评论唯一标识
|
||||
* - storyItemId: 关联的时间线节点ID
|
||||
* - userId: 评论用户ID
|
||||
* - parentId: 父评论ID(用于回复)
|
||||
* - content: 评论内容
|
||||
* - likeCount: 点赞数
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class StoryComment {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 评论唯一标识
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 时间线节点ID
|
||||
*/
|
||||
private String storyItemId;
|
||||
|
||||
/**
|
||||
* 评论用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 父评论ID(用于回复功能)
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 回复的用户ID
|
||||
*/
|
||||
private String replyToUserId;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
private Integer isDelete;
|
||||
}
|
||||
@@ -22,4 +22,9 @@ public class StoryItem {
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
private Integer isDelete;
|
||||
/**
|
||||
* 排序值 - 用于拖拽排序
|
||||
* 数值越小越靠前,默认为0
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* StoryItemTag - 时间线节点标签关联实体
|
||||
*
|
||||
* 功能描述:
|
||||
* 定义时间线节点与标签的多对多关联关系。
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class StoryItemTag {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 时间线节点ID
|
||||
*/
|
||||
private String storyItemId;
|
||||
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
private String tagId;
|
||||
|
||||
/**
|
||||
* 创建者ID
|
||||
*/
|
||||
private String createId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public class StoryPermission {
|
||||
private String storyInstanceId;
|
||||
private String userId;
|
||||
private Integer permissionType; // 1-创建者,2-仅查看,3-可新增,4-可管理
|
||||
private Integer inviteStatus; // 0-待处理, 1-已接受, 2-已拒绝
|
||||
private Integer isDeleted;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Tag - 标签实体类
|
||||
*
|
||||
* 功能描述:
|
||||
* 定义标签的数据结构,用于时间线节点的分类和检索。
|
||||
*
|
||||
* 字段说明:
|
||||
* - instanceId: 标签唯一标识
|
||||
* - name: 标签名称
|
||||
* - color: 标签颜色(十六进制)
|
||||
* - ownerId: 创建者ID
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class Tag {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标签唯一标识
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 标签名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 标签颜色(十六进制,如 #1890ff)
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 创建者ID
|
||||
*/
|
||||
private String ownerId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
private Integer isDelete;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.timeline.story.service;
|
||||
|
||||
import com.timeline.story.vo.TimelineAnalyticsVo;
|
||||
|
||||
/**
|
||||
* AnalyticsService - 数据分析服务接口
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供时间线数据的统计和分析功能。
|
||||
*
|
||||
* 功能列表:
|
||||
* - 总体统计
|
||||
* - 时间分布分析
|
||||
* - 地点分布分析
|
||||
* - 标签分布分析
|
||||
* - 年度报告生成
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
public interface AnalyticsService {
|
||||
|
||||
/**
|
||||
* 获取用户时间线总体统计
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 统计数据
|
||||
*/
|
||||
TimelineAnalyticsVo getOverallStats(String userId);
|
||||
|
||||
/**
|
||||
* 获取故事统计数据
|
||||
*
|
||||
* @param storyInstanceId 故事ID
|
||||
* @return 统计数据
|
||||
*/
|
||||
TimelineAnalyticsVo getStoryStats(String storyInstanceId);
|
||||
|
||||
/**
|
||||
* 获取月度趋势数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param year 年份
|
||||
* @return 月度趋势
|
||||
*/
|
||||
TimelineAnalyticsVo getMonthlyTrend(String userId, int year);
|
||||
|
||||
/**
|
||||
* 获取热门地点
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param limit 返回数量
|
||||
* @return 热门地点列表
|
||||
*/
|
||||
TimelineAnalyticsVo getTopLocations(String userId, int limit);
|
||||
|
||||
/**
|
||||
* 获取热门标签
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param limit 返回数量
|
||||
* @return 热门标签列表
|
||||
*/
|
||||
TimelineAnalyticsVo getTopTags(String userId, int limit);
|
||||
|
||||
/**
|
||||
* 生成年度报告
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param year 年份
|
||||
* @return 年度报告数据
|
||||
*/
|
||||
TimelineAnalyticsVo.YearlyReport generateYearlyReport(String userId, int year);
|
||||
|
||||
/**
|
||||
* 获取活跃度统计
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 活跃度数据
|
||||
*/
|
||||
TimelineAnalyticsVo getActivityStats(String userId);
|
||||
|
||||
/**
|
||||
* 计算连续记录天数
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 连续天数
|
||||
*/
|
||||
int calculateConsecutiveDays(String userId);
|
||||
|
||||
/**
|
||||
* 获取记录时间分布
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 时间分布数据
|
||||
*/
|
||||
TimelineAnalyticsVo getTimeDistribution(String userId);
|
||||
|
||||
/**
|
||||
* 导出统计数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param format 导出格式 (json/csv)
|
||||
* @return 导出数据
|
||||
*/
|
||||
byte[] exportStats(String userId, String format);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.timeline.story.service;
|
||||
|
||||
import com.timeline.story.entity.StoryComment;
|
||||
import com.timeline.story.vo.CommentVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CommentService - 评论服务接口
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供评论的 CRUD 操作和互动功能。
|
||||
*
|
||||
* 功能列表:
|
||||
* - 发表评论
|
||||
* - 回复评论
|
||||
* - 删除评论
|
||||
* - 点赞评论
|
||||
* - 获取评论列表
|
||||
* - 获取回复列表
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
public interface CommentService {
|
||||
|
||||
/**
|
||||
* 发表评论
|
||||
*
|
||||
* @param commentVo 评论信息
|
||||
* @return 创建的评论
|
||||
*/
|
||||
StoryComment createComment(CommentVo commentVo);
|
||||
|
||||
/**
|
||||
* 回复评论
|
||||
*
|
||||
* @param parentId 父评论ID
|
||||
* @param commentVo 回复内容
|
||||
* @return 创建的回复
|
||||
*/
|
||||
StoryComment replyComment(String parentId, CommentVo commentVo);
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
* 软删除,保留数据
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
*/
|
||||
void deleteComment(String commentInstanceId);
|
||||
|
||||
/**
|
||||
* 获取评论详情
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @return 评论信息
|
||||
*/
|
||||
StoryComment getCommentById(String commentInstanceId);
|
||||
|
||||
/**
|
||||
* 获取节点的评论列表
|
||||
* 分页获取,按时间倒序
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 评论列表
|
||||
*/
|
||||
List<CommentVo> getCommentsByStoryItem(String storyItemId, int pageNum, int pageSize);
|
||||
|
||||
/**
|
||||
* 获取评论的回复列表
|
||||
*
|
||||
* @param parentId 父评论ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 回复列表
|
||||
*/
|
||||
List<CommentVo> getRepliesByComment(String parentId, int pageNum, int pageSize);
|
||||
|
||||
/**
|
||||
* 获取节点的评论数量
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @return 评论数量
|
||||
*/
|
||||
int getCommentCountByStoryItem(String storyItemId);
|
||||
|
||||
/**
|
||||
* 点赞评论
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void likeComment(String commentInstanceId, String userId);
|
||||
|
||||
/**
|
||||
* 取消点赞评论
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void unlikeComment(String commentInstanceId, String userId);
|
||||
|
||||
/**
|
||||
* 检查用户是否已点赞
|
||||
*
|
||||
* @param commentInstanceId 评论ID
|
||||
* @param userId 用户ID
|
||||
* @return 是否已点赞
|
||||
*/
|
||||
boolean hasLiked(String commentInstanceId, String userId);
|
||||
|
||||
/**
|
||||
* 获取用户的评论列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 评论列表
|
||||
*/
|
||||
List<CommentVo> getCommentsByUser(String userId, int pageNum, int pageSize);
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.timeline.story.service;
|
||||
|
||||
import com.timeline.story.entity.Notification;
|
||||
import com.timeline.story.vo.NotificationVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* NotificationService - 消息通知服务接口
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供消息通知的管理和推送功能。
|
||||
*
|
||||
* 功能列表:
|
||||
* - 创建通知
|
||||
* - 获取用户通知列表
|
||||
* - 标记已读
|
||||
* - 批量标记已读
|
||||
* - 删除通知
|
||||
* - 获取未读数量
|
||||
* - 实时推送通知
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
public interface NotificationService {
|
||||
|
||||
/**
|
||||
* 创建评论通知
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @param commentId 评论ID
|
||||
* @param senderId 评论者ID
|
||||
*/
|
||||
void createCommentNotification(String storyItemId, String commentId, String senderId);
|
||||
|
||||
/**
|
||||
* 创建点赞通知
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @param senderId 点赞者ID
|
||||
*/
|
||||
void createLikeNotification(String storyItemId, String senderId);
|
||||
|
||||
/**
|
||||
* 创建@提及通知
|
||||
*
|
||||
* @param toUserId 被提及的用户ID
|
||||
* @param relatedType 关联类型
|
||||
* @param relatedId 关联ID
|
||||
* @param senderId 提及者ID
|
||||
*/
|
||||
void createMentionNotification(String toUserId, String relatedType, String relatedId, String senderId);
|
||||
|
||||
/**
|
||||
* 创建协作邀请通知
|
||||
*
|
||||
* @param storyId 故事ID
|
||||
* @param toUserId 被邀请者ID
|
||||
* @param senderId 邀请者ID
|
||||
*/
|
||||
void createInviteNotification(String storyId, String toUserId, String senderId);
|
||||
|
||||
/**
|
||||
* 创建系统通知
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param title 标题
|
||||
* @param content 内容
|
||||
*/
|
||||
void createSystemNotification(String userId, String title, String content);
|
||||
|
||||
/**
|
||||
* 获取用户的通知列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param type 消息类型(可选)
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 通知列表
|
||||
*/
|
||||
List<NotificationVo> getNotifications(String userId, String type, int pageNum, int pageSize);
|
||||
|
||||
/**
|
||||
* 获取未读通知列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 未读通知列表
|
||||
*/
|
||||
List<NotificationVo> getUnreadNotifications(String userId, int pageNum, int pageSize);
|
||||
|
||||
/**
|
||||
* 获取未读通知数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 未读数量
|
||||
*/
|
||||
int getUnreadCount(String userId);
|
||||
|
||||
/**
|
||||
* 按类型获取未读数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 各类型的未读数量
|
||||
*/
|
||||
java.util.Map<String, Integer> getUnreadCountByType(String userId);
|
||||
|
||||
/**
|
||||
* 标记单条通知为已读
|
||||
*
|
||||
* @param notificationInstanceId 通知ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void markAsRead(String notificationInstanceId, String userId);
|
||||
|
||||
/**
|
||||
* 标记所有通知为已读
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void markAllAsRead(String userId);
|
||||
|
||||
/**
|
||||
* 按类型标记所有通知为已读
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param type 消息类型
|
||||
*/
|
||||
void markAllAsReadByType(String userId, String type);
|
||||
|
||||
/**
|
||||
* 删除通知
|
||||
*
|
||||
* @param notificationInstanceId 通知ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void deleteNotification(String notificationInstanceId, String userId);
|
||||
|
||||
/**
|
||||
* 清空所有通知
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void clearAllNotifications(String userId);
|
||||
|
||||
/**
|
||||
* 推送实时通知
|
||||
* 通过 WebSocket 推送给在线用户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param notification 通知内容
|
||||
*/
|
||||
void pushNotification(String userId, Notification notification);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.timeline.story.service;
|
||||
|
||||
import com.timeline.story.entity.Reminder;
|
||||
import com.timeline.story.vo.ReminderVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ReminderService - 提醒服务接口
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供提醒的管理和推送功能。
|
||||
*
|
||||
* 功能列表:
|
||||
* - 创建提醒
|
||||
* - 更新提醒
|
||||
* - 删除提醒
|
||||
* - 获取提醒列表
|
||||
* - 发送提醒
|
||||
* - 生成回忆提醒
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
public interface ReminderService {
|
||||
|
||||
/**
|
||||
* 创建提醒
|
||||
*
|
||||
* @param reminderVo 提醒信息
|
||||
* @return 创建的提醒
|
||||
*/
|
||||
Reminder createReminder(ReminderVo reminderVo);
|
||||
|
||||
/**
|
||||
* 更新提醒
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
* @param reminderVo 提醒信息
|
||||
* @return 更新后的提醒
|
||||
*/
|
||||
Reminder updateReminder(String reminderInstanceId, ReminderVo reminderVo);
|
||||
|
||||
/**
|
||||
* 删除提醒
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
*/
|
||||
void deleteReminder(String reminderInstanceId);
|
||||
|
||||
/**
|
||||
* 获取提醒详情
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
* @return 提醒信息
|
||||
*/
|
||||
Reminder getReminderById(String reminderInstanceId);
|
||||
|
||||
/**
|
||||
* 获取用户的提醒列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param type 提醒类型(可选)
|
||||
* @return 提醒列表
|
||||
*/
|
||||
List<Reminder> getRemindersByUser(String userId, String type);
|
||||
|
||||
/**
|
||||
* 获取待发送的提醒列表
|
||||
*
|
||||
* @return 待发送的提醒列表
|
||||
*/
|
||||
List<Reminder> getPendingReminders();
|
||||
|
||||
/**
|
||||
* 发送提醒
|
||||
*
|
||||
* @param reminder 提醒信息
|
||||
*/
|
||||
void sendReminder(Reminder reminder);
|
||||
|
||||
/**
|
||||
* 批量发送提醒
|
||||
*
|
||||
* @param reminders 提醒列表
|
||||
*/
|
||||
void sendReminders(List<Reminder> reminders);
|
||||
|
||||
/**
|
||||
* 生成回忆提醒
|
||||
*
|
||||
* 功能描述:
|
||||
* 检查用户是否有"去年的今天"的记录,
|
||||
* 如果有则生成回忆提醒。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void generateMemoryReminders(String userId);
|
||||
|
||||
/**
|
||||
* 启用/禁用提醒
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void toggleReminder(String reminderInstanceId, boolean enabled);
|
||||
|
||||
/**
|
||||
* 标记提醒为已发送
|
||||
*
|
||||
* @param reminderInstanceId 提醒ID
|
||||
*/
|
||||
void markAsSent(String reminderInstanceId);
|
||||
|
||||
/**
|
||||
* 处理重复提醒
|
||||
*
|
||||
* 功能描述:
|
||||
* 对于重复类型的提醒,发送后自动创建下一次提醒。
|
||||
*
|
||||
* @param reminder 原提醒
|
||||
*/
|
||||
void handleRepeatReminder(Reminder reminder);
|
||||
}
|
||||
@@ -27,4 +27,37 @@ public interface StoryItemService {
|
||||
// StoryItemShareVo getItemByShareId(String shareId);
|
||||
|
||||
Map<String, Object> searchItems(String keyword, Integer pageNum, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 批量更新时间线节点排序
|
||||
*
|
||||
* 功能描述:
|
||||
* 根据前端传递的排序数据,批量更新各节点的 sortOrder 字段。
|
||||
* 该方法支持事务处理,确保所有排序更新原子性完成。
|
||||
*
|
||||
* @param items 排序数据列表,每个元素包含 instanceId 和 sortOrder
|
||||
*/
|
||||
void updateItemsOrder(List<Map<String, Object>> items);
|
||||
|
||||
/**
|
||||
* 批量删除时间线节点
|
||||
*
|
||||
* 功能描述:
|
||||
* 根据节点ID列表批量软删除时间线节点。
|
||||
* 软删除仅标记 is_delete 字段,数据可恢复。
|
||||
*
|
||||
* @param instanceIds 要删除的节点ID列表
|
||||
*/
|
||||
void batchDeleteItems(List<String> instanceIds);
|
||||
|
||||
/**
|
||||
* 批量修改时间线节点时间
|
||||
*
|
||||
* 功能描述:
|
||||
* 批量修改多个节点的 storyItemTime 字段。
|
||||
*
|
||||
* @param instanceIds 要修改的节点ID列表
|
||||
* @param storyItemTime 新的时间值
|
||||
*/
|
||||
void batchUpdateItemTime(List<String> instanceIds, String storyItemTime);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,26 @@ import java.util.List;
|
||||
|
||||
public interface StoryPermissionService {
|
||||
void createPermission(StoryPermissionVo permissionVo);
|
||||
|
||||
void updatePermission(StoryPermissionVo permissionVo);
|
||||
|
||||
void deletePermission(String permissionId);
|
||||
|
||||
StoryPermission getPermissionById(String permissionId);
|
||||
|
||||
List<StoryPermission> getPermissionsByStoryId(String storyInstanceId);
|
||||
|
||||
List<StoryPermission> getPermissionsByUserId(String userId);
|
||||
|
||||
StoryPermission getPermissionByStoryAndUser(String storyInstanceId, String userId);
|
||||
|
||||
boolean checkUserPermission(String storyInstanceId, String userId, Integer requiredPermissionType);
|
||||
|
||||
List<StoryPermission> getPermissionsByStoryAndType(String storyInstanceId, Integer permissionType);
|
||||
|
||||
void inviteUser(StoryPermissionVo permissionVo);
|
||||
|
||||
void acceptInvite(String permissionId);
|
||||
|
||||
void rejectInvite(String permissionId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.timeline.story.service;
|
||||
|
||||
import com.timeline.story.entity.Tag;
|
||||
import com.timeline.story.vo.TagVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TagService - 标签服务接口
|
||||
*
|
||||
* 功能描述:
|
||||
* 提供标签的 CRUD 操作和节点标签关联管理。
|
||||
*
|
||||
* 功能列表:
|
||||
* - 创建标签
|
||||
* - 更新标签
|
||||
* - 删除标签
|
||||
* - 获取用户标签列表
|
||||
* - 为节点添加标签
|
||||
* - 移除节点标签
|
||||
* - 获取节点的标签列表
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
public interface TagService {
|
||||
|
||||
/**
|
||||
* 创建标签
|
||||
*
|
||||
* @param tagVo 标签信息
|
||||
* @return 创建的标签
|
||||
*/
|
||||
Tag createTag(TagVo tagVo);
|
||||
|
||||
/**
|
||||
* 更新标签
|
||||
*
|
||||
* @param tagInstanceId 标签ID
|
||||
* @param tagVo 标签信息
|
||||
* @return 更新后的标签
|
||||
*/
|
||||
Tag updateTag(String tagInstanceId, TagVo tagVo);
|
||||
|
||||
/**
|
||||
* 删除标签
|
||||
* 同时删除所有关联关系
|
||||
*
|
||||
* @param tagInstanceId 标签ID
|
||||
*/
|
||||
void deleteTag(String tagInstanceId);
|
||||
|
||||
/**
|
||||
* 获取标签详情
|
||||
*
|
||||
* @param tagInstanceId 标签ID
|
||||
* @return 标签信息
|
||||
*/
|
||||
Tag getTagById(String tagInstanceId);
|
||||
|
||||
/**
|
||||
* 获取用户的所有标签
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 标签列表
|
||||
*/
|
||||
List<Tag> getTagsByUserId(String userId);
|
||||
|
||||
/**
|
||||
* 搜索标签
|
||||
* 根据名称模糊搜索
|
||||
*
|
||||
* @param keyword 关键词
|
||||
* @param userId 用户ID
|
||||
* @return 匹配的标签列表
|
||||
*/
|
||||
List<Tag> searchTags(String keyword, String userId);
|
||||
|
||||
/**
|
||||
* 为节点添加标签
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @param tagInstanceId 标签ID
|
||||
*/
|
||||
void addTagToStoryItem(String storyItemId, String tagInstanceId);
|
||||
|
||||
/**
|
||||
* 为节点批量添加标签
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @param tagInstanceIds 标签ID列表
|
||||
*/
|
||||
void addTagsToStoryItem(String storyItemId, List<String> tagInstanceIds);
|
||||
|
||||
/**
|
||||
* 移除节点标签
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @param tagInstanceId 标签ID
|
||||
*/
|
||||
void removeTagFromStoryItem(String storyItemId, String tagInstanceId);
|
||||
|
||||
/**
|
||||
* 移除节点的所有标签
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
*/
|
||||
void removeAllTagsFromStoryItem(String storyItemId);
|
||||
|
||||
/**
|
||||
* 获取节点的标签列表
|
||||
*
|
||||
* @param storyItemId 节点ID
|
||||
* @return 标签列表
|
||||
*/
|
||||
List<Tag> getTagsByStoryItemId(String storyItemId);
|
||||
|
||||
/**
|
||||
* 获取标签下的节点数量
|
||||
*
|
||||
* @param tagInstanceId 标签ID
|
||||
* @return 节点数量
|
||||
*/
|
||||
int getStoryItemCountByTag(String tagInstanceId);
|
||||
|
||||
/**
|
||||
* 获取热门标签
|
||||
* 按使用次数排序
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param limit 返回数量
|
||||
* @return 热门标签列表
|
||||
*/
|
||||
List<Tag> getHotTags(String userId, int limit);
|
||||
}
|
||||
@@ -112,4 +112,100 @@ public class StoryItemServiceImpl implements StoryItemService {
|
||||
result.put("total", pageInfo.getTotal());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新时间线节点排序
|
||||
*
|
||||
* 实现思路:
|
||||
* 1. 使用事务确保批量更新的原子性
|
||||
* 2. 遍历排序数据,逐个更新节点的 sortOrder 字段
|
||||
* 3. 同时更新 updateTime 时间戳
|
||||
*
|
||||
* 注意事项:
|
||||
* - 该方法需要在事务中执行,确保数据一致性
|
||||
* - 如果任一更新失败,整个事务将回滚
|
||||
*
|
||||
* @param items 排序数据列表
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateItemsOrder(List<Map<String, Object>> items) {
|
||||
String currentUserId = UserContextUtils.getCurrentUserId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
for (Map<String, Object> item : items) {
|
||||
String instanceId = (String) item.get("instanceId");
|
||||
Integer sortOrder = ((Number) item.get("sortOrder")).intValue();
|
||||
|
||||
// 构建更新参数
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("instanceId", instanceId);
|
||||
params.put("sortOrder", sortOrder);
|
||||
params.put("updateId", currentUserId);
|
||||
params.put("updateTime", now);
|
||||
|
||||
storyItemMapper.updateOrder(params);
|
||||
log.debug("更新节点排序: instanceId={}, sortOrder={}", instanceId, sortOrder);
|
||||
}
|
||||
|
||||
log.info("批量更新排序完成,共更新 {} 个节点", items.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除时间线节点
|
||||
*
|
||||
* 实现思路:
|
||||
* 1. 使用事务确保批量删除的原子性
|
||||
* 2. 遍历节点ID列表,逐个执行软删除
|
||||
* 3. 软删除仅标记 is_delete = 1,数据可恢复
|
||||
*
|
||||
* 注意事项:
|
||||
* - 该方法需要在事务中执行
|
||||
* - 如果任一删除失败,整个事务将回滚
|
||||
*
|
||||
* @param instanceIds 要删除的节点ID列表
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void batchDeleteItems(List<String> instanceIds) {
|
||||
for (String instanceId : instanceIds) {
|
||||
storyItemMapper.deleteByItemId(instanceId);
|
||||
log.debug("软删除节点: instanceId={}", instanceId);
|
||||
}
|
||||
log.info("批量删除完成,共删除 {} 个节点", instanceIds.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改时间线节点时间
|
||||
*
|
||||
* 实现思路:
|
||||
* 1. 使用事务确保批量修改的原子性
|
||||
* 2. 遍历节点ID列表,逐个更新时间字段
|
||||
* 3. 同时更新 updateTime 时间戳
|
||||
*
|
||||
* @param instanceIds 要修改的节点ID列表
|
||||
* @param storyItemTime 新的时间值
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void batchUpdateItemTime(List<String> instanceIds, String storyItemTime) {
|
||||
String currentUserId = UserContextUtils.getCurrentUserId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 解析时间字符串为 LocalDateTime
|
||||
LocalDateTime parsedTime = LocalDateTime.parse(storyItemTime.replace(" ", "T"));
|
||||
|
||||
for (String instanceId : instanceIds) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("instanceId", instanceId);
|
||||
params.put("storyItemTime", parsedTime);
|
||||
params.put("updateId", currentUserId);
|
||||
params.put("updateTime", now);
|
||||
|
||||
storyItemMapper.updateItemTime(params);
|
||||
log.debug("更新节点时间: instanceId={}, storyItemTime={}", instanceId, storyItemTime);
|
||||
}
|
||||
|
||||
log.info("批量修改时间完成,共修改 {} 个节点", instanceIds.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public class StoryPermissionServiceImpl implements StoryPermissionService {
|
||||
private StoryPermissionMapper storyPermissionMapper;
|
||||
@Autowired
|
||||
private UserServiceClient userServiceClient;
|
||||
|
||||
private String getCurrentUserId() {
|
||||
String uid = UserContextUtils.getCurrentUserId();
|
||||
if (uid == null) {
|
||||
@@ -45,10 +46,13 @@ public class StoryPermissionServiceImpl implements StoryPermissionService {
|
||||
if (currentUserId.equals(permissionVo.getUserId()) && permissionVo.getPermissionType() != 1) {
|
||||
throw new CustomException(ResponseEnum.BAD_REQUEST, "不能授权给自己");
|
||||
}
|
||||
StoryPermission selectByStoryAndUser = storyPermissionMapper.selectByStoryAndUser(permissionVo.getStoryInstanceId(), permissionVo.getUserId());;
|
||||
StoryPermission selectByStoryAndUser = storyPermissionMapper
|
||||
.selectByStoryAndUser(permissionVo.getStoryInstanceId(), permissionVo.getUserId());
|
||||
|
||||
if (selectByStoryAndUser != null) {
|
||||
log.info("用户已有该故事权限,更新当前权限为:{}", permissionVo.getPermissionType());
|
||||
selectByStoryAndUser.setPermissionType(permissionVo.getPermissionType());;
|
||||
selectByStoryAndUser.setPermissionType(permissionVo.getPermissionType());
|
||||
|
||||
selectByStoryAndUser.setUpdateTime(LocalDateTime.now());
|
||||
storyPermissionMapper.update(selectByStoryAndUser);
|
||||
return;
|
||||
@@ -61,19 +65,20 @@ public class StoryPermissionServiceImpl implements StoryPermissionService {
|
||||
} else if (response.getData() == null) {
|
||||
throw new CustomException(ResponseEnum.BAD_REQUEST, "用户不存在");
|
||||
}
|
||||
log.info("新建故事{} 授权 {} 给 {}", permissionVo.getStoryInstanceId(), permissionVo.getPermissionType(), permissionVo.getUserId());
|
||||
log.info("新建故事{} 授权 {} 给 {}", permissionVo.getStoryInstanceId(), permissionVo.getPermissionType(),
|
||||
permissionVo.getUserId());
|
||||
StoryPermission permission = new StoryPermission();
|
||||
BeanUtils.copyProperties(permissionVo, permission);
|
||||
permission.setPermissionId(IdUtils.randomUuidUpper());
|
||||
permission.setCreateTime(LocalDateTime.now());
|
||||
permission.setUpdateTime(LocalDateTime.now());
|
||||
permission.setIsDeleted(CommonConstants.NOT_DELETED);
|
||||
permission.setInviteStatus(1); // Direct creation is accepted
|
||||
storyPermissionMapper.insert(permission);
|
||||
|
||||
} catch(CustomException e) {
|
||||
} catch (CustomException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
log.error("创建权限失败", e);
|
||||
throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "创建权限失败");
|
||||
}
|
||||
@@ -142,6 +147,11 @@ public class StoryPermissionServiceImpl implements StoryPermissionService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check invite status: Must be ACCEPTED (1)
|
||||
if (permission.getInviteStatus() != null && permission.getInviteStatus() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 权限类型数字越小权限越高
|
||||
return permission.getPermissionType() <= requiredPermissionType;
|
||||
}
|
||||
@@ -150,4 +160,87 @@ public class StoryPermissionServiceImpl implements StoryPermissionService {
|
||||
public List<StoryPermission> getPermissionsByStoryAndType(String storyInstanceId, Integer permissionType) {
|
||||
return storyPermissionMapper.selectByStoryAndPermissionType(storyInstanceId, permissionType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void inviteUser(StoryPermissionVo permissionVo) {
|
||||
try {
|
||||
// Check if user exists (Feign)
|
||||
ResponseEntity<Map> response = userServiceClient.getUserByUserId(permissionVo.getUserId());
|
||||
if (response.getCode() != 200 || response.getData() == null) {
|
||||
throw new CustomException(ResponseEnum.BAD_REQUEST, "用户不存在");
|
||||
}
|
||||
|
||||
StoryPermission existing = storyPermissionMapper.selectByStoryAndUser(permissionVo.getStoryInstanceId(),
|
||||
permissionVo.getUserId());
|
||||
if (existing != null) {
|
||||
if (existing.getInviteStatus() != null && existing.getInviteStatus() == 1) {
|
||||
throw new CustomException(ResponseEnum.BAD_REQUEST, "用户已经是协作者");
|
||||
}
|
||||
// Update existing to PENDING
|
||||
existing.setPermissionType(permissionVo.getPermissionType());
|
||||
existing.setInviteStatus(0);
|
||||
storyPermissionMapper.update(existing);
|
||||
storyPermissionMapper.updateInviteStatus(existing.getPermissionId(), 0);
|
||||
} else {
|
||||
StoryPermission permission = new StoryPermission();
|
||||
BeanUtils.copyProperties(permissionVo, permission);
|
||||
permission.setPermissionId(IdUtils.randomUuidUpper());
|
||||
permission.setCreateTime(LocalDateTime.now());
|
||||
permission.setUpdateTime(LocalDateTime.now());
|
||||
permission.setIsDeleted(CommonConstants.NOT_DELETED);
|
||||
permission.setInviteStatus(0); // PENDING
|
||||
storyPermissionMapper.insert(permission);
|
||||
}
|
||||
// TODO: Send notification
|
||||
} catch (CustomException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("邀请用户失败", e);
|
||||
throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "邀请用户失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void acceptInvite(String permissionId) {
|
||||
try {
|
||||
StoryPermission permission = storyPermissionMapper.selectByPermissionId(permissionId);
|
||||
if (permission == null) {
|
||||
throw new CustomException(ResponseEnum.NOT_FOUND, "邀请不存在");
|
||||
}
|
||||
// Check if current user is the invitee
|
||||
String currentUserId = getCurrentUserId();
|
||||
if (!permission.getUserId().equals(currentUserId)) {
|
||||
throw new CustomException(ResponseEnum.FORBIDDEN, "无权操作此邀请");
|
||||
}
|
||||
storyPermissionMapper.updateInviteStatus(permissionId, 1); // ACCEPTED
|
||||
} catch (CustomException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("接受邀请失败", e);
|
||||
throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "接受邀请失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void rejectInvite(String permissionId) {
|
||||
try {
|
||||
StoryPermission permission = storyPermissionMapper.selectByPermissionId(permissionId);
|
||||
if (permission == null) {
|
||||
throw new CustomException(ResponseEnum.NOT_FOUND, "邀请不存在");
|
||||
}
|
||||
String currentUserId = getCurrentUserId();
|
||||
if (!permission.getUserId().equals(currentUserId)) {
|
||||
throw new CustomException(ResponseEnum.FORBIDDEN, "无权操作此邀请");
|
||||
}
|
||||
storyPermissionMapper.updateInviteStatus(permissionId, 2); // REJECTED
|
||||
} catch (CustomException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("拒绝邀请失败", e);
|
||||
throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "拒绝邀请失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CommentVo - 评论值对象
|
||||
*
|
||||
* 功能描述:
|
||||
* 封装评论信息,包含用户信息和回复列表。
|
||||
* 用于前端展示评论树结构。
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class CommentVo {
|
||||
|
||||
/**
|
||||
* 评论ID
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 时间线节点ID
|
||||
*/
|
||||
private String storyItemId;
|
||||
|
||||
/**
|
||||
* 评论用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 评论用户名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 评论用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 父评论ID
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 回复的用户ID
|
||||
*/
|
||||
private String replyToUserId;
|
||||
|
||||
/**
|
||||
* 回复的用户名
|
||||
*/
|
||||
private String replyToUserName;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 当前用户是否已点赞
|
||||
*/
|
||||
private Boolean hasLiked;
|
||||
|
||||
/**
|
||||
* 回复数量
|
||||
*/
|
||||
private Integer replyCount;
|
||||
|
||||
/**
|
||||
* 回复列表(子评论)
|
||||
*/
|
||||
private List<CommentVo> replies;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* NotificationVo - 消息通知值对象
|
||||
*
|
||||
* 功能描述:
|
||||
* 封装消息通知信息,用于前端展示。
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class NotificationVo {
|
||||
|
||||
/**
|
||||
* 通知ID
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 消息类型描述
|
||||
*/
|
||||
private String typeDesc;
|
||||
|
||||
/**
|
||||
* 消息标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 关联ID
|
||||
*/
|
||||
private String relatedId;
|
||||
|
||||
/**
|
||||
* 关联类型
|
||||
*/
|
||||
private String relatedType;
|
||||
|
||||
/**
|
||||
* 发送者ID
|
||||
*/
|
||||
private String senderId;
|
||||
|
||||
/**
|
||||
* 发送者名称
|
||||
*/
|
||||
private String senderName;
|
||||
|
||||
/**
|
||||
* 发送者头像
|
||||
*/
|
||||
private String senderAvatar;
|
||||
|
||||
/**
|
||||
* 是否已读
|
||||
*/
|
||||
private Boolean isRead;
|
||||
|
||||
/**
|
||||
* 阅读时间
|
||||
*/
|
||||
private LocalDateTime readTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 相对时间描述(如:刚刚、5分钟前)
|
||||
*/
|
||||
private String timeAgo;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* ReminderVo - 提醒值对象
|
||||
*
|
||||
* 功能描述:
|
||||
* 封装提醒信息,用于前端展示和创建。
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class ReminderVo {
|
||||
|
||||
/**
|
||||
* 提醒ID
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 提醒类型
|
||||
* RECORD/MEMORY/ANNIVERSARY/CUSTOM
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 提醒类型描述
|
||||
*/
|
||||
private String typeDesc;
|
||||
|
||||
/**
|
||||
* 提醒标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 提醒内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 关联的故事ID
|
||||
*/
|
||||
private String storyInstanceId;
|
||||
|
||||
/**
|
||||
* 关联的故事标题
|
||||
*/
|
||||
private String storyTitle;
|
||||
|
||||
/**
|
||||
* 关联的节点ID
|
||||
*/
|
||||
private String storyItemId;
|
||||
|
||||
/**
|
||||
* 提醒时间
|
||||
*/
|
||||
private LocalDateTime remindTime;
|
||||
|
||||
/**
|
||||
* 重复类型
|
||||
* NONE/DAILY/WEEKLY/MONTHLY/YEARLY
|
||||
*/
|
||||
private String repeatType;
|
||||
|
||||
/**
|
||||
* 重复类型描述
|
||||
*/
|
||||
private String repeatTypeDesc;
|
||||
|
||||
/**
|
||||
* 是否已发送
|
||||
*/
|
||||
private Boolean isSent;
|
||||
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
private LocalDateTime sentTime;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean isEnabled;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SearchResultVo - 搜索结果值对象
|
||||
*
|
||||
* 功能描述:
|
||||
* 封装搜索结果数据,包含分页信息和高亮内容。
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class SearchResultVo {
|
||||
|
||||
/**
|
||||
* 搜索结果列表
|
||||
*/
|
||||
private List<SearchItemVo> list;
|
||||
|
||||
/**
|
||||
* 总数量
|
||||
*/
|
||||
private long total;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private int pageNum;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
private int pageSize;
|
||||
|
||||
/**
|
||||
* 搜索耗时(毫秒)
|
||||
*/
|
||||
private long took;
|
||||
|
||||
/**
|
||||
* 是否有更多结果
|
||||
*/
|
||||
private boolean hasMore;
|
||||
|
||||
/**
|
||||
* 搜索项值对象
|
||||
*/
|
||||
@Data
|
||||
public static class SearchItemVo {
|
||||
/**
|
||||
* 节点ID
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 所属故事ID
|
||||
*/
|
||||
private String storyInstanceId;
|
||||
|
||||
/**
|
||||
* 故事标题
|
||||
*/
|
||||
private String storyTitle;
|
||||
|
||||
/**
|
||||
* 节点标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 高亮标题(包含 <em> 标签)
|
||||
*/
|
||||
private String highlightTitle;
|
||||
|
||||
/**
|
||||
* 节点描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 高亮描述
|
||||
*/
|
||||
private String highlightDescription;
|
||||
|
||||
/**
|
||||
* 时间
|
||||
*/
|
||||
private LocalDateTime storyItemTime;
|
||||
|
||||
/**
|
||||
* 地点
|
||||
*/
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 标签列表
|
||||
*/
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 创建者名称
|
||||
*/
|
||||
private String createName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 相关性得分
|
||||
*/
|
||||
private float score;
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,5 @@ public class StoryPermissionVo {
|
||||
private String storyInstanceId;
|
||||
private String userId;
|
||||
private Integer permissionType; // 1-创建者,2-仅查看,3-可新增,4-可管理
|
||||
private Integer inviteStatus; // 0-待处理, 1-已接受, 2-已拒绝
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* TagVo - 标签值对象
|
||||
*
|
||||
* 功能描述:
|
||||
* 封装标签信息,用于前端展示和创建。
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class TagVo {
|
||||
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* 标签名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 标签颜色
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 创建者ID
|
||||
*/
|
||||
private String ownerId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 使用次数
|
||||
*/
|
||||
private Integer usageCount;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TimelineAnalyticsVo - 时间线数据分析值对象
|
||||
*
|
||||
* 功能描述:
|
||||
* 封装用户时间线的统计数据,用于数据分析面板展示。
|
||||
*
|
||||
* 数据维度:
|
||||
* - 总体统计:时刻数、媒体数、协作数
|
||||
* - 时间分布:按年/月/周统计
|
||||
* - 地点分布:热门地点
|
||||
* - 标签分布:热门标签
|
||||
* - 活跃度:记录频率
|
||||
*
|
||||
* @author Timeline Team
|
||||
* @date 2024
|
||||
*/
|
||||
@Data
|
||||
public class TimelineAnalyticsVo {
|
||||
|
||||
/**
|
||||
* 总时刻数
|
||||
*/
|
||||
private Long totalMoments;
|
||||
|
||||
/**
|
||||
* 总媒体文件数
|
||||
*/
|
||||
private Long totalMedia;
|
||||
|
||||
/**
|
||||
* 图片数量
|
||||
*/
|
||||
private Long imageCount;
|
||||
|
||||
/**
|
||||
* 视频数量
|
||||
*/
|
||||
private Long videoCount;
|
||||
|
||||
/**
|
||||
* 总故事数
|
||||
*/
|
||||
private Long totalStories;
|
||||
|
||||
/**
|
||||
* 协作故事数
|
||||
*/
|
||||
private Long collaborationCount;
|
||||
|
||||
/**
|
||||
* 总评论数
|
||||
*/
|
||||
private Long totalComments;
|
||||
|
||||
/**
|
||||
* 总点赞数
|
||||
*/
|
||||
private Long totalLikes;
|
||||
|
||||
/**
|
||||
* 总收藏数
|
||||
*/
|
||||
private Long totalFavorites;
|
||||
|
||||
/**
|
||||
* 月度记录趋势
|
||||
* key: 月份 (yyyy-MM)
|
||||
* value: 记录数量
|
||||
*/
|
||||
private List<MonthlyStats> monthlyTrend;
|
||||
|
||||
/**
|
||||
* 周记录分布
|
||||
* key: 星期 (1-7)
|
||||
* value: 记录数量
|
||||
*/
|
||||
private Map<Integer, Long> weeklyDistribution;
|
||||
|
||||
/**
|
||||
* 小时分布
|
||||
* key: 小时 (0-23)
|
||||
* value: 记录数量
|
||||
*/
|
||||
private Map<Integer, Long> hourlyDistribution;
|
||||
|
||||
/**
|
||||
* 热门地点 Top 10
|
||||
*/
|
||||
private List<LocationStats> topLocations;
|
||||
|
||||
/**
|
||||
* 热门标签 Top 10
|
||||
*/
|
||||
private List<TagStats> topTags;
|
||||
|
||||
/**
|
||||
* 最近活跃日期
|
||||
*/
|
||||
private String lastActiveDate;
|
||||
|
||||
/**
|
||||
* 连续记录天数
|
||||
*/
|
||||
private Integer consecutiveDays;
|
||||
|
||||
/**
|
||||
* 最长连续记录天数
|
||||
*/
|
||||
private Integer maxConsecutiveDays;
|
||||
|
||||
/**
|
||||
* 年度报告数据
|
||||
*/
|
||||
private YearlyReport yearlyReport;
|
||||
|
||||
/**
|
||||
* 月度统计
|
||||
*/
|
||||
@Data
|
||||
public static class MonthlyStats {
|
||||
private String month;
|
||||
private Long count;
|
||||
private Long mediaCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 地点统计
|
||||
*/
|
||||
@Data
|
||||
public static class LocationStats {
|
||||
private String location;
|
||||
private Long count;
|
||||
private Double percentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签统计
|
||||
*/
|
||||
@Data
|
||||
public static class TagStats {
|
||||
private String tag;
|
||||
private String color;
|
||||
private Long count;
|
||||
private Double percentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 年度报告
|
||||
*/
|
||||
@Data
|
||||
public static class YearlyReport {
|
||||
private Integer year;
|
||||
private Long totalMoments;
|
||||
private Long totalMedia;
|
||||
private String mostActiveMonth;
|
||||
private String mostActiveDay;
|
||||
private String topLocation;
|
||||
private String topTag;
|
||||
private List<MonthlyStats> monthlyBreakdown;
|
||||
}
|
||||
}
|
||||
@@ -112,4 +112,46 @@
|
||||
ORDER BY
|
||||
si.story_item_time DESC
|
||||
</select>
|
||||
|
||||
<!--
|
||||
更新节点排序值
|
||||
|
||||
功能描述:
|
||||
根据拖拽排序结果更新节点的 sort_order 字段。
|
||||
同时更新 update_id 和 update_time 以记录修改信息。
|
||||
|
||||
参数说明:
|
||||
- instanceId: 节点唯一标识
|
||||
- sortOrder: 新的排序值(数值越小越靠前)
|
||||
- updateId: 操作用户ID
|
||||
- updateTime: 更新时间
|
||||
-->
|
||||
<update id="updateOrder">
|
||||
UPDATE story_item
|
||||
SET sort_order = #{sortOrder},
|
||||
update_id = #{updateId},
|
||||
update_time = #{updateTime}
|
||||
WHERE instance_id = #{instanceId}
|
||||
</update>
|
||||
|
||||
<!--
|
||||
更新节点时间
|
||||
|
||||
功能描述:
|
||||
批量修改节点时间时使用,更新 story_item_time 字段。
|
||||
同时更新 update_id 和 update_time 以记录修改信息。
|
||||
|
||||
参数说明:
|
||||
- instanceId: 节点唯一标识
|
||||
- storyItemTime: 新的时间值
|
||||
- updateId: 操作用户ID
|
||||
- updateTime: 更新时间
|
||||
-->
|
||||
<update id="updateItemTime">
|
||||
UPDATE story_item
|
||||
SET story_item_time = #{storyItemTime},
|
||||
update_id = #{updateId},
|
||||
update_time = #{updateTime}
|
||||
WHERE instance_id = #{instanceId}
|
||||
</update>
|
||||
</mapper>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<mapper namespace="com.timeline.story.dao.StoryPermissionMapper">
|
||||
|
||||
<insert id="insert">
|
||||
INSERT INTO story_permission (permission_id, story_instance_id, user_id, permission_type)
|
||||
VALUES (#{permissionId}, #{storyInstanceId}, #{userId}, #{permissionType})
|
||||
INSERT INTO story_permission (permission_id, story_instance_id, user_id, permission_type, invite_status)
|
||||
VALUES (#{permissionId}, #{storyInstanceId}, #{userId}, #{permissionType}, #{inviteStatus})
|
||||
</insert>
|
||||
<update id="update">
|
||||
UPDATE story_permission
|
||||
@@ -15,6 +15,13 @@
|
||||
WHERE permission_id = #{permissionId} AND is_deleted = 0
|
||||
</update>
|
||||
|
||||
<update id="updateInviteStatus">
|
||||
UPDATE story_permission
|
||||
SET invite_status = #{inviteStatus},
|
||||
update_time = NOW()
|
||||
WHERE permission_id = #{permissionId} AND is_deleted = 0
|
||||
</update>
|
||||
|
||||
<update id="deleteByPermissionId">
|
||||
UPDATE story_permission
|
||||
SET is_deleted = 1
|
||||
|
||||
Reference in New Issue
Block a user