Forráskód Böngészése

feature: add app download module

ggbocoder 2 éve
szülő
commit
ef13da68a3

+ 6 - 5
README.md

@@ -10,11 +10,11 @@
 - 发布帖子、评论帖子、热帖排行、发送私信与敏感词过滤等功能 done
 - 点赞、关注与系统通知功能 done
 - 全局搜索帖子信息的功能
-- 上传和下载apk
-- 爬取Fdroid的APP图标和信息介绍 doing
-- 提供实时的热门app榜单
+- 上传和下载apk done
+- 爬取Fdroid的APP图标和信息介绍 done
+- 提供实时的热门app榜单 done
 - 构建fdroid app构建流水线
-- 开发者配置riscv的app构建文件,实现负载均衡模块,执行构建流水线,实时展示构建日志,kafka异步发送邮件通知构建完成
+- 开发者配置riscv的app构建文件,实现负载均衡模块,执行构建流水线,实时展示构建日志,kafka异步发送邮件通知构建完成 (这里可以通过继承xxl-job实现)
 
 ## 核心功能实现
 
@@ -25,7 +25,8 @@
 - 使用Kafka作为消息队列,实现系统通知和解耦
 
 ## TODO
-- 爬取Fdroid的APP图标和信息介绍
+- 爬取Fdroid的APP图标到oss上
+- 完成訂閲apk更新的功能
 - 使用布隆过滤器屏蔽拉黑的ip下载应用
 - 使用Redis实现一致性哈希算法,使用Redis的ZSet实现用户综合评分
 - 使用kafka收集用户下载app量,使用kafka解耦构建流水线,当流水线构建完毕时通过kafka将消息通知到邮箱

+ 52 - 0
src/main/java/cn/seecoder/fdroidrepository/Controller/AppController.java

@@ -7,8 +7,18 @@ import cn.seecoder.fdroidrepository.DataObject.VO.BuildTaskVO;
 import cn.seecoder.fdroidrepository.DataObject.VO.Response;
 import cn.seecoder.fdroidrepository.Service.ServiceImpl.ApkService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
 @RestController
 @RequestMapping("/app")
 public class AppController {
@@ -54,6 +64,48 @@ public class AppController {
         return Response.buildSuccess(appDetailVO);
     }
 
+    @GetMapping("/download")
+    public ResponseEntity<FileSystemResource> downloadFile(
+        @RequestParam Integer apkId,
+        @RequestParam String apkName,
+        @RequestHeader(value = "Range", required = false) String range) throws IOException {
+
+        String filePath = apkService.isExistsApk(apkName);
+        if(filePath==null){
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+        File file = new File(filePath);
+
+//        apkService.addDownloadCount(apkId);
+        // 设置响应头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+        headers.setContentDispositionFormData("attachment", apkName);
+        headers.setContentLength(file.length());
+
+        // 处理断点续传
+        if (range != null && range.startsWith("bytes=")) {
+            String[] ranges = range.substring(6).split("-");
+            long start = Long.parseLong(ranges[0]);
+            long end = file.length() - 1;
+            if (ranges.length == 2) {
+                end = Long.parseLong(ranges[1]);
+            }
+
+            headers.set("Content-Range", "bytes " + start + "-" + end + "/" + file.length());
+            headers.setContentLength(end - start + 1);
+            InputStream inputStream = new FileInputStream(file);
+            inputStream.skip(start);
+            return new ResponseEntity<>(new FileSystemResource(file), headers, HttpStatus.PARTIAL_CONTENT);
+        }
+
+        // 使用Streaming方式下载文件
+        InputStream inputStream = new FileInputStream(file);
+
+        // 返回ResponseEntity
+        return new ResponseEntity<>(new FileSystemResource(file), headers, HttpStatus.OK);
+    }
+
 
 
 }

+ 23 - 7
src/main/java/cn/seecoder/fdroidrepository/Service/ServiceImpl/ApkService.java

@@ -7,6 +7,9 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.sql.*;
 import java.util.HashMap;
 import java.util.Map;
@@ -20,12 +23,15 @@ public class ApkService {
 
     private final static String APP_DOWNLOAD_COUNT_RANK = "app_download_count_rank";
 
-    @Value("$(spring.datasource.url)")
-    private static String JDBC_URL;
-    @Value("$(spring.datasource.username)")
-    private static String JDBC_USER;
-    @Value("$(spring.datasource.password)")
-    private static String JDBC_PASSWORD;
+    @Value("${spring.datasource.url}")
+    private String JDBC_URL;
+    @Value("${spring.datasource.username}")
+    private String JDBC_USER;
+    @Value("${spring.datasource.password}")
+    private String JDBC_PASSWORD;
+
+    @Value("${fdroid.app.path}")
+    private String APP_ABSOLUTE_PATH;
     @Autowired
     private RedisTemplate redisTemplate;
 
@@ -73,7 +79,7 @@ public class ApkService {
         Map<Integer,Double> res =new HashMap<>();
         try (Connection connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
              Statement statement = connection.createStatement();
-             ResultSet resultSet = statement.executeQuery("SELECT app_id, AVG(score) as average_score FROM app_score")) {
+             ResultSet resultSet = statement.executeQuery("SELECT app_id, AVG(score) as average_score FROM app_score GROUP BY app_id")) {
             while (resultSet.next()) {
                 int appId = resultSet.getInt("app_id");
                 double averageScore = resultSet.getDouble("average_score");
@@ -196,5 +202,15 @@ public class ApkService {
         return 0;
     }
 
+    public String isExistsApk(String apkName){
+        String path = APP_ABSOLUTE_PATH;
+        Path directoryPath = Paths.get(path);
+        Path filePath = directoryPath.resolve(apkName);
+
+        if(Files.exists(filePath)){
+            return String.valueOf(filePath);
+        }
+        return null;
+    }
 
 }

+ 1 - 1
src/main/java/cn/seecoder/fdroidrepository/config/WebSecurityConfig.java

@@ -67,7 +67,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Override
     public void configure(WebSecurity web) {
-        String ignoreURLs = "/public/**,/open-api/**,/static/**,/css/**,/js/**,/images/**,/api/**";
+        String ignoreURLs = "/public/**,/open-api/**,/static/**,/css/**,/js/**,/images/**,/api/**,/app/**";
         for (String ignoreURL : ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
             web.ignoring().antMatchers(ignoreURL.trim());
         }

+ 4 - 0
src/main/resources/application.properties

@@ -1,3 +1,7 @@
+## fdroid app 配置
+fdroid.app.path = F:\\a_project\\FdroidRepository\\src\\test\\
+
+
 server.servlet.context-path=
 
 # 数据库连接配置

+ 1 - 0
src/test/ab

@@ -0,0 +1 @@
+123

BIN
src/test/d.d.meshenger_426.apk


+ 19 - 0
src/test/java/cn/seecoder/fdroidrepository/Service/ServiceImpl/ApkServiceTest.java

@@ -0,0 +1,19 @@
+package cn.seecoder.fdroidrepository.Service.ServiceImpl;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+class ApkServiceTest {
+
+    @Autowired
+    private ApkService apkService;
+
+    @Test
+    void isExistsApk() {
+        System.out.println(apkService.isExistsApk("ab"));
+    }
+}