Merge changes I33aa2968,I3e9bbcc8 into whx
* changes:
controller_adjust
follow+sendMessage
diff --git a/pom.xml b/pom.xml
index 9238ad6..d4d9fc2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,7 @@
<version>3.5.11</version>
</dependency>
+
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
diff --git a/src/main/java/com/example/g8backend/controller/UserController.java b/src/main/java/com/example/g8backend/controller/UserController.java
index 2665b4c..0473025 100644
--- a/src/main/java/com/example/g8backend/controller/UserController.java
+++ b/src/main/java/com/example/g8backend/controller/UserController.java
@@ -1,5 +1,6 @@
package com.example.g8backend.controller;
+import com.example.g8backend.entity.Message;
import com.example.g8backend.entity.User;
import com.example.g8backend.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -8,6 +9,9 @@
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
@RestController
@RequestMapping("/user")
public class UserController {
@@ -24,4 +28,63 @@
user.setPassword(null);
return ResponseEntity.ok(user);
}
+ // ==================== 关注功能 ====================
+ @PostMapping("/follow/{userId}")
+ public ResponseEntity<?> followUser(@PathVariable Long userId) {
+ Long currentUserId = getCurrentUserId();
+ boolean success = userService.followUser(currentUserId, userId);
+ return ResponseEntity.ok(Map.of("success", success));
+ }
+
+ @DeleteMapping("/follow/{userId}")
+ public ResponseEntity<?> unfollowUser(@PathVariable Long userId) {
+ Long currentUserId = getCurrentUserId();
+ boolean success = userService.unfollowUser(currentUserId, userId);
+ return ResponseEntity.ok(Map.of("success", success));
+ }
+
+ @GetMapping("/followings")
+ public ResponseEntity<?> getFollowings() {
+ Long currentUserId = getCurrentUserId();
+ List<User> followings = userService.getFollowings(currentUserId);
+ return ResponseEntity.ok(followings);
+ }
+
+ @GetMapping("/followers")
+ public ResponseEntity<?> getFollowers() {
+ Long currentUserId = getCurrentUserId();
+ List<User> followers = userService.getFollowers(currentUserId);
+ return ResponseEntity.ok(followers);
+ }
+
+ // ==================== 私信功能 ====================
+ @PostMapping("/message/{receiverId}")
+ public ResponseEntity<?> sendMessage(
+ @PathVariable Long receiverId,
+ @RequestBody String content
+ ) {
+ Long senderId = getCurrentUserId();
+ Long messageId = userService.sendMessage(senderId, receiverId, content);
+ return ResponseEntity.ok(Map.of("messageId", messageId));
+ }
+
+ @GetMapping("/messages/{otherUserId}")
+ public ResponseEntity<?> getMessages(@PathVariable Long otherUserId) {
+ Long currentUserId = getCurrentUserId();
+ List<Message> messages = userService.getMessages(currentUserId, otherUserId);
+ return ResponseEntity.ok(messages);
+ }
+
+ @GetMapping("/messages/history")
+ public ResponseEntity<?> getMessageHistory() {
+ Long currentUserId = getCurrentUserId();
+ List<Message> messages = userService.getMessageHistory(currentUserId);
+ return ResponseEntity.ok(messages);
+ }
+
+ // ==================== 工具方法 ====================
+ private Long getCurrentUserId() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ return (Long) authentication.getPrincipal();
+ }
}
diff --git a/src/main/java/com/example/g8backend/entity/Follow.java b/src/main/java/com/example/g8backend/entity/Follow.java
new file mode 100644
index 0000000..f3ba6eb
--- /dev/null
+++ b/src/main/java/com/example/g8backend/entity/Follow.java
@@ -0,0 +1,16 @@
+// Follow.java(新增)
+package com.example.g8backend.entity;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("user_follows")
+@Accessors(chain = true)
+public class Follow {
+ private Long followerId; // 保持Long类型
+ private Long followedId;
+ private LocalDateTime createdAt;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/entity/Message.java b/src/main/java/com/example/g8backend/entity/Message.java
new file mode 100644
index 0000000..e16e244
--- /dev/null
+++ b/src/main/java/com/example/g8backend/entity/Message.java
@@ -0,0 +1,24 @@
+// Message.java(新增)
+package com.example.g8backend.entity;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+
+@Data
+@Accessors(chain = true)
+@TableName("private_messages")
+public class Message {
+ @TableId(type = IdType.AUTO)
+ private Long messageId;
+ private Long senderId;
+ private Long receiverId;
+ private String content;
+ private LocalDateTime sentAt;
+ @TableField("is_read")
+ private Boolean isRead = false; // ✅ 默认值
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/entity/User.java b/src/main/java/com/example/g8backend/entity/User.java
index 7ba8e03..a9f5746 100644
--- a/src/main/java/com/example/g8backend/entity/User.java
+++ b/src/main/java/com/example/g8backend/entity/User.java
@@ -4,9 +4,11 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
+import lombok.experimental.Accessors;
@Data
@TableName("users")
+@Accessors(chain = true)
public class User {
@TableId(type = IdType.AUTO)
private Long userId;
@@ -24,4 +26,6 @@
", email='" + email + '\'' +
'}';
}
+
+
}
diff --git a/src/main/java/com/example/g8backend/mapper/FollowMapper.java b/src/main/java/com/example/g8backend/mapper/FollowMapper.java
new file mode 100644
index 0000000..f1d03af
--- /dev/null
+++ b/src/main/java/com/example/g8backend/mapper/FollowMapper.java
@@ -0,0 +1,22 @@
+package com.example.g8backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.g8backend.entity.Follow;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface FollowMapper extends BaseMapper<Follow> {
+ @Delete("DELETE FROM user_follows WHERE follower_id=#{followerId} AND followed_id=#{followedId}")
+ int deleteByPair(@Param("followerId") Long followerId, @Param("followedId") Long followedId);
+
+ @Select("SELECT followed_id FROM user_follows WHERE follower_id = #{userId}")
+ List<Long> selectFollowings(Long userId);
+
+ @Select("SELECT follower_id FROM user_follows WHERE followed_id = #{userId}")
+ List<Long> selectFollowers(Long userId);
+}
diff --git a/src/main/java/com/example/g8backend/mapper/MessageMapper.java b/src/main/java/com/example/g8backend/mapper/MessageMapper.java
new file mode 100644
index 0000000..3f2caf4
--- /dev/null
+++ b/src/main/java/com/example/g8backend/mapper/MessageMapper.java
@@ -0,0 +1,23 @@
+package com.example.g8backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.g8backend.entity.Message;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface MessageMapper extends BaseMapper<Message> {
+ @Select("SELECT * FROM private_messages " +
+ "WHERE (sender_id=#{userId1} AND receiver_id=#{userId2}) " +
+ "OR (sender_id=#{userId2} AND receiver_id=#{userId1}) " +
+ "ORDER BY sent_at")
+ List<Message> selectConversation(@Param("userId1") Long userId1, @Param("userId2") Long userId2);
+
+ @Select("SELECT * FROM private_messages " +
+ "WHERE sender_id=#{userId} OR receiver_id=#{userId} " +
+ "ORDER BY sent_at DESC")
+ List<Message> selectUserMessages(Long userId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/IUserService.java b/src/main/java/com/example/g8backend/service/IUserService.java
index af94d27..1f178f8 100644
--- a/src/main/java/com/example/g8backend/service/IUserService.java
+++ b/src/main/java/com/example/g8backend/service/IUserService.java
@@ -1,11 +1,25 @@
package com.example.g8backend.service;
import com.baomidou.mybatisplus.extension.service.IService;
+import com.example.g8backend.entity.Message;
import com.example.g8backend.entity.User;
import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
public interface IUserService extends IService<User> {
User getUserByName(@Param("name") String name);
User getUserByEmail(@Param("email") String email);
User getUserByPasskey(@Param("passkey") String passkey);
+
+ // 关注功能
+ boolean followUser(Long followerId, Long followedId);
+ boolean unfollowUser(Long followerId, Long followedId);
+ List<User> getFollowings(Long userId);
+ List<User> getFollowers(Long userId);
+
+ // 私信功能
+ Long sendMessage(Long senderId, Long receiverId, String content);
+ List<Message> getMessages(Long userId, Long partnerId);
+ List<Message> getMessageHistory(Long userId);
}
diff --git a/src/main/java/com/example/g8backend/service/impl/UserServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/UserServiceImpl.java
index e8bcf20..1d1b6dc 100644
--- a/src/main/java/com/example/g8backend/service/impl/UserServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/UserServiceImpl.java
@@ -1,12 +1,22 @@
package com.example.g8backend.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.g8backend.entity.Follow;
+import com.example.g8backend.entity.Message;
import com.example.g8backend.entity.User;
+import com.example.g8backend.mapper.FollowMapper;
+import com.example.g8backend.mapper.MessageMapper;
import com.example.g8backend.mapper.UserMapper;
import com.example.g8backend.service.IUserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@@ -22,4 +32,89 @@
@Override
public User getUserByPasskey(String passkey) { return userMapper.getUserByPasskey(passkey);}
+ @Resource
+ private FollowMapper followMapper;
+ @Resource
+ private MessageMapper messageMapper;
+
+ @Override
+ public boolean followUser(Long followerId, Long followedId) {
+ if (followerId.equals(followedId)) return false;
+ Follow follow = new Follow();
+ follow.setFollowerId(followerId);
+ follow.setFollowedId(followedId);
+ return followMapper.insert(follow) > 0;
+ }
+
+ @Override
+ public boolean unfollowUser(Long followerId, Long followedId) {
+ // 删除关注关系
+ return followMapper.deleteByPair(followerId, followedId) > 0;
+ }
+
+ @Override
+ public List<User> getFollowings(Long userId) {
+ // 1. 获取关注ID列表
+ List<Long> followingIds = followMapper.selectFollowings(userId);
+ // 2. 批量查询用户信息
+ return followingIds.isEmpty() ?
+ Collections.emptyList() :
+ this.listByIds(followingIds).stream()
+ .peek(user -> user.setPassword(null)) // 敏感信息脱敏
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<User> getFollowers(Long userId) {
+ // 1. 获取粉丝ID列表
+ List<Long> followerIds = followMapper.selectFollowers(userId);
+ // 2. 批量查询用户信息
+ return followerIds.isEmpty() ?
+ Collections.emptyList() :
+ this.listByIds(followerIds).stream()
+ .peek(user -> user.setPassword(null))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Long sendMessage(Long senderId, Long receiverId, String content) {
+ // 1. 校验接收者是否存在(方案一)
+ if (userMapper.selectById(receiverId) == null) {
+ throw new RuntimeException("接收用户不存在");
+ }
+
+ // 2. 创建消息实体
+ Message message = new Message()
+ .setSenderId(senderId)
+ .setReceiverId(receiverId)
+ .setContent(content)
+ .setSentAt(LocalDateTime.now());
+
+ // 3. 插入数据库
+ messageMapper.insert(message);
+ return message.getMessageId();
+ }
+
+ @Override
+ public List<Message> getMessages(Long userId, Long partnerId) {
+ // 获取双方对话记录
+ return messageMapper.selectConversation(userId, partnerId).stream()
+ .peek(msg -> {
+ // 标记消息为已读
+ if (!msg.getIsRead() && msg.getReceiverId().equals(userId)) {
+ msg.setIsRead(true);
+ messageMapper.updateById(msg);
+ }
+ })
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<Message> getMessageHistory(Long userId) {
+ // 获取用户所有相关消息
+ return messageMapper.selectUserMessages(userId).stream()
+ .sorted(Comparator.comparing(Message::getSentAt).reversed())
+ .collect(Collectors.toList());
+ }
+
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 6bde80e..7f85f87 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,4 +1,4 @@
-spring.datasource.password=123456
+spring.datasource.password=12345678
spring.datasource.username=root
spring.datasource.url=jdbc:mysql://localhost:3306/g8backend
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
diff --git a/src/main/resources/mapper/PostMapper.xml b/src/main/resources/mapper/PostMapper.xml
index c1dbda7..d1cbcf4 100644
--- a/src/main/resources/mapper/PostMapper.xml
+++ b/src/main/resources/mapper/PostMapper.xml
@@ -1,8 +1,8 @@
-.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
<mapper namespace="com.example.g8backend.mapper.PostMapper">
<select id="getPostsByUserId" resultType="com.example.g8backend.entity.Post">
SELECT * FROM posts WHERE user_id = #{userId}
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 952313e..5e5dd43 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -63,3 +63,25 @@
FOREIGN KEY (post_id) REFERENCES posts(post_id),
PRIMARY KEY (user_id, post_id)
);
+
+-- 关注关系表
+CREATE TABLE IF NOT EXISTS `user_follows` (
+ follower_id INT NOT NULL,
+ followed_id INT NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (follower_id) REFERENCES users(user_id),
+ FOREIGN KEY (followed_id) REFERENCES users(user_id),
+ PRIMARY KEY (follower_id, followed_id)
+);
+
+-- 私信表
+CREATE TABLE IF NOT EXISTS `private_messages` (
+ message_id INT AUTO_INCREMENT PRIMARY KEY,
+ sender_id INT NOT NULL,
+ receiver_id INT NOT NULL,
+ content TEXT NOT NULL,
+ sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ is_read BOOLEAN DEFAULT false,
+ FOREIGN KEY (sender_id) REFERENCES users(user_id),
+ FOREIGN KEY (receiver_id) REFERENCES users(user_id)
+);
\ No newline at end of file
diff --git a/src/test/java/com/example/g8backend/service/TorrentServiceTest.java b/src/test/java/com/example/g8backend/service/TorrentServiceTest.java
index 5063501..6ddca58 100644
--- a/src/test/java/com/example/g8backend/service/TorrentServiceTest.java
+++ b/src/test/java/com/example/g8backend/service/TorrentServiceTest.java
@@ -4,6 +4,7 @@
import com.example.g8backend.entity.Torrent;
import com.example.g8backend.mapper.TorrentMapper;
import com.example.g8backend.service.impl.TorrentServiceImpl;
+
import com.example.g8backend.util.TorrentUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/src/test/java/com/example/g8backend/service/UserServiceImplTest.java b/src/test/java/com/example/g8backend/service/UserServiceImplTest.java
new file mode 100644
index 0000000..96cf868
--- /dev/null
+++ b/src/test/java/com/example/g8backend/service/UserServiceImplTest.java
@@ -0,0 +1,149 @@
+package com.example.g8backend.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.example.g8backend.entity.Follow;
+import com.example.g8backend.entity.Message;
+import com.example.g8backend.entity.User;
+import com.example.g8backend.mapper.FollowMapper;
+import com.example.g8backend.mapper.MessageMapper;
+import com.example.g8backend.mapper.UserMapper;
+import com.example.g8backend.service.impl.UserServiceImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.*;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@ExtendWith(MockitoExtension.class)
+public class UserServiceImplTest {
+
+ @Mock
+ private UserMapper userMapper;
+
+ @Mock
+ private FollowMapper followMapper;
+
+ @Mock
+ private MessageMapper messageMapper;
+
+ @InjectMocks
+ private UserServiceImpl userService;
+
+ private User user1;
+ private User user2;
+ private Follow follow;
+ private Message message;
+
+ @BeforeEach
+ void setUp() {
+ user1 = new User().setUserId(1L).setUserName("user1");
+ user2 = new User().setUserId(2L).setUserName("user2");
+ ReflectionTestUtils.setField(userService, "baseMapper", userMapper);
+
+ follow = new Follow().setFollowerId(1L).setFollowedId(2L);
+ message = new Message()
+ .setMessageId(100L)
+ .setSenderId(1L)
+ .setReceiverId(2L)
+ .setContent("Hello")
+ .setIsRead(false)
+ .setSentAt(LocalDateTime.now());
+ }
+
+ // ------------------------- 关注功能测试 -------------------------
+ @Test
+ void followUser_Success() {
+ // 模拟Mapper行为
+ when(followMapper.insert(any(Follow.class))).thenReturn(1);
+
+ // 测试关注
+ boolean result = userService.followUser(1L, 2L);
+ assertTrue(result);
+ verify(followMapper, times(1)).insert(any(Follow.class));
+ }
+
+ @Test
+ void followUser_SelfFollow_Fail() {
+ // 尝试关注自己
+ boolean result = userService.followUser(1L, 1L);
+ assertFalse(result);
+ verify(followMapper, never()).insert((Follow) any());
+ }
+
+ @Test
+ void unfollowUser_Success() {
+ // 模拟Mapper行为
+ when(followMapper.deleteByPair(1L, 2L)).thenReturn(1);
+
+ // 测试取消关注
+ boolean result = userService.unfollowUser(1L, 2L);
+ assertTrue(result);
+ verify(followMapper, times(1)).deleteByPair(1L, 2L);
+ }
+
+
+ @Test
+ void getFollowers_EmptyList() {
+ // 模拟无粉丝
+ when(followMapper.selectFollowers(1L)).thenReturn(Collections.emptyList());
+
+ // 测试获取粉丝列表
+ List<User> followers = userService.getFollowers(1L);
+ assertTrue(followers.isEmpty());
+ }
+
+ // ------------------------- 私信功能测试 -------------------------
+ @Test
+ void sendMessage_Success() {
+ // 模拟用户存在
+ when(userMapper.selectById(2L)).thenReturn(user2);
+ when(messageMapper.insert(any(Message.class))).thenAnswer(invocation -> {
+ Message msg = invocation.getArgument(0);
+ msg.setMessageId(100L);
+ return 1;
+ });
+
+ // 测试发送消息
+ Long messageId = userService.sendMessage(1L, 2L, "Hello");
+ assertNotNull(messageId);
+ verify(messageMapper, times(1)).insert(any(Message.class));
+ }
+
+ @Test
+ void sendMessage_ReceiverNotExist_ThrowException() {
+ // 模拟用户不存在
+ when(userMapper.selectById(99L)).thenReturn(null);
+
+ // 测试异常
+ assertThrows(RuntimeException.class, () ->
+ userService.sendMessage(1L, 99L, "Hello")
+ );
+ verify(messageMapper, never()).insert((Message) any());
+ }
+
+ @Test
+ void getMessageHistory_SortedByTime() {
+ // 模拟历史消息
+ Message oldMessage = message.setSentAt(LocalDateTime.now().minusHours(1));
+ when(messageMapper.selectUserMessages(1L))
+ .thenReturn(Arrays.asList(oldMessage, message));
+
+ // 测试按时间倒序排序
+ List<Message> history = userService.getMessageHistory(1L);
+ assertEquals(message.getMessageId(), history.get(0).getMessageId());
+ }
+}
\ No newline at end of file