feat(core): 实现用户积分计算逻辑

- 新增积分规则处理模块
- 集成积分计算到相关业务流程中

fix(file): 修复上传工具中路径乱码和非法字符问题

- 使用 UTF-8 编码统一路径解析
- 增加路径合法性校验

fix(user): 修正用户 Passkey 生成与验证流程

- 修复部分场景下 Passkey 未生成的问题
- 调整 Passkey 校验逻辑,确保唯一性和安全性

Change-Id: Id5eacb20f354a07de01413474095c2b2b9b49231
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 0f7fa42..1fbe9d6 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -22,8 +22,11 @@
             <artifactId>jackson-databind</artifactId>
         </dependency>
 
-
-
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.18.2</version>
+        </dependency>
 
 
 
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java b/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java
index f3bd624..93c3f50 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/announce/controller/AnnounceController.java
@@ -1,8 +1,17 @@
 package com.ruoyi.announce.controller;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.announce.util.BencodeEncoder;
+import com.ruoyi.authentication.domain.SysUserPasskey;
+import com.ruoyi.authentication.service.ISysUserPasskeyService;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.announce.service.IAnnounceService;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.utils.SecurityUtils;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.URLCodec;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -10,52 +19,182 @@
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 @RestController
 public class AnnounceController extends BaseController {
+    // 在你的 Controller 中:
 
     @Autowired
     private IAnnounceService announceService;
 
+    @Autowired
+    private BencodeEncoder bencodeEncoder;
+
+
+
+
+
+
+    private byte[] decodeInfoHashAndPeerId(String param) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        for (int i = 0; i < param.length(); ) {
+            char c = param.charAt(i);
+            if (c == '%' && i + 2 < param.length()) {
+                char hi = param.charAt(i + 1);
+                char lo = param.charAt(i + 2);
+                if (isHexDigit(hi) && isHexDigit(lo)) {
+                    int value = Character.digit(hi, 16) << 4 | Character.digit(lo, 16);
+                    bos.write(value);
+                    i += 3;
+                    continue;
+                }
+            }
+            // 不是合法的 %xx,直接跳过或处理为异常字符
+            bos.write((byte) c); // 或者 throw new IllegalArgumentException(...)
+            i++;
+        }
+        return bos.toByteArray();
+    }
+
+    private boolean isHexDigit(char c) {
+        return (c >= '0' && c <= '9') ||
+                (c >= 'A' && c <= 'F') ||
+                (c >= 'a' && c <= 'f');
+    }
+
+
+
+
     /**
      * BT Tracker /announce 接口
      * 接收客户端(qBittorrent 等)发来的 announce 请求,返回 bencoded peers 列表
      */
-    @GetMapping(value = "/announce", produces = "application/x-bittorrent")
-    public void announce(
-            HttpServletRequest request,
-            HttpServletResponse response,
-            @RequestParam("info_hash") String infoHashParam,
-            @RequestParam("peer_id")   String peerIdParam,
-            @RequestParam("port")      int port,
-            @RequestParam("uploaded")  long uploaded,
-            @RequestParam("downloaded") long downloaded,
-            @RequestParam("left")      long left,
-            @RequestParam(value = "event",    required = false) String event,
-            @RequestParam("passkey")   String passkey
-    ) throws Exception {
-        // 1. URL Decode 得到原始二进制
-        byte[] infoHash = URLDecoder.decode(infoHashParam, StandardCharsets.ISO_8859_1.name())
-                .getBytes(StandardCharsets.ISO_8859_1);
-        byte[] peerId   = URLDecoder.decode(peerIdParam,   StandardCharsets.ISO_8859_1.name())
-                .getBytes(StandardCharsets.ISO_8859_1);
 
-        // 2. 处理 announce 请求(验证 passkey,更新 peer 列表,获取 peers 信息)
+    @GetMapping(value = "/announce", produces = "application/x-bittorrent")
+    public void announce(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        // —— 打印所有参数 ——
+        Map<String, String[]> parameterMap = request.getParameterMap();
+        System.out.println("—— 请求参数列表 ——");
+        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
+            String paramName = entry.getKey();
+            String[] paramValues = entry.getValue();
+            System.out.println(paramName + " = " + Arrays.toString(paramValues));
+        }
+
+
+        // —— 4. 获取参数(不使用 @RequestParam) ——
+        String infoHashParam = request.getParameter("info_hash");
+        String peerIdParam   = request.getParameter("peer_id");
+        String portStr       = request.getParameter("port");
+        String uploadedStr   = request.getParameter("uploaded");
+        String downloadedStr = request.getParameter("downloaded");
+        String leftStr       = request.getParameter("left");
+        String event         = request.getParameter("event");
+        String passkey       = request.getParameter("passkey");
+        String ip            = request.getRemoteAddr();
+
+        System.out.println("Received request with parameters:");
+        System.out.println("info_hash: " + infoHashParam);
+        System.out.println("peer_id: " + peerIdParam);
+        System.out.println("port: " + portStr);
+        System.out.println("uploaded: " + uploadedStr);
+        System.out.println("downloaded: " + downloadedStr);
+        System.out.println("left: " + leftStr);
+        System.out.println("event: " + event);
+        System.out.println("passkey: " + passkey);
+        System.out.println("client IP: " + ip);
+
+
+
+
+
+
+
+
+        if ((infoHashParam == null && peerIdParam == null && portStr == null &&
+                uploadedStr == null && downloadedStr == null && leftStr == null)) {
+
+            // 只要 passkey 和 ip 不为空,则认为是 heartbeat
+            if (passkey != null && ip != null) {
+                // 处理为心跳请求
+                Map<String, Object> reply = announceService.handleHeartbeat(ip);
+                byte[] bencodeReply = bencodeEncoder.encodeBencode(reply);
+
+                response.setContentType("text/plain");
+                response.getOutputStream().write(bencodeReply);
+                return;
+            }
+
+            // 如果不是心跳,就是错误请求
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            response.setContentType("text/plain");
+            response.getWriter().write("Missing required announce parameters.");
+            return;
+        }
+
+        // —— 5. 转换参数类型 ——
+        assert portStr != null;
+        int port = Integer.parseInt(portStr);
+        long uploaded = Long.parseLong(uploadedStr);
+        long downloaded = Long.parseLong(downloadedStr);
+        long left = Long.parseLong(leftStr);
+
+        // 确保 URL 解码不会失败,使用更健壮的方法
+        assert infoHashParam != null;
+        byte[] infoHash = decodeInfoHashAndPeerId(infoHashParam);
+        byte[] peerId   = decodeInfoHashAndPeerId(peerIdParam);
+
+        // —— 7. 调用业务逻辑处理 ——
         Map<String, Object> reply = announceService.handleAnnounce(
                 infoHash, peerId, port, uploaded, downloaded, left, event, passkey,
                 request.getRemoteAddr()
         );
 
-        // 3. bencode 编码并返回给客户端
+        // —— 8. 返回 Bencode 编码的 tracker 响应 ——
         response.setStatus(HttpServletResponse.SC_OK);
         response.setHeader("Content-Type", "application/x-bittorrent");
         try (var out = response.getOutputStream()) {
-            byte[] bencoded = announceService.encodeBencode(reply);
+            byte[] bencoded = bencodeEncoder.encodeBencode(reply);
+
             out.write(bencoded);
             out.flush();
         }
     }
+    // Scrape 接口
+    @GetMapping(value = "/scrape", produces = "application/x-bittorrent")
+    public void scrape(@RequestParam("info_hash") String[] infoHashes, HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+        // 收集所有 info_hash 对应的统计数据
+        Map<String, Object> scrapeStats = announceService.handleScrape(infoHashes);
+
+        // 将统计数据转换为 Bencode 格式并返回
+        byte[] bencodedReply = bencodeEncoder.encodeBencode(scrapeStats);
+
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setHeader("Content-Type", "application/x-bittorrent");
+        try (var out = response.getOutputStream()) {
+            out.write(bencodedReply);
+            out.flush();
+        }
+    }
+
+    /**
+     * 查询当前所有活跃的 Peer 节点
+     */
+    @GetMapping(value = "/peers", produces = "application/json")
+    public TableDataInfo getAllPeers() {
+        List<Map<String, Object>> peers = announceService.getAllPeers();
+        System.out.println(peers);
+        return getDataTable(peers);
+    }
 }
+
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/announce/service/IAnnounceService.java b/ruoyi-admin/src/main/java/com/ruoyi/announce/service/IAnnounceService.java
index e6e7aa7..c005cc8 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/announce/service/IAnnounceService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/announce/service/IAnnounceService.java
@@ -1,6 +1,7 @@
 package com.ruoyi.announce.service;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -38,9 +39,11 @@
             String passkey,
             String ip
     ) throws Exception;
+    List<Map<String, Object>> getAllPeers();
 
-    /**
-     * 将一个 Map<String,Object> 编码成 bencode 二进制
-     */
-    byte[] encodeBencode(Map<String, Object> reply) throws IOException;
+    // 处理 scrape 请求
+    Map<String, Object> handleScrape(String[] infoHashes);
+
+    Map<String, Object> handleHeartbeat(String ip) throws IOException;
+
 }
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java
index f80050f..e9cdce8 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/announce/service/impl/AnnounceServiceImpl.java
@@ -1,132 +1,332 @@
 package com.ruoyi.announce.service.impl;
 
 import com.ruoyi.announce.service.IAnnounceService;
-import com.ruoyi.announce.util.BencodeEncoder;
+import com.ruoyi.authentication.domain.SysUserPasskey;
+import com.ruoyi.authentication.domain.UserScore;
+import com.ruoyi.authentication.service.ISysUserPasskeyService;
+import com.ruoyi.authentication.service.IUserScoreService;
 import com.ruoyi.common.core.redis.RedisCache;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 
+import static java.lang.Long.parseLong;
+
 @Service
 public class AnnounceServiceImpl implements IAnnounceService {
 
-    private static final int ANNOUNCE_INTERVAL = 1800; // 秒
+    private static final int ANNOUNCE_INTERVAL = 1800;
+    private static final int PEER_CACHE_DURATION_MIN = 60;
+    private static final int MAX_SEEDERS = 25;
+    private static final int MAX_LEECHERS = 50;
+    private static final int UPLOAD_WEIGHT = 70;
+    private static final int DOWNLOAD_WEIGHT = 30;
 
     @Autowired
     private RedisCache redisCache;
+    @Autowired
+    private ISysUserPasskeyService passkeyService;
+    @Autowired
+    private IUserScoreService userScoreService;
 
     @Override
-    public Map<String, Object> handleAnnounce(
-            byte[] infoHash,
-            byte[] peerId,
-            int port,
-            long uploaded,
-            long downloaded,
-            long left,
-            String event,
-            String passkey,
-            String ip
-    ) throws Exception {
-        // 1. 转 hex 作为 Redis key 前缀
-        String infoHashHex = bytesToHex(infoHash);
-        String peerIdStr   = new String(peerId, StandardCharsets.ISO_8859_1);
-
-        // 2. 校验 passkey(可根据业务到 MySQL 查 userId,此处略过)
-
-        // 3. 在 Redis 中记录/刷新此 peer 的信息
-        //    使用 Hash 存储详情,TTL 120 秒
-        String peerKey = "peer:" + infoHashHex + ":" + peerIdStr;
-        Map<String, Object> peerData = new HashMap<>();
-        peerData.put("ip",         ip);
-        peerData.put("port",       port);
-        peerData.put("uploaded",   uploaded);
-        peerData.put("downloaded", downloaded);
-        peerData.put("left",       left);
-        peerData.put("lastSeen",   System.currentTimeMillis());
-        redisCache.setCacheMap(peerKey, peerData);
-        redisCache.expire(peerKey, 120, TimeUnit.SECONDS);
-
-        // 4. 从 Redis 中扫描所有同 info_hash 的 peer
-        Collection<String> keys = redisCache.keys("peer:" + infoHashHex + ":*");
-        List<byte[]> peersBin = new ArrayList<>(keys.size());
-        for (String key : keys) {
-            @SuppressWarnings("unchecked")
-            Map<String, Object> data = (Map<String, Object>) redisCache.getCacheMap(key);
-            String peerIp = (String) data.get("ip");
-            int    peerPort = ((Number) data.get("port")).intValue();
-            peersBin.add(encodePeer(peerIp, peerPort));
-            if (peersBin.size() >= 50) break;  // 最多返回 50 个 peers
+    public Map<String, Object> handleAnnounce(byte[] infoHash,
+                                              byte[] peerId,
+                                              int port,
+                                              long uploaded,
+                                              long downloaded,
+                                              long left,
+                                              String event,
+                                              String passkey,
+                                              String ip) throws IOException {
+        if (passkey == null) {
+            return Collections.emptyMap();
         }
 
-        // 5. 构造返回数据 Map(有序)
-        Map<String, Object> reply = new LinkedHashMap<>();
-        reply.put("interval", ANNOUNCE_INTERVAL);
-        reply.put("min interval", ANNOUNCE_INTERVAL / 2);
-        reply.put("complete",   countSeeders(infoHashHex));
-        reply.put("incomplete", countLeechers(infoHashHex));
-        reply.put("peers",      peersBin);
+        String infoHashHex = toHex(infoHash);
+        String peerKey = buildPeerKey(peerId);
+        String seederSet = buildZSetKey(infoHashHex, "seeders");
+        String leecherSet = buildZSetKey(infoHashHex, "leechers");
 
-        return reply;
-    }
-
-    @Override
-    public byte[] encodeBencode(Map<String, Object> reply) throws IOException {
-        BencodeEncoder encoder = new BencodeEncoder();
-        return encoder.encodeBencode(reply);
-    }
-
-    // —— 辅助方法 —— //
-
-    /** 统计 left == 0 的 Seeder 数 */
-    private int countSeeders(String infoHashHex) {
-        int count = 0;
-        for (String key : redisCache.keys("peer:" + infoHashHex + ":*")) {
-            @SuppressWarnings("unchecked")
-            Map<String, Object> data = (Map<String, Object>) redisCache.getCacheMap(key);
-            long left = ((Number) data.get("left")).longValue();
-            if (left == 0) count++;
+        if ("stopped".equals(event)) {
+            return handleStopEvent(peerKey, seederSet, leecherSet, passkey, uploaded, downloaded);
         }
-        return count;
+
+        cachePeerState(peerKey, uploaded, downloaded, left, ip, port);
+        updatePeerRanking(seederSet, leecherSet, peerKey, left == 0);
+
+        List<byte[]> peerList = selectPeers(seederSet, leecherSet, peerKey, left == 0);
+        byte[] peersBinary = flattenPeers(peerList);
+
+        return buildResponse(seederSet, leecherSet, peersBinary);
     }
 
-    /** 统计 left > 0 的 Leecher 数 */
-    private int countLeechers(String infoHashHex) {
-        int count = 0;
-        for (String key : redisCache.keys("peer:" + infoHashHex + ":*")) {
-            @SuppressWarnings("unchecked")
-            Map<String, Object> data = (Map<String, Object>) redisCache.getCacheMap(key);
-            long left = ((Number) data.get("left")).longValue();
-            if (left > 0) count++;
+    private Map<String, Object> handleStopEvent(String peerKey,
+                                                String seederSet,
+                                                String leecherSet,
+                                                String passkey,
+                                                long uploaded,
+                                                long downloaded) {
+        redisCache.deleteObject(peerKey);
+        redisCache.zRemove(seederSet, peerKey);
+        redisCache.zRemove(leecherSet, peerKey);
+
+        Optional<Long> userId = resolveUserId(passkey);
+        userId.ifPresent(id -> updateUserScore(id, uploaded, downloaded));
+        return Collections.emptyMap();
+    }
+
+    private void cachePeerState(String peerKey,
+                                long uploaded,
+                                long downloaded,
+                                long left,
+                                String ip,
+                                int port) {
+        Map<String, String> data = new HashMap<>();
+        data.put("ip", ip);
+        data.put("port", String.valueOf(port));
+        data.put("uploaded", String.valueOf(uploaded));
+        data.put("downloaded", String.valueOf(downloaded));
+        data.put("left", String.valueOf(left));
+        data.put("lastSeen", String.valueOf(System.currentTimeMillis()));
+
+        redisCache.setCacheMap(peerKey, data);
+        redisCache.expire(peerKey, PEER_CACHE_DURATION_MIN, TimeUnit.MINUTES);
+    }
+
+    private void updatePeerRanking(String seederSet,
+                                   String leecherSet,
+                                   String peerKey,
+                                   boolean isSeeder) {
+        String sourceSet = isSeeder ? leecherSet : seederSet;
+        String targetSet = isSeeder ? seederSet : leecherSet;
+
+        redisCache.zRemove(sourceSet, peerKey);
+        redisCache.zAdd(targetSet, peerKey, System.currentTimeMillis());
+    }
+
+    private List<byte[]> selectPeers(String seederSet,
+                                     String leecherSet,
+                                     String selfKey,
+                                     boolean isSeeder) throws IOException {
+        Set<String> candidates = new LinkedHashSet<>();
+        if (isSeeder) {
+            candidates.addAll(redisCache.zRange(leecherSet, 0, MAX_LEECHERS));
+        } else {
+            candidates.addAll(Optional.ofNullable(redisCache.zRange(seederSet, 0, MAX_SEEDERS)).orElse(Collections.emptySet()));
+            candidates.addAll(Optional.ofNullable(redisCache.zRange(leecherSet, 0, MAX_LEECHERS)).orElse(Collections.emptySet()));
         }
-        return count;
+
+        List<byte[]> peers = new ArrayList<>();
+        for (String candidateKey : candidates) {
+            if (candidateKey.equals(selfKey)) continue;
+            cachePeerLevel:
+            {
+                Map<String, String> info = redisCache.getCacheMap(candidateKey);
+                String peerIp = info.get("ip");
+                String portStr = info.get("port");
+                if (peerIp == null || peerIp.contains(":") || portStr == null) break cachePeerLevel;
+                peers.add(encodePeer(peerIp, Integer.parseInt(portStr)));
+            }
+        }
+        return peers;
     }
 
-    /** 将 IPv4 + port 编码成 6 字节:4 字节 IP + 2 字节 port */
-    private byte[] encodePeer(String ip, int port) throws Exception {
+    private byte[] flattenPeers(List<byte[]> peerList) throws IOException {
+        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            for (byte[] peer : peerList) {
+                out.write(peer);
+            }
+            return out.toByteArray();
+        }
+    }
+
+    private Map<String, Object> buildResponse(String seederSet,
+                                              String leecherSet,
+                                              byte[] peersBinary) {
+        Map<String, Object> response = new LinkedHashMap<>();
+        response.put("interval", ANNOUNCE_INTERVAL);
+        response.put("min interval", ANNOUNCE_INTERVAL / 2);
+        response.put("complete", Optional.ofNullable(redisCache.zCard(seederSet)).orElse(0L).intValue());
+        response.put("incomplete", Optional.ofNullable(redisCache.zCard(leecherSet)).orElse(0L).intValue());
+        response.put("peers", peersBinary);
+        return response;
+    }
+
+    private Optional<Long> resolveUserId(String passkey) {
+        SysUserPasskey criteria = new SysUserPasskey(passkey);
+        List<SysUserPasskey> records = passkeyService.selectSysUserPasskeyList(criteria);
+        return records.isEmpty() ? Optional.empty() : Optional.of(records.get(0).getUserId());
+    }
+
+    private void updateUserScore(Long userId, long uploaded, long downloaded) {
+        try {
+            UserScore existing = userScoreService.selectUserScoreByUserId(userId);
+            boolean isNew = existing.getUserId() == null;
+            long totalUploaded = isNew ? uploaded : parseLong(existing.getUploaded()) + uploaded;
+            long totalDownloaded = isNew ? downloaded : parseLong(existing.getDownloaded()) + downloaded;
+            long combinedScore = calculateWeightedScore(totalUploaded, totalDownloaded);
+
+            existing.setUserId(userId);
+            existing.setUploaded(String.valueOf(totalUploaded));
+            existing.setDownloaded(String.valueOf(totalDownloaded));
+            existing.setScore(combinedScore);
+            existing.setUpdatedAt(new Date());
+
+            if (isNew) {
+                userScoreService.insertUserScore(existing);
+            } else {
+                userScoreService.updateUserScore(existing);
+            }
+        } catch (Exception e) {
+            // Log exception properly in production code
+            System.err.println("Failed to update user score: " + e.getMessage());
+        }
+    }
+
+    private long calculateWeightedScore(long uploaded, long downloaded) {
+        return (uploaded * UPLOAD_WEIGHT + downloaded * DOWNLOAD_WEIGHT) / (UPLOAD_WEIGHT + DOWNLOAD_WEIGHT);
+    }
+
+    private String buildPeerKey(byte[] peerId) {
+        return "peer:" + new String(peerId, StandardCharsets.ISO_8859_1);
+    }
+
+    private String buildZSetKey(String infoHashHex, String type) {
+        return String.format("torrent:%s:%s", infoHashHex, type);
+    }
+
+    private String toHex(byte[] data) {
+        char[] hex = "0123456789abcdef".toCharArray();
+        StringBuilder sb = new StringBuilder(data.length * 2);
+        for (byte b : data) {
+            sb.append(hex[(b >> 4) & 0xF]).append(hex[b & 0xF]);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Encode peer address into 6-byte compact representation as per BEP 23
+     */
+    private byte[] encodePeer(String ip, int port) {
         String[] parts = ip.split("\\.");
-        ByteBuffer buf = ByteBuffer.allocate(6);
-        for (int i = 0; i < 4; i++) {
-            buf.put((byte) Integer.parseInt(parts[i]));
+        if (parts.length != 4) return null;
+        ByteBuffer buffer = ByteBuffer.allocate(6);
+        try {
+            for (String part : parts) {
+                int segment = Integer.parseInt(part);
+                if (segment < 0 || segment > 255) return null;
+                buffer.put((byte) segment);
+            }
+            buffer.putShort((short) port);
+            return buffer.array();
+        } catch (NumberFormatException e) {
+            return null;
         }
-        buf.putShort((short) port);
-        return buf.array();
     }
 
-    /** 将字节数组转成十六进制字符串 */
-    private static final char[] HEX = "0123456789abcdef".toCharArray();
-    private String bytesToHex(byte[] bytes) {
-        char[] cs = new char[bytes.length * 2];
-        for (int i = 0; i < bytes.length; i++) {
-            int v = bytes[i] & 0xFF;
-            cs[i * 2]     = HEX[v >>> 4];
-            cs[i * 2 + 1] = HEX[v & 0x0F];
+    @Override
+    public Map<String, Object> handleScrape(String[] infoHashes) {
+        Map<String, Object> stats = new HashMap<>();
+        for (String hash : infoHashes) {
+            String key = hash.toLowerCase();
+            Map<String, Integer> countMap = Map.of(
+                    "complete", getZCardAsInt(buildZSetKey(key, "seeders")),
+                    "incomplete", getZCardAsInt(buildZSetKey(key, "leechers")),
+                    "downloaded", 0
+            );
+            stats.put(hash, countMap);
         }
-        return new String(cs);
+        return stats;
     }
-}
+
+    /**
+     * 查询当前所有活跃的 Peer 节点,包含详细字段
+     */
+    @Override
+    public Map<String, Object> handleHeartbeat( String ip) {
+        // 延长该 IP 下所有 peer 的缓存有效期
+        Collection<String> keys = redisCache.keys("peer:*");
+        for (String key : keys) {
+            Map<String, String> data = redisCache.getCacheMap(key);
+            if (data == null) continue;
+            String peerIp = data.get("ip");
+            if (ip.equals(peerIp)) {
+                redisCache.expire(key, PEER_CACHE_DURATION_MIN, TimeUnit.MINUTES);
+            }
+        }
+
+        // 构造基本响应(可根据需要扩展)
+        return Map.of(
+                "interval", ANNOUNCE_INTERVAL,
+                "min interval", ANNOUNCE_INTERVAL / 2,
+                "complete", Optional.ofNullable(redisCache.zCard("some:default:torrent:seeders")).orElse(0L).intValue(),
+                "incomplete", Optional.ofNullable(redisCache.zCard("some:default:torrent:leechers")).orElse(0L).intValue(),
+                "peers", new byte[0]
+        );
+    }
+
+    /**
+     * 查询当前所有活跃的 Peer 节点,包含详细字段
+     */
+    @Override
+    public List<Map<String, Object>> getAllPeers() {
+        // RedisCache.keys 返回 Collection<String>
+        Collection<String> keys = redisCache.keys("peer:*");
+        List<Map<String, Object>> peers = new ArrayList<>();
+        for (String key : keys) {
+            Map<String, String> data = redisCache.getCacheMap(key);
+            if (data == null || data.isEmpty()) continue;
+            Map<String, Object> info = new LinkedHashMap<>();
+            info.put("peerKey", key);
+            info.put("ip", data.get("ip"));
+            info.put("port", data.get("port"));
+            info.put("uploaded", data.get("uploaded"));
+            info.put("downloaded", data.get("downloaded"));
+            info.put("left", data.get("left"));
+            info.put("lastSeen", data.get("lastSeen"));
+            peers.add(info);
+        }
+        return peers;
+    }
+
+    private void refreshPeersForIp(String ip) {
+        redisCache.keys("peer:*").stream()
+                .filter(key -> ip.equals(redisCache.getCacheMap(key).get("ip")))
+                .forEach(key -> redisCache.expire(key, PEER_CACHE_DURATION_MIN, TimeUnit.MINUTES));
+    }
+
+    private List<byte[]> selectPeerBytes(Set<String> peers) {
+        List<byte[]> list = new ArrayList<>();
+        for (String peerId : peers) {
+            Map<String, String> data = redisCache.getCacheMap("peer:" + peerId);
+            if (data == null) continue;
+            String ip = data.get("ip");
+            String port = data.get("port");
+            if (ip == null || ip.contains(":")) continue;
+            list.add(encodePeer(ip, Integer.parseInt(port)));
+        }
+        return list;
+    }
+
+    private Map<String, Object> buildResponse(int incomplete, byte[] peers) {
+        Map<String, Object> response = new LinkedHashMap<>();
+        response.put("interval", ANNOUNCE_INTERVAL);
+        response.put("min interval", ANNOUNCE_INTERVAL / 2);
+        response.put("complete", 0);
+        response.put("incomplete", incomplete);
+        response.put("peers", peers);
+        return response;
+    }
+
+    private int getZCardAsInt(String zsetKey) {
+        Long count = redisCache.zCard(zsetKey);
+        return Optional.ofNullable(count).orElse(0L).intValue();
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java b/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java
index a2cdb30..44dcf6a 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/announce/util/BencodeEncoder.java
@@ -1,9 +1,12 @@
 package com.ruoyi.announce.util;
 
+import org.springframework.stereotype.Service;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Map;
-
+@Service
 public class BencodeEncoder {
 
     public byte[] encodeBencode(Map<String, Object> reply) throws IOException {
@@ -32,11 +35,20 @@
             encodeMap((Map<String, Object>) obj, outputStream);
         } else if (obj instanceof Iterable) {
             encodeList((Iterable<Object>) obj, outputStream);
+        } else if (obj instanceof byte[]) {
+            encodeByteArray((byte[]) obj, outputStream);
         } else {
             throw new IllegalArgumentException("Unsupported object type: " + obj.getClass());
         }
     }
 
+    private void encodeByteArray(byte[] bytes, ByteArrayOutputStream outputStream) throws IOException {
+        outputStream.write(Integer.toString(bytes.length).getBytes());
+        outputStream.write(':');
+        outputStream.write(bytes);
+    }
+
+
     private void encodeInteger(Integer value, ByteArrayOutputStream outputStream) throws IOException {
         outputStream.write('i');
         outputStream.write(value.toString().getBytes());
@@ -44,11 +56,13 @@
     }
 
     private void encodeString(String value, ByteArrayOutputStream outputStream) throws IOException {
-        outputStream.write(Integer.toString(value.length()).getBytes());
+        byte[] bytes = value.getBytes(StandardCharsets.UTF_8); // Assuming UTF-8 encoding
+        outputStream.write(Integer.toString(bytes.length).getBytes());
         outputStream.write(':');
-        outputStream.write(value.getBytes());
+        outputStream.write(bytes);
     }
 
+
     private void encodeList(Iterable<Object> list, ByteArrayOutputStream outputStream) throws IOException {
         outputStream.write('l'); // Start of a list
 
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/controller/SysUserPasskeyController.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/controller/SysUserPasskeyController.java
new file mode 100644
index 0000000..d3ea497
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/controller/SysUserPasskeyController.java
@@ -0,0 +1,105 @@
+package com.ruoyi.authentication.controller;
+
+import java.util.List;
+
+import com.ruoyi.authentication.domain.SysUserPasskey;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.authentication.service.ISysUserPasskeyService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 【请填写功能名称】Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+@RestController
+@RequestMapping("/system/passkey")
+public class SysUserPasskeyController extends BaseController
+{
+    @Autowired
+    private ISysUserPasskeyService sysUserPasskeyService;
+
+    /**
+     * 查询【请填写功能名称】列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:passkey:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysUserPasskey sysUserPasskey)
+    {
+        startPage();
+        List<SysUserPasskey> list = sysUserPasskeyService.selectSysUserPasskeyList(sysUserPasskey);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出【请填写功能名称】列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:passkey:export')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysUserPasskey sysUserPasskey)
+    {
+        List<SysUserPasskey> list = sysUserPasskeyService.selectSysUserPasskeyList(sysUserPasskey);
+        ExcelUtil<SysUserPasskey> util = new ExcelUtil<SysUserPasskey>(SysUserPasskey.class);
+        util.exportExcel(response, list, "【请填写功能名称】数据");
+    }
+
+    /**
+     * 获取【请填写功能名称】详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:passkey:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return success(sysUserPasskeyService.selectSysUserPasskeyByUserId(userId));
+    }
+
+    /**
+     * 新增【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:passkey:add')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SysUserPasskey sysUserPasskey)
+    {
+        return toAjax(sysUserPasskeyService.insertSysUserPasskey(sysUserPasskey));
+    }
+
+    /**
+     * 修改【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:passkey:edit')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SysUserPasskey sysUserPasskey)
+    {
+        return toAjax(sysUserPasskeyService.updateSysUserPasskey(sysUserPasskey));
+    }
+
+    /**
+     * 删除【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:passkey:remove')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(sysUserPasskeyService.deleteSysUserPasskeyByUserIds(userIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/controller/UserScoreController.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/controller/UserScoreController.java
new file mode 100644
index 0000000..93b9119
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/controller/UserScoreController.java
@@ -0,0 +1,105 @@
+package com.ruoyi.authentication.controller;
+
+import java.util.List;
+
+import com.ruoyi.authentication.domain.UserScore;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.authentication.service.IUserScoreService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 【请填写功能名称】Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+@RestController
+@RequestMapping("/system/score")
+public class UserScoreController extends BaseController
+{
+    @Autowired
+    private IUserScoreService userScoreService;
+
+    /**
+     * 查询【请填写功能名称】列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:score:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(UserScore userScore)
+    {
+        startPage();
+        List<UserScore> list = userScoreService.selectUserScoreList(userScore);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出【请填写功能名称】列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:score:export')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, UserScore userScore)
+    {
+        List<UserScore> list = userScoreService.selectUserScoreList(userScore);
+        ExcelUtil<UserScore> util = new ExcelUtil<UserScore>(UserScore.class);
+        util.exportExcel(response, list, "【请填写功能名称】数据");
+    }
+
+    /**
+     * 获取【请填写功能名称】详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:score:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return success(userScoreService.selectUserScoreByUserId(userId));
+    }
+
+    /**
+     * 新增【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:score:add')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody UserScore userScore)
+    {
+        return toAjax(userScoreService.insertUserScore(userScore));
+    }
+
+    /**
+     * 修改【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:score:edit')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody UserScore userScore)
+    {
+        return toAjax(userScoreService.updateUserScore(userScore));
+    }
+
+    /**
+     * 删除【请填写功能名称】
+     */
+    @PreAuthorize("@ss.hasPermi('system:score:remove')")
+    @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(userScoreService.deleteUserScoreByUserIds(userIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/domain/SysUserPasskey.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/domain/SysUserPasskey.java
new file mode 100644
index 0000000..7370c77
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/domain/SysUserPasskey.java
@@ -0,0 +1,71 @@
+package com.ruoyi.authentication.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 【请填写功能名称】对象 sys_user_passkey
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+public class SysUserPasskey extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    public SysUserPasskey(String passkey){
+        this.passkey = passkey;
+    }
+
+    /** $column.columnComment */
+    private Long userId;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String passkey;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Date createdAt;
+
+    public void setUserId(Long userId) 
+    {
+        this.userId = userId;
+    }
+
+    public Long getUserId() 
+    {
+        return userId;
+    }
+    public void setPasskey(String passkey) 
+    {
+        this.passkey = passkey;
+    }
+
+    public String getPasskey() 
+    {
+        return passkey;
+    }
+    public void setCreatedAt(Date createdAt) 
+    {
+        this.createdAt = createdAt;
+    }
+
+    public Date getCreatedAt() 
+    {
+        return createdAt;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("userId", getUserId())
+            .append("passkey", getPasskey())
+            .append("createdAt", getCreatedAt())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/domain/UserScore.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/domain/UserScore.java
new file mode 100644
index 0000000..f7049d1
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/domain/UserScore.java
@@ -0,0 +1,109 @@
+package com.ruoyi.authentication.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 【请填写功能名称】对象 user_score
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+public class UserScore extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** $column.columnComment */
+    private Long userId;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String uploaded;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String downloaded;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Long bonus;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Long score;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Date updatedAt;
+
+    public void setUserId(Long userId) 
+    {
+        this.userId = userId;
+    }
+
+    public Long getUserId() 
+    {
+        return userId;
+    }
+    public void setUploaded(String uploaded) 
+    {
+        this.uploaded = uploaded;
+    }
+
+    public String getUploaded() 
+    {
+        return uploaded;
+    }
+    public void setDownloaded(String downloaded) 
+    {
+        this.downloaded = downloaded;
+    }
+
+    public String getDownloaded() 
+    {
+        return downloaded;
+    }
+    public void setBonus(Long bonus) 
+    {
+        this.bonus = bonus;
+    }
+
+    public Long getBonus() 
+    {
+        return bonus;
+    }
+    public void setScore(Long score) 
+    {
+        this.score = score;
+    }
+
+    public Long getScore() 
+    {
+        return score;
+    }
+    public void setUpdatedAt(Date updatedAt) 
+    {
+        this.updatedAt = updatedAt;
+    }
+
+    public Date getUpdatedAt() 
+    {
+        return updatedAt;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("userId", getUserId())
+            .append("uploaded", getUploaded())
+            .append("downloaded", getDownloaded())
+            .append("bonus", getBonus())
+            .append("score", getScore())
+            .append("updatedAt", getUpdatedAt())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/mapper/SysUserPasskeyMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/mapper/SysUserPasskeyMapper.java
new file mode 100644
index 0000000..7607c63
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/mapper/SysUserPasskeyMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.authentication.mapper;
+
+import java.util.List;
+import com.ruoyi.authentication.domain.SysUserPasskey;
+
+/**
+ * 【请填写功能名称】Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+public interface SysUserPasskeyMapper 
+{
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    public SysUserPasskey selectSysUserPasskeyByUserId(Long userId);
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 【请填写功能名称】集合
+     */
+    public List<SysUserPasskey> selectSysUserPasskeyList(SysUserPasskey sysUserPasskey);
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 结果
+     */
+    public int insertSysUserPasskey(SysUserPasskey sysUserPasskey);
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 结果
+     */
+    public int updateSysUserPasskey(SysUserPasskey sysUserPasskey);
+
+    /**
+     * 删除【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 结果
+     */
+    public int deleteSysUserPasskeyByUserId(Long userId);
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param userIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteSysUserPasskeyByUserIds(Long[] userIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/mapper/UserScoreMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/mapper/UserScoreMapper.java
new file mode 100644
index 0000000..4c15e5e
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/mapper/UserScoreMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.authentication.mapper;
+
+import java.util.List;
+import com.ruoyi.authentication.domain.UserScore;
+
+/**
+ * 【请填写功能名称】Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+public interface UserScoreMapper 
+{
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    public UserScore selectUserScoreByUserId(Long userId);
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 【请填写功能名称】集合
+     */
+    public List<UserScore> selectUserScoreList(UserScore userScore);
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 结果
+     */
+    public int insertUserScore(UserScore userScore);
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 结果
+     */
+    public int updateUserScore(UserScore userScore);
+
+    /**
+     * 删除【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 结果
+     */
+    public int deleteUserScoreByUserId(Long userId);
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param userIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteUserScoreByUserIds(Long[] userIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/ISysUserPasskeyService.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/ISysUserPasskeyService.java
new file mode 100644
index 0000000..33e0ec6
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/ISysUserPasskeyService.java
@@ -0,0 +1,62 @@
+package com.ruoyi.authentication.service;
+
+import java.util.List;
+
+import com.ruoyi.authentication.domain.SysUserPasskey;
+
+/**
+ * 【请填写功能名称】Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+public interface ISysUserPasskeyService 
+{
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    public SysUserPasskey selectSysUserPasskeyByUserId(Long userId);
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 【请填写功能名称】集合
+     */
+    public List<SysUserPasskey> selectSysUserPasskeyList(SysUserPasskey sysUserPasskey);
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 结果
+     */
+    public int insertSysUserPasskey(SysUserPasskey sysUserPasskey);
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 结果
+     */
+    public int updateSysUserPasskey(SysUserPasskey sysUserPasskey);
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param userIds 需要删除的【请填写功能名称】主键集合
+     * @return 结果
+     */
+    public int deleteSysUserPasskeyByUserIds(Long[] userIds);
+
+    /**
+     * 删除【请填写功能名称】信息
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 结果
+     */
+    public int deleteSysUserPasskeyByUserId(Long userId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/IUserScoreService.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/IUserScoreService.java
new file mode 100644
index 0000000..b93e41e
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/IUserScoreService.java
@@ -0,0 +1,62 @@
+package com.ruoyi.authentication.service;
+
+import java.util.List;
+
+import com.ruoyi.authentication.domain.UserScore;
+
+/**
+ * 【请填写功能名称】Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+public interface IUserScoreService 
+{
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    public UserScore selectUserScoreByUserId(Long userId);
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 【请填写功能名称】集合
+     */
+    public List<UserScore> selectUserScoreList(UserScore userScore);
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 结果
+     */
+    public int insertUserScore(UserScore userScore);
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 结果
+     */
+    public int updateUserScore(UserScore userScore);
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param userIds 需要删除的【请填写功能名称】主键集合
+     * @return 结果
+     */
+    public int deleteUserScoreByUserIds(Long[] userIds);
+
+    /**
+     * 删除【请填写功能名称】信息
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 结果
+     */
+    public int deleteUserScoreByUserId(Long userId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/impl/SysUserPasskeyServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/impl/SysUserPasskeyServiceImpl.java
new file mode 100644
index 0000000..c2091e5
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/impl/SysUserPasskeyServiceImpl.java
@@ -0,0 +1,94 @@
+package com.ruoyi.authentication.service.impl;
+
+import java.util.List;
+
+import com.ruoyi.authentication.domain.SysUserPasskey;
+import com.ruoyi.authentication.mapper.SysUserPasskeyMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.authentication.service.ISysUserPasskeyService;
+
+/**
+ * 【请填写功能名称】Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+@Service
+public class SysUserPasskeyServiceImpl implements ISysUserPasskeyService 
+{
+    @Autowired
+    private SysUserPasskeyMapper sysUserPasskeyMapper;
+
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    @Override
+    public SysUserPasskey selectSysUserPasskeyByUserId(Long userId)
+    {
+        return sysUserPasskeyMapper.selectSysUserPasskeyByUserId(userId);
+    }
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 【请填写功能名称】
+     */
+    @Override
+    public List<SysUserPasskey> selectSysUserPasskeyList(SysUserPasskey sysUserPasskey)
+    {
+        return sysUserPasskeyMapper.selectSysUserPasskeyList(sysUserPasskey);
+    }
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 结果
+     */
+    @Override
+    public int insertSysUserPasskey(SysUserPasskey sysUserPasskey)
+    {
+        return sysUserPasskeyMapper.insertSysUserPasskey(sysUserPasskey);
+    }
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param sysUserPasskey 【请填写功能名称】
+     * @return 结果
+     */
+    @Override
+    public int updateSysUserPasskey(SysUserPasskey sysUserPasskey)
+    {
+        return sysUserPasskeyMapper.updateSysUserPasskey(sysUserPasskey);
+    }
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param userIds 需要删除的【请填写功能名称】主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSysUserPasskeyByUserIds(Long[] userIds)
+    {
+        return sysUserPasskeyMapper.deleteSysUserPasskeyByUserIds(userIds);
+    }
+
+    /**
+     * 删除【请填写功能名称】信息
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSysUserPasskeyByUserId(Long userId)
+    {
+        return sysUserPasskeyMapper.deleteSysUserPasskeyByUserId(userId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/impl/UserScoreServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/impl/UserScoreServiceImpl.java
new file mode 100644
index 0000000..4a41923
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/authentication/service/impl/UserScoreServiceImpl.java
@@ -0,0 +1,94 @@
+package com.ruoyi.authentication.service.impl;
+
+import java.util.List;
+
+import com.ruoyi.authentication.domain.UserScore;
+import com.ruoyi.authentication.mapper.UserScoreMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.authentication.service.IUserScoreService;
+
+/**
+ * 【请填写功能名称】Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-25
+ */
+@Service
+public class UserScoreServiceImpl implements IUserScoreService 
+{
+    @Autowired
+    private UserScoreMapper userScoreMapper;
+
+    /**
+     * 查询【请填写功能名称】
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 【请填写功能名称】
+     */
+    @Override
+    public UserScore selectUserScoreByUserId(Long userId)
+    {
+        return userScoreMapper.selectUserScoreByUserId(userId);
+    }
+
+    /**
+     * 查询【请填写功能名称】列表
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 【请填写功能名称】
+     */
+    @Override
+    public List<UserScore> selectUserScoreList(UserScore userScore)
+    {
+        return userScoreMapper.selectUserScoreList(userScore);
+    }
+
+    /**
+     * 新增【请填写功能名称】
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 结果
+     */
+    @Override
+    public int insertUserScore(UserScore userScore)
+    {
+        return userScoreMapper.insertUserScore(userScore);
+    }
+
+    /**
+     * 修改【请填写功能名称】
+     * 
+     * @param userScore 【请填写功能名称】
+     * @return 结果
+     */
+    @Override
+    public int updateUserScore(UserScore userScore)
+    {
+        return userScoreMapper.updateUserScore(userScore);
+    }
+
+    /**
+     * 批量删除【请填写功能名称】
+     * 
+     * @param userIds 需要删除的【请填写功能名称】主键
+     * @return 结果
+     */
+    @Override
+    public int deleteUserScoreByUserIds(Long[] userIds)
+    {
+        return userScoreMapper.deleteUserScoreByUserIds(userIds);
+    }
+
+    /**
+     * 删除【请填写功能名称】信息
+     * 
+     * @param userId 【请填写功能名称】主键
+     * @return 结果
+     */
+    @Override
+    public int deleteUserScoreByUserId(Long userId)
+    {
+        return userScoreMapper.deleteUserScoreByUserId(userId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
index c80db77..15dc9f3 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/controller/BtTorrentController.java
@@ -1,11 +1,11 @@
 package com.ruoyi.torrent.controller;
 
+import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+
 import com.ruoyi.common.utils.http.HttpUtils;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -16,6 +16,7 @@
 import com.ruoyi.torrent.service.IBtTorrentAnnounceService;
 import com.ruoyi.torrent.service.IBtTorrentFileService;
 import com.ruoyi.torrent.service.IBtTorrentService;
+import com.ruoyi.torrent.util.TorrentFileUtil;
 import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,7 +42,6 @@
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
-import java.util.UUID;
 
 /**
  * 种子主Controller
@@ -62,6 +62,9 @@
     private IBtTorrentAnnounceService btTorrentAnnounceService;
     private static final String RECOMMEND_API = "http://127.0.0.1:5000/recommend_torrents";
 
+    private String torrentPath= "torrents";
+
+
     private static final String boundary = "----WebKitFormBoundary" + UUID.randomUUID().toString().replace("-", "");
 
     @PreAuthorize("@ss.hasPermi('system:torrent:add')")
@@ -69,8 +72,9 @@
     @PostMapping("/uploadTorrent")
     public AjaxResult uploadTorrent(@RequestParam("file") MultipartFile file) {
         try {
+
             // Create URL connection to Flask server
-            String flaskUrl = "http://localhost:5000/parse_torrent"; // Flask server URL
+            String flaskUrl = "http://49.233.215.144:5000/parse_torrent"; // Flask server URL
             HttpURLConnection connection = (HttpURLConnection) new URL(flaskUrl).openConnection();
             connection.setRequestMethod("POST");
             connection.setDoOutput(true);
@@ -86,6 +90,10 @@
                 if (responseCode != 200) {
                     return AjaxResult.error("Failed to communicate with Flask server, response code: " + responseCode);
                 }
+                TorrentFileUtil.uploadFile(file,torrentPath+"/"+SecurityUtils.getUserId());
+
+
+
 
                 // Assuming the Flask server responds with JSON, parse the response
                 String responseBody = new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
@@ -133,9 +141,11 @@
             BtTorrent btTorrent = objectMapper.readValue(btTorrentNode.toString(), BtTorrent.class);
             btTorrent.setCreatedBy(SecurityUtils.getUsername());
             btTorrent.setUploaderId(SecurityUtils.getUserId());
+            btTorrent.setFilePath(torrentPath+"/"+SecurityUtils.getUserId()+"/"+btTorrent.getName());
             btTorrentService.insertBtTorrent(btTorrent);
             Long torrentId=btTorrent.getTorrentId();
 
+
             // Convert btTorrentFilesNode to List<BtTorrentFile> using TypeReference
             List<BtTorrentFile> btTorrentFiles = objectMapper.readValue(
                     btTorrentFilesNode.toString(),
@@ -175,6 +185,43 @@
         }
     }
 
+    @GetMapping("/download/{id}")
+    public void downloadTorrent(@PathVariable("id") Long id, HttpServletResponse response) {
+        try {
+            // 1. 调用 getTorrentInfo(id) 获取文件路径
+            BtTorrent btTorrent= btTorrentService.selectBtTorrentByTorrentId(id);
+            String filePath=btTorrent.getFilePath();
+            String fileName=btTorrent.getName();
+
+            // 2. 使用工具方法下载文件(不删除源文件)
+            TorrentFileUtil.downloadFile(response, filePath, fileName, false);
+
+        } catch (FileNotFoundException e) {
+
+            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+            System.out.println(e.getMessage());
+
+        } catch (Exception e) {
+
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            System.out.println(e.getMessage());
+
+        }
+
+    }
+
+    /**
+     * 查询种子主列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:torrent:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BtTorrent btTorrent)
+    {
+        startPage();
+        List<BtTorrent> list = btTorrentService.selectBtTorrentList(btTorrent);
+        return getDataTable(list);
+    }
+
     /**
      * 查询种子主列表
      */
@@ -265,6 +312,10 @@
 	@DeleteMapping("/{torrentIds}")
     public AjaxResult remove(@PathVariable Long[] torrentIds)
     {
+        for (Long torrentId : torrentIds){
+            String filePath = btTorrentService.selectBtTorrentByTorrentId(torrentId).getFilePath();
+            TorrentFileUtil.deleteFileOrFolder(filePath);
+        }
         return toAjax(btTorrentService.deleteBtTorrentByTorrentIds(torrentIds));
     }
 }
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/OssMultipartUploader.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/OssMultipartUploader.java
new file mode 100644
index 0000000..6a0323f
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/OssMultipartUploader.java
@@ -0,0 +1,96 @@
+package com.ruoyi.torrent.util;
+
+import com.aliyun.oss.*;
+import com.aliyun.oss.common.auth.CredentialsProviderFactory;
+import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
+import com.aliyun.oss.common.comm.SignVersion;
+import com.aliyun.oss.internal.Mimetypes;
+import com.aliyun.oss.model.*;
+import com.aliyuncs.exceptions.ClientException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OssMultipartUploader {
+
+    private final OSS ossClient;
+    private final String bucketName;
+
+    public OssMultipartUploader(String endpoint, String region, String bucketName) throws ClientException {
+        EnvironmentVariableCredentialsProvider credentialsProvider =
+                CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
+        ClientBuilderConfiguration config = new ClientBuilderConfiguration();
+        config.setSignatureVersion(SignVersion.V4);
+
+        this.ossClient = OSSClientBuilder.create()
+                .endpoint(endpoint)
+                .region(region)
+                .credentialsProvider(credentialsProvider)
+                .clientConfiguration(config)
+                .build();
+
+        this.bucketName = bucketName;
+    }
+
+    public void uploadFile(String objectName, String filePath) throws Exception {
+        File file = new File(filePath);
+        long partSize = 1 * 1024 * 1024L; // 1MB
+        long fileLength = file.length();
+        int partCount = (int) (fileLength / partSize);
+        if (fileLength % partSize != 0) {
+            partCount++;
+        }
+
+        // 设置元数据(如 content-type)
+        ObjectMetadata metadata = new ObjectMetadata();
+        metadata.setContentType(Mimetypes.getInstance().getMimetype(file, objectName));
+
+        InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(bucketName, objectName);
+        initiateRequest.setObjectMetadata(metadata);
+        InitiateMultipartUploadResult initResult = ossClient.initiateMultipartUpload(initiateRequest);
+        String uploadId = initResult.getUploadId();
+
+        List<PartETag> partETags = new ArrayList<>();
+
+        try {
+            for (int i = 0; i < partCount; i++) {
+                long startPos = i * partSize;
+                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
+
+                try (InputStream inputStream = new FileInputStream(file)) {
+                    inputStream.skip(startPos);
+
+                    UploadPartRequest uploadPartRequest = new UploadPartRequest();
+                    uploadPartRequest.setBucketName(bucketName);
+                    uploadPartRequest.setKey(objectName);
+                    uploadPartRequest.setUploadId(uploadId);
+                    uploadPartRequest.setInputStream(inputStream);
+                    uploadPartRequest.setPartSize(curPartSize);
+                    uploadPartRequest.setPartNumber(i + 1);
+
+                    UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
+                    partETags.add(uploadPartResult.getPartETag());
+                }
+            }
+
+            CompleteMultipartUploadRequest completeRequest =
+                    new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
+
+            CompleteMultipartUploadResult result = ossClient.completeMultipartUpload(completeRequest);
+            System.out.println("上传成功,ETag: " + result.getETag());
+
+        } catch (Exception e) {
+            ossClient.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, objectName, uploadId));
+            throw new RuntimeException("上传失败,已中止上传任务", e);
+        }
+    }
+
+    public void shutdown() {
+        if (ossClient != null) {
+            ossClient.shutdown();
+        }
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/TorrentFileUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/TorrentFileUtil.java
index a1cd422..03f2c18 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/TorrentFileUtil.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/TorrentFileUtil.java
@@ -5,10 +5,8 @@
 import java.io.*;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -33,28 +31,30 @@
             Files.createDirectories(saveDir);
         }
 
-        // 获取文件信息
-        String originalFilename = Objects.requireNonNull(file.getOriginalFilename());
-        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
-        String storedFilename = System.currentTimeMillis() + "_" + UUID.randomUUID() + fileExtension;
+        // 获取原始文件名
+        String originalFilename = Objects.requireNonNull(file.getOriginalFilename()).trim();
         long fileSize = file.getSize();
+        String fileExtension = originalFilename.contains(".") ?
+                originalFilename.substring(originalFilename.lastIndexOf(".")) : "";
 
-        // 保存文件
-        Path targetPath = saveDir.resolve(storedFilename);
+        // 使用原始文件名保存(防止路径穿越漏洞)
+        String safeFileName = originalFilename.replaceAll("[\\\\/:*?\"<>|]", "_");
+        Path targetPath = saveDir.resolve(safeFileName);
         Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
 
         // 返回文件信息
         Map<String, Object> fileInfo = new HashMap<>();
         fileInfo.put("originalName", originalFilename);
-        fileInfo.put("storedName", storedFilename);
+        fileInfo.put("storedName", safeFileName);
         fileInfo.put("filePath", targetPath.toString());
         fileInfo.put("fileSize", fileSize);
-        fileInfo.put("fileType", fileExtension.substring(1));
+        fileInfo.put("fileType", fileExtension.isEmpty() ? "unknown" : fileExtension.substring(1));
         fileInfo.put("uploadTime", new Date());
 
         return fileInfo;
     }
 
+
     /**
      * 文件下载工具方法
      *
@@ -114,6 +114,40 @@
             return false;
         }
     }
+    /**
+     * 删除文件或文件夹
+     *
+     * @param path 文件或文件夹路径
+     * @return 是否删除成功
+     */
+    public static boolean deleteFileOrFolder(String path) {
+        try {
+            Path filePath = Paths.get(path);
+            if (Files.isDirectory(filePath)) {
+                // 如果是文件夹,递归删除文件夹中的内容
+                Files.walkFileTree(filePath, new SimpleFileVisitor<Path>() {
+                    @Override
+                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                        Files.delete(file); // 删除文件
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                        Files.delete(dir); // 删除文件夹
+                        return FileVisitResult.CONTINUE;
+                    }
+                });
+            } else {
+                // 如果是文件,直接删除
+                Files.deleteIfExists(filePath);
+            }
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
 
     /**
      * 重命名文件
diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml
index 12ed4d4..ca1fce9 100644
--- a/ruoyi-admin/src/main/resources/application-druid.yml
+++ b/ruoyi-admin/src/main/resources/application-druid.yml
@@ -7,8 +7,8 @@
             # 主库数据源
             master:
                 url: jdbc:mysql://49.233.215.144:3306/pt_station?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                username: sy
-                password: sy_password
+                username: root
+                password: 22301058Zsy*
             # 从库数据源
             slave:
                 # 从数据源开关/默认关闭
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index cb1a87c..01b71fe 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -9,7 +9,7 @@
   # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
   profile: D:/ruoyi/uploadPath
   # 获取ip地址开关
-  addressEnabled: false
+  addressEnabled: true
   # 验证码类型 math 数字计算 char 字符验证
   captchaType: math
 
@@ -75,7 +75,7 @@
       # 数据库索引
       database: 0
       # 密码
-      password:
+      password: 22301058Zsy*
       # 连接超时时间
       timeout: 10s
       lettuce:
diff --git a/ruoyi-admin/src/main/resources/mapper/system/SysUserPasskeyMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/SysUserPasskeyMapper.xml
new file mode 100644
index 0000000..949ac4f
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/SysUserPasskeyMapper.xml
@@ -0,0 +1,62 @@
+<?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.ruoyi.authentication.mapper.SysUserPasskeyMapper">
+    
+    <resultMap type="SysUserPasskey" id="SysUserPasskeyResult">
+        <result property="userId"    column="user_id"    />
+        <result property="passkey"    column="passkey"    />
+        <result property="createdAt"    column="created_at"    />
+    </resultMap>
+
+    <sql id="selectSysUserPasskeyVo">
+        select user_id, passkey, created_at from sys_user_passkey
+    </sql>
+
+    <select id="selectSysUserPasskeyList" parameterType="SysUserPasskey" resultMap="SysUserPasskeyResult">
+        <include refid="selectSysUserPasskeyVo"/>
+        <where>  
+            <if test="passkey != null  and passkey != ''"> and passkey = #{passkey}</if>
+        </where>
+    </select>
+    
+    <select id="selectSysUserPasskeyByUserId" parameterType="Long" resultMap="SysUserPasskeyResult">
+        <include refid="selectSysUserPasskeyVo"/>
+        where user_id = #{userId}
+    </select>
+
+    <insert id="insertSysUserPasskey" parameterType="SysUserPasskey">
+        insert into sys_user_passkey
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="passkey != null and passkey != ''">passkey,</if>
+            <if test="createdAt != null">created_at,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="passkey != null and passkey != ''">#{passkey},</if>
+            <if test="createdAt != null">#{createdAt},</if>
+         </trim>
+    </insert>
+
+    <update id="updateSysUserPasskey" parameterType="SysUserPasskey">
+        update sys_user_passkey
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="passkey != null and passkey != ''">passkey = #{passkey},</if>
+            <if test="createdAt != null">created_at = #{createdAt},</if>
+        </trim>
+        where user_id = #{userId}
+    </update>
+
+    <delete id="deleteSysUserPasskeyByUserId" parameterType="Long">
+        delete from sys_user_passkey where user_id = #{userId}
+    </delete>
+
+    <delete id="deleteSysUserPasskeyByUserIds" parameterType="String">
+        delete from sys_user_passkey where user_id in 
+        <foreach item="userId" collection="array" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/UserScoreMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/UserScoreMapper.xml
new file mode 100644
index 0000000..2f0f7fc
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/UserScoreMapper.xml
@@ -0,0 +1,78 @@
+<?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.ruoyi.authentication.mapper.UserScoreMapper">
+    
+    <resultMap type="UserScore" id="UserScoreResult">
+        <result property="userId"    column="user_id"    />
+        <result property="uploaded"    column="uploaded"    />
+        <result property="downloaded"    column="downloaded"    />
+        <result property="bonus"    column="bonus"    />
+        <result property="score"    column="score"    />
+        <result property="updatedAt"    column="updated_at"    />
+    </resultMap>
+
+    <sql id="selectUserScoreVo">
+        select user_id, uploaded, downloaded, bonus, score, updated_at from user_score
+    </sql>
+
+    <select id="selectUserScoreList" parameterType="UserScore" resultMap="UserScoreResult">
+        <include refid="selectUserScoreVo"/>
+        <where>  
+            <if test="uploaded != null  and uploaded != ''"> and uploaded = #{uploaded}</if>
+            <if test="downloaded != null  and downloaded != ''"> and downloaded = #{downloaded}</if>
+            <if test="bonus != null "> and bonus = #{bonus}</if>
+            <if test="score != null "> and score = #{score}</if>
+            <if test="updatedAt != null "> and updated_at = #{updatedAt}</if>
+        </where>
+    </select>
+    
+    <select id="selectUserScoreByUserId" parameterType="Long" resultMap="UserScoreResult">
+        <include refid="selectUserScoreVo"/>
+        where user_id = #{userId}
+    </select>
+
+    <insert id="insertUserScore" parameterType="UserScore">
+        insert into user_score
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="uploaded != null">uploaded,</if>
+            <if test="downloaded != null">downloaded,</if>
+            <if test="bonus != null">bonus,</if>
+
+            <if test="updatedAt != null">updated_at,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="uploaded != null">#{uploaded},</if>
+            <if test="downloaded != null">#{downloaded},</if>
+            <if test="bonus != null">#{bonus},</if>
+
+            <if test="updatedAt != null">#{updatedAt},</if>
+         </trim>
+    </insert>
+
+    <update id="updateUserScore" parameterType="UserScore">
+        update user_score
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="uploaded != null">uploaded = #{uploaded},</if>
+            <if test="downloaded != null">downloaded = #{downloaded},</if>
+            <if test="bonus != null">bonus = #{bonus},</if>
+
+            <if test="updatedAt != null">updated_at = #{updatedAt},</if>
+        </trim>
+        where user_id = #{userId}
+    </update>
+
+    <delete id="deleteUserScoreByUserId" parameterType="Long">
+        delete from user_score where user_id = #{userId}
+    </delete>
+
+    <delete id="deleteUserScoreByUserIds" parameterType="String">
+        delete from user_score where user_id in 
+        <foreach item="userId" collection="array" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file