Browse Source

first commit

bianlz 3 tuần trước cách đây
commit
e4f0f01912
100 tập tin đã thay đổi với 5327 bổ sung0 xóa
  1. 33 0
      README.md
  2. 62 0
      op-admin-server-adapter/pom.xml
  3. 59 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/GlobalExceptionHandler.java
  4. 27 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/HttpUtils.java
  5. 242 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/IpUtils.java
  6. 101 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/WebConfig.java
  7. 27 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/AuthenticationContextHolder.java
  8. 255 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/CurrentLoginUser.java
  9. 110 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/HttpSecurityConfig.java
  10. 191 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/HttpSecurityHelper.java
  11. 37 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/JwtAuthenticationTokenFilter.java
  12. 35 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/LogoutSuccessHandlerImpl.java
  13. 204 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/TotpUtils.java
  14. 100 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/UserDetailHelper.java
  15. 25 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/common/MerchantController.java
  16. 103 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/egress/EgressApiController.java
  17. 60 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/egress/EgressApiEndpointController.java
  18. 1 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/package-info.java
  19. 125 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/system/AuthController.java
  20. 30 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/system/SysUserController.java
  21. 1 0
      op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/package-info.java
  22. 29 0
      op-admin-server-app/pom.xml
  23. 43 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/EgressApiEndpointServiceImpl.java
  24. 85 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/EgressApiServiceImpl.java
  25. 36 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiDelExe.java
  26. 44 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiEndpointCrtExe.java
  27. 34 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiEndpointQryExe.java
  28. 87 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiLoanCrtExe.java
  29. 40 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiLoanQryExe.java
  30. 38 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiQryExe.java
  31. 93 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiUpsertExe.java
  32. 1 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/package-info.java
  33. 39 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/AuthServiceImpl.java
  34. 47 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/SysMenuServiceImpl.java
  35. 47 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/SysPermissionSerivce.java
  36. 40 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/SysUserServiceImpl.java
  37. 82 0
      op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/executor/CaptchaCreateCmdExe.java
  38. 24 0
      op-admin-server-client/pom.xml
  39. 26 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/AuthService.java
  40. 36 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/EgressApiEndpointService.java
  41. 66 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/EgressApiService.java
  42. 24 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/SysMenuService.java
  43. 16 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/SysPermissionSerivce.java
  44. 20 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/SysUserService.java
  45. 49 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/CaptchaDTO.java
  46. 52 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/CurrentLoginUserDTO.java
  47. 61 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/EgressApiDTO.java
  48. 54 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/EgressApiEndpointDTO.java
  49. 24 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/EgressApiLoanDTO.java
  50. 22 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/FunctionDTO.java
  51. 14 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/FunctionObjectDTO.java
  52. 38 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/HttpConfigDTO.java
  53. 24 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/HttpConfigValueDTO.java
  54. 28 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/MerchantDTO.java
  55. 36 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/Operator.java
  56. 42 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/SysDeptDTO.java
  57. 33 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/SysRoleDTO.java
  58. 29 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/SysUserDTO.java
  59. 30 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/ValueObjectDTO.java
  60. 21 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiEndpointQry.java
  61. 41 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiEndpointUpsertCmd.java
  62. 35 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiLoanCrtCmd.java
  63. 13 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiLoanQry.java
  64. 26 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiQry.java
  65. 41 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiUpsertCmd.java
  66. 18 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/SysUserQry.java
  67. 30 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/UserLoginCmd.java
  68. 17 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/UserOtpLoginCmd.java
  69. 1 0
      op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/package-info.java
  70. 36 0
      op-admin-server-domain/pom.xml
  71. 53 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/common/AuthErrorCodeEnum.java
  72. 49 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/common/EgressApiErrorCodeEnum.java
  73. 30 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/common/ErrorCodeEnum.java
  74. 32 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/egress/EgressApiEndpointIntegrationModeEnum.java
  75. 34 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/egress/EgressApiStatusEnum.java
  76. 1 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/package-info.java
  77. 33 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/system/SysUserStatusEnum.java
  78. 1 0
      op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/system/package-info.java
  79. 89 0
      op-admin-server-infrastructure/pom.xml
  80. 51 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/client/EgressServiceClient.java
  81. 134 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/Base32.java
  82. 291 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/Base64.java
  83. 31 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/CaptchaTypeEnum.java
  84. 15 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/Constants.java
  85. 61 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/HexEncoding.java
  86. 65 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/QrUtils.java
  87. 48 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/FastJson2JsonRedisSerializer.java
  88. 56 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/KaptchaTextCreator.java
  89. 34 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/RedisConfig.java
  90. 56 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/SystemConfig.java
  91. 1 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/package-info.java
  92. 23 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/dao/EgressApiEndpointDao.java
  93. 28 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/dao/SysMenuDao.java
  94. 22 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/dao/SysUserDao.java
  95. 63 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/BaseDO.java
  96. 75 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiDO.java
  97. 109 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiEndpointDO.java
  98. 83 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiEndpointHistoryDO.java
  99. 75 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiLoanDO.java
  100. 44 0
      op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/SysDeptDO.java

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+
+# pangu-archetype-sc
+
+maven archetype工程。
+
+## 构建&&安装archetype到本地maven
+
+mvn archetype:create-from-project
+
+cd target/generated-sources/archetype
+
+mvn install
+
+## 使用本地archetype创建工程
+
+mvn archetype:generate -DarchetypeCatalog=local
+
+## 使用本地模式进行开发
+
+在启动环境中添加spring.profiles.active=local
+
+# 如何删除本地archetype
+
+清理 ~/.m2/repository/archetype-catalog.xml
+
+# GIT
+
+1.fix pangu-archetype-web-domain  修改bug
+
+# base_table
+
+
+

+ 62 - 0
op-admin-server-adapter/pom.xml

@@ -0,0 +1,62 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.hrsk.cloud</groupId>
+        <artifactId>op-admin-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>op-admin-server-adapter</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.hrsk.cloud</groupId>
+            <artifactId>op-admin-server-app</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+
+
+        <!-- spring security 安全认证 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- 解析客户端操作系统、浏览器等 -->
+        <dependency>
+            <groupId>eu.bitwalker</groupId>
+            <artifactId>UserAgentUtils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 59 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/GlobalExceptionHandler.java

@@ -0,0 +1,59 @@
+package com.hrsk.cloud.op.adapter.common;
+
+import com.hrsk.cloud.op.domain.common.ErrorCodeEnum;
+import com.hrsk.pangu.dto.Response;
+import com.hrsk.pangu.tool.exception.BizException;
+import com.hrsk.pangu.tool.exception.SysException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-22 09:37
+ * @description: 全局异常处理
+ **/
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 业务异常
+     * @param e 异常
+     * @param request 请求
+     * @return 响应
+     */
+    @ExceptionHandler(BizException.class)
+    public Response handleBizException(BizException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生业务异常.", requestURI, e);
+        return Response.buildFailure(e.getErrCode(), e.getMessage());
+    }
+
+    /**
+     * 系统异常
+     * @param e 异常
+     * @param request 请求
+     * @return 响应
+     */
+    @ExceptionHandler(SysException.class)
+    public Response handleSysException(SysException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生系统异常.", requestURI, e);
+        return Response.buildFailure(e.getErrCode(), e.getMessage());
+    }
+    /**
+     * 系统异常
+     * @param e 异常
+     * @param request 请求
+     * @return 响应
+     */
+    @ExceptionHandler(Exception.class)
+    public Response handleException(Exception e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生系统未定义异常.", requestURI, e);
+        return Response.buildFailure(ErrorCodeEnum.FAIL.getCode(), ErrorCodeEnum.FAIL.getMessage());
+    }
+}

+ 27 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/HttpUtils.java

@@ -0,0 +1,27 @@
+package com.hrsk.cloud.op.adapter.common;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 15:31
+ * @description: http工具类
+ **/
+public class HttpUtils {
+    /**
+     * 将Object写入reponse
+     * @param response
+     * @param string
+     */
+    public static final void renderString(HttpServletResponse response, String string){
+        try {
+            response.setStatus(200);
+            response.setContentType("application/json");
+            response.setCharacterEncoding("utf-8");
+            response.getWriter().print(string);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 242 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/IpUtils.java

@@ -0,0 +1,242 @@
+package com.hrsk.cloud.op.adapter.common;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.apache.commons.lang.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-15 10:52
+ * @description: IP工具类 COPY FROM RUOYI
+ **/
+@Slf4j
+public class IpUtils {
+    /**
+     * client
+     */
+    private static final OkHttpClient CLIENT = new OkHttpClient();
+    // IP地址查询
+    private static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
+    // 未知地址
+    private static final String UNKNOWN = "XX XX";
+
+    public static String getRealAddressByIP(String ip) {
+        // 内网不查询
+        if (IpUtils.internalIp(ip)) {
+            return "内网IP";
+        }
+        try {
+            Request request = new Request.Builder()
+                    .url(IP_URL + "ip=" + ip + "&json=true")
+                    .build();
+            try (Response response = CLIENT.newCall(request).execute()) {
+                if(response.body() == null ){
+                    return UNKNOWN;
+                }
+                String respStr = response.body().string();
+                JSONObject obj = JSON.parseObject(respStr);
+                String region = obj.getString("pro");
+                String city = obj.getString("city");
+                return String.format("%s %s", region, city);
+            }
+        }catch (Exception e){
+                log.error("获取地理位置异常 {}", ip);
+        }
+        return UNKNOWN;
+    }
+
+
+    /**
+     * 检查是否为内部IP地址
+     *
+     * @param ip IP地址
+     * @return 结果
+     */
+    public static boolean internalIp(String ip) {
+        byte[] addr = textToNumericFormatV4(ip);
+        return internalIp(addr) || "127.0.0.1".equals(ip);
+    }
+
+    /**
+     * 检查是否为内部IP地址
+     *
+     * @param addr byte地址
+     * @return 结果
+     */
+    private static boolean internalIp(byte[] addr) {
+        if (addr == null || addr.length < 2) {
+            return true;
+        }
+        final byte b0 = addr[0];
+        final byte b1 = addr[1];
+        // 10.x.x.x/8
+        final byte SECTION_1 = 0x0A;
+        // 172.16.x.x/12
+        final byte SECTION_2 = (byte) 0xAC;
+        final byte SECTION_3 = (byte) 0x10;
+        final byte SECTION_4 = (byte) 0x1F;
+        // 192.168.x.x/16
+        final byte SECTION_5 = (byte) 0xC0;
+        final byte SECTION_6 = (byte) 0xA8;
+        switch (b0) {
+            case SECTION_1:
+                return true;
+            case SECTION_2:
+                if (b1 >= SECTION_3 && b1 <= SECTION_4) {
+                    return true;
+                }
+            case SECTION_5:
+                switch (b1) {
+                    case SECTION_6:
+                        return true;
+                }
+            default:
+                return false;
+        }
+    }
+
+
+    /**
+     * 将IPv4地址转换成字节
+     *
+     * @param text IPv4地址
+     * @return byte 字节
+     */
+    public static byte[] textToNumericFormatV4(String text){
+        if (text.length() == 0) {
+            return null;
+        }
+
+        byte[] bytes = new byte[4];
+        String[] elements = text.split("\\.", -1);
+        try {
+            long l;
+            int i;
+            switch (elements.length) {
+                case 1:
+                    l = Long.parseLong(elements[0]);
+                    if ((l < 0L) || (l > 4294967295L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);
+                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 2:
+                    l = Integer.parseInt(elements[0]);
+                    if ((l < 0L) || (l > 255L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l & 0xFF);
+                    l = Integer.parseInt(elements[1]);
+                    if ((l < 0L) || (l > 16777215L)) {
+                        return null;
+                    }
+                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 3:
+                    for (i = 0; i < 2; ++i) {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    l = Integer.parseInt(elements[2]);
+                    if ((l < 0L) || (l > 65535L)) {
+                        return null;
+                    }
+                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 4:
+                    for (i = 0; i < 4; ++i) {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    break;
+                default:
+                    return null;
+            }
+        }
+        catch (NumberFormatException e) {
+            return null;
+        }
+        return bytes;
+    }
+
+
+    /**
+     * 获取客户端IP
+     *
+     * @param request 请求对象
+     * @return IP地址
+     */
+    public static String getIpAddr(HttpServletRequest request) {
+        if (request == null) {
+            return "unknown";
+        }
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
+    }
+
+    /**
+     * 从多级反向代理中获得第一个非unknown IP地址
+     *
+     * @param ip 获得的IP地址
+     * @return 第一个非unknown IP地址
+     */
+    public static String getMultistageReverseProxyIp(String ip) {
+        // 多级反向代理检测
+        if (ip != null && ip.indexOf(",") > 0) {
+            final String[] ips = ip.trim().split(",");
+            for (String subIp : ips) {
+                if (false == isUnknown(subIp))
+                {
+                    ip = subIp;
+                    break;
+                }
+            }
+        }
+        return StringUtils.substring(ip, 0, 255);
+    }
+
+    /**
+     * 检测给定字符串是否为未知,多用于检测HTTP请求相关
+     *
+     * @param checkString 被检测的字符串
+     * @return 是否未知
+     */
+    public static boolean isUnknown(String checkString) {
+        return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
+    }
+}

+ 101 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/WebConfig.java

@@ -0,0 +1,101 @@
+package com.hrsk.cloud.op.adapter.common;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.resource.PathResourceResolver;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 17:45
+ * @description: web配置
+ **/
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * 解决LONG过长前端精度缺失,将LONG统一转换成STRING
+     * @param builder builder
+     * @return ObjectMapper
+     */
+    @Bean
+    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
+        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(Long.class, ToStringSerializer.instance);
+        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
+        module.addSerializer(Date.class, new JsonSerializer<Date>() {
+            @Override
+            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+                SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
+                String formattedDate = formatter.format(date);
+                jsonGenerator.writeString(formattedDate);
+            }
+        });
+
+        objectMapper.registerModule(module);
+        return objectMapper;
+    }
+
+    /**
+     * 静态路由配置,新页面要以/ui/**规则开头
+     * @param registry
+     */
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/ui/**")
+                .addResourceLocations("classpath:/static/")
+                .resourceChain(true)
+                .addResolver(new PathResourceResolver() {
+                    @Override
+                    protected Resource getResource(String resourcePath, Resource location) throws IOException {
+                        Resource requestedResource = location.createRelative(resourcePath);
+                        return requestedResource.exists() && requestedResource.isReadable() ? requestedResource : new ClassPathResource("/static/index.html");
+                    }
+                });
+    }
+
+    /**
+     * 跨域配置
+     * @return
+     */
+    @Bean
+    public CorsFilter corsFilter() {
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        // 设置访问源地址
+        config.addAllowedOriginPattern("*");
+        // 设置访问源请求头
+        config.addAllowedHeader("*");
+        // 设置访问源请求方法
+        config.addAllowedMethod("*");
+        // 有效期 1800秒
+        config.setMaxAge(1800L);
+        // 添加映射路径,拦截一切请求
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", config);
+        // 返回新的CorsFilter
+        return new CorsFilter(source);
+    }
+}

+ 27 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/AuthenticationContextHolder.java

@@ -0,0 +1,27 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import org.springframework.security.core.Authentication;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 17:48
+ * @description: 身份验证信息
+ **/
+public class AuthenticationContextHolder {
+    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
+
+    public static Authentication getContext()
+    {
+        return contextHolder.get();
+    }
+
+    public static void setContext(Authentication context)
+    {
+        contextHolder.set(context);
+    }
+
+    public static void clearContext()
+    {
+        contextHolder.remove();
+    }
+}

+ 255 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/CurrentLoginUser.java

@@ -0,0 +1,255 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import com.hrsk.cloud.op.client.dto.CurrentLoginUserDTO;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.SysUserDO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 15:18
+ * @description: 当前登录用户
+ **/
+public class CurrentLoginUser implements UserDetails {
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 权限列表
+     */
+    private Set<String> permissions;
+
+    /**
+     * token
+     */
+    private String token;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * otp密钥
+     */
+    private String otpSecret;
+    /**
+     * 是否完成双因子验证
+     */
+    private boolean finishTfa = false;
+
+    /**
+     * 系统用户信息
+     */
+    private SysUserDO user;
+
+    /**
+     * 构造函数
+     * @param user 系统用户
+     * @param permissions 权限
+     */
+    public CurrentLoginUser(SysUserDO user,Set<String> permissions) {
+        this.userId = user.getId();
+        this.deptId = user.getDeptId();
+        this.otpSecret = user.getOtpSecret();
+        this.permissions = permissions;
+        this.user = user;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return null;
+    }
+
+    @Override
+    public String getPassword() {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getUsername();
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Long getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Long expireTime) {
+        this.expireTime = expireTime;
+    }
+
+    public Long getLoginTime() {
+        return loginTime;
+    }
+
+    public void setLoginTime(Long loginTime) {
+        this.loginTime = loginTime;
+    }
+
+    public String getIpaddr() {
+        return ipaddr;
+    }
+
+    public void setIpaddr(String ipaddr) {
+        this.ipaddr = ipaddr;
+    }
+
+    public String getLoginLocation() {
+        return loginLocation;
+    }
+
+    public void setLoginLocation(String loginLocation) {
+        this.loginLocation = loginLocation;
+    }
+
+    public String getBrowser() {
+        return browser;
+    }
+
+    public void setBrowser(String browser) {
+        this.browser = browser;
+    }
+
+    public String getOs() {
+        return os;
+    }
+
+    public void setOs(String os) {
+        this.os = os;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public Long getDeptId() {
+        return deptId;
+    }
+
+    public void setDeptId(Long deptId) {
+        this.deptId = deptId;
+    }
+
+    public Set<String> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions) {
+        this.permissions = permissions;
+    }
+
+    public SysUserDO getUser() {
+        return user;
+    }
+
+    public void setUser(SysUserDO user) {
+        this.user = user;
+    }
+
+    public boolean isFinishTfa() {
+        return finishTfa;
+    }
+
+    public void setFinishTfa(boolean finishTfa) {
+        this.finishTfa = finishTfa;
+    }
+
+    public String getOtpSecret() {
+        return otpSecret;
+    }
+
+    public void setOtpSecret(String otpSecret) {
+        this.otpSecret = otpSecret;
+    }
+
+    /**
+     * 转换DTO
+     * @return DTO
+     */
+    public CurrentLoginUserDTO to(){
+        CurrentLoginUserDTO dto = new CurrentLoginUserDTO();
+        BeanUtils.copyProperties(this.user,dto);
+        dto.setPermissions(permissions);
+        dto.setLoginIp(ipaddr);
+        return dto;
+    }
+
+    /**
+     * 转换操作者
+     * @return 操作者
+     */
+    public Operator toOperator(){
+        return new Operator(getUserId(),getUsername());
+    }
+}

+ 110 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/HttpSecurityConfig.java

@@ -0,0 +1,110 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.web.filter.CorsFilter;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 11:27
+ * @description: spring security配置 copy from ruoyi
+ **/
+@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
+@Configuration
+public class HttpSecurityConfig {
+
+    /**
+     * 自定义用户认证逻辑
+     */
+    @Autowired
+    private UserDetailsService userDetailsService;
+
+    /**
+     * 退出处理类
+     */
+    @Autowired
+    private LogoutSuccessHandlerImpl logoutSuccessHandler;
+
+    /**
+     * token认证过滤器
+     */
+    @Autowired
+    private JwtAuthenticationTokenFilter authenticationTokenFilter;
+    
+    /**
+     * 跨域过滤器
+     */
+    @Autowired
+    private CorsFilter corsFilter;
+
+    /**
+     * 身份验证实现
+     */
+    @Bean
+    public AuthenticationManager authenticationManager() {
+        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
+        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
+        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
+        return new ProviderManager(daoAuthenticationProvider);
+    }
+
+    /**
+     * SecurityFilterChain
+     * @param httpSecurity
+     * @return
+     * @throws Exception
+     */
+    @Bean
+    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
+        return httpSecurity
+            // CSRF禁用,因为不使用session
+            .csrf(csrf -> csrf.disable())
+            // 禁用HTTP响应标头
+            .headers((headersCustomizer) -> {
+                headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
+            })
+            // 基于token,所以不需要session
+            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+            // 注解标记允许匿名访问的url
+            .authorizeHttpRequests((requests) -> {
+                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
+                requests.antMatchers("/auth/login","/auth/otp-login", "/register", "/auth/captcha/image").permitAll()
+                    // 静态资源,可匿名访问
+                    .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js","/icons/**","/ui/**").permitAll()
+                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+                    // 除上面外的所有请求全部需要鉴权认证
+                    .anyRequest().authenticated();
+            })
+            // 添加Logout filter
+            .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
+            // 添加JWT filter
+            .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
+            // 添加CORS filter
+            .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
+            .addFilterBefore(corsFilter, LogoutFilter.class)
+            .build();
+    }
+
+    /**
+     * 强散列哈希加密实现
+     */
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder()
+    {
+        return new BCryptPasswordEncoder();
+    }
+
+}

+ 191 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/HttpSecurityHelper.java

@@ -0,0 +1,191 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import com.hrsk.cloud.op.adapter.common.IpUtils;
+import com.hrsk.cloud.op.infrastructure.common.Constants;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisCache;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisKeyEnum;
+import eu.bitwalker.useragentutils.UserAgent;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 14:50
+ * @description: http security 工具
+ **/
+@Component
+public class HttpSecurityHelper {
+
+    /**
+     * JWT令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * HEADER令牌前缀
+     */
+    public static final String HEADER_TOKEN_PREFIX = "Bearer ";
+
+
+    @Value("${security.token.secret}")
+    private String secret;
+
+    @Value("${security.token.header}")
+    private String header;
+
+    @Value("${security.token.expireTime}")
+    private int expireTime;
+
+    @Resource
+    private RedisCache redisCache;
+
+    /**
+     * 通过token获取当前登录用户
+     * @param token token令牌
+     * @return 当前登录用户
+     */
+    public CurrentLoginUser getCurrentLoginUser(String token){
+        if(StringUtils.isEmpty(token)){
+            return null;
+        }
+        Claims claims = Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody();
+        //解析对应的权限以及用户信息
+        String uuid = (String) claims.get(LOGIN_USER_KEY);
+        String userKey = RedisKeyEnum.LOGIN_TOKEN_KEY.getKey(uuid);
+        return redisCache.getCacheObject(userKey);
+    }
+
+    /**
+     * 通过request获取当前登录用户
+     * @param request httprequest
+     * @return 当前登录用户
+     */
+    public CurrentLoginUser getCurrentLoginUser(HttpServletRequest request){
+        String token = getToken(request);
+        return getCurrentLoginUser(token);
+    }
+
+    /**
+     * 获取TOKEN
+     * @param request request
+     * @return token
+     */
+    public String getToken(HttpServletRequest request){
+        String token = request.getHeader(header);
+        if (StringUtils.isNotEmpty(token) && token.startsWith(HEADER_TOKEN_PREFIX)) {
+            token = token.replace(HEADER_TOKEN_PREFIX, "");
+        }
+        return token;
+    }
+
+    /**
+     * 删除登录缓存
+     * @param token token令牌
+     */
+    public void delLoginCache(String token){
+        redisCache.deleteObject(RedisKeyEnum.LOGIN_TOKEN_KEY.getKey(token));
+    }
+
+
+    /**
+     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
+     * @param loginUser 登录用户
+     */
+    public void verifyToken(CurrentLoginUser loginUser) {
+        long expireTime = loginUser.getExpireTime();
+        long currentTime = System.currentTimeMillis();
+        if (expireTime - currentTime <= Constants.UNIT_MILLIS_MINUTE_TEN) {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 刷新令牌有效期
+     * @param loginUser 登录用户
+     */
+    public void refreshToken(CurrentLoginUser loginUser) {
+        loginUser.setLoginTime(System.currentTimeMillis());
+        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * Constants.UNIT_MILLIS_SECOND);
+        // 根据uuid将loginUser缓存
+        String userKey = RedisKeyEnum.LOGIN_TOKEN_KEY.getKey(loginUser.getToken());
+        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
+    }
+
+
+    /**
+     * 创建令牌
+     *
+     * @param loginUser 用户信息
+     * @return 令牌
+     */
+    public String createToken(CurrentLoginUser loginUser) {
+        String token = UUID.randomUUID().toString();
+        loginUser.setToken(token);
+        setUserAgent(loginUser);
+        refreshToken(loginUser);
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(LOGIN_USER_KEY, token);
+        return createToken(claims);
+    }
+
+    /**
+     * 设置用户代理信息
+     *
+     * @param loginUser 登录信息
+     */
+    public void setUserAgent(CurrentLoginUser loginUser) {
+        UserAgent userAgent = UserAgent.parseUserAgentString(getRequest().getHeader("User-Agent"));
+        String ip = IpUtils.getIpAddr(getRequest());
+        loginUser.setIpaddr(ip);
+        loginUser.setLoginLocation(IpUtils.getRealAddressByIP(ip));
+        loginUser.setBrowser(userAgent.getBrowser().getName());
+        loginUser.setOs(userAgent.getOperatingSystem().getName());
+    }
+
+
+    /**
+     * 获取 ServletRequestAttributes
+     * @return
+     */
+    public ServletRequestAttributes getRequestAttributes() {
+        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+        return (ServletRequestAttributes) attributes;
+    }
+
+    /**
+     * 获取request
+     */
+    public HttpServletRequest getRequest() {
+        return getRequestAttributes().getRequest();
+    }
+
+    /**
+     * 从数据声明生成令牌
+     *
+     * @param claims 数据声明
+     * @return 令牌
+     */
+    private String createToken(Map<String, Object> claims) {
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, secret).compact();
+        return token;
+    }
+}

+ 37 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/JwtAuthenticationTokenFilter.java

@@ -0,0 +1,37 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.annotation.Resource;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 16:40
+ * @description: JWT验证过滤器
+ **/
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+    @Resource
+    private HttpSecurityHelper httpSecurityHelper;
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        CurrentLoginUser currentLoginUser = httpSecurityHelper.getCurrentLoginUser(request);
+        if (currentLoginUser != null && SecurityContextHolder.getContext().getAuthentication() == null && currentLoginUser.isFinishTfa()) {
+            httpSecurityHelper.verifyToken(currentLoginUser);
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(currentLoginUser, null, currentLoginUser.getAuthorities());
+            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        }
+        filterChain.doFilter(request, response);
+    }
+
+}

+ 35 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/LogoutSuccessHandlerImpl.java

@@ -0,0 +1,35 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import com.alibaba.fastjson2.JSON;
+import com.hrsk.cloud.op.adapter.common.HttpUtils;
+import com.hrsk.pangu.dto.Response;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 09:10
+ * @description: 登出成功Hander实现类
+ **/
+@Component
+public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
+    @Resource
+    private HttpSecurityHelper httpSecurityHelper;
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+        String token = httpSecurityHelper.getToken(request);
+        CurrentLoginUser currentLoginUser = httpSecurityHelper.getCurrentLoginUser(token);
+        if (currentLoginUser != null) {
+            String userName = currentLoginUser.getUsername();
+            httpSecurityHelper.delLoginCache(currentLoginUser.getToken());
+        }
+        HttpUtils.renderString(response, JSON.toJSONString(Response.buildFailure("","")));
+    }
+}

+ 204 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/TotpUtils.java

@@ -0,0 +1,204 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import com.google.zxing.WriterException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base32;
+import org.apache.commons.lang3.RandomStringUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-18 11:17
+ * @description:
+ **/
+@Slf4j
+public class TotpUtils {
+    /** 时间步长,动态口令变化时间周期(单位秒) */
+    private static final int TIME_STEP = 30;
+    /** 动态口令默认长度 */
+    private static final int CODE_DIGITS = 6;
+
+    /**
+     * 生成唯一密钥
+     *
+     * @return
+     */
+    public static String generateSecretKey() {
+        // UUID + 4位随机字符生成唯一标识
+        String uniqueId = UUID.randomUUID() + RandomStringUtils.randomAlphabetic(4);
+        return new String(new Base32().encode(uniqueId.getBytes()));
+    }
+
+    /**
+     * 生成一个基于TOTP标准身份验证器识别的字符串
+     * 将该字符串生成二维码可供通用动态密码工具识别,例如:iOS应用(Authy)、微信小程序(二次验证码)
+     *
+     * @param user
+     * @param secret
+     * @return   &issuer={2}
+     */
+    public static String getQRCodeStr(String user, String secret) {
+        String format = "otpauth://totp/%s?secret=%s";
+        return String.format(format, user, secret);
+    }
+
+    /**
+     * 生成动态口令
+     *
+     * @param secret
+     * @return
+     */
+    public static String generateTOTP(String secret) {
+        return TotpUtils.generateTOTP(secret, TotpUtils.getCurrentInterval(), CODE_DIGITS);
+    }
+
+    /**
+     * 生成指定位数的动态口令
+     *
+     * @param secret
+     * @param codeDigits
+     * @return
+     */
+    public static String generateTOTP(String secret, int codeDigits) {
+        return TotpUtils.generateTOTP(secret, TotpUtils.getCurrentInterval(), codeDigits);
+    }
+
+    /**
+     * 验证动态口令
+     *
+     * @param secret
+     * @param code
+     * @return
+     */
+    public static boolean verify(String secret, String code) {
+        return TotpUtils.verify(secret, code, CODE_DIGITS);
+    }
+
+    /**
+     * 验证动态口令
+     *
+     * @param secret
+     * @param code
+     * @param codeDigits
+     * @return
+     */
+    public static boolean verify(String secret, String code, int codeDigits) {
+        long currentInterval = TotpUtils.getCurrentInterval();
+        // 考虑到时间延时,需考虑前一个步长的动态密码是否匹配
+        for (int i = 0; i <= 1; i++) {
+            String tmpCode = TotpUtils.generateTOTP(secret, currentInterval - i, codeDigits);
+            if (tmpCode.equals(code)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取动态口令剩余秒数
+     * <p>
+     * 所有口令是基于时间戳计算,因此任何动态口令的剩余有效时间都是一致的
+     *
+     * @return
+     */
+    public static int getRemainingSeconds() {
+        return TIME_STEP - (int) (System.currentTimeMillis() / 1000 % TIME_STEP);
+    }
+
+    /**
+     * 生成动态口令
+     *
+     * @param secret
+     * @param currentInterval
+     * @param codeDigits
+     * @return
+     */
+    private static String generateTOTP(String secret, long currentInterval, int codeDigits) {
+        if (codeDigits < 1 || codeDigits > 18) {
+            throw new UnsupportedOperationException("不支持" + codeDigits + "位数的动态口令");
+        }
+        byte[] content = ByteBuffer.allocate(8).putLong(currentInterval).array();
+        byte[] hash = TotpUtils.hmacsha(content, secret);
+        // 获取hash最后一个字节的低4位,作为选择结果的开始下标偏移
+        int offset = hash[hash.length - 1] & 0xf;
+        // 获取4个字节组成一个整数,其中第一个字节最高位为符号位,不获取,使用0x7f
+        int binary = ((hash[offset] & 0x7f) << 24) |
+                ((hash[offset + 1] & 0xff) << 16) |
+                ((hash[offset + 2] & 0xff) << 8) |
+                (hash[offset + 3] & 0xff);
+        // 如果所需位数为6,则该值为1000000
+        long digitsPower = Long.parseLong(TotpUtils.rightPadding("1", codeDigits + 1));
+        // 获取当前数值后的指定位数
+        long code = binary % digitsPower;
+        // 将数字转成字符串,不够指定位前面补0
+        return TotpUtils.leftPadding(Long.toString(code), codeDigits);
+    }
+
+    /**
+     * 获取当前时间戳
+     *
+     * @return
+     */
+    private static long getCurrentInterval() {
+        return System.currentTimeMillis() / 1000 / TIME_STEP;
+    }
+
+    /**
+     * 向左补足0
+     *
+     * @param value
+     * @param length
+     * @return
+     */
+    private static String leftPadding(String value, int length) {
+        while (value.length() < length) {
+            value = "0" + value;
+        }
+        return value;
+    }
+
+    /**
+     * 向右补足0
+     *
+     * @param value
+     * @param length
+     * @return
+     */
+    private static String rightPadding(String value, int length) {
+        while (value.length() < length) {
+            value = value + "0";
+        }
+        return value;
+    }
+
+    /**
+     * 使用HmacSHA1加密
+     *
+     * @param content
+     * @param key
+     * @return
+     */
+    private static byte[] hmacsha(byte[] content, String key) {
+        try {
+            byte[] byteKey = new Base32().decode(key);
+            Mac hmac = Mac.getInstance("HmacSHA1");
+            SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA1");
+            hmac.init(keySpec);
+            return hmac.doFinal(content);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static void main(String[] args) throws IOException, WriterException {
+        //System.out.println(Base64.encode(QrUtils.draw(TotpUtils.getQRCodeStr("admin","e867dc42-46f8-421d-8122-6c9d1d6fb820"),200,200)));
+        System.out.println(TotpUtils.verify("e867dc42-46f8-421d-8122-6c9d1d6fb820","935145"));
+    }
+}

+ 100 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/common/security/UserDetailHelper.java

@@ -0,0 +1,100 @@
+package com.hrsk.cloud.op.adapter.common.security;
+
+import com.hrsk.cloud.op.app.system.SysPermissionSerivce;
+import com.hrsk.cloud.op.domain.common.AuthErrorCodeEnum;
+import com.hrsk.cloud.op.domain.system.SysUserStatusEnum;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.dao.SysUserDao;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.SysUserDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisCache;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisKeyEnum;
+import com.hrsk.pangu.tool.exception.SysException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 17:52
+ * @description: UserDetailsService
+ **/
+@Slf4j
+@Component
+public class UserDetailHelper implements UserDetailsService {
+    @Resource
+    private SysPermissionSerivce sysPermissionSerivce;
+
+    @Resource
+    private SysUserDao sysUserDao;
+
+    @Resource
+    private RedisCache redisCache;
+
+    @Value(value = "${security.user.password.maxRetryCount}")
+    private int maxRetryCount;
+
+    @Value(value = "${security.user.password.lockTime}")
+    private int lockTime;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        SysUserDO sysUser = sysUserDao.selectUserByUserName(username);
+        if (sysUser == null) {
+            log.info("登录用户:{} 不存在.", username);
+            throw new UsernameNotFoundException(AuthErrorCodeEnum.USER_NOT_FOUND.getMessage());
+        }
+        if (StringUtils.equals(sysUser.getStatus(), SysUserStatusEnum.DELETE.getCode())) {
+            log.info("登录用户:{} 已被删除.", username);
+            throw new SysException(AuthErrorCodeEnum.USER_HAS_DELETED.getCode(),AuthErrorCodeEnum.USER_HAS_DELETED.getMessage());
+        } else if (StringUtils.equals(sysUser.getStatus(), SysUserStatusEnum.DEACTIVATE.getCode())) {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new SysException(AuthErrorCodeEnum.USER_HAS_DEACTIVATED.getCode(),AuthErrorCodeEnum.USER_HAS_DEACTIVATED.getMessage());
+        }
+        validatePassword(sysUser.getPassword());
+        return new CurrentLoginUser(sysUser, sysPermissionSerivce.getMenuPermission(sysUser) );
+    }
+
+    /**
+     * 验证密码
+     * @param password 密码
+     */
+    private void validatePassword(String password){
+        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
+        String username = usernamePasswordAuthenticationToken.getName();
+        String rawPassword = usernamePasswordAuthenticationToken.getCredentials().toString();
+        Integer retryCount = redisCache.getCacheObject(RedisKeyEnum.PWD_ERR_CNT.getKey(username));
+        if (retryCount == null) {
+            retryCount = 0;
+        }
+        if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) {
+            throw new SysException(AuthErrorCodeEnum.USER_PASSWORD_RETRY_LIMIT_EXCEED.getCode(),AuthErrorCodeEnum.USER_PASSWORD_RETRY_LIMIT_EXCEED.getMessage());
+        }
+        if (!matches(rawPassword, password)) {
+            retryCount = retryCount + 1;
+            redisCache.setCacheObject(RedisKeyEnum.PWD_ERR_CNT.getKey(username), retryCount, lockTime, TimeUnit.MINUTES);
+            throw new SysException(AuthErrorCodeEnum.PASSWORD_NOT_MATCH.getCode(),AuthErrorCodeEnum.PASSWORD_NOT_MATCH.getMessage());
+        } else {
+            redisCache.deleteObject(RedisKeyEnum.PWD_ERR_CNT.getKey(username));
+        }
+    }
+
+    /**
+     * 判断密码是否相同
+     * @param rawPassword 真实密码
+     * @param encodedPassword 加密后字符
+     * @return 结果
+     */
+    public static boolean matches(String rawPassword, String encodedPassword) {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+}

+ 25 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/common/MerchantController.java

@@ -0,0 +1,25 @@
+package com.hrsk.cloud.op.adapter.controller.common;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.hrsk.cloud.op.client.dto.MerchantDTO;
+import com.hrsk.pangu.dto.AntDMultiResponse;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-28 16:00
+ * @description: 商户Controller
+ **/
+@RestController
+@RequestMapping("/merchant")
+public class MerchantController {
+    @GetMapping("/list")
+    public AntDMultiResponse<MerchantDTO> list(){
+        List<MerchantDTO> merchants = CollectionUtils.toList(new MerchantDTO(10000L,"优逸客"),new MerchantDTO(10001L,"信业帮"));
+        return AntDMultiResponse.of(merchants);
+    }
+}

+ 103 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/egress/EgressApiController.java

@@ -0,0 +1,103 @@
+package com.hrsk.cloud.op.adapter.controller.egress;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.hrsk.cloud.op.adapter.common.security.HttpSecurityHelper;
+import com.hrsk.cloud.op.client.api.EgressApiService;
+import com.hrsk.cloud.op.client.dto.EgressApiDTO;
+import com.hrsk.cloud.op.client.dto.EgressApiLoanDTO;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiUpsertCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanQry;
+import com.hrsk.cloud.op.client.dto.command.EgressApiQry;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanCrtCmd;
+import com.hrsk.pangu.dto.AntDMultiResponse;
+import com.hrsk.pangu.dto.AntDPageResponse;
+import com.hrsk.pangu.dto.AntDSingleResponse;
+import com.hrsk.pangu.dto.MultiResponse;
+import com.hrsk.pangu.dto.PageResponse;
+import com.hrsk.pangu.dto.Response;
+import com.hrsk.pangu.dto.SingleResponse;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 14:39
+ * @description: 接出API控制器
+ **/
+@RestController()
+@RequestMapping("/egress/api")
+public class EgressApiController {
+    @Resource
+    private EgressApiService egressApiService;
+
+    @Resource
+    private HttpSecurityHelper httpSecurityHelper;
+    /**
+     * 分页查询
+     * @return
+     */
+    @GetMapping("/page")
+    public AntDPageResponse<EgressApiDTO> page(EgressApiQry egressApiQry){
+        PageResponse<EgressApiDTO> response = egressApiService.page(egressApiQry);
+        return AntDPageResponse.of(response);
+    }
+
+    /**
+     * 创建/更新API
+     * @param egressApiUpsertCmd
+     * @param request
+     * @return
+     */
+    @PutMapping("/upsert")
+    public AntDSingleResponse<Long> upsert(@RequestBody EgressApiUpsertCmd egressApiUpsertCmd, HttpServletRequest request){
+        Operator operator = httpSecurityHelper.getCurrentLoginUser(request).toOperator();
+        SingleResponse response = egressApiService.upsert(egressApiUpsertCmd,operator);
+        return AntDSingleResponse.of(response);
+    }
+
+
+    /**
+     * 列表查询
+     * @return
+     */
+    @GetMapping("/list")
+    public AntDMultiResponse<EgressApiDTO> list(EgressApiQry egressApiQry){
+        MultiResponse<EgressApiDTO> response = egressApiService.list(egressApiQry);
+        return AntDMultiResponse.of(response);
+    }
+
+    /**
+     * 助贷API对接
+     * @param egressApiLoanCrtCmd 助贷
+     * @return
+     */
+    @PutMapping("/loan/integration")
+    public Response loanApiIntegration(@RequestBody EgressApiLoanCrtCmd egressApiLoanCrtCmd, HttpServletRequest request){
+        Operator operator = httpSecurityHelper.getCurrentLoginUser(request).toOperator();
+        return egressApiService.createLoanApi(egressApiLoanCrtCmd,operator);
+    }
+
+    @GetMapping("/loan")
+    public SingleResponse<EgressApiLoanDTO> queryLoan(Long apiId){
+        EgressApiLoanQry qry = new EgressApiLoanQry();
+        qry.setApiId(apiId);
+        MultiResponse<EgressApiLoanDTO> multiResponse = egressApiService.queryLoan(qry);
+        if(CollectionUtils.isNotEmpty(multiResponse.getData())){
+            return SingleResponse.of(multiResponse.getData().get(0));
+        }
+        return SingleResponse.buildSuccess();
+    }
+
+    @DeleteMapping()
+    public Response delete(Long id){
+        return egressApiService.delete(id);
+    }
+}

+ 60 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/egress/EgressApiEndpointController.java

@@ -0,0 +1,60 @@
+package com.hrsk.cloud.op.adapter.controller.egress;
+
+import com.hrsk.cloud.op.client.api.EgressApiEndpointService;
+import com.hrsk.cloud.op.client.dto.EgressApiEndpointDTO;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointUpsertCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointQry;
+import com.hrsk.pangu.dto.AntDMultiResponse;
+import com.hrsk.pangu.dto.AntDPageResponse;
+import com.hrsk.pangu.dto.PageResponse;
+import com.hrsk.pangu.dto.SingleResponse;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 11:36
+ * @description: 通道controller
+ **/
+@RestController()
+@RequestMapping("/egress/api/endpoint")
+public class EgressApiEndpointController {
+
+    @Resource
+    private EgressApiEndpointService egressApiEndpointService;
+    /**
+     * 创建
+     * @param egressApiEndpointUpsertCmd 创建通道
+     * @return
+     */
+    @PutMapping("/create")
+    public SingleResponse<Long> create(@RequestBody EgressApiEndpointUpsertCmd egressApiEndpointUpsertCmd){
+        return null;
+    }
+
+    /**
+     * 分页查询
+     * @param egressApiEndpointQry
+     * @return
+     */
+    @GetMapping("/page")
+    public AntDPageResponse<EgressApiEndpointDTO> page(EgressApiEndpointQry egressApiEndpointQry){
+        PageResponse<EgressApiEndpointDTO> response = egressApiEndpointService.page(egressApiEndpointQry);
+        return AntDPageResponse.of(response);
+    }
+
+    /**
+     * 列表查询
+     * @param egressApiEndpointQry
+     * @return
+     */
+    @GetMapping("/list")
+    public AntDMultiResponse<EgressApiEndpointDTO> list(EgressApiEndpointQry egressApiEndpointQry){
+        return AntDMultiResponse.of(egressApiEndpointService.list(egressApiEndpointQry));
+    }
+}

+ 1 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/package-info.java

@@ -0,0 +1 @@
+package com.hrsk.cloud.op.adapter.controller;

+ 125 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/system/AuthController.java

@@ -0,0 +1,125 @@
+package com.hrsk.cloud.op.adapter.controller.system;
+
+import com.hrsk.cloud.op.adapter.common.security.AuthenticationContextHolder;
+import com.hrsk.cloud.op.adapter.common.security.CurrentLoginUser;
+import com.hrsk.cloud.op.adapter.common.security.HttpSecurityHelper;
+import com.hrsk.cloud.op.adapter.common.security.TotpUtils;
+import com.hrsk.cloud.op.client.api.AuthService;
+import com.hrsk.cloud.op.client.dto.CaptchaDTO;
+import com.hrsk.cloud.op.client.dto.CurrentLoginUserDTO;
+import com.hrsk.cloud.op.client.dto.command.UserLoginCmd;
+import com.hrsk.cloud.op.client.dto.command.UserOtpLoginCmd;
+import com.hrsk.cloud.op.domain.common.AuthErrorCodeEnum;
+import com.hrsk.pangu.dto.AntDSingleResponse;
+import com.hrsk.pangu.dto.Response;
+import com.hrsk.pangu.dto.SingleResponse;
+import com.hrsk.pangu.tool.exception.BizException;
+import com.hrsk.pangu.tool.exception.SysException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 10:56
+ * @description: 认证Controller
+ **/
+@Slf4j
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+    @Resource
+    private AuthService authService;
+    @Resource
+    private AuthenticationManager authenticationManager;
+    @Resource
+    private HttpSecurityHelper httpSecurityHelper;
+
+    /**
+     * 图形验证码
+     * @return 图形验证码
+     * @throws IOException
+     */
+    @GetMapping("/captcha/image")
+    public AntDSingleResponse<CaptchaDTO> captcha() throws IOException {
+        SingleResponse<CaptchaDTO> response = authService.imageCaptcha();
+        return  AntDSingleResponse.of(response);
+    }
+
+    /**
+     * 登录
+     * @param userLoginCmd 登录用户
+     * @return token
+     */
+    @PostMapping("/login")
+    public AntDSingleResponse<String> login(@RequestBody UserLoginCmd userLoginCmd){
+        if(!authService.validateCaptcha(userLoginCmd.getCaptcha(), userLoginCmd.getUuid()).getData()){
+            return AntDSingleResponse.buildFailure(AuthErrorCodeEnum.OTP_NOT_MATCH.getCode(),AuthErrorCodeEnum.OTP_NOT_MATCH.getMessage());
+        }
+        Authentication authentication = null;
+        try {
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLoginCmd.getUsername(), userLoginCmd.getPassword());
+            AuthenticationContextHolder.setContext(authenticationToken);
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager.authenticate(authenticationToken);
+        } catch (Exception e) {
+            log.error("登录失败!",e);
+            if (e instanceof BadCredentialsException) {
+                return AntDSingleResponse.buildFailure(AuthErrorCodeEnum.PASSWORD_NOT_MATCH.getCode(),AuthErrorCodeEnum.PASSWORD_NOT_MATCH.getMessage());
+            } else {
+                if(e.getCause() instanceof SysException){
+                    throw new SysException(((SysException) e.getCause()).getErrCode(),e.getMessage(),e);
+                }else if(e.getCause() instanceof BizException){
+                    throw new BizException(((SysException) e.getCause()).getErrCode(),e.getMessage(),e);
+                }else{
+                    throw e;
+                }
+            }
+        } finally {
+            AuthenticationContextHolder.clearContext();
+        }
+        CurrentLoginUser loginUser = (CurrentLoginUser) authentication.getPrincipal();
+        return AntDSingleResponse.of(httpSecurityHelper.createToken(loginUser));
+    }
+
+    /**
+     * otp登录
+     * @param userOtpLoginCmd cmd
+     * @return
+     */
+    @PostMapping("/otp-login")
+    public Response otpLogin(@RequestBody UserOtpLoginCmd userOtpLoginCmd,HttpServletRequest request){
+        CurrentLoginUser currentLoginUser = httpSecurityHelper.getCurrentLoginUser(request);
+        if(currentLoginUser == null){
+            return Response.buildFailure(AuthErrorCodeEnum.NOT_LOGIN.getCode(),AuthErrorCodeEnum.NOT_LOGIN.getMessage());
+        }
+        if(TotpUtils.verify(currentLoginUser.getOtpSecret(),userOtpLoginCmd.getCode())){
+            currentLoginUser.setFinishTfa(true);
+            httpSecurityHelper.refreshToken(currentLoginUser);
+            return Response.buildSuccess();
+        }
+        return Response.buildFailure(AuthErrorCodeEnum.OTP_NOT_MATCH.getCode(),AuthErrorCodeEnum.OTP_NOT_MATCH.getMessage());
+    }
+
+    /**
+     * 获取当前登录用户
+     * @param request request
+     * @return 当前登录用户
+     */
+    @GetMapping("/current-user")
+    public AntDSingleResponse<CurrentLoginUserDTO> currentUser(HttpServletRequest request){
+        CurrentLoginUser currentLoginUser = httpSecurityHelper.getCurrentLoginUser(request);
+        return AntDSingleResponse.of(currentLoginUser.to());
+    }
+}

+ 30 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/controller/system/SysUserController.java

@@ -0,0 +1,30 @@
+package com.hrsk.cloud.op.adapter.controller.system;
+
+import com.hrsk.cloud.op.client.api.SysUserService;
+import com.hrsk.cloud.op.client.dto.SysUserDTO;
+import com.hrsk.cloud.op.client.dto.command.SysUserQry;
+import com.hrsk.pangu.dto.AntDMultiResponse;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 18:22
+ * @description:
+ **/
+@RestController
+@RequestMapping("/system/user")
+public class SysUserController {
+    @Resource
+    private SysUserService sysUserService;
+
+    @GetMapping("/list")
+    public AntDMultiResponse<SysUserDTO> list(SysUserQry sysUserQry){
+        List<SysUserDTO> list = sysUserService.querySysUser(sysUserQry);
+        return AntDMultiResponse.of(list);
+    }
+}

+ 1 - 0
op-admin-server-adapter/src/main/java/com/hrsk/cloud/op/adapter/package-info.java

@@ -0,0 +1 @@
+package com.hrsk.cloud.op.adapter;

+ 29 - 0
op-admin-server-app/pom.xml

@@ -0,0 +1,29 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.hrsk.cloud</groupId>
+        <artifactId>op-admin-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>op-admin-server-app</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.hrsk.cloud</groupId>
+            <artifactId>op-admin-server-infrastructure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.hrsk.cloud</groupId>
+            <artifactId>op-admin-server-client</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 43 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/EgressApiEndpointServiceImpl.java

@@ -0,0 +1,43 @@
+package com.hrsk.cloud.op.app.egress;
+
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import com.hrsk.cloud.op.app.egress.executor.EgressApiEndpointQryExe;
+import com.hrsk.cloud.op.client.api.EgressApiEndpointService;
+import com.hrsk.cloud.op.client.dto.EgressApiDTO;
+import com.hrsk.cloud.op.client.dto.EgressApiEndpointDTO;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointUpsertCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointQry;
+import com.hrsk.pangu.dto.MultiResponse;
+import com.hrsk.pangu.dto.PageResponse;
+import com.hrsk.pangu.dto.SingleResponse;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 13:33
+ * @description: 接出通道服务实现
+ **/
+@Service
+public class EgressApiEndpointServiceImpl implements EgressApiEndpointService {
+    @Resource
+    private EgressApiEndpointQryExe egressApiEndpointQryExe;
+
+    @Override
+    public SingleResponse<Long> create(EgressApiEndpointUpsertCmd egressApiEndpointUpsertCmd) {
+        return null;
+    }
+
+    @Override
+    public PageResponse<EgressApiEndpointDTO> page(EgressApiEndpointQry egressApiEndpointQry) {
+        Page<EgressApiDTO> page = PageHelper.startPage(egressApiEndpointQry.getCurrent(), egressApiEndpointQry.getPageSize());
+        return PageResponse.of(egressApiEndpointQryExe.execute(egressApiEndpointQry),page.getTotal(),page.getPageSize(),page.getPageNum());
+    }
+
+    @Override
+    public MultiResponse<EgressApiEndpointDTO> list(EgressApiEndpointQry egressApiEndpointQry) {
+        return MultiResponse.of(egressApiEndpointQryExe.execute(egressApiEndpointQry));
+    }
+}

+ 85 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/EgressApiServiceImpl.java

@@ -0,0 +1,85 @@
+package com.hrsk.cloud.op.app.egress;
+
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import com.hrsk.cloud.op.app.egress.executor.EgressApiDelExe;
+import com.hrsk.cloud.op.app.egress.executor.EgressApiUpsertExe;
+import com.hrsk.cloud.op.app.egress.executor.EgressApiLoanCrtExe;
+import com.hrsk.cloud.op.app.egress.executor.EgressApiLoanQryExe;
+import com.hrsk.cloud.op.app.egress.executor.EgressApiQryExe;
+import com.hrsk.cloud.op.client.api.EgressApiService;
+import com.hrsk.cloud.op.client.dto.EgressApiDTO;
+import com.hrsk.cloud.op.client.dto.EgressApiLoanDTO;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiUpsertCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanCrtCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanQry;
+import com.hrsk.cloud.op.client.dto.command.EgressApiQry;
+import com.hrsk.pangu.dto.MultiResponse;
+import com.hrsk.pangu.dto.PageResponse;
+import com.hrsk.pangu.dto.Response;
+import com.hrsk.pangu.dto.SingleResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 14:46
+ * @description: 接出API服务实现类
+ **/
+@Slf4j
+@Service
+public class EgressApiServiceImpl implements EgressApiService {
+    @Resource
+    private EgressApiQryExe egressApiQryExe;
+
+    @Resource
+    private EgressApiUpsertExe egressApiUpsertExe;
+
+    @Resource
+    private EgressApiLoanCrtExe egressApiLoanCrtExe;
+
+    @Resource
+    private EgressApiLoanQryExe egressApiLoanQryExe;
+
+    @Resource
+    private EgressApiDelExe egressApiDelExe;
+
+    @Override
+    public PageResponse<EgressApiDTO> page(EgressApiQry egressApiQry) {
+        Page<EgressApiDTO> page = PageHelper.startPage(egressApiQry.getCurrent(), egressApiQry.getPageSize());
+        return PageResponse.of(egressApiQryExe.execute(egressApiQry),page.getTotal(),page.getPageSize(),page.getPageNum());
+    }
+
+    @Override
+    public SingleResponse<Long> upsert(EgressApiUpsertCmd egressApiUpsertCmd, Operator operator) {
+        Long id = egressApiUpsertExe.execute(egressApiUpsertCmd,operator);
+        return SingleResponse.of(id);
+    }
+
+    @Override
+    public MultiResponse<EgressApiDTO> list(EgressApiQry egressApiQry) {
+        List<EgressApiDTO> list = egressApiQryExe.execute(egressApiQry);
+        return MultiResponse.of(list);
+    }
+
+    @Override
+    public Response createLoanApi(EgressApiLoanCrtCmd egressApiLoanCrtCmd, Operator operator) {
+        egressApiLoanCrtExe.execute(egressApiLoanCrtCmd,operator);
+        return Response.buildSuccess();
+    }
+
+    @Override
+    public MultiResponse<EgressApiLoanDTO> queryLoan(EgressApiLoanQry egressApiLoanQry) {
+        return MultiResponse.of(egressApiLoanQryExe.execute(egressApiLoanQry));
+    }
+
+    @Override
+    public Response delete(Long id) {
+        egressApiDelExe.execute(id);
+        return Response.buildSuccess();
+    }
+}

+ 36 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiDelExe.java

@@ -0,0 +1,36 @@
+package com.hrsk.cloud.op.app.egress.executor;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.hrsk.cloud.op.domain.common.EgressApiErrorCodeEnum;
+import com.hrsk.cloud.op.domain.egress.EgressApiStatusEnum;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiMapper;
+import com.hrsk.pangu.tool.exception.BizException;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-11-03 17:31
+ * @description: 删除接出API
+ **/
+@Component
+public class EgressApiDelExe {
+    @Resource
+    private EgressApiMapper egressApiMapper;
+    /**
+     * 执行
+     * @param id 主键
+     */
+    public void execute(Long id){
+        EgressApiDO egressApiDO = egressApiMapper.selectById(id);
+        if(!StringUtils.equals(EgressApiStatusEnum.DRAFT.getCode(),egressApiDO.getStatus())){
+            throw new BizException(EgressApiErrorCodeEnum.API_STATUS_CAN_NOT_DELETE.getCode(),EgressApiErrorCodeEnum.API_STATUS_CAN_NOT_DELETE.getMessage());
+        }
+        Wrapper update = Wrappers.update().set("status",EgressApiStatusEnum.DELETE.getCode()).eq("id",id);
+        egressApiMapper.update(update);
+    }
+}

+ 44 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiEndpointCrtExe.java

@@ -0,0 +1,44 @@
+package com.hrsk.cloud.op.app.egress.executor;
+
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointUpsertCmd;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiEndpointDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiEndpointHistoryDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiEndpointHistoryMapper;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiEndpointMapper;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 13:34
+ * @description: 创建API通道EXE
+ **/
+@Component
+public class EgressApiEndpointCrtExe {
+    @Resource
+    private EgressApiEndpointMapper egressApiEndpointMapper;
+    @Resource
+    private EgressApiEndpointHistoryMapper egressApiEndpointHistoryMapper;
+
+    /**
+     * 执行 备份->插入->删除
+     * @param egressApiEndpointUpsertCmd CMD
+     * @param operator 执行人
+     * @return 主键
+     */
+    public Long execute(EgressApiEndpointUpsertCmd egressApiEndpointUpsertCmd, Operator operator){
+        int version = 1;
+        if(egressApiEndpointUpsertCmd.getId()!=null){
+            EgressApiEndpointDO egressApiEndpoint = egressApiEndpointMapper.selectById(egressApiEndpointUpsertCmd.getId());
+            egressApiEndpointMapper.deleteById(egressApiEndpointUpsertCmd.getId());
+            egressApiEndpointHistoryMapper.insert(EgressApiEndpointHistoryDO.from(egressApiEndpoint));
+            version = Integer.valueOf(egressApiEndpoint.getVersion())+1;
+        }
+        EgressApiEndpointDO egressApiEndpoint = EgressApiEndpointDO.from(egressApiEndpointUpsertCmd,version+"",operator);
+        egressApiEndpointMapper.insert(egressApiEndpoint);
+        return egressApiEndpoint.getId();
+    }
+
+}

+ 34 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiEndpointQryExe.java

@@ -0,0 +1,34 @@
+package com.hrsk.cloud.op.app.egress.executor;
+
+import com.hrsk.cloud.op.client.dto.EgressApiEndpointDTO;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointQry;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.dao.EgressApiEndpointDao;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiEndpointDO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 15:29
+ * @description: 接出API通道查询执行
+ **/
+@Component
+public class EgressApiEndpointQryExe {
+    @Resource
+    private EgressApiEndpointDao egressApiEndpointDao;
+    /**
+     * 执行
+     * @param egressApiEndpointQry CMD
+     * @return 接出API列表
+     */
+    public List<EgressApiEndpointDTO> execute(EgressApiEndpointQry egressApiEndpointQry){
+        EgressApiEndpointDO query = new EgressApiEndpointDO();
+        BeanUtils.copyProperties(egressApiEndpointQry,query);
+        List<EgressApiEndpointDO> egressApiEndpointDOS = egressApiEndpointDao.selectEgressApiEndpoint(query);
+        return egressApiEndpointDOS.stream().map(egressApiEndpointDO -> egressApiEndpointDO.toDTO()).collect(Collectors.toList());
+    }
+}

+ 87 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiLoanCrtExe.java

@@ -0,0 +1,87 @@
+package com.hrsk.cloud.op.app.egress.executor;
+
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointUpsertCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanCrtCmd;
+import com.hrsk.cloud.op.domain.common.EgressApiErrorCodeEnum;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiLoanDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiLoanMapper;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiMapper;
+import com.hrsk.pangu.tool.exception.BizException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import javax.annotation.Resource;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 19:26
+ * @description: 助贷API创建EXE
+ **/
+@Slf4j
+@Component
+public class EgressApiLoanCrtExe {
+    private static final String LOAN_ENDPOINT_NAME_CHECK_FORMAT = "%s-撞库";
+    private static final String LOAN_ENDPOINT_NAME_APPLY_FORMAT = "%s-注册";
+    @Resource
+    private EgressApiEndpointCrtExe egressApiEndpointCrtExe;
+    @Resource
+    private EgressApiMapper egressApiMapper;
+    @Resource
+    private EgressApiLoanMapper egressApiLoanMapper;
+    @Resource
+    private TransactionTemplate transactionTemplate;
+    /**
+     * 执行
+     * @param egressApiLoanCrtCmd CMD
+     * @param operator 操作人
+     */
+    public void execute(EgressApiLoanCrtCmd egressApiLoanCrtCmd, Operator operator){
+        EgressApiDO egressApiDO = egressApiMapper.selectById(egressApiLoanCrtCmd.getApiId());
+        if(egressApiDO == null){
+            throw new BizException(EgressApiErrorCodeEnum.API_NOT_EXIST.getCode(),EgressApiErrorCodeEnum.API_NOT_EXIST.getMessage());
+        }
+        transactionTemplate.execute(status -> {
+            try {
+                createApiEndpoints(egressApiDO.getApiName(),egressApiLoanCrtCmd,operator);
+                createEgressApiLoan(egressApiLoanCrtCmd,operator);
+            }catch (Exception ex){
+                log.error("创建失败!",ex);
+                status.setRollbackOnly();
+                throw new BizException(EgressApiErrorCodeEnum.API_ENDPOINT_CRT_FAIL.getCode(),EgressApiErrorCodeEnum.API_ENDPOINT_CRT_FAIL.getMessage(),ex);
+            }
+            return null;
+        });
+    }
+
+    /**
+     * 创建ENDPOINT
+     * @param egressApiLoanCrtCmd cmd
+     * @param operator 操作人
+     */
+    private void createApiEndpoints(String apiName,EgressApiLoanCrtCmd egressApiLoanCrtCmd, Operator operator){
+        //checkinto
+        EgressApiEndpointUpsertCmd checkinto = egressApiLoanCrtCmd.getCheckinto();
+        checkinto.setEndpointCode("checkinto");
+        checkinto.setEndpointName(String.format(LOAN_ENDPOINT_NAME_CHECK_FORMAT,apiName));
+        egressApiEndpointCrtExe.execute(checkinto,operator);
+        //apply
+        EgressApiEndpointUpsertCmd apply = egressApiLoanCrtCmd.getApply();
+        apply.setEndpointName(String.format(LOAN_ENDPOINT_NAME_APPLY_FORMAT,apiName));
+        apply.setEndpointCode("apply");
+        egressApiEndpointCrtExe.execute(apply,operator);
+    }
+
+    /**
+     * 创建API LOAN
+     * @param egressApiLoanCrtCmd CMD
+     * @param operator 操作人
+     */
+    private void createEgressApiLoan(EgressApiLoanCrtCmd egressApiLoanCrtCmd, Operator operator){
+        EgressApiLoanDO egressApiLoan = EgressApiLoanDO.from(egressApiLoanCrtCmd.getApiLoanInfo(),operator);
+        egressApiLoan.setApiId(egressApiLoanCrtCmd.getApiId());
+        egressApiLoanMapper.insert(egressApiLoan);
+    }
+}

+ 40 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiLoanQryExe.java

@@ -0,0 +1,40 @@
+package com.hrsk.cloud.op.app.egress.executor;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.hrsk.cloud.op.client.dto.EgressApiLoanDTO;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanQry;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiLoanDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiLoanMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 19:26
+ * @description: 助贷API查询EXE
+ **/
+@Slf4j
+@Component
+public class EgressApiLoanQryExe {
+    @Resource
+    private EgressApiLoanMapper egressApiLoanMapper;
+
+    /**
+     * 执行
+     * @param egressApiLoanQry CMD
+     * @return 列表
+     */
+    public List<EgressApiLoanDTO> execute(EgressApiLoanQry egressApiLoanQry){
+        EgressApiLoanDO query = EgressApiLoanDO.from(egressApiLoanQry);
+        QueryWrapper<EgressApiLoanDO> queryWrapper = Wrappers
+                .query(query);
+        List<EgressApiLoanDO> egressApiLoanDOS = egressApiLoanMapper.selectList(queryWrapper);
+        return egressApiLoanDOS.stream().map(egressApiLoanDO -> egressApiLoanDO.toDTO()).collect(Collectors.toList());
+    }
+
+}

+ 38 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiQryExe.java

@@ -0,0 +1,38 @@
+package com.hrsk.cloud.op.app.egress.executor;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.hrsk.cloud.op.client.dto.EgressApiDTO;
+import com.hrsk.cloud.op.client.dto.command.EgressApiQry;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiMapper;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 15:29
+ * @description: 接出API查询执行
+ **/
+@Component
+public class EgressApiQryExe {
+    @Resource
+    private EgressApiMapper egressApiMapper;
+    /**
+     * 执行
+     * @param egressApiQry CMD
+     * @return 接出API列表
+     */
+    public List<EgressApiDTO> execute(EgressApiQry egressApiQry){
+        EgressApiDO query = new EgressApiDO();
+        BeanUtils.copyProperties(egressApiQry,query);
+        QueryWrapper<EgressApiDO> queryWrapper = Wrappers
+                .query(query);
+        List<EgressApiDO> egressApiDOS = egressApiMapper.selectList(queryWrapper);
+        return egressApiDOS.stream().map(egressApiDO -> egressApiDO.toDTO()).collect(Collectors.toList());
+    }
+}

+ 93 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/egress/executor/EgressApiUpsertExe.java

@@ -0,0 +1,93 @@
+package com.hrsk.cloud.op.app.egress.executor;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiUpsertCmd;
+import com.hrsk.cloud.op.domain.common.EgressApiErrorCodeEnum;
+import com.hrsk.cloud.op.domain.egress.EgressApiStatusEnum;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.SysUserDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.EgressApiMapper;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.SysUserMapper;
+import com.hrsk.pangu.tool.exception.BizException;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-22 09:45
+ * @description: 接出API创建EXE
+ **/
+@Component
+public class EgressApiUpsertExe {
+    @Resource
+    private EgressApiMapper egressApiMapper;
+    @Resource
+    private SysUserMapper sysUserMapper;
+
+    /**
+     * 处理
+     * @param egressApiUpsertCmd CMD
+     * @param operator 操作人
+     * @return ID
+     */
+    public Long execute(EgressApiUpsertCmd egressApiUpsertCmd, Operator operator){
+        SysUserDO techOwner = sysUserMapper.selectById(egressApiUpsertCmd.getTechOwnerUserid());
+        if(techOwner == null){
+            throw new BizException(EgressApiErrorCodeEnum.TECH_OWNER_NOT_EXIST.getCode(),EgressApiErrorCodeEnum.TECH_OWNER_NOT_EXIST.getMessage());
+        }
+        EgressApiDO egressApiDO = EgressApiDO.from(egressApiUpsertCmd,operator);
+        egressApiDO.setStatus(EgressApiStatusEnum.DRAFT.getCode());
+        egressApiDO.setTechOwnerUsername(techOwner.getUsername());
+        if(egressApiUpsertCmd.getId()!=null && egressApiUpsertCmd.getId()>0){
+            egressApiMapper.updateById(egressApiDO);
+        }else{
+            check(egressApiUpsertCmd);
+            egressApiMapper.insert(egressApiDO);
+        }
+        return egressApiDO.getId();
+    }
+
+    /**
+     * 校验
+     * @param egressApiUpsertCmd cmd
+     */
+    private void check(EgressApiUpsertCmd egressApiUpsertCmd){
+        checkByName(egressApiUpsertCmd);
+        checkByCode(egressApiUpsertCmd);
+    }
+
+    /**
+     * 校验名称
+     * @param egressApiUpsertCmd cmd
+     */
+    private void checkByName(EgressApiUpsertCmd egressApiUpsertCmd){
+        EgressApiDO byName = new EgressApiDO();
+        byName.setApiName(egressApiUpsertCmd.getApiName());
+        QueryWrapper<EgressApiDO> queryWrapper = Wrappers
+                .query(byName);
+        List<EgressApiDO> egressApis = egressApiMapper.selectList(queryWrapper);
+        if(egressApis.size()>0){
+            throw new BizException(EgressApiErrorCodeEnum.API_NAME_IS_DUPLICATE.getCode(),EgressApiErrorCodeEnum.API_NAME_IS_DUPLICATE.getMessage());
+        }
+    }
+
+
+    /**
+     * 校验编码
+     * @param egressApiUpsertCmd cmd
+     */
+    private void checkByCode(EgressApiUpsertCmd egressApiUpsertCmd){
+        EgressApiDO byCode = new EgressApiDO();
+        byCode.setApiCode(egressApiUpsertCmd.getApiCode());
+        QueryWrapper<EgressApiDO> queryWrapper = Wrappers
+                .query(byCode);
+        List<EgressApiDO> egressApis = egressApiMapper.selectList(queryWrapper);
+        if(egressApis.size()>0){
+            throw new BizException(EgressApiErrorCodeEnum.API_CODE_IS_DUPLICATE.getCode(),EgressApiErrorCodeEnum.API_CODE_IS_DUPLICATE.getMessage());
+        }
+    }
+}

+ 1 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/package-info.java

@@ -0,0 +1 @@
+package com.hrsk.cloud.op.app;

+ 39 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/AuthServiceImpl.java

@@ -0,0 +1,39 @@
+package com.hrsk.cloud.op.app.system;
+
+import com.hrsk.cloud.op.app.system.executor.CaptchaCreateCmdExe;
+import com.hrsk.cloud.op.client.api.AuthService;
+import com.hrsk.cloud.op.client.dto.CaptchaDTO;
+import com.hrsk.cloud.op.infrastructure.common.CaptchaTypeEnum;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisCache;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisKeyEnum;
+import com.hrsk.pangu.dto.SingleResponse;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 11:46
+ * @description: 认证服务
+ **/
+@Service
+public class AuthServiceImpl implements AuthService {
+    @Resource
+    private CaptchaCreateCmdExe captchaCreateCmdExe;
+
+    @Resource
+    private RedisCache redisCache;
+
+    @Override
+    public SingleResponse<CaptchaDTO> imageCaptcha() {
+        return SingleResponse.of(captchaCreateCmdExe.execute(CaptchaTypeEnum.IMAGE.getCode()));
+    }
+
+    @Override
+    public SingleResponse<Boolean> validateCaptcha(String code, String uuid) {
+        String captcha = redisCache.getCacheObject(RedisKeyEnum.CAPTCHA.getKey(uuid));
+        return SingleResponse.of(StringUtils.equals(captcha,code));
+    }
+
+}

+ 47 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/SysMenuServiceImpl.java

@@ -0,0 +1,47 @@
+package com.hrsk.cloud.op.app.system;
+
+import com.hrsk.cloud.op.client.api.SysMenuService;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.dao.SysMenuDao;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-17 10:52
+ * @description: 系统菜单服务
+ **/
+@Component
+public class SysMenuServiceImpl implements SysMenuService {
+    @Resource
+    private SysMenuDao sysMenuDao;
+
+    @Override
+    public Set<String> selectMenuPermsByRoleId(Long roleId) {
+        List<String> perms = sysMenuDao.selectMenuPermsByRoleId(roleId);
+        Set<String> permsSet = new HashSet<>();
+        for (String perm : perms) {
+            if (StringUtils.isNotEmpty(perm)) {
+                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
+            }
+        }
+        return permsSet;
+    }
+
+    @Override
+    public Set<String> selectMenuPermsByUserId(Long userId) {
+        List<String> perms = sysMenuDao.selectMenuPermsByUserId(userId);
+        Set<String> permsSet = new HashSet<>();
+        for (String perm : perms) {
+            if (StringUtils.isNotEmpty(perm)) {
+                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
+            }
+        }
+        return permsSet;
+    }
+}

+ 47 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/SysPermissionSerivce.java

@@ -0,0 +1,47 @@
+package com.hrsk.cloud.op.app.system;
+
+import com.hrsk.cloud.op.client.api.SysMenuService;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.SysRoleDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.SysUserDO;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 20:03
+ * @description: 系统权限实现类
+ **/
+@Service
+public class SysPermissionSerivce {
+    @Resource
+    private SysMenuService sysMenuService;
+    /**
+     * 获取菜单权限
+     * @param sysUser 系统用户
+     * @return 权限集合
+     */
+    public Set<String> getMenuPermission(SysUserDO sysUser) {
+        Set<String> perms = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (sysUser.isAdmin()) {
+            perms.add("*:*:*");
+        } else {
+            List<SysRoleDO> roles = sysUser.getRoles();
+            if (!CollectionUtils.isEmpty(roles)) {
+                // 多角色设置permissions属性,以便数据权限匹配权限
+                for (SysRoleDO role : roles) {
+                    Set<String> rolePerms = sysMenuService.selectMenuPermsByRoleId(role.getId());
+                    perms.addAll(rolePerms);
+                }
+            } else {
+                perms.addAll(sysMenuService.selectMenuPermsByUserId(sysUser.getId()));
+            }
+        }
+        return perms;
+    }
+}

+ 40 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/SysUserServiceImpl.java

@@ -0,0 +1,40 @@
+package com.hrsk.cloud.op.app.system;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.hrsk.cloud.op.client.api.SysUserService;
+import com.hrsk.cloud.op.client.dto.SysUserDTO;
+import com.hrsk.cloud.op.client.dto.command.SysUserQry;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.SysUserDO;
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.mapper.SysUserMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 18:03
+ * @description: 用户服务实现
+ **/
+@Service
+public class SysUserServiceImpl implements SysUserService {
+    @Resource
+    private SysUserMapper sysUserMapper;
+
+    @Override
+    public List<SysUserDTO> querySysUser(SysUserQry sysUserQry) {
+        SysUserDO query = new SysUserDO().from(sysUserQry);
+        QueryWrapper<SysUserDO> queryWrapper = Wrappers
+                .query(query);
+        List<SysUserDO> sysUserDOList = sysUserMapper.selectList(queryWrapper);
+        return sysUserDOList.stream().map(new Function<SysUserDO, SysUserDTO>() {
+            @Override
+            public SysUserDTO apply(SysUserDO sysUserDO) {
+                return sysUserDO.toDTO();
+            }
+        }).collect(Collectors.toList());
+    }
+}

+ 82 - 0
op-admin-server-app/src/main/java/com/hrsk/cloud/op/app/system/executor/CaptchaCreateCmdExe.java

@@ -0,0 +1,82 @@
+package com.hrsk.cloud.op.app.system.executor;
+
+import com.google.code.kaptcha.Producer;
+import com.hrsk.cloud.op.client.dto.CaptchaDTO;
+import com.hrsk.cloud.op.infrastructure.common.Base64;
+import com.hrsk.cloud.op.infrastructure.common.CaptchaTypeEnum;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisCache;
+import com.hrsk.cloud.op.infrastructure.repository.database.redis.RedisKeyEnum;
+import com.hrsk.pangu.tool.exception.BizException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.FastByteArrayOutputStream;
+
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 15:51
+ * @description: 验证码创建CMD
+ **/
+@Slf4j
+@Component
+public class CaptchaCreateCmdExe {
+
+    /**
+     * 验证码超时时间
+     */
+    private static final Integer CAPTCHA_EXPIRATION = 1;
+
+    /**
+     * 验证码超时时间单位
+     */
+    private static final TimeUnit CAPTCHA_EXPIRATION_UNIT = TimeUnit.MINUTES;
+
+    @Resource(name = "captchaProducer")
+    private Producer captchaProducer;
+
+    @Resource
+    private RedisCache redisCache;
+
+    /**
+     * 执行
+     * @param captchaType 验证码类型 ref:com.hrsk.cloud.op.infrastructure.common.CaptchaTypeEnum
+     * @return 验证码DTO
+     */
+    public CaptchaDTO execute(String captchaType){
+        CaptchaDTO captchaDto = null;
+        if(StringUtils.equals(captchaType,CaptchaTypeEnum.IMAGE.getCode())){
+            captchaDto = imageCaptcha();
+        }else{
+            throw new BizException("不支持的验证码类型!");
+        }
+        redisCache.setCacheObject(RedisKeyEnum.CAPTCHA.getKey(captchaDto.getUuid()), captchaDto.getCode(), CAPTCHA_EXPIRATION, CAPTCHA_EXPIRATION_UNIT);
+        return captchaDto;
+    }
+
+    /**
+     * 创建图片验证码
+     * @return 验证码DTO
+     */
+    private CaptchaDTO imageCaptcha(){
+        String uuid = UUID.randomUUID().toString();
+        String capText = captchaProducer.createText();
+        String capStr = capText.substring(0, capText.lastIndexOf("@"));
+        String code = capText.substring(capText.lastIndexOf("@") + 1);
+        BufferedImage image = captchaProducer.createImage(capStr);
+        // 转换流信息写出
+        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+        try {
+            ImageIO.write(image, "jpg", os);
+        } catch (IOException e) {
+            log.error("创建验证码异常!",e);
+        }
+        return new CaptchaDTO(uuid, Base64.encode(os.toByteArray()), CaptchaTypeEnum.IMAGE.getCode(),code);
+    }
+}

+ 24 - 0
op-admin-server-client/pom.xml

@@ -0,0 +1,24 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.hrsk.cloud</groupId>
+        <artifactId>op-admin-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>op-admin-server-client</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.hrsk.pangu</groupId>
+            <artifactId>pangu-component-dto</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 26 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/AuthService.java

@@ -0,0 +1,26 @@
+package com.hrsk.cloud.op.client.api;
+
+import com.hrsk.cloud.op.client.dto.CaptchaDTO;
+import com.hrsk.pangu.dto.SingleResponse;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 11:46
+ * @description: 系统服务
+ **/
+public interface AuthService {
+    /**
+     * 获取图片验证码
+     * @return 验证码
+     */
+    SingleResponse<CaptchaDTO> imageCaptcha();
+
+    /**
+     * 校验验证码
+     * @param code 编码
+     * @param uuid UUID
+     * @return 验证结果
+     */
+    SingleResponse<Boolean> validateCaptcha(String code,String uuid);
+
+}

+ 36 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/EgressApiEndpointService.java

@@ -0,0 +1,36 @@
+package com.hrsk.cloud.op.client.api;
+
+import com.hrsk.cloud.op.client.dto.EgressApiEndpointDTO;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointUpsertCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointQry;
+import com.hrsk.pangu.dto.MultiResponse;
+import com.hrsk.pangu.dto.PageResponse;
+import com.hrsk.pangu.dto.SingleResponse;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 13:33
+ * @description: 接出API通道服务
+ **/
+public interface EgressApiEndpointService {
+    /**
+     * 创建
+     * @param egressApiEndpointUpsertCmd CMD
+     * @return ID
+     */
+    SingleResponse<Long> create(EgressApiEndpointUpsertCmd egressApiEndpointUpsertCmd);
+
+    /**
+     * 分页查询
+     * @param egressApiEndpointQry 查询条件
+     * @return 分页数据
+     */
+    PageResponse<EgressApiEndpointDTO> page(EgressApiEndpointQry egressApiEndpointQry);
+
+    /**
+     * 列表查询
+     * @param egressApiEndpointQry 查询条件
+     * @return 列表数据
+     */
+    MultiResponse<EgressApiEndpointDTO> list(EgressApiEndpointQry egressApiEndpointQry);
+}

+ 66 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/EgressApiService.java

@@ -0,0 +1,66 @@
+package com.hrsk.cloud.op.client.api;
+
+import com.hrsk.cloud.op.client.dto.EgressApiDTO;
+import com.hrsk.cloud.op.client.dto.EgressApiLoanDTO;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiUpsertCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanCrtCmd;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanQry;
+import com.hrsk.cloud.op.client.dto.command.EgressApiQry;
+import com.hrsk.pangu.dto.MultiResponse;
+import com.hrsk.pangu.dto.PageResponse;
+import com.hrsk.pangu.dto.Response;
+import com.hrsk.pangu.dto.SingleResponse;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 14:46
+ * @description: 接出API服务
+ **/
+public interface EgressApiService {
+    /**
+     * 分页查询接出API
+     * @param egressApiQry 查询条件
+     * @return 分页数据
+     */
+    PageResponse<EgressApiDTO> page(EgressApiQry egressApiQry);
+
+    /**
+     * 创建/更新API
+     * @param egressApiUpsertCmd cmd
+     * @param operator 操作人
+     * @return ID
+     */
+    SingleResponse<Long> upsert(EgressApiUpsertCmd egressApiUpsertCmd, Operator operator);
+
+
+    /**
+     * 查询接出API列表
+     * @param egressApiQry 查询条件
+     * @return 分页数据
+     */
+    MultiResponse<EgressApiDTO> list(EgressApiQry egressApiQry);
+
+    /**
+     * 创建助贷API
+     * @param egressApiLoanCrtCmd cmd
+     * @param operator 操作人
+     * @return
+     */
+    Response createLoanApi(EgressApiLoanCrtCmd egressApiLoanCrtCmd, Operator operator);
+
+
+    /**
+     * 查询API助贷信息
+     * @param egressApiLoanQry CMD
+     * @return 助贷信息
+     */
+    MultiResponse<EgressApiLoanDTO> queryLoan(EgressApiLoanQry egressApiLoanQry);
+
+    /**
+     * 删除API
+     * @param id 主键
+     * @return
+     */
+    Response delete(Long id);
+}

+ 24 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/SysMenuService.java

@@ -0,0 +1,24 @@
+package com.hrsk.cloud.op.client.api;
+
+import java.util.Set;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-17 09:25
+ * @description:
+ **/
+public interface SysMenuService {
+    /**
+     * 通过RoleID获取
+     * @param roleId roleID
+     * @return 权限字符串
+     */
+    Set<String> selectMenuPermsByRoleId(Long roleId);
+
+    /**
+     * 通过userID获取菜单权限
+     * @param userId
+     * @return
+     */
+    Set<String> selectMenuPermsByUserId(Long userId);
+}

+ 16 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/SysPermissionSerivce.java

@@ -0,0 +1,16 @@
+package com.hrsk.cloud.op.client.api;
+
+import java.util.Set;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 19:49
+ * @description: 系统权限服务
+ **/
+public interface SysPermissionSerivce {
+    /**
+     * 查询菜单权限
+     * @param userId 用户ID
+     */
+    Set<String> getMenuPermission(String userId);
+}

+ 20 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/api/SysUserService.java

@@ -0,0 +1,20 @@
+package com.hrsk.cloud.op.client.api;
+
+import com.hrsk.cloud.op.client.dto.SysUserDTO;
+import com.hrsk.cloud.op.client.dto.command.SysUserQry;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 17:58
+ * @description: 用户服务
+ **/
+public interface SysUserService {
+    /**
+     * 查询用户
+     * @param sysUserQry
+     * @return 用户列表
+     */
+    List<SysUserDTO> querySysUser(SysUserQry sysUserQry);
+}

+ 49 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/CaptchaDTO.java

@@ -0,0 +1,49 @@
+package com.hrsk.cloud.op.client.dto;
+
+import com.hrsk.pangu.dto.DTO;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 11:33
+ * @description: 验证码
+ **/
+@Data
+public class CaptchaDTO extends DTO {
+    /**
+     * UUID
+     */
+    private String uuid ;
+    /**
+     * image byte string
+     */
+    private String img;
+    /**
+     * 类型:image/sms
+     */
+    private String type;
+    /**
+     * 验证码
+     */
+    private String code;
+
+    /**
+     * 默认构造函数
+     */
+    public CaptchaDTO() {
+    }
+
+    /**
+     * 构造函数
+     * @param uuid ref:uuid
+     * @param img ref:img
+     * @param type ref:type
+     * @param code ref:code
+     */
+    public CaptchaDTO(String uuid, String img, String type, String code) {
+        this.uuid = uuid;
+        this.img = img;
+        this.type = type;
+        this.code = code;
+    }
+}

+ 52 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/CurrentLoginUserDTO.java

@@ -0,0 +1,52 @@
+package com.hrsk.cloud.op.client.dto;
+
+import com.hrsk.pangu.dto.DTO;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-18 15:16
+ * @description: 当前登录用户
+ **/
+@Data
+public class CurrentLoginUserDTO extends DTO {
+    /**
+     * ID
+     */
+    private Long id;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 昵称
+     */
+    private String nickName;
+    /**
+     * 部门ID
+     */
+    private String deptId;
+    /**
+     * 头像
+     */
+    private String avatar;
+    /**
+     * 登录IP
+     */
+    private String loginIp;
+    /**
+     * 权限
+     */
+    private Set<String> permissions;
+    /**
+     * 所属部门
+     */
+    private SysDeptDTO sysDept;
+    /**
+     * 角色
+     */
+    private List<SysRoleDTO> roles;
+}

+ 61 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/EgressApiDTO.java

@@ -0,0 +1,61 @@
+package com.hrsk.cloud.op.client.dto;
+
+import com.hrsk.pangu.dto.DTO;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 14:40
+ * @description: 接出API DTO
+ **/
+@Data
+public class EgressApiDTO extends DTO {
+    /** ID **/
+    private Long id;
+    /**
+     * API名称
+     */
+    private String apiName;
+    /**
+     * API编码
+     */
+    private String apiCode;
+    /**
+     * API类型
+     */
+    private String apiType;
+    /**
+     * 创建人名称
+     */
+    private String createUsername;
+    /**
+     * 创建时间
+     */
+    private Date gmtCreate;
+    /**
+     * 修改人名称
+     */
+    private String modifyUsername;
+    /**
+     * 修改时间
+     */
+    private Date gmtModify;
+    /**
+     * 技术负责人
+     */
+    private String techOwnerUsername;
+    /**
+     * 技术负责ID
+     */
+    private Long techOwnerUserid;
+    /**
+     * 备注
+     */
+    private String memo;
+    /**
+     * 状态
+     */
+    private String status;
+}

+ 54 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/EgressApiEndpointDTO.java

@@ -0,0 +1,54 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-29 14:08
+ * @description: 接出API通道数据传输类
+ **/
+@Data
+public class EgressApiEndpointDTO {
+    /**
+     * 主键
+     */
+    private Long id;
+    /**
+     * apiId
+     */
+    private Long apiId;
+    /**
+     * API类型
+     */
+    private String apiType;
+    /**
+     * API名称
+     */
+    private String apiName;
+    /**
+     * 通道名称
+     */
+    private String endpointName;
+    /**
+     * 集成方式
+     */
+    private String integrationMode;
+    /**
+     * 创建时间
+     */
+    private Date gmtCreate;
+    /**
+     * 修改时间
+     */
+    private Date gmtModify;
+    /**
+     * 请求配置
+     */
+    private String requestConfig;
+    /**
+     * 响应配置
+     */
+    private String responseConfig;
+}

+ 24 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/EgressApiLoanDTO.java

@@ -0,0 +1,24 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 17:35
+ * @description: 助贷API DTO
+ **/
+@Data
+public class EgressApiLoanDTO {
+    /**
+     * 是否撞库
+     */
+    private String checkinto;
+    /**
+     * 商户ID
+     */
+    private Long merchantId;
+    /**
+     * 集成系统
+     */
+    private String integrationSystem;
+}

+ 22 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/FunctionDTO.java

@@ -0,0 +1,22 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-28 16:28
+ * @description: 函数数据传输对象
+ **/
+@Data
+public class FunctionDTO {
+    /**
+     * 函数编码
+     */
+    private String code;
+    /**
+     * 额外参数
+     */
+    private List<ValueObjectDTO> extParams;
+}

+ 14 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/FunctionObjectDTO.java

@@ -0,0 +1,14 @@
+package com.hrsk.cloud.op.client.dto;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 18:29
+ * @description: 函数对象DTO
+ **/
+public class FunctionObjectDTO {
+    private String code;
+
+    private List<ValueObjectDTO> params;
+}

+ 38 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/HttpConfigDTO.java

@@ -0,0 +1,38 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 17:46
+ * @description: http配置
+ **/
+@Data
+public class HttpConfigDTO {
+    /**
+     * 地址
+     */
+    private String url;
+    /**
+     * 方法
+     */
+    private String method;
+    /**
+     * http header
+     */
+    private List<ValueObjectDTO> header;
+    /**
+     * path
+     */
+    private List<ValueObjectDTO> path;
+    /**
+     * query
+     */
+    private List<ValueObjectDTO> query;
+    /**
+     * 请求体body
+     */
+    private String body;
+}

+ 24 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/HttpConfigValueDTO.java

@@ -0,0 +1,24 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 13:28
+ * @description:
+ **/
+@Data
+public class HttpConfigValueDTO {
+    /**
+     * 名称
+     */
+    private String name;
+    /**
+     * 值
+     */
+    private String value;
+    /**
+     * 备注
+     */
+    private String memo;
+}

+ 28 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/MerchantDTO.java

@@ -0,0 +1,28 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-28 16:02
+ * @description:
+ **/
+@Data
+public class MerchantDTO {
+    /**
+     * ID
+     */
+    private Long id;
+    /**
+     * 商户名称
+     */
+    private String merchantName;
+
+    public MerchantDTO() {
+    }
+
+    public MerchantDTO(Long id, String merchantName) {
+        this.id = id;
+        this.merchantName = merchantName;
+    }
+}

+ 36 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/Operator.java

@@ -0,0 +1,36 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-22 09:58
+ * @description: 操作人
+ **/
+@Data
+public class Operator {
+    /**
+     * 构造函数
+     */
+    public Operator() {
+    }
+
+    /**
+     * 构造函数
+     * @param id ID
+     * @param username 用户名
+     */
+    public Operator(Long id, String username) {
+        this.id = id;
+        this.username = username;
+    }
+
+    /**
+     * ID
+     */
+    private Long id;
+    /**
+     * 姓名
+     */
+    private String username;
+}

+ 42 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/SysDeptDTO.java

@@ -0,0 +1,42 @@
+package com.hrsk.cloud.op.client.dto;
+
+import com.hrsk.pangu.dto.DTO;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-18 15:20
+ * @description: 系统部门
+ **/
+@Data
+public class SysDeptDTO extends DTO {
+    /** 部门ID */
+    private Long id;
+
+    /** 父部门ID */
+    private Long parentId;
+
+    /** 祖级列表 */
+    private String ancestors;
+
+    /** 部门名称 */
+    private String deptName;
+
+    /** 显示顺序 */
+    private Integer orderNum;
+
+    /** 负责人 */
+    private String leader;
+
+    /** 联系电话 */
+    private String phone;
+
+    /** 邮箱 */
+    private String email;
+
+    /** 部门状态 */
+    private String status;
+
+    /** 父部门名称 */
+    private String parentName;
+}

+ 33 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/SysRoleDTO.java

@@ -0,0 +1,33 @@
+package com.hrsk.cloud.op.client.dto;
+
+import com.hrsk.pangu.dto.DTO;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-18 15:20
+ * @description: 系统角色
+ **/
+@Data
+public class SysRoleDTO extends DTO {
+    /** 角色ID */
+    private Long id;
+
+    /** 角色名称 */
+    private String roleName;
+
+    /** 角色权限 */
+    private String roleKey;
+
+    /** 角色排序 */
+    private Integer roleSort;
+
+    /** 数据范围 */
+    private String dataScope;
+
+    /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
+    private boolean menuCheckStrictly;
+
+    /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
+    private boolean deptCheckStrictly;
+}

+ 29 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/SysUserDTO.java

@@ -0,0 +1,29 @@
+package com.hrsk.cloud.op.client.dto;
+
+import com.hrsk.pangu.dto.DTO;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 17:05
+ * @description: 登录用户
+ **/
+@Data
+public class SysUserDTO extends DTO {
+    /**
+     * 主键
+     */
+    private Long id;
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 状态
+     */
+    private String status;
+}

+ 30 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/ValueObjectDTO.java

@@ -0,0 +1,30 @@
+package com.hrsk.cloud.op.client.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 18:25
+ * @description:
+ **/
+@Data
+public class ValueObjectDTO {
+    /**
+     * KEY
+     */
+    private String key;
+    /**
+     * value值
+     */
+    private String value;
+    /**
+     * 描述
+     */
+    private String memo;
+    /**
+     * 函数
+     */
+    private List<FunctionDTO> functions;
+}

+ 21 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiEndpointQry.java

@@ -0,0 +1,21 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import com.hrsk.pangu.dto.AntDQuery;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-29 14:05
+ * @description: 接出API通道查询
+ **/
+@Data
+public class EgressApiEndpointQry extends AntDQuery {
+    /**
+     * API名称
+     */
+    private String apiName;
+    /**
+     * 通道编码
+     */
+    private String endpointCode;
+}

+ 41 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiEndpointUpsertCmd.java

@@ -0,0 +1,41 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 11:38
+ * @description: api endpoint 创建CMD
+ **/
+@Data
+public class EgressApiEndpointUpsertCmd {
+    /**
+     * 主键
+     */
+    private Long id;
+    /**
+     * API ID
+     */
+    private Long apiId;
+    /**
+     * 通道名称
+     */
+    private String endpointName;
+    /**
+     * 通道编码
+     */
+    private String endpointCode;
+    /**
+     * 集成方式
+     */
+    private String integrationMode;
+    /**
+     * http请求配置
+     */
+    private String requestConfig;
+    /**
+     * http响应配置
+     */
+    private String responseConfig;
+
+}

+ 35 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiLoanCrtCmd.java

@@ -0,0 +1,35 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import com.hrsk.cloud.op.client.dto.EgressApiLoanDTO;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 17:34
+ * @description: 助贷接出API创建CMD
+ **/
+@Data
+public class EgressApiLoanCrtCmd {
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * API表主键
+     */
+    private Long apiId;
+    /**
+     * 助贷API信息
+     */
+    private EgressApiLoanDTO apiLoanInfo;
+    /**
+     * 撞库通道
+     */
+    private EgressApiEndpointUpsertCmd checkinto;
+    /**
+     * 申请通道
+     */
+    private EgressApiEndpointUpsertCmd apply;
+
+}

+ 13 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiLoanQry.java

@@ -0,0 +1,13 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-11-01 17:30
+ * @description: 接出API助贷查询 CMD
+ **/
+@Data
+public class EgressApiLoanQry {
+    private Long apiId;
+}

+ 26 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiQry.java

@@ -0,0 +1,26 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import com.hrsk.pangu.dto.AntDQuery;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 15:02
+ * @description:接出API查询CMD
+ **/
+@Data
+public class EgressApiQry extends AntDQuery {
+    /**
+     * API名称
+     */
+    private String apiName;
+    /**
+     * API编码
+     */
+    private String apiCode;
+    /**
+     * 状态
+     */
+    private String status;
+
+}

+ 41 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/EgressApiUpsertCmd.java

@@ -0,0 +1,41 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import com.hrsk.pangu.dto.Command;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 14:40
+ * @description: 接出API DTO
+ **/
+@Data
+public class EgressApiUpsertCmd extends Command {
+    /**
+     * ID
+     */
+    private Long id;
+    /**
+     * API名称
+     */
+    private String apiName;
+    /**
+     * API编码
+     */
+    private String apiCode;
+    /**
+     * API类型
+     */
+    private String apiType;
+    /**
+     * 技术负责人
+     */
+    private Long techOwnerUserid;
+    /**
+     * 技术负责人名称
+     */
+    private String techOwnerUsername;
+    /**
+     * 备注
+     */
+    private String memo;
+}

+ 18 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/SysUserQry.java

@@ -0,0 +1,18 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import lombok.Data;
+
+import javax.management.Query;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 17:58
+ * @description: 系统用户查询CMD
+ **/
+@Data
+public class SysUserQry extends Query {
+    /**
+     * 用户名称
+     */
+    private String username;
+}

+ 30 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/UserLoginCmd.java

@@ -0,0 +1,30 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import com.hrsk.pangu.dto.Command;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-15 15:47
+ * @description: 用户登录CMD
+ **/
+@Data
+public class UserLoginCmd extends Command {
+    /**
+     * 验证码
+     */
+    private String captcha;
+    /**
+     * 验证码的UUID
+     */
+    private String uuid;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+
+}

+ 17 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/dto/command/UserOtpLoginCmd.java

@@ -0,0 +1,17 @@
+package com.hrsk.cloud.op.client.dto.command;
+
+import com.hrsk.pangu.dto.Command;
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 15:56
+ * @description: 用户OTP登录CMD
+ **/
+@Data
+public class UserOtpLoginCmd extends Command {
+    /**
+     * otp验证码
+     */
+    private String code;
+}

+ 1 - 0
op-admin-server-client/src/main/java/com/hrsk/cloud/op/client/package-info.java

@@ -0,0 +1 @@
+package com.hrsk.cloud.op.client;

+ 36 - 0
op-admin-server-domain/pom.xml

@@ -0,0 +1,36 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.hrsk.cloud</groupId>
+        <artifactId>op-admin-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>op-admin-server-domain</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.hrsk.pangu</groupId>
+            <artifactId>pangu-component-domain-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.hrsk.pangu</groupId>
+            <artifactId>pangu-component-tool</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 53 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/common/AuthErrorCodeEnum.java

@@ -0,0 +1,53 @@
+package com.hrsk.cloud.op.domain.common;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-15 10:10
+ * @description: 验证类错误码枚举
+ **/
+public enum AuthErrorCodeEnum {
+
+    ACCESS_DENIED("100009","您没有当前操作权限!"),
+    CAPTCHA_NOT_MATCH("100008","验证码错误!"),
+    NOT_LOGIN("100007","尚未登录,请先登录!"),
+    OTP_NOT_MATCH("100006","验证码错误!"),
+    USER_PASSWORD_RETRY_LIMIT_EXCEED("100005","用户密码重试超过最大次数!"),
+    USER_HAS_DEACTIVATED("100004","用户已经被停用!"),
+    USER_HAS_DELETED("100003","用户已经被删除!"),
+    PASSWORD_NOT_MATCH("100002","用户名密码不匹配!"),
+    USER_NOT_FOUND("100001","用户不存在!");
+
+    private String code;
+
+    private String message;
+    /**
+     * 前缀
+     */
+    private static final String PREFIX = "auth-";
+
+    /**
+     * 构造函数
+     * @param code
+     * @param message
+     */
+    AuthErrorCodeEnum(String code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    /**
+     * 获取编码
+     * @return 编码
+     */
+    public String getCode() {
+        return PREFIX + code;
+    }
+
+    /**
+     * 获取消息
+     * @return 返回消息
+     */
+    public String getMessage() {
+        return message;
+    }
+}

+ 49 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/common/EgressApiErrorCodeEnum.java

@@ -0,0 +1,49 @@
+package com.hrsk.cloud.op.domain.common;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-22 11:16
+ * @description: 接出API错误编码枚举
+ **/
+public enum EgressApiErrorCodeEnum {
+    API_NOT_EXIST("100006","api不存在!"),
+    API_ENDPOINT_CRT_FAIL("100005","api通道创建失败!"),
+    API_STATUS_CAN_NOT_DELETE("100004","当前状态API无法删除!"),
+    API_CODE_IS_DUPLICATE("100003","api编码已存在!"),
+    API_NAME_IS_DUPLICATE("100002","api名称已存在!"),
+    TECH_OWNER_NOT_EXIST("100001","技术负责人不存在!");
+
+    private String code;
+
+    private String message;
+    /**
+     * 前缀
+     */
+    private static final String PREFIX = "egress-";
+
+    /**
+     * 构造函数
+     * @param code
+     * @param message
+     */
+    EgressApiErrorCodeEnum(String code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    /**
+     * 获取编码
+     * @return 编码
+     */
+    public String getCode() {
+        return PREFIX + code;
+    }
+
+    /**
+     * 获取消息
+     * @return 返回消息
+     */
+    public String getMessage() {
+        return message;
+    }
+}

+ 30 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/common/ErrorCodeEnum.java

@@ -0,0 +1,30 @@
+package com.hrsk.cloud.op.domain.common;
+
+import lombok.Getter;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-15 10:10
+ * @description: 错误码枚举
+ **/
+@Getter
+public enum ErrorCodeEnum {
+    OK("000000","操作成功!"),
+    FAIL("-999999","操作失败!");
+
+    private String code;
+
+    private String message;
+
+    /**
+     * 构造函数
+     * @param code 编码
+     * @param message 消息
+     */
+    ErrorCodeEnum(String code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+
+}

+ 32 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/egress/EgressApiEndpointIntegrationModeEnum.java

@@ -0,0 +1,32 @@
+package com.hrsk.cloud.op.domain.egress;
+
+import lombok.Getter;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-11-04 10:43
+ * @description: 通道接出方式
+ **/
+@Getter
+public enum EgressApiEndpointIntegrationModeEnum {
+    CUSTOMIZE("customize","自定义代码模式"),
+    GUIDE("guide","向导模式");
+    /**
+     * 编码
+     */
+    private String code;
+    /**
+     * 描述
+     */
+    private String desc;
+
+    /**
+     * 构造函数
+     * @param code
+     * @param desc
+     */
+    EgressApiEndpointIntegrationModeEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}

+ 34 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/egress/EgressApiStatusEnum.java

@@ -0,0 +1,34 @@
+package com.hrsk.cloud.op.domain.egress;
+
+import lombok.Getter;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-22 10:49
+ * @description: 接出API状态枚举
+ **/
+@Getter
+public enum EgressApiStatusEnum {
+    DRAFT("draft","草稿"),
+    OK("ok","启用"),
+    DEACTIVATE("deactivate","停用"),
+    DELETE("delete","删除");
+    /**
+     * 编码
+     */
+    private String code;
+    /**
+     * 描述
+     */
+    private String desc;
+
+    /**
+     * 构造函数
+     * @param code 编码
+     * @param desc 描述
+     */
+    EgressApiStatusEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}

+ 1 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/package-info.java

@@ -0,0 +1 @@
+package com.hrsk.cloud.op.domain;

+ 33 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/system/SysUserStatusEnum.java

@@ -0,0 +1,33 @@
+package com.hrsk.cloud.op.domain.system;
+
+import lombok.Getter;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-15 14:01
+ * @description: 用户状态枚举
+ **/
+@Getter
+public enum SysUserStatusEnum {
+    OK("ok","正常"),
+    DELETE("delete","已删除"),
+    DEACTIVATE("deactivate","已停用");
+    /**
+     * 编码
+     */
+    private String code;
+    /**
+     * 描述
+     */
+    private String desc;
+
+    /**
+     * 构造函数
+     * @param code 编码
+     * @param desc 描述
+     */
+    SysUserStatusEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}

+ 1 - 0
op-admin-server-domain/src/main/java/com/hrsk/cloud/op/domain/system/package-info.java

@@ -0,0 +1 @@
+package com.hrsk.cloud.op.domain.system;

+ 89 - 0
op-admin-server-infrastructure/pom.xml

@@ -0,0 +1,89 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.hrsk.cloud</groupId>
+        <artifactId>op-admin-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>op-admin-server-infrastructure</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.hrsk.cloud</groupId>
+            <artifactId>op-admin-server-domain</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.hrsk.cloud</groupId>
+            <artifactId>op-admin-server-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <!-- redis 缓存操作 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!-- 验证码 -->
+        <dependency>
+            <groupId>pro.fessional</groupId>
+            <artifactId>kaptcha</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>servlet-api</artifactId>
+                    <groupId>javax.servlet</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 51 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/client/EgressServiceClient.java

@@ -0,0 +1,51 @@
+package com.hrsk.cloud.op.infrastructure.client;
+
+import com.alibaba.fastjson2.JSON;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointUpsertCmd;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 20:48
+ * @description: 接出服务客户端
+ **/
+@Slf4j
+@Component
+public class EgressServiceClient {
+
+    @Value(value = "${egress.service.url}")
+    private String egServiceUrl;
+    private final OkHttpClient client = new OkHttpClient();
+
+    public static final MediaType CONTENT_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
+
+    /**
+     * 批量创建API通道
+     * @param egressApiEndpointUpsertCmds cmds
+     */
+    public boolean batchCreateApiEndpoint(List<EgressApiEndpointUpsertCmd> egressApiEndpointUpsertCmds) throws IOException {
+        String requestBody = JSON.toJSONString(egressApiEndpointUpsertCmds);
+        log.info("开始http请求(post),url:{},payload:{}", egServiceUrl+"/api/endpoint/batchCreate", requestBody);
+        RequestBody body = RequestBody.create(requestBody, CONTENT_TYPE_JSON);
+        Request request = new Request.Builder()
+                .url(egServiceUrl)
+                .post(body)
+                .build();
+        try (Response response = client.newCall(request).execute()) {
+            if(response.body() == null){
+                return false;
+            }
+            return JSON.parseObject(response.body().toString(), com.hrsk.pangu.dto.Response.class).isSuccess();
+        }
+    }
+}

+ 134 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/Base32.java

@@ -0,0 +1,134 @@
+package com.hrsk.cloud.op.infrastructure.common;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 16:24
+ * @description: base32
+ **/
+public class Base32 {
+    private static final Base32 INSTANCE =
+            new Base32("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"); // RFC 4648/3548
+
+    static Base32 getInstance() {
+        return INSTANCE;
+    }
+
+    private String ALPHABET;
+    private char[] DIGITS;
+    private int MASK;
+    private int SHIFT;
+    private HashMap<Character, Integer> CHAR_MAP;
+
+    static final String SEPARATOR = "-";
+
+    protected Base32(String alphabet) {
+        this.ALPHABET = alphabet;
+        DIGITS = ALPHABET.toCharArray();
+        MASK = DIGITS.length - 1;
+        SHIFT = Integer.numberOfTrailingZeros(DIGITS.length);
+        CHAR_MAP = new HashMap<Character, Integer>();
+        for (int i = 0; i < DIGITS.length; i++) {
+            CHAR_MAP.put(DIGITS[i], i);
+        }
+    }
+
+    public static byte[] decode(String encoded) throws DecodingException {
+        return getInstance().decodeInternal(encoded);
+    }
+
+    protected byte[] decodeInternal(String encoded) throws DecodingException {
+        // Remove whitespace and separators
+        encoded = encoded.trim().replaceAll(SEPARATOR, "").replaceAll(" ", "");
+
+        // Remove padding. Note: the padding is used as hint to determine how many
+        // bits to decode from the last incomplete chunk (which is commented out
+        // below, so this may have been wrong to start with).
+        encoded = encoded.replaceFirst("[=]*$", "");
+
+        // Canonicalize to all upper case
+        encoded = encoded.toUpperCase(Locale.US);
+        if (encoded.length() == 0) {
+            return new byte[0];
+        }
+        int encodedLength = encoded.length();
+        int outLength = encodedLength * SHIFT / 8;
+        byte[] result = new byte[outLength];
+        int buffer = 0;
+        int next = 0;
+        int bitsLeft = 0;
+        for (char c : encoded.toCharArray()) {
+            if (!CHAR_MAP.containsKey(c)) {
+                throw new DecodingException("Illegal character: " + c);
+            }
+            buffer <<= SHIFT;
+            buffer |= CHAR_MAP.get(c) & MASK;
+            bitsLeft += SHIFT;
+            if (bitsLeft >= 8) {
+                result[next++] = (byte) (buffer >> (bitsLeft - 8));
+                bitsLeft -= 8;
+            }
+        }
+        // We'll ignore leftover bits for now.
+        //
+        // if (next != outLength || bitsLeft >= SHIFT) {
+        //  throw new DecodingException("Bits left: " + bitsLeft);
+        // }
+        return result;
+    }
+
+    public static String encode(byte[] data) {
+        return getInstance().encodeInternal(data);
+    }
+
+    protected String encodeInternal(byte[] data) {
+        if (data.length == 0) {
+            return "";
+        }
+
+        // SHIFT is the number of bits per output character, so the length of the
+        // output is the length of the input multiplied by 8/SHIFT, rounded up.
+        if (data.length >= (1 << 28)) {
+            // The computation below will fail, so don't do it.
+            throw new IllegalArgumentException();
+        }
+
+        int outputLength = (data.length * 8 + SHIFT - 1) / SHIFT;
+        StringBuilder result = new StringBuilder(outputLength);
+
+        int buffer = data[0];
+        int next = 1;
+        int bitsLeft = 8;
+        while (bitsLeft > 0 || next < data.length) {
+            if (bitsLeft < SHIFT) {
+                if (next < data.length) {
+                    buffer <<= 8;
+                    buffer |= (data[next++] & 0xff);
+                    bitsLeft += 8;
+                } else {
+                    int pad = SHIFT - bitsLeft;
+                    buffer <<= pad;
+                    bitsLeft += pad;
+                }
+            }
+            int index = MASK & (buffer >> (bitsLeft - SHIFT));
+            bitsLeft -= SHIFT;
+            result.append(DIGITS[index]);
+        }
+        return result.toString();
+    }
+
+    @Override
+    // enforce that this class is a singleton
+    public Object clone() throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+    public static class DecodingException extends Exception {
+        public DecodingException(String message) {
+            super(message);
+        }
+    }
+}

+ 291 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/Base64.java

@@ -0,0 +1,291 @@
+package com.hrsk.cloud.op.infrastructure.common;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 11:27
+ * @description: base64 copy from ruoyi
+ **/
+public final class Base64
+{
+    static private final int     BASELENGTH           = 128;
+    static private final int     LOOKUPLENGTH         = 64;
+    static private final int     TWENTYFOURBITGROUP   = 24;
+    static private final int     EIGHTBIT             = 8;
+    static private final int     SIXTEENBIT           = 16;
+    static private final int     FOURBYTE             = 4;
+    static private final int     SIGN                 = -128;
+    static private final char    PAD                  = '=';
+    static final private byte[]  base64Alphabet       = new byte[BASELENGTH];
+    static final private char[]  lookUpBase64Alphabet = new char[LOOKUPLENGTH];
+
+    static
+    {
+        for (int i = 0; i < BASELENGTH; ++i)
+        {
+            base64Alphabet[i] = -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+
+        for (int i = '9'; i >= '0'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+
+        for (int i = 0; i <= 25; i++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('A' + i);
+        }
+
+        for (int i = 26, j = 0; i <= 51; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('a' + j);
+        }
+
+        for (int i = 52, j = 0; i <= 61; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('0' + j);
+        }
+        lookUpBase64Alphabet[62] = (char) '+';
+        lookUpBase64Alphabet[63] = (char) '/';
+    }
+
+    private static boolean isWhiteSpace(char octect)
+    {
+        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
+    }
+
+    private static boolean isPad(char octect)
+    {
+        return (octect == PAD);
+    }
+
+    private static boolean isData(char octect)
+    {
+        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
+    }
+
+    /**
+     * Encodes hex octects into Base64
+     *
+     * @param binaryData Array containing binaryData
+     * @return Encoded Base64 array
+     */
+    public static String encode(byte[] binaryData)
+    {
+        if (binaryData == null)
+        {
+            return null;
+        }
+
+        int lengthDataBits = binaryData.length * EIGHTBIT;
+        if (lengthDataBits == 0)
+        {
+            return "";
+        }
+
+        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
+        char encodedData[] = null;
+
+        encodedData = new char[numberQuartet * 4];
+
+        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+
+        for (int i = 0; i < numberTriplets; i++)
+        {
+            b1 = binaryData[dataIndex++];
+            b2 = binaryData[dataIndex++];
+            b3 = binaryData[dataIndex++];
+
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
+        }
+
+        // form integral number of 6-bit groups
+        if (fewerThan24bits == EIGHTBIT)
+        {
+            b1 = binaryData[dataIndex];
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
+            encodedData[encodedIndex++] = PAD;
+            encodedData[encodedIndex++] = PAD;
+        }
+        else if (fewerThan24bits == SIXTEENBIT)
+        {
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
+            encodedData[encodedIndex++] = PAD;
+        }
+        return new String(encodedData);
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param encoded string containing Base64 data
+     * @return Array containind decoded data.
+     */
+    public static byte[] decode(String encoded)
+    {
+        if (encoded == null)
+        {
+            return null;
+        }
+
+        char[] base64Data = encoded.toCharArray();
+        // remove white spaces
+        int len = removeWhiteSpace(base64Data);
+
+        if (len % FOURBYTE != 0)
+        {
+            return null;// should be divisible by four
+        }
+
+        int numberQuadruple = (len / FOURBYTE);
+
+        if (numberQuadruple == 0)
+        {
+            return new byte[0];
+        }
+
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
+        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
+
+        int i = 0;
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        decodedData = new byte[(numberQuadruple) * 3];
+
+        for (; i < numberQuadruple - 1; i++)
+        {
+
+            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
+                    || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++])))
+            {
+                return null;
+            } // if found "no data" just return null
+
+            b1 = base64Alphabet[d1];
+            b2 = base64Alphabet[d2];
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+        }
+
+        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])))
+        {
+            return null;// if found "no data" just return null
+        }
+
+        b1 = base64Alphabet[d1];
+        b2 = base64Alphabet[d2];
+
+        d3 = base64Data[dataIndex++];
+        d4 = base64Data[dataIndex++];
+        if (!isData((d3)) || !isData((d4)))
+        {// Check if they are PAD characters
+            if (isPad(d3) && isPad(d4))
+            {
+                if ((b2 & 0xf) != 0)// last 4 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 1];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                return tmp;
+            }
+            else if (!isPad(d3) && isPad(d4))
+            {
+                b3 = base64Alphabet[d3];
+                if ((b3 & 0x3) != 0)// last 2 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 2];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                return tmp;
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        { // No PAD e.g 3cQl
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+
+        }
+        return decodedData;
+    }
+
+    /**
+     * remove WhiteSpace from MIME containing encoded Base64 data.
+     *
+     * @param data the byte array of base64 data (with WS)
+     * @return the new length
+     */
+    private static int removeWhiteSpace(char[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        // count characters that's not whitespace
+        int newSize = 0;
+        int len = data.length;
+        for (int i = 0; i < len; i++)
+        {
+            if (!isWhiteSpace(data[i]))
+            {
+                data[newSize++] = data[i];
+            }
+        }
+        return newSize;
+    }
+}

+ 31 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/CaptchaTypeEnum.java

@@ -0,0 +1,31 @@
+package com.hrsk.cloud.op.infrastructure.common;
+
+import lombok.Getter;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 11:27
+ * @description: 验证码类型枚举
+ **/
+@Getter
+public enum CaptchaTypeEnum {
+    IMAGE("IMAGE","图片验证码");
+    /**
+     * 编码
+     */
+    private String code;
+    /**
+     * 值
+     */
+    private String value;
+
+    /**
+     * 构造函数
+     * @param code 编码
+     * @param value 值
+     */
+    CaptchaTypeEnum(String code, String value) {
+        this.code = code;
+        this.value = value;
+    }
+}

+ 15 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/Constants.java

@@ -0,0 +1,15 @@
+package com.hrsk.cloud.op.infrastructure.common;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-14 17:31
+ * @description:
+ **/
+public class Constants {
+    /**
+     * 单位10分钟
+     */
+    public static final Long UNIT_MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
+
+    public static final long UNIT_MILLIS_SECOND = 1000;
+}

+ 61 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/HexEncoding.java

@@ -0,0 +1,61 @@
+package com.hrsk.cloud.op.infrastructure.common;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 16:23
+ * @description: hex
+ **/
+public class HexEncoding {
+
+    /** Hidden constructor to prevent instantiation. */
+    private HexEncoding() {}
+
+    private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
+
+    /**
+     * Encodes the provided data as a hexadecimal string.
+     */
+    public static String encode(byte[] data) {
+        StringBuilder result = new StringBuilder(data.length * 2);
+        for (byte b : data) {
+            result.append(HEX_DIGITS[(b >>> 4) & 0x0f]);
+            result.append(HEX_DIGITS[b & 0x0f]);
+        }
+        return result.toString();
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into an array of bytes.
+     */
+    public static byte[] decode(String encoded) {
+        // IMPLEMENTATION NOTE: Special care is taken to permit odd number of hexadecimal digits.
+        int resultLengthBytes = (encoded.length() + 1) / 2;
+        byte[] result = new byte[resultLengthBytes];
+        int resultOffset = 0;
+        int encodedCharOffset = 0;
+        if ((encoded.length() % 2) != 0) {
+            // Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
+            result[resultOffset++] = (byte) getHexadecimalDigitValue(encoded.charAt(encodedCharOffset));
+            encodedCharOffset++;
+        }
+        for (int len = encoded.length(); encodedCharOffset < len; encodedCharOffset += 2) {
+            result[resultOffset++] = (byte)
+                    ((getHexadecimalDigitValue(encoded.charAt(encodedCharOffset)) << 4)
+                            | getHexadecimalDigitValue(encoded.charAt(encodedCharOffset + 1)));
+        }
+        return result;
+    }
+
+    private static int getHexadecimalDigitValue(char c) {
+        if ((c >= 'a') && (c <= 'f')) {
+            return (c - 'a') + 0x0a;
+        } else if ((c >= 'A') && (c <= 'F')) {
+            return (c - 'A') + 0x0a;
+        } else if ((c >= '0') && (c <= '9')) {
+            return c - '0';
+        } else {
+            throw new IllegalArgumentException(
+                    "Invalid hexadecimal digit at position : '" + c + "' (0x" + Integer.toHexString(c) + ")");
+        }
+    }
+}

+ 65 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/QrUtils.java

@@ -0,0 +1,65 @@
+package com.hrsk.cloud.op.infrastructure.common;
+
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 16:59
+ * @description: 二维码工具类
+ **/
+public class QrUtils {
+    private QrUtils() {}
+    /**
+     * @description: 生成一个普通的黑白二维码
+     * @param content 二维码内容
+     * @param width 生成图片宽度
+     * @param height 生成图片高度
+     * @return 二维码图片字节流
+     **/
+    public static byte[] draw(String content, int width, int height) throws WriterException, IOException {
+        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+        BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, width, height, new HashMap() {
+            private static final long serialVersionUID = 1L;
+            {
+                put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+                put(EncodeHintType.CHARACTER_SET, "UTF-8");
+                put(EncodeHintType.MARGIN, 0);
+            }
+        });
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        // 开始利用二维码数据图片,分别设为黑(0xFFFFFFFF)白(0xFF000000)两色
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
+            }
+        }
+        image.flush();
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ImageIO.write(image, "png", outputStream);
+        return outputStream.toByteArray();
+    }
+
+    public static void main(String[] args) {
+        try {
+            String secret = "otpauth://totp/admin?secret=e867dc42-46f8-421d-8122-6c9d1d6fb820&issuer=hrsk-open-platform";
+            System.out.println(secret);
+            System.out.println(Base64.encode(QrUtils.draw(secret,200,200)));
+        } catch (WriterException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 48 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,48 @@
+package com.hrsk.cloud.op.infrastructure.common.config;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.filter.Filter;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.nio.charset.Charset;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 19:34
+ * @description: copy from ruoyi
+ **/
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
+{
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter( "org.springframework", "com.hrsk" );
+
+    private Class<T> clazz;
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null)
+        {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
+    }
+}

+ 56 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/KaptchaTextCreator.java

@@ -0,0 +1,56 @@
+package com.hrsk.cloud.op.infrastructure.common.config;
+
+import com.google.code.kaptcha.text.impl.DefaultTextCreator;
+
+import java.util.Random;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-15 19:49
+ * @description: 验证码生成器
+ **/
+public class KaptchaTextCreator extends DefaultTextCreator {
+    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
+
+    @Override
+    public String getText() {
+        Integer result = 0;
+        Random random = new Random();
+        int x = random.nextInt(10);
+        int y = random.nextInt(10);
+        StringBuilder suChinese = new StringBuilder();
+        int randomoperands = random.nextInt(3);
+        if (randomoperands == 0) {
+            result = x * y;
+            suChinese.append(CNUMBERS[x]);
+            suChinese.append("*");
+            suChinese.append(CNUMBERS[y]);
+        } else if (randomoperands == 1) {
+            if ((x != 0) && y % x == 0) {
+                result = y / x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("/");
+                suChinese.append(CNUMBERS[x]);
+            } else {
+                result = x + y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("+");
+                suChinese.append(CNUMBERS[y]);
+            }
+        } else {
+            if (x >= y) {
+                result = x - y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[y]);
+            } else {
+                result = y - x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[x]);
+            }
+        }
+        suChinese.append("=?@" + result);
+        return suChinese.toString();
+    }
+}

+ 34 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/RedisConfig.java

@@ -0,0 +1,34 @@
+package com.hrsk.cloud.op.infrastructure.common.config;
+
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-16 19:34
+ * @description: redis配置
+ **/
+
+@Configuration
+@EnableCaching
+public class RedisConfig {
+    @Bean
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+        template.afterPropertiesSet();
+        return template;
+    }
+
+}

+ 56 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/common/config/SystemConfig.java

@@ -0,0 +1,56 @@
+package com.hrsk.cloud.op.infrastructure.common.config;
+
+import com.google.code.kaptcha.impl.DefaultKaptcha;
+import com.google.code.kaptcha.util.Config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Properties;
+
+import static com.google.code.kaptcha.Constants.*;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-11 11:27
+ * @description: 系统配置 copy from ruoyi
+ **/
+@Configuration
+public class SystemConfig {
+
+    @Bean(name = "captchaProducer")
+    public DefaultKaptcha getKaptchaBean(){
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "no");
+        // 边框颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "100");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "35");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "20");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
+        // 验证码文本生成器
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.hrsk.cloud.op.infrastructure.common.config.KaptchaTextCreator");
+        // 验证码文本字符间距 默认为2
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "4");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 验证码噪点颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
+        // 干扰实现类
+        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+}

+ 1 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/package-info.java

@@ -0,0 +1 @@
+package com.hrsk.cloud.op.infrastructure;

+ 23 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/dao/EgressApiEndpointDao.java

@@ -0,0 +1,23 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.dao;
+
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.EgressApiEndpointDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-29 17:01
+ * @description: egress_api_endpoint 表DAO
+ **/
+@Repository
+@Mapper
+public interface EgressApiEndpointDao {
+    /**
+     * 查询egress_api_endpoint
+     * @param egressApiEndpointDO 条件
+     * @return egress_api_endpoint列表
+     */
+    List<EgressApiEndpointDO> selectEgressApiEndpoint(EgressApiEndpointDO egressApiEndpointDO);
+}

+ 28 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/dao/SysMenuDao.java

@@ -0,0 +1,28 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.dao;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-17 10:56
+ * @description: 系统菜单DAO
+ **/
+@Repository
+@Mapper
+public interface SysMenuDao {
+    /**
+     * 通过ROLEID获取菜单权限字符串
+     * @param roleId 角色ID
+     * @return 菜单权限字符串
+     */
+    List<String> selectMenuPermsByRoleId(Long roleId);
+    /**
+     * 通过USERID获取菜单权限字符串
+     * @param userId 用户ID
+     * @return 菜单权限字符串
+     */
+    List<String> selectMenuPermsByUserId(Long userId);
+}

+ 22 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/dao/SysUserDao.java

@@ -0,0 +1,22 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.dao;
+
+import com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity.SysUserDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-17 11:13
+ * @description: 系统用户DAO
+ **/
+@Repository
+@Mapper
+public interface SysUserDao {
+    /**
+     * 通过用户名查询用户
+     * @param userName
+     * @return
+     */
+    SysUserDO selectUserByUserName(String userName);
+}
+

+ 63 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/BaseDO.java

@@ -0,0 +1,63 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-09-03 09:13
+ * @description: 数据库基础数据对象
+ **/
+@Data
+public class BaseDO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    /**
+     * 主键
+     */
+    private Long id;
+    /**
+     * 创建人ID
+     */
+    private Long createUserid;
+    /**
+     * 创建名称
+     */
+    private String createUsername;
+    /**
+     * 创建时间
+     */
+    private Date gmtCreate;
+    /**
+     * 修改人ID
+     */
+    private Long modifyUserid;
+    /**
+     * 修改人名称
+     */
+    private String modifyUsername;
+    /**
+     * 修改时间
+     */
+    private Date gmtModify;
+    /**
+     * 状态
+     */
+    private String status;
+
+//    /**
+//     * 通过用户ID和用户名称创建
+//     * @param username 用户名
+//     * @param userId 用户ID
+//     * @return baseDo
+//     */
+//    private BaseDO from(String username,Long userId){
+//        this.createUserid = userId;
+//        this.createUsername = username;
+//        this.modifyUserid = userId;
+//        this.modifyUsername = username;
+//        return this;
+//    }
+}

+ 75 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiDO.java

@@ -0,0 +1,75 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.hrsk.cloud.op.client.dto.EgressApiDTO;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiUpsertCmd;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-21 14:48
+ * @description: egress_api
+ **/
+@Data
+@TableName("egress_api")
+public class EgressApiDO extends BaseDO{
+    /** ID **/
+    private Long id;
+    /**
+     * API名称
+     */
+    private String apiName;
+    /**
+     * API编码
+     */
+    private String apiCode;
+    /**
+     * API类型
+     */
+    private String apiType;
+    /**
+     * 技术负责人ID
+     */
+    private Long techOwnerUserid;
+    /**
+     * 技术负责人
+     */
+    private String techOwnerUsername;
+
+    /**
+     * 备注
+     */
+    private String memo;
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 创建
+     * @param egressApiUpsertCmd CMD
+     * @param operator 操作人
+     * @return EgressApiDO
+     */
+    public static final EgressApiDO from(EgressApiUpsertCmd egressApiUpsertCmd, Operator operator){
+        EgressApiDO egressApiDO = new EgressApiDO();
+        BeanUtils.copyProperties(egressApiUpsertCmd,egressApiDO);
+        egressApiDO.setCreateUserid(operator.getId());
+        egressApiDO.setCreateUsername(operator.getUsername());
+        egressApiDO.setModifyUserid(operator.getId());
+        egressApiDO.setModifyUsername(operator.getUsername());
+        return egressApiDO;
+    }
+
+    /**
+     * 转换
+     * @return DTO
+     */
+    public EgressApiDTO toDTO(){
+        EgressApiDTO egressApiDTO = new EgressApiDTO();
+        BeanUtils.copyProperties(this,egressApiDTO);
+        return egressApiDTO;
+    }
+}

+ 109 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiEndpointDO.java

@@ -0,0 +1,109 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.hrsk.cloud.op.client.dto.EgressApiEndpointDTO;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiEndpointUpsertCmd;
+import com.hrsk.cloud.op.domain.egress.EgressApiStatusEnum;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 14:25
+ * @description: 表 egress_api_endpoint 数据对象。
+ **/
+@Data
+@TableName("egress_api_endpoint")
+public class EgressApiEndpointDO extends BaseDO{
+
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * egress_api的主键
+     */
+    private Long apiId;
+
+    /**
+     * api名称
+     */
+    @TableField(exist = false)
+    private String apiName;
+
+    /**
+     * API类型
+     */
+    @TableField(exist = false)
+    private String apiType;
+
+    /**
+     * 通道名称
+     */
+    private String endpointName;
+    /**
+     * 通道编码
+     */
+    private String endpointCode;
+    /**
+     * 集成方式
+     */
+    private String integrationMode;
+
+    /**
+     * 请求配置
+     */
+    private String requestConfig;
+
+    /**
+     * 响应配置
+     */
+    private String responseConfig;
+
+    /**
+     * 版本
+     */
+    private String version;
+
+    /**
+     * 备注
+     */
+    private String memo;
+
+    /**
+     * 转化
+     * @param egressApiEndpointUpsertCmd cmd
+     * @param version 版本
+     * @param operator cmd
+     * @return EgressApiEndpointDO
+     */
+    public static EgressApiEndpointDO from(EgressApiEndpointUpsertCmd egressApiEndpointUpsertCmd, String version,Operator operator){
+        EgressApiEndpointDO egressApiEndpoint = new EgressApiEndpointDO();
+        BeanUtils.copyProperties(egressApiEndpointUpsertCmd,egressApiEndpoint);
+        egressApiEndpoint.setRequestConfig(egressApiEndpointUpsertCmd.getRequestConfig());
+        egressApiEndpoint.setResponseConfig(egressApiEndpointUpsertCmd.getResponseConfig());
+        egressApiEndpoint.setCreateUserid(operator.getId());
+        egressApiEndpoint.setCreateUsername(operator.getUsername());
+        egressApiEndpoint.setModifyUserid(operator.getId());
+        egressApiEndpoint.setModifyUsername(operator.getUsername());
+        egressApiEndpoint.setVersion(version);
+        egressApiEndpoint.setStatus(EgressApiStatusEnum.OK.getCode());
+        return egressApiEndpoint;
+    }
+
+    /**
+     * 转DTO
+     * @return
+     */
+    public EgressApiEndpointDTO toDTO(){
+        EgressApiEndpointDTO egressApiEndpoint = new EgressApiEndpointDTO();
+        BeanUtils.copyProperties(this,egressApiEndpoint);
+        egressApiEndpoint.setRequestConfig(requestConfig);
+        egressApiEndpoint.setResponseConfig(responseConfig);
+        return egressApiEndpoint;
+    }
+
+}

+ 83 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiEndpointHistoryDO.java

@@ -0,0 +1,83 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.hrsk.cloud.op.client.dto.EgressApiEndpointDTO;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-23 14:25
+ * @description: 表 egress_api_endpoint 数据对象。
+ **/
+@Data
+@TableName("egress_api_endpoint_history")
+public class EgressApiEndpointHistoryDO extends BaseDO{
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * 通道ID
+     */
+    private Long endpointId;
+
+    /**
+     * egress_api的主键
+     */
+    private Long apiId;
+
+    /**
+     * api名称
+     */
+    private String apiName;
+
+    /**
+     * API类型
+     */
+    private String apiType;
+
+    /**
+     * 通道名称
+     */
+    private String endpointName;
+
+    /**
+     * 请求配置
+     */
+    private byte[] requestConfig;
+
+    /**
+     * 备注
+     */
+    private String memo;
+    /**
+     * 版本
+     */
+    private String version;
+
+    /**
+     * 转化
+     * @param egressApiEndpointDO DO
+     * @return this
+     */
+    public static EgressApiEndpointHistoryDO from(EgressApiEndpointDO egressApiEndpointDO){
+        EgressApiEndpointHistoryDO egressApiEndpointHistory = new EgressApiEndpointHistoryDO();
+        BeanUtils.copyProperties(egressApiEndpointDO,egressApiEndpointHistory);
+        egressApiEndpointHistory.setId(null);
+        egressApiEndpointHistory.setEndpointId(egressApiEndpointDO.getId());
+        egressApiEndpointHistory.setVersion(egressApiEndpointDO.getVersion());
+        return egressApiEndpointHistory;
+    }
+
+    /**
+     * 转DTO
+     * @return
+     */
+    public EgressApiEndpointDTO toDTO(){
+        EgressApiEndpointDTO egressApiEndpointDTO = new EgressApiEndpointDTO();
+        BeanUtils.copyProperties(this,egressApiEndpointDTO);
+        return egressApiEndpointDTO;
+    }
+}

+ 75 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/EgressApiLoanDO.java

@@ -0,0 +1,75 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.hrsk.cloud.op.client.dto.EgressApiLoanDTO;
+import com.hrsk.cloud.op.client.dto.Operator;
+import com.hrsk.cloud.op.client.dto.command.EgressApiLoanQry;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-27 20:16
+ * @description: 助贷API
+ **/
+@Data
+@TableName("egress_api_loan")
+public class EgressApiLoanDO extends BaseDO{
+    /**
+     * ID
+     */
+    private Long id;
+    /**
+     * API主键
+     */
+    private Long apiId;
+    /**
+     * 是否撞库
+     */
+    private String checkinto;
+    /**
+     * 商户ID
+     */
+    private Long merchantId;
+    /**
+     * 集成系统
+     */
+    private String integrationSystem;
+
+    /**
+     * 转化
+     * @param egressApiLoanDTO DRO
+     * @param operator 操作人
+     * @return
+     */
+    public static EgressApiLoanDO from(EgressApiLoanDTO egressApiLoanDTO, Operator operator){
+        EgressApiLoanDO egressApiLoan = new EgressApiLoanDO();
+        BeanUtils.copyProperties(egressApiLoanDTO,egressApiLoan);
+        egressApiLoan.setCreateUserid(operator.getId());
+        egressApiLoan.setCreateUsername(operator.getUsername());
+        egressApiLoan.setModifyUserid(operator.getId());
+        egressApiLoan.setModifyUsername(operator.getUsername());
+        return egressApiLoan;
+    }
+
+    /**
+     * 转化
+     * @param egressApiLoanQry QRT
+     * @return DO
+     */
+    public static EgressApiLoanDO from(EgressApiLoanQry egressApiLoanQry){
+        EgressApiLoanDO egressApiLoanDO = new EgressApiLoanDO();
+        BeanUtils.copyProperties(egressApiLoanQry,egressApiLoanDO);
+        return egressApiLoanDO;
+    }
+
+    /**
+     * 转化
+     * @return DTO
+     */
+    public EgressApiLoanDTO toDTO(){
+        EgressApiLoanDTO egressApiLoanDTO = new EgressApiLoanDTO();
+        BeanUtils.copyProperties(this,egressApiLoanDTO);
+        return egressApiLoanDTO;
+    }
+}

+ 44 - 0
op-admin-server-infrastructure/src/main/java/com/hrsk/cloud/op/infrastructure/repository/database/mysql/entity/SysDeptDO.java

@@ -0,0 +1,44 @@
+package com.hrsk.cloud.op.infrastructure.repository.database.mysql.entity;
+
+import lombok.Data;
+
+/**
+ * @author: bianlanzhou
+ * @create: 2024-10-17 11:23
+ * @description: 系统部门实体
+ **/
+@Data
+public class SysDeptDO extends BaseDO{
+
+    private static final long serialVersionUID = 1L;
+
+    /** 部门ID */
+    private Long id;
+
+    /** 父部门ID */
+    private Long parentId;
+
+    /** 祖级列表 */
+    private String ancestors;
+
+    /** 部门名称 */
+    private String deptName;
+
+    /** 显示顺序 */
+    private Integer orderNum;
+
+    /** 负责人 */
+    private String leader;
+
+    /** 联系电话 */
+    private String phone;
+
+    /** 邮箱 */
+    private String email;
+
+    /** 部门状态 */
+    private String status;
+
+    /** 父部门名称 */
+    private String parentName;
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác