Inbox Message & bugs

This commit is contained in:
2026-05-13 13:58:08 +08:00
parent 4dfd27c785
commit 9e42918d5e
26 changed files with 1262 additions and 140 deletions
@@ -3,9 +3,11 @@ package com.yaoyuan.jiscuss;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableCaching
@EnableAsync
public class JiccussApplication {
public static void main(String[] args) {
@@ -1,15 +1,115 @@
package com.yaoyuan.jiscuss.controller;
import com.yaoyuan.jiscuss.dto.InboxItem;
import com.yaoyuan.jiscuss.entity.Message;
import com.yaoyuan.jiscuss.entity.Notification;
import com.yaoyuan.jiscuss.entity.User;
import com.yaoyuan.jiscuss.entity.UserInfo;
import com.yaoyuan.jiscuss.service.IMessageService;
import com.yaoyuan.jiscuss.service.INotificationService;
import com.yaoyuan.jiscuss.service.IUsersService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.List;
/**
* @author Chuyaoyuan
* 用户消息控制器
* 站内私信与通知控制器
*/
@Controller
public class UserMsgController extends BaseController {
/**
* 首页最新消息
*/
@Autowired
private IMessageService messageService;
@Autowired
private INotificationService notificationService;
@Autowired
private IUsersService usersService;
/** 收件箱:对话列表 */
@GetMapping("/messages")
public String inbox(HttpServletRequest request, ModelMap map) {
UserInfo user = getUserInfo(request);
if (user == null) return "redirect:/login";
List<Message> inboxList = messageService.getInbox(user.getId());
List<InboxItem> items = new ArrayList<>();
for (Message msg : inboxList) {
Integer otherId = msg.getSenderId().equals(user.getId()) ? msg.getReceiverId() : msg.getSenderId();
User other = usersService.findOne(otherId);
String rawName = (other != null) ? other.getUsername() : null;
String otherName = (rawName != null && !rawName.isBlank()) ? rawName : "用户" + otherId;
boolean hasUnread = !Boolean.TRUE.equals(msg.getIsRead()) && msg.getReceiverId().equals(user.getId());
items.add(new InboxItem(otherId, otherName, msg.getContent(), msg.getCreateTime(), hasUnread));
}
map.put("username", user.getUsername());
map.put("currentUserId", user.getId());
map.put("inboxItems", items);
map.put("unreadMsgCount", messageService.countUnread(user.getId()));
map.put("unreadNotifCount", notificationService.countUnread(user.getId()));
return "messages";
}
/** 对话详情页 */
@GetMapping("/messages/{otherId}")
public String conversation(@PathVariable Integer otherId, HttpServletRequest request, ModelMap map) {
UserInfo user = getUserInfo(request);
if (user == null) return "redirect:/login";
messageService.markConversationRead(user.getId(), otherId);
List<Message> messages = messageService.getConversation(user.getId(), otherId);
User other = usersService.findOne(otherId);
map.put("username", user.getUsername());
map.put("currentUserId", user.getId());
map.put("otherId", otherId);
map.put("otherUsername", other != null ? other.getUsername() : "用户" + otherId);
map.put("messages", messages);
map.put("unreadMsgCount", messageService.countUnread(user.getId()));
map.put("unreadNotifCount", notificationService.countUnread(user.getId()));
return "conversation";
}
/** 通知列表页 */
@GetMapping("/notifications")
public String notifications(@RequestParam(defaultValue = "0") int page,
HttpServletRequest request, ModelMap map) {
UserInfo user = getUserInfo(request);
if (user == null) return "redirect:/login";
notificationService.markAllRead(user.getId());
Page<Notification> notifs = notificationService.listByUser(user.getId(), page, 20);
List<String> fromUsernames = new ArrayList<>();
for (Notification n : notifs.getContent()) {
if (n.getFromUserId() != null) {
User from = usersService.findOne(n.getFromUserId());
fromUsernames.add(from != null ? from.getUsername() : "用户" + n.getFromUserId());
} else {
fromUsernames.add("系统");
}
}
map.put("username", user.getUsername());
map.put("notifications", notifs.getContent());
map.put("fromUsernames", fromUsernames);
map.put("totalPages", notifs.getTotalPages());
map.put("currentPage", page);
map.put("unreadMsgCount", messageService.countUnread(user.getId()));
map.put("unreadNotifCount", 0L);
return "notifications";
}
}
@@ -1,68 +1,105 @@
package com.yaoyuan.jiscuss.controller;
import com.yaoyuan.jiscuss.entity.Discussion;
import com.yaoyuan.jiscuss.entity.Post;
import com.yaoyuan.jiscuss.entity.User;
import com.yaoyuan.jiscuss.entity.UserInfo;
import com.yaoyuan.jiscuss.service.IDiscussionsService;
import com.yaoyuan.jiscuss.service.ITagsService;
import com.yaoyuan.jiscuss.repository.DiscussionsRepository;
import com.yaoyuan.jiscuss.repository.LikeCollectRepository;
import com.yaoyuan.jiscuss.repository.PostsRepository;
import com.yaoyuan.jiscuss.service.IMessageService;
import com.yaoyuan.jiscuss.service.INotificationService;
import com.yaoyuan.jiscuss.service.IUsersService;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* @author Chuyaoyuan
* @Title:
* @Package com.yaoyuan.jiscuss.controller
* @Description:
* @date
*/
@Controller
public class UserPageController extends BaseController {
private static Logger logger = LoggerFactory.getLogger(UserPageController.class);
private static final Logger logger = LoggerFactory.getLogger(UserPageController.class);
@Autowired
private IUsersService usersService;
@Autowired
private IDiscussionsService discussionsService;
private DiscussionsRepository discussionsRepository;
@Autowired
private ITagsService tagsService;
private PostsRepository postsRepository;
@Autowired
private LikeCollectRepository likeCollectRepository;
@Autowired
private IMessageService messageService;
@Autowired
private INotificationService notificationService;
/**
* 用户页
*
* @return
* 用户个人主
*/
@RequestMapping("/user")
public String user(@RequestParam(defaultValue = "discussion") String type, HttpServletRequest request, ModelMap map) {
public String user(@RequestParam(defaultValue = "discussion") String type,
HttpServletRequest request, ModelMap map) {
// type 判断
if (type.equals("discussion")) {
map.put("discussion", "active");
UserInfo currentUser = getUserInfo(request);
if (currentUser == null) return "redirect:/login";
User userEntity = usersService.findOne(currentUser.getId());
long liveDiscCount = discussionsRepository.countByStartUserId(currentUser.getId());
long liveReplyCount = postsRepository.countByCreateId(currentUser.getId());
map.put("username", currentUser.getUsername());
map.put("userEntity", userEntity);
map.put("liveDiscCount", liveDiscCount);
map.put("liveReplyCount", liveReplyCount);
map.put("type", type);
map.put("unreadMsgCount", messageService.countUnread(currentUser.getId()));
map.put("unreadNotifCount", notificationService.countUnread(currentUser.getId()));
if ("discussion".equals(type)) {
Page<Discussion> discussions = discussionsRepository.findByStartUserIdOrderByStartTimeDesc(
currentUser.getId(), PageRequest.of(0, 20));
map.put("discussions", discussions.getContent());
} else if ("reply".equals(type)) {
Page<Post> posts = postsRepository.findByCreateIdOrderByCreateTimeDesc(
currentUser.getId(), PageRequest.of(0, 20));
map.put("posts", posts.getContent());
// Attach discussion titles
List<String> discTitles = new ArrayList<>();
for (Post p : posts.getContent()) {
if (p.getDiscussionId() != null) {
Discussion d = discussionsRepository.findById(p.getDiscussionId()).orElse(null);
discTitles.add(d != null ? d.getTitle() : "");
} else {
discTitles.add("");
}
}
map.put("discTitles", discTitles);
} else if ("like".equals(type)) {
List<Integer> likedIds = likeCollectRepository.findLikedDiscussionIdsByUser(currentUser.getId());
List<Discussion> liked = new ArrayList<>();
for (Integer id : likedIds) {
Discussion d = discussionsRepository.findById(id).orElse(null);
if (d != null) liked.add(d);
}
map.put("likedDiscussions", liked);
}
if (type.equals("change")) {
map.put("change", "active");
}
if (type.equals("like")) {
map.put("like", "active");
}
UserInfo user = getUserInfo(request);
if (user != null) {
map.put("username", user.getUsername());
map.put("data", "Jiscuss 用户:" + user.getUsername());
}
return "user";
}
}
@@ -62,6 +62,9 @@ public class UserPostController extends BaseController {
@Autowired
private IpRegionService ipRegionService;
@Autowired
private com.yaoyuan.jiscuss.service.INotificationService notificationService;
/**
* 首页查看主题列表(各种条件筛选,最热最新标签等)
@@ -205,6 +208,14 @@ public class UserPostController extends BaseController {
}
Post savePost = postsService.insert(post);
// Notify the discussion starter about a reply (async, ignores self-reply)
if (post.getDiscussionId() != null && user != null) {
Discussion disc = discussionsService.findOne(post.getDiscussionId());
if (disc != null && disc.getStartUserId() != null) {
notificationService.notifyReply(disc.getStartUserId(), user.getId(),
disc.getId(), disc.getTitle());
}
}
Map<String, Object> resultobj = new HashMap<>();
logger.info(">>>{}", savePost);
resultobj.put("msg", "添加评论成功");
@@ -0,0 +1,129 @@
package com.yaoyuan.jiscuss.controller.api;
import com.yaoyuan.jiscuss.controller.BaseController;
import com.yaoyuan.jiscuss.entity.Message;
import com.yaoyuan.jiscuss.entity.Notification;
import com.yaoyuan.jiscuss.entity.User;
import com.yaoyuan.jiscuss.entity.UserInfo;
import com.yaoyuan.jiscuss.response.ApiResponse;
import com.yaoyuan.jiscuss.service.IMessageService;
import com.yaoyuan.jiscuss.service.INotificationService;
import com.yaoyuan.jiscuss.service.IUsersService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* REST API for private messages and notifications.
*/
@Tag(name = "Message API", description = "Private messages and notifications")
@RestController
@RequestMapping("/msg_api")
public class RestMsgController extends BaseController {
@Autowired
private IMessageService messageService;
@Autowired
private INotificationService notificationService;
@Autowired
private IUsersService usersService;
// ─── Debug (TEMP) ─────────────────────────────────────────────────────
/** Temp debug: returns raw message counts and inbox list for current user. */
@GetMapping("/debug/inbox")
public ApiResponse<Map<String, Object>> debugInbox(HttpServletRequest request) {
UserInfo user = getUserInfo(request);
if (user == null) return ApiResponse.fail(401, "请先登录");
long unread = messageService.countUnread(user.getId());
List<Message> inbox = messageService.getInbox(user.getId());
return ApiResponse.ok(Map.of(
"userId", user.getId(),
"username", user.getUsername(),
"unreadCount", unread,
"inboxSize", inbox.size(),
"inbox", inbox
));
}
// ─── User card (public — accessible to any authenticated user) ────────
/** Returns basic user info for the hover card (discussionsCount, commentsCount). */
@Operation(summary = "Get user card info")
@GetMapping("/user-card/{id}")
public ApiResponse<Map<String, Object>> getUserCard(@PathVariable Integer id, HttpServletRequest request) {
UserInfo currentUser = getUserInfo(request);
if (currentUser == null) return ApiResponse.fail(401, "请先登录");
User user = usersService.findOne(id);
if (user == null) return ApiResponse.fail(404, "用户不存在");
return ApiResponse.ok(Map.of(
"id", user.getId(),
"username", user.getUsername() != null ? user.getUsername() : "",
"discussionsCount", user.getDiscussionsCount() != null ? user.getDiscussionsCount() : 0,
"commentsCount", user.getCommentsCount() != null ? user.getCommentsCount() : 0
));
}
// ─── Notifications ────────────────────────────────────────────────────
@Operation(summary = "Get unread notification + message counts")
@GetMapping("/unread-counts")
public ApiResponse<Map<String, Long>> unreadCounts(HttpServletRequest request) {
UserInfo user = getUserInfo(request);
if (user == null) return ApiResponse.ok(Map.of("notifications", 0L, "messages", 0L));
return ApiResponse.ok(Map.of(
"notifications", notificationService.countUnread(user.getId()),
"messages", messageService.countUnread(user.getId())
));
}
@Operation(summary = "Mark a notification as read")
@PostMapping("/notifications/{id}/read")
public ApiResponse<Void> markNotifRead(@PathVariable Integer id, HttpServletRequest request) {
UserInfo user = getUserInfo(request);
if (user == null) return ApiResponse.fail(401, "请先登录");
notificationService.markOneRead(id, user.getId());
return ApiResponse.ok(null);
}
@Operation(summary = "Mark all notifications as read")
@PostMapping("/notifications/read-all")
public ApiResponse<Void> markAllNotifRead(HttpServletRequest request) {
UserInfo user = getUserInfo(request);
if (user == null) return ApiResponse.fail(401, "请先登录");
notificationService.markAllRead(user.getId());
return ApiResponse.ok(null);
}
// ─── Messages ─────────────────────────────────────────────────────────
@Operation(summary = "Send a private message")
@PostMapping("/messages/send")
public ApiResponse<Message> sendMessage(
@RequestParam Integer receiverId,
@RequestParam String content,
HttpServletRequest request) {
UserInfo user = getUserInfo(request);
if (user == null) return ApiResponse.fail(401, "请先登录");
if (user.getId().equals(receiverId)) return ApiResponse.fail(400, "不能给自己发消息");
if (content == null || content.isBlank()) return ApiResponse.fail(400, "消息内容不能为空");
Message msg = messageService.send(user.getId(), receiverId, content.trim());
return ApiResponse.ok(msg);
}
@Operation(summary = "Get conversation with another user")
@GetMapping("/messages/conversation/{otherId}")
public ApiResponse<List<Message>> getConversation(@PathVariable Integer otherId, HttpServletRequest request) {
UserInfo user = getUserInfo(request);
if (user == null) return ApiResponse.fail(401, "请先登录");
messageService.markConversationRead(user.getId(), otherId);
return ApiResponse.ok(messageService.getConversation(user.getId(), otherId));
}
}
@@ -0,0 +1,23 @@
package com.yaoyuan.jiscuss.dto;
import lombok.Data;
import java.util.Date;
/** View model for one inbox entry (latest message in a conversation). */
@Data
public class InboxItem {
private final Integer otherId;
private final String otherUsername;
private final String lastContent;
private final Date lastTime;
private final boolean hasUnread;
public InboxItem(Integer otherId, String otherUsername, String lastContent, Date lastTime, boolean hasUnread) {
this.otherId = otherId;
this.otherUsername = otherUsername;
this.lastContent = lastContent;
this.lastTime = lastTime;
this.hasUnread = hasUnread;
}
}
@@ -82,6 +82,6 @@ public class Discussion implements Serializable {
private Date createTime;
@Column(name = "view_count")
private Integer viewCount = 0;
private Integer viewCount;
}
@@ -0,0 +1,40 @@
package com.yaoyuan.jiscuss.entity;
import jakarta.persistence.*;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@Entity
@Table(name = "message")
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "sender_id", nullable = false)
private Integer senderId;
@Column(name = "receiver_id", nullable = false)
private Integer receiverId;
@Column(name = "content", columnDefinition = "TEXT", nullable = false)
private String content;
@Column(name = "is_read")
private Boolean isRead = false;
@Column(name = "create_time")
private Date createTime;
/**
* Conversation identifier = "min(a,b)_max(a,b)", groups all messages
* between two users into a single conversation thread.
*/
@Column(name = "conversation_id", nullable = false)
private String conversationId;
}
@@ -0,0 +1,50 @@
package com.yaoyuan.jiscuss.entity;
import jakarta.persistence.*;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@Entity
@Table(name = "notification")
public class Notification implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/** Receiver user ID. */
@Column(name = "user_id", nullable = false)
private Integer userId;
/** Sender user ID (null for system notifications). */
@Column(name = "from_user_id")
private Integer fromUserId;
/** Notification type: reply / like / system. */
@Column(name = "type", nullable = false)
private String type;
@Column(name = "title")
private String title;
@Column(name = "content", columnDefinition = "TEXT")
private String content;
/** Related entity ID (e.g., discussion or post id). */
@Column(name = "related_id")
private Integer relatedId;
/** Type of related entity: discussion / post. */
@Column(name = "related_type")
private String relatedType;
@Column(name = "is_read")
private Boolean isRead = false;
@Column(name = "create_time")
private Date createTime;
}
@@ -42,4 +42,11 @@ public interface DiscussionsRepository extends JpaRepository<Discussion, Integer
@Query("UPDATE Discussion d SET d.dislikeCount = COALESCE(d.dislikeCount, 0) + :delta WHERE d.id = :id")
void adjustDislikeCount(@org.springframework.data.repository.query.Param("id") Integer id,
@org.springframework.data.repository.query.Param("delta") int delta);
/** Discussions started by a given user, most recent first. */
@Query("SELECT d FROM Discussion d WHERE d.startUserId = :userId ORDER BY d.startTime DESC")
org.springframework.data.domain.Page<Discussion> findByStartUserIdOrderByStartTimeDesc(
@org.springframework.data.repository.query.Param("userId") Integer userId,
org.springframework.data.domain.Pageable pageable);
long countByStartUserId(Integer startUserId);
}
@@ -18,4 +18,8 @@ public interface LikeCollectRepository extends JpaRepository<LikeCollect, Intege
/** Find an existing vote record for a user on a post. */
@Query("FROM LikeCollect lc WHERE lc.userId = :userId AND lc.postId = :postId AND lc.type = 'post'")
Optional<LikeCollect> findPostVote(@Param("userId") Integer userId, @Param("postId") Integer postId);
/** Discussions liked by a user (action = 'like'). */
@Query("SELECT lc.discussionId FROM LikeCollect lc WHERE lc.userId = :userId AND lc.type = 'discussion' AND lc.action = 'like'")
java.util.List<Integer> findLikedDiscussionIdsByUser(@Param("userId") Integer userId);
}
@@ -0,0 +1,31 @@
package com.yaoyuan.jiscuss.repository;
import com.yaoyuan.jiscuss.entity.Message;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository
public interface MessageRepository extends JpaRepository<Message, Integer> {
/** Latest messages in a conversation, most recent first. */
List<Message> findByConversationIdOrderByCreateTimeDesc(String conversationId);
/** All messages sent by user. */
List<Message> findBySenderIdOrderByCreateTimeDesc(Integer senderId);
/** All messages received by user. */
List<Message> findByReceiverIdOrderByCreateTimeDesc(Integer receiverId);
long countByReceiverIdAndIsReadFalse(Integer receiverId);
@Modifying
@Transactional
@Query("UPDATE Message m SET m.isRead = true WHERE m.conversationId = :convId AND m.receiverId = :userId")
void markConversationReadForUser(@Param("convId") String convId, @Param("userId") Integer userId);
}
@@ -0,0 +1,29 @@
package com.yaoyuan.jiscuss.repository;
import com.yaoyuan.jiscuss.entity.Notification;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
public interface NotificationRepository extends JpaRepository<Notification, Integer> {
Page<Notification> findByUserIdOrderByCreateTimeDesc(Integer userId, Pageable pageable);
long countByUserIdAndIsReadFalse(Integer userId);
@Modifying
@Transactional
@Query("UPDATE Notification n SET n.isRead = true WHERE n.userId = :userId")
void markAllReadByUserId(@Param("userId") Integer userId);
@Modifying
@Transactional
@Query("UPDATE Notification n SET n.isRead = true WHERE n.id = :id AND n.userId = :userId")
void markReadById(@Param("id") Integer id, @Param("userId") Integer userId);
}
@@ -89,4 +89,11 @@ public interface PostsRepository extends JpaRepository<Post, Integer> {
//
// @Query("from Post where parentId is not null and discussionId = :dId")
// List<Post> findAllByDIdAndparentIdNotNull(@Param("dId")Integer dId);
/** Posts (replies) created by a given user, most recent first. */
@Query("SELECT p FROM Post p WHERE p.createId = :userId ORDER BY p.createTime DESC")
org.springframework.data.domain.Page<Post> findByCreateIdOrderByCreateTimeDesc(
@Param("userId") Integer userId, org.springframework.data.domain.Pageable pageable);
long countByCreateId(Integer createId);
}
@@ -0,0 +1,23 @@
package com.yaoyuan.jiscuss.service;
import com.yaoyuan.jiscuss.entity.Message;
import java.util.List;
public interface IMessageService {
/** Send a message from sender to receiver. */
Message send(Integer senderId, Integer receiverId, String content);
/** All messages in a conversation (ordered oldest→newest). */
List<Message> getConversation(Integer userId, Integer otherId);
/** Inbox: latest message per conversation, sorted by latest activity (max 50). */
List<Message> getInbox(Integer userId);
/** Unread message count for a user. */
long countUnread(Integer userId);
/** Mark an entire conversation as read for the current user. */
void markConversationRead(Integer userId, Integer otherId);
}
@@ -0,0 +1,27 @@
package com.yaoyuan.jiscuss.service;
import com.yaoyuan.jiscuss.entity.Notification;
import org.springframework.data.domain.Page;
public interface INotificationService {
/** Create a notification for a user. */
Notification create(Integer toUserId, Integer fromUserId, String type, String title, String content,
Integer relatedId, String relatedType);
/** Convenience: create a "reply" notification. */
void notifyReply(Integer toUserId, Integer fromUserId, Integer discussionId, String discussionTitle);
/** Convenience: create a "like" notification for a post. */
void notifyLike(Integer toUserId, Integer fromUserId, Integer postId, Integer discussionId);
/** Unread notification count for a user. */
long countUnread(Integer userId);
/** Paged notification list for a user. */
Page<Notification> listByUser(Integer userId, int page, int size);
void markAllRead(Integer userId);
void markOneRead(Integer notificationId, Integer userId);
}
@@ -1,10 +1,12 @@
package com.yaoyuan.jiscuss.service.impl;
import com.yaoyuan.jiscuss.entity.LikeCollect;
import com.yaoyuan.jiscuss.entity.Post;
import com.yaoyuan.jiscuss.repository.DiscussionsRepository;
import com.yaoyuan.jiscuss.repository.LikeCollectRepository;
import com.yaoyuan.jiscuss.repository.PostsRepository;
import com.yaoyuan.jiscuss.service.ILikeCollectService;
import com.yaoyuan.jiscuss.service.INotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -24,6 +26,9 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
@Autowired
private PostsRepository postsRepository;
@Autowired
private INotificationService notificationService;
@Transactional
@Override
public String voteDiscussion(Integer userId, Integer discussionId, String action) {
@@ -85,6 +90,13 @@ public class LikeCollectServiceImpl implements ILikeCollectService {
vote.setCreateTime(new Date());
likeCollectRepository.save(vote);
adjustPost(postId, action, +1);
// Notify post author when liked (not disliked)
if ("like".equals(action)) {
Post post = postsRepository.findById(postId).orElse(null);
if (post != null && post.getCreateId() != null) {
notificationService.notifyLike(post.getCreateId(), userId, postId, post.getDiscussionId());
}
}
return action;
}
}
@@ -0,0 +1,85 @@
package com.yaoyuan.jiscuss.service.impl;
import com.yaoyuan.jiscuss.entity.Message;
import com.yaoyuan.jiscuss.repository.MessageRepository;
import com.yaoyuan.jiscuss.service.IMessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
public class MessageServiceImpl implements IMessageService {
@Autowired
private MessageRepository messageRepository;
private String buildConversationId(Integer a, Integer b) {
return Math.min(a, b) + "_" + Math.max(a, b);
}
@Transactional
@Override
public Message send(Integer senderId, Integer receiverId, String content) {
Message msg = new Message();
msg.setSenderId(senderId);
msg.setReceiverId(receiverId);
msg.setContent(content);
msg.setIsRead(false);
msg.setCreateTime(new Date());
msg.setConversationId(buildConversationId(senderId, receiverId));
return messageRepository.save(msg);
}
@Transactional(readOnly = true)
@Override
public List<Message> getConversation(Integer userId, Integer otherId) {
String convId = buildConversationId(userId, otherId);
List<Message> msgs = messageRepository.findByConversationIdOrderByCreateTimeDesc(convId);
// reverse to get oldest-first order
java.util.Collections.reverse(msgs);
return msgs;
}
@Transactional(readOnly = true)
@Override
public List<Message> getInbox(Integer userId) {
// Fetch sent and received separately, merge, then deduplicate by conversationId
List<Message> sent = messageRepository.findBySenderIdOrderByCreateTimeDesc(userId);
List<Message> received = messageRepository.findByReceiverIdOrderByCreateTimeDesc(userId);
// Merge both lists, sort by createTime descending
java.util.List<Message> all = new java.util.ArrayList<>();
all.addAll(sent);
all.addAll(received);
all.sort((a, b) -> {
if (a.getCreateTime() == null) return 1;
if (b.getCreateTime() == null) return -1;
return b.getCreateTime().compareTo(a.getCreateTime());
});
// Keep only the latest message per conversation
java.util.LinkedHashMap<String, Message> seen = new java.util.LinkedHashMap<>();
for (Message m : all) {
String cid = (m.getConversationId() != null && !m.getConversationId().isEmpty())
? m.getConversationId()
: Math.min(m.getSenderId(), m.getReceiverId()) + "_" + Math.max(m.getSenderId(), m.getReceiverId());
seen.putIfAbsent(cid, m);
}
return new java.util.ArrayList<>(seen.values());
}
@Transactional(readOnly = true)
@Override
public long countUnread(Integer userId) {
return messageRepository.countByReceiverIdAndIsReadFalse(userId);
}
@Transactional
@Override
public void markConversationRead(Integer userId, Integer otherId) {
messageRepository.markConversationReadForUser(buildConversationId(userId, otherId), userId);
}
}
@@ -0,0 +1,83 @@
package com.yaoyuan.jiscuss.service.impl;
import com.yaoyuan.jiscuss.entity.Notification;
import com.yaoyuan.jiscuss.repository.NotificationRepository;
import com.yaoyuan.jiscuss.service.INotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@Service
public class NotificationServiceImpl implements INotificationService {
@Autowired
private NotificationRepository notificationRepository;
@Transactional
@Override
public Notification create(Integer toUserId, Integer fromUserId, String type, String title,
String content, Integer relatedId, String relatedType) {
// Do not notify yourself
if (toUserId != null && toUserId.equals(fromUserId)) {
return null;
}
Notification n = new Notification();
n.setUserId(toUserId);
n.setFromUserId(fromUserId);
n.setType(type);
n.setTitle(title);
n.setContent(content);
n.setRelatedId(relatedId);
n.setRelatedType(relatedType);
n.setIsRead(false);
n.setCreateTime(new Date());
return notificationRepository.save(n);
}
@Async
@Override
public void notifyReply(Integer toUserId, Integer fromUserId, Integer discussionId, String discussionTitle) {
create(toUserId, fromUserId, "reply",
"有人回复了你的帖子",
"在话题《" + discussionTitle + "》中回复了你",
discussionId, "discussion");
}
@Async
@Override
public void notifyLike(Integer toUserId, Integer fromUserId, Integer postId, Integer discussionId) {
create(toUserId, fromUserId, "like",
"有人赞了你的回复",
null, postId, "post");
}
@Transactional(readOnly = true)
@Override
public long countUnread(Integer userId) {
return notificationRepository.countByUserIdAndIsReadFalse(userId);
}
@Transactional(readOnly = true)
@Override
public Page<Notification> listByUser(Integer userId, int page, int size) {
return notificationRepository.findByUserIdOrderByCreateTimeDesc(
userId, PageRequest.of(page, size));
}
@Transactional
@Override
public void markAllRead(Integer userId) {
notificationRepository.markAllReadByUserId(userId);
}
@Transactional
@Override
public void markOneRead(Integer notificationId, Integer userId) {
notificationRepository.markReadById(notificationId, userId);
}
}