supeng 2 months ago
parent
commit
1e0252d02d
71 changed files with 4818 additions and 4 deletions
  1. 4 1
      .gitignore
  2. 2 3
      README.md
  3. 182 0
      pom.xml
  4. 15 0
      risk-control-core/pom.xml
  5. 62 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/aop/RequestLogAspect.java
  6. 35 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/annotation/NoRequestLog.java
  7. 19 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/CacheKeyConstant.java
  8. 40 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/CommonRequest.java
  9. 104 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/CommonResponse.java
  10. 16 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/Constant.java
  11. 85 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/AppTypeEnum.java
  12. 35 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/DataStatusEnum.java
  13. 38 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/ExceptionEnum.java
  14. 32 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/RecommendStatusEnum.java
  15. 62 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/VideoStatusEnum.java
  16. 11 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/CacheException.java
  17. 97 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/CommonException.java
  18. 11 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/DAOException.java
  19. 56 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/HttpServiceException.java
  20. 15 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/StopRetryException.java
  21. 43 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/TimeoutException.java
  22. 67 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/config/RedisTemplateConfig.java
  23. 60 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/config/XxlJobConfig.java
  24. 30 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/dao/generator/MybatisGeneratorMain.java
  25. 111 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/handle/GlobalExceptionHandle.java
  26. 30 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/job/DemoJob.java
  27. 39 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/AuthorInfoDTO.java
  28. 169 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/BaseInfoDTO.java
  29. 9 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/ImageUrlDTO.java
  30. 26 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/PageDTO.java
  31. 29 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/VideoBillBoardItemDTO.java
  32. 41 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/VideoInfoDTO.java
  33. 9 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/VideoUrlDTO.java
  34. 8 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/mq/VideoPortraitMessageDTO.java
  35. 11 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/AuthorInfoParam.java
  36. 18 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/UserPortraitParam.java
  37. 17 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/VideoBillBoardParam.java
  38. 13 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/VideoInfoParam.java
  39. 18 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/VideoPortraitParam.java
  40. 17 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/LoghubService.java
  41. 155 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/ODPSManager.java
  42. 55 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/impl/LoghubServiceImpl.java
  43. 36 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/BinaryOp.java
  44. 49 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Comparison.java
  45. 10 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Expression.java
  46. 83 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Lexer.java
  47. 24 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Parenthesis.java
  48. 111 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Parser.java
  49. 67 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/RuleEvaluator.java
  50. 17 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Token.java
  51. 59 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/TokenType.java
  52. 24 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/VariableParser.java
  53. 260 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/DateUtil.java
  54. 90 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/HttpClientUtil.java
  55. 219 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/HttpPoolClient.java
  56. 930 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/RedisUtil.java
  57. 120 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/RetryUtil.java
  58. 80 0
      risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/UrlUtil.java
  59. 53 0
      risk-control-core/src/main/resources/mybatis-generator-config.xml
  60. 48 0
      risk-control-server/pom.xml
  61. 31 0
      risk-control-server/src/main/java/com/tzld/piaoquan/risk/control/Application.java
  62. 30 0
      risk-control-server/src/main/java/com/tzld/piaoquan/risk/control/controller/IndexController.java
  63. 65 0
      risk-control-server/src/main/resources/application-dev.yml
  64. 64 0
      risk-control-server/src/main/resources/application-pre.yml
  65. 65 0
      risk-control-server/src/main/resources/application-prod.yml
  66. 66 0
      risk-control-server/src/main/resources/application-stress.yml
  67. 66 0
      risk-control-server/src/main/resources/application-test.yml
  68. 26 0
      risk-control-server/src/main/resources/application.yml
  69. 193 0
      risk-control-server/src/main/resources/logback-spring.xml
  70. 13 0
      risk-control-server/src/test/java/com/tzld/piaoquan/risk/control/BaseTest.java
  71. 23 0
      risk-control-server/src/test/java/com/tzld/piaoquan/risk/control/service/DemoServiceTest.java

+ 4 - 1
.gitignore

@@ -11,4 +11,7 @@
 
 # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
 hs_err_pid*
-
+.idea
+datalog
+*/datalog
+*/target

+ 2 - 3
README.md

@@ -1,3 +1,2 @@
-# risk-control
-
-封控系统
+# risk-controls
+风控系统

+ 182 - 0
pom.xml

@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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>
+    <packaging>pom</packaging>
+    <parent>
+        <groupId>com.tzld.commons</groupId>
+        <artifactId>supom</artifactId>
+        <version>1.0.5</version>
+    </parent>
+    <groupId>com.tzld.piaoquan</groupId>
+    <artifactId>risk-control</artifactId>
+    <version>1.0.0</version>
+    <name>risk-control</name>
+    <description>risk-control</description>
+
+    <modules>
+        <module>risk-control-core</module>
+        <module>risk-control-server</module>
+    </modules>
+
+    <properties>
+        <lombok.version>1.18.20</lombok.version>
+        <xxl.job.version>2.2.0</xxl.job.version>
+        <httpclient.version>4.5.13</httpclient.version>
+        <common.lang3.version>3.12.0</common.lang3.version>
+        <spring.kafka.version>2.6.4</spring.kafka.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpclient</artifactId>
+                <version>${httpclient.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>${common.lang3.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.generator</groupId>
+            <artifactId>mybatis-generator-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ctrip.framework.apollo</groupId>
+            <artifactId>apollo-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.tzld.commons</groupId>
+            <artifactId>aliyun-log-spring-boot-starter</artifactId>
+            <version>2.0.0</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>aliyun-log</artifactId>
+                    <groupId>com.aliyun.openservices</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.openservices</groupId>
+            <artifactId>aliyun-log-logback-appender</artifactId>
+            <version>0.1.18</version>
+        </dependency>
+        <!-- xxl-job -->
+        <dependency>
+            <groupId>com.xuxueli</groupId>
+            <artifactId>xxl-job-core</artifactId>
+            <version>2.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.odps</groupId>
+            <artifactId>odps-sdk-core</artifactId>
+            <version>0.27.2-public</version>
+        </dependency>
+        <dependency>
+            <groupId>com.tzld.commons</groupId>
+            <artifactId>aliyun-oss-spring-boot-starter</artifactId>
+            <version>2.0.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-v5-client-spring-boot-starter</artifactId>
+            <version>2.3.1</version>
+        </dependency>
+    </dependencies>
+</project>

+ 15 - 0
risk-control-core/pom.xml

@@ -0,0 +1,15 @@
+<?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.tzld.piaoquan</groupId>
+        <artifactId>risk-control</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>risk-control-core</artifactId>
+    <name>risk-control-core</name>
+    <description>risk-control-core</description>
+
+</project>

+ 62 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/aop/RequestLogAspect.java

@@ -0,0 +1,62 @@
+package com.tzld.piaoquan.risk.control.aop;
+
+import com.alibaba.fastjson.JSON;
+import com.google.common.base.Stopwatch;
+import com.tzld.piaoquan.risk.control.service.LoghubService;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+@Aspect
+@Component
+public class RequestLogAspect {
+
+    @Autowired
+    private LoghubService loghubService;
+
+    @Value("${aliyun.log.project}")
+    private String aliyunLogProject;
+    @Value("${aliyun.log.logstore.request}")
+    private String aliyunLogLogstoreRequest;
+
+    @Pointcut("execution(* com.tzld.piaoquan.risk.control..*Controller.*(..)) && !within(com.tzld.piaoquan.risk.control.controller.IndexController)")
+    public void allController() {
+    }
+
+    @Around("allController()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        // 获取请求信息
+        HttpServletRequest request = Objects.requireNonNull((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+        String uri = request.getRequestURI();
+        String clientIp = request.getRemoteAddr();
+        String queryString = request.getQueryString();
+        String method = joinPoint.getSignature().getName();
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        // 执行目标方法
+        Object result = joinPoint.proceed();
+        // 计算耗时
+        long executionTime = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
+        Map<String, Object> data = new HashMap<>();
+        data.put("uri", uri);
+        data.put("clientIp", clientIp);
+        data.put("method", method);
+        data.put("executionTime", executionTime);
+        data.put("queryString", queryString);
+        data.put("requestBody", JSON.toJSONString(joinPoint.getArgs()));
+        data.put("responseBody", JSON.toJSONString(result));
+        loghubService.asyncSubmitLog(aliyunLogProject, aliyunLogLogstoreRequest, "", data);
+        return result;
+    }
+}

+ 35 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/annotation/NoRequestLog.java

@@ -0,0 +1,35 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.piaoquan.risk.control.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface NoRequestLog {
+}

+ 19 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/CacheKeyConstant.java

@@ -0,0 +1,19 @@
+package com.tzld.piaoquan.risk.control.common.base;
+
+/**
+ * 缓存Key
+ *
+ * @author supeng
+ * @date 2020/08/31
+ */
+public class CacheKeyConstant {
+    public static final long ONE_HOUR_SECOND = 3600;
+    public static final long SIX_HOUR_SECOND = 6 * ONE_HOUR_SECOND;
+    public static final long ONE_HOUR_MILLS = ONE_HOUR_SECOND * 1000;
+    public static final long ONE_DAY_SECOND = ONE_HOUR_SECOND * 24;
+    public static final long ONE_DAY_MILLS = ONE_DAY_SECOND * 1000;
+    public static final long ONE_WEEK_SECOND = ONE_DAY_SECOND * 7;
+    public static final long ONE_WEEK_MILLS = ONE_WEEK_SECOND * 1000;
+    public static final long ONE_MOUTH_SECOND = ONE_DAY_SECOND * 30;
+    public static final long ONE_MOUTH_MILLS = ONE_MOUTH_SECOND * 1000;
+}

+ 40 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/CommonRequest.java

@@ -0,0 +1,40 @@
+package com.tzld.piaoquan.risk.control.common.base;
+
+import com.tzld.piaoquan.risk.control.model.dto.BaseInfoDTO;
+
+/**
+ * 请求参数
+ *
+ * @author supeng
+ */
+public class CommonRequest<T> {
+    /**
+     * 基础信息
+     */
+    BaseInfoDTO baseInfo;
+    /**
+     * 请求参数
+     */
+    T params;
+
+    public BaseInfoDTO getBaseInfo() {
+        return baseInfo;
+    }
+
+    public void setBaseInfo(BaseInfoDTO baseInfo) {
+        this.baseInfo = baseInfo;
+    }
+
+    public T getParams() {
+        return params;
+    }
+
+    public void setParams(T params) {
+        this.params = params;
+    }
+
+    @Override
+    public String toString() {
+        return "CommonRequest{" + "baseInfo=" + baseInfo + ", params=" + params + '}';
+    }
+}

+ 104 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/CommonResponse.java

@@ -0,0 +1,104 @@
+package com.tzld.piaoquan.risk.control.common.base;
+
+import java.util.UUID;
+
+/**
+ * Common Response
+ */
+public class CommonResponse<T> {
+
+    private static final int SUCCESS_CODE = 0;
+    private static final String SUCCESS_MSG = "success";
+
+    /** 返回状态码,0 表示业务成功 */
+    private int code = 0;
+    /** 返回消息 */
+    private String msg = "success";
+    /** 业务成功时返回数据 */
+    private T data;
+    /** 重定向 */
+    private String redirect;
+    /**
+     * 请求ID
+     */
+    private String requestId = UUID.randomUUID().toString();
+
+    public boolean isSuccess() {
+        return this.code == SUCCESS_CODE;
+    }
+
+    public static <T> CommonResponse<T> success() {
+        CommonResponse<T> commonResponse = new CommonResponse<>();
+        commonResponse.setCode(SUCCESS_CODE);
+        commonResponse.setMsg(SUCCESS_MSG);
+        return commonResponse;
+    }
+
+    public static <T> CommonResponse<T> success(T data) {
+        CommonResponse<T> commonResponse = new CommonResponse<>();
+        commonResponse.setCode(SUCCESS_CODE);
+        commonResponse.setMsg(SUCCESS_MSG);
+        commonResponse.setData(data);
+        return commonResponse;
+    }
+
+    public static <T> CommonResponse<T> create() {
+        return create(SUCCESS_CODE, SUCCESS_MSG, null);
+    }
+    
+    public static <T> CommonResponse<T> create(T data) {
+        return create(SUCCESS_CODE, SUCCESS_MSG, data);
+    }
+
+    public static <T> CommonResponse<T> create(int code, String msg) {
+        return create(code, msg, null);
+    }
+
+    public static <T> CommonResponse<T> create(int code, String msg, T data) {
+        CommonResponse<T> commonResponse = new CommonResponse<>();
+        commonResponse.setCode(code);
+        commonResponse.setMsg(msg);
+        commonResponse.setData(data);
+        return commonResponse;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+    public String getRedirect() {
+        return redirect;
+    }
+
+    public void setRedirect(String redirect) {
+        this.redirect = redirect;
+    }
+
+    public String getRequestId() {
+        return requestId;
+    }
+
+    public void setRequestId(String requestId) {
+        this.requestId = requestId;
+    }
+}

+ 16 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/base/Constant.java

@@ -0,0 +1,16 @@
+package com.tzld.piaoquan.risk.control.common.base;
+
+/**
+ * 常量
+ *
+ * @author supeng
+ */
+public class Constant {
+    /**
+     * traceID
+     */
+    public static final String LOG_TRACE_ID = "logTraceId";
+    public static final String UNDERLINE = "_";
+    public static final String POINT = ".";
+    public static final String DOLLAR = "$";
+}

+ 85 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/AppTypeEnum.java

@@ -0,0 +1,85 @@
+package com.tzld.piaoquan.risk.control.common.enums;
+
+/**
+ * AppType
+ * @author supeng
+ * @date 2020/08/19
+ */
+public enum AppTypeEnum {
+    /**
+     * vlog小程序
+     */
+    VLOG(0, "VLOG"),
+    /**
+     * 票圈视频APP
+     */
+    QINGQUAPP(1, "轻趣视频APP"),
+    SMILE(2, "搞笑视频"),
+    LOVEMOVIE(3, "爱电影"),
+    /**
+     * 票圈视频小程序
+     */
+    LOVELIVE(4, "爱生活"),
+    /**
+     * 长视频小程序
+     */
+    LONGVIDEO(5, "长视频"),
+    SHORTVIDEO(6, "短视频"),
+    SURPRISE(7, "惊奇视频"),
+    PC(8,"PC端"),
+    CIRCLEAPP(9,"票圈长视频APP"),
+    LONGVIDEOLITE(10,"票圈长视频lite"),
+    /**
+     * 票圈相册小程序
+     */
+    ALBUM(11,"票圈相册"),
+    H5(12,"H5端"),
+    /**
+     * 极速版APP
+     */
+    APPSPEED(13,"APP极速"),
+    SMALLSPEED(14,"小程序极速"),
+    SHANYINAPP(15,"闪音APP"),
+
+    LEHUOQUAN(16, "乐活圈"),
+
+    ADMIN_CRAWLER(888888,"后台-爬虫"),
+    OTHERS(999999,"其他的调用方,比如后台管理系统"),
+    COOPERATION(777777, "合作方渠道视频对接类型");
+
+    private Integer code;
+    private String desc;
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
+
+    private AppTypeEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public static AppTypeEnum valueOf( Integer code ){
+        if(code == null) {
+            return null;
+        }
+        for( AppTypeEnum appTypeEnum :AppTypeEnum.values()){
+            if( appTypeEnum.getCode().intValue() == code.intValue() ){
+                return appTypeEnum;
+            }
+        }
+        return null;
+    }
+}

+ 35 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/DataStatusEnum.java

@@ -0,0 +1,35 @@
+package com.tzld.piaoquan.risk.control.common.enums;
+
+/**
+ * 数据状态
+ *
+ * @author supeng
+ * @Date 2020/7/31
+ */
+public enum DataStatusEnum {
+    INVALID(0, "无效"),
+    VALID(1, "有效");
+    private Integer value;
+    private String desc;
+
+    DataStatusEnum(int value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
+}

+ 38 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/ExceptionEnum.java

@@ -0,0 +1,38 @@
+package com.tzld.piaoquan.risk.control.common.enums;
+
+/**
+ * 异常
+ *
+ * @author supeng
+ * @date 2020/08/31
+ */
+public enum ExceptionEnum {
+
+    SUCCESS(0, "成功"),
+    SYSTEM_ERROR(1, "系统错误"),
+    PARAMS_INVALID(2, "参数错误"),
+    DATA_NOT_EXIST(3, "数据不存在"),
+    DATA_ERROR(4, "数据错误"),
+    EXIST_RELATED_DATA(5, "存在关联数据"),
+    DATA_EXIST(6, "数据已存在"),
+    ILLEGAL_OPERATION (7, "非法操作"),
+    CONFIG_ERROR (8, "配置异常"),
+    ;
+
+    private int code;
+    private String msg;
+
+    public int getCode() {
+        return code;
+    }
+
+
+    public String getMsg() {
+        return msg;
+    }
+
+    ExceptionEnum(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+}

+ 32 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/RecommendStatusEnum.java

@@ -0,0 +1,32 @@
+package com.tzld.piaoquan.risk.control.common.enums;
+
+/**
+ * @author supeng
+ */
+
+public enum RecommendStatusEnum {
+
+	NOMAL(0,  "不可搜"),
+	RECOMMEND(1, "普通推荐"),
+	CUSTOMRECOMMEND(10, "编辑推荐"),
+	WAITRECOMMEND(-6,"待推荐"),
+	SEARCHECOMMEND(-7, "可搜索"),
+    REALTIMERECOMMEND(20,"实时推荐"),
+	UNCLASSIFIED(-1, "未分类/未选择");
+
+	RecommendStatusEnum(Integer value, String desc) {
+		this.value = value;
+		this.desc = desc;
+	}
+
+	private Integer value;
+	private String desc;
+
+	public Integer getValue() {
+		return value;
+	}
+
+	public String getDesc() {
+		return desc;
+	}
+}

+ 62 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/enums/VideoStatusEnum.java

@@ -0,0 +1,62 @@
+package com.tzld.piaoquan.risk.control.common.enums;
+
+public enum VideoStatusEnum {
+
+	    INVALID(0,"无效状态"),
+	    NORMAL(1,"有效(公开)"),
+	    DELETED(2,"已删除"),
+	   	FORBIDDEN(3,"已屏蔽(不可见)"),
+	   	FOLLOWED(4,"关注可见(可见)"),
+	   	SHARED(5,"分享可见(私密)"),
+	   	MYSELF(6,"自己可见");
+	   	
+	   	private Integer code;
+	   	private String desc;
+		public Integer getCode() {
+			return code;
+		}
+		public void setCode(Integer code) {
+			this.code = code;
+		}
+		public String getDesc() {
+			return desc;
+		}
+		public void setDesc(String desc) {
+			this.desc = desc;
+		}
+		private VideoStatusEnum(Integer code, String desc) {
+			this.code = code;
+			this.desc = desc;
+		}
+		
+		 public static boolean videoIsCanOperation( Integer status ) {
+	  		 if(NORMAL.getCode().equals(status)||
+	  			  FOLLOWED.getCode().equals(status)|| 
+	  			  SHARED.getCode().equals(status)||
+					 MYSELF.getCode().equals(status)) {
+	  			 return true;
+	  		 }
+	  		 return false;
+	  	 }
+		 
+		 public static boolean videoCanNotOperation( Integer status ) {
+	  		 if(INVALID.getCode().equals(status)||
+	  				DELETED.getCode().equals(status)|| 
+	  				FORBIDDEN.getCode().equals(status)) {
+	  			 return true;
+	  		 }
+	  		 return false;
+	  	 }
+
+	public static VideoStatusEnum valueOf(Integer code, VideoStatusEnum defaultValue) {
+		if (code == null) {
+			return defaultValue;
+		}
+		for (VideoStatusEnum oneEnum : VideoStatusEnum.values()) {
+			if (oneEnum.getCode().intValue() == code.intValue()) {
+				return oneEnum;
+			}
+		}
+		return defaultValue;
+	}
+}

+ 11 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/CacheException.java

@@ -0,0 +1,11 @@
+package com.tzld.piaoquan.risk.control.common.exception;
+
+/**
+ * 缓存异常
+ *
+ * @author supeng
+ * @date 2020/08/19
+ */
+public class CacheException extends RuntimeException {
+
+}

+ 97 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/CommonException.java

@@ -0,0 +1,97 @@
+package com.tzld.piaoquan.risk.control.common.exception;
+
+import com.tzld.piaoquan.risk.control.common.enums.ExceptionEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 异常
+ *
+ * @author supeng
+ * @date 2020/08/28
+ */
+public class CommonException extends RuntimeException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CommonException.class);
+    /**
+     * 异常
+     */
+    private ExceptionEnum exceptionEnum;
+    /**
+     * 错误码
+     */
+    private int code;
+    /**
+     * 异常信息
+     */
+    private String msg;
+
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public CommonException(Throwable throwable) {
+        super(throwable);
+    }
+
+    public CommonException(int code, String msg) {
+        super(msg);
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public CommonException(ExceptionEnum exceptionEnum) {
+        super(exceptionEnum.getMsg());
+        this.exceptionEnum = exceptionEnum;
+        this.code = exceptionEnum.getCode();
+        this.msg = exceptionEnum.getMsg();
+    }
+
+    public CommonException(ExceptionEnum exceptionEnum, String msg) {
+        super(msg);
+        this.exceptionEnum = exceptionEnum;
+        this.code = exceptionEnum.getCode();
+        this.msg = msg;
+    }
+
+
+    public CommonException(int code, String msg, Throwable throwable) {
+        super(msg, throwable);
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public CommonException(ExceptionEnum exceptionEnum, Throwable throwable) {
+        super(exceptionEnum.getMsg(), throwable);
+        this.exceptionEnum = exceptionEnum;
+        this.code = exceptionEnum.getCode();
+        this.msg = exceptionEnum.getMsg();
+    }
+
+
+    @Override
+    public void printStackTrace() {
+        if (exceptionEnum != null) {
+            LOGGER.info("exception code = {}, msg = {}", exceptionEnum.getCode(), exceptionEnum.getMsg());
+        }
+        LOGGER.info("exception code = {}, msg = {}", code, msg);
+    }
+}

+ 11 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/DAOException.java

@@ -0,0 +1,11 @@
+package com.tzld.piaoquan.risk.control.common.exception;
+
+/**
+ * DAO异常
+ *
+ * @author supeng
+ * @date 2020/08/19
+ */
+public class DAOException extends RuntimeException{
+
+}

+ 56 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/HttpServiceException.java

@@ -0,0 +1,56 @@
+package com.tzld.piaoquan.risk.control.common.exception;
+
+/**
+ *
+ */
+public class HttpServiceException extends RuntimeException {
+
+    private int code;
+    private String message;
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public HttpServiceException(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public HttpServiceException(String message, int code, String message1) {
+        super(message);
+        this.code = code;
+        this.message = message1;
+    }
+
+    public HttpServiceException(String message, Throwable cause, int code, String message1) {
+        super(message, cause);
+        this.code = code;
+        this.message = message1;
+    }
+
+    public HttpServiceException(Throwable cause, int code, String message) {
+        super(cause);
+        this.code = code;
+        this.message = message;
+    }
+
+    public HttpServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, int code, String message1) {
+        super(message, cause, enableSuppression, writableStackTrace);
+        this.code = code;
+        this.message = message1;
+    }
+}

+ 15 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/StopRetryException.java

@@ -0,0 +1,15 @@
+package com.tzld.piaoquan.risk.control.common.exception;
+
+/**
+ * 不在重试
+ *
+ * @author supeng
+ * @date 2020/08/19
+ */
+public class StopRetryException extends RuntimeException {
+
+    public StopRetryException(String message) {
+        super(message);
+    }
+
+}

+ 43 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/common/exception/TimeoutException.java

@@ -0,0 +1,43 @@
+package com.tzld.piaoquan.risk.control.common.exception;
+
+
+public class TimeoutException extends RuntimeException {
+
+    private String message;
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public TimeoutException() {
+    }
+
+    public TimeoutException(String message) {
+        this.message = message;
+    }
+
+    public TimeoutException(String message, String message1) {
+        super(message);
+        this.message = message1;
+    }
+
+    public TimeoutException(String message, Throwable cause, String message1) {
+        super(message, cause);
+        this.message = message1;
+    }
+
+    public TimeoutException(Throwable cause, String message) {
+        super(cause);
+        this.message = message;
+    }
+
+    public TimeoutException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, String message1) {
+        super(message, cause, enableSuppression, writableStackTrace);
+        this.message = message1;
+    }
+}

+ 67 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/config/RedisTemplateConfig.java

@@ -0,0 +1,67 @@
+//package com.tzld.piaoquan.risk.control.config;
+//
+//import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+//import org.springframework.beans.factory.annotation.Qualifier;
+//import org.springframework.boot.context.properties.ConfigurationProperties;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.context.annotation.Primary;
+//import org.springframework.data.redis.connection.RedisConnectionFactory;
+//import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+//import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
+//import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+//import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
+//import org.springframework.data.redis.core.RedisTemplate;
+//import org.springframework.data.redis.serializer.StringRedisSerializer;
+//
+///**
+// * redis config
+// *
+// * @author supeng
+// * @date 2020/09/02
+// */
+//@Configuration
+//public class RedisTemplateConfig {
+//
+//    @Bean
+//    @ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
+//    public GenericObjectPoolConfig<LettucePoolingClientConfiguration> redisPool() {
+//        return new GenericObjectPoolConfig<>();
+//    }
+//
+//    @Bean
+//    @ConfigurationProperties(prefix = "spring.redis")
+//    public RedisStandaloneConfiguration redisConfig() {
+//        return new RedisStandaloneConfiguration();
+//    }
+//
+//    @Bean("factory")
+//    @Primary
+//    public LettuceConnectionFactory factory(GenericObjectPoolConfig<LettucePoolingClientConfiguration> config, RedisStandaloneConfiguration redisConfig) {
+//        LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(config).build();
+//        return new LettuceConnectionFactory(redisConfig, lettuceClientConfiguration);
+//    }
+//
+//    @Bean(name = "redisTemplate")
+//    public RedisTemplate<String, String> getRedisTemplate(@Qualifier("factory") RedisConnectionFactory factory) {
+//        return buildRedisTemplate(factory);
+//    }
+//
+//    /**
+//     * 构建redisTemplate 使用string序列化
+//     *
+//     * @param factory
+//     * @return
+//     */
+//    public RedisTemplate<String, String> buildRedisTemplate(RedisConnectionFactory factory) {
+//        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
+//        redisTemplate.setConnectionFactory(factory);
+//        // key的序列化类型 保证可读性
+//        redisTemplate.setKeySerializer(new StringRedisSerializer());
+//        redisTemplate.setValueSerializer(new StringRedisSerializer());
+//        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+//        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
+//        return redisTemplate;
+//    }
+//
+//}

+ 60 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/config/XxlJobConfig.java

@@ -0,0 +1,60 @@
+package com.tzld.piaoquan.risk.control.config;
+
+import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * <p>
+ * xxl-job config
+ * </p>
+ *
+ * @author ZhouYang
+ * @date 2021/08/17
+ */
+@Slf4j
+@Configuration
+public class XxlJobConfig {
+
+    @Value("${xxl.job.admin.addresses}")
+    private String adminAddresses;
+
+    @Value("${xxl.job.accessToken}")
+    private String accessToken;
+
+    @Value("${xxl.job.executor.appname}")
+    private String appname;
+
+    @Value("${xxl.job.executor.address}")
+    private String address;
+
+    @Value("${xxl.job.executor.ip}")
+    private String ip;
+
+    @Value("${xxl.job.executor.port}")
+    private int port;
+
+    @Value("${xxl.job.executor.logpath}")
+    private String logPath;
+
+    @Value("${xxl.job.executor.logretentiondays}")
+    private int logRetentionDays;
+
+    @Bean
+    public XxlJobSpringExecutor xxlJobExecutor() {
+        log.info("xxl-job config init");
+        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
+        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
+        xxlJobSpringExecutor.setAppname(appname);
+        xxlJobSpringExecutor.setAddress(address);
+        xxlJobSpringExecutor.setIp(ip);
+        xxlJobSpringExecutor.setPort(port);
+        xxlJobSpringExecutor.setAccessToken(accessToken);
+        xxlJobSpringExecutor.setLogPath(logPath);
+        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
+        return xxlJobSpringExecutor;
+    }
+
+}

+ 30 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/dao/generator/MybatisGeneratorMain.java

@@ -0,0 +1,30 @@
+package com.tzld.piaoquan.risk.control.dao.generator;
+
+import org.mybatis.generator.api.MyBatisGenerator;
+import org.mybatis.generator.config.Configuration;
+import org.mybatis.generator.config.xml.ConfigurationParser;
+import org.mybatis.generator.exception.InvalidConfigurationException;
+import org.mybatis.generator.exception.XMLParserException;
+import org.mybatis.generator.internal.DefaultShellCallback;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MybatisGeneratorMain {
+
+	public static void main(String[] args)
+			throws SQLException, IOException, InterruptedException, InvalidConfigurationException, XMLParserException {
+		List<String> warnings = new ArrayList<String>();
+		boolean overwrite = true;
+		File configFile = new File(MybatisGeneratorMain.class.getResource("/mybatis-generator-config.xml").getFile());
+		ConfigurationParser cp = new ConfigurationParser(warnings);
+		Configuration config = cp.parseConfiguration(configFile);
+		DefaultShellCallback callback = new DefaultShellCallback(overwrite);
+		MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
+		myBatisGenerator.generate(null);
+		System.out.println("genreate finish");
+	}
+}

+ 111 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/handle/GlobalExceptionHandle.java

@@ -0,0 +1,111 @@
+package com.tzld.piaoquan.risk.control.handle;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.tzld.piaoquan.risk.control.common.base.CommonResponse;
+import com.tzld.piaoquan.risk.control.service.LoghubService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.util.CollectionUtils;
+import org.springframework.validation.BindException;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import com.tzld.piaoquan.risk.control.common.enums.ExceptionEnum;
+import com.tzld.piaoquan.risk.control.common.exception.CommonException;
+
+/**
+ * 全局异常处理器
+ * 
+ * @author liuzhiheng
+ * @date 2020年11月20日 上午11:04:48
+ * @version 1.0
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandle {
+
+    private static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandle.class);
+
+    @Autowired
+    private LoghubService loghubService;
+
+    @Value("${aliyun.log.project}")
+    private String aliyunLogProject;
+    @Value("${aliyun.log.logstore.error}")
+    private String aliyunLogLogstoreError;
+
+    @ExceptionHandler
+    public Object handleException(HttpServletRequest req, Exception exception) throws Exception {
+        String uri = req.getRequestURI();
+        CommonResponse<Object> response = new CommonResponse<Object>();
+        // 业务异常
+        if (exception instanceof CommonException) {
+            CommonException e = (CommonException) exception;
+            response.setCode(e.getCode());
+            response.setMsg(e.getMsg());
+            LOGGER.warn("uri:" + uri + "\n" + "CustomException log.", exception);
+            Map<String, Object> logMap = new HashMap<>();
+            logMap.put("uri", uri);
+            logMap.put("code", e.getCode());
+            logMap.put("msg", e.getMsg());
+            loghubService.asyncSubmitLog(aliyunLogProject, aliyunLogLogstoreError, "" , logMap);
+        } else if (exception instanceof MethodArgumentNotValidException) {
+            // 参数校验异常
+            MethodArgumentNotValidException e = (MethodArgumentNotValidException) exception;
+            List<ObjectError> errorList = e.getBindingResult().getAllErrors();
+            StringBuilder errorMsg = new StringBuilder();
+            errorMsg.append("|");
+            if (!CollectionUtils.isEmpty(errorList)) {
+                for (ObjectError objectError : errorList) {
+                    errorMsg.append(objectError.getDefaultMessage()).append("|");
+                }
+            }
+            response.setCode(ExceptionEnum.PARAMS_INVALID.getCode());
+            response.setMsg(errorMsg.toString());
+            LOGGER.warn("uri:" + uri + "\n" + "MethodArgumentNotValidException log.", exception);
+            Map<String, Object> logMap = new HashMap<>();
+            logMap.put("uri", uri);
+            logMap.put("code", ExceptionEnum.PARAMS_INVALID.getCode());
+            logMap.put("msg", errorMsg.toString());
+            loghubService.asyncSubmitLog(aliyunLogProject, aliyunLogLogstoreError, "" , logMap);
+        } else if (exception instanceof BindException) {
+            // 参数绑定异常
+            BindException e = (BindException) exception;
+            List<ObjectError> errorList = e.getBindingResult().getAllErrors();
+            StringBuilder errorMsg = new StringBuilder();
+            errorMsg.append("|");
+            if (!CollectionUtils.isEmpty(errorList)) {
+                for (ObjectError objectError : errorList) {
+                    errorMsg.append(objectError.getDefaultMessage()).append("|");
+                }
+            }
+            response.setCode(ExceptionEnum.PARAMS_INVALID.getCode());
+            response.setMsg(errorMsg.toString());
+            LOGGER.warn("uri:" + uri + "\n" + "BindException log.", exception);
+            Map<String, Object> logMap = new HashMap<>();
+            logMap.put("uri", uri);
+            logMap.put("code", ExceptionEnum.PARAMS_INVALID.getCode());
+            logMap.put("msg", errorMsg.toString());
+            loghubService.asyncSubmitLog(aliyunLogProject, aliyunLogLogstoreError, "" , logMap);
+        } else {
+            response.setCode(ExceptionEnum.SYSTEM_ERROR.getCode());
+            response.setMsg(ExceptionEnum.SYSTEM_ERROR.getMsg());
+            LOGGER.error("uri:" + uri + "\n" + "unknow exception log.", exception);
+            Map<String, Object> logMap = new HashMap<>();
+            logMap.put("uri", uri);
+            logMap.put("code", ExceptionEnum.SYSTEM_ERROR.getCode());
+            logMap.put("msg", ExceptionEnum.SYSTEM_ERROR.getMsg());
+            loghubService.asyncSubmitLog(aliyunLogProject, aliyunLogLogstoreError, "" , logMap);
+        }
+        return response;
+    }
+
+}

+ 30 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/job/DemoJob.java

@@ -0,0 +1,30 @@
+package com.tzld.piaoquan.risk.control.job;
+
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import com.xxl.job.core.log.XxlJobLogger;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * @author supeng
+ */
+@Slf4j
+@Component
+public class DemoJob {
+
+    @XxlJob("demoHandler")
+    public ReturnT<String> demoHandler(String params) {
+        XxlJobLogger.log("demoHandler start");
+        try {
+            //TODO
+        } catch (Exception e) {
+            XxlJobLogger.log("demoHandler error", e);
+            return ReturnT.FAIL;
+        } finally {
+            XxlJobLogger.log("demoHandler end");
+        }
+        return ReturnT.SUCCESS;
+    }
+}

+ 39 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/AuthorInfoDTO.java

@@ -0,0 +1,39 @@
+package com.tzld.piaoquan.risk.control.model.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class AuthorInfoDTO {
+    private Integer channel;
+    private String channel_account_id;
+    private String xhs_id;
+    /**
+     * 抖音ID
+     */
+    private String dy_id;
+    private String ks_id;
+    private String digit_id;
+    private String account_link;
+    private String account_name;
+    private String avatar_url;
+    private String background_url;
+    private String gender;
+    private String description;
+    private String ip_location;
+    private List<String> tags;
+    private Long follower_count;
+    private Long publish_count;
+    private Long like_count;
+    private Long collect_count;
+    private Long comment_count;
+    private Long looking_count;
+    private String biz_info;
+    private String wx_gh;
+    /**
+     * 毫秒
+     */
+    private Long create_timestamp;
+    private Long update_timestamp;
+}

+ 169 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/BaseInfoDTO.java

@@ -0,0 +1,169 @@
+package com.tzld.piaoquan.risk.control.model.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 基础信息
+ *
+ * @author supeng
+ */
+public class BaseInfoDTO {
+    /// 用户信息
+    @ApiModelProperty(value = "公共参数-token值")
+    private String token;
+    @ApiModelProperty(value = "公共参数-登录用户ID")
+    private Long loginUid;
+
+    /// 应用信息
+    @ApiModelProperty(value = "公共参数-应用版本号")
+    private Integer appVersionCode;
+    @ApiModelProperty(value = "公共参数-产品代号")
+    private Integer appType;
+
+    /// 设备信息
+    @ApiModelProperty(value = "公共参数-手机设备的唯一码")
+    private String machineCode;
+    @ApiModelProperty(value = "公共参数-ios,android")
+    private String platform;
+    @ApiModelProperty(value = "公共参数-系统版本(例:ios10.1)")
+    private String systemVersion;
+    @ApiModelProperty(value = "公共参数-手机信息")
+    private String machineInfo;
+    @ApiModelProperty(value = "公共参数-网络类型 WI-FI 5G 4G 3G 2G")
+    private String networkType;
+    @ApiModelProperty(value = "公共参数-客户端ip")
+    private String clientIp;
+
+    // pageSource相关的参数
+    @ApiModelProperty(value = "公共参数-页面来源")
+    private String pageSource;
+
+    // 某次操作相关的参数
+    @ApiModelProperty(value = "公共参数-前端请求时间")
+    private Long clientTimestamp;
+    @ApiModelProperty(value = "公共参数-sessionId")
+    private String sessionId;
+    @ApiModelProperty(value = "公共参数-requestId,每次请求客户端生成唯一ID,不超过64位")
+    private String requestId;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Long getLoginUid() {
+        return loginUid;
+    }
+
+    public void setLoginUid(Long loginUid) {
+        this.loginUid = loginUid;
+    }
+
+    public Integer getAppVersionCode() {
+        return appVersionCode;
+    }
+
+    public void setAppVersionCode(Integer appVersionCode) {
+        this.appVersionCode = appVersionCode;
+    }
+
+    public Integer getAppType() {
+        return appType;
+    }
+
+    public void setAppType(Integer appType) {
+        this.appType = appType;
+    }
+
+    public String getMachineCode() {
+        return machineCode;
+    }
+
+    public void setMachineCode(String machineCode) {
+        this.machineCode = machineCode;
+    }
+
+    public String getPlatform() {
+        return platform;
+    }
+
+    public void setPlatform(String platform) {
+        this.platform = platform;
+    }
+
+    public String getSystemVersion() {
+        return systemVersion;
+    }
+
+    public void setSystemVersion(String systemVersion) {
+        this.systemVersion = systemVersion;
+    }
+
+    public String getMachineInfo() {
+        return machineInfo;
+    }
+
+    public void setMachineInfo(String machineInfo) {
+        this.machineInfo = machineInfo;
+    }
+
+    public String getNetworkType() {
+        return networkType;
+    }
+
+    public void setNetworkType(String networkType) {
+        this.networkType = networkType;
+    }
+
+    public String getClientIp() {
+        return clientIp;
+    }
+
+    public void setClientIp(String clientIp) {
+        this.clientIp = clientIp;
+    }
+
+    public String getPageSource() {
+        return pageSource;
+    }
+
+    public void setPageSource(String pageSource) {
+        this.pageSource = pageSource;
+    }
+
+    public Long getClientTimestamp() {
+        return clientTimestamp;
+    }
+
+    public void setClientTimestamp(Long clientTimestamp) {
+        this.clientTimestamp = clientTimestamp;
+    }
+
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    public void setSessionId(String sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    public String getRequestId() {
+        return requestId;
+    }
+
+    public void setRequestId(String requestId) {
+        this.requestId = requestId;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+            "BaseInfoDTO [token=%s, appVersionCode=%s, appType=%s, machineCode=%s, platform=%s, systemVersion=%s, machineInfo=%s, networkType=%s, clientIp=%s, pageSource=%s, clientTimestamp=%s, sessionId=%s, requestId=%s]",
+            token, appVersionCode, appType, machineCode, platform, systemVersion, machineInfo, networkType, clientIp,
+            pageSource, clientTimestamp, sessionId, requestId);
+    }
+
+}

+ 9 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/ImageUrlDTO.java

@@ -0,0 +1,9 @@
+package com.tzld.piaoquan.risk.control.model.dto;
+
+import lombok.Data;
+
+@Data
+public class ImageUrlDTO {
+    private Integer image_type;
+    private String image_url;
+}

+ 26 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/PageDTO.java

@@ -0,0 +1,26 @@
+package com.tzld.piaoquan.risk.control.model.dto;
+
+/**
+ * @author supeng
+ */
+public class PageDTO {
+
+    private int pageNum;
+    private int pageSize;
+
+    public int getPageNum() {
+        return pageNum;
+    }
+
+    public void setPageNum(int pageNum) {
+        this.pageNum = pageNum;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(int pageSize) {
+        this.pageSize = pageSize;
+    }
+}

+ 29 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/VideoBillBoardItemDTO.java

@@ -0,0 +1,29 @@
+package com.tzld.piaoquan.risk.control.model.dto;
+
+import lombok.Data;
+
+@Data
+public class VideoBillBoardItemDTO {
+    private String item_id;
+    private String item_title;
+    private String item_cover_url;
+    private Long item_duration;
+    private String nick_name;
+    private String avatar_url;
+    private Long fans_cnt;
+    private Long play_cnt;
+    private Long publish_time;
+    private Long score;
+    private String item_url;
+    private Long like_cnt;
+    private Long follow_cnt;
+    private Double follow_rate;
+    private Double like_rate;
+    private Integer media_type;
+    private Long favorite_id;
+    private Boolean is_favorite;
+    private Integer image_cnt;
+
+    private Integer crawlerType;
+    private String crawlerConfig;
+}

+ 41 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/VideoInfoDTO.java

@@ -0,0 +1,41 @@
+package com.tzld.piaoquan.risk.control.model.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class VideoInfoDTO {
+    private Integer channel;
+    private String channel_content_id;
+    private String content_link;
+    private String wx_sn;
+    private String title;
+    private String content_type;
+    private String body_text;
+    private String location;
+    private String source_url;
+    private String mini_program;
+    private List<String> topic_list;
+    private List<ImageUrlDTO> image_url_list;
+    private List<VideoUrlDTO> video_url_list;
+    private String bgm_data;
+    private String ad_info;
+    private String voice_data;
+    private String channel_account_id;
+    private String channel_account_name;
+    private String avatar;
+    private Long item_index;
+    private Long view_count;
+    private Long like_count;
+    private Long collect_count;
+    private Long comment_count;
+    private Long share_count;
+    private Long looking_count;
+    /**
+     * 毫秒
+     */
+    private Long publish_timestamp;
+    private Long modify_timestamp;
+    private Long update_timestamp;
+}

+ 9 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/VideoUrlDTO.java

@@ -0,0 +1,9 @@
+package com.tzld.piaoquan.risk.control.model.dto;
+
+import lombok.Data;
+
+@Data
+public class VideoUrlDTO {
+    private Long video_duration;
+    private String video_url;
+}

+ 8 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/dto/mq/VideoPortraitMessageDTO.java

@@ -0,0 +1,8 @@
+package com.tzld.piaoquan.risk.control.model.dto.mq;
+
+import lombok.Data;
+
+@Data
+public class VideoPortraitMessageDTO {
+    private String vid;
+}

+ 11 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/AuthorInfoParam.java

@@ -0,0 +1,11 @@
+package com.tzld.piaoquan.risk.control.model.param.douhot;
+
+import lombok.Data;
+
+/**
+ * @author supeng
+ */
+@Data
+public class AuthorInfoParam {
+    private String account_id;
+}

+ 18 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/UserPortraitParam.java

@@ -0,0 +1,18 @@
+package com.tzld.piaoquan.risk.control.model.param.douhot;
+
+import lombok.Data;
+
+/**
+ * @author supeng
+ */
+@Data
+public class UserPortraitParam {
+    private String account_id;
+    private Boolean need_province;
+    private Boolean need_city;
+    private Boolean need_city_level;
+    private Boolean need_gender;
+    private Boolean need_age;
+    private Boolean need_phone_brand;
+    private Boolean need_phone_price;
+}

+ 17 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/VideoBillBoardParam.java

@@ -0,0 +1,17 @@
+package com.tzld.piaoquan.risk.control.model.param.douhot;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author supeng
+ */
+@Data
+public class VideoBillBoardParam {
+    private String type="视频总榜";
+    private String time="近1天";
+    private String category;
+    private List<String> sub_categories;
+    private String cursor;
+}

+ 13 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/VideoInfoParam.java

@@ -0,0 +1,13 @@
+package com.tzld.piaoquan.risk.control.model.param.douhot;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author supeng
+ */
+@Data
+public class VideoInfoParam {
+    private String content_id;
+}

+ 18 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/model/param/douhot/VideoPortraitParam.java

@@ -0,0 +1,18 @@
+package com.tzld.piaoquan.risk.control.model.param.douhot;
+
+import lombok.Data;
+
+/**
+ * @author supeng
+ */
+@Data
+public class VideoPortraitParam {
+    private String content_id;
+    private Boolean need_province;
+    private Boolean need_city;
+    private Boolean need_city_level;
+    private Boolean need_gender;
+    private Boolean need_age;
+    private Boolean need_phone_brand;
+    private Boolean need_phone_price;
+}

+ 17 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/LoghubService.java

@@ -0,0 +1,17 @@
+package com.tzld.piaoquan.risk.control.service;
+
+import java.util.Map;
+
+/**
+ * @author supeng
+ */
+public interface LoghubService {
+    /**
+     *
+     * @param project
+     * @param logStore
+     * @param topic
+     * @param data
+     */
+    void asyncSubmitLog(String project, String logStore, String topic, Map<String, Object> data);
+}

+ 155 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/ODPSManager.java

@@ -0,0 +1,155 @@
+package com.tzld.piaoquan.risk.control.service;
+
+import com.aliyun.odps.Instance;
+import com.aliyun.odps.Odps;
+import com.aliyun.odps.OdpsException;
+import com.aliyun.odps.PartitionSpec;
+import com.aliyun.odps.account.Account;
+import com.aliyun.odps.account.AliyunAccount;
+import com.aliyun.odps.data.Record;
+import com.aliyun.odps.data.RecordReader;
+import com.aliyun.odps.task.SQLTask;
+import com.aliyun.odps.tunnel.TableTunnel;
+import com.aliyun.odps.tunnel.TunnelException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * @author sunxy
+ */
+@Slf4j
+@Component
+public class ODPSManager {
+    private final static String ACCESSID = "LTAIWYUujJAm7CbH";
+    private final static String ACCESSKEY = "RfSjdiWwED1sGFlsjXv0DlfTnZTG1P";
+//    private final static String ENDPOINT = "http://service.cn.maxcompute.aliyun.com/api";
+    //内网
+    private final static String ENDPOINT = "http://service.cn-hangzhou-vpc.maxcompute.aliyun-inc.com/api";
+
+    public List<Record> query(String sql) {
+        Account account = new AliyunAccount(ACCESSID, ACCESSKEY);
+        Odps odps = new Odps(account);
+        odps.setEndpoint(ENDPOINT);
+        odps.setDefaultProject("loghubods");
+        Instance i;
+        try {
+            i = SQLTask.run(odps, sql);
+            i.waitForSuccess();
+            List<Record> records = SQLTask.getResultByInstanceTunnel(i);
+            if (Objects.nonNull(records) && records.size() != 0) {
+                return records;
+            }
+        } catch (Exception e) {
+            log.error("odps query error", e);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * 使用table tunnel的方式获取阿里云dataWork上的数据
+     *
+     * @param partition 分区,格式为:{分区字段}={分区值}
+     */
+    public List<Record> tableTunnelQuery(String project, String table, String partition){
+        log.info("ODPSManager.tableTunnelQuery.param project: {}, table: {}, partition: {}", project, table, partition);
+        Account account = new AliyunAccount(ACCESSID, ACCESSKEY);
+        Odps odps = new Odps(account);
+        odps.setDefaultProject(project);
+        TableTunnel tunnel = new TableTunnel(odps);
+        List<Record> records = new ArrayList<>();
+        RecordReader recordReader = null;
+        try {
+            TableTunnel.DownloadSession downloadSession = tunnel.createDownloadSession(project, table, new PartitionSpec(partition));
+            log.info("ODPSManager.tableTunnelQuery Session Status is : {} ", downloadSession.getStatus().toString());
+            long count = downloadSession.getRecordCount();
+            log.info("ODPSManager.tableTunnelQuery.param RecordCount is: {}", count);
+            recordReader = downloadSession.openRecordReader(0, count);
+            Record record;
+            while ((record = recordReader.read()) != null) {
+                records.add(record);
+            }
+        } catch (
+                TunnelException |
+                IOException ex) {
+            log.error("ex-error", ex);
+        } finally {
+            if (!Objects.isNull(recordReader)) {
+                try {
+                    recordReader.close();
+                } catch (Exception e) {
+                    log.error("error: ", e);
+                }
+            }
+        }
+        return records;
+    }
+
+    public List<Record> tableTunnelQuery(String project, String table){
+        log.info("ODPSManager.tableTunnelQuery.param project: {}, table: {}", project, table);
+        Account account = new AliyunAccount(ACCESSID, ACCESSKEY);
+        Odps odps = new Odps(account);
+        odps.setDefaultProject(project);
+        TableTunnel tunnel = new TableTunnel(odps);
+        List<Record> records =  new ArrayList<>();
+        RecordReader recordReader = null;
+        try {
+            TableTunnel.DownloadSession downloadSession = tunnel.createDownloadSession(project, table);
+            log.info("ODPSManager.tableTunnelQuery Session Status is : {} ", downloadSession.getStatus().toString());
+            long count = downloadSession.getRecordCount();
+            log.info("ODPSManager.tableTunnelQuery.param RecordCount is: {}", count);
+            recordReader = downloadSession.openRecordReader(0, count);
+            Record record;
+            while ((record = recordReader.read()) != null) {
+                records.add(record);
+            }
+        } catch (
+                TunnelException |
+                IOException ex) {
+            log.error("ex-error", ex);
+        } finally {
+            if (!Objects.isNull(recordReader)) {
+                try {
+                    recordReader.close();
+                } catch (Exception e) {
+                    log.error("error: ", e);
+                }
+            }
+        }
+        return records;
+    }
+
+    public Odps getOdps(){
+        Account account = new AliyunAccount(ACCESSID, ACCESSKEY);
+        return new Odps(account);
+    }
+
+    public static void main(String[] args) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(new Date());
+        cal.add(Calendar.DATE, -1);
+        Date date2 = cal.getTime();
+        SimpleDateFormat format2 = new SimpleDateFormat("yyyyMMdd");
+
+        Account account = new AliyunAccount(ACCESSID, ACCESSKEY);
+        Odps odps = new Odps(account);
+        odps.setEndpoint(ENDPOINT);
+        odps.setDefaultProject("loghubods");
+        String sql = "select * from yesterday_return_top1000 where dt='" + format2.format(date2) + "';";
+        Instance i;
+        try {
+            i = SQLTask.run(odps, sql);
+            i.waitForSuccess();
+            List<Record> records = SQLTask.getResult(i);
+            for (Record r : records) {
+                System.out.println(Integer.parseInt(r.get(0).toString()));
+                System.out.println(r.get(1).toString());
+            }
+        } catch (OdpsException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 55 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/impl/LoghubServiceImpl.java

@@ -0,0 +1,55 @@
+package com.tzld.piaoquan.risk.control.service.impl;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.tzld.commons.aliyun.log.AliyunLogManager;
+import com.tzld.piaoquan.risk.control.service.LoghubService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * @author supeng
+ */
+@Slf4j
+@Service
+public class LoghubServiceImpl implements LoghubService {
+
+    @Autowired
+    private AliyunLogManager aliyunLogManager;
+
+    /**
+     * 线程池队列大小
+     */
+    private static final int QUEUE_MAX_SIZE = 100000;
+    /**
+     * 线程命名
+     */
+    private static final ThreadFactory NAMED_THREAD_FACTORY = new ThreadFactoryBuilder().setNameFormat("loghub-service-pool-%d").build();
+    /**
+     * 线程池
+     */
+    private static ExecutorService pool;
+
+    @PostConstruct
+    public void init() {
+        //init thread pool
+        pool = new ThreadPoolExecutor(32, 32,
+                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(QUEUE_MAX_SIZE), NAMED_THREAD_FACTORY, new ThreadPoolExecutor.AbortPolicy());
+    }
+
+    @PreDestroy
+    public void destroy() {
+        //gracefully shutdown
+        pool.shutdown();
+    }
+
+    @Override
+    public void asyncSubmitLog(String project, String logStore, String topic, Map<String, Object> data) {
+        pool.execute(() -> aliyunLogManager.sendLog(project, logStore, topic, data));
+    }
+}

+ 36 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/BinaryOp.java

@@ -0,0 +1,36 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import lombok.Getter;
+
+import java.util.Map;
+
+@Getter
+public class BinaryOp implements Expression {
+    private final Expression left;
+    private final Expression right;
+    private final TokenType operator;
+
+    public BinaryOp(Expression left, Expression right, TokenType operator) {
+        this.left = left;
+        this.right = right;
+        this.operator = operator;
+    }
+
+    @Override
+    public boolean evaluate(Map<String, Double> context) {
+        boolean leftVal = left.evaluate(context);
+        if (operator == TokenType.OR && leftVal) {
+            // Short-circuit for OR
+            return true;
+        }
+        boolean rightVal = right.evaluate(context);
+        switch (operator){
+            case AND:
+                return leftVal && rightVal;
+            case OR:
+                return leftVal || rightVal;
+            default:
+                throw new UnsupportedOperationException("Unsupported operator: " + operator);
+        }
+    }
+}

+ 49 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Comparison.java

@@ -0,0 +1,49 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import lombok.Getter;
+
+import java.util.Map;
+
+/**
+ * 比较
+ *
+ * @author supeng
+ */
+@Getter
+public class Comparison implements Expression {
+    private final String leftVar;
+    private final Double leftNum;
+    private final TokenType operator;
+    private final String rightVar;
+    private final Double rightNum;
+
+    public Comparison(String leftVar, Double leftNum, TokenType operator, String rightVar, Double rightNum) {
+        this.leftVar = leftVar;
+        this.leftNum = leftNum;
+        this.operator = operator;
+        this.rightVar = rightVar;
+        this.rightNum = rightNum;
+    }
+
+    @Override
+    public boolean evaluate(Map<String, Double> context) {
+        double left = leftVar != null ? context.getOrDefault(leftVar, 0.0) : leftNum;
+        double right = rightVar != null ? context.getOrDefault(rightVar, 0.0) : rightNum;
+        switch (operator) {
+            case GT:
+                return left > right;
+            case LT:
+                return left < right;
+            case EQ:
+                return left == right;
+            case GTE:
+                return left >= right;
+            case LTE:
+                return left <= right;
+            case NEQ:
+                return left != right;
+            default:
+                throw new UnsupportedOperationException("Unsupported operator: " + operator);
+        }
+    }
+}

+ 10 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Expression.java

@@ -0,0 +1,10 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import java.util.Map;
+
+/**
+ * @author supeng
+ */
+public interface Expression {
+    boolean evaluate(Map<String, Double> context);
+}

+ 83 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Lexer.java

@@ -0,0 +1,83 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 词法分析
+ *
+ * @author supeng
+ */
+public class Lexer {
+    private final String input;
+    private int pos = 0;
+
+    public Lexer(String input) {
+        this.input = input.trim();
+    }
+
+    public List<Token> tokenize() {
+        List<Token> tokens = new ArrayList<>();
+        while (pos < input.length()) {
+            char c = input.charAt(pos);
+            if (Character.isWhitespace(c)) {
+                pos++;
+            } else if (c == '(') {
+                tokens.add(new Token(TokenType.LPAREN, "("));
+                pos++;
+            } else if (c == ')') {
+                tokens.add(new Token(TokenType.RPAREN, ")"));
+                pos++;
+            } else if (c == '&' && pos + 1 < input.length() && input.charAt(pos + 1) == '&') {
+                tokens.add(new Token(TokenType.AND, "&&"));
+                pos += 2;
+            } else if (c == '|' && pos + 1 < input.length() && input.charAt(pos + 1) == '|') {
+                tokens.add(new Token(TokenType.OR, "||"));
+                pos += 2;
+            } else if (c == '=' && pos + 1 < input.length() && input.charAt(pos + 1) == '=') {
+                tokens.add(new Token(TokenType.EQ, "=="));
+                pos += 2;
+            } else if (c == '!' && pos + 1 < input.length() && input.charAt(pos + 1) == '=') {
+                tokens.add(new Token(TokenType.NEQ, "!="));
+                pos += 2;
+            } else if (c == '>' && pos + 1 < input.length() && input.charAt(pos + 1) == '=') {
+                tokens.add(new Token(TokenType.GTE, ">="));
+                pos += 2;
+            } else if (c == '<' && pos + 1 < input.length() && input.charAt(pos + 1) == '=') {
+                tokens.add(new Token(TokenType.LTE, "<="));
+                pos += 2;
+            } else if (c == '>') {
+                tokens.add(new Token(TokenType.GT, ">"));
+                pos++;
+            } else if (c == '<') {
+                tokens.add(new Token(TokenType.LT, "<"));
+                pos++;
+            } else if (Character.isLetter(c) || c == '_' || c == '$') {
+                StringBuilder sb = new StringBuilder();
+                while (pos < input.length() && (Character.isLetterOrDigit(input.charAt(pos)) || input.charAt(pos) == '_' || input.charAt(pos) == '$')) {
+                    sb.append(input.charAt(pos));
+                    pos++;
+                }
+                tokens.add(new Token(TokenType.VARIABLE, sb.toString()));
+            } else if (Character.isDigit(c) || c == '.') {
+                StringBuilder sb = new StringBuilder();
+                boolean hasDot = false;
+                while (pos < input.length() && (Character.isDigit(input.charAt(pos)) || input.charAt(pos) == '.')) {
+                    if (input.charAt(pos) == '.') {
+                        if (hasDot) {
+                            throw new RuntimeException("Invalid number format");
+                        }
+                        hasDot = true;
+                    }
+                    sb.append(input.charAt(pos));
+                    pos++;
+                }
+                tokens.add(new Token(TokenType.NUMBER, sb.toString()));
+            } else {
+                throw new RuntimeException("Unexpected character: " + c);
+            }
+        }
+        tokens.add(new Token(TokenType.EOF, ""));
+        return tokens;
+    }
+}

+ 24 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Parenthesis.java

@@ -0,0 +1,24 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import lombok.Getter;
+
+import java.util.Map;
+
+/**
+ * 括号
+ *
+ * @author supeng
+ */
+@Getter
+public class Parenthesis implements Expression {
+    private final Expression expr;
+
+    public Parenthesis(Expression expr) {
+        this.expr = expr;
+    }
+
+    @Override
+    public boolean evaluate(Map<String, Double> context) {
+        return expr.evaluate(context);
+    }
+}

+ 111 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Parser.java

@@ -0,0 +1,111 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import java.util.List;
+
+/**
+ * 解析
+ *
+ * @author supeng
+ */
+public class Parser {
+
+    private final List<Token> tokens;
+    private int current = 0;
+
+    public Parser(List<Token> tokens) {
+        this.tokens = tokens;
+    }
+
+    public Expression parse() {
+        return parseExpression();
+    }
+
+    private Expression parseExpression() {
+        Expression expr = parseTerm();
+        while (match(TokenType.OR)) {
+            expr = new BinaryOp(expr, parseTerm(), TokenType.OR);
+        }
+        return expr;
+    }
+
+    private Expression parseTerm() {
+        Expression expr = parseFactor();
+        while (match(TokenType.AND)) {
+            expr = new BinaryOp(expr, parseFactor(), TokenType.AND);
+        }
+        return expr;
+    }
+
+    private Expression parseFactor() {
+        if (match(TokenType.LPAREN)) {
+            Expression expr = parseExpression();
+            consume(TokenType.RPAREN, "Expect ')' after expression.");
+            return new Parenthesis(expr);
+        } else {
+            return parseComparison();
+        }
+    }
+
+    private Expression parseComparison() {
+        Token leftToken = advance();
+        String leftVar = null;
+        Double leftNum = null;
+        if (leftToken.type == TokenType.VARIABLE) {
+            leftVar = leftToken.value;
+        } else if (leftToken.type == TokenType.NUMBER) {
+            leftNum = Double.parseDouble(leftToken.value);
+        } else {
+            throw new RuntimeException("Expected variable or number");
+        }
+
+        Token op = advance();
+        if (!isComparisonOperator(op.type)) {
+            throw new RuntimeException("Expected comparison operator");
+        }
+
+        Token rightToken = advance();
+        String rightVar = null;
+        Double rightNum = null;
+        if (rightToken.type == TokenType.VARIABLE) {
+            rightVar = rightToken.value;
+        } else if (rightToken.type == TokenType.NUMBER) {
+            rightNum = Double.parseDouble(rightToken.value);
+        } else {
+            throw new RuntimeException("Expected variable or number");
+        }
+
+        return new Comparison(leftVar, leftNum, op.type, rightVar, rightNum);
+    }
+
+    private boolean isComparisonOperator(TokenType type) {
+        return type == TokenType.GT || type == TokenType.LT || type == TokenType.EQ
+                || type == TokenType.GTE || type == TokenType.LTE || type == TokenType.NEQ;
+    }
+
+    private boolean match(TokenType type) {
+        if (check(type)) {
+            current++;
+            return true;
+        }
+        return false;
+    }
+
+    private Token consume(TokenType type, String error) {
+        if (check(type)) {
+            return advance();
+        }
+        throw new RuntimeException(error);
+    }
+
+    private boolean check(TokenType type) {
+        return current < tokens.size() && tokens.get(current).type == type;
+    }
+
+    private Token advance() {
+        if (current < tokens.size()) {
+            return tokens.get(current++);
+        }
+        // Return EOF
+        return tokens.get(tokens.size() - 1);
+    }
+}

+ 67 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/RuleEvaluator.java

@@ -0,0 +1,67 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import java.util.*;
+
+/**
+ * 规则计算
+ *
+ * @author supeng
+ */
+public class RuleEvaluator {
+    private final Expression ast;
+    private final Set<String> variables = new HashSet<>();
+
+    public RuleEvaluator(String rule) {
+        Lexer lexer = new Lexer(rule);
+        List<Token> tokens = lexer.tokenize();
+        Parser parser = new Parser(tokens);
+        this.ast = parser.parse();
+        // 收集变量
+        collectVariables(ast);
+    }
+
+    public boolean evaluate(Map<String, Double> context) {
+        return ast.evaluate(context);
+    }
+
+    private void collectVariables(Expression expr) {
+        if (expr instanceof Comparison) {
+            Comparison comp = (Comparison) expr;
+            if (comp.getLeftVar() != null) {
+                variables.add(comp.getLeftVar());
+            }
+            if (comp.getRightVar() != null) {
+                variables.add(comp.getRightVar());
+            }
+        } else if (expr instanceof BinaryOp) {
+            BinaryOp op = (BinaryOp) expr;
+            collectVariables(op.getLeft());
+            collectVariables(op.getRight());
+        } else if (expr instanceof Parenthesis) {
+            Parenthesis paren = (Parenthesis) expr;
+            collectVariables(paren.getExpr());
+        }
+    }
+
+    public Set<String> getVariables() {
+        return variables;
+    }
+
+    public static void main(String[] args) {
+        String rule = "(region_rate_50_>0.4&&region_tgi_50_>100)||(gender_rate_male>0.5||gender_tgi_male>100)";
+        RuleEvaluator evaluator = new RuleEvaluator(rule);
+
+        // 获取所有变量名并查询数据库
+        Set<String> variables = evaluator.getVariables();
+        System.out.println(variables);
+        Map<String, Double> context = new HashMap<>();
+        context.put("region_rate_50_", 0.4);
+        context.put("region_tgi_50_", 0.4);
+        context.put("gender_rate_male", 0.4);
+        context.put("gender_tgi_male", 0.4);
+
+        // 规则求值
+        boolean result = evaluator.evaluate(context);
+        System.out.println("Result: " + result);
+    }
+}

+ 17 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/Token.java

@@ -0,0 +1,17 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import lombok.Data;
+
+/**
+ * @author supeng
+ */
+@Data
+public class Token {
+    final TokenType type;
+    final String value;
+
+    public Token(TokenType type, String value) {
+        this.type = type;
+        this.value = value;
+    }
+}

+ 59 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/TokenType.java

@@ -0,0 +1,59 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+/**
+ * @author supeng
+ */
+public enum TokenType {
+    /**
+     * 左括号
+     */
+    LPAREN,
+    /**
+     * 右括号
+     */
+    RPAREN,
+    /**
+     * &&
+     */
+    AND,
+    /**
+     * ||
+     */
+    OR,
+    /**
+     * >
+     */
+    GT,
+    /**
+     * <
+     */
+    LT,
+    /**
+     * ==
+     */
+    EQ,
+    /**
+     * >=
+     */
+    GTE,
+    /**
+     * <=
+     */
+    LTE,
+    /**
+     * !=
+     */
+    NEQ,
+    /**
+     * 变量
+     */
+    VARIABLE,
+    /**
+     * 数字
+     */
+    NUMBER,
+    /**
+     * 空
+     */
+    EOF;
+}

+ 24 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/rule/VariableParser.java

@@ -0,0 +1,24 @@
+package com.tzld.piaoquan.risk.control.service.rule;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class VariableParser {
+
+    public static Map<String, String[]> parseVariables(Set<String> variables) {
+        Map<String, String[]> result = new HashMap<>();
+        for (String var : variables) {
+            // 去除末尾下划线
+            String cleanedVar = var.replaceAll("_+$", "");
+            int lastUnderline = cleanedVar.lastIndexOf('_');
+            if (lastUnderline == -1) {
+                throw new IllegalArgumentException("Invalid variable format: " + var);
+            }
+            String type = cleanedVar.substring(0, lastUnderline);
+            String level = cleanedVar.substring(lastUnderline + 1);
+            result.put(var, new String[]{type, level});
+        }
+        return result;
+    }
+}

+ 260 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/DateUtil.java

@@ -0,0 +1,260 @@
+package com.tzld.piaoquan.risk.control.util;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * date
+ * @author supeng
+ */
+public final class DateUtil {
+
+    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    public static final String YYYY_MM_DD = "yyyy-MM-dd";
+
+    public static final String HH_MM_SS = "HH:mm:ss";
+
+    private static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
+
+    private static final DateTimeFormatter DEFAULT_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYY_MM_DD);
+
+    private static final DateTimeFormatter DEFAULT_TIME_FORMATTER = DateTimeFormatter.ofPattern(HH_MM_SS);
+
+    private static final Map<String, DateTimeFormatter> PATTEN_FORMATTER_MAPPER = new HashMap<>();
+
+    static {
+        PATTEN_FORMATTER_MAPPER.put(YYYY_MM_DD_HH_MM_SS, DEFAULT_DATE_TIME_FORMATTER);
+        PATTEN_FORMATTER_MAPPER.put(YYYY_MM_DD, DEFAULT_DATE_FORMATTER);
+        PATTEN_FORMATTER_MAPPER.put(HH_MM_SS, DEFAULT_TIME_FORMATTER);
+    }
+
+    private static DateTimeFormatter cacheFormatterAndGet(String patten) {
+        DateTimeFormatter dateTimeFormatter = PATTEN_FORMATTER_MAPPER.get(patten);
+        if (dateTimeFormatter == null) {
+            dateTimeFormatter = DateTimeFormatter.ofPattern(patten);
+            PATTEN_FORMATTER_MAPPER.put(patten, dateTimeFormatter);
+        }
+        return dateTimeFormatter;
+    }
+
+    /**
+     * @param localDateTime date time
+     * @return yyyy-MM-dd HH:mm:ss
+     */
+    public static String formatLocalDateTime(LocalDateTime localDateTime) {
+        return localDateTime.format(DEFAULT_DATE_TIME_FORMATTER);
+    }
+
+    /**
+     * @param localDateTime time
+     * @param patten        yyyy-MM-dd HH:mm:ss
+     * @return yyyy-MM-dd HH:mm:ss
+     */
+    public static String formatLocalDateTime(LocalDateTime localDateTime, String patten) {
+        DateTimeFormatter dateTimeFormatter = cacheFormatterAndGet(patten);
+        return localDateTime.format(dateTimeFormatter);
+    }
+
+    /**
+     * @param localDate date
+     * @param patten    only date patten
+     * @return yyyy-MM-dd
+     */
+    public static String formatLocalDate(LocalDate localDate, String patten) {
+        DateTimeFormatter dateTimeFormatter = cacheFormatterAndGet(patten);
+        return localDate.format(dateTimeFormatter);
+    }
+
+    /**
+     * @param localDate localDate
+     * @return yyyy-MM-dd
+     */
+    public static String formatLocalDate(LocalDate localDate) {
+        return localDate.format(DEFAULT_DATE_FORMATTER);
+    }
+
+    /**
+     * @param localTime localTime
+     * @param patten    patten
+     * @return HH:mm:ss
+     */
+    public static String formatLocalTime(LocalTime localTime, String patten) {
+        DateTimeFormatter dateTimeFormatter = cacheFormatterAndGet(patten);
+        return localTime.format(dateTimeFormatter);
+    }
+
+    /**
+     * @param localTime localTime
+     * @return HH:mm:ss
+     */
+    public static String formatLocalTime(LocalTime localTime) {
+        return localTime.format(DEFAULT_TIME_FORMATTER);
+    }
+
+    /**
+     * @param date   date time
+     * @param patten patten
+     * @return yyyy-MM-dd HH:mm:ss
+     */
+    public static String format(Date date, String patten) {
+        Instant instant = date.toInstant();
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+        return localDateTime.format(cacheFormatterAndGet(patten));
+    }
+
+    /**
+     * @param date date
+     * @return yyyy-MM-dd HH:mm:ss
+     */
+    public static String format(Date date) {
+        return format(date, YYYY_MM_DD_HH_MM_SS);
+    }
+
+    /**
+     * @param date date
+     * @return yyyy-MM-dd
+     */
+    public static String formatDate(Date date) {
+        return format(date, YYYY_MM_DD);
+    }
+
+    /**
+     * @param date date
+     * @return HH:mm:ss
+     */
+    public static String formatTime(Date date) {
+        return format(date, HH_MM_SS);
+    }
+
+    /**
+     * @param mills mills
+     * @return yyyy-MM-dd HH:mm:ss
+     */
+    public static String formatMills(long mills, String patten) {
+        Instant instant = Instant.ofEpochMilli(mills);
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+        return formatLocalDateTime(localDateTime, patten);
+    }
+
+    /**
+     * @param mills mills
+     * @return yyyy-MM-dd HH:mm:ss
+     */
+    public static String formatMills(long mills) {
+        return formatMills(mills, YYYY_MM_DD_HH_MM_SS);
+    }
+
+    /**
+     * @param mills mills
+     * @return yyyy-MM-dd
+     */
+    public static String formatMillsDate(long mills) {
+        return formatMills(mills, YYYY_MM_DD);
+    }
+
+    /**
+     * @param date date
+     * @return HH:mm:ss
+     */
+    public static String formatMillsTime(long date) {
+        return formatMills(date, HH_MM_SS);
+    }
+
+    /**
+     * @param date yyyy-MM-dd HH:mm:ss
+     * @return Date
+     */
+    public static Date parse(String date) {
+        LocalDateTime localDateTime = parseToLocalDateTime(date);
+        Instant instant = localDateTime.toInstant(OffsetDateTime.now().getOffset());
+        return Date.from(instant);
+    }
+
+    /**
+     * @param date   string date
+     * @param patten formatter patten
+     * @return LocalDateTime
+     */
+    public static LocalDateTime parseToLocalDateTime(String date, String patten) {
+        return LocalDateTime.parse(date, DateTimeFormatter.ofPattern(patten));
+    }
+
+    /**
+     * @param date yyyy-MM-dd HH:mm:ss
+     * @return LocalDateTime
+     */
+    public static LocalDateTime parseToLocalDateTime(String date) {
+        return LocalDateTime.parse(date, DEFAULT_DATE_TIME_FORMATTER);
+    }
+
+    /**
+     * @param date yyyy-MM-dd
+     * @return LocalDate
+     */
+    public static LocalDate parseToLocalDate(String date) {
+        return LocalDate.parse(date, DEFAULT_DATE_FORMATTER);
+    }
+
+    /**
+     * @param date HH:mm:ss
+     * @return LocalTime
+     */
+    public static LocalTime parseToLocalTime(String date) {
+        return LocalTime.parse(date, DEFAULT_TIME_FORMATTER);
+    }
+
+    /**
+     * @param dayStart date
+     * @param dayEnd   date
+     * @return
+     */
+    public static Period betweenDays(Date dayStart, Date dayEnd) {
+        LocalDateTime localDateTimeStart = LocalDateTime.ofInstant(dayStart.toInstant(), OffsetDateTime.now().getOffset());
+        LocalDateTime localDateTimeEnd = LocalDateTime.ofInstant(dayEnd.toInstant(), OffsetDateTime.now().getOffset());
+        return Period.between(localDateTimeStart.toLocalDate(), localDateTimeEnd.toLocalDate());
+    }
+
+    /**
+     * @param dayStart date
+     * @param dayEnd   date
+     * @return
+     */
+    public static Duration betweenTimes(Date dayStart, Date dayEnd) {
+        LocalDateTime localDateTimeStart = LocalDateTime.ofInstant(dayStart.toInstant(), OffsetDateTime.now().getOffset());
+        LocalDateTime localDateTimeEnd = LocalDateTime.ofInstant(dayEnd.toInstant(), OffsetDateTime.now().getOffset());
+        return Duration.between(localDateTimeStart, localDateTimeEnd);
+    }
+
+    /**
+     * @param dayStart date
+     * @param dayEnd   date
+     * @return
+     */
+    public static Period betweenDays(String dayStart, String dayEnd) {
+        return Period.between(parseToLocalDate(dayStart), parseToLocalDate(dayEnd));
+    }
+
+    /**
+     * @param dayStart date
+     * @param dayEnd   date
+     * @return
+     */
+    public static Duration betweenTimes(String dayStart, String dayEnd) {
+        return Duration.between(parseToLocalDateTime(dayStart), parseToLocalDateTime(dayEnd));
+    }
+
+    public static void main(String[] args) {
+//        LocalDateTime localDateTime = parseToLocalDateTime("2021-11-26","yyyy-MM-dd");
+        LocalDate localDate = parseToLocalDate("2021-11-26");
+//        System.out.println(formatLocalDateTime(localDateTime));
+        LocalDateTime startDateTime = localDate.atTime(0,0,0);
+        LocalDateTime endDateTime = localDate.plusDays(1L).atTime(0,0,0);
+        System.out.println(formatLocalDateTime(startDateTime));
+        System.out.println(formatLocalDateTime(endDateTime));
+//        System.out.println(formatLocalDate(localDate));
+    }
+}

+ 90 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/HttpClientUtil.java

@@ -0,0 +1,90 @@
+package com.tzld.piaoquan.risk.control.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * http util
+ *
+ * @author supeng
+ */
+public class HttpClientUtil {
+
+    private static final String DEFAULT_SHARED_KEY = "DEFAULT_SHARED_KEY";
+
+    /**
+     * 链接建立的超时时间 ms
+     */
+    private static final int DEFAULT_CONNECTION_TIMEOUT = 3000;
+    /**
+     * 响应超时时间 ms
+     */
+    private static final int DEFAULT_SOCKET_TIMEOUT = 3000;
+
+    /**
+     * 每个路由的最大连接数
+     */
+    private static final int DEFAULT_DEFAULT_MAX_PER_ROUTE = 50;
+
+    /**
+     * 最大连接数
+     */
+    private static final int DEFAULT_DEFAULT_MAX_TOTAL = 200;
+
+    /**
+     * 重试次数,默认0
+     */
+    private static final int DEFAULT_RETRY_COUNT = 0;
+
+    /**
+     * 从connection pool中获得一个connection的超时时间 ms
+     */
+    private static final int DEFAULT_CONNECTION_WAIT_TIMEOUT = 300;
+
+    private static final Map<String, HttpPoolClient> CREATED_HTTP_CLIENTS = new HashMap<>();
+
+    private static final Lock LOCK = new ReentrantLock();
+
+    private HttpClientUtil() {
+    }
+
+    public static HttpPoolClient useDefault() {
+        return createCached(DEFAULT_SHARED_KEY);
+    }
+
+
+    public static HttpPoolClient createCached(String cachedKey) {
+        HttpPoolClient httpPoolClient = CREATED_HTTP_CLIENTS.get(cachedKey);
+        if (httpPoolClient != null) {
+            return httpPoolClient;
+        }
+        LOCK.lock();
+        try {
+            httpPoolClient = CREATED_HTTP_CLIENTS.get(cachedKey);
+            if (httpPoolClient == null) {
+                httpPoolClient = create(DEFAULT_CONNECTION_TIMEOUT, DEFAULT_SOCKET_TIMEOUT, DEFAULT_DEFAULT_MAX_PER_ROUTE, DEFAULT_DEFAULT_MAX_TOTAL, DEFAULT_RETRY_COUNT, DEFAULT_CONNECTION_WAIT_TIMEOUT);
+                CREATED_HTTP_CLIENTS.put(cachedKey, httpPoolClient);
+            }
+        } finally {
+            LOCK.unlock();
+        }
+        return httpPoolClient;
+    }
+
+    /**
+     * 创建httpclient
+     *
+     * @param connectTimeout        连接超时时间 ms
+     * @param socketTimeout         读超时时间(等待数据超时时间)ms
+     * @param maxPerRoute           每个路由的最大连接数
+     * @param maxTotal              最大连接数
+     * @param retryCount            重试次数
+     * @param connectionWaitTimeout 连接等待超市时间 ms
+     * @return httpclient instance
+     */
+    public static HttpPoolClient create(int connectTimeout, int socketTimeout, int maxPerRoute, int maxTotal, int retryCount, int connectionWaitTimeout) {
+        return HttpPoolClient.create(connectTimeout, socketTimeout, maxPerRoute, maxTotal, retryCount, connectionWaitTimeout);
+    }
+}

+ 219 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/HttpPoolClient.java

@@ -0,0 +1,219 @@
+package com.tzld.piaoquan.risk.control.util;
+
+import com.google.common.collect.Lists;
+import com.tzld.piaoquan.risk.control.common.exception.HttpServiceException;
+import com.tzld.piaoquan.risk.control.common.exception.TimeoutException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+import javax.net.ssl.SSLContext;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * http client
+ * @author supeng
+ */
+public class HttpPoolClient {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpPoolClient.class);
+
+    private static final ScheduledExecutorService SCHEDULED_CLOSED_EXECUTOR = new ScheduledThreadPoolExecutor(1,
+            new BasicThreadFactory.Builder().namingPattern("http conn-closed-thread-%s").priority(Thread.NORM_PRIORITY).daemon(false).build(), (r, e) -> LOGGER.error(" monitor push reject task error={}", e.toString()));
+
+    private static final List<HttpClientConnectionManager> HTTP_CLIENT_CONNECTION_MANAGERS = Lists.newArrayList();
+
+    static {
+        SCHEDULED_CLOSED_EXECUTOR.schedule(() -> HTTP_CLIENT_CONNECTION_MANAGERS.forEach(HttpClientConnectionManager::closeExpiredConnections), 5, TimeUnit.SECONDS);
+    }
+
+    private CloseableHttpClient closeableHttpClient;
+
+    private HttpPoolClient(CloseableHttpClient closeableHttpClient) {
+        this.closeableHttpClient = closeableHttpClient;
+    }
+
+    private static HttpRequestInterceptor getInterceptor() {
+        HttpRequestInterceptor requestInterceptor = (request, context) -> {
+            try {
+                String missSpanId = MDC.get("missSpanId");
+                String missTraceId = MDC.get("request-id");
+                if (missTraceId != null && !"".equals(missTraceId.trim())) {
+                    request.setHeader("request-id", missTraceId);
+                }
+                if (missSpanId != null && !"".equals(missSpanId.trim())) {
+                    request.setHeader("missSpanId", missSpanId);
+                }
+            } catch (Exception e) {
+                LOGGER.error(e.getMessage(), e);
+            }
+        };
+        return requestInterceptor;
+    }
+
+
+    public Optional<String> get(String url) {
+        HttpGet httpGet = new HttpGet(url);
+        return request(httpGet);
+    }
+
+    public Optional<String> post(String url) {
+        HttpPost httpPost = new HttpPost(url);
+        return request(httpPost);
+    }
+
+
+    public Optional<String> postJson(String url, String json) {
+//        HttpPost httpPost = new HttpPost(url);
+//        if (StringUtils.isBlank(json)) {
+//            return request(httpPost);
+//        }
+//        StringEntity entity = new StringEntity(json, Charset.forName("UTF-8"));
+//        entity.setContentEncoding("UTF-8");
+//        entity.setContentType("application/json");
+//        httpPost.setEntity(entity);
+//        return request(httpPost);
+        return postJson(url, json, null);
+    }
+
+    public Optional<String> postJson(String url, String json, Map<String, String> headers) {
+        HttpPost httpPost = new HttpPost(url);
+        // 添加自定义请求头
+        if (headers != null && !headers.isEmpty()) {
+            for (Map.Entry<String, String> entry : headers.entrySet()) {
+                httpPost.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        if (StringUtils.isBlank(json)) {
+            return request(httpPost);
+        }
+        // 正确设置 Content-Type 和字符集
+        StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
+        httpPost.setEntity(entity);
+
+        return request(httpPost);
+    }
+
+    public Optional<String> request(HttpRequestBase request) {
+
+        if (LOGGER.isDebugEnabled()) {
+            String path = request.getURI().toString();
+            LOGGER.debug("http request url = {} ", path);
+        }
+        HttpEntity entity = null;
+        try {
+            CloseableHttpResponse response = request((HttpUriRequest) request);
+            if (response == null) {
+                throw new RuntimeException("call api exception no response");
+            }
+            entity = response.getEntity();
+            String content = null;
+            if (entity != null) {
+                content = EntityUtils.toString(entity, "UTF-8");
+            }
+            int httpStatus = response.getStatusLine().getStatusCode();
+            if (httpStatus == HttpStatus.SC_OK) {
+                return Optional.ofNullable(content);
+            }
+            String path = request.getURI().toString();
+            LOGGER.error("http call api {} fail response status {} content {}", path, httpStatus, content);
+            throw new HttpServiceException(httpStatus, content);
+        } catch (Exception e) {
+            if (e instanceof TimeoutException) {
+                throw (TimeoutException) e;
+            }
+            if (e instanceof HttpServiceException) {
+                throw (HttpServiceException) e;
+            }
+            throw new RuntimeException(e.getMessage(), e);
+        } finally {
+            if (request != null) {
+                request.abort();
+            }
+            EntityUtils.consumeQuietly(entity);
+        }
+    }
+
+
+    public CloseableHttpResponse request(HttpUriRequest request) {
+        try {
+            CloseableHttpResponse execute = closeableHttpClient.execute(request);
+            return execute;
+        } catch (Exception e) {
+            String path = request.getURI().toString();
+            if (e instanceof SocketTimeoutException) {
+                LOGGER.error(String.format("http timeout request url = %s .", path));
+                throw new TimeoutException();
+            } else {
+            }
+            throw new RuntimeException(String.format("http exception request url = %s ", path), e);
+        }
+    }
+
+    /**
+     * @param connectTimeout 连接超时时间 ms
+     * @param socketTimeout  读超时时间(等待数据超时时间)ms
+     * @param maxPerRoute    每个路由的最大连接数
+     * @param maxTotal       最大连接数
+     * @param retryCount     重试次数
+     * @return httpclient instance
+     */
+    protected static HttpPoolClient create(int connectTimeout, int socketTimeout, int maxPerRoute, int maxTotal, int retryCount, int connectionWaitTimeout) {
+        try {
+            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).setConnectionRequestTimeout(connectionWaitTimeout).build();
+            CloseableHttpClient client = HttpClientBuilder.create()
+                    .setDefaultRequestConfig(requestConfig)
+                    .setConnectionManager(createConnectionManager(maxPerRoute, maxTotal))
+                    .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, false)).addInterceptorFirst(getInterceptor()).build();
+            return new HttpPoolClient(client);
+        } catch (Throwable e) {
+            LOGGER.error("create HttpPoolClient exception", e);
+            throw new RuntimeException("create HttpPoolClient exception");
+        }
+    }
+
+    private static PoolingHttpClientConnectionManager createConnectionManager(int maxPerRoute, int maxTotal) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
+        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial((chain, authType) -> true).build();
+        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
+                .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)).build();
+        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
+        cm.setDefaultMaxPerRoute(maxPerRoute);
+        cm.setMaxTotal(maxTotal);
+        HTTP_CLIENT_CONNECTION_MANAGERS.add(cm);
+        return cm;
+    }
+
+}

+ 930 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/RedisUtil.java

@@ -0,0 +1,930 @@
+//package com.tzld.piaoquan.risk.control.util;
+//
+//import org.apache.ibatis.cache.CacheException;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.beans.factory.annotation.Qualifier;
+//import org.springframework.data.redis.core.Cursor;
+//import org.springframework.data.redis.core.RedisCallback;
+//import org.springframework.data.redis.core.RedisTemplate;
+//import org.springframework.data.redis.core.ScanOptions;
+//import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
+//import org.springframework.data.redis.core.script.DefaultRedisScript;
+//import org.springframework.scripting.support.StaticScriptSource;
+//import org.springframework.stereotype.Component;
+//
+//import java.util.*;
+//import java.util.concurrent.TimeUnit;
+//
+///**
+// * redis 操作
+// *
+// * @author supeng
+// * @date 2020/11/10
+// */
+//@Component
+//public class RedisUtil {
+//    /**
+//     * 锁前缀
+//     */
+//    private static final String LOCK_PREFIX = "LOCK_";
+//    /**
+//     * 默认重试次数
+//     */
+//    private static final Integer DEFAULT_RETRIES = 1;
+//    /**
+//     * 默认 10毫秒
+//     */
+//    private static final Long DEFAULT_INTERVAL = 10L;
+//
+//    /**
+//     * 加锁lua脚本
+//     */
+//    private static final String LOCK = "if (redis.call('exists', KEYS[1]) == 0) then " +
+//            "redis.call('hset', KEYS[1], ARGV[2], 1); " +
+//            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
+//            "return 1; " +
+//            "end; " +
+//            "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
+//            "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
+//            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
+//            "return 1; " +
+//            "end; " +
+//            "return 0;";
+//
+//    /**
+//     * 解锁 lua 脚本
+//     */
+//    public static final String UNLOCK = "if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then " +
+//            "return nil; " +
+//            "end; " +
+//            "local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1); " +
+//            "if (counter > 0) then " +
+//            "return 0; " +
+//            "else " +
+//            "redis.call('del', KEYS[1]); " +
+//            "return 1; " +
+//            "end; " +
+//            "return nil;";
+//
+//    @Qualifier("redisTemplate")
+//    @Autowired
+//    RedisTemplate<String, String> redisTemplate;
+//
+//    /** ================Key相关操作================ */
+//
+//    /**
+//     * 是否存在key
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Boolean hasKey(String key) {
+//        return redisTemplate.hasKey(key);
+//    }
+//
+//    /**
+//     * 删除 key
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Boolean deleteKey(String key) {
+//        return redisTemplate.delete(key);
+//    }
+//
+//    /**
+//     * 删除多个key
+//     *
+//     * @param keys
+//     * @return
+//     */
+//    public Long deleteKeys(Collection<String> keys) {
+//        return redisTemplate.delete(keys);
+//    }
+//
+//    /**
+//     * 设置key的过期时间
+//     *
+//     * @param key
+//     * @param timeout
+//     * @return
+//     */
+//    public Boolean expire(String key, long timeout) {
+//        if (timeout < 0) {
+//            return false;
+//        }
+//        return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
+//    }
+//
+//    /**
+//     * 获取key剩余的过期时间,秒
+//     *
+//     * @param key
+//     * @return key不存在,返回-2,key存在并且没有设置过期时间(永久有效),返回 -1
+//     */
+//    public Long getExpire(String key) {
+//        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+//    }
+//
+//    /** ================String相关操作================ */
+//
+//    /**
+//     * 获取 值
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public String get(String key) {
+//        return redisTemplate.opsForValue().get(key);
+//    }
+//
+//    /**
+//     * 获取多个值
+//     *
+//     * @param keys
+//     * @return
+//     */
+//    public List<String> multiGet(Collection<String> keys) {
+//        if (keys != null && keys.size() > 200) {
+//            throw new CacheException("too many keys, max 200");
+//        }
+//        return redisTemplate.opsForValue().multiGet(keys);
+//    }
+//
+//    /**
+//     * set 值
+//     *
+//     * @param key
+//     * @param value
+//     */
+//    public void set(String key, String value) {
+//        redisTemplate.opsForValue().set(key, value);
+//    }
+//
+//    /**
+//     * set 值并指定过期时间
+//     *
+//     * @param key
+//     * @param value
+//     * @param timeout
+//     */
+//    public void set(String key, String value, long timeout) {
+//        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
+//    }
+//
+//    /**
+//     * 设置String缓存值,只有key不存在时才能设置成功
+//     *
+//     * @param key
+//     * @param value
+//     * @return
+//     */
+//    public Boolean setNx(String key, String value) {
+//        return redisTemplate.opsForValue().setIfAbsent(key, value);
+//    }
+//
+//    /**
+//     * 设置String缓存值并指定过期时间,只有key不存在时才能设置成功
+//     *
+//     * @param key
+//     * @param value
+//     * @param timeout
+//     * @return
+//     */
+//    public Boolean setNx(String key, String value, long timeout) {
+//        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
+//    }
+//
+//    /**
+//     * getAndSet 值
+//     *
+//     * @param key
+//     * @param value
+//     * @return
+//     */
+//    public String getAndSet(String key, String value) {
+//        return redisTemplate.opsForValue().getAndSet(key, value);
+//    }
+//
+//    /**
+//     * key的值 +1
+//     *
+//     * @param key
+//     * @return
+//     * @version 1.0
+//     */
+//    public Long incr(String key) {
+//        return redisTemplate.opsForValue().increment(key);
+//    }
+//
+//    /**
+//     * key的值 +delta
+//     *
+//     * @param key
+//     * @param delta
+//     * @return
+//     * @version 1.0
+//     */
+//    public Long incrBy(String key, long delta) {
+//        return redisTemplate.opsForValue().increment(key, delta);
+//    }
+//
+//    /**
+//     * key的值 -1
+//     *
+//     * @param key
+//     * @return
+//     * @version 1.0
+//     */
+//    public Long decr(String key) {
+//        return redisTemplate.opsForValue().decrement(key);
+//    }
+//
+//    /**
+//     * key的值 -delta
+//     *
+//     * @param key
+//     * @param delta
+//     * @return
+//     * @version 1.0
+//     */
+//    public Long desrBy(String key, long delta) {
+//        return redisTemplate.opsForValue().decrement(key, delta);
+//    }
+//
+//    /** ================Hashes相关操作================ */
+//
+//    /**
+//     * 是否存在 hashkey
+//     *
+//     * @param key
+//     * @param hashKey
+//     * @return
+//     */
+//    public boolean hasHashKey(String key, String hashKey) {
+//        return redisTemplate.opsForHash().hasKey(key, hashKey);
+//    }
+//
+//    /**
+//     * 获取hash某个字段的值
+//     *
+//     * @param key
+//     * @param hashKey
+//     * @return
+//     */
+//    public Object hget(String key, String hashKey) {
+//        return redisTemplate.opsForHash().get(key, hashKey);
+//    }
+//
+//    /**
+//     * 获取hash所有键值(hashkey超过200个时禁止使用)
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Map<Object, Object> hgetAll(String key) {
+//        return redisTemplate.opsForHash().entries(key);
+//    }
+//
+//    /**
+//     * 获取hash对应多个字段的值
+//     *
+//     * @param key
+//     * @param hashKeys
+//     * @return
+//     */
+//    public List<Object> hmget(String key, List<Object> hashKeys) {
+//        return redisTemplate.opsForHash().multiGet(key, hashKeys);
+//    }
+//
+//    /**
+//     * @param key
+//     * @param map
+//     * @return
+//     */
+//    public boolean hmset(String key, Map<String, Object> map) {
+//        try {
+//            redisTemplate.opsForHash().putAll(key, map);
+//            return true;
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//            return false;
+//        }
+//    }
+//
+//    /**
+//     * 不保证原子性
+//     *
+//     * @param key
+//     * @param map
+//     * @param expire
+//     * @return
+//     */
+//    public boolean hmset(String key, Map<String, Object> map, long expire) {
+//        try {
+//            redisTemplate.opsForHash().putAll(key, map);
+//            expire(key, expire);
+//            return true;
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//            return false;
+//        }
+//    }
+//
+//    /**
+//     * 设置hash中某个字段的值,如果不存在将创建
+//     *
+//     * @param key
+//     * @param hashKey
+//     * @param value
+//     */
+//    public void hset(String key, String hashKey, String value) {
+//        redisTemplate.opsForHash().put(key, hashKey, value);
+//    }
+//
+//    /**
+//     * 设置hash中某个字段的值并指定key的过期时间,如果不存在将创建
+//     *
+//     * @param key
+//     * @param hashKey
+//     * @param value
+//     * @param timeout 单位:秒
+//     */
+//    public void hset(String key, String hashKey, String value, long timeout) {
+//        redisTemplate.opsForHash().put(key, hashKey, value);
+//        expire(key, timeout);
+//    }
+//
+//    /**
+//     * 设置hash中某个字段的值,只有当这个字段不存在时才能设置成功
+//     *
+//     * @param key
+//     * @param hashKey
+//     * @param value
+//     * @return
+//     */
+//    public Boolean hsetNx(String key, String hashKey, String value) {
+//        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
+//    }
+//
+//    /**
+//     * 删除hash中的多个字段
+//     *
+//     * @param key
+//     * @param hashKey
+//     */
+//    public void hdel(String key, Object... hashKey) {
+//        redisTemplate.opsForHash().delete(key, hashKey);
+//    }
+//
+//    /**
+//     * 将hash中的某个字段值增加delta
+//     *
+//     * @param key
+//     * @param hashKey
+//     * @param delta
+//     * @return
+//     */
+//    public Long hincrBy(String key, String hashKey, long delta) {
+//        return redisTemplate.opsForHash().increment(key, hashKey, delta);
+//    }
+//
+//    /** ================Lists相关操作================ */
+//
+//    /**
+//     * 返回list中指定范围的元素,第一个元素为0,最后一个元素为-1,倒数第二个为-2,以此类推。
+//     *
+//     * @param key
+//     * @param start
+//     * @param end
+//     * @return
+//     */
+//    public List<String> lrange(String key, long start, long end) {
+//        return redisTemplate.opsForList().range(key, start, end);
+//    }
+//
+//    /**
+//     * 获取list的长度
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Long llen(String key) {
+//        return redisTemplate.opsForList().size(key);
+//    }
+//
+//    /**
+//     * 获取list中index索引的元素
+//     *
+//     * @param key
+//     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+//     * @return
+//     */
+//    public String lindex(String key, long index) {
+//        return redisTemplate.opsForList().index(key, index);
+//    }
+//
+//    /**
+//     * 向list的尾部插入指定的元素
+//     *
+//     * @param key
+//     * @param value
+//     * @return
+//     */
+//    public Long rpush(String key, String value) {
+//        return redisTemplate.opsForList().rightPush(key, value);
+//    }
+//
+//    /**
+//     * 向list的尾部插入多个元素
+//     *
+//     * @param key
+//     * @param values
+//     * @return
+//     */
+//    public Long rpushAll(String key, Collection<String> values) {
+//        return redisTemplate.opsForList().rightPushAll(key, values);
+//    }
+//
+//    /**
+//     * 移除并返回list尾部的元素
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public String rpop(String key) {
+//        return redisTemplate.opsForList().rightPop(key);
+//    }
+//
+//    /**
+//     * 向list的头部插入指定的元素
+//     *
+//     * @param key
+//     * @param value
+//     * @return
+//     */
+//    public Long lpush(String key, String value) {
+//        return redisTemplate.opsForList().leftPush(key, value);
+//    }
+//
+//    /**
+//     * 向list的头部插入多个元素
+//     *
+//     * @param key
+//     * @param values
+//     * @return
+//     */
+//    public Long lpushAll(String key, Collection<String> values) {
+//        return redisTemplate.opsForList().leftPushAll(key, values);
+//    }
+//
+//    /**
+//     * 移除并返回list头部的元素
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public String lpop(String key) {
+//        return redisTemplate.opsForList().leftPop(key);
+//    }
+//
+//    /**
+//     * 修改list中index索引对应元素的值
+//     *
+//     * @param key
+//     * @param index
+//     * @param value
+//     */
+//    public void lset(String key, long index, String value) {
+//        redisTemplate.opsForList().set(key, index, value);
+//    }
+//
+//    /**
+//     * 从list中移除前count个出现值为value的元素
+//     *
+//     * @param key
+//     * @param count count > 0: 从头往尾移除值为 value 的元素。</br>
+//     *              count < 0: 从尾往头移除值为 value 的元素。</br>
+//     *              count = 0: 移除所有值为 value 的元素。
+//     * @param value
+//     * @return
+//     */
+//    public Long lrem(String key, long count, String value) {
+//        return redisTemplate.opsForList().remove(key, count, value);
+//    }
+//
+//    /**
+//     * lpop多个数据
+//     *
+//     * @param key
+//     * @param count
+//     * @return
+//     */
+//    public List<Object> lpopMutil(String key, long count) {
+//        List<Object> result = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
+//            connection.lRange(key.getBytes(), 0, count - 1);
+//            connection.lTrim(key.getBytes(), count, -1);
+//            return null;
+//        });
+//        return result;
+//    }
+//
+//    /**
+//     * rpop多个数据
+//     *
+//     * @param key
+//     * @param start 起始位置  end=-1
+//     * @return
+//     */
+//    public List<Object> rpopMutil(String key, long start) {
+//        List<Object> result = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
+//            connection.lRange(key.getBytes(), start, -1);
+//            connection.lTrim(key.getBytes(), 0, start - 1);
+//            return null;
+//        });
+//        return result;
+//    }
+//
+//    /**
+//     * 从右取多个值
+//     *
+//     * @param key
+//     * @param count 个数
+//     * @return
+//     */
+//    public List<Object> rpopMutil(String key, int count) {
+//        List<Object> result = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
+//            connection.lRange(key.getBytes(), 0 - count, -1);
+//            connection.lTrim(key.getBytes(), 0, -1 - count);
+//            return null;
+//        });
+//        return result;
+//    }
+//
+//    /** ================Sets相关操作================ */
+//
+//    /**
+//     * set集合中是否存在值为value的元素
+//     *
+//     * @param key
+//     * @param value
+//     * @return
+//     */
+//    public boolean sIsMember(String key, String value) {
+//        return redisTemplate.opsForSet().isMember(key, value);
+//    }
+//
+//    /**
+//     * 返回set集合所有元素,禁止直接使用members,通过scan获取
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Set<String> sscan(String key) {
+//        Set<String> result = new HashSet<>();
+//        Cursor<String> cursor = redisTemplate.opsForSet().scan(key, ScanOptions.NONE);
+//        while (cursor != null && cursor.hasNext()) {
+//            result.add(cursor.next());
+//        }
+//        return result;
+//    }
+//
+//    /**
+//     * 向set集合添加多个元素
+//     *
+//     * @param key
+//     * @param values
+//     * @return
+//     */
+//    public Long sadd(String key, String... values) {
+//        return redisTemplate.opsForSet().add(key, values);
+//    }
+//
+//    /**
+//     * 返回set集合的元素个数
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Long scard(String key) {
+//        return redisTemplate.opsForSet().size(key);
+//    }
+//
+//    /**
+//     * 从set中移除值为value的元素
+//     *
+//     * @param key
+//     * @param values
+//     * @return
+//     */
+//    public Long srem(String key, Object... values) {
+//        return redisTemplate.opsForSet().remove(key, values);
+//    }
+//
+//    /** ================Sorted Sets相关操作================ */
+//
+//    /**
+//     * 向zset集合添加一个元素,或者更新已存在元素的分数
+//     *
+//     * @param key
+//     * @param vaule
+//     * @param score
+//     */
+//    public void zadd(String key, String vaule, double score) {
+//        redisTemplate.opsForZSet().add(key, vaule, score);
+//    }
+//
+//    /**
+//     * 向zset集合添加一个元素,或者更新已存在元素的分数,并更新key的过期时间
+//     *
+//     * @param key
+//     * @param vaule
+//     * @param score
+//     */
+//    public void zadd(String key, String vaule, double score, long timeout) {
+//        redisTemplate.opsForZSet().add(key, vaule, score);
+//        expire(key, timeout);
+//    }
+//
+//    /**
+//     * 获取某个元素的分数
+//     *
+//     * @param key
+//     * @param value
+//     * @return
+//     */
+//    public Double zscore(String key, String value) {
+//        return redisTemplate.opsForZSet().score(key, value);
+//    }
+//
+//    /**
+//     * 获取zset集合的元素数量
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Long zcard(String key) {
+//        return redisTemplate.opsForZSet().zCard(key);
+//    }
+//
+//    /**
+//     * zset 范围内元素个数
+//     *
+//     * @param key
+//     * @param min
+//     * @param max
+//     * @return
+//     */
+//    public Long zcount(String key, double min, double max) {
+//        return redisTemplate.opsForZSet().count(key, min, max);
+//    }
+//
+//    /**
+//     * 通过索引区间返回zset集合成指定区间内的元素,分数从小到大排序
+//     *
+//     * @param key
+//     * @param start
+//     * @param end
+//     * @return
+//     */
+//    public Set<String> zrange(String key, long start, long end) {
+//        return redisTemplate.opsForZSet().range(key, start, end);
+//    }
+//
+//    /**
+//     * 通过索引区间返回zset集合成指定区间内的元素,分数从大到小排序
+//     *
+//     * @param key
+//     * @param start
+//     * @param end
+//     * @return
+//     */
+//    public Set<String> zreverseRange(String key, long start, long end) {
+//        return redisTemplate.opsForZSet().reverseRange(key, start, end);
+//    }
+//
+//    /**
+//     * 通过索引区间返回zset集合成指定区间内的元素及分数
+//     *
+//     * @param key
+//     * @param start
+//     * @param end
+//     * @return
+//     */
+//    public List<Map<String, Double>> zrangeWithScores(String key, long start, long end) {
+//        List<Map<String, Double>> result = new ArrayList<>();
+//        Set<TypedTuple<String>> cursor = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
+//        for (Iterator<TypedTuple<String>> it = cursor.iterator(); it.hasNext(); ) {
+//            TypedTuple<String> item = it.next();
+//            Map<String, Double> map = new HashMap<>();
+//            map.put(item.getValue(), item.getScore());
+//            result.add(map);
+//        }
+//        return result;
+//    }
+//
+//    /**
+//     * 通过索引区间返回zset集合成指定区间内的元素及分数
+//     *
+//     * @param key
+//     * @param start
+//     * @param end
+//     * @return
+//     */
+//    public List<Map<String, Object>> zrangeWithScoresToList(String key, long start, long end) {
+//        List<Map<String, Object>> result = new ArrayList<>();
+//        Set<TypedTuple<String>> cursor = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
+//        for (Iterator<TypedTuple<String>> it = cursor.iterator(); it.hasNext(); ) {
+//            TypedTuple<String> item = it.next();
+//            Map<String, Object> map = new HashMap<>();
+//            map.put("value", item.getValue());
+//            map.put("score", item.getScore());
+//            result.add(map);
+//        }
+//        return result;
+//    }
+//
+//    /**
+//     * 通过索引区间返回zset集合成指定区间内的元素及分数
+//     *
+//     * @param key
+//     * @param start
+//     * @param end
+//     * @return map
+//     */
+//    public Map<String, Double> zrangeWithScoresToMap(String key, long start, long end) {
+//        Map<String, Double> result = new HashMap<>();
+//        Set<TypedTuple<String>> cursor = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
+//        for (Iterator<TypedTuple<String>> it = cursor.iterator(); it.hasNext(); ) {
+//            TypedTuple<String> item = it.next();
+//            result.put(item.getValue(), item.getScore());
+//        }
+//        return result;
+//    }
+//
+//    /**
+//     * 迭代zset集合中的元素(只包括元素成员)
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Set<String> zscanValues(String key) {
+//        Set<String> result = new HashSet<>();
+//        Cursor<TypedTuple<String>> cursor = redisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
+//        while (cursor.hasNext()) {
+//            TypedTuple<String> item = cursor.next();
+//            result.add(item.getValue());
+//        }
+//        return result;
+//    }
+//
+//    /**
+//     * 迭代zset集合中的元素(包括元素和分数)
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public List<Map<String, Double>> zscan(String key) {
+//        List<Map<String, Double>> result = new ArrayList<Map<String, Double>>();
+//        Cursor<TypedTuple<String>> cursor = redisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
+//        while (cursor.hasNext()) {
+//            TypedTuple<String> item = cursor.next();
+//            Map<String, Double> map = new HashMap<String, Double>();
+//            map.put(item.getValue(), item.getScore());
+//            result.add(map);
+//        }
+//        return result;
+//
+//    }
+//
+//    /**
+//     * 移除zset集合中的元素
+//     *
+//     * @param key
+//     * @param values
+//     */
+//    public void zRemove(String key, Object... values) {
+//        redisTemplate.opsForZSet().remove(key, values);
+//    }
+//
+//    /**
+//     * 移除zset集合指定范围数据
+//     *
+//     * @param key
+//     * @param start
+//     * @param end
+//     */
+//    public void zRemRange(String key, long start, long end) {
+//        redisTemplate.opsForZSet().removeRange(key, start, end);
+//    }
+//
+//
+//    /**
+//     * 删除
+//     *
+//     * @param key
+//     * @param min
+//     * @param max
+//     */
+//    public void zRemRangeByScore(String key, double min, double max) {
+//        redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
+//    }
+//
+//    /**
+//     * 加锁 不可重入
+//     *
+//     * @param key
+//     * @param timeout 毫秒
+//     * @return
+//     */
+//    public Boolean lock(String key, long timeout) {
+//        return redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + key, "1", timeout, TimeUnit.MILLISECONDS);
+//    }
+//
+//    /**
+//     * 解锁 不可重入
+//     *
+//     * @param key
+//     * @return
+//     */
+//    public Boolean unlock(String key) {
+//        return redisTemplate.delete(LOCK_PREFIX + key);
+//    }
+//
+//    /**
+//     * 加锁
+//     *
+//     * @param key         key
+//     * @param reentrantId 重入Id
+//     * @param timeout     超时时间 毫秒ms
+//     * @return
+//     */
+//    public Boolean lock(String key, String reentrantId, long timeout) {
+//        return lock(key, reentrantId, timeout, DEFAULT_RETRIES);
+//    }
+//
+//    /**
+//     * 加锁
+//     *
+//     * @param key         key
+//     * @param reentrantId 重入Id
+//     * @param timeout     超时时间 毫秒ms
+//     * @param retries     重试次数
+//     * @return
+//     */
+//    public Boolean lock(String key, String reentrantId, long timeout, int retries) {
+//        return lock(key, reentrantId, timeout, retries, DEFAULT_INTERVAL);
+//    }
+//
+//    /**
+//     * 加锁
+//     *
+//     * @param key         key
+//     * @param reentrantId 重入Id
+//     * @param timeout     超时时间 毫秒ms
+//     * @param retries     重试次数
+//     * @param interval    每次重试间隔时间 毫秒
+//     * @return
+//     */
+//    public Boolean lock(String key, String reentrantId, long timeout, int retries, long interval) {
+//        String lockKey = LOCK_PREFIX + key;
+//        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
+//        script.setResultType(Long.class);
+//        script.setScriptSource(new StaticScriptSource(LOCK));
+//        for (int i = 0; i < retries; i++) {
+//            Object result = redisTemplate.execute(script, Arrays.asList(lockKey), String.valueOf(timeout), reentrantId);
+//            if (Objects.nonNull(result) && Objects.equals(1L, Long.valueOf(result.toString()))) {
+//                return true;
+//            }
+//            try {
+//                TimeUnit.MILLISECONDS.sleep(interval);
+//            } catch (InterruptedException e) {
+//                e.printStackTrace();
+//                return null;
+//            }
+//        }
+//        return false;
+//    }
+//
+//    /**
+//     * 解锁
+//     *
+//     * @param key         key
+//     * @param reentrantId 重入ID
+//     */
+//    public Boolean unlock(String key, String reentrantId) {
+//        String lockKey = LOCK_PREFIX + key;
+//        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
+//        script.setResultType(Long.class);
+//        script.setScriptSource(new StaticScriptSource(UNLOCK));
+//        Object result = redisTemplate.execute(script, Arrays.asList(lockKey), reentrantId);
+//        if (Objects.isNull(result)) {
+//            return null;
+//        }
+//        if (Objects.equals(1L, Long.valueOf(result.toString()))) {
+//            return true;
+//        }
+//        return false;
+//    }
+//
+//}

+ 120 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/RetryUtil.java

@@ -0,0 +1,120 @@
+package com.tzld.piaoquan.risk.control.util;
+
+import com.tzld.piaoquan.risk.control.common.exception.StopRetryException;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@Slf4j
+public class RetryUtil {
+
+    private static final Integer MAX_RETRIES = 3;
+
+    /**
+     * 重试
+     *
+     * @param action
+     * @param actionName
+     * @param <T>
+     * @return
+     */
+    public static <T> T executeWithRetry(Supplier<T> action, String actionName) {
+        return executeWithRetry(action, MAX_RETRIES, actionName, null);
+    }
+
+    /**
+     * 重试
+     *
+     * @param action
+     * @param maxRetries
+     * @param actionName
+     * @param <T>
+     * @return
+     */
+    public static <T> T executeWithRetry(Supplier<T> action, Integer maxRetries, String actionName) {
+        return executeWithRetry(action, maxRetries, actionName, null);
+    }
+
+    /**
+     * 重试
+     *
+     * @param action
+     * @param maxRetries
+     * @param actionName
+     * @param retryIntervalProvider 毫秒
+     * @param <T>
+     * @return
+     */
+    public static <T> T executeWithRetry(Supplier<T> action, Integer maxRetries, String actionName, Function<Integer, Long> retryIntervalProvider) {
+        for (int i = 1; i <= maxRetries; i++) {
+            try {
+                return action.get();
+            } catch (Exception e) {
+                log.error("{} failed on attempt {}: {}", actionName, i, e.getMessage(), e);
+                if (e instanceof StopRetryException) {
+                    log.error("{} failed on attempt {} due to StopRetryException. Stopping retries.", actionName, i);
+                    throw new RuntimeException(actionName + " failed due to Stopping retries.", e);
+                }
+                if (i < maxRetries) {
+                    if (Objects.nonNull(retryIntervalProvider) && Objects.nonNull(retryIntervalProvider.apply(i))) {
+                        long interval = retryIntervalProvider.apply(i);
+                        if (interval > 0) {
+                            try {
+                                TimeUnit.MILLISECONDS.sleep(interval);
+                            } catch (InterruptedException ex) {
+                                log.error("{} failed on attempt {}: {}", actionName, i, ex.getMessage(), ex);
+                            }
+                        }
+                    }
+                    log.info("{} will be retried (attempt {}/{})", actionName, i + 1, maxRetries);
+                } else {
+                    log.error("{} failed after {} retries due to non-IO error.", actionName, maxRetries);
+                    throw new RuntimeException(actionName + " failed after multiple retries due to error.", e);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param i 第i次
+     * @return 毫秒
+     */
+    public static long retryInterval(int i) {
+        if (i == 1) {
+            return 3 * 1000L;
+        }
+        if (i == 2) {
+            return 5 * 1000L;
+        }
+        if (i == 3) {
+            return 10 * 1000L;
+        }
+        if (i == 4) {
+            return 20 * 1000L;
+        }
+        if (i == 5) {
+            return 30 * 1000L;
+        }
+        if (i == 6) {
+            return 40 * 1000L;
+        }
+        if (i == 7) {
+            return 50 * 1000L;
+        }
+        if (i == 8) {
+            return 60 * 1000L;
+        }
+        if (i == 9) {
+            return 60 * 1000L;
+        }
+        if (i == 10) {
+            return 60 * 1000L;
+        }
+        return 0L;
+    }
+
+}

+ 80 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/util/UrlUtil.java

@@ -0,0 +1,80 @@
+package com.tzld.piaoquan.risk.control.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.util.Objects;
+
+/**
+ * @author supeng
+ */
+public class UrlUtil {
+    /**
+     * 返回http Url
+     *
+     * @param httpDomain
+     * @param url
+     * @return
+     */
+    public static String processUrl(String httpDomain, String url) {
+        if (Objects.isNull(url)) {
+            return null;
+        }
+        String lowerUrl = url.toLowerCase();
+        if (!lowerUrl.startsWith("http://") && !lowerUrl.startsWith("https://")) {
+            return httpDomain + url;
+        } else {
+            return url;
+        }
+    }
+
+    public static boolean isM3u8ByExtension(String filePath) {
+        if (Objects.isNull(filePath) || filePath.trim().isEmpty()) {
+            return false;
+        }
+        return filePath.toLowerCase().endsWith(".m3u8");
+    }
+
+    public static String getFileFormatFromUrl(String url) {
+        if (url == null || url.isEmpty()) {
+            return null;
+        }
+
+        try {
+            url = URLDecoder.decode(url, "utf-8");
+            URI uri = new URI(url);
+            // 自动解码百分比编码
+            String path = uri.getPath();
+
+            String[] pathSegments = path.split("/");
+            String filename = "";
+            // 从后往前查找最后一个非空的路径段作为文件名
+            for (int i = pathSegments.length - 1; i >= 0; i--) {
+                if (!pathSegments[i].isEmpty()) {
+                    filename = pathSegments[i];
+                    break;
+                }
+            }
+
+            int lastDotIndex = filename.lastIndexOf('.');
+            if (lastDotIndex != -1 && lastDotIndex < filename.length() - 1) {
+                return filename.substring(lastDotIndex + 1).toLowerCase();
+            } else {
+                // 无扩展名
+                return "";
+            }
+        } catch (URISyntaxException | UnsupportedEncodingException e) {
+            return "";
+        }
+    }
+
+    public static void main(String[] args) {
+        System.out.println("1-"+getFileFormatFromUrl("http://www.baidu.com"));
+        System.out.println("2-"+getFileFormatFromUrl("https://p26-sign.douyinpic.com/tos-cn-i-dy/8a3389f159994fd3ac6a25f19a8ccf1c~tplv-dy-resize-walign-adapt-aq:540:q75.jpeg?lk3s=138a59ce&x-expires=1747911600&x-signature=rPrBxvo5sw6Am9HAGUxlowwQk%2Fk%3D&from=327834062&s=PackSourceEnum_PUBLISH&se=false&sc=cover&biz_tag=aweme_video&l=20250508195515F7E21D4D81B4C003D6B5"));
+        System.out.println("3-"+getFileFormatFromUrl("https://p26.douyinpic.com/aweme/100x100/aweme-avatar/tos-cn-avt-0015_14ceddcb8d4ae3dad93e6784e2765d15.jpeg?from=2956013662"));
+        System.out.println("33-"+getFileFormatFromUrl("https://p11.douyinpic.com/aweme/100x100/aweme-avatar/tos-cn-avt-0015_8baa644e98ebc9b8edcf2d07258d0406.jpeg?from=2956013662"));
+        System.out.println("4-"+getFileFormatFromUrl("https://v5-h.douyinvod.com/f1f6de57cfa3b511e60fe0e5cf30f78c/6811d38e/video/tos/cn/tos-cn-ve-15c000-ce/oU4E5v1mIR8Ae8DCkPTDfEe9hzAFQ42wTucoEq/?a=1128\\u0026ch=10\\u0026cr=3\\u0026dr=0\\u0026lr=all\\u0026cd=0%7C0%7C0%7C3\\u0026cv=1\\u0026br=1500\\u0026bt=1500\\u0026cs=0\\u0026ds=6\\u0026ft=OXXf~77JWH6BMZ7Df_r0PD1IN\\u0026mime_type=video_mp4\\u0026qs=0\\u0026rc=Zzk5M2c5aDxkZDlnOzY5ZkBpM2xtZ3g5cnZwMzMzbGkzNEAyNS4vNWAuXl4xXjVhNjYuYSNjaV8xMmRzZDRhLS1kLWJzcw%3D%3D\\u0026btag=c0010e000ad200\\u0026cquery=103S_100b_105O_103Q_103R\\u0026dy_q=1745995026\\u0026feature_id=f0150a16a324336cda5d6dd0b69ed299\\u0026l=202504301437051266DB29BE2902786D43"));
+        System.out.println("5-"+getFileFormatFromUrl("https://v3-c.douyinvod.com/0958ba0af49b57b46d3a01c7afe4e9e9/681ca9cf/video/tos/cn/tos-cn-ve-15c001-alinc2/okFAMe0EaXhBkBgRg6NvOPiaiAAecQD90FVpHI/?a=1128&ch=10&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=2094&bt=2094&cs=0&ds=6&ft=OXXf~77JWH6BMgHxt_r0PD1IN&mime_type=video_mp4&qs=0&rc=ODZmaDo5ZDg2NThkNTRkOkBpajQ0b3c5cnR3MzMzNGkzM0AxMWM1YS4wXjQxLjFiMmBhYSMvX3BoMmQ0cWBhLS1kLWFzcw%3D%3D&btag=c0010e00090000&cquery=105O_103Q_103W_103Y_100b&dy_q=1746705315&feature_id=f0150a16a324336cda5d6dd0b69ed299&l=20250508195515F7E21D4D81B4C003D6B5"));
+    }
+}

+ 53 - 0
risk-control-core/src/main/resources/mybatis-generator-config.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE generatorConfiguration
+        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
+        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
+<!-- 配置生成器 -->
+<generatorConfiguration>
+    <context id="mysql" defaultModelType="flat">
+        <property name="autoDelimitKeywords" value="true"/>
+        <!-- 生成的Java文件的编码 -->
+        <property name="javaFileEncoding" value="UTF-8"/>
+        <!-- 格式化java代码 -->
+        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
+        <!-- 格式化XML代码 -->
+        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
+        <!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; -->
+        <property name="beginningDelimiter" value="`"/>
+        <property name="endingDelimiter" value="`"/>
+
+        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"></plugin>
+        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
+
+        <commentGenerator>
+            <property name="addRemarkComments" value="true"/>
+        </commentGenerator>
+
+        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
+                        connectionURL="jdbc:mysql://rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com:3306/longvideo?useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull&amp;useSSL=false"
+                        userId="wx2016_longvideo" password="wx2016_longvideoP@assword1234">
+        </jdbcConnection>
+
+        <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
+            <property name="forceBigDecimals" value="false"/>
+        </javaTypeResolver>
+
+        <javaModelGenerator targetPackage="com.tzld.piaoquan.risk.control.model.po" targetProject="src/main/java">
+            <property name="constructorBased" value="false"/>
+            <property name="enableSubPackages" value="true"/>
+            <property name="immutable" value="false"/>
+        </javaModelGenerator>
+
+        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
+            <property name="enableSubPackages" value="true"/>
+        </sqlMapGenerator>
+
+        <javaClientGenerator targetPackage="com.tzld.piaoquan.risk.control.dao.mapper" type="XMLMAPPER" targetProject="src/main/java">
+            <property name="enableSubPackages" value="true"/>
+        </javaClientGenerator>
+
+<!--        <table tableName="wx_video" domainObjectName="WxVideo" alias=""/>-->
+
+    </context>
+
+</generatorConfiguration>

+ 48 - 0
risk-control-server/pom.xml

@@ -0,0 +1,48 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.tzld.piaoquan</groupId>
+        <artifactId>risk-control</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>risk-control-server</artifactId>
+    <name>risk-control-server</name>
+    <description>risk-control-server for Spring Boot</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.tzld.piaoquan</groupId>
+            <artifactId>risk-control-core</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>risk-control-server</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>com.tzld.piaoquan.risk.control.Application</mainClass>
+                    <layout>ZIP</layout>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+
+    </build>
+
+</project>

+ 31 - 0
risk-control-server/src/main/java/com/tzld/piaoquan/risk/control/Application.java

@@ -0,0 +1,31 @@
+package com.tzld.piaoquan.risk.control;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * 启动类
+ *
+ * @author supeng
+ */
+@SpringBootApplication
+@ServletComponentScan("com.tzld.piaoquan.risk.control")
+@MapperScan("com.tzld.piaoquan.risk.control.dao")
+@EnableFeignClients
+@EnableEurekaClient
+@EnableSwagger2
+public class Application {
+    private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+        LOGGER.info("risk-control SpringBoot Start Success");
+    }
+}

+ 30 - 0
risk-control-server/src/main/java/com/tzld/piaoquan/risk/control/controller/IndexController.java

@@ -0,0 +1,30 @@
+package com.tzld.piaoquan.risk.control.controller;
+
+import com.tzld.piaoquan.risk.control.common.annotation.NoRequestLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ *
+ */
+@RestController
+@RequestMapping("/")
+public class IndexController {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(IndexController.class);
+
+    /**
+     * 探活
+     *
+     * @return
+     */
+    @NoRequestLog
+    @GetMapping("/healthcheck")
+    public String healthcheck() {
+        LOGGER.info("I'm ok");
+        return "ok";
+    }
+}

+ 65 - 0
risk-control-server/src/main/resources/application-dev.yml

@@ -0,0 +1,65 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://deveureka-internal.piaoquantv.com/eureka/
+
+spring:
+  datasource:
+    driver-class-name: com.mysql.jdbc.Driver
+    url: jdbc:mysql://rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com:3306/longvideo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 10
+      maximum-pool-size: 20
+      connection-test-query: SELECT 1
+
+logging:
+  file:
+    path: ./datalog/weblog/${project.name}
+app:
+  id: risk-control
+apollo:
+  bootstrap:
+    enabled: true
+    namespaces: application
+  meta: http://devapolloconfig-internal.piaoquantv.com
+  cacheDir: /datalog/apollo-cache-dir
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: risk-control
+    logstore:
+      request: request-log-test
+      error: error-log-test
+  oss:
+    endpoint: oss-cn-hangzhou.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+
+xxl:
+  job:
+    admin:
+      addresses: http://127.0.0.1:8080/xxl-job-admin
+    accessToken:
+    executor:
+      appname: ${project.name}
+      address:
+      ip:
+      port: 9999
+      logpath: /datalog/weblog/${project.name}/xxl-job/
+      logretentiondays: 30

+ 64 - 0
risk-control-server/src/main/resources/application-pre.yml

@@ -0,0 +1,64 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://preeureka-internal.piaoquantv.com/eureka/
+
+spring:
+  datasource:
+    driver-class-name: com.mysql.jdbc.Driver
+    url: jdbc:mysql://rm-bp1jjv3jv98133plv285-vpc-rw.mysql.rds.aliyuncs.com:3306/longvideo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 10
+      maximum-pool-size: 20
+      connection-test-query: SELECT 1
+
+logging:
+  file:
+    path: /datalog/weblog/${project.name}
+app:
+  id: risk-control
+apollo:
+  bootstrap:
+    enabled: true
+    namespaces: application
+  meta: http://preapolloconfig-internal.piaoquantv.com
+  cacheDir: /datalog/apollo-cache-dir
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: risk-control
+    logstore:
+      request: request-log
+      error: error-log
+  oss:
+    endpoint: oss-cn-hangzhou-internal.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+
+
+xxl:
+  job:
+    admin:
+      addresses: http://xxl-job-pre-internal.piaoquantv.com/xxl-job-admin
+    accessToken:
+    executor:
+      appname: ${project.name}
+      address:
+      ip:
+      port: 9999
+      logpath: /datalog/weblog/${project.name}/xxl-job/
+      logretentiondays: 30

+ 65 - 0
risk-control-server/src/main/resources/application-prod.yml

@@ -0,0 +1,65 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://eureka-internal.piaoquantv.com/eureka/
+
+spring:
+  datasource:
+    driver-class-name: com.mysql.jdbc.Driver
+    url: jdbc:mysql://rm-bp1jjv3jv98133plv285-vpc-rw.mysql.rds.aliyuncs.com:3306/longvideo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 10
+      maximum-pool-size: 20
+      connection-test-query: SELECT 1
+
+logging:
+  file:
+    path: /datalog/weblog/${project.name}
+
+app:
+  id: risk-control
+apollo:
+  bootstrap:
+    enabled: true
+    namespaces: application
+  meta: http://apolloconfig-internal.piaoquantv.com
+  cacheDir: /datalog/apollo-cache-dir
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: risk-control
+    logstore:
+      request: request-log
+      error: error-log
+  oss:
+    endpoint: oss-cn-hangzhou-internal.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+
+xxl:
+  job:
+    admin:
+      addresses: http://xxl-job-internal.piaoquantv.com/xxl-job-admin
+    accessToken:
+    executor:
+      appname: ${project.name}
+      address:
+      ip:
+      port: 9999
+      logpath: /datalog/weblog/${project.name}/xxl-job/
+      logretentiondays: 30

+ 66 - 0
risk-control-server/src/main/resources/application-stress.yml

@@ -0,0 +1,66 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://testeureka-internal.piaoquantv.com/eureka/
+
+spring:
+  datasource:
+    driver-class-name: com.mysql.jdbc.Driver
+    url: jdbc:mysql://rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com:3306/longvideo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 10
+      maximum-pool-size: 20
+      connection-test-query: SELECT 1
+
+
+logging:
+  file:
+    path: /datalog/weblog/${project.name}
+
+app:
+  id: risk-control
+apollo:
+  bootstrap:
+    enabled: true
+    namespaces: application
+  meta: http://testapolloconfig-internal.piaoquantv.com
+  cacheDir: /datalog/apollo-cache-dir
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: risk-control
+    logstore:
+      request: request-log-test
+      error: error-log-test
+  oss:
+    endpoint: oss-cn-hangzhou-internal.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+
+xxl:
+  job:
+    admin:
+      addresses: http://xxl-job-test-internal.piaoquantv.com/xxl-job-admin
+    accessToken:
+    executor:
+      appname: ${project.name}
+      address:
+      ip:
+      port: 9999
+      logpath: /datalog/weblog/${project.name}/xxl-job/
+      logretentiondays: 30

+ 66 - 0
risk-control-server/src/main/resources/application-test.yml

@@ -0,0 +1,66 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://testeureka-internal.piaoquantv.com/eureka/
+
+spring:
+  datasource:
+    driver-class-name: com.mysql.jdbc.Driver
+    url: jdbc:mysql://rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com:3306/longvideo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 10
+      maximum-pool-size: 20
+      connection-test-query: SELECT 1
+
+
+logging:
+  file:
+    path: /datalog/weblog/${project.name}
+
+app:
+  id: risk-control
+apollo:
+  bootstrap:
+    enabled: true
+    namespaces: application
+  meta: http://testapolloconfig-internal.piaoquantv.com
+  cacheDir: /datalog/apollo-cache-dir
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: risk-control
+    logstore:
+      request: request-log-test
+      error: error-log-test
+  oss:
+    endpoint: oss-cn-hangzhou-internal.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+
+xxl:
+  job:
+    admin:
+      addresses: http://xxl-job-test-internal.piaoquantv.com/xxl-job-admin
+    accessToken:
+    executor:
+      appname: ${project.name}
+      address:
+      ip:
+      port: 9999
+      logpath: /datalog/weblog/${project.name}/xxl-job/
+      logretentiondays: 30

+ 26 - 0
risk-control-server/src/main/resources/application.yml

@@ -0,0 +1,26 @@
+spring:
+  application:
+    name: risk-control
+  profiles:
+    active: dev
+
+project:
+  name: risk-control
+
+server:
+  tomcat:
+    threads:
+      max: 1000
+    uri-encoding: UTF-8
+    accept-count: 1000
+  servlet:
+    session:
+      timeout: 60
+    context-path: /risk-control
+
+mybatis:
+  type-aliases-package: com.tzld.piaoquan.risk.control.model.po
+  mapper-locations: classpath:mapper/*.xml
+
+pagehelper:
+  helper-dialect: mysql

+ 193 - 0
risk-control-server/src/main/resources/logback-spring.xml

@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<configuration  scan="true" scanPeriod="10 seconds">
+
+    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
+
+    <contextName>logback</contextName>
+    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
+<!--    <property name="LOG_PATH"  value="${logging.file.path}" />-->
+    <springProperty name="LOG_PATH" source="logging.file.path"/>
+
+    <!-- 彩色日志 -->
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
+    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
+    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
+    <!-- 彩色日志格式 -->
+    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+
+    <!--输出到控制台-->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>info</level>
+        </filter>
+        <encoder>
+            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
+            <!-- 设置字符集 -->
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!--输出到文件-->
+    <!-- 时间滚动输出 level为 DEBUG 日志 -->
+    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/debug.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{logTraceId}] %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset> <!-- 设置字符集 -->
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志归档 -->
+            <fileNamePattern>${LOG_PATH}/debug/debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>15</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录debug级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>debug</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 时间滚动输出 level为 INFO 日志 -->
+    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/info.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{logTraceId}] %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 每天日志归档路径以及格式 -->
+            <fileNamePattern>${LOG_PATH}/info/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>15</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录info级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>info</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 时间滚动输出 level为 WARN 日志 -->
+    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/warn.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{logTraceId}] %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_PATH}/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>15</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录warn级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>warn</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+
+    <!-- 时间滚动输出 level为 ERROR 日志 -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/error.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{logTraceId}] %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_PATH}/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>15</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录ERROR级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!--
+        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
+        以及指定<appender>。<logger>仅有一个name属性,
+        一个可选的level和一个可选的addtivity属性。
+        name:用来指定受此logger约束的某一个包或者具体的某一个类。
+        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
+              如果未设置此属性,那么当前logger将会继承上级的级别。
+        addtivity:是否向上级logger传递打印信息。默认是true。
+    -->
+    <!--<logger name="org.springframework.web" level="info"/>-->
+    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
+    <!--
+        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
+        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
+        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
+     -->
+
+    <!--
+        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
+        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+        不能设置为INHERITED或者同义词NULL。默认是DEBUG
+        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
+    -->
+
+    <springProfile name="dev">
+        <logger name="com.tzld.piaoquan.crawler.scheduler" level="info"/>
+    </springProfile>
+    <springProfile name="test">
+        <logger name="com.tzld.piaoquan.crawler.scheduler" level="info"/>
+    </springProfile>
+    <springProfile name="pre">
+        <logger name="com.tzld.piaoquan.crawler.scheduler" level="info"/>
+    </springProfile>
+    <springProfile name="stress">
+        <logger name="com.tzld.piaoquan.crawler.scheduler" level="info"/>
+    </springProfile>
+    <springProfile name="prod">
+        <logger name="com.tzld.piaoquan.crawler.scheduler" level="info"/>
+    </springProfile>
+
+    <root level="info">
+        <appender-ref ref="CONSOLE" />
+        <appender-ref ref="DEBUG_FILE" />
+        <appender-ref ref="INFO_FILE" />
+        <appender-ref ref="WARN_FILE" />
+        <appender-ref ref="ERROR_FILE" />
+    </root>
+
+</configuration>

+ 13 - 0
risk-control-server/src/test/java/com/tzld/piaoquan/risk/control/BaseTest.java

@@ -0,0 +1,13 @@
+package com.tzld.piaoquan.risk.control;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public class BaseTest {
+
+    @Test
+    void contextLoads() {
+    }
+
+}

+ 23 - 0
risk-control-server/src/test/java/com/tzld/piaoquan/risk/control/service/DemoServiceTest.java

@@ -0,0 +1,23 @@
+package com.tzld.piaoquan.risk.control.service;
+
+import com.tzld.piaoquan.risk.control.BaseTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DemoServiceTest extends BaseTest {
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    @Test
+    void test(){
+//        demoService
+        Map<String,Object> map = new HashMap<>();
+        map.put("video_id",1);
+        mongoTemplate.insert(map, "content_understanding_info");
+    }
+}