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

将 oss-console 模块从 bnt 项目迁移到 file-service

reghao 5 месяцев назад
Родитель
Сommit
0af6314585
49 измененных файлов с 2475 добавлено и 41 удалено
  1. 5 0
      file/file-service/pom.xml
  2. 37 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/config/BeanConfig.java
  3. 73 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/config/web/OssSdkInterceptor.java
  4. 12 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/config/web/WebConfig.java
  5. 2 2
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/FileOssController.java
  6. 146 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/OssSdkController.java
  7. 64 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/StoreNodeController.java
  8. 97 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/StoreObjectController.java
  9. 105 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/UploadChannelController.java
  10. 40 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/UserKeyController.java
  11. 74 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/UserNodeController.java
  12. 15 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/StoreNodeMapper.java
  13. 16 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/StoreVolumeMapper.java
  14. 24 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/UploadChannelMapper.java
  15. 17 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/UserKeyMapper.java
  16. 21 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/db/mapper/UserNodeMapper.java
  17. 41 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/db/repository/StoreRepository.java
  18. 21 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/dto/ChannelProcessDto.java
  19. 19 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/dto/KeyAuthDto.java
  20. 31 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/dto/NodeUpdateDto.java
  21. 31 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/dto/UploadChannelDto.java
  22. 22 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/dto/UserNodeDto.java
  23. 32 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/StoreNode.java
  24. 44 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/StoreVolume.java
  25. 38 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/UploadChannel.java
  26. 27 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/UserKey.java
  27. 30 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/po/UserNode.java
  28. 31 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/FileInfo.java
  29. 15 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/SelectOption.java
  30. 23 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/StoreDiskInfo.java
  31. 33 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/StoreNodeInfo.java
  32. 25 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/UploadChannelInfo.java
  33. 15 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/UserKeyVo.java
  34. 23 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/model/vo/UserNodeInfo.java
  35. 112 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/ConsoleServiceImpl.java
  36. 33 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/RemoteService.java
  37. 51 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/RpcService.java
  38. 148 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/StoreServiceWrapper.java
  39. 30 39
      file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/StoreServiceWrapperRouter.java
  40. 99 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/service/OssFileService.java
  41. 161 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/service/StoreNodeService.java
  42. 155 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UploadChannelService.java
  43. 102 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UserKeyService.java
  44. 137 0
      file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UserNodeService.java
  45. 37 0
      file/file-service/src/main/resources/mapper/StoreNodeMapper.xml
  46. 25 0
      file/file-service/src/main/resources/mapper/StoreVolumeMapper.xml
  47. 55 0
      file/file-service/src/main/resources/mapper/UploadChannelMapper.xml
  48. 36 0
      file/file-service/src/main/resources/mapper/UserKeyMapper.xml
  49. 45 0
      file/file-service/src/main/resources/mapper/UserNodeMapper.xml

+ 5 - 0
file/file-service/pom.xml

@@ -23,6 +23,11 @@
             <artifactId>common</artifactId>
             <version>1.0.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>cn.reghao.oss</groupId>
+            <artifactId>oss-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>cn.reghao.tnb.account</groupId>
             <artifactId>account-api</artifactId>

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

@@ -0,0 +1,37 @@
+package cn.reghao.tnb.file.app.config;
+
+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 org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * @author reghao
+ * @date 2023-08-02 09:14:17
+ */
+@Configuration
+public class BeanConfig {
+    @Bean
+    public RestTemplate restTemplate(){
+        return new RestTemplate();
+    }
+
+    @Bean
+    public WebRequest webRequest() {
+        //return new DefaultWebRequest();
+        return new WebClient();
+    }
+
+    @Bean
+    public ShellExecutor shellExecutor() {
+        return new ShellExecutor();
+    }
+
+    @Bean
+    public ByteConverter byteConverter() {
+        return new ByteConverter();
+    }
+}

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

@@ -0,0 +1,73 @@
+package cn.reghao.tnb.file.app.config.web;
+
+import cn.reghao.jutil.web.HeaderNames;
+import cn.reghao.jutil.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 lombok.extern.slf4j.Slf4j;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.UUID;
+
+/**
+ * 用户访问拦截器
+ *
+ * @author reghao
+ * @date 2023-07-31 10:19:07
+ */
+@Slf4j
+@Component
+public class OssSdkInterceptor implements HandlerInterceptor {
+    private final UserKeyService userKeyService;
+
+    public OssSdkInterceptor(UserKeyService userKeyService) {
+        this.userKeyService = userKeyService;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+            throws Exception {
+        String requestId = UUID.randomUUID().toString().replace("-", "");
+        request.setAttribute(HeaderNames.XRequestId, requestId);
+        long startTime = System.currentTimeMillis();
+        request.setAttribute(HeaderNames.XRequestTime, startTime);
+
+        String uri = request.getRequestURI();
+        String method = request.getMethod();
+        if (uri.equals("/api/oss/sdk/key/auth")) {
+            return true;
+        }
+
+        String token = ServletUtil.getBearerToken();
+        if (token == null || token.isBlank()) {
+            log.error("request {} start with /bg/api/oss/, but not auth", uri);
+            response.setStatus(403);
+            return false;
+        }
+
+        LoginUser loginUser = userKeyService.getUserFromToken(token);
+        if (loginUser == null) {
+            log.error("request {} start with /api/oss/sdk, but not auth", uri);
+            response.setStatus(403);
+            return false;
+        }
+        UserContext user = new UserContext(loginUser);
+        return true;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response,
+                           Object handler, @Nullable ModelAndView modelAndView) throws Exception {
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
+                                Object handler, @Nullable Exception ex) throws Exception {
+    }
+}

+ 12 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/config/web/WebConfig.java

@@ -14,6 +14,18 @@ import javax.servlet.Filter;
  */
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
+    private final OssSdkInterceptor ossSdkInterceptor;
+
+    public WebConfig(OssSdkInterceptor ossSdkInterceptor) {
+        this.ossSdkInterceptor = ossSdkInterceptor;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(ossSdkInterceptor)
+                .addPathPatterns("/api/oss/sdk/**");
+    }
+
     @Bean
     public FilterRegistrationBean<Filter> filterRegistrationBean1(TokenFilter tokenFilter) {
         FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();

+ 2 - 2
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/OssController.java → file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/FileOssController.java

@@ -18,10 +18,10 @@ import org.springframework.web.bind.annotation.RestController;
 @Tag(name = "OSS 接口")
 @RestController
 @RequestMapping("/api/file/oss")
-public class OssController {
+public class FileOssController {
     private final StoreServiceWrapperRouter storeServiceWrapperRouter;
 
-    public OssController(StoreServiceWrapperRouter storeServiceWrapperRouter) {
+    public FileOssController(StoreServiceWrapperRouter storeServiceWrapperRouter) {
         this.storeServiceWrapperRouter = storeServiceWrapperRouter;
     }
 

+ 146 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/OssSdkController.java

@@ -0,0 +1,146 @@
+package cn.reghao.tnb.file.app.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.oss.sdk.model.dto.ObjectInfo;
+import cn.reghao.oss.sdk.model.dto.ServerInfo;
+import cn.reghao.oss.sdk.model.dto.media.AudioInfo;
+import cn.reghao.oss.sdk.model.dto.media.ConvertedImageInfo;
+import cn.reghao.oss.sdk.model.dto.media.ImageInfo;
+import cn.reghao.oss.sdk.model.dto.media.VideoInfo;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.model.dto.KeyAuthDto;
+import cn.reghao.tnb.file.app.rpc.StoreServiceWrapper;
+import cn.reghao.tnb.file.app.service.UserKeyService;
+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.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 09:05:58
+ */
+@Tag(name = "oss-sdk 接口")
+@RestController
+@RequestMapping("/api/oss/sdk")
+public class OssSdkController {
+    private final UserKeyService userKeyService;
+    private final ConsoleService consoleService;
+    private final StoreServiceWrapper storeServiceWrapper;
+
+    public OssSdkController(UserKeyService userKeyService, ConsoleService consoleService, 
+                            StoreServiceWrapper storeServiceWrapper) {
+        this.userKeyService = userKeyService;
+        this.consoleService = consoleService;
+        this.storeServiceWrapper = storeServiceWrapper;
+    }
+
+    @Operation(summary = "对 oss key 进行认证", description = "N")
+    @PostMapping(value = "/key/auth", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String authKey(@Validated KeyAuthDto keyAuthDto) {
+        Result result = userKeyService.auth(keyAuthDto);
+        if (result.getCode() == 0) {
+            return WebResult.success(result.getData());
+        }
+
+        return WebResult.result(result);
+    }
+
+    @Operation(summary = "获取上传文件存储的 oss-store 节点", description = "N")
+    @GetMapping(value = "/upload/store", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getUploadStore(@RequestParam("channelCode") int channelCode) {
+        ServerInfo serverInfo = consoleService.getUploadStore(channelCode);
+        return WebResult.success(serverInfo);
+    }
+
+    @Operation(summary = "设置对象可见范围", description = "N")
+    @PostMapping(value = "/object/scope", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String setObjectScope(int channelCode, String objectId, int scope) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        storeServiceWrapper.setObjectScope(channelCode, objectId, scope, loginUser);
+        return WebResult.success();
+    }
+
+    @Operation(summary = "根据 objectId 删除对象", description = "N")
+    @PostMapping(value = "/object/delete/id", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteByObjectId(@RequestParam("channelCode") int channelCode,
+                                   @RequestParam("objectId") String objectId) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        storeServiceWrapper.deleteByObjectId(channelCode, objectId, loginUser);
+        return WebResult.success();
+    }
+
+    @Operation(summary = "根据 objectUrl 删除对象", description = "N")
+    @PostMapping(value = "/object/delete/url", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteByObjectUrl(@RequestParam("objectUrl") String objectUrl) {
+        int loginUser = (int) UserContext.getUserId();
+        storeServiceWrapper.deleteByObjectUrl(objectUrl, loginUser);
+        return WebResult.success();
+    }
+
+    @Operation(summary = "获取对象信息", description = "N")
+    @GetMapping(value = "/object/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getObjectInfo(@RequestParam("channelCode") int channelCode,
+                                @RequestParam("objectId") String objectId) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        ObjectInfo objectInfo = storeServiceWrapper.getObjectInfo(channelCode, objectId, loginUser);
+        return WebResult.success(objectInfo);
+    }
+
+    @Operation(summary = "获取对象签名 url", description = "N")
+    @GetMapping(value = "/object/url", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getSignedUrl(@RequestParam("channelCode") Integer channelCode,
+                               @RequestParam("objectId") String objectId) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        String signedUrl  = storeServiceWrapper.getSignedUrl(channelCode, objectId, loginUser);
+        return WebResult.success(signedUrl);
+    }
+
+    @Operation(summary = "根据对象 url 获取签名 url", description = "N")
+    @GetMapping(value = "/object/signed_url", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getSignedUrlByUrl(@RequestParam("channelCode") Integer channelCode,
+                                    @RequestParam("objectUrl") String objectUrl) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        String signedUrl  = storeServiceWrapper.getSignedUrlByUrl(channelCode, objectUrl, loginUser);
+        return WebResult.success(signedUrl);
+    }
+
+    @Operation(summary = "获取视频文件信息", description = "N")
+    @GetMapping(value = "/object/video/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getVideoInfo(@RequestParam("channelCode") Integer channelCode,
+                               @RequestParam("objectId") String objectId) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        VideoInfo videoInfo = storeServiceWrapper.getVideoInfo(channelCode, objectId, loginUser);
+        return WebResult.success(videoInfo);
+    }
+
+    @Operation(summary = "获取图片文件信息", description = "N")
+    @GetMapping(value = "/object/image/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getImagesInfo(@RequestParam("channelCode") Integer channelCode,
+                                @RequestParam("objectId") String objectId) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        ImageInfo imageInfo = storeServiceWrapper.getImageInfo(channelCode, objectId, loginUser);
+        return WebResult.success(imageInfo);
+    }
+
+    @Operation(summary = "获取 webp 图片文件信息", description = "N")
+    @PostMapping(value = "/object/image/webp", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getWebpInfo(@RequestParam("channelCode") Integer channelCode,
+                              @RequestParam("objectId") String objectId) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        ConvertedImageInfo convertedImageInfo = storeServiceWrapper.getWebpInfo(channelCode, objectId, loginUser);
+        return WebResult.success(convertedImageInfo);
+    }
+
+    @Operation(summary = "获取音频文件信息", description = "N")
+    @GetMapping(value = "/media/audio/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getAudioInfo(@RequestParam("channelCode") Integer channelCode,
+                               @RequestParam("objectId") String objectId) throws Exception {
+        int loginUser = (int) UserContext.getUserId();
+        AudioInfo audioInfo = storeServiceWrapper.getAudioInfo(channelCode, objectId, loginUser);
+        return WebResult.success(audioInfo);
+    }
+}

+ 64 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/StoreNodeController.java

@@ -0,0 +1,64 @@
+package cn.reghao.tnb.file.app.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.file.app.model.vo.SelectOption;
+import cn.reghao.tnb.file.app.model.vo.StoreDiskInfo;
+import cn.reghao.tnb.file.app.model.vo.StoreNodeInfo;
+import cn.reghao.tnb.file.app.service.StoreNodeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:29:14
+ */
+@Tag(name = "存储节点接口")
+@RestController
+@RequestMapping("/api/oss/store")
+public class StoreNodeController {
+    private final StoreNodeService storeNodeService;
+
+    public StoreNodeController(StoreNodeService storeNodeService) {
+        this.storeNodeService = storeNodeService;
+    }
+
+    @Operation(summary = "存储节点列表", description = "N")
+    @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String storeNodePage() {
+        List<StoreNodeInfo> list = storeNodeService.getByPage();
+        return WebResult.success(list);
+    }
+
+    @Operation(summary = "存储节点磁盘列表", description = "N")
+    @GetMapping(value = "/disk", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String storeNodePage(@RequestParam("storeNodeId") Integer storeNodeId) {
+        List<StoreDiskInfo> list = storeNodeService.getStoreDetail(storeNodeId);
+        return WebResult.success(list);
+    }
+
+    @Operation(summary = "存储节点 kv 列表", description = "N")
+    @GetMapping(value = "/kv", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String storeNodeKeyValue() {
+        List<SelectOption> storeNodes = storeNodeService.getNodeKeyValues();
+        return WebResult.success(storeNodes);
+    }
+
+    @Operation(summary = "设置存储节点的状态", description = "N")
+    @PostMapping(value = "/update_status", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String updateStoreNodeDomain(@RequestParam("id") int id) {
+        Result result = storeNodeService.updateStatus(id);
+        return WebResult.result(result);
+    }
+
+    @Operation(summary = "删除存储节点", description = "N")
+    @PostMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteStoreNode(Integer nodeId) {
+        Result result = storeNodeService.delete(nodeId);
+        return WebResult.result(result);
+    }
+}

+ 97 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/StoreObjectController.java

@@ -0,0 +1,97 @@
+package cn.reghao.tnb.file.app.controller;
+
+import cn.reghao.file.api.iface.FileService;
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.oss.sdk.model.dto.ObjectInfo;
+import cn.reghao.oss.sdk.model.dto.ServerInfo;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.model.vo.FileInfo;
+import cn.reghao.tnb.file.app.model.vo.UserNodeInfo;
+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 io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:12:14
+ */
+@Tag(name = "存储对象接口")
+@RestController
+@RequestMapping("/api/oss/object")
+public class StoreObjectController {
+    private final ConsoleService consoleService;
+    private final OssFileService ossFileService;
+    private final UserNodeService userNodeService;
+    private final UploadChannelService uploadChannelService;
+    private final FileService fileService;
+
+    public StoreObjectController(ConsoleService consoleService, OssFileService ossFileService,
+                                 UserNodeService userNodeService, UploadChannelService uploadChannelService,
+                                 FileService fileService) {
+        this.consoleService = consoleService;
+        this.ossFileService = ossFileService;
+        this.userNodeService = userNodeService;
+        this.uploadChannelService = uploadChannelService;
+        this.fileService = fileService;
+    }
+
+    @Operation(summary = "对象列表页面", description = "N")
+    @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String index(@RequestParam(value = "userNodeId", required = false) Integer userNodeId,
+                        @RequestParam(value = "path", required = false) String path) {
+        if (path == null || path.isBlank()) {
+            path = "/";
+        }
+
+        int loginUser = (int) UserContext.getUserId();
+        List<UserNodeInfo> userNodes = userNodeService.getUserNodes(loginUser);
+        if (userNodes.isEmpty()) {
+            String errMsg = "没有可用节点";
+            WebResult.failWithMsg(errMsg);
+        }
+
+        UserNodeInfo userNode;
+        if (userNodeId != null) {
+            userNode = userNodeService.getUserNodeInfo(userNodeId);
+        } else {
+            userNode = userNodes.get(0);
+            userNodeId = userNode.getUserNodeId();
+        }
+        String nodeDomain = userNode.getUserNodeDomain();
+
+        int pn = 1;
+        int ps = 10;
+        String objectName = "fileService.getObjectName(path)";
+        PageList<FileInfo> pageList = ossFileService.getFileList(nodeDomain, objectName, pn);
+        /*model.addAttribute("parentDirs", Collections.emptyList());
+        model.addAttribute("page", pageList);
+        model.addAttribute("userNodeId", userNodeId);
+        model.addAttribute("storeNodes", userNodes);
+        model.addAttribute("uploadPage", "/admin/oss/object/upload");
+        model.addAttribute("showTitle", 1);*/
+        return WebResult.success(pageList);
+    }
+
+    @Operation(summary = "对象详情页面", description = "N")
+    @GetMapping(value = "/detail", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String objectInfoPage(@RequestParam(value = "userNodeId") Integer userNodeId,
+                                 @RequestParam(value = "objectId") String objectId) throws Exception {
+        ObjectInfo objectInfo = ossFileService.getObjectInfo(userNodeId, objectId);
+        return WebResult.success(objectInfo);
+    }
+
+    @Operation(summary = "获取对象上传需要的 token", description = "N")
+    @PostMapping(value = "/serverinfo", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String uploadFile(Integer channelCode) {
+        ServerInfo serverInfo = consoleService.getUploadStore(channelCode);
+        return WebResult.success(serverInfo);
+    }
+}

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

@@ -0,0 +1,105 @@
+package cn.reghao.tnb.file.app.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.oss.api.constant.ObjectSize;
+import cn.reghao.oss.sdk.model.constant.ObjectScope;
+import cn.reghao.oss.sdk.model.constant.ObjectType;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.model.dto.UploadChannelDto;
+import cn.reghao.tnb.file.app.model.vo.SelectOption;
+import cn.reghao.tnb.file.app.model.vo.UploadChannelInfo;
+import cn.reghao.tnb.file.app.model.vo.UserNodeInfo;
+import cn.reghao.tnb.file.app.service.UploadChannelService;
+import cn.reghao.tnb.file.app.service.UserNodeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+@Slf4j
+@Tag(name = "上传通道接口")
+@RestController
+@RequestMapping("/api/oss/channel")
+public class UploadChannelController {
+    private final UserNodeService userNodeService;
+    private final UploadChannelService uploadChannelService;
+
+    public UploadChannelController(UserNodeService userNodeService, UploadChannelService uploadChannelService) {
+        this.userNodeService = userNodeService;
+        this.uploadChannelService = uploadChannelService;
+    }
+
+    @Operation(summary = "上传通道列表页面", description = "N")
+    @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String index(@RequestParam("userNodeId") Integer userNodeId) {
+        int loginUser = (int) UserContext.getUserId();
+        List<UserNodeInfo> userNodes = userNodeService.getUserNodes(loginUser);
+        if (userNodes.isEmpty()) {
+            String errMsg = "没有可用节点";
+            WebResult.failWithMsg(errMsg);
+        }
+
+        List<UploadChannelInfo> list = uploadChannelService.getChannelsByUserNode(userNodeId);
+        return WebResult.success(list);
+    }
+
+    @Operation(summary = "添加上传通道页面", description = "N")
+    @GetMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addChannelPage(@RequestParam("userNodeId") Integer userNodeId) {
+        List<SelectOption> objectTypes = new ArrayList<>();
+        for (ObjectType objectType : ObjectType.values()) {
+            if (objectType.getCode() == ObjectType.Dir.getCode()) {
+                continue;
+            }
+            objectTypes.add(new SelectOption(objectType.getCode()+"", objectType.name()));
+        }
+
+        List<SelectOption> objectScopes = new ArrayList<>();
+        for (ObjectScope objectScope : ObjectScope.values()) {
+            objectScopes.add(new SelectOption(objectScope.getCode()+"", objectScope.name()));
+        }
+
+        List<SelectOption> sizeList = Arrays.stream(ObjectSize.values())
+                .map(objectSize -> new SelectOption(objectSize.getSize()+"", objectSize.getDesc()))
+                .collect(Collectors.toList());
+
+        /*model.addAttribute("objectTypes", objectTypes);
+        model.addAttribute("objectScopes", objectScopes);
+        model.addAttribute("sizeList", sizeList);
+        model.addAttribute("userNodeId", userNodeId);*/
+        return WebResult.success(objectScopes);
+    }
+
+    @Operation(summary = "添加上传通道", description = "N")
+    @PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addUploadChannel(@Validated UploadChannelDto uploadChannelDto) throws Exception {
+        Result result = uploadChannelService.addObjectChannel(uploadChannelDto);
+        return WebResult.result(result);
+    }
+
+    @Operation(summary = "设置通道状态", description = "N")
+    @PostMapping(value = "/channel_status", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String setChannelStatus(Integer id) {
+        uploadChannelService.updateChannelStatus(id);
+        return WebResult.success();
+    }
+
+    @Operation(summary = "删除通道", description = "N")
+    @PostMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteUploadChannel(Integer id) throws Exception {
+        Result result = uploadChannelService.deleteChannel(id);
+        return WebResult.result(result);
+    }
+}

+ 40 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/UserKeyController.java

@@ -0,0 +1,40 @@
+package cn.reghao.tnb.file.app.controller;
+
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.file.app.model.vo.UserKeyVo;
+import cn.reghao.tnb.file.app.service.UserKeyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 10:24:31
+ */
+@Tag(name = "oss key 接口")
+@RestController
+@RequestMapping("/api/oss/key")
+public class UserKeyController {
+    private final UserKeyService userKeyService;
+
+    public UserKeyController(UserKeyService userKeyService) {
+        this.userKeyService = userKeyService;
+    }
+
+    @Operation(summary = "oss key 页面", description = "N")
+    @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String userKeyPage() {
+        List<UserKeyVo> list = userKeyService.getUserKeys();
+        return WebResult.success(list);
+    }
+
+    @Operation(summary = "重新生成  oss key", description = "N")
+    @PostMapping(value = "/regenerate", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String delete() {
+        userKeyService.regenerate();
+        return WebResult.success();
+    }
+}

+ 74 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/controller/UserNodeController.java

@@ -0,0 +1,74 @@
+package cn.reghao.tnb.file.app.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.model.dto.UserNodeDto;
+import cn.reghao.tnb.file.app.model.vo.SelectOption;
+import cn.reghao.tnb.file.app.model.vo.UserNodeInfo;
+import cn.reghao.tnb.file.app.service.UserNodeService;
+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 2019-08-30 18:49:15
+ */
+@Tag(name = "用户节点接口")
+@RestController
+@RequestMapping("/api/oss/my")
+public class UserNodeController {
+    private final UserNodeService userNodeService;
+
+    public UserNodeController(UserNodeService userNodeService) {
+        this.userNodeService = userNodeService;
+    }
+
+    @Operation(summary = "用户节点列表", description = "N")
+    @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String userNodesPage() {
+        int loginUser = (int) UserContext.getUserId();
+        List<UserNodeInfo> list = userNodeService.getUserNodes(loginUser);
+        return WebResult.success(list);
+    }
+
+    @Operation(summary = "用户节点 kv 列表", description = "N")
+    @GetMapping(value = "/kv", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String userNodeKeyValue() {
+        long loginUser = UserContext.getUserId();
+        List<SelectOption> storeNodes = userNodeService.getNodeKeyValues(loginUser);
+        return WebResult.success(storeNodes);
+    }
+
+    @Operation(summary = "添加用户节点", description = "N")
+    @PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addUserNode(@Validated UserNodeDto userNodeDto) {
+        Result result = userNodeService.add(userNodeDto);
+        return WebResult.result(result);
+    }
+
+    @Operation(summary = "更新用户节点", description = "N")
+    @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String updateNode(@Validated UserNodeDto userNodeDto) {
+        return WebResult.success();
+    }
+
+    @Operation(summary = "删除用户节点", description = "N")
+    @PostMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteUserNode(Integer userNodeId) {
+        Result result = userNodeService.delete(userNodeId);
+        return WebResult.result(result);
+    }
+
+    @Operation(summary = "用户节点详情", description = "N")
+    @GetMapping(value = "/detail", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String nodeDetailPage(@RequestParam(value = "userNodeId") Integer userNodeId) {
+        UserNodeInfo userNode = userNodeService.getUserNodeInfo(userNodeId);
+        return WebResult.success(userNode);
+    }
+}

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

@@ -0,0 +1,15 @@
+package cn.reghao.tnb.file.app.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author reghao
+ * @date 2025-10-16 14:12:42
+ */
+@Mapper
+public interface StoreNodeMapper extends BaseMapper<StoreNode> {
+    StoreNode findByNodeAddr(String nodeAddr);
+    StoreNode findById(int id);
+}

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

@@ -0,0 +1,16 @@
+package cn.reghao.tnb.file.app.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.file.app.model.po.StoreVolume;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-10-16 16:22:12
+ */
+@Mapper
+public interface StoreVolumeMapper extends BaseMapper<StoreVolume> {
+    List<StoreVolume> findByStoreNodeId(int storeNodeId);
+}

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

@@ -0,0 +1,24 @@
+package cn.reghao.tnb.file.app.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.file.app.model.po.UploadChannel;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-10-16 14:13:04
+ */
+@Mapper
+public interface UploadChannelMapper extends BaseMapper<UploadChannel> {
+    void deleteById(int id);
+
+    int countByCreateBy(long createBy);
+    List<UploadChannel> findByCreateBy(long createBy);
+    List<UploadChannel> findByCreateByAndUserNodeId(@Param("createBy") long createBy, @Param("userNodeId") int userNodeId);
+    UploadChannel findByCreateByAndChannelCode(@Param("createBy") long createBy, @Param("channelCode") int channelCode);
+    UploadChannel findByCreateByAndPrefix(@Param("createBy") long createBy, @Param("prefix") String prefix);
+    UploadChannel findById(int id);
+}

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

@@ -0,0 +1,17 @@
+package cn.reghao.tnb.file.app.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.file.app.model.po.UserKey;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author reghao
+ * @date 2025-10-16 14:13:12
+ */
+@Mapper
+public interface UserKeyMapper extends BaseMapper<UserKey> {
+    void updateByCreateBy(UserKey userKey);
+
+    UserKey findByCreateBy(long createBy);
+    UserKey findByAccessKeyId(String  accessKeyId);
+}

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

@@ -0,0 +1,21 @@
+package cn.reghao.tnb.file.app.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.file.app.model.po.UserNode;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-10-16 14:13:20
+ */
+@Mapper
+public interface UserNodeMapper extends BaseMapper<UserNode> {
+    int countByStoreNodeId(int storeNodeId);
+    UserNode findByCreateByAndStoreNodeId(@Param("createBy") long createBy, @Param("storeNodeId") int storeNodeId);
+    List<UserNode> findByCreateBy(long createBy);
+    UserNode findByDomain(String domain);
+    UserNode findById(int id);
+}

+ 41 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/db/repository/StoreRepository.java

@@ -0,0 +1,41 @@
+package cn.reghao.tnb.file.app.db.repository;
+
+import cn.reghao.oss.api.dto.StoreNodeDto;
+import cn.reghao.tnb.file.app.db.mapper.StoreNodeMapper;
+import cn.reghao.tnb.file.app.db.mapper.StoreVolumeMapper;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import cn.reghao.tnb.file.app.model.po.StoreVolume;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2025-10-17 09:14:47
+ */
+@Repository
+public class StoreRepository {
+    private final StoreNodeMapper storeNodeMapper;
+    private final StoreVolumeMapper storeVolumeMapper;
+
+    public StoreRepository(StoreNodeMapper storeNodeMapper, StoreVolumeMapper storeVolumeMapper) {
+        this.storeNodeMapper = storeNodeMapper;
+        this.storeVolumeMapper = storeVolumeMapper;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void saveStoreNode(StoreNodeDto storeNodeDto) {
+        StoreNode storeNode = new StoreNode(storeNodeDto);
+        storeNodeMapper.save(storeNode);
+
+        int storeNodeId = storeNode.getId();
+        List<StoreVolume> storeVolumes = storeNodeDto.getDiskVolumes().stream()
+                .map(diskVolume -> new StoreVolume(storeNodeId, diskVolume))
+                .collect(Collectors.toList());
+        if (!storeVolumes.isEmpty()) {
+            storeVolumeMapper.saveAll(storeVolumes);
+        }
+    }
+}

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

@@ -0,0 +1,21 @@
+package cn.reghao.tnb.file.app.model.dto;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2024-03-07 16:26:52
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class ChannelProcessDto {
+    @NotNull
+    private Integer id;
+    @NotNull
+    private Boolean seturl;
+}

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

@@ -0,0 +1,19 @@
+package cn.reghao.tnb.file.app.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Size;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 11:21:07
+ */
+@Setter
+@Getter
+public class KeyAuthDto {
+    @Size(min = 8, max = 32, message = "accessKeyId 长度应在 8~32 个字符之间")
+    private String accessKeyId;
+    @Size(min = 8, max = 32, message = "accessKeySecret 长度应在 8~32 个字符之间")
+    private String accessKeySecret;
+}

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

@@ -0,0 +1,31 @@
+package cn.reghao.tnb.file.app.model.dto;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * @author reghao
+ * @date 2023-03-10 15:00:20
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class NodeUpdateDto {
+    @NotNull
+    private Integer userNodeId;
+    @Pattern(regexp = "^[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,6}$", message = "域名格式不正确")
+    @Size(max = 128, message = "域名长度应小于 128 个字符")
+    private String domain;
+    @Pattern(regexp = "^[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,6}$", message = "referer 域名格式不正确")
+    @Size(max = 128, message = "referer 域名长度应小于 128 个字符")
+    private String referer;
+    @NotBlank(message = "必须设置 SecretKey")
+    @Size(min = 8, max = 32, message = "SecretKey 长度在 8 ~ 32 个字符之间")
+    private String secretKey;
+}

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

@@ -0,0 +1,31 @@
+package cn.reghao.tnb.file.app.model.dto;
+
+import cn.reghao.jutil.web.validator.ValidEnum;
+import cn.reghao.oss.api.constant.ObjectSize;
+import cn.reghao.oss.sdk.model.constant.ObjectScope;
+import cn.reghao.oss.sdk.model.constant.ObjectType;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * @author reghao
+ * @date 2021-06-03 16:15:23
+ */
+@Data
+public class UploadChannelDto {
+    //@Pattern(regexp = "(^/)([^\\\\:*<>|\"?\\r\\n\\s/]+/)*([^\\\\:*<>|\"?\\r\\n\\s/]+)?$", message = "通道前缀不匹配")
+    @Pattern(regexp = "([^\\\\:*<>|\"?\\r\\n\\s/]+/)*([^\\\\:*<>|\"?\\r\\n\\s/]+)/$", message = "通道前缀不匹配")
+    @Size(min = 2, max = 128, message = "通道前缀超过限制长度")
+    private String channelPrefix;
+    @ValidEnum(value = ObjectSize.class, message = "通道允许上传的对象大小不合法")
+    private Long maxSize;
+    @ValidEnum(value = ObjectType.class, message = "通道允许上传的对象类型不合法")
+    private Integer objectType;
+    @ValidEnum(value = ObjectScope.class, message = "通道作用域不合法")
+    private Integer scope;
+    @NotNull
+    private Integer userNodeId;
+}

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

@@ -0,0 +1,22 @@
+package cn.reghao.tnb.file.app.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:42:44
+ */
+@Setter
+@Getter
+public class UserNodeDto {
+    @NotNull
+    private Integer storeNodeId;
+    @Pattern(regexp = "^[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,6}$", message = "域名格式不正确")
+    @Size(max = 128, message = "域名长度应小于 128 个字符")
+    private String domain;
+}

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

@@ -0,0 +1,32 @@
+package cn.reghao.tnb.file.app.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:29:14
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+public class StoreNode extends BaseObject<Integer> {
+    private String nodeAddr;
+    @NotNull
+    private Integer httpPort;
+    @NotNull
+    private Integer rpcPort;
+    private Boolean enabled;
+
+    public StoreNode(StoreNodeDto storeNodeDto) {
+        this.nodeAddr = storeNodeDto.getNodeAddr();
+        this.httpPort = storeNodeDto.getHttpPort();
+        this.rpcPort = storeNodeDto.getRpcPort();
+        this.enabled = false;
+    }
+}

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

@@ -0,0 +1,44 @@
+package cn.reghao.tnb.file.app.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import cn.reghao.oss.api.dto.disk.DiskVolume;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-03-04 17:08:20
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Setter
+@Getter
+public class StoreVolume extends BaseObject<Integer> {
+    private int storeNodeId;
+    private String name;
+    private String volume;
+    private String mountPoint;
+    private String fsType;
+    private String blockId;
+    private long totalSpace;
+    private long availSpace;
+    private long totalInode;
+    private long availInode;
+    private String storeDir;
+
+    public StoreVolume(int storeNodeId, DiskVolume diskVolume) {
+        this.storeNodeId = storeNodeId;
+        this.name = diskVolume.getName();
+        this.volume = diskVolume.getVolume();
+        this.mountPoint = diskVolume.getMountPoint();
+        this.fsType = diskVolume.getFsType();
+        this.blockId = diskVolume.getBlockId();
+        this.totalSpace = diskVolume.getTotalSpace();
+        this.availSpace = diskVolume.getAvailSpace();
+        this.totalInode = diskVolume.getTotalInode();
+        this.availInode = diskVolume.getAvailInode();
+        this.storeDir = diskVolume.getStoreDir();
+    }
+}

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

@@ -0,0 +1,38 @@
+package cn.reghao.tnb.file.app.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import cn.reghao.tnb.file.app.model.dto.UploadChannelDto;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+public class UploadChannel extends BaseObject<Integer> {
+    private Integer channelCode;
+    private String prefix;
+    private Long maxSize;
+    private Integer fileType;
+    private Boolean seturl;
+    private Integer scope;
+    private Boolean enabled;
+    private Integer userNodeId;
+    private Integer createBy;
+
+    public UploadChannel(int channelCode, UploadChannelDto uploadChannelDto, int createBy) {
+        this.channelCode = channelCode;
+        this.prefix = uploadChannelDto.getChannelPrefix();
+        this.maxSize = uploadChannelDto.getMaxSize();
+        this.fileType = uploadChannelDto.getObjectType();
+        this.seturl = false;
+        this.scope = uploadChannelDto.getScope();
+        this.enabled = true;
+        this.userNodeId = uploadChannelDto.getUserNodeId();
+        this.createBy = createBy;
+    }
+}

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

@@ -0,0 +1,27 @@
+package cn.reghao.tnb.file.app.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 10:32:53
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class UserKey extends BaseObject<Integer> {
+    private String accessKeyId;
+    private String accessKeySecret;
+    private Long createBy;
+
+    public UserKey(String accessKeyId, String accessKeySecret, long createBy) {
+        this.accessKeyId = accessKeyId;
+        this.accessKeySecret = accessKeySecret;
+        this.createBy = createBy;
+    }
+}

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

@@ -0,0 +1,30 @@
+package cn.reghao.tnb.file.app.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import cn.reghao.tnb.file.app.model.dto.UserNodeDto;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:45:11
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class UserNode extends BaseObject<Integer> {
+    private Integer storeNodeId;
+    private String domain;
+    private String secretKey;
+    private String referer;
+    private Long createBy;
+
+    public UserNode(UserNodeDto userNodeDto, long createBy) {
+        this.storeNodeId = userNodeDto.getStoreNodeId();
+        this.domain = userNodeDto.getDomain();
+        this.createBy = createBy;
+    }
+}

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

@@ -0,0 +1,31 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import cn.reghao.oss.sdk.model.constant.ObjectType;
+import cn.reghao.oss.sdk.model.dto.ObjectInfo;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-10-30 10:24:28
+ */
+@AllArgsConstructor
+@Getter
+public class FileInfo {
+    private String fileId;
+    private String filename;
+    private String updateTime;
+    private String size;
+    private int fileType;
+    private String fileTypeStr;
+
+    public FileInfo(ObjectInfo objectInfo, String size) {
+        this.fileId = objectInfo.getObjectId();
+        this.filename = objectInfo.getFilename();
+        this.updateTime = objectInfo.getUpdateTime();
+        this.size = size;
+        this.fileType = objectInfo.getFileType();
+        this.fileTypeStr = ObjectType.getDescByCode(objectInfo.getFileType());
+    }
+}

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

@@ -0,0 +1,15 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2021-06-03 19:00:57
+ */
+@AllArgsConstructor
+@Getter
+public class SelectOption {
+    private String label;
+    private String value;
+}

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

@@ -0,0 +1,23 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-03-07 14:25:07
+ */
+@AllArgsConstructor
+@Getter
+public class StoreDiskInfo {
+    private String blockId;
+    private String fsType;
+    private String volume;
+    private String storeDir;
+    private String total;
+    private String used;
+    private double percent;
+    private String totalInode;
+    private String usedInode;
+    private double percentInode;
+}

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

@@ -0,0 +1,33 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-08-23 17:47:49
+ */
+@Getter
+public class StoreNodeInfo {
+    private int id;
+    private String nodeAddr;
+    private int httpPort;
+    private int rpcPort;
+    private String total;
+    private String used;
+    private double percent;
+    private String status;
+    private boolean enabled;
+
+    public StoreNodeInfo(StoreNode storeNode, String total, String used, double percent) {
+        this.id = storeNode.getId();
+        this.nodeAddr = storeNode.getNodeAddr();
+        this.httpPort = storeNode.getHttpPort();
+        this.rpcPort = storeNode.getRpcPort();
+        this.total = total;
+        this.used = used;
+        this.percent = percent;
+        this.status = storeNode.getEnabled() ? "可读写" : "只读";
+        this.enabled = storeNode.getEnabled();
+    }
+}

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

@@ -0,0 +1,25 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-02-28 13:16:02
+ */
+@AllArgsConstructor
+@Getter
+public class UploadChannelInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int id;
+    private int channelCode;
+    private String prefix;
+    private String maxSize;
+    private String fileType;
+    private boolean seturl;
+    private String scope;
+    private boolean enabled;
+}

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

@@ -0,0 +1,15 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-03-05 17:17:16
+ */
+@AllArgsConstructor
+@Getter
+public class UserKeyVo {
+    private String accessKeyId;
+    private String accessKeySecret;
+}

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

@@ -0,0 +1,23 @@
+package cn.reghao.tnb.file.app.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-07-02 16:18:14
+ */
+@AllArgsConstructor
+@Getter
+public class UserNodeInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int storeNodeId;
+    private String storeNodeAddr;
+    private int userNodeId;
+    private String userNodeDomain;
+    private String referer;
+    private String secretKey;
+}

+ 112 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/ConsoleServiceImpl.java

@@ -0,0 +1,112 @@
+package cn.reghao.tnb.file.app.rpc;
+
+import cn.reghao.oss.api.dto.NodeProperties;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.oss.sdk.model.dto.ServerInfo;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import cn.reghao.tnb.file.app.model.po.UploadChannel;
+import cn.reghao.tnb.file.app.model.po.UserNode;
+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 org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-02 10:58:51
+ */
+@DubboService
+@Service
+public class ConsoleServiceImpl implements ConsoleService {
+    private final UploadChannelService uploadChannelService;
+    private final StoreNodeService storeNodeService;
+    private final UserNodeService userNodeService;
+    private final RpcService rpcService;
+
+    public ConsoleServiceImpl(UploadChannelService uploadChannelService, StoreNodeService storeNodeService,
+                              UserNodeService userNodeService, RpcService rpcService) {
+        this.uploadChannelService = uploadChannelService;
+        this.storeNodeService = storeNodeService;
+        this.userNodeService = userNodeService;
+        this.rpcService = rpcService;
+    }
+
+    @Override
+    public void registerNode(StoreNodeDto storeNodeDto) {
+        storeNodeService.addOrUpdate(storeNodeDto);
+    }
+
+    @Override
+    public NodeProperties getNodeProperties(String domain) {
+        UserNode userNode = userNodeService.getUserNodeByDomain(domain);
+        if (userNode != null) {
+            String secretKey = userNode.getDomain();
+            String referer = userNode.getReferer();
+            long owner = userNode.getCreateBy();
+            return new NodeProperties(domain, secretKey, referer, (int) owner);
+        }
+
+        return null;
+    }
+
+    @Override
+    public ObjectChannel getChannelByCode(int owner, int channelCode) {
+        return uploadChannelService.getObjectChannelByChannelCode(channelCode, owner);
+    }
+
+    @Override
+    public Integer getChannelCodeByUrl(int owner, String url) {
+        List<UploadChannel> uploadChannels = uploadChannelService.getUploadChannelsByCreateBy(owner);
+        String url1 = url.replace("//", "");
+        int idx = url1.indexOf("/");
+        String objectName = url1.substring(idx+1);
+        for (UploadChannel uploadChannel : uploadChannels) {
+            if (objectName.startsWith(uploadChannel.getPrefix())) {
+                return uploadChannel.getChannelCode();
+            }
+        }
+
+        return -1;
+    }
+
+    @Override
+    public ServerInfo getUploadStore(int channelCode) {
+        long owner = UserContext.getUserId();
+        UploadChannel uploadChannel = uploadChannelService.getByChannelCodeAndCreateBy(channelCode, owner);
+        if (uploadChannel == null) {
+            String errMsg = String.format("channelCode %s not exist", channelCode);
+            return null;
+        }
+
+        UserNode userNode = userNodeService.getUserNode(uploadChannel.getUserNodeId());
+        if (userNode == null) {
+            String errMsg = String.format("channel_id %s not associate with any store_node", uploadChannel.getId());
+            return null;
+        }
+
+        String domain = userNode.getDomain();
+        String ossUrl = String.format("https://%s", domain);
+        long maxSize = uploadChannel.getMaxSize();
+
+        try {
+            StoreNode storeNode = storeNodeService.getByStoreNodeId(userNode.getStoreNodeId());
+            StoreService storeService = rpcService.getStoreService(storeNode);
+
+            // 多少秒后 token 过期
+            int expire = 3600;
+            String uploadToken = storeService.getUploadToken(channelCode, (int) owner, expire);
+            return new ServerInfo(ossUrl, channelCode, maxSize, uploadToken);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+}

+ 33 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/RemoteService.java

@@ -0,0 +1,33 @@
+package cn.reghao.tnb.file.app.rpc;
+
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ConsumerConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 13:19:20
+ */
+public class RemoteService<T> {
+    public T getService(String host, int port, Class<T> clazz) {
+        String serviceName = "remote-service";
+        String dubboUrl = String.format("dubbo://%s:%s/%s", host, port, clazz.getName());
+
+        // 当前应用配置
+        ApplicationConfig application = new ApplicationConfig();
+        application.setName(serviceName);
+
+        ConsumerConfig consumerConfig = new ConsumerConfig();
+        consumerConfig.setTimeout(60_000);
+
+        // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
+        // 引用远程服务
+        // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
+        ReferenceConfig<T> reference = new ReferenceConfig<>();
+        reference.setApplication(application);
+        reference.setInterface(clazz);
+        reference.setUrl(dubboUrl);
+        reference.setConsumer(consumerConfig);
+        return reference.get();
+    }
+}

+ 51 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/RpcService.java

@@ -0,0 +1,51 @@
+package cn.reghao.tnb.file.app.rpc;
+
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ConsumerConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author reghao
+ * @date 2024-07-05 10:44:51
+ */
+@Service
+public class RpcService {
+    public StoreService getStoreService(StoreNode storeNode) {
+        RemoteService<StoreService> remoteService = new RemoteService<>();
+        String host = storeNode.getNodeAddr();
+        int port = storeNode.getRpcPort();
+        return remoteService.getService(host, port, StoreService.class);
+    }
+
+    public StoreService getStoreService(String host, int port) {
+        RemoteService<StoreService> remoteService = new RemoteService<>();
+        return remoteService.getService(host, port, StoreService.class);
+    }
+
+    static class RemoteService<T> {
+        public T getService(String host, int port, Class<T> clazz) {
+            String serviceName = "remote-service";
+            String dubboUrl = String.format("dubbo://%s:%s/%s", host, port, clazz.getName());
+
+            // 当前应用配置
+            ApplicationConfig application = new ApplicationConfig();
+            application.setName(serviceName);
+
+            ConsumerConfig consumerConfig = new ConsumerConfig();
+            consumerConfig.setTimeout(60_000);
+
+            // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
+            // 引用远程服务
+            // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
+            ReferenceConfig<T> reference = new ReferenceConfig<>();
+            reference.setApplication(application);
+            reference.setInterface(clazz);
+            reference.setUrl(dubboUrl);
+            reference.setConsumer(consumerConfig);
+            return reference.get();
+        }
+    }
+}

+ 148 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/rpc/StoreServiceWrapper.java

@@ -0,0 +1,148 @@
+package cn.reghao.tnb.file.app.rpc;
+
+import cn.reghao.oss.api.dto.disk.DiskVolume;
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.oss.sdk.model.dto.ObjectInfo;
+import cn.reghao.oss.sdk.model.dto.media.AudioInfo;
+import cn.reghao.oss.sdk.model.dto.media.ConvertedImageInfo;
+import cn.reghao.oss.sdk.model.dto.media.ImageInfo;
+import cn.reghao.oss.sdk.model.dto.media.VideoInfo;
+import cn.reghao.tnb.file.app.db.mapper.StoreNodeMapper;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import cn.reghao.tnb.file.app.service.UserNodeService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 对 oss-store 进行操作
+ * 第三方通过 oss-console 调用
+ *
+ * @author reghao
+ * @date 2024-04-23 15:14:06
+ */
+@Service
+public class StoreServiceWrapper {
+    private final RpcService rpcService;
+    private final UserNodeService userNodeService;
+    private final StoreNodeMapper storeNodeMapper;
+
+    public StoreServiceWrapper(RpcService rpcService, UserNodeService userNodeService, StoreNodeMapper storeNodeMapper) {
+        this.rpcService = rpcService;
+        this.userNodeService = userNodeService;
+        this.storeNodeMapper = storeNodeMapper;
+    }
+
+    private StoreService getStoreService(int channelCode, int owner) throws Exception {
+        StoreNode storeNode = userNodeService.getStoreNodeByChannel(channelCode, owner);
+        if (storeNode == null) {
+            String errMsg = String.format("channel_id %s not associate with any store_node", channelCode);
+            throw new Exception(errMsg);
+        }
+
+        return rpcService.getStoreService(storeNode);
+    }
+
+    public void createChannel(int userNodeId, String channelPrefix, int scope, int owner) throws Exception {
+        StoreNode storeNode = userNodeService.getStoreNode(userNodeId);
+        StoreService storeService = rpcService.getStoreService(storeNode);
+        storeService.createChannel(owner, channelPrefix, scope);
+    }
+
+    public Integer countChannelObjects(int channelCode, String channelPrefix, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        return storeService.countChannelObjects(channelPrefix, owner);
+    }
+
+    public List<DiskVolume> getDiskVolumes(int storeNodeId) {
+        StoreNode storeNode = storeNodeMapper.findById(storeNodeId);
+        StoreService storeService = rpcService.getStoreService(storeNode);
+        return storeService.getDiskVolumes();
+    }
+
+    public void setObjectScope(int channelCode, String objectId, int scope, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        storeService.setObjectScope(objectId, scope);
+    }
+
+    public void deleteByObjectId(int channelCode, String objectId, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        storeService.deleteByObjectId(objectId);
+    }
+
+    public void deleteByObjectName(int channelCode, String objectName, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        storeService.deleteByObjectName(objectName, owner);
+    }
+
+    public void deleteByObjectUrl(String objectUrl, int owner) {
+        String domain = objectUrl.replace("//", "").split("/")[0];
+        String objectName = objectUrl.replace("//" + domain + "/", "");
+        StoreNode storeNode = userNodeService.getStoreNodeByDomain(domain);
+        StoreService storeService = rpcService.getStoreService(storeNode);
+        storeService.deleteByObjectName(objectName, owner);
+    }
+
+    public ObjectInfo getObjectInfo(int channelCode, String objectId, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        ObjectInfo objectInfo = storeService.getObjectInfo(objectId);
+        String domain = userNodeService.getDomain(channelCode, owner);
+        String url = String.format("//%s/%s", domain, objectInfo.getUrl());
+        objectInfo.setUrl(url);
+        return objectInfo;
+    }
+
+    public String getSignedUrl(int channelCode, String objectId, int owner) throws Exception {
+        String domain = userNodeService.getDomain(channelCode, owner);
+        StoreService storeService = getStoreService(channelCode, owner);
+        // url 有效期为 24h
+        int expire = 3600*24;
+        String signedUrl = storeService.getSignedUrl(domain, owner, objectId, expire);
+        return signedUrl;
+    }
+
+    public String getSignedUrlByUrl(int channelCode, String objectUrl, int owner) throws Exception {
+        String domain = userNodeService.getDomain(channelCode, owner);
+        StoreService storeService = getStoreService(channelCode, owner);
+        // url 有效期为 24h
+        int expire = 3600*24;
+        String signedUrl = storeService.getSignedUrl(objectUrl, owner, expire);
+        return signedUrl;
+    }
+
+    public VideoInfo getVideoInfo(int channelCode, String videoFileId, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        VideoInfo videoInfo = storeService.getVideoInfo(videoFileId);
+        String domain = userNodeService.getDomain(channelCode, owner);
+        String url = String.format("//%s/%s", domain, videoInfo.getUrl());
+        videoInfo.setUrl(url);
+        return videoInfo;
+    }
+
+    public ImageInfo getImageInfo(int channelCode, String imageFileId, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        ImageInfo imageInfo = storeService.getImageInfo(imageFileId);
+        String domain = userNodeService.getDomain(channelCode, owner);
+        String url = String.format("//%s/%s", domain, imageInfo.getUrl());
+        imageInfo.setUrl(url);
+        return imageInfo;
+    }
+
+    public ConvertedImageInfo getWebpInfo(int channelCode, String imageFileId, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        ConvertedImageInfo convertedImageInfo = storeService.getWebpInfo(imageFileId);
+        String domain = userNodeService.getDomain(channelCode, owner);
+        String url = String.format("//%s/%s", domain, convertedImageInfo.getUrl());
+        convertedImageInfo.setUrl(url);
+        return convertedImageInfo;
+    }
+
+    public AudioInfo getAudioInfo(int channelCode, String audioFileId, int owner) throws Exception {
+        StoreService storeService = getStoreService(channelCode, owner);
+        AudioInfo audioInfo = storeService.getAudioInfo(audioFileId);
+        String domain = userNodeService.getDomain(channelCode, owner);
+        String url = String.format("//%s/%s", domain, audioInfo.getUrl());
+        audioInfo.setUrl(url);
+        return audioInfo;
+    }
+}

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

@@ -1,6 +1,7 @@
 package cn.reghao.tnb.file.app.rpc;
 
 import cn.reghao.file.api.iface.OssService;
+import cn.reghao.oss.api.iface.ConsoleService;
 import cn.reghao.oss.sdk.model.constant.ObjectScope;
 import cn.reghao.oss.sdk.model.dto.ObjectInfo;
 import cn.reghao.oss.sdk.model.dto.ServerInfo;
@@ -8,9 +9,7 @@ import cn.reghao.oss.sdk.model.dto.media.AudioInfo;
 import cn.reghao.oss.sdk.model.dto.media.ConvertedImageInfo;
 import cn.reghao.oss.sdk.model.dto.media.ImageInfo;
 import cn.reghao.oss.sdk.model.dto.media.VideoInfo;
-import cn.reghao.oss.sdk.OssConsoleClient;
 import cn.reghao.tnb.file.app.config.AppProperties;
-import cn.reghao.tnb.file.app.config.OssConsoleClientFactory;
 import cn.reghao.tnb.file.app.model.constant.OssType;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.springframework.stereotype.Service;
@@ -22,25 +21,27 @@ import org.springframework.stereotype.Service;
 @DubboService
 @Service
 public class StoreServiceWrapperRouter implements OssService {
-    private final OssConsoleClientFactory ossConsoleClientFactory;
+    private int owner = 10001;
     private final int ossType;
+    private final ConsoleService consoleService;
+    private final StoreServiceWrapper storeServiceWrapper;
 
-    public StoreServiceWrapperRouter(OssConsoleClientFactory ossConsoleClientFactory, AppProperties appProperties) {
-        this.ossConsoleClientFactory = ossConsoleClientFactory;
+    public StoreServiceWrapperRouter(AppProperties appProperties, ConsoleService consoleService,
+                                     StoreServiceWrapper storeServiceWrapper) {
         this.ossType = appProperties.getOssType();
+        this.consoleService = consoleService;
+        this.storeServiceWrapper = storeServiceWrapper;
     }
 
-    public ServerInfo getUploadStore(int channelId) throws Exception {
-        OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-        ServerInfo serverInfo = ossConsoleClient.getUploadStore(channelId);
-        return serverInfo;
+    public ServerInfo getUploadStore(int channelCode) throws Exception {
+        return consoleService.getUploadStore(channelCode);
     }
 
-    public Integer getChannelScope(int channelId) {
+    public Integer getChannelScope(int channelCode) {
         /*try {
             if (ossType == OssType.localOss.getCode()) {
                 OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-                return ossConsoleClient.getChannelScope(channelId);
+                return ossConsoleClient.getChannelScope(channelCode);
             } else if (ossType == OssType.aliyunOss.getCode()) {
             }
         } catch (Exception e) {
@@ -49,44 +50,39 @@ public class StoreServiceWrapperRouter implements OssService {
         return ObjectScope.PRIVATE.getCode();
     }
 
-    public void setObjectScope(int channelId, String objectId, int scope) throws Exception {
+    public void setObjectScope(int channelCode, String objectId, int scope) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            ossConsoleClient.setObjectScope(channelId, objectId, scope);
+            storeServiceWrapper.setObjectScope(channelCode, objectId, scope, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
     }
 
-    public void deleteByObjectId(int channelId, String objectId) throws Exception {
+    public void deleteByObjectId(int channelCode, String objectId) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            ossConsoleClient.deleteByObjectId(channelId, objectId);
+            storeServiceWrapper.deleteByObjectId(channelCode, objectId, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
     }
 
     public void deleteByObjectUrl(String objectUrl) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            ossConsoleClient.deleteByObjectUrl(objectUrl);
+            storeServiceWrapper.deleteByObjectUrl(objectUrl, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
     }
 
-    public ObjectInfo getObjectInfo(int channelId, String objectId) throws Exception {
+    public ObjectInfo getObjectInfo(int channelCode, String objectId) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            return ossConsoleClient.getObjectInfo(channelId, objectId);
+            return storeServiceWrapper.getObjectInfo(channelCode, objectId, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
 
         throw new Exception("OSSType not found");
     }
 
-    public String getSignedUrl(int channelId, String objectId) throws Exception {
+    public String getSignedUrl(int channelCode, String objectId) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            return ossConsoleClient.getSignedUrl(channelId, objectId);
+            return storeServiceWrapper.getSignedUrl(channelCode, objectId, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
 
@@ -95,42 +91,37 @@ public class StoreServiceWrapperRouter implements OssService {
 
     public String getSignedUrlByUrl(int channelCode, String objectUrl) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            return ossConsoleClient.getSignedUrlByUrl(channelCode, objectUrl);
+            return storeServiceWrapper.getSignedUrlByUrl(channelCode, objectUrl, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
 
         throw new Exception("OSSType not found");
     }
 
-    public VideoInfo getVideoInfo(int channelId, String videoFileId) throws Exception {
-        OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-        return ossConsoleClient.getVideoInfo(channelId, videoFileId);
+    public VideoInfo getVideoInfo(int channelCode, String videoFileId) throws Exception {
+        return storeServiceWrapper.getVideoInfo(channelCode, videoFileId, owner);
     }
 
-    public ImageInfo getImageInfo(int channelId, String imageFileId) throws Exception {
+    public ImageInfo getImageInfo(int channelCode, String imageFileId) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            return ossConsoleClient.getImageInfo(channelId, imageFileId);
+            return storeServiceWrapper.getImageInfo(channelCode, imageFileId, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
 
         throw new Exception("OSSType not found");
     }
 
-    public ConvertedImageInfo getWebpInfo(int channelId, String imageFileId) throws Exception {
+    public ConvertedImageInfo getWebpInfo(int channelCode, String imageFileId) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            return ossConsoleClient.getWebpInfo(channelId, imageFileId);
+            return storeServiceWrapper.getWebpInfo(channelCode, imageFileId, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
         throw new Exception("OSSType not found");
     }
 
-    public AudioInfo getAudioInfo(int channelId, String audioFileId) throws Exception {
+    public AudioInfo getAudioInfo(int channelCode, String audioFileId) throws Exception {
         if (ossType == OssType.localOss.getCode()) {
-            OssConsoleClient ossConsoleClient = ossConsoleClientFactory.getOssConsoleClient();
-            return ossConsoleClient.getAudioInfo(channelId, audioFileId);
+            return storeServiceWrapper.getAudioInfo(channelCode, audioFileId, owner);
         } else if (ossType == OssType.aliyunOss.getCode()) {
         }
 

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

@@ -0,0 +1,99 @@
+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.db.PageList;
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.oss.sdk.model.constant.ObjectScope;
+import cn.reghao.oss.sdk.model.constant.ObjectType;
+import cn.reghao.oss.sdk.model.dto.ObjectInfo;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import cn.reghao.tnb.file.app.model.vo.FileInfo;
+import cn.reghao.tnb.file.app.model.vo.UserNodeInfo;
+import cn.reghao.tnb.file.app.rpc.RpcService;
+import org.apache.dubbo.rpc.RpcException;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-07-11 16:49:34
+ */
+@Service
+public class OssFileService {
+    private final ByteConverter byteConverter = new ByteConverter();
+    private final UserNodeService userNodeService;
+    private final RpcService rpcService;
+
+    public OssFileService(UserNodeService userNodeService, RpcService rpcService) {
+        this.userNodeService = userNodeService;
+        this.rpcService = rpcService;
+    }
+
+    public PageList<FileInfo> getFileList(String nodeDomain, String objectName, int pn) {
+        try {
+            StoreNode storeNode = userNodeService.getStoreNodeByDomain(nodeDomain);
+            if (storeNode == null) {
+                return PageList.empty();
+            }
+
+            String host = storeNode.getNodeAddr();
+            int port = storeNode.getRpcPort();
+            StoreService storeService = rpcService.getStoreService(host, port);
+
+            long owner = UserContext.getUserId();
+            int pz = 10;
+            PageList<ObjectInfo> pageList = storeService.getByPrefix((int) owner, objectName, pn, pz);
+            List<FileInfo> list = pageList.getList().stream().map(this::getFileInfo).collect(Collectors.toList());
+            return PageList.pageList(pn, pz, (int) pageList.getTotalSize(), list);
+        } catch (Exception e) {
+            if (e instanceof RpcException) {
+                //throw new Exception("RPC 调用失败");
+            }
+
+            e.printStackTrace();
+        }
+
+        return PageList.empty();
+    }
+
+    private FileInfo getFileInfo(ObjectInfo objectInfo) {
+        String size = byteConverter.convert(ByteType.Bytes, objectInfo.getSize());
+        return new FileInfo(objectInfo, size);
+    }
+
+    public ObjectInfo getObjectInfo(int userNodeId, String objectId) throws Exception {
+        UserNodeInfo userNodeInfo = userNodeService.getUserNodeInfo(userNodeId);
+        String nodeDomain = userNodeInfo.getUserNodeDomain();
+        StoreNode storeNode = userNodeService.getStoreNodeByDomain(nodeDomain);
+        if (storeNode == null) {
+            return null;
+        }
+
+        String host = storeNode.getNodeAddr();
+        int port = storeNode.getRpcPort();
+        StoreService storeService = rpcService.getStoreService(host, port);
+        ObjectInfo objectInfo = storeService.getObjectInfo(objectId);
+        if (objectInfo != null) {
+            if (objectInfo.getFileType() == ObjectType.Dir.getCode()) {
+                return objectInfo;
+            }
+
+            int scope = objectInfo.getScope();
+            if (scope == ObjectScope.PRIVATE.getCode()) {
+                long owner = UserContext.getUserId();
+                int expire = 3600;
+                String signedVideoUrl = storeService.getSignedUrl(nodeDomain, (int) owner, objectId, expire);
+                objectInfo.setUrl(signedVideoUrl);
+            } else {
+                String url = String.format("//%s/%s", nodeDomain, objectInfo.getUrl());
+                objectInfo.setUrl(url);
+            }
+        }
+
+        return objectInfo;
+    }
+}

+ 161 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/StoreNodeService.java

@@ -0,0 +1,161 @@
+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.math.Calculator;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+import cn.reghao.oss.api.dto.disk.DiskVolume;
+import cn.reghao.tnb.file.app.db.mapper.StoreNodeMapper;
+import cn.reghao.tnb.file.app.db.mapper.StoreVolumeMapper;
+import cn.reghao.tnb.file.app.db.mapper.UserNodeMapper;
+import cn.reghao.tnb.file.app.db.repository.StoreRepository;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import cn.reghao.tnb.file.app.model.po.StoreVolume;
+import cn.reghao.tnb.file.app.model.vo.SelectOption;
+import cn.reghao.tnb.file.app.model.vo.StoreDiskInfo;
+import cn.reghao.tnb.file.app.model.vo.StoreNodeInfo;
+import cn.reghao.tnb.file.app.rpc.StoreServiceWrapper;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 13:01:38
+ */
+@Service
+public class StoreNodeService {
+    private final ByteConverter byteConverter;
+    private final StoreNodeMapper storeNodeMapper;
+    private final StoreVolumeMapper storeVolumeMapper;
+    private final UserNodeMapper userNodeMapper;
+    private final StoreServiceWrapper storeServiceWrapper;
+    private StoreRepository storeRepository;
+
+    public StoreNodeService(ByteConverter byteConverter, StoreNodeMapper storeNodeMapper,
+                            StoreVolumeMapper storeVolumeMapper, UserNodeMapper userNodeMapper,
+                            StoreServiceWrapper storeServiceWrapper, StoreRepository storeRepository) {
+        this.byteConverter = byteConverter;
+        this.storeNodeMapper = storeNodeMapper;
+        this.storeVolumeMapper = storeVolumeMapper;
+        this.userNodeMapper = userNodeMapper;
+        this.storeServiceWrapper = storeServiceWrapper;
+        this.storeRepository = storeRepository;
+    }
+
+    public void addOrUpdate(StoreNodeDto storeNodeDto) {
+        String nodeAddr = storeNodeDto.getNodeAddr();
+        StoreNode storeNode = storeNodeMapper.findByNodeAddr(nodeAddr);
+        if (storeNode == null) {
+            storeRepository.saveStoreNode(storeNodeDto);
+        } else {
+            int storeNodeId = storeNode.getId();
+            List<StoreVolume> storeVolumes = storeNodeDto.getDiskVolumes().stream()
+                    .map(diskVolume -> new StoreVolume(storeNodeId, diskVolume))
+                    .collect(Collectors.toList());
+            //storeVolumeMapper.saveAll(storeVolumes);
+        }
+    }
+
+    public Result updateStatus(int storeNodeId) {
+        StoreNode storeNode = storeNodeMapper.findById(storeNodeId);
+        if (storeNode != null) {
+            boolean enabled = storeNode.getEnabled();
+            storeNode.setEnabled(!enabled);
+            storeNodeMapper.save(storeNode);
+            return Result.success();
+        }
+
+        return Result.fail("存储节点不存在");
+    }
+
+    public Result delete(int storeNodeId) {
+        StoreNode storeNode = storeNodeMapper.findById(storeNodeId);
+        if (storeNode == null) {
+            return Result.fail("node not exist");
+        }
+
+        int total = userNodeMapper.countByStoreNodeId(storeNodeId);
+        if (total > 0) {
+            return Result.fail("someone uses the node");
+        }
+
+        storeNodeMapper.delete(storeNode);
+        return Result.success();
+    }
+
+    public List<StoreNodeInfo> getByPage() {
+        List<StoreNode> page = storeNodeMapper.findAll();
+        List<StoreNodeInfo> list =  page.stream().map(storeNode -> {
+            List<StoreVolume> storeVolumeList = storeVolumeMapper.findByStoreNodeId(storeNode.getId());
+            long total = 0L;
+            long avail = 0L;
+            for (StoreVolume storeVolume : storeVolumeList) {
+                total += storeVolume.getTotalSpace();
+                avail += storeVolume.getAvailSpace();
+            }
+            long used = total - avail;
+
+            String totalStr = byteConverter.convert(ByteType.Bytes, total);
+            String usedStr = byteConverter.convert(ByteType.Bytes, used);
+            double percent = 0;
+            if (avail != 0) {
+                percent = Calculator.getPercentage(total, avail);
+            }
+            return new StoreNodeInfo(storeNode, totalStr, usedStr, percent);
+        }).collect(Collectors.toList());
+
+        return list;
+    }
+
+    public StoreNode getByStoreNodeId(int id) {
+        return storeNodeMapper.findById(id);
+    }
+
+    public List<SelectOption> getNodeKeyValues() {
+        return storeNodeMapper.findAll().stream()
+                .map(storeNode -> {
+                    int storeNodeId = storeNode.getId();
+                    String nodeAddr = storeNode.getNodeAddr();
+                    return new SelectOption(storeNodeId+"", nodeAddr);
+                }).collect(Collectors.toList());
+    }
+
+    public List<StoreDiskInfo> getStoreDetail(int storeNodeId) {
+        StoreNode storeNode = storeNodeMapper.findById(storeNodeId);
+        if (storeNode == null) {
+            return null;
+        }
+
+        List<DiskVolume> list = storeServiceWrapper.getDiskVolumes(storeNodeId);
+        List<StoreVolume> storeVolumes = list.stream()
+                .map(diskVolume -> new StoreVolume(storeNodeId, diskVolume))
+                .collect(Collectors.toList());
+        //storeVolumeMapper.saveAll(storeVolumes);
+
+        return list.stream()
+                .map(diskVolume -> {
+                    String blockId = diskVolume.getBlockId();
+                    String fsType = diskVolume.getFsType();
+                    String volume = diskVolume.getVolume();
+                    String storeDir = diskVolume.getStoreDir();
+
+                    long total = diskVolume.getTotalSpace();
+                    long avail = diskVolume.getAvailSpace();
+                    long used = total - avail;
+                    double percent = Calculator.getPercentage(total, avail);
+                    String totalStr = byteConverter.convert(ByteType.Bytes, total);
+                    String usedStr = byteConverter.convert(ByteType.Bytes, used);
+
+                    long totalInode = diskVolume.getTotalInode();
+                    long availInode = diskVolume.getAvailInode();
+                    long usedInode = totalInode - availInode;
+                    double percentInode = Calculator.getPercentage(totalInode, availInode);
+
+                    return new StoreDiskInfo(blockId, fsType, volume, storeDir,
+                            totalStr, usedStr, percent, totalInode+"", usedInode+"", percentInode);
+                }).collect(Collectors.toList());
+    }
+}

+ 155 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UploadChannelService.java

@@ -0,0 +1,155 @@
+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.result.Result;
+import cn.reghao.jutil.jdk.result.ResultStatus;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.sdk.model.constant.ObjectScope;
+import cn.reghao.oss.sdk.model.constant.ObjectType;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.db.mapper.UploadChannelMapper;
+import cn.reghao.tnb.file.app.model.dto.UploadChannelDto;
+import cn.reghao.tnb.file.app.model.po.UploadChannel;
+import cn.reghao.tnb.file.app.model.po.UserNode;
+import cn.reghao.tnb.file.app.model.vo.UploadChannelInfo;
+import cn.reghao.tnb.file.app.rpc.StoreServiceWrapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:14:53
+ */
+@Service
+public class UploadChannelService {
+    private final ByteConverter byteConverter;
+    private final UploadChannelMapper uploadChannelMapper;
+    private final UserNodeService userNodeService;
+    private final StoreServiceWrapper storeServiceWrapper;
+
+    public UploadChannelService(ByteConverter byteConverter, UploadChannelMapper uploadChannelMapper,
+                                UserNodeService userNodeService, StoreServiceWrapper storeServiceWrapper) {
+        this.byteConverter = byteConverter;
+        this.uploadChannelMapper = uploadChannelMapper;
+        this.userNodeService = userNodeService;
+        this.storeServiceWrapper = storeServiceWrapper;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public synchronized Result addObjectChannel(UploadChannelDto uploadChannelDto) throws Exception {
+        long createBy = UserContext.getUserId();
+        int userNodeId = uploadChannelDto.getUserNodeId();
+        UserNode userNode = userNodeService.getUserNode(userNodeId);
+        if (userNode == null) {
+            return Result.fail(String.format("store_node with id %s not exist", userNodeId));
+        }
+
+        String channelPrefix = uploadChannelDto.getChannelPrefix();
+        UploadChannel uploadChannel = uploadChannelMapper.findByCreateByAndPrefix(createBy, channelPrefix);
+        if (uploadChannel != null) {
+            return Result.fail(String.format("channel_prefix %s exist", channelPrefix));
+        }
+
+        int channelCode = getNextChannelCode(createBy);
+        uploadChannel = new UploadChannel(channelCode, uploadChannelDto, (int) createBy);
+        uploadChannelMapper.save(uploadChannel);
+
+        int scope = uploadChannel.getScope();
+        long loginUser = UserContext.getUserId();
+        storeServiceWrapper.createChannel(userNodeId, channelPrefix, scope, (int) loginUser);
+        return Result.success();
+    }
+
+    private int getNextChannelCode(long createBy) {
+        int channelCode = 101;
+        int total = uploadChannelMapper.countByCreateBy(createBy);
+        return channelCode + total;
+    }
+
+    public void updateChannelStatus(int id) {
+        UploadChannel uploadChannel = uploadChannelMapper.findById(id);
+        if (uploadChannel != null) {
+            boolean enabled = uploadChannel.getEnabled();
+            uploadChannel.setEnabled(!enabled);
+            uploadChannelMapper.save(uploadChannel);
+        }
+    }
+
+    public Result deleteChannel(int id) throws Exception {
+        UploadChannel uploadChannel = uploadChannelMapper.findById(id);
+        if (uploadChannel == null) {
+            String errMsg = String.format("通道不存在");
+            return Result.result(ResultStatus.FAIL, errMsg);
+        }
+
+        int channelCode = uploadChannel.getId();
+        String prefix = uploadChannel.getPrefix();
+        long loginUser = UserContext.getUserId();
+        Integer total = storeServiceWrapper.countChannelObjects(channelCode, prefix, (int) loginUser);
+        if (total != null && total == 0) {
+            long owner = UserContext.getUserId();
+            storeServiceWrapper.deleteByObjectName(channelCode, prefix, (int) owner);
+            uploadChannelMapper.deleteById(id);
+            return Result.result(ResultStatus.SUCCESS);
+        }
+
+        String errMsg = String.format("%s 通道中尚有对象存在", prefix);
+        return Result.result(ResultStatus.FAIL, errMsg);
+    }
+
+    public List<UploadChannelInfo> getChannelsByUserNode(int userNodeId) {
+        long createBy = UserContext.getUserId();
+        List<UploadChannel> page = uploadChannelMapper.findByCreateByAndUserNodeId(createBy, userNodeId);
+        List<UploadChannelInfo> list = page.stream().map(uploadChannel -> {
+            long maxSize = uploadChannel.getMaxSize();
+            String maxSizeStr = byteConverter.convert(ByteType.Bytes, maxSize);
+            int id = uploadChannel.getId();
+            int channelCode = uploadChannel.getChannelCode();
+            String prefix = uploadChannel.getPrefix();
+            String fileTypeStr = ObjectType.getDescByCode(uploadChannel.getFileType());
+            boolean seturl = uploadChannel.getSeturl();
+            String scopeStr = ObjectScope.getByCode(uploadChannel.getScope()).name();
+            boolean enabled = uploadChannel.getEnabled();
+            return new UploadChannelInfo(id, channelCode, prefix, maxSizeStr, fileTypeStr, seturl, scopeStr, enabled);
+        }).collect(Collectors.toList());
+
+        return list;
+    }
+
+    public List<UploadChannel> getUploadChannelsByCreateBy(long createBy) {
+        return uploadChannelMapper.findByCreateBy(createBy);
+    }
+
+    public UploadChannel getByChannelCodeAndCreateBy(int channelCode, long createBy) {
+        return uploadChannelMapper.findByCreateByAndChannelCode(createBy, channelCode);
+    }
+
+    public ObjectChannel getObjectChannelByChannelCode(int channelCode, long createBy) {
+        UploadChannel uploadChannel = uploadChannelMapper.findByCreateByAndChannelCode(createBy, channelCode);
+        return getObjectChannel(uploadChannel);
+    }
+
+    private ObjectChannel getObjectChannel(UploadChannel uploadChannel) {
+        int id = uploadChannel.getId();
+        int channelCode = uploadChannel.getChannelCode();
+        String channelPrefix = uploadChannel.getPrefix();
+        long maxSize = uploadChannel.getMaxSize();
+        int fileType = uploadChannel.getFileType();
+        boolean seturl = uploadChannel.getSeturl();
+        int scope = uploadChannel.getScope();
+        long createBy = uploadChannel.getCreateBy();
+
+        UserNode userNode = userNodeService.getUserNode(uploadChannel.getUserNodeId());
+        String domain = userNode.getDomain();
+        return new ObjectChannel(id, channelCode, channelPrefix, maxSize, fileType, seturl, scope, domain, (int) createBy);
+    }
+
+    public UploadChannel getUploadChannel(int channelCode) {
+        long loginUser = UserContext.getUserId();
+        return uploadChannelMapper.findByCreateByAndChannelCode(loginUser, channelCode);
+    }
+}

+ 102 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UserKeyService.java

@@ -0,0 +1,102 @@
+package cn.reghao.tnb.file.app.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.security.RandomString;
+import cn.reghao.oss.api.constant.ChannelAction;
+import cn.reghao.oss.api.dto.OssPayload;
+import cn.reghao.oss.api.util.JwtUtil;
+import cn.reghao.tnb.common.auth.LoginUser;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.db.mapper.UserKeyMapper;
+import cn.reghao.tnb.file.app.db.mapper.UserNodeMapper;
+import cn.reghao.tnb.file.app.model.dto.KeyAuthDto;
+import cn.reghao.tnb.file.app.model.po.UserKey;
+import cn.reghao.tnb.file.app.model.vo.UserKeyVo;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 10:25:25
+ */
+@Service
+public class UserKeyService {
+    private final String secretKey = "ossconsole.reghao.cn";
+    private final UserKeyMapper userKeyMapper;
+
+    public UserKeyService(UserKeyMapper userKeyMapper) {
+        this.userKeyMapper = userKeyMapper;
+    }
+
+    public Result auth(KeyAuthDto keyAuthDto) {
+        String accessKeyId = keyAuthDto.getAccessKeyId();
+        UserKey userKey = userKeyMapper.findByAccessKeyId(accessKeyId);
+        if (userKey == null) {
+            return Result.fail("key not exist");
+        }
+
+        String accessKeySecret = keyAuthDto.getAccessKeySecret();
+        if (userKey.getAccessKeySecret().equals(accessKeySecret)) {
+            long loginUser = userKey.getCreateBy();
+            String token = getTokenByUserId(loginUser);
+            return Result.success(token);
+        }
+
+        return Result.fail("secret not matched");
+    }
+
+    private String getTokenByUserId(long loginUser) {
+        long timestamp = System.currentTimeMillis() + 3600*24*365*1000L;
+        String action = ChannelAction.all.getName();
+        int channelCode = 1;
+        OssPayload ossPayload = new OssPayload(action, channelCode, (int) loginUser);
+        return JwtUtil.createToken(ossPayload, timestamp, secretKey);
+    }
+
+    public LoginUser getUserFromToken(String token) {
+        try {
+            OssPayload ossPayload = JwtUtil.getOssPayload(token, secretKey);
+            int userId = ossPayload.getUserId();
+            return new LoginUser(userId);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public void regenerate() {
+        long loginUser = UserContext.getUserId();
+        UserKey userKey = userKeyMapper.findByCreateBy(loginUser);
+        if (userKey == null) {
+            return;
+        }
+
+        String accessKeyId = RandomString.getString(8);
+        String accessKeySecret = RandomString.getString(18);
+        userKey.setAccessKeyId(accessKeyId);
+        userKey.setAccessKeySecret(accessKeySecret);
+        userKeyMapper.updateByCreateBy(userKey);
+    }
+
+    public List<UserKeyVo> getUserKeys() {
+        long loginUser = UserContext.getUserId();
+        UserKey userKey = userKeyMapper.findByCreateBy(loginUser);
+        if (userKey == null) {
+            userKey = create();
+        }
+
+        String accessKeyId = userKey.getAccessKeyId();
+        String accessKeySecret = userKey.getAccessKeySecret();
+        return List.of(new UserKeyVo(accessKeyId, accessKeySecret));
+    }
+
+    private UserKey create() {
+        long loginUser = UserContext.getUserId();
+        String accessKeyId = RandomString.getString(8);
+        String accessKeySecret = RandomString.getString(18);
+        UserKey userKey = new UserKey(accessKeyId, accessKeySecret, loginUser);
+        userKeyMapper.save(userKey);
+        return userKey;
+    }
+}

+ 137 - 0
file/file-service/src/main/java/cn/reghao/tnb/file/app/service/UserNodeService.java

@@ -0,0 +1,137 @@
+package cn.reghao.tnb.file.app.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.file.app.db.mapper.StoreNodeMapper;
+import cn.reghao.tnb.file.app.db.mapper.UploadChannelMapper;
+import cn.reghao.tnb.file.app.db.mapper.UserNodeMapper;
+import cn.reghao.tnb.file.app.model.dto.UserNodeDto;
+import cn.reghao.tnb.file.app.model.po.StoreNode;
+import cn.reghao.tnb.file.app.model.po.UploadChannel;
+import cn.reghao.tnb.file.app.model.po.UserNode;
+import cn.reghao.tnb.file.app.model.vo.SelectOption;
+import cn.reghao.tnb.file.app.model.vo.UserNodeInfo;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:49:56
+ */
+@Service
+public class UserNodeService {
+    private final UserNodeMapper userNodeMapper;
+    private final StoreNodeMapper storeNodeMapper;
+    private final UploadChannelMapper uploadChannelMapper;
+
+    public UserNodeService(UserNodeMapper userNodeMapper, StoreNodeMapper storeNodeMapper,
+                           UploadChannelMapper uploadChannelMapper) {
+        this.userNodeMapper = userNodeMapper;
+        this.storeNodeMapper = storeNodeMapper;
+        this.uploadChannelMapper = uploadChannelMapper;
+    }
+
+    public Result add(UserNodeDto userNodeDto) {
+        int storeNodeId = userNodeDto.getStoreNodeId();
+        long userId = UserContext.getUserId();
+        UserNode userNode = userNodeMapper.findByCreateByAndStoreNodeId(userId, storeNodeId);
+        if (userNode != null) {
+            return Result.fail("UserNode exist");
+        }
+
+        StoreNode storeNode = storeNodeMapper.findById(storeNodeId);
+        if (storeNode == null) {
+            return Result.success("StoreNode not exist");
+        }
+
+        userNode = new UserNode(userNodeDto, userId);
+        userNodeMapper.save(userNode);
+        return Result.success("node added");
+    }
+
+    public Result delete(int id) {
+        UserNode userNode = userNodeMapper.findById(id);
+        if (userNode != null) {
+            int userNodeId = userNode.getId();
+            long userId = UserContext.getUserId();
+            List<UploadChannel> uploadChannels = uploadChannelMapper.findByCreateByAndUserNodeId(userId, userNodeId);
+            if (!uploadChannels.isEmpty()) {
+                return Result.fail("UploadChannel exists");
+            }
+
+            userNodeMapper.delete(userNode);
+            return Result.success();
+        }
+
+        return Result.fail("UserNode not exists");
+    }
+
+    public List<UserNodeInfo> getUserNodes(int loginUser) {
+        List<UserNode> list = userNodeMapper.findByCreateBy(loginUser);
+        return list.stream()
+                .map(this::getUserNodeInfo)
+                .collect(Collectors.toList());
+    }
+
+    private UserNodeInfo getUserNodeInfo(UserNode userNode) {
+        int userNodeId = userNode.getId();
+        int storeNodeId = userNode.getStoreNodeId();
+        StoreNode storeNode = storeNodeMapper.findById(storeNodeId);
+        String nodeAddr = storeNode.getNodeAddr();
+        String domain = userNode.getDomain();
+        String referer = userNode.getReferer();
+        String secretKey = userNode.getSecretKey();
+        return new UserNodeInfo(storeNodeId, nodeAddr, userNodeId, domain, referer, secretKey);
+    }
+
+    public UserNode getUserNode(int userNodeId) {
+        return userNodeMapper.findById(userNodeId);
+    }
+
+    public UserNodeInfo getUserNodeInfo(int userNodeId) {
+        UserNode userNode = userNodeMapper.findById(userNodeId);
+        return getUserNodeInfo(userNode);
+    }
+
+    public UserNode getUserNodeByDomain(String domain) {
+        return userNodeMapper.findByDomain(domain);
+    }
+
+    public String getDomain(int channelCode, int loginUser) {
+        UploadChannel uploadChannel = uploadChannelMapper.findByCreateByAndChannelCode(loginUser, channelCode);
+        int userNodeId = uploadChannel.getUserNodeId();
+        UserNode userNode = userNodeMapper.findById(userNodeId);
+        return userNode.getDomain();
+    }
+
+    public StoreNode getStoreNode(int userNodeId) {
+        UserNode userNode = userNodeMapper.findById(userNodeId);
+        return storeNodeMapper.findById(userNode.getStoreNodeId());
+    }
+
+    public StoreNode getStoreNodeByChannel(int channelCode, int owner) throws Exception {
+        UploadChannel uploadChannel = uploadChannelMapper.findByCreateByAndChannelCode(owner, channelCode);
+        if (uploadChannel == null) {
+            String errMsg = String.format("channel_id %s not exist", channelCode);
+            throw new Exception(errMsg);
+        }
+
+        UserNode userNode = userNodeMapper.findById(uploadChannel.getUserNodeId());
+        return storeNodeMapper.findById(userNode.getStoreNodeId());
+    }
+
+    public StoreNode getStoreNodeByDomain(String domain) {
+        UserNode userNode = userNodeMapper.findByDomain(domain);
+        int storeNodeId = userNode.getStoreNodeId();
+        return storeNodeMapper.findById(storeNodeId);
+    }
+
+    public List<SelectOption> getNodeKeyValues(long createBy) {
+        return userNodeMapper.findByCreateBy(createBy).stream().map(userNode -> {
+            String userNodeDomain = userNode.getDomain();
+            return new SelectOption(userNodeDomain, ""+userNode.getId());
+        }).collect(Collectors.toList());
+    }
+}

+ 37 - 0
file/file-service/src/main/resources/mapper/StoreNodeMapper.xml

@@ -0,0 +1,37 @@
+<?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.StoreNodeMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into file_store_node
+        (`node_addr`,`http_port`,`rpc_port`,`enabled`)
+        values
+        (#{nodeAddr},#{httpPort},#{rpcPort},#{enabled})
+    </insert>
+
+    <select id="findAll" resultType="cn.reghao.tnb.file.app.model.po.StoreNode">
+        select *
+        from file_store_node
+    </select>
+    <select id="findByNodeAddr" resultType="cn.reghao.tnb.file.app.model.po.StoreNode">
+        select *
+        from file_store_node
+        where node_addr=#{nodeAddr}
+    </select>
+    <select id="findById" resultType="cn.reghao.tnb.file.app.model.po.StoreNode">
+        select *
+        from file_store_node
+        where id=#{id}
+    </select>
+    <resultMap id="imageAlbumCard" type="cn.reghao.tnb.file.app.model.po.StoreNode">
+        <result property="userId" column="user_id"/>
+        <result property="albumId" column="album_id"/>
+        <result property="albumName" column="album_name"/>
+        <collection property="imageFiles" column="album_id" ofType="cn.reghao.tnb.file.app.model.po.StoreVolume" select="findStoreVolumes"/>
+    </resultMap>
+    <select id="findStoreVolumes" resultType="cn.reghao.tnb.file.app.model.po.StoreVolume">
+        select *
+        from file_store_volume
+        where store_node_id=#{storeNodeId}
+    </select>
+</mapper>

+ 25 - 0
file/file-service/src/main/resources/mapper/StoreVolumeMapper.xml

@@ -0,0 +1,25 @@
+<?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.StoreVolumeMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into file_store_volume
+        (`store_node_id`,`name`,`volume`,`mount_point`,`fs_type`,`block_id`,`total_space`,`avail_space`,`total_inode`,`avail_inode`,`store_dir`)
+        values
+        (#{storeNodeId},#{name},#{volume},#{mountPoint},#{fsType},#{blockId},#{totalSpace},#{availSpace},#{totalInode},#{availInode},#{storeDir})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into file_store_volume
+        (`store_node_id`,`name`,`volume`,`mount_point`,`fs_type`,`block_id`,`total_space`,`avail_space`,`total_inode`,`avail_inode`,`store_dir`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.storeNodeId},#{item.name},#{item.volume},#{item.mountPoint},#{item.fsType},#{item.blockId},#{item.totalSpace},#{item.availSpace},#{item.totalInode},#{item.availInode},#{item.storeDir})
+        </foreach>
+    </insert>
+
+    <select id="findByStoreNodeId" resultType="cn.reghao.tnb.file.app.model.po.StoreVolume">
+        select *
+        from file_store_volume
+        where store_node_id=#{storeNodeId}
+    </select>
+</mapper>

+ 55 - 0
file/file-service/src/main/resources/mapper/UploadChannelMapper.xml

@@ -0,0 +1,55 @@
+<?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.UploadChannelMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into file_upload_channel
+        (`oss_type`,`endpoint`,`access_key_id`,`access_key_secret`,`region`,`bucket_name`,`role_arn`)
+        values
+        (#{ossType},#{endpoint},#{accessKeyId},#{accessKeySecret},#{region},#{bucketName},#{roleArn})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into file_upload_channel
+        (`oss_type`,`endpoint`,`access_key_id`,`access_key_secret`,`region`,`bucket_name`,`role_arn`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.ossType},#{item.endpoint},#{item.accessKeyId},#{item.accessKeySecret},#{item.region},#{item.bucketName},#{item.roleArn})
+        </foreach>
+    </insert>
+
+    <delete id="deleteById">
+        delete from file_upload_channel
+        where id=#{id}
+    </delete>
+
+    <select id="countByCreateBy" resultType="java.lang.Integer">
+        select count(*)
+        from file_upload_channel
+        where create_by=#{createBy}
+    </select>
+    <select id="findByCreateBy" resultType="cn.reghao.tnb.file.app.model.po.UploadChannel">
+        select *
+        from file_upload_channel
+        where create_by=#{createBy}
+    </select>
+    <select id="findByCreateByAndUserNodeId" resultType="cn.reghao.tnb.file.app.model.po.UploadChannel">
+        select *
+        from file_upload_channel
+        where create_by=#{createBy} and user_node_id=#{userNodeId}
+    </select>
+    <select id="findByCreateByAndChannelCode" resultType="cn.reghao.tnb.file.app.model.po.UploadChannel">
+        select *
+        from file_upload_channel
+        where create_by=#{createBy} and channel_code=#{channelCode}
+    </select>
+    <select id="findByCreateByAndPrefix" resultType="cn.reghao.tnb.file.app.model.po.UploadChannel">
+        select *
+        from file_upload_channel
+        where create_by=#{createBy} and prefix like concat(#{prefix},'%')
+    </select>
+    <select id="findById" resultType="cn.reghao.tnb.file.app.model.po.UploadChannel">
+        select *
+        from file_upload_channel
+        where id=#{id}
+    </select>
+</mapper>

+ 36 - 0
file/file-service/src/main/resources/mapper/UserKeyMapper.xml

@@ -0,0 +1,36 @@
+<?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.UserKeyMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into file_user_key
+        (`oss_type`,`endpoint`,`access_key_id`,`access_key_secret`,`region`,`bucket_name`,`role_arn`)
+        values
+        (#{ossType},#{endpoint},#{accessKeyId},#{accessKeySecret},#{region},#{bucketName},#{roleArn})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into file_user_key
+        (`oss_type`,`endpoint`,`access_key_id`,`access_key_secret`,`region`,`bucket_name`,`role_arn`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.ossType},#{item.endpoint},#{item.accessKeyId},#{item.accessKeySecret},#{item.region},#{item.bucketName},#{item.roleArn})
+        </foreach>
+    </insert>
+
+    <update id="updateByCreateBy">
+        update file_user_key
+        set access_key_id=#{accessKeyId},access_key_secret=#{accessKeySecret}
+        where create_by=#{createBy}
+    </update>
+
+    <select id="findByCreateBy" resultType="cn.reghao.tnb.file.app.model.po.UserKey">
+        select *
+        from file_user_key
+        where create_by=#{createBy}
+    </select>
+    <select id="findByAccessKeyId" resultType="cn.reghao.tnb.file.app.model.po.UserKey">
+        select *
+        from file_user_key
+        where access_key_id=#{accessKeyId}
+    </select>
+</mapper>

+ 45 - 0
file/file-service/src/main/resources/mapper/UserNodeMapper.xml

@@ -0,0 +1,45 @@
+<?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.UserNodeMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into file_user_node
+        (`oss_type`,`endpoint`,`access_key_id`,`access_key_secret`,`region`,`bucket_name`,`role_arn`)
+        values
+        (#{ossType},#{endpoint},#{accessKeyId},#{accessKeySecret},#{region},#{bucketName},#{roleArn})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into file_user_node
+        (`oss_type`,`endpoint`,`access_key_id`,`access_key_secret`,`region`,`bucket_name`,`role_arn`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.ossType},#{item.endpoint},#{item.accessKeyId},#{item.accessKeySecret},#{item.region},#{item.bucketName},#{item.roleArn})
+        </foreach>
+    </insert>
+
+    <select id="countByStoreNodeId" resultType="java.lang.Integer">
+        select *
+        from file_user_node
+        where create_by=#{createBy}
+    </select>
+    <select id="findByCreateByAndStoreNodeId" resultType="cn.reghao.tnb.file.app.model.po.UserNode">
+        select *
+        from file_user_node
+        where create_by=#{create_by} and store_node_id=#{storeNodeId}
+    </select>
+    <select id="findByCreateBy" resultType="cn.reghao.tnb.file.app.model.po.UserNode">
+        select *
+        from file_user_node
+        where create_by=#{create_by}
+    </select>
+    <select id="findByDomain" resultType="cn.reghao.tnb.file.app.model.po.UserNode">
+        select *
+        from file_user_node
+        where domain=#{domain}
+    </select>
+    <select id="findById" resultType="cn.reghao.tnb.file.app.model.po.UserNode">
+        select *
+        from file_user_node
+        where id=#{id}
+    </select>
+</mapper>