Просмотр исходного кода

file-service 添加 FileController

reghao 3 месяцев назад
Родитель
Сommit
a9b2fd1204
30 измененных файлов с 846 добавлено и 52 удалено
  1. 21 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/config/AppProperties.java
  2. 6 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/config/BeanConfig.java
  3. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/config/web/OssSdkInterceptor.java
  4. 0 31
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/AdminFileController.java
  5. 105 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/FileController.java
  6. 26 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/LocalFileMapper.java
  7. 20 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/dto/UploadFile.java
  8. 62 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/LocalFile.java
  9. 82 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/FileQuery.java
  10. 30 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/LocalFileInfo.java
  11. 20 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/LocalFileUrl.java
  12. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/StoreServiceWrapperRouter.java
  13. 214 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/service/FileService.java
  14. 1 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/service/OssFileService.java
  15. 104 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/util/JarFileResources.java
  16. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/OssSdkController.java
  17. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/StoreNodeController.java
  18. 2 2
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/StoreObjectController.java
  19. 2 2
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/UploadChannelController.java
  20. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/UserKeyController.java
  21. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/UserNodeController.java
  22. 3 3
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/rpc/ConsoleServiceImpl.java
  23. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/rpc/StoreServiceWrapper.java
  24. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/StoreNodeService.java
  25. 2 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/UploadChannelService.java
  26. 1 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/UserKeyService.java
  27. 2 1
      file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/UserNodeService.java
  28. 3 1
      file/file-service/src/main/resources/application.yml
  29. 65 0
      file/file-service/src/main/resources/mapper/file/LocalFileMapper.xml
  30. 67 2
      file/file-service/src/test/java/FileTest.java

+ 21 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/config/AppProperties.java

@@ -0,0 +1,21 @@
+package cn.reghao.tnb.file.app.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2021-12-30 11:01:46
+ */
+@Configuration
+@ConfigurationProperties(prefix = "app")
+@Setter
+@Getter
+public class AppProperties {
+    private String baseDir;
+}

+ 6 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/config/BeanConfig.java

@@ -4,6 +4,7 @@ import cn.reghao.jutil.jdk.converter.ByteConverter;
 import cn.reghao.jutil.jdk.http.WebClient;
 import cn.reghao.jutil.jdk.http.WebRequest;
 import cn.reghao.jutil.jdk.shell.ShellExecutor;
+import cn.reghao.tnb.file.app.util.JarFileResources;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.client.RestTemplate;
@@ -34,4 +35,9 @@ public class BeanConfig {
     public ByteConverter byteConverter() {
         return new ByteConverter();
     }
+
+    @Bean
+    public JarFileResources jarFileResources() {
+        return new JarFileResources();
+    }
 }

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/config/web/OssSdkInterceptor.java

@@ -4,7 +4,7 @@ import cn.reghao.jutil.jdk.http.HeaderNames;
 import cn.reghao.tnb.common.web.ServletUtil;
 import cn.reghao.tnb.common.auth.LoginUser;
 import cn.reghao.tnb.common.auth.UserContext;
-import cn.reghao.tnb.file.app.service.UserKeyService;
+import cn.reghao.tnb.file.app.zoss.service.UserKeyService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;

+ 0 - 31
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/AdminFileController.java

@@ -1,31 +0,0 @@
-package cn.reghao.tnb.file.app.controller;
-
-import cn.reghao.tnb.common.web.WebResult;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.http.MediaType;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-/**
- * @author reghao
- * @date 2025-09-26 20:40:27
- */
-@Tag(name = "file 管理接口")
-@RestController
-@RequestMapping("/api/admin/file")
-public class AdminFileController {
-    @Operation(summary = "", description = "N")
-    @PostMapping(value = "/file_store", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String addSiteConfig() {
-        return WebResult.success();
-    }
-
-    @Operation(summary = "", description = "N")
-    @GetMapping(value = "/file_store", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String siteConfigPage() {
-        return WebResult.success();
-    }
-}

+ 105 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/FileController.java

@@ -0,0 +1,105 @@
+package cn.reghao.tnb.file.app.controller;
+
+import cn.reghao.jutil.jdk.web.db.PageList;
+import cn.reghao.tnb.common.web.ServletUtil;
+import cn.reghao.tnb.common.web.WebResult;
+import cn.reghao.tnb.file.app.model.dto.UploadFile;
+import cn.reghao.tnb.file.app.model.po.LocalFile;
+import cn.reghao.tnb.file.app.model.vo.LocalFileInfo;
+import cn.reghao.tnb.file.app.model.vo.LocalFileUrl;
+import cn.reghao.tnb.file.app.service.FileService;
+import cn.reghao.tnb.file.app.util.JarFileResources;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2025-09-26 20:40:27
+ */
+@Tag(name = "file 管理接口")
+@Controller
+public class FileController {
+    private final JarFileResources jarFileResources;
+    private final FileService fileService;
+
+    public FileController(JarFileResources jarFileResources, FileService fileService) {
+        this.jarFileResources = jarFileResources;
+        this.fileService = fileService;
+    }
+
+    @Operation(summary = "文件列表页面", description = "N")
+    @GetMapping("/api/file/list")
+    public String fileList(@RequestParam("pn") int pageNumber) {
+        PageList<LocalFileInfo> pageList = fileService.getLocalFiles(pageNumber);
+        return WebResult.success(pageList);
+    }
+
+    @Operation(summary = "图片列表页面", description = "N")
+    @GetMapping("/api/file/image")
+    public String imageList(@RequestParam("pn") int pageNumber) {
+        PageList<LocalFileUrl> pageList = fileService.getImageFiles(pageNumber);
+        return WebResult.success(pageList);
+    }
+
+    @Operation(summary = "文件上传接口", description = "N")
+    @PostMapping(value = "/api/file/upload", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public String uploadFile(UploadFile uploadFile) throws Exception {
+        MultipartFile file = uploadFile.getFile();
+        LocalFile localFile = fileService.putLocalFile(file);
+        String url = String.format("/%s", localFile.getObjectName());
+        Map<String, String> map = new HashMap<>();
+        map.put("name", file.getOriginalFilename());
+        map.put("url", url);
+        return WebResult.success(map);
+    }
+
+    @Operation(summary = "文件预览接口", description = "N")
+    @GetMapping(value = "/api/file/preview", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public String previewFile(@RequestParam("objectId") String objectId) {
+        LocalFile localFile = fileService.getLocalFile(objectId);
+        String objectName = localFile.getObjectName();
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("filename", localFile.getFilename());
+        map.put("fileType", localFile.getFileType());
+        map.put("url", "/"+objectName);
+        return WebResult.success(map);
+    }
+
+    @Operation(summary = "文件访问接口", description = "N")
+    @GetMapping("/file/**")
+    @ResponseBody
+    public void getFile() throws IOException {
+        HttpServletRequest servletRequest = ServletUtil.getRequest();
+        String uri = servletRequest.getRequestURI();
+        String uri1 = URLDecoder.decode(uri, StandardCharsets.UTF_8);
+        String objectName =  uri1.replaceFirst("/", "");
+        fileService.getFile(objectName);
+    }
+
+    @Operation(summary = "默认头像", description = "N")
+    @GetMapping(value = "/avatar.jpg", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<byte[]> getAvatar() throws IOException {
+        String avatarResource = "static/dist/images/avatar.jpg";
+        byte[] imageBytes = jarFileResources.getFileFromResourceAsStream(avatarResource).readAllBytes();
+        // 返回包含图片字节数组的 ResponseEntity
+        return ResponseEntity.ok()
+                .contentType(MediaType.IMAGE_JPEG)
+                .contentLength(imageBytes.length)
+                .body(imageBytes);
+    }
+}

+ 26 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/LocalFileMapper.java

@@ -0,0 +1,26 @@
+package cn.reghao.tnb.file.app.db.mapper;
+
+import cn.reghao.jutil.jdk.web.db.BaseMapper;
+import cn.reghao.jutil.jdk.web.db.Page;
+import cn.reghao.tnb.file.app.model.po.LocalFile;
+import cn.reghao.tnb.file.app.model.vo.FileQuery;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-12-05 09:28:28
+ */
+@Mapper
+public interface LocalFileMapper extends BaseMapper<LocalFile> {
+    LocalFile findByObjectName(String objectName);
+    LocalFile findByObjectId(String objectId);
+    List<LocalFile> findBySha256sum(String sha256sum);
+    List<LocalFile> findByOwner(long owner);
+    List<LocalFile> findByFileTypeAndOwner(@Param("fileType") int fileType, @Param("owner") long owner);
+
+    int countByFileQuery(FileQuery fileQuery);
+    List<LocalFile> findFileQueryByPage(@Param("page") Page page, @Param("fileQuery") FileQuery fileQuery);
+}

+ 20 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/model/dto/UploadFile.java

@@ -0,0 +1,20 @@
+package cn.reghao.tnb.file.app.model.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author reghao
+ * @date 2025-07-12 16:50:58
+ */
+@Setter
+@Getter
+public class UploadFile {
+    @NotNull
+    private MultipartFile file;
+    @NotBlank
+    private String path;
+}

+ 62 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/LocalFile.java

@@ -0,0 +1,62 @@
+package cn.reghao.tnb.file.app.model.po;
+
+import cn.reghao.jutil.jdk.web.db.BaseObject;
+import cn.reghao.tnb.common.auth.UserContext;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2025-12-05 09:28:00
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Setter
+@Getter
+public class LocalFile extends BaseObject<Integer> {
+    @NotBlank
+    private String objectName;
+    @NotBlank
+    private String objectId;
+    @NotBlank
+    private String absolutePath;
+    @NotBlank
+    private String sha256sum;
+    @NotBlank
+    private String filename;
+    @NotBlank
+    private String contentType;
+    private Integer fileType;
+    @NotNull
+    private Long size;
+    private Long owner;
+
+    public LocalFile(String objectName, String objectId, LocalFile localFile, String filename) {
+        this.objectName = objectName;
+        this.objectId = objectId;
+        this.absolutePath = localFile.getAbsolutePath();
+        this.sha256sum = localFile.getSha256sum();
+        this.filename = filename;
+        this.contentType = localFile.getContentType();
+        this.fileType = localFile.getFileType();
+        this.size = localFile.getSize();
+        this.owner = UserContext.getUserId();
+    }
+
+    public LocalFile(String objectName, String objectId, String absolutePath, String sha256sum,
+                    String filename, String contentType, int fileType, long size) {
+        this.objectName = objectName;
+        this.objectId = objectId;
+        this.absolutePath = absolutePath;
+        this.sha256sum = sha256sum;
+        this.filename = filename;
+        this.contentType = contentType;
+        this.fileType = fileType;
+        this.size = size;
+        this.owner = UserContext.getUserId();
+    }
+}

+ 82 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/FileQuery.java

@@ -0,0 +1,82 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2025-08-29 20:09:08
+ */
+@Getter
+@Setter
+public class FileQuery {
+    private Integer pageNumber;
+    private Integer pageSize;
+    private Integer fileType;
+    private Long owner;
+    private String objectName;
+    private String objectId;
+    private String sha256sum;
+
+    private FileQuery(Builder builder) {
+        this.pageNumber = builder.pageNumber;
+        this.pageSize = builder.pageSize;
+        this.fileType = builder.fileType;
+        this.owner = builder.owner;
+        this.objectName = builder.objectName;
+        this.objectId = builder.objectId;
+        this.sha256sum = builder.sha256sum;
+    }
+
+    public static final class Builder {
+        private Integer pageNumber;
+        private Integer pageSize;
+        private Integer fileType;
+        private Long owner;
+        private String objectName;
+        private String objectId;
+        private String sha256sum;
+
+        public Builder() {
+        }
+
+        public Builder pageNumber(int pageNumber) {
+            this.pageNumber = pageNumber;
+            return this;
+        }
+
+        public Builder pageSize(int pageSize) {
+            this.pageSize = pageSize;
+            return this;
+        }
+
+        public Builder fileType(int fileType) {
+            this.fileType = fileType;
+            return this;
+        }
+
+        public Builder owner(long owner) {
+            this.owner = owner;
+            return this;
+        }
+
+        public Builder objectName(String objectName) {
+            this.objectName = objectName;
+            return this;
+        }
+
+        public Builder objectId(String objectId) {
+            this.objectId = objectId;
+            return this;
+        }
+
+        public Builder sha256sum(String sha256sum) {
+            this.sha256sum = sha256sum;
+            return this;
+        }
+
+        public FileQuery build() {
+            return new FileQuery(this);
+        }
+    }
+}

+ 30 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/LocalFileInfo.java

@@ -0,0 +1,30 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import cn.reghao.tnb.file.app.model.po.LocalFile;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-10-30 10:24:28
+ */
+@AllArgsConstructor
+@Getter
+public class LocalFileInfo {
+    private String fileId;
+    private String filename;
+    private String updateTime;
+    private String size;
+    private int fileType;
+    private String fileTypeStr;
+
+    public LocalFileInfo(LocalFile localFile, String size) {
+        this.fileId = localFile.getObjectId();
+        this.filename = localFile.getFilename();
+        this.updateTime = DateTimeConverter.format(localFile.getUpdateTime());
+        this.size = size;
+        this.fileType = localFile.getFileType();
+        this.fileTypeStr = "";
+    }
+}

+ 20 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/LocalFileUrl.java

@@ -0,0 +1,20 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-10-30 17:37:17
+ */
+@AllArgsConstructor
+@Getter
+public class LocalFileUrl {
+    private String fileId;
+    private String filename;
+    private String url;
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+}

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/StoreServiceWrapperRouter.java

@@ -14,7 +14,7 @@ import cn.reghao.tnb.file.app.zoss.db.repository.StoreRepository;
 import cn.reghao.tnb.file.app.model.constant.OssType;
 import cn.reghao.tnb.file.app.zoss.model.po.UploadChannel;
 import cn.reghao.tnb.file.app.service.StoreConfigService;
-import cn.reghao.tnb.file.app.service.UploadChannelService;
+import cn.reghao.tnb.file.app.zoss.service.UploadChannelService;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.springframework.stereotype.Service;
 

+ 214 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/FileService.java

@@ -0,0 +1,214 @@
+package cn.reghao.tnb.file.app.service;
+
+import cn.reghao.jutil.jdk.converter.ByteConverter;
+import cn.reghao.jutil.jdk.converter.ByteType;
+import cn.reghao.jutil.jdk.security.DigestUtil;
+import cn.reghao.jutil.jdk.web.db.PageList;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.common.web.ServletUtil;
+import cn.reghao.tnb.file.app.config.AppProperties;
+import cn.reghao.tnb.file.app.db.mapper.LocalFileMapper;
+import cn.reghao.tnb.file.app.model.po.LocalFile;
+import cn.reghao.tnb.file.app.model.vo.LocalFileInfo;
+import cn.reghao.tnb.file.app.model.vo.LocalFileUrl;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-01-19 21:29:24
+ */
+@Slf4j
+@Service
+public class FileService {
+    // 8MiB
+    private final int bufSize = 1024*1024*8;
+    private final ByteConverter byteConverter;
+    private final LocalFileMapper localFileMapper;
+    private int pageSize = 10;
+    private String baseDir;
+
+    public FileService(ByteConverter byteConverter, LocalFileMapper localFileMapper, AppProperties appProperties) {
+        this.byteConverter = byteConverter;
+        this.localFileMapper = localFileMapper;
+        this.baseDir = appProperties.getBaseDir();
+    }
+
+    public void getFile(String objectName) throws IOException {
+        LocalFile diskFile = getByObjectName(objectName);
+        if (diskFile == null) {
+            log.error("LocalFile {} not exist", objectName);
+            writeResponse(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        String absolutePath = diskFile.getAbsolutePath();
+        File file = new File(absolutePath);
+        if (!file.exists() || file.isDirectory()) {
+            log.error("file {} not exist", absolutePath);
+            writeResponse(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        String contentType = diskFile.getContentType();
+        long size = diskFile.getSize();
+        HttpServletResponse response = ServletUtil.getResponse();
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setContentType(contentType);
+        response.setContentLengthLong(size);
+
+        OutputStream outputStream = response.getOutputStream();
+        writeResponse(outputStream, absolutePath, 0, size);
+    }
+
+    @Cacheable(cacheNames = "filePaths", key = "#objectName", unless = "#result == null")
+    public LocalFile getByObjectName(String objectName) {
+        LocalFile diskFile = localFileMapper.findByObjectName(objectName);
+        return diskFile;
+    }
+
+    private void writeResponse(int statusCode) throws IOException {
+        HttpServletResponse response = ServletUtil.getResponse();
+        response.setStatus(statusCode);
+        OutputStream outputStream = response.getOutputStream();
+        outputStream.flush();
+        outputStream.close();
+    }
+
+    private void writeResponse(OutputStream outputStream, String absolutePath, long start, long end) throws IOException {
+        RandomAccessFile raf = new RandomAccessFile(absolutePath, "r");
+        raf.seek(start);
+
+        long len = end-start+1;
+        if (len < bufSize) {
+            int len1 = (int) len;
+            byte[] buf1 = new byte[len1];
+            int readLen1 = raf.read(buf1, 0, len1);
+            outputStream.write(buf1, 0, readLen1);
+        } else {
+            byte[] buf = new byte[bufSize];
+            long totalRead = 0;
+            int readLen;
+            while ((readLen = raf.read(buf, 0, bufSize)) != -1) {
+                outputStream.write(buf, 0, readLen);
+                totalRead += readLen;
+
+                long left = len - totalRead;
+                if (left < bufSize) {
+                    int left1 = (int) left;
+                    byte[] buf1 = new byte[left1];
+                    int readLen1 = raf.read(buf1, 0, left1);
+                    outputStream.write(buf1, 0, readLen1);
+                    break;
+                }
+            }
+        }
+
+        outputStream.flush();
+        outputStream.close();
+        raf.close();
+    }
+
+    public LocalFile putLocalFile(MultipartFile file) throws Exception {
+        long size = file.getSize();
+        String filename = file.getOriginalFilename();
+        String suffix = getSuffix(filename);
+
+        String objectId = UUID.randomUUID().toString().replace("-", "");;
+        String objectName;
+        if (suffix.isBlank()) {
+            objectName = String.format("file/%s", objectId);
+        } else {
+            objectName = String.format("file/%s%s", objectId, suffix);
+        }
+
+        String contentId = UUID.randomUUID().toString().replace("-", "");
+        String destPath = String.format("%s/%s", baseDir, contentId);
+        File savedFile = saveFile(file.getInputStream(), destPath);
+        String contentType = "image/jpeg";
+        int fileType = 1001;
+        String sha256sum = DigestUtil.sha256sum(savedFile.getAbsolutePath());
+
+        List<LocalFile> diskFiles = localFileMapper.findBySha256sum(sha256sum);
+        LocalFile diskFile;
+        if (!diskFiles.isEmpty()) {
+            LocalFile existFile = diskFiles.get(0);
+            diskFile = new LocalFile(objectName, objectId, existFile, filename);
+            FileUtils.deleteQuietly(savedFile);
+        } else {
+            diskFile = new LocalFile(objectName, objectId, savedFile.getAbsolutePath(), sha256sum, filename, contentType, fileType, size);
+        }
+
+        localFileMapper.save(diskFile);
+        return diskFile;
+    }
+
+    private File saveFile(InputStream inputStream, String absolutePath) throws IOException {
+        File file = new File(absolutePath);
+        if (file.exists()) {
+            throw new IOException(absolutePath + " exist");
+        }
+
+        Files.copy(inputStream, Path.of(absolutePath), StandardCopyOption.REPLACE_EXISTING);
+        return file;
+    }
+
+    private String getSuffix(String filename) {
+        if (filename == null) {
+            return "";
+        }
+
+        int idx = filename.lastIndexOf(".");
+        return idx == -1 ? "" : filename.substring(idx);
+    }
+
+    public PageList<LocalFileInfo> getLocalFiles(int pageNumber) {
+        long owner = UserContext.getUserId();
+        List<LocalFile> list = localFileMapper.findByOwner(owner);
+        List<LocalFileInfo> list0 = list.stream().map(diskFile -> {
+            String size = byteConverter.convert(ByteType.Bytes, diskFile.getSize());
+            return new LocalFileInfo(diskFile, size);
+        }).collect(Collectors.toList());
+        return PageList.empty();
+    }
+
+    public PageList<LocalFileUrl> getImageFiles(int pageNumber) {
+        long owner = UserContext.getUserId();
+        int fileType = 1001;
+        List<LocalFile> list = localFileMapper.findByFileTypeAndOwner(fileType, owner);
+        List<LocalFileUrl> list0 = list.stream().map(diskFile -> {
+            String fileId = diskFile.getObjectId();
+            String filename = diskFile.getFilename();
+            String objectName = diskFile.getObjectName();
+            String url = String.format("/%s", objectName);
+            return new LocalFileUrl(fileId, filename, url);
+        }).collect(Collectors.toList());
+
+        return PageList.empty();
+    }
+
+    public LocalFile getLocalFile(String objectId) {
+        return localFileMapper.findByObjectId(objectId);
+    }
+
+    public String getObjectName(String path) {
+        String objectName = "";
+        if (!path.equals("/")) {
+            objectName = path.substring(1) + "/";
+        }
+
+        return objectName;
+    }
+}

+ 1 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/OssFileService.java

@@ -3,6 +3,7 @@ package cn.reghao.tnb.file.app.service;
 import cn.reghao.jutil.jdk.converter.ByteConverter;
 import cn.reghao.jutil.jdk.converter.ByteType;
 import cn.reghao.jutil.jdk.web.db.PageList;
+import cn.reghao.tnb.file.app.zoss.service.UserNodeService;
 import cn.reghao.tnb.oss.api.iface.StoreService;
 import cn.reghao.tnb.oss.api.constant.ObjectScope;
 import cn.reghao.tnb.oss.api.constant.ObjectType;

+ 104 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/util/JarFileResources.java

@@ -0,0 +1,104 @@
+package cn.reghao.tnb.file.app.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 获取 jar 包内的文件资源
+ * 
+ * @author reghao
+ * @date 2025-11-03 16:05:43
+ */
+public class JarFileResources {
+    /**
+     * get a file from the resources folder
+     * works everywhere, IDEA, unit test and jar file.
+     * 
+     * @param
+     * @return
+     * @date 2025-11-03 16:07:27
+     */
+    public InputStream getFileFromResourceAsStream(String resourcePath) {
+        // The class loader that loaded the class
+        ClassLoader classLoader = getClass().getClassLoader();
+        InputStream inputStream = classLoader.getResourceAsStream(resourcePath);
+        // the stream holding the file content
+        if (inputStream == null) {
+            throw new IllegalArgumentException("file not found! " + resourcePath);
+        } else {
+            return inputStream;
+        }
+    }
+
+    /**
+     * The resource URL is not working in the JAR
+     * If we try to access a file that is inside a JAR,
+     * It throws NoSuchFileException (linux), InvalidPathException (Windows)
+     * Resource URL Sample: file:java-io.jar!/json/file1.json
+     *
+     * @param
+     * @return
+     * @date 2025-11-03 16:16:48
+     */
+    public File getFileFromResource(String resourcePath) throws URISyntaxException{
+        ClassLoader classLoader = getClass().getClassLoader();
+        URL resource = classLoader.getResource(resourcePath);
+        if (resource == null) {
+            throw new IllegalArgumentException("file not found! " + resourcePath);
+        } else {
+            // failed if files have whitespaces or special characters
+            //return new File(resource.getFile());
+            return new File(resource.toURI());
+        }
+    }
+
+    /**
+     * Get all paths from a folder that inside the jar file
+     * 
+     * @param
+     * @return
+     * @date 2025-11-03 16:07:52
+     */
+    public List<Path> getPathsFromResourceJar(String folder) throws URISyntaxException, IOException {
+        // get path of the current running jar
+        String jarPath = getClass().getProtectionDomain()
+                .getCodeSource()
+                .getLocation()
+                .toURI()
+                .getPath();
+        System.out.println("jar Path :" + jarPath);
+        // file walks jar
+        URI uri = URI.create("jar:file:" + jarPath);
+        List<Path> result;
+        try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
+            result = Files.walk(fs.getPath(folder))
+                    .filter(Files::isRegularFile)
+                    .collect(Collectors.toList());
+        }
+        return result;
+    }
+
+    public String getRunningJarPath() {
+        URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
+        String jarFilePath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
+        if (jarFilePath.endsWith(".jar")) {
+            jarFilePath = jarFilePath.substring(0, jarFilePath.lastIndexOf("/") + 1);
+        }
+
+        File file = new File(jarFilePath);
+        return file.getAbsolutePath();
+    }
+}

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/OssSdkController.java

@@ -12,7 +12,7 @@ import cn.reghao.tnb.oss.api.dto.media.VideoInfo;
 import cn.reghao.tnb.common.auth.UserContext;
 import cn.reghao.tnb.file.app.zoss.model.dto.KeyAuthDto;
 import cn.reghao.tnb.file.app.zoss.rpc.StoreServiceWrapper;
-import cn.reghao.tnb.file.app.service.UserKeyService;
+import cn.reghao.tnb.file.app.zoss.service.UserKeyService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.http.MediaType;

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/StoreNodeController.java

@@ -5,7 +5,7 @@ import cn.reghao.tnb.common.web.WebResult;
 import cn.reghao.tnb.common.db.SelectOption;
 import cn.reghao.tnb.file.app.zoss.model.po.StoreVolume;
 import cn.reghao.tnb.file.app.zoss.model.vo.StoreNodeInfo;
-import cn.reghao.tnb.file.app.service.StoreNodeService;
+import cn.reghao.tnb.file.app.zoss.service.StoreNodeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.http.MediaType;

+ 2 - 2
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/StoreObjectController.java

@@ -10,8 +10,8 @@ import cn.reghao.tnb.common.auth.UserContext;
 import cn.reghao.tnb.file.app.zoss.model.po.UserNode;
 import cn.reghao.tnb.file.app.zoss.model.vo.FileInfo;
 import cn.reghao.tnb.file.app.service.OssFileService;
-import cn.reghao.tnb.file.app.service.UploadChannelService;
-import cn.reghao.tnb.file.app.service.UserNodeService;
+import cn.reghao.tnb.file.app.zoss.service.UploadChannelService;
+import cn.reghao.tnb.file.app.zoss.service.UserNodeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.http.MediaType;

+ 2 - 2
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/UploadChannelController.java

@@ -11,8 +11,8 @@ import cn.reghao.tnb.common.db.SelectOption;
 import cn.reghao.tnb.file.app.zoss.model.po.UploadChannel;
 import cn.reghao.tnb.file.app.zoss.model.po.UserNode;
 import cn.reghao.tnb.file.app.zoss.model.vo.AddChannelAttr;
-import cn.reghao.tnb.file.app.service.UploadChannelService;
-import cn.reghao.tnb.file.app.service.UserNodeService;
+import cn.reghao.tnb.file.app.zoss.service.UploadChannelService;
+import cn.reghao.tnb.file.app.zoss.service.UserNodeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/UserKeyController.java

@@ -3,7 +3,7 @@ package cn.reghao.tnb.file.app.zoss.controller;
 import cn.reghao.tnb.common.web.WebResult;
 import cn.reghao.tnb.file.app.zoss.model.po.UserKey;
 import cn.reghao.tnb.file.app.service.StoreConfigService;
-import cn.reghao.tnb.file.app.service.UserKeyService;
+import cn.reghao.tnb.file.app.zoss.service.UserKeyService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.http.MediaType;

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/controller/UserNodeController.java

@@ -7,7 +7,7 @@ import cn.reghao.tnb.file.app.zoss.model.dto.NodeAddDto;
 import cn.reghao.tnb.common.db.SelectOption;
 import cn.reghao.tnb.file.app.zoss.model.dto.NodeUpdateDto;
 import cn.reghao.tnb.file.app.zoss.model.po.UserNode;
-import cn.reghao.tnb.file.app.service.UserNodeService;
+import cn.reghao.tnb.file.app.zoss.service.UserNodeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.http.MediaType;

+ 3 - 3
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/rpc/ConsoleServiceImpl.java

@@ -10,9 +10,9 @@ import cn.reghao.tnb.file.app.zoss.model.po.StoreNode;
 import cn.reghao.tnb.file.app.zoss.model.po.UploadChannel;
 import cn.reghao.tnb.file.app.zoss.model.po.UserNode;
 import cn.reghao.tnb.file.app.service.StoreConfigService;
-import cn.reghao.tnb.file.app.service.StoreNodeService;
-import cn.reghao.tnb.file.app.service.UploadChannelService;
-import cn.reghao.tnb.file.app.service.UserNodeService;
+import cn.reghao.tnb.file.app.zoss.service.StoreNodeService;
+import cn.reghao.tnb.file.app.zoss.service.UploadChannelService;
+import cn.reghao.tnb.file.app.zoss.service.UserNodeService;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.springframework.stereotype.Service;
 

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/rpc/StoreServiceWrapper.java

@@ -9,7 +9,7 @@ import cn.reghao.tnb.oss.api.dto.media.ImageInfo;
 import cn.reghao.tnb.oss.api.dto.media.VideoInfo;
 import cn.reghao.tnb.file.app.zoss.db.mapper.StoreNodeMapper;
 import cn.reghao.tnb.file.app.zoss.model.po.StoreNode;
-import cn.reghao.tnb.file.app.service.UserNodeService;
+import cn.reghao.tnb.file.app.zoss.service.UserNodeService;
 import org.springframework.stereotype.Service;
 
 import java.util.List;

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/StoreNodeService.java → file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/StoreNodeService.java

@@ -1,4 +1,4 @@
-package cn.reghao.tnb.file.app.service;
+package cn.reghao.tnb.file.app.zoss.service;
 
 import cn.reghao.jutil.jdk.converter.ByteConverter;
 import cn.reghao.jutil.jdk.converter.ByteType;

+ 2 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UploadChannelService.java → file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/UploadChannelService.java

@@ -1,9 +1,10 @@
-package cn.reghao.tnb.file.app.service;
+package cn.reghao.tnb.file.app.zoss.service;
 
 import cn.reghao.jutil.jdk.converter.ByteConverter;
 import cn.reghao.jutil.jdk.converter.ByteType;
 import cn.reghao.jutil.jdk.web.result.Result;
 import cn.reghao.jutil.jdk.web.result.ResultStatus;
+import cn.reghao.tnb.file.app.service.StoreConfigService;
 import cn.reghao.tnb.oss.api.dto.ObjectChannel;
 import cn.reghao.tnb.oss.api.constant.ObjectScope;
 import cn.reghao.tnb.oss.api.constant.ObjectType;

+ 1 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UserKeyService.java → file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/UserKeyService.java

@@ -1,4 +1,4 @@
-package cn.reghao.tnb.file.app.service;
+package cn.reghao.tnb.file.app.zoss.service;
 
 import cn.reghao.jutil.jdk.web.result.Result;
 import cn.reghao.jutil.jdk.security.RandomString;

+ 2 - 1
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UserNodeService.java → file/file-service/src/main/java/cn/reghao/tnb/file/app/zoss/service/UserNodeService.java

@@ -1,7 +1,8 @@
-package cn.reghao.tnb.file.app.service;
+package cn.reghao.tnb.file.app.zoss.service;
 
 import cn.reghao.jutil.jdk.web.result.Result;
 import cn.reghao.tnb.common.util.ConstantId;
+import cn.reghao.tnb.file.app.service.StoreConfigService;
 import cn.reghao.tnb.file.app.zoss.db.mapper.StoreNodeMapper;
 import cn.reghao.tnb.file.app.zoss.db.mapper.UploadChannelMapper;
 import cn.reghao.tnb.file.app.zoss.db.mapper.UserNodeMapper;

+ 3 - 1
file/file-service/src/main/resources/application.yml

@@ -47,4 +47,6 @@ eureka:
     prefer-ip-address: true
   client:
     register-with-eureka: true
-    fetch-registry: true
+    fetch-registry: true
+app:
+  baseDir: /opt/data/tnbfile

+ 65 - 0
file/file-service/src/main/resources/mapper/file/LocalFileMapper.xml

@@ -0,0 +1,65 @@
+<?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="cn.reghao.tnb.file.app.db.mapper.LocalFileMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into local_file
+        (`object_name`,`object_id`,`absolute_path`,`filename`,`file_type`,`content_type`,`sha256sum`,`size`,`owner`)
+        values
+        (#{objectName},#{objectId},#{absolutePath},#{filename},#{fileType},#{contentType},#{sha256sum},#{size},#{owner})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into local_file
+        (`object_name`,`object_id`,`absolute_path`,`filename`,`file_type`,`content_type`,`sha256sum`,`size`,`owner`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.objectName},#{item.objectId},#{item.absolutePath},#{item.filename},#{item.fileType},#{item.contentType},#{item.sha256sum},#{item.size},#{item.owner})
+        </foreach>
+    </insert>
+
+    <select id="countByDiskQuery" resultType="java.lang.Integer">
+        select count(*)
+        from local_file
+        <where>
+            deleted=0
+            <if test="fileType != null">
+                and file_type=#{fileType}
+            </if>
+            <if test="owner != null">
+                and `owner`=#{owner}
+            </if>
+            <if test="objectId != null">
+                and object_id=#{objectId}
+            </if>
+            <if test="objectName != null">
+                and object_name=#{objectName}
+            </if>
+            <if test="sha256sum != null">
+                and sha256sum=#{sha256sum}
+            </if>
+        </where>
+    </select>
+    <select id="findDiskQueryByPage" resultType="cn.reghao.tnb.file.app.zdisk.model.po.DiskFile">
+        select *
+        from local_file
+        <where>
+            deleted=0
+            <if test="fileQuery.fileType != null">
+                and file_type=#{fileQuery.fileType}
+            </if>
+            <if test="fileQuery.owner != null">
+                and `owner`=#{fileQuery.owner}
+            </if>
+            <if test="fileQuery.objectId != null">
+                and object_id=#{fileQuery.objectId}
+            </if>
+            <if test="fileQuery.objectName != null">
+                and object_name=#{fileQuery.objectName}
+            </if>
+            <if test="fileQuery.sha256sum != null">
+                and sha256sum=#{fileQuery.sha256sum}
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+</mapper>

+ 67 - 2
file/file-service/src/test/java/FileTest.java

@@ -1,15 +1,19 @@
 import cn.reghao.tnb.content.api.dto.CamPhoto;
 import cn.reghao.tnb.file.app.FileApplication;
 import cn.reghao.tnb.file.app.service.EarthService;
+import cn.reghao.tnb.file.app.util.JarFileResources;
 import cn.reghao.tnb.file.app.util.PhotoExif;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.io.ClassPathResource;
 import org.springframework.test.context.ActiveProfiles;
 
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.FileVisitResult;
 import java.nio.file.FileVisitor;
 import java.nio.file.Files;
@@ -70,4 +74,65 @@ public class FileTest {
         earthService.getEarthKml(kmlFile, camPhotoList);
         System.out.println();
     }
+
+    // print input stream
+    private static void printInputStream(InputStream is) {
+        try (InputStreamReader streamReader = new InputStreamReader(is, StandardCharsets.UTF_8);
+             BufferedReader reader = new BufferedReader(streamReader)) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                System.out.println(line);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void getFileFromJar() throws IOException, URISyntaxException {
+        ClassLoader classLoader = this.getClass().getClassLoader();
+        ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
+        ClassLoader classLoader2 = FileTest.class.getClassLoader();
+
+        String resourcePath = "templates/index.html";
+        URL resource = classLoader.getResource(resourcePath);
+        File file = new File(resource.toURI());
+        InputStream inputStream = classLoader.getResourceAsStream(resourcePath);
+
+        URL resource1 = classLoader1.getResource(resourcePath);
+        URL resource2 = classLoader2.getResource(resourcePath);
+
+        InputStream resourceAsStream = new ClassPathResource(resourcePath).getInputStream();
+
+
+        /*URL resource = Thread.currentThread().getContextClassLoader().getResource(avatarResource);
+        String avatarPath = resource.getPath();
+        System.out.println(avatarPath);
+        File file = new File(avatarPath);
+        byte[] imageBytes = Files.readAllBytes(file.toPath());*/
+
+        JarFileResources app = new JarFileResources();
+        // Sample 3 - read all files from a resources folder (jar version)
+        try {
+            // get paths from src/main/resources/json
+            List<Path> result = app.getPathsFromResourceJar("json");
+            for (Path path : result) {
+                System.out.println("Path : " + path);
+
+                String filePathInJar = path.toString();
+                // Windows will returns /json/file1.json, cut the first /
+                // the correct path should be json/file1.json
+                if (filePathInJar.startsWith("/")) {
+                    filePathInJar = filePathInJar.substring(1, filePathInJar.length());
+                }
+
+                System.out.println("filePathInJar : " + filePathInJar);
+                // read a file from resource folder
+                InputStream is = app.getFileFromResourceAsStream(filePathInJar);
+                printInputStream(is);
+            }
+        } catch (URISyntaxException | IOException e) {
+            e.printStackTrace();
+        }
+    }
 }