post_rating

Change-Id: Ia1a6fb3f87b793a6307046e36951c1fb36b213c8
diff --git a/src/main/java/com/example/g8backend/service/IForgotPasswordService.java b/src/main/java/com/example/g8backend/service/IForgotPasswordService.java
new file mode 100644
index 0000000..af865ae
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/IForgotPasswordService.java
@@ -0,0 +1,6 @@
+package com.example.g8backend.service;
+
+public interface IForgotPasswordService {
+    void sendCodeToEmail(String username);
+    boolean resetPassword(String username, String code, String newPassword);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/IPostRatingService.java b/src/main/java/com/example/g8backend/service/IPostRatingService.java
new file mode 100644
index 0000000..dc0a4f9
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/IPostRatingService.java
@@ -0,0 +1,10 @@
+package com.example.g8backend.service;
+
+import com.example.g8backend.dto.ApiResponse;
+
+public interface IPostRatingService {
+    void ratePost(Long userId, Long postId, Integer rating);
+    Double getAverageRating(Long postId);
+
+    Long getRatingUserCount(Long postId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/ITorrentService.java b/src/main/java/com/example/g8backend/service/ITorrentService.java
index 87622c8..517887c 100644
--- a/src/main/java/com/example/g8backend/service/ITorrentService.java
+++ b/src/main/java/com/example/g8backend/service/ITorrentService.java
@@ -7,7 +7,7 @@
 import java.io.IOException;
 
 public interface ITorrentService extends IService<Torrent> {
-    Torrent handleTorrentUpload(File file, Long userId, String passkey) throws IOException;
+    Torrent handleTorrentUpload(File file, String fileName,Long userId, String passkey) throws IOException;
     File handleTorrentDownload(Torrent torrent, String passkey) throws IOException;
     Torrent findByInfoHash(String infoHash);
     Torrent findByTorrentId(Long torrentId);
diff --git a/src/main/java/com/example/g8backend/service/IUserSecurityService.java b/src/main/java/com/example/g8backend/service/IUserSecurityService.java
new file mode 100644
index 0000000..0ac40dc
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/IUserSecurityService.java
@@ -0,0 +1,5 @@
+package com.example.g8backend.service;
+
+public interface IUserSecurityService {
+    boolean changePassword(Long userId, String oldPassword, String newPassword);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/IUserStatsService.java b/src/main/java/com/example/g8backend/service/IUserStatsService.java
new file mode 100644
index 0000000..4771829
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/IUserStatsService.java
@@ -0,0 +1,12 @@
+package com.example.g8backend.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.example.g8backend.entity.UserStats;
+
+/**
+ * 这个service类暂时是空的,因为目前还没做流量检测相关的。
+ * 后面弄作弊检测的时候再补充相关的方法。
+ */
+public interface IUserStatsService extends IService<UserStats> {
+
+}
diff --git a/src/main/java/com/example/g8backend/service/impl/ForgotPasswordServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/ForgotPasswordServiceImpl.java
new file mode 100644
index 0000000..f75617b
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/impl/ForgotPasswordServiceImpl.java
@@ -0,0 +1,66 @@
+package com.example.g8backend.service.impl;
+
+import com.example.g8backend.entity.User;
+import com.example.g8backend.mapper.UserMapper;
+import com.example.g8backend.service.IForgotPasswordService;
+import com.example.g8backend.util.mailUtil;  // 导入 mailUtil
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class ForgotPasswordServiceImpl implements IForgotPasswordService {
+
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private RedisTemplate<String, String> redisTemplate;
+
+    @Resource
+    private PasswordEncoder passwordEncoder;
+
+    @Resource
+    private mailUtil mailUtil;  // 注入 mailUtil
+
+    @Override
+    public void sendCodeToEmail(String username) {
+        User user = userMapper.getUserByName(username);
+        if (user == null || user.getEmail() == null) throw new RuntimeException("用户不存在或未绑定邮箱");
+
+        // 生成验证码
+        String code = String.valueOf(100000 + new Random().nextInt(900000));
+
+        // 将验证码存储到 Redis
+        redisTemplate.opsForValue().set("reset_code:" + username, code, 10, TimeUnit.MINUTES);
+
+        // 使用 mailUtil 发送邮件
+        String subject = "重置密码验证码";
+        String message = "您的验证码为:" + code + ",10分钟内有效。";
+        mailUtil.sendMail(user.getEmail(), subject, message);  // 发送邮件
+    }
+
+    @Override
+    public boolean resetPassword(String username, String code, String newPassword) {
+        String key = "reset_code:" + username;
+        String realCode = redisTemplate.opsForValue().get(key);
+
+        if (realCode == null || !realCode.equals(code)) throw new RuntimeException("验证码错误或已过期");
+
+        User user = userMapper.getUserByName(username);
+        if (user == null) throw new RuntimeException("用户不存在");
+
+        // 更新密码
+        user.setPassword(passwordEncoder.encode(newPassword));
+
+        // 删除 Redis 中存储的验证码
+        redisTemplate.delete(key);
+
+        // 更新用户信息
+        return userMapper.updateById(user) > 0;
+    }
+}
diff --git a/src/main/java/com/example/g8backend/service/impl/PostRatingServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/PostRatingServiceImpl.java
new file mode 100644
index 0000000..9afb01d
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/impl/PostRatingServiceImpl.java
@@ -0,0 +1,55 @@
+package com.example.g8backend.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.g8backend.dto.ApiResponse;
+import com.example.g8backend.entity.Post;
+import com.example.g8backend.entity.PostRating;
+import com.example.g8backend.mapper.PostMapper;
+import com.example.g8backend.mapper.PostRatingMapper;
+import com.example.g8backend.service.IPostRatingService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class PostRatingServiceImpl extends ServiceImpl<PostRatingMapper, PostRating> implements IPostRatingService {
+
+    private final PostRatingMapper postRatingMapper;
+    private final PostMapper postMapper;
+
+    @Override
+    @Transactional
+    public void ratePost(Long userId, Long postId, Integer rating) {
+        // 校验评分范围
+        if (rating < 1 || rating > 5) {
+            throw new IllegalArgumentException("评分值必须在1到5之间");
+        }
+
+        // 插入或更新评分记录
+        PostRating postRating = new PostRating();
+        postRating.setUserId(userId);
+        postRating.setPostId(postId);
+        postRating.setRating(rating);
+        boolean success = postRatingMapper.insertOrUpdate(postRating);
+
+        if (!success) {
+            throw new RuntimeException("评分操作失败");
+        }
+
+        // 更新统计信息
+        Double avgRating = postRatingMapper.calculateAverageRating(postId);
+        Integer count = postRatingMapper.getRatingCount(postId);
+        postMapper.updateRatingStats(postId, avgRating, count);
+    }
+
+    @Override
+    public Double getAverageRating(Long postId) {
+        return postRatingMapper.calculateAverageRating(postId);
+    }
+
+    @Override
+    public Long getRatingUserCount(Long postId) {
+        return postRatingMapper.selectRatingUserCount(postId);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
index 199ff2a..43855a4 100644
--- a/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/TorrentServiceImpl.java
@@ -20,7 +20,7 @@
     String tracker = "http://127.0.0.1:8080/tracker/announce/";
 
     @Override
-    public Torrent handleTorrentUpload(File file, Long userId, String passkey) throws IOException, IllegalArgumentException {
+    public Torrent handleTorrentUpload(File file, String fileName, Long userId, String passkey) throws IOException, IllegalArgumentException {
         // 修改 announce 字段
         byte[] modifiedBytes = TorrentUtil.injectTracker(file, tracker + passkey);
 
@@ -44,7 +44,7 @@
         }
 
         // 插入数据库
-        torrentMapper.insertTorrent(userId, file.getName(), infoHash, fileSize);
+        torrentMapper.insertTorrent(userId, fileName, file.getName(), infoHash, fileSize);
 
         // 构建返回实体
         Torrent torrent = new Torrent();
@@ -57,7 +57,7 @@
 
     @Override
     public File handleTorrentDownload(Torrent torrent, String passkey) throws IOException {
-        File torrentFile = new File("uploaded-torrents/" + torrent.getTorrentName());
+        File torrentFile = new File("uploaded-torrents/" + torrent.getFilePath());
         byte[] modifiedBytes = TorrentUtil.injectTracker(torrentFile, tracker + passkey);
 
         File tempFile = File.createTempFile("user_torrent_", ".torrent");
diff --git a/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
index ffb5e65..f3d966f 100644
--- a/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/TrackerServiceImpl.java
@@ -4,6 +4,7 @@
 import com.example.g8backend.dto.AnnounceResponseDTO;
 import com.example.g8backend.entity.Peer;
 import com.example.g8backend.mapper.PeerMapper;
+import com.example.g8backend.mapper.UserStatsMapper;
 import com.example.g8backend.service.ITrackerService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
@@ -19,6 +20,9 @@
     private PeerMapper peerMapper;
 
     @Autowired
+    private UserStatsMapper userStatsMapper;
+
+    @Autowired
     private RedisTemplate<String, Object> redisTemplate;
 
     @Override
@@ -47,7 +51,7 @@
             newPeer.setPeerId(peerId);
             newPeer.setInfo_hash(infoHash);
             newPeer.setPasskey(passkey);
-            newPeer.setIpAddress(ip); // TODO: 从 request 获取真实 IP
+            newPeer.setIpAddress(ip);
             newPeer.setPort(port);
             newPeer.setUploaded(uploaded);
             newPeer.setDownloaded(downloaded);
@@ -62,6 +66,33 @@
         // 缓存 peer 到 Redis
         redisTemplate.opsForSet().add(redisKey, peerId);
 
+        // 更新用户流量
+        if (!event.equalsIgnoreCase("started")){
+            String statKey = "user:peer:" + passkey + ":" + infoHash + ":" + peerId;
+            Map<Object, Object> last = redisTemplate.opsForHash().entries(statKey);
+
+            Object uploadedObj = last.get("uploaded");
+            Object downloadedObj = last.get("downloaded");
+
+            double lastUploaded = (uploadedObj instanceof Number) ? ((Number) uploadedObj).doubleValue() : 0.0;
+            double lastDownloaded = (downloadedObj instanceof Number) ? ((Number) downloadedObj).doubleValue() : 0.0;
+
+            // 计算流量差值
+            double deltaUploaded = Math.max(0, uploaded - lastUploaded);
+            double deltaDownloaded = Math.max(0, downloaded - lastDownloaded);
+
+            // 更新数据库中用户统计值
+            if (deltaUploaded > 0 || deltaDownloaded > 0) {
+                userStatsMapper.incrementTraffic(passkey, deltaUploaded, deltaDownloaded);
+            }
+
+            // Redis 中更新上传下载量缓存
+            Map<String, Object> current = new HashMap<>();
+            current.put("uploaded", uploaded);
+            current.put("downloaded", downloaded);
+            redisTemplate.opsForHash().putAll(statKey, current);
+        }
+
         // 构造返回 peer 列表
         List<Map<String, Object>> peerList = new ArrayList<>();
         Set<Object> peerIds = redisTemplate.opsForSet().members(redisKey);
diff --git a/src/main/java/com/example/g8backend/service/impl/UserSecurityServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/UserSecurityServiceImpl.java
new file mode 100644
index 0000000..e49551f
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/impl/UserSecurityServiceImpl.java
@@ -0,0 +1,27 @@
+package com.example.g8backend.service.impl;
+
+import com.example.g8backend.entity.User;
+import com.example.g8backend.mapper.UserMapper;
+import com.example.g8backend.service.IUserSecurityService;
+import jakarta.annotation.Resource;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserSecurityServiceImpl implements IUserSecurityService {
+    @Resource
+    private UserMapper userMapper;
+    @Resource
+    private PasswordEncoder passwordEncoder;
+
+    @Override
+    public boolean changePassword(Long userId, String oldPassword, String newPassword) {
+        User user = userMapper.selectById(userId);
+        if (user == null) throw new RuntimeException("用户不存在");
+        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
+            throw new RuntimeException("原密码错误");
+        }
+        user.setPassword(passwordEncoder.encode(newPassword));
+        return userMapper.updateById(user) > 0;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/impl/UserStatsServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/UserStatsServiceImpl.java
new file mode 100644
index 0000000..9110d16
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/impl/UserStatsServiceImpl.java
@@ -0,0 +1,11 @@
+package com.example.g8backend.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.g8backend.entity.UserStats;
+import com.example.g8backend.mapper.UserStatsMapper;
+import com.example.g8backend.service.IUserStatsService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserStatsServiceImpl extends ServiceImpl<UserStatsMapper, UserStats> implements IUserStatsService {
+}