Bladeren bron

添加 tnb 项目的 admin 模块, 这引入了 dubbo 和 curator 依赖

reghao 1 jaar geleden
bovenliggende
commit
fd7859861b
59 gewijzigde bestanden met toevoegingen van 2136 en 19 verwijderingen
  1. 19 0
      admin-api/pom.xml
  2. 21 0
      admin-api/src/main/java/cn/reghao/bnt/admin/api/dto/AdminMessage.java
  3. 11 0
      admin-api/src/main/java/cn/reghao/bnt/admin/api/iface/AdminService.java
  4. 9 0
      admin-api/src/main/java/cn/reghao/bnt/admin/api/iface/SiteConfigService.java
  5. 1 0
      pom.xml
  6. 18 1
      web/pom.xml
  7. 3 2
      web/src/main/java/cn/reghao/bnt/web/WebApplication.java
  8. 57 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/AccountController.java
  9. 33 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/ContentController.java
  10. 49 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/GatewayController.java
  11. 47 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/MessageController.java
  12. 64 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/OssFileController.java
  13. 54 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/SiteController.java
  14. 64 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/page/AccountPageController.java
  15. 127 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/page/ContentPageController.java
  16. 112 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/page/FilePageController.java
  17. 30 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/page/GatewayPageController.java
  18. 30 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/page/MessagePageController.java
  19. 33 0
      web/src/main/java/cn/reghao/bnt/web/admin/controller/page/SitePageController.java
  20. 15 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/constant/AppType.java
  21. 30 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/constant/BuildStatus.java
  22. 30 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/constant/DeployStatus.java
  23. 19 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/constant/EnvType.java
  24. 15 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/constant/build/CompileType.java
  25. 13 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/constant/build/RepoAuthType.java
  26. 13 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/constant/build/RepoType.java
  27. 15 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/dto/NoticeContent.java
  28. 17 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/po/SiteNotice.java
  29. 15 0
      web/src/main/java/cn/reghao/bnt/web/admin/model/vo/KeyValue.java
  30. 31 0
      web/src/main/java/cn/reghao/bnt/web/admin/rpc/AdminServiceImpl.java
  31. 18 0
      web/src/main/java/cn/reghao/bnt/web/admin/rpc/SiteConfigServiceImpl.java
  32. 40 0
      web/src/main/java/cn/reghao/bnt/web/admin/service/AdminAccountService.java
  33. 26 0
      web/src/main/java/cn/reghao/bnt/web/admin/service/ContentService.java
  34. 36 0
      web/src/main/java/cn/reghao/bnt/web/admin/service/SiteService.java
  35. 1 1
      web/src/main/java/cn/reghao/bnt/web/devops/machine/service/MachineService.java
  36. 1 1
      web/src/main/java/cn/reghao/bnt/web/parser/task/DataConsumer.java
  37. 3 0
      web/src/main/resources/application-dev.yml
  38. 3 0
      web/src/main/resources/application-test.yml
  39. 6 0
      web/src/main/resources/application.yml
  40. 82 0
      web/src/main/resources/templates/admin/account/chargeindex.html
  41. 52 0
      web/src/main/resources/templates/admin/account/detail.html
  42. 107 0
      web/src/main/resources/templates/admin/account/index.html
  43. 91 0
      web/src/main/resources/templates/admin/channel/add.html
  44. 48 0
      web/src/main/resources/templates/admin/channel/edit.html
  45. 36 0
      web/src/main/resources/templates/admin/channel/edit1.html
  46. 75 0
      web/src/main/resources/templates/admin/channel/index.html
  47. 114 0
      web/src/main/resources/templates/admin/content/index.html
  48. 124 0
      web/src/main/resources/templates/admin/content/videocategory.html
  49. 166 0
      web/src/main/resources/templates/admin/content/videolist.html
  50. 78 0
      web/src/main/resources/templates/admin/content/videopreview.html
  51. 10 0
      web/src/main/resources/templates/admin/content/videoresource.html
  52. 10 0
      web/src/main/resources/templates/admin/content/videostatus.html
  53. 2 2
      web/src/test/java/cn/reghao/bnt/web/account/AccountTest.java
  54. 2 2
      web/src/test/java/cn/reghao/bnt/web/devops/AppConfigTest.java
  55. 2 2
      web/src/test/java/cn/reghao/bnt/web/parser/BiliSpiderTest.java
  56. 2 2
      web/src/test/java/cn/reghao/bnt/web/parser/CommonDataTest.java
  57. 2 2
      web/src/test/java/cn/reghao/bnt/web/parser/LocalDataTest.java
  58. 2 2
      web/src/test/java/cn/reghao/bnt/web/parser/TmallSpiderTest.java
  59. 2 2
      web/src/test/java/cn/reghao/bnt/web/parser/ZhihuSpiderTest.java

+ 19 - 0
admin-api/pom.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>bnt</artifactId>
+        <groupId>cn.reghao.bnt</groupId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>admin-api</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+    </properties>
+
+</project>

+ 21 - 0
admin-api/src/main/java/cn/reghao/bnt/admin/api/dto/AdminMessage.java

@@ -0,0 +1,21 @@
+package cn.reghao.bnt.admin.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 17:26:07
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class AdminMessage implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int type;
+    private String content;
+}

+ 11 - 0
admin-api/src/main/java/cn/reghao/bnt/admin/api/iface/AdminService.java

@@ -0,0 +1,11 @@
+package cn.reghao.bnt.admin.api.iface;
+
+import cn.reghao.bnt.admin.api.dto.AdminMessage;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 17:20:31
+ */
+public interface AdminService {
+    void sendMessage(AdminMessage adminMessage);
+}

+ 9 - 0
admin-api/src/main/java/cn/reghao/bnt/admin/api/iface/SiteConfigService.java

@@ -0,0 +1,9 @@
+package cn.reghao.bnt.admin.api.iface;
+
+/**
+ * @author reghao
+ * @date 2024-04-06 20:40:56
+ */
+public interface SiteConfigService {
+    String getSiteNotice();
+}

+ 1 - 0
pom.xml

@@ -16,6 +16,7 @@
         <module>spider</module>
         <module>browser</module>
         <module>crawler</module>
+        <module>admin-api</module>
     </modules>
     <packaging>pom</packaging>
 

+ 18 - 1
web/pom.xml

@@ -164,7 +164,7 @@
             <version>2.6</version>
         </dependency>
 
-        <!-- spider 依赖-->
+        <!-- parser 依赖-->
         <dependency>
             <groupId>cn.reghao.jutil</groupId>
             <artifactId>media</artifactId>
@@ -244,6 +244,23 @@
             <artifactId>protobuf-java</artifactId>
             <version>3.20.0-rc-1</version>
         </dependency>
+
+        <!-- admin 依赖 -->
+        <dependency>
+            <groupId>cn.reghao.bnt</groupId>
+            <artifactId>admin-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-starter</artifactId>
+            <version>2.7.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-recipes</artifactId>
+            <version>2.12.0</version>
+        </dependency>
     </dependencies>
 
     <profiles>

+ 3 - 2
web/src/main/java/cn/reghao/bnt/web/SpringApplication.java → web/src/main/java/cn/reghao/bnt/web/WebApplication.java

@@ -3,6 +3,7 @@ package cn.reghao.bnt.web;
 import cn.reghao.bnt.web.config.AppProperties;
 import cn.reghao.bnt.web.parser.util.FileCache;
 import cn.reghao.oss.sdk.model.OssConsoleConfig;
+import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.domain.EntityScan;
 import org.springframework.context.annotation.Bean;
@@ -13,9 +14,9 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
 @EnableJpaRepositories
 @EntityScan({"cn.reghao.bnt.web"})
 @ComponentScan({"cn.reghao.bnt.web", "cn.reghao.bnt.common"})
-public class SpringApplication {
+public class WebApplication {
 	public static void main(String[] args) {
-		org.springframework.boot.SpringApplication.run(SpringApplication.class, args);
+		SpringApplication.run(WebApplication.class, args);
 	}
 
 	@Bean

+ 57 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/AccountController.java

@@ -0,0 +1,57 @@
+package cn.reghao.bnt.web.admin.controller;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.bnt.web.admin.service.AdminAccountService;
+import cn.reghao.tnb.user.api.dto.UserInfo;
+import cn.reghao.tnb.user.api.dto.UserSearch;
+import cn.reghao.tnb.user.api.iface.AdminUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-15 09:15:40
+ */
+@Api(tags = "用户管理接口")
+@RestController
+@RequestMapping("/api/account")
+public class AccountController {
+    @DubboReference(check = false, retries = 0)
+    private AdminUserService adminUserService;
+
+    private AdminAccountService adminAccountService;
+
+    public AccountController(AdminAccountService adminAccountService) {
+        this.adminAccountService = adminAccountService;
+    }
+
+    @PostMapping("/create")
+    public String updateUserProfile() {
+        return WebResult.success();
+    }
+
+    @ApiOperation("获取用户列表")
+    @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getUserList(UserSearch userSearch) {
+        PageList<UserInfo> pageList = adminUserService.getUserList(userSearch);
+        return WebResult.success(pageList);
+    }
+
+    @ApiOperation("同意用户充值")
+    @PostMapping(value = "/charge/approve/{chargeId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String approveCharge(@PathVariable("chargeId") Long chargeId) {
+        adminAccountService.approve(chargeId);
+        return WebResult.success();
+    }
+
+    @ApiOperation("拒绝用户充值")
+    @PostMapping(value = "/charge/decline/{chargeId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String declineCharge(@PathVariable("chargeId") Long chargeId) {
+        adminAccountService.decline(chargeId);
+        return WebResult.success();
+    }
+}

+ 33 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/ContentController.java

@@ -0,0 +1,33 @@
+package cn.reghao.bnt.web.admin.controller;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.tnb.content.api.dto.UserVideoPost;
+import cn.reghao.tnb.content.api.dto.VideoSearch;
+import cn.reghao.tnb.content.api.iface.AdminVideoService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author reghao
+ * @date 2024-02-15 09:36:39
+ */
+@Api(tags = "视频管理接口")
+@RestController
+@RequestMapping("/api/admin/video")
+public class ContentController {
+    @DubboReference(check = false, retries = 0, timeout = 10_000)
+    private AdminVideoService adminVideoService;
+
+    @ApiOperation("获取稿件数据")
+    @GetMapping(value = "/post", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getPostList(VideoSearch videoSearch) {
+        PageList<UserVideoPost> pageList = adminVideoService.getVideoPosts(videoSearch);
+        return WebResult.success(pageList);
+    }
+}

+ 49 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/GatewayController.java

@@ -0,0 +1,49 @@
+package cn.reghao.bnt.web.admin.controller;
+
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.bnt.web.admin.model.dto.NoticeContent;
+import cn.reghao.bnt.web.admin.service.SiteService;
+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-15 09:28:09
+ */
+@Api(tags = "站点管理接口")
+@RestController
+@RequestMapping("/api/gateway")
+public class GatewayController {
+    private final SiteService siteService;
+
+    public GatewayController(SiteService siteService) {
+        this.siteService = siteService;
+    }
+
+    @ApiOperation("添加站点公告")
+    @PostMapping(value = "/site/notice", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addSiteNotice(@RequestBody @Validated NoticeContent noticeContent) {
+        siteService.add(noticeContent.getContent());
+        return WebResult.success();
+    }
+
+    @ApiOperation("获取站点公告")
+    @GetMapping(value = "/site/notice", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getSiteNotice() {
+        String content = siteService.get();
+        return WebResult.success(content);
+    }
+
+    @GetMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getMessages() {
+        return WebResult.success();
+    }
+
+    @PostMapping(value = "/message/approve", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String approve() {
+        return WebResult.success();
+    }
+}

+ 47 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/MessageController.java

@@ -0,0 +1,47 @@
+package cn.reghao.bnt.web.admin.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Slf4j
+@Api(tags = "机器接口")
+@RestController
+@RequestMapping("/api/message")
+public class MessageController {
+    @ApiOperation(value = "设置机器环境")
+    @PostMapping(value = "/env/{machineId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> editExtraMachineInfo(@PathVariable("machineId") String machineId,
+                                                       @RequestParam("env") String env) {
+        return ResponseEntity.ok().body(WebResult.success());
+    }
+
+    @ApiOperation(value = "编辑 SSH 认证信息")
+    @PostMapping(value = "/sshauth/{machineId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> editSshAuth(@PathVariable("machineId") String machineId) {
+        return ResponseEntity.ok().body(WebResult.success());
+    }
+
+    @ApiOperation(value = "删除机器")
+    @DeleteMapping(value = "/{machineId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String machine(@PathVariable("machineId") String machineId) {
+        return WebResult.result(Result.success());
+    }
+
+    @PostMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteAll(@RequestParam(value = "ids") List<String> machineIds) {
+        return WebResult.result(Result.success());
+    }
+}

+ 64 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/OssFileController.java

@@ -0,0 +1,64 @@
+package cn.reghao.bnt.web.admin.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+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.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2019-11-27 11:29:43
+ */
+@Slf4j
+@Api(tags = "应用配置 CRUD 接口")
+@RestController
+@RequestMapping("/api/oss/config/app")
+public class OssFileController {
+    @ApiOperation(value = "添加应用配置")
+    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addAppConfig() {
+        Result result = Result.success();
+        return WebResult.result(result);
+    }
+
+    // TODO 使用 @PathVariable 注解时会自动填充实体
+    @ApiOperation(value = "复制应用配置")
+    @PostMapping(value = "/copy", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String copyAppConfig() {
+        Result result = Result.success();
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "修改应用配置")
+    @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String updateAppConfig() {
+        Result result = Result.success();
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "删除应用配置")
+    @DeleteMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteAppConfig(@PathVariable("id") String app) {
+        Result result = Result.success();
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "清空应用本地仓库")
+    @DeleteMapping(value = "/repo/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteAppLocalRepo(@PathVariable("id") String app) {
+        Result result = Result.success();
+        return WebResult.result(result);
+    }
+
+    @PostMapping(value = "/enable", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteAll(@RequestParam(value = "ids") List<String> appIds) {
+        Result result = Result.success();
+        return WebResult.result(result);
+    }
+}

+ 54 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/SiteController.java

@@ -0,0 +1,54 @@
+package cn.reghao.bnt.web.admin.controller;
+
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.bnt.admin.api.dto.AdminMessage;
+import cn.reghao.bnt.web.admin.model.dto.NoticeContent;
+import cn.reghao.bnt.web.admin.service.SiteService;
+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.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-15 09:28:09
+ */
+@Api(tags = "站点管理接口")
+@RestController
+@RequestMapping("/api/site")
+public class SiteController {
+    private final SiteService siteService;
+
+    public SiteController(SiteService siteService) {
+        this.siteService = siteService;
+    }
+
+    @ApiOperation("添加站点公告")
+    @PostMapping(value = "/notice", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addSiteNotice(@RequestBody @Validated NoticeContent noticeContent) {
+        siteService.add(noticeContent.getContent());
+        return WebResult.success();
+    }
+
+    @ApiOperation("获取站点公告")
+    @GetMapping(value = "/notice", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getSiteNotice() {
+        String content = siteService.get();
+        return WebResult.success(content);
+    }
+
+    @GetMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getMessages() {
+        List<AdminMessage> list = siteService.getChargeReqs();
+        return WebResult.success(list);
+    }
+
+    @PostMapping(value = "/message/approve", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String approve(@RequestBody AdminMessage chargeReq) {
+        siteService.approve();
+        return WebResult.success();
+    }
+}

+ 64 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/page/AccountPageController.java

@@ -0,0 +1,64 @@
+package cn.reghao.bnt.web.admin.controller.page;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.tnb.account.api.dto.AccountInfo;
+import cn.reghao.bnt.web.admin.service.AdminAccountService;
+import cn.reghao.tnb.user.api.dto.WalletChargeDto;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+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 org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.*;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Slf4j
+@Api(tags = "account 管理")
+@Controller
+@RequestMapping("/account")
+public class AccountPageController {
+    private final AdminAccountService accountService;
+
+    public AccountPageController(AdminAccountService accountService) {
+        this.accountService = accountService;
+    }
+
+    @ApiOperation(value = "帐号列表页面")
+    @GetMapping("/list")
+    public String indexPage(@RequestParam(value = "env", required = false) String env,
+                            @RequestParam(value = "machineIpv4", required = false) String machineIpv4,
+                            Model model) {
+        int pageNumber = 1;
+        int pageSize = 10;
+        PageList<AccountInfo> pageList = accountService.getUserAccounts(pageNumber);
+        long total = pageList.getTotalSize();
+
+        Page<AccountInfo> page = new PageImpl<>(pageList.getList());
+        model.addAttribute("env", env);
+        model.addAttribute("page", page);
+        model.addAttribute("list", page.getContent());
+        return "/admin/account/index";
+    }
+
+    @ApiOperation(value = "充值请求页面")
+    @GetMapping("/charge")
+    public String chargeReqPage(@RequestParam(value = "env", required = false) String env,
+                                @RequestParam(value = "machineIpv4", required = false) String machineIpv4,
+                                Model model) {
+
+        List<WalletChargeDto> list = accountService.getChargeReqs();
+        model.addAttribute("env", env);
+        model.addAttribute("page", Page.empty());
+        model.addAttribute("list", list);
+        return "/admin/account/chargeindex";
+    }
+}

+ 127 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/page/ContentPageController.java

@@ -0,0 +1,127 @@
+package cn.reghao.bnt.web.admin.controller.page;
+
+import cn.reghao.bnt.web.util.DefaultSetting;
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.bnt.web.admin.service.ContentService;
+import cn.reghao.tnb.content.api.dto.UserVideoPost;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Slf4j
+@Api(tags = "content 管理")
+@Controller
+@RequestMapping("/content")
+public class ContentPageController {
+    private ContentService contentService;
+
+    public ContentPageController(ContentService contentService) {
+        this.contentService = contentService;
+    }
+
+    @ApiOperation(value = "视频分区")
+    @GetMapping("/category")
+    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) {
+        if (env == null) {
+            env = DefaultSetting.getDefaultAppEnv();
+        }
+
+        if (type == null) {
+            type = DefaultSetting.getDefaultAppType();
+        }
+
+        model.addAttribute("env", env);
+        model.addAttribute("type", type);
+        model.addAttribute("page", Page.empty());
+        model.addAttribute("list", Collections.emptyList());
+        return "/admin/content/videocategory";
+    }
+
+    @ApiOperation(value = "稿件列表")
+    @GetMapping("/post")
+    public String postIndex(@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 = DefaultSetting.getDefaultAppEnv();
+        }
+
+        if (type == null) {
+            type = DefaultSetting.getDefaultAppType();
+        }
+
+        PageList<UserVideoPost> pageList = contentService.getUserVideoPosts(1);
+        Page<UserVideoPost> page = new PageImpl<>(pageList.getList());
+
+        model.addAttribute("env", env);
+        model.addAttribute("type", type);
+        model.addAttribute("page", page);
+        model.addAttribute("list", page.getContent());
+        return "/admin/content/videolist";
+    }
+
+    @ApiOperation(value = "推荐模块")
+    @GetMapping("/video/preview/{videoId}")
+    public String previewPage(@PathVariable(value = "videoId") String videoId, Model model) {
+
+        model.addAttribute("videoInfo", "type");
+        return "/admin/content/videopreview";
+    }
+
+    @ApiOperation(value = "推荐模块")
+    @GetMapping("/rcmd")
+    public String rcmdIndex(@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 = DefaultSetting.getDefaultAppEnv();
+        }
+
+        if (type == null) {
+            type = DefaultSetting.getDefaultAppType();
+        }
+
+        model.addAttribute("env", env);
+        model.addAttribute("type", type);
+        model.addAttribute("page", Page.empty());
+        model.addAttribute("list", Collections.emptyList());
+        return "/app/rcmdindex";
+    }
+
+    @ApiOperation(value = "搜索模块")
+    @GetMapping("/search")
+    public String searchIndex(@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 = DefaultSetting.getDefaultAppEnv();
+        }
+
+        if (type == null) {
+            type = DefaultSetting.getDefaultAppType();
+        }
+
+        model.addAttribute("env", env);
+        model.addAttribute("type", type);
+        model.addAttribute("page", Page.empty());
+        model.addAttribute("list", Collections.emptyList());
+        return "/app/searchindex";
+    }
+}

+ 112 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/page/FilePageController.java

@@ -0,0 +1,112 @@
+package cn.reghao.bnt.web.admin.controller.page;
+
+import cn.reghao.bnt.web.account.service.UserContext;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.api.constant.ObjectScope;
+import cn.reghao.oss.api.constant.ObjectType;
+import cn.reghao.oss.api.dto.ChannelScopeDto;
+import cn.reghao.oss.api.dto.ObjectChannel;
+import cn.reghao.oss.api.dto.UploadChannelDto;
+import cn.reghao.oss.api.dto.UploadChannelVo;
+import cn.reghao.oss.api.iface.ObjectChannelService;
+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;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Slf4j
+@Api(tags = "file 管理")
+@Controller
+@RequestMapping("/file/store/channel")
+public class FilePageController {
+    @DubboReference(check = false)
+    private 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.getUser().getId();
+        List<UploadChannelVo> list = objectChannelService.getObjectChannels(loginUser);
+        model.addAttribute("list", list);
+        return "/admin/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"));
+
+        model.addAttribute("objectTypes", objectTypes);
+        model.addAttribute("objectScopes", objectScopes);
+        model.addAttribute("sizeList", sizeList);
+        return "/admin/channel/add";
+    }
+
+    @ApiOperation(value = "添加上传通道")
+    @PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public String addUploadChannel(@Validated UploadChannelDto uploadChannelDto) {
+        Result result = objectChannelService.addObjectChannel(uploadChannelDto);
+        return WebResult.result(result);
+    }
+
+    @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 "/admin/channel/edit";
+    }
+
+    @ApiOperation(value = "修改通道可见范围")
+    @PostMapping(value = "/update/scope", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public String updateChannelScope(ChannelScopeDto channelScopeDto) {
+        objectChannelService.updateScope(channelScopeDto);
+        return WebResult.success();
+    }
+}

+ 30 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/page/GatewayPageController.java

@@ -0,0 +1,30 @@
+package cn.reghao.bnt.web.admin.controller.page;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+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.Collections;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Slf4j
+@Api(tags = "gateway 管理")
+@Controller
+@RequestMapping("/gateway")
+public class GatewayPageController {
+    @ApiOperation(value = "路由列表页面")
+    @GetMapping("/route")
+    public String buildDirPage(Model model) {
+        model.addAttribute("page", Page.empty());
+        model.addAttribute("list", Collections.emptyList());
+        return "/app/config/build/builddir";
+    }
+}

+ 30 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/page/MessagePageController.java

@@ -0,0 +1,30 @@
+package cn.reghao.bnt.web.admin.controller.page;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+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.Collections;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Slf4j
+@Api(tags = "message 管理")
+@Controller
+@RequestMapping("/message")
+public class MessagePageController {
+    @ApiOperation(value = "消息列表页面")
+    @GetMapping("/list")
+    public String buildDirPage(Model model) {
+        model.addAttribute("page", Page.empty());
+        model.addAttribute("list", Collections.emptyList());
+        return "/app/config/build/builddir";
+    }
+}

+ 33 - 0
web/src/main/java/cn/reghao/bnt/web/admin/controller/page/SitePageController.java

@@ -0,0 +1,33 @@
+package cn.reghao.bnt.web.admin.controller.page;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+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 org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.Collections;
+
+/**
+ * @author reghao
+ * @date 2019-08-30 18:49:15
+ */
+@Slf4j
+@Api(tags = "site 管理")
+@Controller
+@RequestMapping("/site")
+public class SitePageController {
+    @ApiOperation(value = "站点配置页面")
+    @GetMapping("/config")
+    public String indexPage(@RequestParam(value = "env", required = false) String env,
+                            Model model) {
+        model.addAttribute("env", env);
+        model.addAttribute("page", Page.empty());
+        model.addAttribute("list", Collections.emptyList());
+        return "/admin/site/config";
+    }
+}

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

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.admin.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/admin/model/constant/BuildStatus.java

@@ -0,0 +1,30 @@
+package cn.reghao.bnt.web.admin.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/admin/model/constant/DeployStatus.java

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

+ 19 - 0
web/src/main/java/cn/reghao/bnt/web/admin/model/constant/EnvType.java

@@ -0,0 +1,19 @@
+package cn.reghao.bnt.web.admin.model.constant;
+
+/**
+ * @author reghao
+ * @date 2023-12-01 14:17:44
+ */
+public enum EnvType {
+    dev("开发环境"),test("测试环境"),uat("预发布环境"),prod("生产环境");
+
+    private final String desc;
+
+    EnvType(String desc) {
+        this.desc = desc;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}

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

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.admin.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/admin/model/constant/build/RepoAuthType.java

@@ -0,0 +1,13 @@
+package cn.reghao.bnt.web.admin.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/admin/model/constant/build/RepoType.java

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

+ 15 - 0
web/src/main/java/cn/reghao/bnt/web/admin/model/dto/NoticeContent.java

@@ -0,0 +1,15 @@
+package cn.reghao.bnt.web.admin.model.dto;
+
+import lombok.Getter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author reghao
+ * @date 2023-12-07 14:36:32
+ */
+@Getter
+public class NoticeContent {
+    @NotBlank
+    private String content;
+}

+ 17 - 0
web/src/main/java/cn/reghao/bnt/web/admin/model/po/SiteNotice.java

@@ -0,0 +1,17 @@
+package cn.reghao.bnt.web.admin.model.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author reghao
+ * @date 2023-12-07 14:03:51
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+public class SiteNotice {
+    private String content;
+    private long createBy;
+}

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

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

+ 31 - 0
web/src/main/java/cn/reghao/bnt/web/admin/rpc/AdminServiceImpl.java

@@ -0,0 +1,31 @@
+package cn.reghao.bnt.web.admin.rpc;
+
+import cn.reghao.bnt.admin.api.dto.AdminMessage;
+import cn.reghao.bnt.admin.api.iface.AdminService;
+import cn.reghao.bnt.web.devops.sys.db.repository.SysMessageRepository;
+import cn.reghao.bnt.web.devops.sys.model.po.SysMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 17:07:50
+ */
+@Slf4j
+@DubboService
+@Service
+public class AdminServiceImpl implements AdminService {
+    private final SysMessageRepository sysMessageRepository;
+    
+    public AdminServiceImpl(SysMessageRepository sysMessageRepository) {
+        this.sysMessageRepository = sysMessageRepository;
+    }
+
+    @Override
+    public void sendMessage(AdminMessage adminMessage) {
+        log.info("新消息: {} -> {}", adminMessage.getType(), adminMessage.getContent());
+        SysMessage sysMessage = new SysMessage("充值消息", adminMessage.getContent());
+        sysMessageRepository.save(sysMessage);
+    }
+}

+ 18 - 0
web/src/main/java/cn/reghao/bnt/web/admin/rpc/SiteConfigServiceImpl.java

@@ -0,0 +1,18 @@
+package cn.reghao.bnt.web.admin.rpc;
+
+import cn.reghao.bnt.admin.api.iface.SiteConfigService;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author reghao
+ * @date 2024-04-06 20:41:11
+ */
+@DubboService
+@Service
+public class SiteConfigServiceImpl implements SiteConfigService {
+    @Override
+    public String getSiteNotice() {
+        return null;
+    }
+}

+ 40 - 0
web/src/main/java/cn/reghao/bnt/web/admin/service/AdminAccountService.java

@@ -0,0 +1,40 @@
+package cn.reghao.bnt.web.admin.service;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.tnb.account.api.dto.AccountInfo;
+import cn.reghao.tnb.account.api.iface.AccountQuery;
+import cn.reghao.tnb.user.api.dto.WalletChargeDto;
+import cn.reghao.tnb.user.api.iface.UserWalletService;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-07-06 11:22:17
+ */
+@Service
+public class AdminAccountService {
+    @DubboReference(check = false)
+    private AccountQuery accountQuery;
+    @DubboReference(check = false)
+    private UserWalletService userWalletService;
+
+    public PageList<AccountInfo> getUserAccounts(int pageNumber) {
+        String screenName = "";
+        return accountQuery.getByScreenName(screenName, pageNumber);
+    }
+
+    public List<WalletChargeDto> getChargeReqs() {
+        return userWalletService.getChargeReqs();
+    }
+
+    public void approve(long chargeId) {
+        userWalletService.approveCharge(chargeId);
+    }
+
+    public void decline(long chargeId) {
+        userWalletService.declineCharge(chargeId);
+    }
+}

+ 26 - 0
web/src/main/java/cn/reghao/bnt/web/admin/service/ContentService.java

@@ -0,0 +1,26 @@
+package cn.reghao.bnt.web.admin.service;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.tnb.content.api.dto.UserVideoPost;
+import cn.reghao.tnb.content.api.dto.VideoSearch;
+import cn.reghao.tnb.content.api.iface.AdminVideoService;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author reghao
+ * @date 2024-07-06 22:16:55
+ */
+@Service
+public class ContentService {
+    @DubboReference(check = false)
+    private AdminVideoService adminVideoService;
+
+    public PageList<UserVideoPost> getUserVideoPosts(int pageNumber) {
+        String title = "";
+        VideoSearch videoSearch = new VideoSearch();
+        videoSearch.setType(1);
+        videoSearch.setPage(pageNumber);
+        return adminVideoService.getVideoPosts(videoSearch);
+    }
+}

+ 36 - 0
web/src/main/java/cn/reghao/bnt/web/admin/service/SiteService.java

@@ -0,0 +1,36 @@
+package cn.reghao.bnt.web.admin.service;
+
+import cn.reghao.bnt.admin.api.dto.AdminMessage;
+import cn.reghao.bnt.admin.api.iface.AdminService;
+import cn.reghao.bnt.web.admin.model.po.SiteNotice;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2023-12-07 14:10:09
+ */
+@Service
+public class SiteService {
+    @DubboReference(check = false)
+    private AdminService chargeService;
+
+    public void add(String content) {
+        long createBy = 10000;
+        SiteNotice siteNotice = new SiteNotice(content, createBy);
+    }
+
+    public String get() {
+        return null;
+    }
+
+    public List<AdminMessage> getChargeReqs() {
+        return Collections.emptyList();
+    }
+
+    public void approve() {
+    }
+}

+ 1 - 1
web/src/main/java/cn/reghao/bnt/web/devops/machine/service/MachineService.java

@@ -36,7 +36,7 @@ public class MachineService {
         this.machineInfoRepository = machineInfoRepository;
     }
 
-    @PostConstruct
+    //@PostConstruct
     private void loadFromDataSource() {
         machineHostRepository.findAll().forEach(machineHost -> {
             String machineId = machineHost.getMachineId();

+ 1 - 1
web/src/main/java/cn/reghao/bnt/web/parser/task/DataConsumer.java

@@ -64,7 +64,7 @@ public class DataConsumer {
         this.ossConsoleClientFactory = ossConsoleClientFactory;
     }
 
-    @PostConstruct
+    //@PostConstruct
     public void run() {
         threadPool.submit(new ConsumerThread());
         log.info("producer-consumer 模型启动...");

+ 3 - 0
web/src/main/resources/application-dev.yml

@@ -1,3 +1,6 @@
+dubbo:
+  registry:
+    address: zookeeper://localhost:2181
 spring:
   thymeleaf:
     cache: false

+ 3 - 0
web/src/main/resources/application-test.yml

@@ -1,3 +1,6 @@
+dubbo:
+  registry:
+    address: zookeeper://192.168.0.210:2181
 spring:
   thymeleaf:
     cache: true

+ 6 - 0
web/src/main/resources/application.yml

@@ -1,3 +1,9 @@
+dubbo:
+  scan:
+    base-packages: cn.reghao.bnt.web.admin.rpc
+  protocol:
+    name: dubbo
+    port: 4040
 server:
   port: 4020
   servlet:

+ 82 - 0
web/src/main/resources/templates/admin/account/chargeindex.html

@@ -0,0 +1,82 @@
+<!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})"></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">
+                        <div class="btn-group">
+                            <button class="layui-btn">批量处理<span class="caret"></span></button>
+                            <dl class="layui-nav-child layui-anim layui-anim-upbit">
+                                <dd><a class="ajax-status" th:href="@{/api/machine/host/delete}">批量删除</a></dd>
+                            </dl>
+                        </div>
+                    </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 data-field="machineIpv4">用户</th>
+                    <th data-field="bootTime">充值金额</th>
+                    <th data-field="status">当前状态</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td><label class="timo-checkbox"><input type="checkbox" th:value="${item.chargeId}">
+                        <i class="layui-icon layui-icon-ok"></i></label></td>
+                    <td th:text="${item.owner}"></td>
+                    <td th:text="${item.quantity}"></td>
+                    <td th:text="${item.quantity}"></td>
+                    <td>
+                        <a class="ajax-post" th:attr="data-msg='同意 '+ ${item.owner} + ' 的充值?'"
+                           th:href="@{'/api/account/charge/approve/' + ${item.chargeId}}">同意</a>
+                        <a class="ajax-post" th:attr="data-msg='拒绝 '+ ${item.owner} + ' 的充值?'"
+                           th:href="@{'/api/account/charge/decline/' + ${item.chargeId}}">拒绝</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 getPageByEnv() {
+        var selectedOption = $("#getPageByEnv option:selected")
+        var param = selectedOption.text()
+        url = '?env=' + param
+        window.location.href = window.location.pathname + url;
+    }
+
+    $(".machine-status").each(function () {
+        var text = $(this).text().trim()
+        if (text === 'Online') {
+            $(this).css("color", "#009688")
+        } else {
+            $(this).css("color", "#ff0000")
+        }
+    })
+</script>
+</body>
+</html>

+ 52 - 0
web/src/main/resources/templates/admin/account/detail.html

@@ -0,0 +1,52 @@
+<!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">
+            <thead>
+            <tr>
+                <th data-field="name">名字</th>
+                <th data-field="arch">架构</th>
+                <th data-field="version">版本</th>
+                <th data-field="byteOrder">字节序</th>
+                <th data-field="bootTime">启动时间</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr>
+                <td th:text="${machine.name}"></td>
+                <td th:text="${machine.arch}"></td>
+                <td th:text="${machine.version}"></td>
+                <td th:text="${machine.byteOrder}"></td>
+                <td th:text="${machine.bootTime}"></td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="timo-detail-title">网络</div>
+        <table class="layui-table timo-detail-table">
+            <thead>
+            <tr>
+                <th data-field="iface">网卡接口</th>
+                <th data-field="mac">MAC 地址</th>
+                <th data-field="ipv4">IPv4 地址</th>
+                <th data-field="pubicIpv4">IPv4 公网地址</th>
+                <th data-field="ipv6">IPv6 地址</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr th:each="item:${machine.networkInfos}">
+                <td th:text="${item.iface}"></td>
+                <td th:text="${item.mac}"></td>
+                <td th:text="${item.ipv4}"></td>
+                <td th:text="${item.publicIpv4}"></td>
+                <td th:text="${item.ipv6}"></td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 107 - 0
web/src/main/resources/templates/admin/account/index.html

@@ -0,0 +1,107 @@
+<!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-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-left layui-form-pane">
+                    <div class="layui-inline timo-search-box">
+                        <label class="layui-form-label">用户名</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="screenName" th:value="${param.screenName}" placeholder="请输入用户名"
+                                   autocomplete="off" class="layui-input">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <button class="layui-btn timo-search-btn">
+                            <i class="fa fa-search"></i>
+                        </button>
+                    </div>
+                </div>
+                <div class="pull-right">
+                    <div class="btn-group-right">
+                        <div class="btn-group">
+                            <button class="layui-btn">批量处理<span class="caret"></span></button>
+                            <dl class="layui-nav-child layui-anim layui-anim-upbit">
+                                <dd><a class="ajax-status" th:href="@{/api/machine/host/delete}">批量删除</a></dd>
+                            </dl>
+                        </div>
+                    </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 data-field="avatarUrl">头像</th>
+                    <th data-field="userId">用户 ID</th>
+                    <th data-field="screenName">用户名</th>
+                    <!--<th data-field="gender">性别</th>
+                    <th data-field="signature">签名</th>
+                    <th data-field="following">关注</th>
+                    <th data-field="follower">粉丝</th>-->
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td><label class="timo-checkbox"><input type="checkbox" th:value="${item.userId}">
+                        <i class="layui-icon layui-icon-ok"></i></label></td>
+                    <td>
+                        <img class="layui-side-user-avatar" data-size="320,240" th:src="@{${item.avatarUrl}}" alt="头像">
+                    </td>
+                    <td th:text="${item.userId}">
+                    </td>
+                    <td th:text="${item.screenName}"></td>
+                    <!--<td class="machine-status" th:text="${item.status}">当前状态</td>-->
+                    <!--<td th:text="${item.gender}"></td>
+                    <td th:text="${item.signature}"></td>
+                    <td th:text="${item.following}"></td>
+                    <td th:text="${item.follower}"></td>-->
+                    <td>
+                        <a class="open-popup" data-title="帐号详情" th:attr="data-url=@{'/account/detail/'+${item.userId}}"
+                           data-size="960,480" href="#">详情</a>
+                        <a class="ajax-post" th:attr="data-msg='确定要禁用帐号 '+ ${item.userId} + '?'"
+                           th:href="@{'/bili/site/account/status/' + ${item.userId}}">禁用</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 getPageByEnv() {
+        var selectedOption = $("#getPageByEnv option:selected")
+        var param = selectedOption.text()
+        url = '?env=' + param
+        window.location.href = window.location.pathname + url;
+    }
+
+    $(".machine-status").each(function () {
+        var text = $(this).text().trim()
+        if (text === 'Online') {
+            $(this).css("color", "#009688")
+        } else {
+            $(this).css("color", "#ff0000")
+        }
+    })
+</script>
+</body>
+</html>

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

@@ -0,0 +1,91 @@
+<!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="@{/file/store/channel/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.nodeId}">[[${item.domain}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">通道前缀</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <label>
+                                <input class="layui-input" type="text" name="channelPrefix" placeholder="请填写通道前缀" required>
+                            </label>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">文件类型</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="fileType">
+                                <option th:each="item : ${objectTypes}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">可见范围</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="scope">
+                                <option th:each="item : ${objectScopes}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">最大文件</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="maxSize">
+                                <option th:each="item : ${sizeList}" 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>

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

@@ -0,0 +1,48 @@
+<!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="@{/file/store/channel/update/scope}">
+        <input class="layui-input" type="hidden" name="id" th:value="${id}" readonly>
+        <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">
+                            <input class="layui-input" type="text" name="objectScope" th:value="${objectScope}" readonly>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">选择新范围</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="newScope">
+                                <option th:each="item : ${objectScopes}" 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>

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

@@ -0,0 +1,36 @@
+<!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/channel/update/seturl}">
+        <input class="layui-input" type="hidden" name="id" th:value="${id}" readonly>
+        <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="seturl">
+                                <option th:each="item : ${list}" 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>

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

@@ -0,0 +1,75 @@
+<!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=@{/file/store/channel/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">Channel ID</th>
+                    <th class="sortable" data-field="appId">前缀</th>
+                    <th class="sortable" data-field="repoBranch">最大文件</th>
+                    <th class="sortable" data-field="repoBranch">文件类型</th>
+                    <th class="sortable" data-field="repoBranch">可见范围</th>
+                    <th class="sortable" data-field="appName">绑定的域名</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td th:text="${item.channelId}">Channel ID</td>
+                    <td th:text="${item.prefix}">前缀</td>
+                    <td th:text="${item.maxSize}">最大文件</td>
+                    <td th:text="${item.fileType}">文件类型</td>
+                    <td>
+                        <a class="open-popup" data-title="修改 channel 可见范围" th:attr="data-url=@{'/file/store/channel/edit/'+${item.id}}"
+                           data-size="640,480" href="#" th:text="${item.scope}"></a>
+                    </td>
+                    <td th:text="${item.bindDomain}">绑定域名</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>

+ 114 - 0
web/src/main/resources/templates/admin/content/index.html

@@ -0,0 +1,114 @@
+<!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})"></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-left layui-form-pane">
+                    <!--<div class="layui-inline">
+                        <label class="layui-form-label">环境</label>
+                        <div class="layui-input-block timo-search-status">
+                            <select id="getPageByEnv" class="timo-search-select" name="env" onchange="getPageByEnv()"
+                                    mo:dict="ENVIRONMENT" mo-selected="${env}"></select>
+                        </div>
+                    </div>-->
+                    <div class="layui-inline timo-search-box">
+                        <label class="layui-form-label">用户名</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="username" th:value="${param.username}" placeholder="请输入用户名"
+                                   autocomplete="off" class="layui-input">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <button class="layui-btn timo-search-btn">
+                            <i class="fa fa-search"></i>
+                        </button>
+                    </div>
+                </div>
+                <div class="pull-right">
+                    <div class="btn-group-right">
+                        <div class="btn-group">
+                            <button class="layui-btn">批量处理<span class="caret"></span></button>
+                            <dl class="layui-nav-child layui-anim layui-anim-upbit">
+                                <dd><a class="ajax-status" th:href="@{/api/machine/host/delete}">批量删除</a></dd>
+                            </dl>
+                        </div>
+                    </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 data-field="avatarUrl">头像</th>
+                    <th data-field="userId">用户 ID</th>
+                    <th data-field="screenName">用户名</th>
+                    <th data-field="gender">性别</th>
+                    <th data-field="signature">签名</th>
+                    <th data-field="following">关注</th>
+                    <th data-field="follower">粉丝</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td><label class="timo-checkbox"><input type="checkbox" th:value="${item.userId}">
+                        <i class="layui-icon layui-icon-ok"></i></label></td>
+                    <td>
+                        <img class="layui-side-user-avatar" data-size="320,240" th:src="@{${item.avatarUrl}}" alt="头像">
+                    </td>
+                    <td th:text="${item.userId}"></td>
+                    <td th:text="${item.screenName}"></td>
+                    <!--<td class="machine-status" th:text="${item.status}">当前状态</td>-->
+                    <td th:text="${item.gender}"></td>
+                    <td th:text="${item.signature}"></td>
+                    <td th:text="${item.following}"></td>
+                    <td th:text="${item.follower}"></td>
+                    <td>
+                        <a class="open-popup" data-title="机器详细信息" th:attr="data-url=@{'/machine/host/detail/'+${item.userId}}"
+                           data-size="960,480" href="#">详细</a>
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.userId} + ' 机器?'"
+                           th:href="@{'/api/machine/host/' + ${item.userId}}">删除</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 getPageByEnv() {
+        var selectedOption = $("#getPageByEnv option:selected")
+        var param = selectedOption.text()
+        url = '?env=' + param
+        window.location.href = window.location.pathname + url;
+    }
+
+    $(".machine-status").each(function () {
+        var text = $(this).text().trim()
+        if (text === 'Online') {
+            $(this).css("color", "#009688")
+        } else {
+            $(this).css("color", "#ff0000")
+        }
+    })
+</script>
+</body>
+</html>

+ 124 - 0
web/src/main/resources/templates/admin/content/videocategory.html

@@ -0,0 +1,124 @@
+<!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 timo-tree" th:attr="data-url=@{'/api/bili/post/category'}">
+    <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 layui-row">
+        <div class="layui-col-lg2 layui-col-sm3">
+            <div class="layui-card timo-nav-tree">
+                <div class="layui-card-header">视频分类树</div>
+                <div class="layui-card-body">
+                    <ul class="ztree"></ul>
+                </div>
+            </div>
+        </div>
+        <div class="layui-col-lg10 layui-col-sm9">
+            <div class="layui-row timo-card-screen">
+                <div class="pull-left layui-form-pane timo-search-box">
+                    <div class="layui-inline">
+                        <label class="layui-form-label">状态</label>
+                        <div class="layui-input-block timo-search-status">
+                            <select id="getByStatus" class="timo-search-select" name="isEnabled" onchange="getListByStatus()"
+                                    mo:dict="MENU_STATUS" mo-selected="${isEnabled}"></select>
+                        </div>
+                    </div>
+                </div>
+                <div class="pull-right screen-btn-group">
+                    <button class="layui-btn open-popup popup-add" data-title="添加资源"
+                            th:attr="data-url=@{/rbac/menu/add}">
+                        <i class="fa fa-plus"></i>
+                        添加
+                    </button>
+                    <div class="btn-group">
+                        <button class="layui-btn">操作
+                            <span class="caret"></span>
+                        </button>
+                        <dl class="layui-nav-child layui-anim layui-anim-upbit">
+                            <dd>
+                                <a class="ajax-status" th:href="@{/rbac/menu/status/1}">启用</a>
+                            </dd>
+                            <dd>
+                                <a class="ajax-status" th:href="@{/rbac/menu/status/0}">停用</a>
+                            </dd>
+                        </dl>
+                    </div>
+                </div>
+            </div>
+            <div class="timo-table-wrap">
+                <table class="layui-table timo-table timo-tree-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>名称</th>
+                        <th>URL 地址</th>
+                        <th>菜单类型</th>
+                        <th>可访问的角色</th>
+                        <th>操作</th>
+                    </tr>
+                    </thead>
+                    <tbody class="tree-hide">
+                    <tr class="{{$hide}}" tree-pid="{{pid}}" tree-id="{{id}}">
+                        <td>
+                            <label class="timo-checkbox">
+                                <input type="checkbox" value="{{id}}">
+                                <i class="layui-icon layui-icon-ok"></i>
+                            </label>
+                        </td>
+                        <td>{{name}}</td>
+                        <td>{{url}}</td>
+                        <td>{{type}}</td>
+                        <td>
+                            <a class="open-popup" data-title="已授予的角色"
+                               th:attr="data-url=@{'/rbac/menu/roleList/{{id}}'}" data-size="800,600"
+                               href="#">查看</a>
+                        </td>
+                        <td>
+                            <a class="open-popup popup-edit" data-title="编辑资源"
+                               th:attr="data-url=@{'/rbac/menu/edit/{{id}}'}" href="#">编辑
+                            </a>
+                            <a class="open-popup" data-title="详细信息"
+                               th:attr="data-url=@{'/rbac/menu/detail/{{id}}'}"
+                               data-size="800,600" href="#">详细
+                            </a>
+                            <a class="ajax-delete popup-delete" th:attr="data-msg='确定要删除 {{name}}?'"
+                               th:href="@{'/api/rbac/menu/{{id}}'}">删除
+                            </a>
+                        </td>
+                    </tr>
+                    </tbody>
+                </table>
+            </div>
+        </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" th:src="@{/lib/zTree_v3/js/jquery.ztree.core.min.js}"></script>
+<script type="text/javascript" th:src="@{/lib/zTree_v3/js/jquery.ztree.exedit.min.js}"></script>
+<script type="text/javascript" th:src="@{/js/timoTree.js}"></script>
+<script type="text/javascript">
+    $.fn.timoTree();
+    function getListByStatus() {
+        var selectedOption = $("#getByStatus option:selected")
+        var param = selectedOption.text()
+        url = '?isEnabled=' + param
+        window.location.href = window.location.pathname + url;
+    }
+</script>
+</body>
+</html>

+ 166 - 0
web/src/main/resources/templates/admin/content/videolist.html

@@ -0,0 +1,166 @@
+<!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-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-left layui-form-pane">
+                    <!--<div class="layui-inline">
+                        <label class="layui-form-label">环境</label>
+                        <div class="layui-input-block timo-search-status">
+                            <select id="getPageByEnv" class="timo-search-select" name="env" onchange="getPageByEnv()"
+                                    mo:dict="ENVIRONMENT" mo-selected="${env}"></select>
+                        </div>
+                    </div>-->
+                    <div class="layui-inline timo-search-box">
+                        <label class="layui-form-label">标题</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="title" th:value="${param.title}" placeholder="请输入视频标题"
+                                   autocomplete="off" class="layui-input">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <button class="layui-btn timo-search-btn">
+                            <i class="fa fa-search"></i>
+                        </button>
+                    </div>
+                </div>
+                <div class="pull-right">
+                    <div class="btn-group-right">
+                        <div class="btn-group">
+                            <button class="layui-btn">批量处理<span class="caret"></span></button>
+                            <dl class="layui-nav-child layui-anim layui-anim-upbit">
+                                <dd><a class="ajax-status" th:href="@{/api/machine/host/delete}">批量删除</a></dd>
+                            </dl>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="timo-table-wrap">
+            <table class="layui-table timo-table" style="table-layout:fixed">
+                <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 data-field="avatarUrl">发布时间</th>
+                    <th data-field="avatarUrl">封面</th>
+                    <th data-field="videoId">视频 ID</th>
+                    <th data-field="screenName">标题</th>
+                    <!--<th data-field="gender">描述</th>-->
+                    <th data-field="gender">时长</th>
+                    <th data-field="gender">方向</th>
+                    <!--<th data-field="signature">视频资源</th>-->
+                    <th data-field="following">可见范围</th>
+                    <th data-field="follower">审核状态</th>
+                    <th data-field="publishBy">发布用户</th>
+                    <th width="90px">操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td><label class="timo-checkbox"><input type="checkbox" th:value="${item.videoId}">
+                        <i class="layui-icon layui-icon-ok"></i></label>
+                    </td>
+                    <td th:text="${item.pubDate}"></td>
+                    <td>
+                        <img class="layui-side-user-avatar" data-size="320,240" th:src="@{${item.coverUrl}}" alt="video cover">
+                    </td>
+                    <td th:text="${item.videoId}"></td>
+                    <td th:text="${item.title}"></td>
+                    <!--<td th:text="${item.description}"></td>-->
+                    <td th:text="${item.duration}"></td>
+                    <td th:text="${item.direction}"></td>
+                    <td th:text="${item.scope}"></td>
+                    <td>
+                        <a class="open-popup" data-title="修改状态" th:attr="data-url=@{'/bili/post/video/status/'+${item.videoId}}"
+                           href="#" th:text="${item.status}"></a>
+                    </td>
+                    <td th:text="${item.pubDate}"></td>
+                    <td width="90px">
+                        <a class="open-popup" data-title="视频预览" th:attr="data-url=@{'/content/video/preview/'+${item.videoId}}"
+                           data-size="960,480" href="#">预览</a>
+                        <a class="open-popup" data-title="修改状态" th:attr="data-url=@{'/bili/post/video/status/'+${item.videoId}}"
+                           href="#">修改状态</a>
+                        <a class="open-popup" data-title="视频资源" th:attr="data-url=@{'/bili/post/video/resource/'+${item.videoId}}"
+                           data-size="960,480" href="#">视频资源</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">
+    var tip_index;
+    /**
+     * 页面表格内容超出宽度显示...
+     * 鼠标放上去之后回显具体内容
+     * 移开鼠标内容消失
+     */
+    function showAndHideMsg(){
+        $(document).on("mouseenter","td",function(){
+            if (this.offsetWidth < this.scrollWidth) {
+                let that = this;
+                let text = $(this).text();
+                let wid;
+
+                if(text.length>1000){
+                    wid = '900px';
+                }else if(text.length>600){
+                    wid = '700px';
+                }else if(text.length>150){
+                    wid = '500px';
+                }else if(text.length>100){
+                    wid = '250px';
+                }else if(text.length>50){
+                    wid = '150px';
+                }
+                layui.use('layer', function(layer) {
+                    tip_index = layer.tips('<span style="word-wrap: break-word;">'+text+'</span>', that, {
+                        tips: [4, "#000000"],
+                        time: 0,
+                        area: [wid, 'auto']
+                    });
+                });
+            }
+        });
+        $(document).on("mouseout","td",function(){
+            layui.use('layer', function(layer) {
+                layer.close(tip_index);
+            });
+        });
+    }
+    showAndHideMsg();
+
+    function getPageByEnv() {
+        var selectedOption = $("#getPageByEnv option:selected")
+        var param = selectedOption.text()
+        url = '?env=' + param
+        window.location.href = window.location.pathname + url;
+    }
+
+    $(".machine-status").each(function () {
+        var text = $(this).text().trim()
+        if (text === 'Online') {
+            $(this).css("color", "#009688")
+        } else {
+            $(this).css("color", "#ff0000")
+        }
+    })
+</script>
+</body>
+</html>

+ 78 - 0
web/src/main/resources/templates/admin/content/videopreview.html

@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
+    <link rel="stylesheet" th:href="@{/css/video-js.min.css}"/>
+</head>
+
+<body>
+    <div class="timo-detail-page">
+        <video id="video" class="video-js vjs-big-play-centered vjs-fluid">
+            <p class="vjs-no-js">
+                To view this video please enable JavaScript, and consider upgrading to a
+                web browser that
+                <a href="https://videojs.com/html5-video-support/" target="_blank">
+                    supports HTML5 video
+                </a>
+            </p>
+        </video>
+    </div>
+</body>
+
+<script type="text/javascript" th:src="@{/js/video.min.js}"></script>
+<script type="text/javascript" th:inline="javascript">
+    window.addEventListener('DOMContentLoaded', function () {
+        var videoId = '${videoInfo.fileId}'
+        var url = '${videoInfo.url}'
+        var urlType = "video/mp4"
+        initPlayer(videoId, url, urlType)
+    })
+
+    function initPlayer(videoId, url, urlType) {
+        var player = videojs(document.getElementById('video'), {
+            controls: true, // 是否显示控制条
+            poster: '', // 视频封面图地址
+            preload: 'auto',
+            autoplay: false,
+            fluid: true, // 自适应宽高
+            language: 'zh-CN', // 设置语言
+            muted: false, // 是否静音
+            inactivityTimeout: false,
+            controlBar: { // 设置控制条组件
+                /* 设置控制条里面组件的相关属性及显示与否
+                'currentTimeDisplay':true,
+                'timeDivider':true,
+                'durationDisplay':true,
+                'remainingTimeDisplay':false,
+                volumePanel: {
+                    inline: false,
+                }
+                */
+                /* 使用children的形式可以控制每一个控件的位置,以及显示与否 */
+                children: [
+                    {name: 'playToggle'}, // 播放按钮
+                    {name: 'currentTimeDisplay'}, // 当前已播放时间
+                    {name: 'progressControl'}, // 播放进度条
+                    {name: 'durationDisplay'}, // 总时间
+                    { // 倍数播放
+                        name: 'playbackRateMenuButton',
+                        'playbackRates': [0.5, 1, 1.5, 2, 2.5]
+                    },
+                    {
+                        name: 'volumePanel', // 音量控制
+                        inline: false, // 不使用水平方式
+                    },
+                    {name: 'FullscreenToggle'} // 全屏
+                ]
+            },
+            sources:[ // 视频源
+                {
+                    src: url,
+                    type: urlType,
+                    poster: ''
+                }
+            ]
+        }, function (){
+        });
+    }
+</script>
+</html>

+ 10 - 0
web/src/main/resources/templates/admin/content/videoresource.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>
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 10 - 0
web/src/main/resources/templates/admin/content/videostatus.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>
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 2 - 2
web/src/test/java/cn/reghao/bnt/web/account/AccountTest.java

@@ -1,6 +1,6 @@
 package cn.reghao.bnt.web.account;
 
-import cn.reghao.bnt.web.SpringApplication;
+import cn.reghao.bnt.web.WebApplication;
 import cn.reghao.bnt.web.account.service.AccountService;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;
@@ -16,7 +16,7 @@ import org.springframework.test.context.junit4.SpringRunner;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = SpringApplication.class)
+@SpringBootTest(classes = WebApplication.class)
 @RunWith(SpringRunner.class)
 public class AccountTest {
     @Autowired

+ 2 - 2
web/src/test/java/cn/reghao/bnt/web/devops/AppConfigTest.java

@@ -1,6 +1,6 @@
 package cn.reghao.bnt.web.devops;
 
-import cn.reghao.bnt.web.SpringApplication;
+import cn.reghao.bnt.web.WebApplication;
 import cn.reghao.bnt.web.devops.app.db.repository.AppBuildingRepository;
 import cn.reghao.bnt.web.devops.app.db.repository.AppDeployingRepository;
 import cn.reghao.bnt.web.devops.app.db.repository.config.AppConfigRepository;
@@ -23,7 +23,7 @@ import org.springframework.test.context.junit4.SpringRunner;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = SpringApplication.class)
+@SpringBootTest(classes = WebApplication.class)
 @RunWith(SpringRunner.class)
 public class AppConfigTest {
     @Autowired

+ 2 - 2
web/src/test/java/cn/reghao/bnt/web/parser/BiliSpiderTest.java

@@ -1,6 +1,6 @@
 package cn.reghao.bnt.web.parser;
 
-import cn.reghao.bnt.web.SpringApplication;
+import cn.reghao.bnt.web.WebApplication;
 import cn.reghao.jutil.jdk.http.WebRequest;
 import cn.reghao.jutil.jdk.http.WebResponse;
 import cn.reghao.jutil.jdk.serializer.JsonConverter;
@@ -45,7 +45,7 @@ import java.util.*;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = SpringApplication.class)
+@SpringBootTest(classes = WebApplication.class)
 @RunWith(SpringRunner.class)
 public class BiliSpiderTest {
     @Autowired

+ 2 - 2
web/src/test/java/cn/reghao/bnt/web/parser/CommonDataTest.java

@@ -1,7 +1,7 @@
 package cn.reghao.bnt.web.parser;
 
 import cn.reghao.bnt.spider.url.Site;
-import cn.reghao.bnt.web.SpringApplication;
+import cn.reghao.bnt.web.WebApplication;
 import cn.reghao.bnt.web.parser.db.mongo.UnparsedDataMongo;
 import cn.reghao.bnt.web.parser.model.po.UnparsedData;
 import cn.reghao.bnt.web.parser.site.bilibili.BiliVideoDataParser;
@@ -24,7 +24,7 @@ import java.util.*;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = SpringApplication.class)
+@SpringBootTest(classes = WebApplication.class)
 @RunWith(SpringRunner.class)
 public class CommonDataTest {
     @Autowired

+ 2 - 2
web/src/test/java/cn/reghao/bnt/web/parser/LocalDataTest.java

@@ -1,6 +1,6 @@
 package cn.reghao.bnt.web.parser;
 
-import cn.reghao.bnt.web.SpringApplication;
+import cn.reghao.bnt.web.WebApplication;
 import cn.reghao.jutil.jdk.text.TextFile;
 import cn.reghao.jutil.media.FFmpegWrapper;
 import cn.reghao.bnt.web.config.AppProperties;
@@ -24,7 +24,7 @@ import java.util.List;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = SpringApplication.class)
+@SpringBootTest(classes = WebApplication.class)
 @RunWith(SpringRunner.class)
 public class LocalDataTest {
     @Autowired

+ 2 - 2
web/src/test/java/cn/reghao/bnt/web/parser/TmallSpiderTest.java

@@ -5,7 +5,7 @@ import cn.reghao.bnt.browser.chrome.ChromeBrowser;
 import cn.reghao.bnt.browser.chrome.ReqMatcher;
 import cn.reghao.bnt.spider.url.BodyDataType;
 import cn.reghao.bnt.spider.url.Site;
-import cn.reghao.bnt.web.SpringApplication;
+import cn.reghao.bnt.web.WebApplication;
 import cn.reghao.bnt.web.parser.site.taobao.*;
 import cn.reghao.bnt.web.parser.util.media.PhotoExif;
 import com.drew.imaging.ImageProcessingException;
@@ -28,7 +28,7 @@ import java.util.*;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = SpringApplication.class)
+@SpringBootTest(classes = WebApplication.class)
 @RunWith(SpringRunner.class)
 public class TmallSpiderTest {
     @Autowired

+ 2 - 2
web/src/test/java/cn/reghao/bnt/web/parser/ZhihuSpiderTest.java

@@ -5,7 +5,7 @@ import cn.reghao.bnt.browser.chrome.ChromeBrowser;
 import cn.reghao.bnt.browser.chrome.ReqMatcher;
 import cn.reghao.bnt.spider.url.BodyDataType;
 import cn.reghao.bnt.spider.url.Site;
-import cn.reghao.bnt.web.SpringApplication;
+import cn.reghao.bnt.web.WebApplication;
 import cn.reghao.bnt.web.parser.db.mongo.UnparsedDataMongo;
 import cn.reghao.bnt.web.parser.db.mongo.UrlResourceMongo;
 import cn.reghao.bnt.web.parser.model.po.UnparsedData;
@@ -31,7 +31,7 @@ import java.util.*;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = SpringApplication.class)
+@SpringBootTest(classes = WebApplication.class)
 @RunWith(SpringRunner.class)
 public class ZhihuSpiderTest {
     @Autowired