完成对announce请求的处理,初步完成上传量和下载量的统计

Change-Id: Ief942d0364a11d945a10a5a58d7652a054bedaa4
diff --git a/src/main/java/com/example/g8backend/controller/AuthController.java b/src/main/java/com/example/g8backend/controller/AuthController.java
index e824b63..5561dcd 100644
--- a/src/main/java/com/example/g8backend/controller/AuthController.java
+++ b/src/main/java/com/example/g8backend/controller/AuthController.java
@@ -2,7 +2,9 @@
 
 import com.example.g8backend.dto.UserRegisterDTO;
 import com.example.g8backend.entity.User;
+import com.example.g8backend.entity.UserStats;
 import com.example.g8backend.service.IUserService;
+import com.example.g8backend.service.IUserStatsService;
 import com.example.g8backend.util.JwtUtil;
 import com.example.g8backend.util.mailUtil;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +24,8 @@
     @Autowired
     private IUserService userService;
     @Autowired
+    private IUserStatsService userStatsService;
+    @Autowired
     private mailUtil mailUtil;
     @Autowired
     private PasswordEncoder passwordEncoder;
@@ -68,6 +72,12 @@
         user.setPasskey(UUID.randomUUID().toString().replace("-", ""));
         userService.save(user);
 
+        // 保存用户统计用户的上传量与下载量
+        UserStats userStats = new UserStats();
+        userStats.setUserId(user.getUserId());
+        userStats.setPasskey(user.getPasskey());
+        userStatsService.save(userStats);
+
         return ResponseEntity.ok("注册成功");
     }
 
diff --git a/src/main/java/com/example/g8backend/entity/UserStats.java b/src/main/java/com/example/g8backend/entity/UserStats.java
new file mode 100644
index 0000000..64225ee
--- /dev/null
+++ b/src/main/java/com/example/g8backend/entity/UserStats.java
@@ -0,0 +1,18 @@
+package com.example.g8backend.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("user_stats")
+public class UserStats {
+    @TableId
+    private Long userId;
+    private String passkey;
+    private double total_upload;
+    private double total_download;
+    private LocalDateTime last_update_time;
+}
diff --git a/src/main/java/com/example/g8backend/mapper/UserStatsMapper.java b/src/main/java/com/example/g8backend/mapper/UserStatsMapper.java
new file mode 100644
index 0000000..1e23c69
--- /dev/null
+++ b/src/main/java/com/example/g8backend/mapper/UserStatsMapper.java
@@ -0,0 +1,13 @@
+package com.example.g8backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.g8backend.entity.UserStats;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface UserStatsMapper extends BaseMapper<UserStats> {
+    void incrementTraffic(@Param("passkey") String passkey,
+                          @Param("deltaUploaded") double deltaUploaded,
+                          @Param("deltaDownloaded") double deltaDownloaded);
+}
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/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/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 {
+}
diff --git a/src/main/resources/mapper/UserStatsMapper.xml b/src/main/resources/mapper/UserStatsMapper.xml
new file mode 100644
index 0000000..3560a39
--- /dev/null
+++ b/src/main/resources/mapper/UserStatsMapper.xml
@@ -0,0 +1,12 @@
+<?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.UserStatsMapper">
+    <update id="incrementTraffic">
+        UPDATE user_stats
+        SET total_upload = total_upload + #{deltaUploaded},
+            total_download = total_download + #{deltaDownloaded}
+        WHERE passkey = #{passkey}
+    </update>
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 5e5dd43..dc59ec1 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -9,6 +9,16 @@
   passkey VARCHAR(255) NOT NULL UNIQUE
 );
 
+CREATE TABLE IF NOT EXISTS `user_stats` (
+    user_id INT PRIMARY KEY,
+    passkey VARCHAR(255) NOT NULL UNIQUE,
+    total_upload FLOAT NOT NULL DEFAULT 0,
+    total_download FLOAT NOT NULL DEFAULT 0,
+    last_update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    FOREIGN KEY (user_id) REFERENCES users(user_id),
+    FOREIGN KEY (passkey) REFERENCES users(passkey)
+);
+
 CREATE TABLE IF NOT EXISTS `torrents` (
     torrent_id INT AUTO_INCREMENT PRIMARY KEY,
     user_id INT NOT NULL,