Sfoglia il codice sorgente

引入 oss 项目的 oss-console 模块

reghao 1 anno fa
parent
commit
e0a85e2b95
70 ha cambiato i file con 2905 aggiunte e 7 eliminazioni
  1. 1 1
      web/src/main/java/cn/reghao/bnt/web/account/service/HomeService.java
  2. 10 0
      web/src/main/java/cn/reghao/bnt/web/account/service/UserContext.java
  3. 5 3
      web/src/main/java/cn/reghao/bnt/web/admin/controller/page/FilePageController.java
  4. 39 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/KeyAuthController.java
  5. 115 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/OssController.java
  6. 42 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/StoreNodeController.java
  7. 76 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/StoreNodePageController.java
  8. 68 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/StoreObjectPageController.java
  9. 43 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/UploadChannelController.java
  10. 96 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/UploadChannelPageController.java
  11. 38 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserKeyController.java
  12. 35 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserKeyPageController.java
  13. 40 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserNodeController.java
  14. 60 0
      web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserNodePageController.java
  15. 13 0
      web/src/main/java/cn/reghao/bnt/web/console/db/mapper/StoreNodeRepository.java
  16. 19 0
      web/src/main/java/cn/reghao/bnt/web/console/db/mapper/UploadChannelRepository.java
  17. 12 0
      web/src/main/java/cn/reghao/bnt/web/console/db/mapper/UserKeyRepository.java
  18. 18 0
      web/src/main/java/cn/reghao/bnt/web/console/db/mapper/UserNodeRepository.java
  19. 13 0
      web/src/main/java/cn/reghao/bnt/web/console/db/repository/StoreNodeRepository.java
  20. 19 0
      web/src/main/java/cn/reghao/bnt/web/console/db/repository/UploadChannelRepository.java
  21. 13 0
      web/src/main/java/cn/reghao/bnt/web/console/db/repository/UserKeyRepository.java
  22. 18 0
      web/src/main/java/cn/reghao/bnt/web/console/db/repository/UserNodeRepository.java
  23. 15 0
      web/src/main/java/cn/reghao/bnt/web/console/model/constant/AppType.java
  24. 30 0
      web/src/main/java/cn/reghao/bnt/web/console/model/constant/BuildStatus.java
  25. 30 0
      web/src/main/java/cn/reghao/bnt/web/console/model/constant/DeployStatus.java
  26. 15 0
      web/src/main/java/cn/reghao/bnt/web/console/model/constant/build/CompileType.java
  27. 13 0
      web/src/main/java/cn/reghao/bnt/web/console/model/constant/build/RepoAuthType.java
  28. 13 0
      web/src/main/java/cn/reghao/bnt/web/console/model/constant/build/RepoType.java
  29. 21 0
      web/src/main/java/cn/reghao/bnt/web/console/model/dto/ChannelProcessDto.java
  30. 15 0
      web/src/main/java/cn/reghao/bnt/web/console/model/dto/KeyAuthDto.java
  31. 26 0
      web/src/main/java/cn/reghao/bnt/web/console/model/dto/NodeUpdateDto.java
  32. 20 0
      web/src/main/java/cn/reghao/bnt/web/console/model/dto/UserNodeDto.java
  33. 28 0
      web/src/main/java/cn/reghao/bnt/web/console/model/po/StoreDisk.java
  34. 45 0
      web/src/main/java/cn/reghao/bnt/web/console/model/po/StoreNode.java
  35. 39 0
      web/src/main/java/cn/reghao/bnt/web/console/model/po/UploadChannel.java
  36. 26 0
      web/src/main/java/cn/reghao/bnt/web/console/model/po/UserKey.java
  37. 33 0
      web/src/main/java/cn/reghao/bnt/web/console/model/po/UserNode.java
  38. 15 0
      web/src/main/java/cn/reghao/bnt/web/console/model/vo/KeyValue.java
  39. 16 0
      web/src/main/java/cn/reghao/bnt/web/console/model/vo/StoreDiskVO.java
  40. 36 0
      web/src/main/java/cn/reghao/bnt/web/console/model/vo/StoreNodeVO.java
  41. 15 0
      web/src/main/java/cn/reghao/bnt/web/console/model/vo/UserKeyVo.java
  42. 98 0
      web/src/main/java/cn/reghao/bnt/web/console/rpc/ConsoleServiceImpl.java
  43. 33 0
      web/src/main/java/cn/reghao/bnt/web/console/rpc/RemoteService.java
  44. 54 0
      web/src/main/java/cn/reghao/bnt/web/console/rpc/RpcService.java
  45. 245 0
      web/src/main/java/cn/reghao/bnt/web/console/rpc/StoreServiceWrapperImpl.java
  46. 29 0
      web/src/main/java/cn/reghao/bnt/web/console/service/DiskFileService.java
  47. 145 0
      web/src/main/java/cn/reghao/bnt/web/console/service/ObjectChannelServiceImpl.java
  48. 102 0
      web/src/main/java/cn/reghao/bnt/web/console/service/StoreChannelService.java
  49. 83 0
      web/src/main/java/cn/reghao/bnt/web/console/service/StoreNodeService.java
  50. 80 0
      web/src/main/java/cn/reghao/bnt/web/console/service/UploadChannelService.java
  51. 102 0
      web/src/main/java/cn/reghao/bnt/web/console/service/UserKeyService.java
  52. 103 0
      web/src/main/java/cn/reghao/bnt/web/console/service/UserNodeService.java
  53. 4 3
      web/src/main/resources/application.yml
  54. 0 0
      web/src/main/resources/templates/console/channel/add.html
  55. 0 0
      web/src/main/resources/templates/console/channel/edit.html
  56. 0 0
      web/src/main/resources/templates/console/channel/edit1.html
  57. 0 0
      web/src/main/resources/templates/console/channel/index.html
  58. 73 0
      web/src/main/resources/templates/console/node/detail.html
  59. 34 0
      web/src/main/resources/templates/console/node/edit.html
  60. 65 0
      web/src/main/resources/templates/console/node/index.html
  61. 11 0
      web/src/main/resources/templates/console/object/add.html
  62. 93 0
      web/src/main/resources/templates/console/object/index.html
  63. 86 0
      web/src/main/resources/templates/console/object/upload.html
  64. 35 0
      web/src/main/resources/templates/console/userkey/add.html
  65. 57 0
      web/src/main/resources/templates/console/userkey/index1.html
  66. 47 0
      web/src/main/resources/templates/console/usernode/add.html
  67. 10 0
      web/src/main/resources/templates/console/usernode/detail.html
  68. 72 0
      web/src/main/resources/templates/console/usernode/index1.html
  69. 0 0
      web/src/main/resources/templates/spider/chart.html
  70. 0 0
      web/src/main/resources/templates/spider/index.html

+ 1 - 1
web/src/main/java/cn/reghao/bnt/web/account/service/HomeService.java

@@ -50,7 +50,7 @@ public class HomeService {
         menus.forEach(menu -> keyMenu.put(menu.getId(), menu));
 
         // 树形菜单数据
-        Map<Integer, Menu> treeMenu = new HashMap<>(16);
+        Map<Integer, Menu> treeMenu = new TreeMap<>();
         keyMenu.forEach((id, menu) -> {
             Menu parentMenu = keyMenu.get(menu.getPid());
             if (parentMenu != null) {

+ 10 - 0
web/src/main/java/cn/reghao/bnt/web/account/service/UserContext.java

@@ -31,6 +31,16 @@ public class UserContext {
         return null;
     }
 
+    public static int getUserId() {
+        Authentication authToken = SecurityContextHolder.getContext().getAuthentication();
+        if (authToken instanceof AccountAuthToken) {
+            User user = (User) authToken.getDetails();
+            return user.getId();
+        }
+
+        return -1;
+    }
+
     public static String getUserRole() {
         User user = getUser();
         if (user != null && !user.getRole().isEmpty()) {

+ 5 - 3
web/src/main/java/cn/reghao/bnt/web/admin/controller/page/FilePageController.java

@@ -14,7 +14,6 @@ import cn.reghao.bnt.web.admin.model.vo.KeyValue;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.dubbo.config.annotation.DubboReference;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
@@ -33,8 +32,11 @@ import java.util.List;
 @Controller
 @RequestMapping("/file/store/channel")
 public class FilePageController {
-    @DubboReference(check = false)
-    private ObjectChannelService objectChannelService;
+    private final ObjectChannelService objectChannelService;
+
+    public FilePageController(ObjectChannelService objectChannelService) {
+        this.objectChannelService = objectChannelService;
+    }
 
     @ApiOperation(value = "上传通道列表页面")
     @GetMapping(value = "/list")

+ 39 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/KeyAuthController.java

@@ -0,0 +1,39 @@
+package cn.reghao.bnt.web.console.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.bnt.web.console.model.dto.KeyAuthDto;
+import cn.reghao.bnt.web.console.service.UserKeyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 11:32:23
+ */
+@Api(tags = "oss key 认证接口")
+@RestController
+@RequestMapping("/api/oss/key")
+public class KeyAuthController {
+    private final UserKeyService userKeyService;
+
+    public KeyAuthController(UserKeyService userKeyService) {
+        this.userKeyService = userKeyService;
+    }
+
+    @ApiOperation(value = "认证 oss key")
+    @PostMapping(value = "/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);
+    }
+}

+ 115 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/OssController.java

@@ -0,0 +1,115 @@
+package cn.reghao.bnt.web.console.controller;
+
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.api.dto.ObjectInfo;
+import cn.reghao.oss.api.dto.ServerInfo;
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.oss.api.iface.StoreServiceWrapper;
+import cn.reghao.oss.api.util.AuthContext;
+import cn.reghao.oss.api.dto.media.*;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 09:05:58
+ */
+@Api(tags = "视频文件接口")
+@RestController
+@RequestMapping("/api/oss")
+public class OssController {
+    private final ConsoleService consoleService;
+    private final StoreServiceWrapper storeServiceWrapper;
+
+    public OssController(ConsoleService consoleService, StoreServiceWrapper storeServiceWrapper) {
+        this.consoleService = consoleService;
+        this.storeServiceWrapper = storeServiceWrapper;
+    }
+
+    @ApiOperation(value = "获取 oss-store 节点")
+    @GetMapping(value = "/upload/store", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getUploadStore(@RequestParam("channelId") int channelId) {
+        int loginUser = AuthContext.getUserId();
+        ServerInfo serverInfo = consoleService.getUploadStore(loginUser, channelId);
+        return WebResult.success(serverInfo);
+    }
+
+    @ApiOperation(value = "获取 channel 可见范围")
+    @GetMapping(value = "/channel/scope", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getChannelScope(int channelId) throws Exception {
+        int scope = storeServiceWrapper.getChannelScope(channelId);
+        return WebResult.success(scope);
+    }
+
+    @ApiOperation(value = "设置对象可见范围")
+    @PostMapping(value = "/object/scope", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String setObjectScope(int channelId, String objectId, int scope) {
+        storeServiceWrapper.setObjectScope(channelId, objectId, scope);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "根据 object_id 删除对象")
+    @PostMapping(value = "/object/delete/id", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteByObjectId(@RequestParam("channelId") int channelId,
+                                   @RequestParam("objectId") String objectId) {
+        storeServiceWrapper.deleteByObjectId(channelId, objectId);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "根据 object_url 删除对象")
+    @PostMapping(value = "/object/delete/url", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteByObjectUrl(@RequestParam("objectUrl") String objectUrl) {
+        storeServiceWrapper.deleteByObjectUrl(objectUrl);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "获取对象信息")
+    @GetMapping(value = "/object/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getObjectInfo(@RequestParam("channelId") int channelId,
+                                @RequestParam("objectId") String objectId) throws Exception {
+        ObjectInfo objectInfo = storeServiceWrapper.getObjectInfo(channelId, objectId);
+        return WebResult.success(objectInfo);
+    }
+
+    @ApiOperation(value = "获取对象签名 url")
+    @GetMapping(value = "/object/url", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getSignedUrl(@RequestParam("channelId") Integer channelId,
+                               @RequestParam("objectId") String objectId) throws Exception {
+        String signedUrl = storeServiceWrapper.getSignedUrl(channelId, objectId);
+        return WebResult.success(signedUrl);
+    }
+
+    @ApiOperation(value = "获取视频文件信息")
+    @GetMapping(value = "/object/video/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getVideoInfo(@RequestParam("channelId") Integer channelId,
+                               @RequestParam("objectId") String objectId) throws Exception {
+        VideoInfo videoInfo = storeServiceWrapper.getVideoInfo(channelId, objectId);
+        return WebResult.success(videoInfo);
+    }
+
+    @ApiOperation(value = "获取图片文件信息")
+    @GetMapping(value = "/object/image/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getImagesInfo(@RequestParam("channelId") Integer channelId,
+                                @RequestParam("objectId") String objectId) throws Exception {
+        ImageInfo imageInfo = storeServiceWrapper.getImageInfo(channelId, objectId);
+        return WebResult.success(imageInfo);
+    }
+
+    @ApiOperation(value = "获取 webp 图片文件信息")
+    @PostMapping(value = "/object/image/webp", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getWebpInfo(@RequestParam("channelId") Integer channelId,
+                              @RequestParam("objectId") String objectId) throws Exception {
+        ConvertedImageInfo convertedImageInfo = storeServiceWrapper.getWebpInfo(channelId, objectId);
+        return WebResult.success(convertedImageInfo);
+    }
+
+    @ApiOperation(value = "获取音频文件信息")
+    @GetMapping(value = "/media/audio/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getAudioInfo(@RequestParam("channelId") Integer channelId,
+                               @RequestParam("objectId") String objectId) throws Exception {
+        AudioInfo audioInfo = storeServiceWrapper.getAudioInfo(channelId, objectId);
+        return WebResult.success(audioInfo);
+    }
+}

+ 42 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/StoreNodeController.java

@@ -0,0 +1,42 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.bnt.web.console.model.dto.NodeUpdateDto;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.bnt.web.console.service.StoreNodeService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:29:14
+ */
+@Slf4j
+@Api(tags = "存储节点数据接口")
+@RestController
+@RequestMapping("/api/store/node")
+public class StoreNodeController {
+    private final StoreNodeService storeNodeService;
+
+    public StoreNodeController(StoreNodeService storeNodeService) {
+        this.storeNodeService = storeNodeService;
+    }
+
+    @ApiOperation(value = "设置存储节点的状态")
+    @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String updateStoreNodeDomain() {
+        storeNodeService.updateStatus();
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "删除存储节点")
+    @DeleteMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteStoreNode(@PathVariable("id") Integer nodeId) {
+        Result result = storeNodeService.delete(nodeId);
+        return WebResult.result(result);
+    }
+}

+ 76 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/StoreNodePageController.java

@@ -0,0 +1,76 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.vo.StoreNodeVO;
+import cn.reghao.bnt.web.console.service.StoreChannelService;
+import cn.reghao.bnt.web.console.service.StoreNodeService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:29:14
+ */
+@Api(tags = "存储节点页面")
+@Controller
+@RequestMapping("/store/node")
+public class StoreNodePageController {
+    private final StoreNodeService storeNodeService;
+    private final StoreChannelService storeChannelService;
+
+    public StoreNodePageController(StoreNodeService storeNodeService, StoreChannelService storeChannelService) {
+        this.storeNodeService = storeNodeService;
+        this.storeChannelService = storeChannelService;
+    }
+
+    @ApiOperation(value = "存储节点列表页面")
+    @GetMapping("/list")
+    public String storeNodePage(@RequestParam(value = "env", required = false) String env,
+                                @RequestParam(value = "type", required = false) String type,
+                                @RequestParam(value = "appName", required = false) String appName,
+                                Model model) {
+        if (env == null) {
+            env = "test";
+        }
+
+        if (type == null) {
+            type = "test";
+        }
+
+        if (appName != null) {
+            Map<String, String> map = new HashMap<>();
+            map.put("appName", appName);
+            //List<AppConfig> list = appConfigQuery.query(map);
+            List<String> list = Collections.emptyList();
+            //model.addAttribute("page", page);
+            model.addAttribute("list", list);
+            return "/console/node/index";
+        }
+
+        List<StoreNode> list = storeNodeService.getByPage();
+        //model.addAttribute("page", page);
+        model.addAttribute("list", list);
+        return "/console/node/index";
+    }
+
+    @ApiOperation(value = "存储节点域名设置页面")
+    @GetMapping("/config/{id}")
+    public String editStoreNodePage(@PathVariable("id") Integer id, Model model) {
+        StoreNode storeNode = storeNodeService.getById(id);
+        model.addAttribute("storeNode", storeNode);
+        return "/console/node/edit";
+    }
+
+    @ApiOperation(value = "存储节点详情页面")
+    @GetMapping("/detail/{id}")
+    public String storeNodePage(@PathVariable("id") Integer id, Model model) {
+        StoreNodeVO storeNode = storeChannelService.getStoreDetail(id);
+        model.addAttribute("storeNode", storeNode);
+        return "/console/node/detail";
+    }
+}

+ 68 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/StoreObjectPageController.java

@@ -0,0 +1,68 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.api.dto.FileInfo;
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.oss.api.dto.ServerInfo;
+import cn.reghao.bnt.web.console.service.DiskFileService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:12:14
+ */
+@Api(tags = "存储对象页面")
+@Controller
+@RequestMapping("/store/object")
+public class StoreObjectPageController {
+    private final ConsoleService consoleService;
+    private DiskFileService diskFileService;
+
+    public StoreObjectPageController(ConsoleService consoleService, DiskFileService diskFileService) {
+        this.consoleService = consoleService;
+        this.diskFileService = diskFileService;
+    }
+
+    @ApiOperation(value = "文件列表页面")
+    @GetMapping(value = "/list")
+    public String index(@RequestParam(value = "env", required = false) String env,
+                        @RequestParam(value = "type", required = false) String type,
+                        @RequestParam(value = "appName", required = false) String appName, Model model) throws Exception {
+
+        int pageNumber = 1;
+        List<FileInfo> list = diskFileService.getFileList(pageNumber);
+
+        model.addAttribute("list", list);
+        return "/console/object/index";
+    }
+
+    @ApiOperation(value = "文件上传页面")
+    @GetMapping(value = "/upload")
+    public String uploadPage(Model model) {
+        int loginUser = UserContext.getUserId();
+        int channelId = 105;
+        try {
+            ServerInfo serverInfo = consoleService.getUploadStore(loginUser, channelId);
+            model.addAttribute("serverInfo", serverInfo);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return "/console/object/upload";
+    }
+
+    @ApiOperation(value = "文件上传接口")
+    @PostMapping(value = "/upload")
+    @ResponseBody
+    public String uploadFile(MultipartFile[] files) {
+        return WebResult.success();
+    }
+}

+ 43 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/UploadChannelController.java

@@ -0,0 +1,43 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.api.dto.ChannelScopeDto;
+import cn.reghao.oss.api.dto.UploadChannelDto;
+import cn.reghao.oss.api.iface.ObjectChannelService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+@Slf4j
+@Api(tags = "上传通道接口")
+@RestController
+@RequestMapping("/api/store/channel")
+public class UploadChannelController {
+    private final ObjectChannelService objectChannelService;
+
+    public UploadChannelController(ObjectChannelService objectChannelService) {
+        this.objectChannelService = objectChannelService;
+    }
+
+    @ApiOperation(value = "添加上传通道")
+    @PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addUploadChannel(@Validated UploadChannelDto uploadChannelDto) {
+        Result result = objectChannelService.addObjectChannel(uploadChannelDto);
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "修改通道可见范围")
+    @PostMapping(value = "/update/scope", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String updateChannelScope(@Validated ChannelScopeDto channelScopeDto) {
+        objectChannelService.updateScope(channelScopeDto);
+        return WebResult.success();
+    }
+}

+ 96 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/UploadChannelPageController.java

@@ -0,0 +1,96 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.oss.api.constant.ObjectScope;
+import cn.reghao.oss.api.constant.ObjectType;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.iface.ObjectChannelService;
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.bnt.web.console.model.vo.KeyValue;
+import cn.reghao.oss.api.dto.UploadChannelVo;
+import cn.reghao.oss.api.dto.UserNodeVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+@Slf4j
+@Api(tags = "上传通道页面")
+@Controller
+@RequestMapping("/store/channel")
+public class UploadChannelPageController {
+    private final ObjectChannelService objectChannelService;
+
+    public UploadChannelPageController(ObjectChannelService objectChannelService) {
+        this.objectChannelService = objectChannelService;
+    }
+
+    @ApiOperation(value = "上传通道列表页面")
+    @GetMapping(value = "/list")
+    public String index(@RequestParam(value = "env", required = false) String env,
+                        @RequestParam(value = "type", required = false) String type,
+                        @RequestParam(value = "appName", required = false) String appName, Model model) throws Exception {
+        int loginUser = UserContext.getUserId();
+        List<UploadChannelVo> list = objectChannelService.getObjectChannels(loginUser);
+        model.addAttribute("list", list);
+        return "/console/channel/index";
+    }
+
+    @ApiOperation(value = "新增上传通道页面")
+    @GetMapping("/add")
+    public String addChannelPage(Model model) {
+        List<KeyValue> objectTypes = new ArrayList<>();
+        for (ObjectType objectType : ObjectType.values()) {
+            if (objectType.getCode() == 1000) {
+                continue;
+            }
+            objectTypes.add(new KeyValue(objectType.getCode()+"", objectType.name()));
+        }
+
+        List<KeyValue> objectScopes = new ArrayList<>();
+        for (ObjectScope objectScope : ObjectScope.values()) {
+            objectScopes.add(new KeyValue(objectScope.getCode()+"", objectScope.name()));
+        }
+
+        List<KeyValue> sizeList = new ArrayList<>();
+        sizeList.add(new KeyValue(1024L*1024*2+"", "2MB"));
+        sizeList.add(new KeyValue(1024L*1024*10+"", "10MB"));
+        sizeList.add(new KeyValue(1024L*1024*100+"", "100MB"));
+        sizeList.add(new KeyValue(1024L*1024*1024+"", "1GB"));
+        sizeList.add(new KeyValue(1024L*1024*1024*10+"", "10GB"));
+        sizeList.add(new KeyValue(1024L*1024*1024*20+"", "20GB"));
+
+        int loginUser = UserContext.getUserId();
+        List<UserNodeVO> storeNodes = objectChannelService.getUserStoreNodes(loginUser);
+
+        model.addAttribute("objectTypes", objectTypes);
+        model.addAttribute("objectScopes", objectScopes);
+        model.addAttribute("sizeList", sizeList);
+        model.addAttribute("storeNodes", storeNodes);
+        return "/console/channel/add";
+    }
+
+    @ApiOperation(value = "修改通道可见范围")
+    @GetMapping("/edit/{id}")
+    public String editChannelScopePage(@PathVariable("id") Integer id, Model model) {
+        ObjectChannel objectChannel = objectChannelService.getObjectChannel(id);
+        int scope = objectChannel.getScope();
+        ObjectScope objectScope0 = ObjectScope.getByCode(scope);
+
+        List<KeyValue> objectScopes = new ArrayList<>();
+        for (ObjectScope objectScope : ObjectScope.values()) {
+            objectScopes.add(new KeyValue(objectScope.getCode()+"", objectScope.name()));
+        }
+
+        model.addAttribute("objectScope", objectScope0.getName());
+        model.addAttribute("objectScopes", objectScopes);
+        return "/console/channel/edit";
+    }
+}

+ 38 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserKeyController.java

@@ -0,0 +1,38 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.bnt.web.console.service.UserKeyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 10:59:07
+ */
+@Api(tags = "用户帐号接口")
+@RestController
+@RequestMapping("/api/store/my/key")
+public class UserKeyController {
+    private final UserKeyService userKeyService;
+
+    public UserKeyController(UserKeyService userKeyService) {
+        this.userKeyService = userKeyService;
+    }
+
+    @ApiOperation(value = "创建 oss key")
+    @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String create() {
+        Result result = userKeyService.create();
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "重新生成  oss key")
+    @PostMapping(value = "/regenerate", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String delete() {
+        userKeyService.regenerate();
+        return WebResult.success();
+    }
+}

+ 35 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserKeyPageController.java

@@ -0,0 +1,35 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.bnt.web.console.model.vo.UserKeyVo;
+import cn.reghao.bnt.web.console.service.UserKeyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 10:24:31
+ */
+@Api(tags = "我的帐号页面")
+@Controller
+@RequestMapping("/store/my/key")
+public class UserKeyPageController {
+    private final UserKeyService userKeyService;
+
+    public UserKeyPageController(UserKeyService userKeyService) {
+        this.userKeyService = userKeyService;
+    }
+
+    @ApiOperation(value = "我的帐号页面")
+    @GetMapping(value = "")
+    public String userKeyPage(Model model) {
+        List<UserKeyVo> list = userKeyService.getUserKeys();
+        model.addAttribute("list", list);
+        return "/console/userkey/index1";
+    }
+}

+ 40 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserNodeController.java

@@ -0,0 +1,40 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.bnt.web.console.model.dto.UserNodeDto;
+import cn.reghao.bnt.web.console.service.UserNodeService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:40:54
+ */
+@Api(tags = "用户存储节点数据接口")
+@RestController
+@RequestMapping("/api/store/my")
+public class UserNodeController {
+    private final UserNodeService userNodeService;
+
+    public UserNodeController(UserNodeService userNodeService) {
+        this.userNodeService = userNodeService;
+    }
+
+    @ApiOperation(value = "添加用户节点")
+    @PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addUserNode(@Validated UserNodeDto userNodeDto) {
+        Result result = userNodeService.add(userNodeDto);
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "删除用户节点")
+    @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteUserNode(@PathVariable("id") Integer userNodeId) {
+        Result result = userNodeService.delete(userNodeId);
+        return WebResult.result(result);
+    }
+}

+ 60 - 0
web/src/main/java/cn/reghao/bnt/web/console/controller/page/UserNodePageController.java

@@ -0,0 +1,60 @@
+package cn.reghao.bnt.web.console.controller.page;
+
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.bnt.web.console.model.vo.KeyValue;
+import cn.reghao.oss.api.dto.UserNodeVO;
+import cn.reghao.bnt.web.console.service.StoreNodeService;
+import cn.reghao.bnt.web.console.service.UserNodeService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.*;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Api(tags = "我的节点页面")
+@Controller
+@RequestMapping("/store/my")
+public class UserNodePageController {
+    private final UserNodeService userNodeService;
+    private final StoreNodeService storeNodeService;
+
+    public UserNodePageController(UserNodeService userNodeService, StoreNodeService storeNodeService) {
+        this.userNodeService = userNodeService;
+        this.storeNodeService = storeNodeService;
+    }
+
+    @ApiOperation(value = "我的节点列表页面")
+    @GetMapping(value = "/list")
+    public String userNodesPage(@RequestParam(value = "env", required = false) String env,
+                              @RequestParam(value = "appType", required = false) String appType,
+                              Model model) {
+        int loginUser = UserContext.getUserId();
+        List<UserNodeVO> list = userNodeService.getUserStoreNodes(loginUser);
+        model.addAttribute("list", list);
+        return "/console/usernode/index1";
+    }
+
+    @ApiOperation(value = "我的节点创建页面")
+    @GetMapping(value = "/add")
+    public String addNodePage(Model model) {
+        List<KeyValue> storeNodes = storeNodeService.getNodeKeyValues();
+        model.addAttribute("storeNodes", storeNodes);
+        return "/console/usernode/add";
+    }
+
+    @ApiOperation(value = "节点详情页面")
+    @GetMapping(value = "/detail/{appId}")
+    public String nodeDetailPage(@PathVariable(value = "appId") String appId, Model model) {
+        model.addAttribute("list", Collections.emptyList());
+        return "/console/usernode/detail";
+    }
+}

+ 13 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/mapper/StoreNodeRepository.java

@@ -0,0 +1,13 @@
+package cn.reghao.bnt.web.console.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+
+/**
+ * @author reghao
+ * @date 2024-07-02 18:15:11
+ */
+public interface StoreNodeRepository extends BaseMapper<StoreNode> {
+    StoreNode findByNodeAddr(String nodeAddr);
+    StoreNode findById(int id);
+}

+ 19 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/mapper/UploadChannelRepository.java

@@ -0,0 +1,19 @@
+package cn.reghao.bnt.web.console.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-02 18:15:27
+ */
+public interface UploadChannelRepository extends BaseMapper<UploadChannel> {
+    int countByCreateBy(int createBy);
+    List<UploadChannel> findByCreateBy(int createBy);
+    List<UploadChannel> findByCreateByAndNodeId(int createBy, int nodeId);
+    UploadChannel findByCreateByAndChannelId(int createBy, int channelId);
+    UploadChannel findByCreateByAndPrefix(int createBy, String prefix);
+    UploadChannel findById(int id);
+}

+ 12 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/mapper/UserKeyRepository.java

@@ -0,0 +1,12 @@
+package cn.reghao.bnt.web.console.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.bnt.web.console.model.po.UserKey;
+
+/**
+ * @author reghao
+ * @date 2024-07-02 18:15:35
+ */
+public interface UserKeyRepository extends BaseMapper<UserKey> {
+    UserKey findByCreateBy(int createBy);
+}

+ 18 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/mapper/UserNodeRepository.java

@@ -0,0 +1,18 @@
+package cn.reghao.bnt.web.console.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.bnt.web.console.model.po.UserNode;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-02 18:15:42
+ */
+public interface UserNodeRepository extends BaseMapper<UserNode> {
+    int countByNodeId(int nodeId);
+    UserNode findByCreateByAndNodeId(int createBy, int nodeId);
+    List<UserNode> findByCreateBy(int createBy);
+    UserNode findByDomain(String domain);
+    UserNode findById(int id);
+}

+ 13 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/repository/StoreNodeRepository.java

@@ -0,0 +1,13 @@
+package cn.reghao.bnt.web.console.db.repository;
+
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:31:05
+ */
+public interface StoreNodeRepository extends JpaRepository<StoreNode, Integer> {
+    StoreNode findByNodeAddr(String nodeAddr);
+    StoreNode findById(int id);
+}

+ 19 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/repository/UploadChannelRepository.java

@@ -0,0 +1,19 @@
+package cn.reghao.bnt.web.console.db.repository;
+
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+public interface UploadChannelRepository extends JpaRepository<UploadChannel, Integer> {
+    int countByCreateBy(int createBy);
+    List<UploadChannel> findByCreateBy(int createBy);
+    List<UploadChannel> findByCreateByAndNodeId(int createBy, int nodeId);
+    UploadChannel findByCreateByAndChannelId(int createBy, int channelId);
+    UploadChannel findByCreateByAndPrefix(int createBy, String prefix);
+    UploadChannel findById(int id);
+}

+ 13 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/repository/UserKeyRepository.java

@@ -0,0 +1,13 @@
+package cn.reghao.bnt.web.console.db.repository;
+
+import cn.reghao.bnt.web.console.model.po.UserKey;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 10:35:53
+ */
+public interface UserKeyRepository extends JpaRepository<UserKey, Integer> {
+    UserKey findByCreateBy(int createBy);
+    UserKey findByAccessKeyId(String  accessKeyId);
+}

+ 18 - 0
web/src/main/java/cn/reghao/bnt/web/console/db/repository/UserNodeRepository.java

@@ -0,0 +1,18 @@
+package cn.reghao.bnt.web.console.db.repository;
+
+import cn.reghao.bnt.web.console.model.po.UserNode;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:49:12
+ */
+public interface UserNodeRepository extends JpaRepository<UserNode, Integer> {
+    int countByNodeId(int nodeId);
+    UserNode findByCreateByAndNodeId(int createBy, int nodeId);
+    List<UserNode> findByCreateBy(int createBy);
+    UserNode findByDomain(String domain);
+    UserNode findById(int id);
+}

+ 15 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/constant/AppType.java

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.console.model.constant;
+
+/**
+ * 应用类型列表
+ *
+ * @author reghao
+ * @date 2019-10-18 14:31:29
+ */
+public enum AppType {
+    maven, dotnetCore, npm;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 30 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/constant/BuildStatus.java

@@ -0,0 +1,30 @@
+package cn.reghao.bnt.web.console.model.constant;
+
+/**
+ * 构建状态
+ *
+ * @author reghao
+ * @date 2021-11-08 16:35:42
+ */
+public enum BuildStatus {
+    neverBuild(1, "尚未构建"),
+    onBuilding(2, "正在构建"),
+    buildSuccess(3, "构建成功"),
+    buildFail(4, "构建失败");
+
+    private final Integer code;
+    private final String desc;
+
+    BuildStatus(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}

+ 30 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/constant/DeployStatus.java

@@ -0,0 +1,30 @@
+package cn.reghao.bnt.web.console.model.constant;
+
+/**
+ * 部署状态
+ *
+ * @author reghao
+ * @date 2021-11-08 16:35:42
+ */
+public enum DeployStatus {
+    neverDeploy(1, "尚未部署"),
+    onDeploying(2, "正在部署"),
+    deploySuccess(3, "部署成功"),
+    deployFail(4, "部署失败");
+
+    private final Integer code;
+    private final String desc;
+
+    DeployStatus(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}

+ 15 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/constant/build/CompileType.java

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.console.model.constant.build;
+
+/**
+ * 编译方式类型
+ *
+ * @author reghao
+ * @date 2019-10-18 14:31:29
+ */
+public enum CompileType {
+    none, shell, maven;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 13 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/constant/build/RepoAuthType.java

@@ -0,0 +1,13 @@
+package cn.reghao.bnt.web.console.model.constant.build;
+
+/**
+ * @author reghao
+ * @date 2021-02-05 18:50:01
+ */
+public enum RepoAuthType {
+    http, ssh, none;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 13 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/constant/build/RepoType.java

@@ -0,0 +1,13 @@
+package cn.reghao.bnt.web.console.model.constant.build;
+
+/**
+ * @author reghao
+ * @date 2021-02-05 22:50:41
+ */
+public enum RepoType {
+    git;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 21 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/dto/ChannelProcessDto.java

@@ -0,0 +1,21 @@
+package cn.reghao.bnt.web.console.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;
+}

+ 15 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/dto/KeyAuthDto.java

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.console.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 11:21:07
+ */
+@Setter
+@Getter
+public class KeyAuthDto {
+    private String accessKeyId;
+    private String accessKeySecret;
+}

+ 26 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/dto/NodeUpdateDto.java

@@ -0,0 +1,26 @@
+package cn.reghao.bnt.web.console.model.dto;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2023-03-10 15:00:20
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class NodeUpdateDto {
+    @NotNull
+    private Integer id;
+    private String nodeAddr;
+    @NotBlank(message = "必须设置域名")
+    private String domain;
+    @NotBlank(message = "必须设置 SecretKey")
+    private String secretKey;
+    private String referer;
+}

+ 20 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/dto/UserNodeDto.java

@@ -0,0 +1,20 @@
+package cn.reghao.bnt.web.console.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:42:44
+ */
+@Setter
+@Getter
+public class UserNodeDto {
+    @NotNull
+    private Integer nodeId;
+    @NotBlank
+    private String bucket;
+}

+ 28 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/po/StoreDisk.java

@@ -0,0 +1,28 @@
+package cn.reghao.bnt.web.console.model.po;
+
+import cn.reghao.oss.api.dto.StoreDiskDto;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Embeddable;
+
+/**
+ * @author reghao
+ * @date 2024-03-04 17:08:20
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Embeddable
+public class StoreDisk {
+    private String diskDir;
+    private Long total;
+    private Long avail;
+
+    public StoreDisk(StoreDiskDto storeDiskDto) {
+        this.diskDir = storeDiskDto.getDiskDir();
+        this.total = storeDiskDto.getTotal();
+        this.avail = storeDiskDto.getAvail();
+    }
+}

+ 45 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/po/StoreNode.java

@@ -0,0 +1,45 @@
+package cn.reghao.bnt.web.console.model.po;
+
+import cn.reghao.bnt.web.util.db.BaseEntity;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:29:14
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@Entity
+public class StoreNode extends BaseEntity {
+    @Column(nullable = false, unique = true)
+    private String nodeAddr;
+    @NotNull
+    private Integer httpPort;
+    @NotNull
+    private Integer rpcPort;
+    @ElementCollection(targetClass = StoreDisk.class)
+    @LazyCollection(LazyCollectionOption.FALSE)
+    private List<StoreDisk> storeDisks;
+    private Boolean enabled;
+
+    public StoreNode(StoreNodeDto storeNodeDto) {
+        this.nodeAddr = storeNodeDto.getNodeAddr();
+        this.httpPort = storeNodeDto.getHttpPort();
+        this.rpcPort = storeNodeDto.getRpcPort();
+        this.storeDisks = storeNodeDto.getDiskDtoList().stream().map(StoreDisk::new).collect(Collectors.toList());
+        this.enabled = false;
+    }
+}

+ 39 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/po/UploadChannel.java

@@ -0,0 +1,39 @@
+package cn.reghao.bnt.web.console.model.po;
+
+import cn.reghao.oss.api.dto.UploadChannelDto;
+import cn.reghao.bnt.web.util.db.BaseEntity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Entity;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@Entity
+public class UploadChannel extends BaseEntity {
+    private Integer channelId;
+    private String prefix;
+    private Long maxSize;
+    private Integer fileType;
+    private Boolean seturl;
+    private Integer scope;
+    private Integer nodeId;
+    private Integer createBy;
+
+    public UploadChannel(int channelId, UploadChannelDto uploadChannelDto, int createBy) {
+        this.channelId = channelId;
+        this.prefix = uploadChannelDto.getChannelPrefix();
+        this.maxSize = uploadChannelDto.getMaxSize();
+        this.fileType = uploadChannelDto.getFileType();
+        this.seturl = false;
+        this.scope = uploadChannelDto.getScope();
+        this.nodeId = uploadChannelDto.getNodeId();
+        this.createBy = createBy;
+    }
+}

+ 26 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/po/UserKey.java

@@ -0,0 +1,26 @@
+package cn.reghao.bnt.web.console.model.po;
+
+import cn.reghao.bnt.web.util.db.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 10:32:53
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Entity
+public class UserKey extends BaseEntity {
+    @Column(unique = true)
+    private String accessKeyId;
+    private String accessKeySecret;
+    private Integer createBy;
+}

+ 33 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/po/UserNode.java

@@ -0,0 +1,33 @@
+package cn.reghao.bnt.web.console.model.po;
+
+import cn.reghao.bnt.web.console.model.dto.UserNodeDto;
+import cn.reghao.bnt.web.util.db.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Entity;
+
+/**
+ * @author reghao
+ * @date 2024-02-27 09:45:11
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Entity
+public class UserNode extends BaseEntity {
+    private Integer nodeId;
+    private String domain;
+    private String secretKey;
+    private String referer;
+    private Integer createBy;
+
+    public UserNode(UserNodeDto userNodeDto, int createBy) {
+        this.nodeId = userNodeDto.getNodeId();
+        this.domain = String.format("oss-%s.reghao.cn", userNodeDto.getBucket());
+        this.createBy = createBy;
+    }
+}

+ 15 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/vo/KeyValue.java

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.console.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-06-03 19:00:57
+ */
+@AllArgsConstructor
+@Data
+public class KeyValue {
+    private String key;
+    private String value;
+}

+ 16 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/vo/StoreDiskVO.java

@@ -0,0 +1,16 @@
+package cn.reghao.bnt.web.console.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2024-03-07 14:25:07
+ */
+@AllArgsConstructor
+@Getter
+public class StoreDiskVO {
+    private String diskDir;
+    private String total;
+    private String avail;
+}

+ 36 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/vo/StoreNodeVO.java

@@ -0,0 +1,36 @@
+package cn.reghao.bnt.web.console.model.vo;
+
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.dto.StoreInfo;
+import lombok.Getter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-03-07 14:18:14
+ */
+@Getter
+public class StoreNodeVO {
+    private String nodeAddr;
+    private Integer httpPort;
+    private Integer rpcPort;
+    private List<StoreDiskVO> storeDisks;
+    private String domain;
+    private String referer;
+    private String secretKey;
+    private List<ObjectChannel> objectChannels;
+
+    public StoreNodeVO(StoreNode storeNode, List<StoreDiskVO> storeDisks, StoreInfo storeInfo) {
+        this.nodeAddr = storeNode.getNodeAddr();
+        this.httpPort = storeNode.getHttpPort();
+        this.rpcPort = storeNode.getRpcPort();
+        this.storeDisks = storeDisks;
+        this.domain = "";
+        this.referer = "";
+        this.secretKey = "";
+        this.objectChannels = Collections.emptyList();
+    }
+}

+ 15 - 0
web/src/main/java/cn/reghao/bnt/web/console/model/vo/UserKeyVo.java

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.console.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;
+}

+ 98 - 0
web/src/main/java/cn/reghao/bnt/web/console/rpc/ConsoleServiceImpl.java

@@ -0,0 +1,98 @@
+package cn.reghao.bnt.web.console.rpc;
+
+import cn.reghao.oss.api.dto.*;
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+import cn.reghao.bnt.web.console.model.po.UserNode;
+import cn.reghao.bnt.web.console.service.StoreNodeService;
+import cn.reghao.bnt.web.console.service.UploadChannelService;
+import cn.reghao.oss.api.iface.ConsoleService;
+import cn.reghao.bnt.web.console.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) {
+            return new NodeProperties(userNode.getDomain(), userNode.getSecretKey(), userNode.getReferer());
+        }
+
+        return null;
+    }
+
+    @Override
+    public ObjectChannel getChannelById(int owner, int channelId) {
+        return uploadChannelService.getObjectChannelByChannelId(channelId, owner);
+    }
+
+    @Override
+    public Integer getChannelIdByUrl(int loginUser, String url) {
+        List<UploadChannel> uploadChannels = uploadChannelService.getUploadChannelsByCreateBy(loginUser);
+        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.getChannelId();
+            }
+        }
+
+        return -1;
+    }
+
+    @Override
+    public ServerInfo getUploadStore(int loginUser, int channelId) {
+        UploadChannel uploadChannel = uploadChannelService.getByChannelIdAndCreateBy(channelId, loginUser);
+        if (uploadChannel == null) {
+            String errMsg = String.format("channel_id %s not exist", channelId);
+            return null;
+        }
+
+        UserNode userNode = userNodeService.getUserNode(uploadChannel.getNodeId(), loginUser);
+        if (userNode == null) {
+            String errMsg = String.format("channel_id %s not associate with any store_node", channelId);
+            return null;
+        }
+
+        String domain = userNode.getDomain();
+        String ossUrl = String.format("//%s", domain);
+        long maxSize = uploadChannel.getMaxSize();
+
+        StoreNode storeNode = storeNodeService.getById(userNode.getNodeId());
+        StoreService storeService = rpcService.getStoreService(storeNode);
+
+        // 多少秒后 token 过期
+        int expire = 3600;
+        String uploadToken = storeService.getUploadToken(channelId, loginUser, expire);
+        return new ServerInfo(ossUrl, channelId, maxSize, uploadToken);
+    }
+}

+ 33 - 0
web/src/main/java/cn/reghao/bnt/web/console/rpc/RemoteService.java

@@ -0,0 +1,33 @@
+package cn.reghao.bnt.web.console.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();
+    }
+}

+ 54 - 0
web/src/main/java/cn/reghao/bnt/web/console/rpc/RpcService.java

@@ -0,0 +1,54 @@
+package cn.reghao.bnt.web.console.rpc;
+
+import cn.reghao.oss.api.iface.DiskService;
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.bnt.web.console.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 DiskService getDiskService() {
+        RemoteService<DiskService> remoteService = new RemoteService<>();
+        String host = "127.0.0.1";
+        int port = 8110;
+        return remoteService.getService(host, port, DiskService.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();
+        }
+    }
+}

+ 245 - 0
web/src/main/java/cn/reghao/bnt/web/console/rpc/StoreServiceWrapperImpl.java

@@ -0,0 +1,245 @@
+package cn.reghao.bnt.web.console.rpc;
+
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.dto.ObjectInfo;
+import cn.reghao.oss.api.dto.StoreInfo;
+import cn.reghao.oss.api.dto.media.AudioInfo;
+import cn.reghao.oss.api.dto.media.ConvertedImageInfo;
+import cn.reghao.oss.api.dto.media.ImageInfo;
+import cn.reghao.oss.api.dto.media.VideoInfo;
+import cn.reghao.oss.api.iface.StoreService;
+import cn.reghao.oss.api.iface.StoreServiceWrapper;
+import cn.reghao.oss.api.util.AuthContext;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+import cn.reghao.bnt.web.console.service.UploadChannelService;
+import cn.reghao.bnt.web.console.service.UserNodeService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-03 14:40:25
+ */
+@Service
+public class StoreServiceWrapperImpl implements StoreServiceWrapper {
+    private final UploadChannelService uploadChannelService;
+    private final UserNodeService userNodeService;
+    private final RpcService rpcService;
+
+    public StoreServiceWrapperImpl(UploadChannelService uploadChannelService, RpcService rpcService,
+                                   UserNodeService userNodeService) {
+        this.uploadChannelService = uploadChannelService;
+        this.rpcService = rpcService;
+        this.userNodeService = userNodeService;
+    }
+
+    @Override
+    public Integer getChannelScope(int channelId) {
+        int loginUser = AuthContext.getUserId();
+        ObjectChannel objectChannel = uploadChannelService.getObjectChannelByChannelId(channelId, loginUser);
+        if (objectChannel != null) {
+            return objectChannel.getChannelId();
+        }
+        return -1;
+    }
+
+    private StoreService getStoreService(int channelId) throws Exception {
+        int loginUser = AuthContext.getUserId();
+        StoreNode storeNode = uploadChannelService.getStoreNodeByChannelId(loginUser, channelId);
+        if (storeNode == null) {
+            String errMsg = String.format("channel_id %s not associate with any store_node", channelId);
+            throw new Exception(errMsg);
+        }
+
+        return rpcService.getStoreService(storeNode);
+    }
+
+    /*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);
+    }*/
+
+    @Override
+    public void createChannel(int channelId, String channelPrefix) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            storeService.createChannel(channelPrefix);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public StoreInfo getStoreInfo(int channelId) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            return storeService.getStoreInfo();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public void setObjectScope(int channelId, String objectId, int scope) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            storeService.setObjectScope(objectId, scope);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void deleteByObjectId(int channelId, String objectId) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            storeService.deleteByObjectId(objectId);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void deleteByObjectUrl(String objectUrl) {
+        int loginUser = AuthContext.getUserId();
+        String domain = objectUrl.replace("//", "").split("/")[0];
+        String objectName = objectUrl.replace("//" + domain + "/", "");
+        List<UploadChannel> uploadChannels = uploadChannelService.getByDomainAndCreateBy(domain, loginUser);
+        for (UploadChannel uploadChannel : uploadChannels) {
+            String prefix = uploadChannel.getPrefix();
+            // TODO get best match
+            if (objectName.startsWith(prefix)) {
+                int channelId = uploadChannel.getChannelId();
+                try {
+                    StoreService storeService = getStoreService(channelId);
+                    storeService.deleteByObjectUrl(objectName);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    @Override
+    public ObjectInfo getObjectInfo(int channelId, String objectId) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            int expireSecond = 3600;
+            ObjectInfo objectInfo = storeService.getObjectInfo(objectId);
+            if (objectInfo != null) {
+                int loginUser = AuthContext.getUserId();
+                String domain = userNodeService.getDomain(channelId, loginUser);
+                String url = String.format("//%s/%s", domain, objectInfo.getUrl());
+                objectInfo.setUrl(url);
+            }
+
+            return objectInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getSignedUrl(int channelId, String objectId) {
+        int loginUser = AuthContext.getUserId();
+        String domain = userNodeService.getDomain(channelId, loginUser);
+        try {
+            StoreService storeService = getStoreService(channelId);
+            // 多少秒后 url 过期
+            int expire = 3600;
+            return storeService.getSignedUrl(domain, loginUser, objectId, expire);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public VideoInfo getVideoInfo(int channelId, String videoFileId) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            VideoInfo videoInfo = storeService.getVideoInfo(videoFileId);
+            if (videoInfo != null) {
+                int loginUser = AuthContext.getUserId();
+                String domain = userNodeService.getDomain(channelId, loginUser);
+                String url = String.format("//%s/%s", domain, videoInfo.getUrl());
+                videoInfo.setUrl(url);
+            }
+
+            return videoInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public ImageInfo getImageInfo(int channelId, String imageFileId) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            ImageInfo imageInfo = storeService.getImageInfo(imageFileId);
+            if (imageInfo != null) {
+                int loginUser = AuthContext.getUserId();
+                String domain = userNodeService.getDomain(channelId, loginUser);
+                String url = String.format("//%s/%s", domain, imageInfo.getUrl());
+                imageInfo.setUrl(url);
+            }
+
+            return imageInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public ConvertedImageInfo getWebpInfo(int channelId, String imageFileId) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            ConvertedImageInfo convertedImageInfo = storeService.getWebpInfo(imageFileId);
+            if (convertedImageInfo != null) {
+                int loginUser = AuthContext.getUserId();
+                String domain = userNodeService.getDomain(channelId, loginUser);
+                String url = String.format("//%s/%s", domain, convertedImageInfo.getUrl());
+                convertedImageInfo.setUrl(url);
+            }
+
+            return convertedImageInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @Override
+    public AudioInfo getAudioInfo(int channelId, String audioFileId) {
+        try {
+            StoreService storeService = getStoreService(channelId);
+            AudioInfo audioInfo = storeService.getAudioInfo(audioFileId);
+            if (audioInfo != null) {
+                int loginUser = AuthContext.getUserId();
+                String domain = userNodeService.getDomain(channelId, loginUser);
+                String url = String.format("//%s/%s", domain, audioInfo.getUrl());
+                audioInfo.setUrl(url);
+            }
+
+            return audioInfo;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+}

+ 29 - 0
web/src/main/java/cn/reghao/bnt/web/console/service/DiskFileService.java

@@ -0,0 +1,29 @@
+package cn.reghao.bnt.web.console.service;
+
+import cn.reghao.oss.api.dto.FileInfo;
+import cn.reghao.oss.api.iface.DiskService;
+import cn.reghao.bnt.web.console.rpc.RpcService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-11 16:49:34
+ */
+@Service
+public class DiskFileService {
+    private final RpcService rpcService;
+
+    public DiskFileService(RpcService rpcService) {
+        this.rpcService = rpcService;
+    }
+
+    public List<FileInfo> getFileList(int pageNumber) {
+        DiskService diskService = rpcService.getDiskService();
+
+        int pageSize = 10;
+        List<FileInfo> list = diskService.getFiles(pageNumber, pageSize);
+        return list;
+    }
+}

+ 145 - 0
web/src/main/java/cn/reghao/bnt/web/console/service/ObjectChannelServiceImpl.java

@@ -0,0 +1,145 @@
+package cn.reghao.bnt.web.console.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.oss.api.constant.ObjectScope;
+import cn.reghao.oss.api.constant.ObjectType;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.iface.ObjectChannelService;
+import cn.reghao.oss.api.iface.StoreServiceWrapper;
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.bnt.web.console.db.repository.StoreNodeRepository;
+import cn.reghao.bnt.web.console.db.repository.UploadChannelRepository;
+import cn.reghao.oss.api.dto.ChannelScopeDto;
+import cn.reghao.oss.api.dto.UploadChannelDto;
+import cn.reghao.bnt.web.console.db.repository.UserNodeRepository;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+import cn.reghao.bnt.web.console.model.po.UserNode;
+import cn.reghao.oss.api.dto.UploadChannelVo;
+import cn.reghao.oss.api.dto.UserNodeVO;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:14:53
+ */
+@Service
+public class ObjectChannelServiceImpl implements ObjectChannelService {
+    private final ByteConverter byteConverter = new ByteConverter();
+    private final UploadChannelRepository uploadChannelRepository;
+    private final UserNodeRepository userNodeRepository;
+    private final StoreNodeRepository storeNodeRepository;
+    private final StoreServiceWrapper storeServiceWrapper;
+
+    public ObjectChannelServiceImpl(UploadChannelRepository uploadChannelRepository, UserNodeRepository userNodeRepository,
+                                    StoreNodeRepository storeNodeRepository, StoreServiceWrapper storeServiceWrapper) {
+        this.uploadChannelRepository = uploadChannelRepository;
+        this.userNodeRepository = userNodeRepository;
+        this.storeNodeRepository = storeNodeRepository;
+        this.storeServiceWrapper = storeServiceWrapper;
+    }
+
+    @Override
+    public synchronized Result addObjectChannel(UploadChannelDto uploadChannelDto) {
+        int createBy = UserContext.getUserId();
+        int nodeId = uploadChannelDto.getNodeId();
+        UserNode userNode = userNodeRepository.findByCreateByAndNodeId(createBy, nodeId);
+        if (userNode == null) {
+            return Result.fail(String.format("store_node with id %s not exist", nodeId));
+        }
+
+        String channelPrefix = uploadChannelDto.getChannelPrefix();
+        UploadChannel uploadChannel = uploadChannelRepository.findByCreateByAndPrefix(createBy, channelPrefix);
+        if (uploadChannel != null) {
+            return Result.fail(String.format("channel_prefix %s exist", channelPrefix));
+        }
+
+        int channelId = getNextChannelId(createBy, nodeId);
+        uploadChannel = new UploadChannel(channelId, uploadChannelDto, createBy);
+        uploadChannelRepository.save(uploadChannel);
+        storeServiceWrapper.createChannel(channelId, channelPrefix);
+        return Result.success();
+    }
+
+    private int getNextChannelId(int createBy, int nodeId) {
+        int channelId = 101;
+        int total = uploadChannelRepository.countByCreateBy(createBy);
+        return channelId + total;
+    }
+
+    @Override
+    public void updateScope(ChannelScopeDto channelScopeDto) {
+        int id = channelScopeDto.getId();
+        int newScope = channelScopeDto.getNewScope();
+        UploadChannel uploadChannel = uploadChannelRepository.findById(id);
+        if (uploadChannel != null) {
+            int currentScope = uploadChannel.getScope();
+            if (currentScope != newScope) {
+                uploadChannel.setScope(newScope);
+                uploadChannelRepository.save(uploadChannel);
+            }
+        }
+    }
+
+    @Override
+    public List<UploadChannelVo> getObjectChannels(int createBy) {
+        List<UploadChannel> page = uploadChannelRepository.findByCreateBy(createBy);
+        List<UploadChannelVo> list = page.stream().map(uploadChannel -> {
+            long maxSize = uploadChannel.getMaxSize();
+            String maxSizeStr = byteConverter.convert(ByteType.Bytes, maxSize);
+            int nodeId = uploadChannel.getNodeId();
+            UserNode userNode = userNodeRepository.findByCreateByAndNodeId(createBy, nodeId);
+            String bindDomain = userNode.getDomain();
+            int id = uploadChannel.getId();
+            int channelId = uploadChannel.getChannelId();
+            String prefix = uploadChannel.getPrefix();
+            String fileTypeStr = ObjectType.getDescByCode(uploadChannel.getFileType());
+            boolean seturl = uploadChannel.getSeturl();
+            String scopeStr = ObjectScope.getByCode(uploadChannel.getScope()).name();
+            return new UploadChannelVo(id, channelId, prefix, maxSizeStr, fileTypeStr, seturl, scopeStr, bindDomain);
+        }).collect(Collectors.toList());
+
+        return list;
+    }
+
+    @Override
+    public ObjectChannel getObjectChannel(int id) {
+        UploadChannel uploadChannel = uploadChannelRepository.findById(id);
+        return uploadChannel != null ? getObjectChannel(uploadChannel) : null;
+    }
+
+    private ObjectChannel getObjectChannel(UploadChannel uploadChannel) {
+        int id = uploadChannel.getId();
+        int channelId = uploadChannel.getChannelId();
+        String channelPrefix = uploadChannel.getPrefix();
+        long maxSize = uploadChannel.getMaxSize();
+        int fileType = uploadChannel.getFileType();
+        boolean seturl = uploadChannel.getSeturl();
+        int scope = uploadChannel.getScope();
+        int createBy = uploadChannel.getCreateBy();
+
+        UserNode userNode = userNodeRepository.findByCreateByAndNodeId(createBy, uploadChannel.getNodeId());
+        String domain = userNode.getDomain();
+        return new ObjectChannel(id, channelId, channelPrefix, maxSize, fileType, seturl, scope, domain, createBy);
+    }
+
+    @Override
+    public List<UserNodeVO> getUserStoreNodes(int loginUser) {
+        List<UserNode> list = userNodeRepository.findByCreateBy(loginUser);
+        List<UserNodeVO> storeNodes = list.stream()
+                .map(userNode -> {
+                    int nodeId = userNode.getNodeId();
+                    StoreNode storeNode = storeNodeRepository.findById(nodeId);
+                    String nodeAddr = storeNode.getNodeAddr();
+                    String domain = userNode.getDomain();
+                    return new UserNodeVO(nodeId, nodeId, nodeAddr, domain);
+                })
+                .collect(Collectors.toList());
+        return storeNodes;
+    }
+}

+ 102 - 0
web/src/main/java/cn/reghao/bnt/web/console/service/StoreChannelService.java

@@ -0,0 +1,102 @@
+package cn.reghao.bnt.web.console.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.oss.api.dto.StoreDiskDto;
+import cn.reghao.oss.api.dto.StoreInfo;
+import cn.reghao.oss.api.iface.StoreServiceWrapper;
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.bnt.web.console.db.repository.StoreNodeRepository;
+import cn.reghao.bnt.web.console.db.repository.UploadChannelRepository;
+import cn.reghao.oss.api.dto.UploadChannelDto;
+import cn.reghao.bnt.web.console.model.po.StoreDisk;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+import cn.reghao.bnt.web.console.model.po.UserNode;
+import cn.reghao.bnt.web.console.model.vo.StoreDiskVO;
+import cn.reghao.bnt.web.console.model.vo.StoreNodeVO;
+import cn.reghao.bnt.web.console.rpc.RpcService;
+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 StoreChannelService {
+    private final ByteConverter byteConverter = new ByteConverter();
+    private final UploadChannelRepository uploadChannelRepository;
+    private final StoreNodeRepository storeNodeRepository;
+    private final UserNodeService userNodeService;
+    private final StoreServiceWrapper storeServiceWrapper;
+    private final RpcService rpcService;
+
+    public StoreChannelService(UploadChannelRepository uploadChannelRepository, StoreNodeRepository storeNodeRepository,
+                               UserNodeService userNodeService, StoreServiceWrapper storeServiceWrapper, RpcService rpcService) {
+        this.uploadChannelRepository = uploadChannelRepository;
+        this.storeNodeRepository = storeNodeRepository;
+        this.userNodeService = userNodeService;
+        this.storeServiceWrapper = storeServiceWrapper;
+        this.rpcService = rpcService;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public synchronized Result add(UploadChannelDto uploadChannelDto) {
+        int createBy = UserContext.getUserId();
+        int nodeId = uploadChannelDto.getNodeId();
+        UserNode userNode = userNodeService.getUserNode(nodeId, createBy);
+        if (userNode == null) {
+            return Result.fail(String.format("store_node with id %s not exist", nodeId));
+        }
+
+        String channelPrefix = uploadChannelDto.getChannelPrefix();
+        UploadChannel uploadChannel = uploadChannelRepository.findByCreateByAndPrefix(createBy, channelPrefix);
+        if (uploadChannel != null) {
+            return Result.fail(String.format("channel_prefix %s exist", channelPrefix));
+        }
+
+        int channelId = getNextChannelId(createBy, nodeId);
+        /*uploadChannel = new UploadChannel(channelId, uploadChannelDto, createBy);
+        uploadChannelRepository.save(uploadChannel);
+        storeServiceWrapper.createChannel(channelId, channelPrefix);*/
+        return Result.success();
+    }
+
+    private int getNextChannelId(int createBy, int nodeId) {
+        int channelId = 101;
+        int total = uploadChannelRepository.countByCreateBy(createBy);
+        return channelId + total;
+    }
+
+    public StoreNodeVO getStoreDetail(int nodeId) {
+        StoreNode storeNode = storeNodeRepository.findById(nodeId);
+        if (storeNode == null) {
+            return null;
+        }
+
+        StoreInfo storeInfo = rpcService.getStoreService(storeNode).getStoreInfo();
+        List<StoreDiskDto> storeDiskDtos = storeInfo.getStoreDisks();
+        List<StoreDisk> list = storeDiskDtos.stream()
+                .map(StoreDisk::new)
+                .collect(Collectors.toList());
+        storeNode.setStoreDisks(list);
+        storeNodeRepository.save(storeNode);
+
+        List<StoreDiskVO> storeDisks = storeDiskDtos.stream()
+                .map(storeDisk -> {
+                    String diskDir = storeDisk.getDiskDir();
+                    long total = storeDisk.getTotal();
+                    String totalStr = byteConverter.convert(ByteType.Bytes, total);
+                    long avail = storeDisk.getAvail();
+                    String availStr = byteConverter.convert(ByteType.Bytes, avail);
+                    return new StoreDiskVO(diskDir, totalStr, availStr);
+                }).collect(Collectors.toList());
+
+        return new StoreNodeVO(storeNode, storeDisks, storeInfo);
+    }
+}

+ 83 - 0
web/src/main/java/cn/reghao/bnt/web/console/service/StoreNodeService.java

@@ -0,0 +1,83 @@
+package cn.reghao.bnt.web.console.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.bnt.web.console.db.repository.StoreNodeRepository;
+import cn.reghao.bnt.web.console.db.repository.UserNodeRepository;
+import cn.reghao.bnt.web.console.model.po.StoreDisk;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.vo.KeyValue;
+import cn.reghao.oss.api.dto.StoreNodeDto;
+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 StoreNodeRepository storeNodeRepository;
+    private final UserNodeRepository userNodeRepository;
+
+    public StoreNodeService(StoreNodeRepository storeNodeRepository, UserNodeRepository userNodeRepository) {
+        this.storeNodeRepository = storeNodeRepository;
+        this.userNodeRepository = userNodeRepository;
+    }
+
+    public void addOrUpdate(StoreNodeDto storeNodeDto) {
+        String nodeAddr = storeNodeDto.getNodeAddr();
+        StoreNode storeNode = storeNodeRepository.findByNodeAddr(nodeAddr);
+        if (storeNode == null) {
+            storeNode = new StoreNode(storeNodeDto);
+            storeNodeRepository.save(storeNode);
+        } else {
+            List<StoreDisk> storeDisks = storeNodeDto.getDiskDtoList().stream()
+                    .map(StoreDisk::new)
+                    .collect(Collectors.toList());
+            storeNode.setStoreDisks(storeDisks);
+            storeNodeRepository.save(storeNode);
+        }
+    }
+
+    public Result updateStatus() {
+        return Result.success();
+    }
+
+    public Result delete(int nodeId) {
+        StoreNode storeNode = storeNodeRepository.findById(nodeId);
+        if (storeNode == null) {
+            return Result.fail("node not exist");
+        }
+
+        int total = userNodeRepository.countByNodeId(nodeId);
+        if (total > 0) {
+            return Result.fail("someone uses the node");
+        }
+
+        storeNodeRepository.delete(storeNode);
+        return Result.success();
+    }
+
+    public List<StoreNode> getByPage() {
+        return storeNodeRepository.findAll();
+    }
+
+    public StoreNode getById(int id) {
+        return storeNodeRepository.findById(id);
+    }
+
+    public List<KeyValue> getNodeKeyValues() {
+        return storeNodeRepository.findAll().stream()
+                .map(storeNode -> {
+                    int nodeId = storeNode.getId();
+                    String nodeAddr = storeNode.getNodeAddr();
+                    return new KeyValue(nodeId+"", nodeAddr);
+                }).collect(Collectors.toList());
+    }
+
+    public StoreNode getByNodeAddr(String nodeAddress) {
+        return storeNodeRepository.findByNodeAddr(nodeAddress);
+    }
+}

+ 80 - 0
web/src/main/java/cn/reghao/bnt/web/console/service/UploadChannelService.java

@@ -0,0 +1,80 @@
+package cn.reghao.bnt.web.console.service;
+
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.bnt.web.console.db.repository.UploadChannelRepository;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+import cn.reghao.bnt.web.console.model.po.UserNode;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:14:53
+ */
+@Service
+public class UploadChannelService {
+    private final UploadChannelRepository uploadChannelRepository;
+    private final UserNodeService userNodeService;
+
+    public UploadChannelService(UploadChannelRepository uploadChannelRepository, UserNodeService userNodeService) {
+        this.uploadChannelRepository = uploadChannelRepository;
+        this.userNodeService = userNodeService;
+    }
+
+    public List<UploadChannel> getUploadChannelsByCreateBy(int createBy) {
+        return uploadChannelRepository.findByCreateBy(createBy);
+    }
+
+    public UploadChannel getByChannelIdAndCreateBy(int channelId, int createBy) {
+        return uploadChannelRepository.findByCreateByAndChannelId(createBy, channelId);
+    }
+
+    public ObjectChannel getObjectChannelByChannelId(int channelId, int createBy) {
+        UploadChannel uploadChannel = uploadChannelRepository.findByCreateByAndChannelId(createBy, channelId);
+        return getObjectChannel(uploadChannel);
+    }
+
+    private ObjectChannel getObjectChannel(UploadChannel uploadChannel) {
+        int id = uploadChannel.getId();
+        int channelId = uploadChannel.getChannelId();
+        String channelPrefix = uploadChannel.getPrefix();
+        long maxSize = uploadChannel.getMaxSize();
+        int fileType = uploadChannel.getFileType();
+        boolean seturl = uploadChannel.getSeturl();
+        int scope = uploadChannel.getScope();
+        int createBy = uploadChannel.getCreateBy();
+
+        UserNode userNode = userNodeService.getUserNode(uploadChannel.getNodeId(), createBy);
+        String domain = userNode.getDomain();
+        return new ObjectChannel(id, channelId, channelPrefix, maxSize, fileType, seturl, scope, domain, createBy);
+    }
+
+    public StoreNode getStoreNodeByChannelId(int loginUser, int channelId) throws Exception {
+        UploadChannel uploadChannel = uploadChannelRepository.findByCreateByAndChannelId(loginUser, channelId);
+        if (uploadChannel == null) {
+            String errMsg = String.format("channel_id %s not exist", channelId);
+            throw new Exception(errMsg);
+        }
+
+        UserNode userNode = userNodeService.getUserNode(uploadChannel.getNodeId(), uploadChannel.getCreateBy());
+        String domain = userNode.getDomain();
+        String ossUrl = String.format("//%s", domain);
+        long maxSize = uploadChannel.getMaxSize();
+
+        StoreNode storeNode = userNodeService.getByDomain(domain);
+        if (storeNode == null) {
+            String errMsg = String.format("store_node %s not exist", domain);
+            throw new Exception(errMsg);
+        }
+
+        return storeNode;
+    }
+
+    public List<UploadChannel> getByDomainAndCreateBy(String domain, int createBy) {
+        StoreNode storeNode = userNodeService.getByDomain(domain);
+        int nodeId = storeNode.getId();
+        return uploadChannelRepository.findByCreateByAndNodeId(createBy, nodeId);
+    }
+}

+ 102 - 0
web/src/main/java/cn/reghao/bnt/web/console/service/UserKeyService.java

@@ -0,0 +1,102 @@
+package cn.reghao.bnt.web.console.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.security.RandomString;
+import cn.reghao.bnt.web.console.model.vo.UserKeyVo;
+import cn.reghao.oss.api.constant.ChannelAction;
+import cn.reghao.oss.api.dto.OssPayload;
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.bnt.web.console.db.repository.UserKeyRepository;
+import cn.reghao.bnt.web.console.model.dto.KeyAuthDto;
+import cn.reghao.bnt.web.console.model.po.UserKey;
+import cn.reghao.oss.api.util.JwtUtil;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+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 UserKeyRepository userKeyRepository;
+
+    public UserKeyService(UserKeyRepository userKeyRepository) {
+        this.userKeyRepository = userKeyRepository;
+    }
+
+    public Result create() {
+        int loginUser = UserContext.getUserId();
+        UserKey userKey = userKeyRepository.findByCreateBy(loginUser);
+        if (userKey != null) {
+            String errMsg = String.format("you already have key");
+            return Result.fail(errMsg);
+        } else {
+            String accessKeyId = RandomString.getString(8);
+            String accessKeySecret = RandomString.getString(18);
+            userKey = new UserKey(accessKeyId, accessKeySecret, loginUser);
+            userKeyRepository.save(userKey);
+        }
+
+        return Result.success("key has created");
+    }
+
+    public Result auth(KeyAuthDto keyAuthDto) {
+        String accessKeyId = keyAuthDto.getAccessKeyId();
+        UserKey userKey = userKeyRepository.findByAccessKeyId(accessKeyId);
+        if (userKey == null) {
+            return Result.fail("key not exist");
+        }
+
+        String accessKeySecret = keyAuthDto.getAccessKeySecret();
+        if (userKey.getAccessKeySecret().equals(accessKeySecret)) {
+            int loginUser = userKey.getCreateBy();
+            String token = getTokenByUserId(loginUser);
+            return Result.success(token);
+        }
+
+        return Result.fail("secret not matched");
+    }
+
+    public String getTokenByUserId(int loginUser) {
+        long timestamp = System.currentTimeMillis() + 3600*24*365*1000L;
+        String action = ChannelAction.all.getName();
+        int channelId = 1;
+        OssPayload ossPayload = new OssPayload(action, channelId, loginUser);
+        return JwtUtil.createToken(ossPayload, timestamp, secretKey);
+    }
+
+    public int getUserIdFromToken(String token) {
+        OssPayload ossPayload = JwtUtil.getOssPayload(token, secretKey);
+        return ossPayload.getUserId();
+    }
+
+    public void regenerate() {
+        int loginUser = UserContext.getUserId();
+        UserKey userKey = userKeyRepository.findByCreateBy(loginUser);
+        if (userKey == null) {
+            return;
+        }
+
+        String accessKeyId = RandomString.getString(8);
+        String accessKeySecret = RandomString.getString(18);
+        userKey.setAccessKeyId(accessKeyId);
+        userKey.setAccessKeySecret(accessKeySecret);
+        userKeyRepository.save(userKey);
+    }
+
+    public List<UserKeyVo> getUserKeys() {
+        int loginUser = UserContext.getUserId();
+        UserKey userKey = userKeyRepository.findByCreateBy(loginUser);
+        if (userKey == null) {
+            return Collections.emptyList();
+        }
+
+        String accessKeyId = userKey.getAccessKeyId();
+        String accessKeySecret = userKey.getAccessKeySecret();
+        return List.of(new UserKeyVo(accessKeyId, accessKeySecret));
+    }
+}

+ 103 - 0
web/src/main/java/cn/reghao/bnt/web/console/service/UserNodeService.java

@@ -0,0 +1,103 @@
+package cn.reghao.bnt.web.console.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.bnt.web.console.db.repository.StoreNodeRepository;
+import cn.reghao.bnt.web.console.db.repository.UploadChannelRepository;
+import cn.reghao.bnt.web.console.db.repository.UserNodeRepository;
+import cn.reghao.bnt.web.console.model.dto.UserNodeDto;
+import cn.reghao.bnt.web.console.model.po.StoreNode;
+import cn.reghao.bnt.web.console.model.po.UploadChannel;
+import cn.reghao.bnt.web.console.model.po.UserNode;
+import cn.reghao.oss.api.dto.UserNodeVO;
+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 UserNodeRepository userNodeRepository;
+    private final StoreNodeRepository storeNodeRepository;
+    private final UploadChannelRepository uploadChannelRepository;
+
+    public UserNodeService(UserNodeRepository userNodeRepository, StoreNodeRepository storeNodeRepository,
+                           UploadChannelRepository uploadChannelRepository) {
+        this.userNodeRepository = userNodeRepository;
+        this.storeNodeRepository = storeNodeRepository;
+        this.uploadChannelRepository = uploadChannelRepository;
+    }
+
+    public Result add(UserNodeDto userNodeDto) {
+        int nodeId = userNodeDto.getNodeId();
+        int userId = UserContext.getUserId();
+        UserNode userNode = userNodeRepository.findByCreateByAndNodeId(userId, nodeId);
+        if (userNode != null) {
+            return Result.fail("UserNode exist");
+        }
+
+        StoreNode storeNode = storeNodeRepository.findById(nodeId);
+        if (storeNode == null) {
+            return Result.success("StoreNode not exist");
+        }
+
+        userNode = new UserNode(userNodeDto, userId);
+        userNodeRepository.save(userNode);
+        return Result.success("node added");
+    }
+
+    public Result delete(int id) {
+        UserNode userNode = userNodeRepository.findById(id);
+        if (userNode != null) {
+            int nodeId = userNode.getNodeId();
+            int userId = UserContext.getUserId();
+            List<UploadChannel> uploadChannels = uploadChannelRepository.findByCreateByAndNodeId(userId, nodeId);
+            if (!uploadChannels.isEmpty()) {
+                return Result.fail("UploadChannel exists");
+            }
+
+            userNodeRepository.delete(userNode);
+            return Result.success();
+        }
+
+        return Result.fail("UserNode not exists");
+    }
+
+    public StoreNode getByDomain(String domain) {
+        UserNode userNode = userNodeRepository.findByDomain(domain);
+        int id = userNode.getNodeId();
+        return storeNodeRepository.findById(id);
+    }
+
+    public List<UserNodeVO> getUserStoreNodes(int loginUser) {
+        List<UserNode> list = userNodeRepository.findByCreateBy(loginUser);
+        return list.stream()
+                .map(userNode -> {
+                    int nodeId = userNode.getNodeId();
+                    StoreNode storeNode = storeNodeRepository.findById(nodeId);
+                    String nodeAddr = storeNode.getNodeAddr();
+                    String domain = userNode.getDomain();
+                    return new UserNodeVO(nodeId, nodeId, nodeAddr, domain);
+                })
+                .collect(Collectors.toList());
+    }
+
+    public UserNode getUserNode(int nodeId, int loginUser) {
+        return userNodeRepository.findByCreateByAndNodeId(loginUser, nodeId);
+    }
+
+    public UserNode getUserNodeByDomain(String domain) {
+        return userNodeRepository.findByDomain(domain);
+    }
+
+    public String getDomain(int channelId, int loginUser) {
+        UploadChannel uploadChannel = uploadChannelRepository.findByCreateByAndChannelId(loginUser, channelId);
+        int nodeId = uploadChannel.getNodeId();
+        UserNode userNode = userNodeRepository.findByCreateByAndNodeId(loginUser, nodeId);
+        return userNode.getDomain();
+    }
+}

+ 4 - 3
web/src/main/resources/application.yml

@@ -1,15 +1,16 @@
 dubbo:
   scan:
-    base-packages: cn.reghao.bnt.web.admin.rpc
+    base-packages: cn.reghao.bnt.web.admin.rpc,cn.reghao.bnt.web.console.rpc
   protocol:
     name: dubbo
-    port: 4040
+    port: 14020
 server:
   port: 4020
   servlet:
     session:
       # 超时时间为一周
-      timeout: 604800
+      timeout: 7d
+      persistent: true
 spring:
   application:
     name: bnt-web

+ 0 - 0
web/src/main/resources/templates/admin/channel/add.html → web/src/main/resources/templates/console/channel/add.html


+ 0 - 0
web/src/main/resources/templates/admin/channel/edit.html → web/src/main/resources/templates/console/channel/edit.html


+ 0 - 0
web/src/main/resources/templates/admin/channel/edit1.html → web/src/main/resources/templates/console/channel/edit1.html


+ 0 - 0
web/src/main/resources/templates/admin/channel/index.html → web/src/main/resources/templates/console/channel/index.html


+ 73 - 0
web/src/main/resources/templates/console/node/detail.html

@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+<body>
+    <div class="timo-detail-page">
+        <div class="timo-detail-title">节点信息</div>
+        <table class="layui-table timo-detail-table">
+            <tbody>
+            <tr>
+                <th>地址</th>
+                <td th:text="${storeNode.nodeAddr}"></td>
+                <th>HTTP 端口</th>
+                <td th:text="${storeNode.httpPort}"></td>
+                <th>RPC 端口</th>
+                <td th:text="${storeNode.rpcPort}"></td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="timo-detail-title">磁盘信息</div>
+        <table class="layui-table timo-detail-table">
+            <thead>
+            <tr>
+                <th class="sortable" data-field="appName">磁盘路径</th>
+                <th class="sortable" data-field="appName">总容量</th>
+                <th class="sortable" data-field="appName">可用容量</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr th:each="item:${storeNode.storeDisks}">
+                <td th:text="${item.diskDir}"></td>
+                <td th:text="${item.total}"></td>
+                <td th:text="${item.avail}"></td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="timo-detail-title">节点配置</div>
+        <table class="layui-table timo-detail-table">
+            <tbody>
+            <tr>
+                <th>域名</th>
+                <td th:text="${storeNode.domain}"></td>
+            </tr>
+            <tr>
+                <th>SecretKey</th>
+                <td th:text="${storeNode.secretKey}"></td>
+            </tr>
+            <tr>
+                <th>Referer</th>
+                <td th:text="${storeNode.referer}"></td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="timo-detail-title">节点通道</div>
+        <table class="layui-table timo-detail-table">
+            <thead>
+            <tr>
+                <th class="sortable" data-field="appName">创建者</th>
+                <th class="sortable" data-field="appName">Channel ID</th>
+                <th class="sortable" data-field="appName">Prefix</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr th:each="item:${storeNode.objectChannels}">
+                <td th:text="${item.createBy}"></td>
+                <td th:text="${item.channelId}"></td>
+                <td th:text="${item.prefix}"></td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 34 - 0
web/src/main/resources/templates/console/node/edit.html

@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body>
+<div class="layui-form timo-compile">
+    <form th:action="@{/api/store/node/update}">
+        <input class="layui-input" type="hidden" name="id" readonly="readonly" th:value="${storeNode.id}">
+        <table class="layui-table timo-detail-table">
+            <tbody>
+            <tr>
+                <th>
+                    <label class="layui-form-label">节点地址</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <input class="layui-input" type="text" name="nodeAddr" readonly="readonly" th:value="${storeNode.nodeAddr}">
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="layui-form-item timo-finally">
+            <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+            <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
+        </div>
+    </form>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 65 - 0
web/src/main/resources/templates/console/node/index.html

@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org"
+      xmlns:mo="https://gitee.com/aun/Timo">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
+    <link rel="stylesheet" th:href="@{/lib/zTree_v3/css/zTreeStyle/zTreeStyle.css}" type="text/css">
+</head>
+
+<body class="timo-layout-page">
+<div class="layui-card">
+    <div class="layui-card-header timo-card-header">
+        <span><i class="fa fa-bars"></i> 存储节点列表</span>
+        <i class="layui-icon layui-icon-refresh refresh-btn"></i>
+    </div>
+    <div class="layui-card-body">
+        <div class="timo-table-wrap">
+            <table class="layui-table timo-table">
+                <thead>
+                <tr>
+                    <th class="sortable" data-field="appName">节点地址</th>
+                    <th class="sortable" data-field="appId">HTTP 端口</th>
+                    <th class="sortable" data-field="repoBranch">RPC 端口</th>
+                    <th class="sortable" data-field="appName">启用</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td th:text="${item.nodeAddr}">应用名</td>
+                    <td th:text="${item.httpPort}">应用 ID</td>
+                    <td th:text="${item.rpcPort}">分支</td>
+                    <td th:text="${item.enabled}">应用名</td>
+                    <td>
+                        <a class="open-popup" data-title="设置存储节点" th:attr="data-url=@{'/store/node/config/'+${item.id}}"
+                           data-size="640,480" href="#">设置</a>
+                        <a class="open-popup" data-title="存储节点详细信息" th:attr="data-url=@{'/store/node/detail/'+${item.id}}"
+                           data-size="720,540" href="#">详细</a>
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除节点 '+ ${item.nodeAddr}"
+                           th:href="@{'/api/store/node/' + ${item.id}}">删除</a>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+        <!--<div th:replace="/common/fragment :: page"></div>-->
+    </div>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+<script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
+<script type="text/javascript">
+    function getPageByCriteria() {
+        console.log('select 事件')
+
+        var envSelectedOption = $("#getPageByEnv option:selected")
+        var envParam = envSelectedOption.text()
+
+        var typeSelectedOption = $("#getPageByType option:selected")
+        var typeParam = typeSelectedOption.text()
+
+        url = '?env=' + envParam + '&type=' + typeParam
+        window.location.href = window.location.pathname + url;
+    }
+</script>
+</body>
+</html>

+ 11 - 0
web/src/main/resources/templates/console/object/add.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body>
+<div class="layui-form timo-compile">
+</div>
+
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 93 - 0
web/src/main/resources/templates/console/object/index.html

@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org"
+      xmlns:mo="https://gitee.com/aun/Timo">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
+    <link rel="stylesheet" th:href="@{/lib/zTree_v3/css/zTreeStyle/zTreeStyle.css}" type="text/css">
+</head>
+
+<body class="timo-layout-page">
+<div class="layui-card">
+    <div class="layui-card-header timo-card-header">
+        <span><i class="fa fa-bars"></i> 文件列表</span>
+        <i class="layui-icon layui-icon-refresh refresh-btn"></i>
+    </div>
+    <div class="layui-card-body">
+        <div class="layui-row timo-card-screen put-row">
+            <div class="layui-row timo-card-screen put-row">
+                <div class="pull-right">
+                    <div class="btn-group-right">
+                        <button class="layui-btn open-popup" data-title="添加应用" th:attr="data-url=@{/app/bd/add}"
+                                data-size="640,480">
+                            <i class="fa fa-plus"></i> 新建目录
+                        </button>
+                        <button class="layui-btn open-popup" data-title="添加应用" th:attr="data-url=@{/app/bd/add}"
+                                data-size="640,480">
+                            <i class="fa fa-upload"></i> 上传文件
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="timo-table-wrap">
+            <table class="layui-table timo-table">
+                <thead>
+                <tr>
+                    <th class="timo-table-checkbox">
+                        <label class="timo-checkbox"><input type="checkbox">
+                            <i class="layui-icon layui-icon-ok"></i></label>
+                    </th>
+                    <th class="sortable" data-field="appName">ID</th>
+                    <th class="sortable" data-field="appId">文件名</th>
+                    <th class="sortable" data-field="repoBranch">文件大小</th>
+                    <th class="sortable" data-field="appName">存储类型</th>
+                    <th class="sortable" data-field="appType">更新时间</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td>
+                        <label class="timo-checkbox">
+                            <input type="checkbox" th:value="${item.id}">
+                            <i class="layui-icon layui-icon-ok"></i>
+                        </label>
+                    </td>
+                    <td th:text="${item.id}">ID</td>
+                    <td th:text="${item.prefix}">前缀</td>
+                    <td th:text="${item.maxSize}">最大文件</td>
+                    <td th:text="${item.name}">简介</td>
+                    <td th:text="${item.bindDomain}">绑定域名</td>
+                    <td>
+                        <a class="open-popup" data-title="应用详细信息" th:attr="data-url=@{'/app/config/app/detail/'+${item.id}}"
+                           data-size="1200,500" href="#">详细</a>
+                        <a class="open-popup" data-title="编辑" th:attr="data-url=@{'/app/config/app/edit/'+${item.id}}"
+                           data-size="1200,500" href="#">编辑</a>
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.id}"
+                           th:href="@{'/api/app/config/app/' + ${item.id}}">删除</a>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+        <!--<div th:replace="/common/fragment :: page"></div>-->
+    </div>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+<script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
+<script type="text/javascript">
+    function getPageByCriteria() {
+        console.log('select 事件')
+
+        var envSelectedOption = $("#getPageByEnv option:selected")
+        var envParam = envSelectedOption.text()
+
+        var typeSelectedOption = $("#getPageByType option:selected")
+        var typeParam = typeSelectedOption.text()
+
+        url = '?env=' + envParam + '&type=' + typeParam
+        window.location.href = window.location.pathname + url;
+    }
+</script>
+</body>
+</html>

+ 86 - 0
web/src/main/resources/templates/console/object/upload.html

@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body class="timo-layout-page">
+<div class="layui-row layui-col-space20">
+    <div class="layui-col-md4">
+        <div class="layui-card build-card">
+            <div class="layui-card-header timo-card-header">
+                <span><i class="fa fa-bars"></i> 目录树</span>
+                <button class="layui-btn layui-btn-primary layui-btn-sm build-generate">生成HTML</button>
+            </div>
+            <div class="layui-card-body layui-form timo-compile build-panel"></div>
+        </div>
+    </div>
+    <div class="layui-col-md4">
+        <div class="layui-card">
+            <div class="layui-card-header timo-card-header">
+                <span><i class="fa fa-bars"></i> 上传文件</span>
+            </div>
+            <div class="layui-card-body">
+                <div class="layui-form timo-compile element-panel">
+                    <div class="layui-form-item">
+                        <label class="layui-form-label">选择 Channel</label>
+                        <div class="layui-input-inline">
+                            <select name="interest" lay-filter="aihao">
+                                <option value=""></option>
+                                <option value="0">写作</option>
+                                <option value="1">阅读</option>
+                                <option value="2">游戏</option>
+                                <option value="3">音乐</option>
+                                <option value="4">旅行</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label">上传文件</label>
+                        <div class="layui-input-inline">
+                            <button type="button" class="layui-btn upload-file" name="files[]"
+                                    th:attr="up-url=@{/store/object/upload}" up-field="path">
+                                <i class="layui-icon">&#xe67c;</i>上传图片
+                            </button>
+                        </div>
+                        <div class="upload-show"></div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label">时间选择</label>
+                        <div class="layui-input-inline">
+                            <input type="text" class="layui-input" id="laydate" placeholder="yyyy-MM-dd">
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label">开关</label>
+                        <div class="layui-input-block">
+                            <input type="checkbox" name="close" lay-skin="switch" lay-text="ON|OFF">
+                        </div>
+                    </div>
+                    <div class="layui-form-item layui-form-text">
+                        <label class="layui-form-label">文本域</label>
+                        <div class="layui-input-block">
+                            <textarea placeholder="请输入内容" class="layui-textarea" name="desc"></textarea>
+                        </div>
+                    </div>
+                    <div class="layui-form-item timo-finally">
+                        <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+                        <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="layui-col-md4">
+        <div class="layui-card build-card">
+            <div class="layui-card-header timo-card-header">
+                <span><i class="fa fa-bars"></i> 构建面板</span>
+                <button class="layui-btn layui-btn-primary layui-btn-sm build-generate">生成HTML</button>
+            </div>
+            <div class="layui-card-body layui-form timo-compile build-panel"></div>
+        </div>
+    </div>
+</div>
+</body>
+
+<script th:replace="/common/template :: script"></script>
+<script type="text/javascript" th:src="@{/js/build.js}"></script>
+</html>

+ 35 - 0
web/src/main/resources/templates/console/userkey/add.html

@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body>
+<div class="layui-form timo-compile">
+    <form th:action="@{/api/store/my/key/create}">
+        <table class="layui-table timo-detail-table">
+            <tbody>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">存储节点</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="nodeId">
+                                <option th:each="item : ${storeNodes}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="layui-form-item timo-finally">
+            <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+            <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
+        </div>
+    </form>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 57 - 0
web/src/main/resources/templates/console/userkey/index1.html

@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org"
+      xmlns:mo="https://gitee.com/aun/Timo">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
+    <link rel="stylesheet" th:href="@{/lib/zTree_v3/css/zTreeStyle/zTreeStyle.css}" type="text/css">
+</head>
+
+<body class="timo-layout-page">
+<div class="layui-card">
+    <div class="layui-card-header timo-card-header">
+        <span><i class="fa fa-bars"></i> 我的 Key 列表</span>
+        <i class="layui-icon layui-icon-refresh refresh-btn"></i>
+    </div>
+    <div class="layui-card-body">
+        <div class="timo-table-wrap">
+            <table class="layui-table timo-table">
+                <thead>
+                <tr>
+                    <th class="sortable" data-field="appName">AccessKeyId</th>
+                    <th class="sortable" data-field="appName">AccessKeySecret</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td th:text="${item.accessKeyId}">应用名</td>
+                    <td th:text="${item.accessKeySecret}">应用 ID</td>
+                    <td>
+                        <a class="ajax-post" th:attr="data-msg='确定要重新生成?'" th:href="@{'/api/store/my/key/regenerate'}">重新生成</a>
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.accessKeyId}"
+                           th:href="@{'/api/store/my/key/delete/' + ${item.accessKeyId}}">删除</a>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+<script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
+<script type="text/javascript">
+    function getPageByCriteria() {
+        console.log('select 事件')
+
+        var envSelectedOption = $("#getPageByEnv option:selected")
+        var envParam = envSelectedOption.text()
+
+        var typeSelectedOption = $("#getPageByType option:selected")
+        var typeParam = typeSelectedOption.text()
+
+        url = '?env=' + envParam + '&type=' + typeParam
+        window.location.href = window.location.pathname + url;
+    }
+</script>
+</body>
+</html>

+ 47 - 0
web/src/main/resources/templates/console/usernode/add.html

@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body>
+<div class="layui-form timo-compile">
+    <form th:action="@{/api/store/my/add}">
+        <table class="layui-table timo-detail-table">
+            <tbody>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">选择存储节点</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="nodeId">
+                                <option th:each="item : ${storeNodes}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label">bucket</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <input class="layui-input" name="bucket" placeholder="请填写节点 bucket"/>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="layui-form-item timo-finally">
+            <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+            <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
+        </div>
+    </form>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 10 - 0
web/src/main/resources/templates/console/usernode/detail.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+<body>
+    <div class="timo-detail-page">
+        <div class="timo-detail-title">我的节点信息</div>
+    </div>
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 72 - 0
web/src/main/resources/templates/console/usernode/index1.html

@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org"
+      xmlns:mo="https://gitee.com/aun/Timo">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
+    <link rel="stylesheet" th:href="@{/lib/zTree_v3/css/zTreeStyle/zTreeStyle.css}" type="text/css">
+</head>
+
+<body class="timo-layout-page">
+<div class="layui-card">
+    <div class="layui-card-header timo-card-header">
+        <span><i class="fa fa-bars"></i> 我的节点列表</span>
+        <i class="layui-icon layui-icon-refresh refresh-btn"></i>
+    </div>
+    <div class="layui-card-body">
+        <div class="layui-row timo-card-screen put-row">
+            <div class="layui-row timo-card-screen put-row">
+                <div class="pull-right">
+                    <div class="btn-group-right">
+                        <button class="layui-btn open-popup" data-title="添加节点" th:attr="data-url=@{/store/my/add}"
+                                data-size="640,480">
+                            <i class="fa fa-plus"></i> 添加
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="timo-table-wrap">
+            <table class="layui-table timo-table">
+                <thead>
+                <tr>
+                    <th class="sortable" data-field="appName">节点域名</th>
+                    <th class="sortable" data-field="appName">节点地址</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td th:text="${item.domain}">节点域名</td>
+                    <td th:text="${item.nodeAddr}">节点地址</td>
+                    <td>
+                        <a class="open-popup" data-title="设置存储节点属性" th:attr="data-url=@{'/store/node/config/'+${item.id}}"
+                           data-size="640,480" href="#">设置</a>
+                        <a class="open-popup" data-title="我的节点详细信息" th:attr="data-url=@{'/store/my/detail/'+${item.id}}"
+                           data-size="640,480" href="#">详细</a>
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.domain}"
+                           th:href="@{'/api/store/my/delete/' + ${item.id}}">删除</a>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+<script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
+<script type="text/javascript">
+    function getPageByCriteria() {
+        console.log('select 事件')
+
+        var envSelectedOption = $("#getPageByEnv option:selected")
+        var envParam = envSelectedOption.text()
+
+        var typeSelectedOption = $("#getPageByType option:selected")
+        var typeParam = typeSelectedOption.text()
+
+        url = '?env=' + envParam + '&type=' + typeParam
+        window.location.href = window.location.pathname + url;
+    }
+</script>
+</body>
+</html>

+ 0 - 0
web/src/main/resources/templates/admin/spider/chart.html → web/src/main/resources/templates/spider/chart.html


+ 0 - 0
web/src/main/resources/templates/admin/spider/index.html → web/src/main/resources/templates/spider/index.html