JAVA+多数据源+定时任务+全局捕获异常+copyin

目录

多数据源

application.yml

application-test.yml

application-test1.yml

DataSourceAspect.java

DataSourceConfig.java

DataSourceContextHolder.java

DynamicDataSource.java

自定义注解说明数据源

DynamicDS.java

DynamicDataSourceAspect.java

全局捕获异常

BaseErrorInfoInterface.java

BusinessException.java

CommonEnum.java

ErrController.java

GlobalExceptionHandler.java

MyCustomErrorAttributes.java

Result.java

日志

logback-spring.xml

工具类

LogIpConfig.java

JobUtil.java

JobUtil测试类

PgUtil.java

定时任务

Component.java

QuartzConfig.java

功能代码

TestController.java

TestService.java

TestServiceImpl.java

TestMapper.java

TestMapper.xml

Test1Mapper.java

Test1Mapper.xml


多数据源

application.yml

spring:
  profiles:
    active: test

application-test.yml

server:
  tomcat:
    connection-timeout: 60000
  port: 9093
  shutdown: graceful
cus:
  default: t1
  msg: t1
spring:
  datasource:
    t1:
      driver-class-name: org.postgresql.Driver
      jdbc-url: url
      username: username
      password: password
      type: com.zaxxer.hikari.HikariDataSource
      maximum-pool-size: 3
      #      最小連接池
      minimum-idle: 0
      #      允許在連接池中保持空閒的最大時長
      idle-timeout: 30000
      #      存活時間
      max-lifetime: 120000
      auto-commit: true #開啟自動提交
      #      自動回收鏈接的時間
      leak-detection-threshold: 60000
      pool-name: T1HikariCP
      connection-timeout: 30000
      connection-test-query: SELECT 1
    t11:
      driver-class-name: org.postgresql.Driver
      jdbc-url: url
      username: username
      password: password
      type: com.zaxxer.hikari.HikariDataSource
      maximum-pool-size: 3
      #      最小連接池
      minimum-idle: 0
      #      允許在連接池中保持空閒的最大時長
      idle-timeout: 30000
      #      存活時間
      max-lifetime: 120000
      auto-commit: true #開啟自動提交
      #      自動回收鏈接的時間
      leak-detection-threshold: 60000
      pool-name: T11HikariCP
      connection-timeout: 30000
      connection-test-query: SELECT 1
    t12:
      driver-class-name: org.postgresql.Driver
      jdbc-url: url
      username: username
      password: password.2024
      type: com.zaxxer.hikari.HikariDataSource
      maximum-pool-size: 3
      #      最小連接池
      minimum-idle: 0
      #      允許在連接池中保持空閒的最大時長
      idle-timeout: 30000
      #      存活時間
      max-lifetime: 120000
      auto-commit: true #開啟自動提交
      #      自動回收鏈接的時間
      leak-detection-threshold: 60000
      pool-name: T12HikariCP
      connection-timeout: 30000
      connection-test-query: SELECT 1
  lifecycle:
    timeout-per-shutdown-phase: 30s
  redis:
    # IP地址
    host: 
    # 端口號
    port: 6379
    # 数据库索引
    database: 0
    # 密碼
    password: 
    # 连接超时时间
    connect-timeout: 10s
    timeout: 5000ms
    lettuce:
      pool:
        #连接池中的最小空闲连接
        min-idle: 0
        #连接池中的最大空闲连接(使用负值表示没有限制)
        max-idle: 5
        # 连接池的最大数据库连接数(使用负值表示没有限制)
        max-active: 5
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
  mvc:
    #    # 静态文件请求匹配方式
    static-path-pattern: /**
    web:
      resources:
        static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:*/js,classpath:/templates,classpath:/static/,classpath:/public/,classpath:/os/,classpath:/resources/static
    #      static-locations: class\ath:/templates
    pathmatch:
      matching-strategy: ant_path_matcher
logging:
  config: classpath:logback-spring.xml
mybatis:
  configuration:
    call-setters-on-nulls: true

application-test1.yml

server:
  tomcat:
    connection-timeout: 60000
  port: 9093
  shutdown: graceful
cus:
  default: t2
  msg: t2
spring:
  datasource:
    t2:
      driver-class-name: org.postgresql.Driver
      jdbc-url: url
      username: username
      password: password
      type: com.zaxxer.hikari.HikariDataSource
      maximum-pool-size: 3
      #      最小連接池
      minimum-idle: 0
      #      允許在連接池中保持空閒的最大時長
      idle-timeout: 30000
      #      存活時間
      max-lifetime: 120000
      auto-commit: true #開啟自動提交
      #      自動回收鏈接的時間
      leak-detection-threshold: 60000
      pool-name: T2HikariCP
      connection-timeout: 30000
      connection-test-query: SELECT 1
    t21:
      driver-class-name: org.postgresql.Driver
      jdbc-url: url
      username: username
      password: password
      type: com.zaxxer.hikari.HikariDataSource
      maximum-pool-size: 3
      #      最小連接池
      minimum-idle: 0
      #      允許在連接池中保持空閒的最大時長
      idle-timeout: 30000
      #      存活時間
      max-lifetime: 120000
      auto-commit: true #開啟自動提交
      #      自動回收鏈接的時間
      leak-detection-threshold: 60000
      pool-name: T21HikariCP
      connection-timeout: 30000
      connection-test-query: SELECT 1
    t22:
      driver-class-name: org.postgresql.Driver
      jdbc-url: url
      username: username
      password: password.2024
      type: com.zaxxer.hikari.HikariDataSource
      maximum-pool-size: 3
      #      最小連接池
      minimum-idle: 0
      #      允許在連接池中保持空閒的最大時長
      idle-timeout: 30000
      #      存活時間
      max-lifetime: 120000
      auto-commit: true #開啟自動提交
      #      自動回收鏈接的時間
      leak-detection-threshold: 60000
      pool-name: T22HikariCP
      connection-timeout: 30000
      connection-test-query: SELECT 1
  lifecycle:
    timeout-per-shutdown-phase: 30s
  redis:
    # IP地址
    host: 
    # 端口號
    port: 6379
    # 数据库索引
    database: 0
    # 密碼
    password: 
    # 连接超时时间
    connect-timeout: 10s
    timeout: 5000ms
    lettuce:
      pool:
        #连接池中的最小空闲连接
        min-idle: 0
        #连接池中的最大空闲连接(使用负值表示没有限制)
        max-idle: 5
        # 连接池的最大数据库连接数(使用负值表示没有限制)
        max-active: 5
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
  mvc:
    #    # 静态文件请求匹配方式
    static-path-pattern: /**
    web:
      resources:
        static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:*/js,classpath:/templates,classpath:/static/,classpath:/public/,classpath:/os/,classpath:/resources/static
    #      static-locations: class\ath:/templates
    pathmatch:
      matching-strategy: ant_path_matcher
logging:
  config: classpath:logback-spring.xml
mybatis:
  configuration:
    call-setters-on-nulls: true

DataSourceAspect.java

注意:@Aspect标记一个类为切面;@Pointcut 注解通常与 @Aspect 注解一起使用,定义切点;@EnableAspectJAutoProxy 是一个 Spring 注解,用于启用基于 AspectJ 的 AOP(面向切面编程)。当你在 Spring 的配置类上使用这个注解时,Spring 会自动扫描和处理用 @Aspect 注解标记的类,并为这些类创建代理,以支持 AOP 功能。在 Spring Boot 应用中可以直接添加到 Spring Boot 启动类上,这样可以简化配置,并使得 AOP 功能在整个应用中生效;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {
    @Value("${cus.default}")
    private String db;

    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerMethods() {
    }

    @Before("controllerMethods()")
    public void beforeControllerMethod() {
        DataSourceContextHolder.setDataSource(db); // 设置默认数据源
    }

    @After("controllerMethods()")
    public void afterControllerMethod() {
        DataSourceContextHolder.setDataSource(db); // 设置回默认数据源
    }
}

DataSourceConfig.java

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    @Value("${cus.default}")
    private String dbDefault;

    @Bean(name = "t1")
    @ConfigurationProperties(prefix = "spring.datasource.t1")
    public DataSource dataSourceT1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "t11")
    @ConfigurationProperties(prefix = "spring.datasource.t11")
    public DataSource dataSourceT11() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "t12")
    @ConfigurationProperties(prefix = "spring.datasource.t12")
    public DataSource dataSourceT12() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "t2")
    @ConfigurationProperties(prefix = "spring.datasource.t2")
    public DataSource dataSourceT2() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "t21")
    @ConfigurationProperties(prefix = "spring.datasource.t21")
    public DataSource dataSourceT21() {
        return DataSourceBuilder.create().build();
    }



    /**
     * 动态数据源: 通过AOP在不同数据源之间动态切换
     * @return
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap<>();
        dsMap.put("t1", dataSourceT1());
        dsMap.put("t11", dataSourceT11());
        dsMap.put("t12", dataSourceT12());
        dsMap.put("t2", dataSourceT2());
        dsMap.put("t21", dataSourceT21());
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(dsMap.get(dbDefault));

        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }

    /**
     * 配置多数据源后IOC中存在多个数据源了,事务管理器需要重新配置,不然不知道选择哪个数据源
     * 事务管理器此时管理的数据源将是动态数据源dynamicDataSource
     * 配置@Transactional注解
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }


}

DataSourceContextHolder.java

public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }

    public static String getDataSource() {
        String dataSource = CONTEXT_HOLDER.get();
        return dataSource;
    }

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

DynamicDataSource.java

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

自定义注解说明数据源

注意:使用方式为在mapper中的方法上加@DynamicDS("数据源名")注解,在DynamicDataSourceAspect类中特殊处理后,@DynamicDS("")取默认数据源,@DynamicDS("1")取t11数据源,@DynamicDS("2")则取t12数据源

DynamicDS.java

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 DynamicDS {
    String value() default "";
}

DynamicDataSourceAspect.java

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DynamicDataSourceAspect {

    @Value("${cus.default}")
    private String defaultName;

    @Before("@annotation(dynamicDS)")
    public void before(JoinPoint joinPoint, DynamicDS dynamicDS) {
        String suffix = dynamicDS.value();
        String dataSourceName = defaultName + suffix;
        // 动态设置数据源的逻辑
        System.out.println("Using data source: " + dataSourceName);
        DataSourceContextHolder.setDataSource(dataSourceName);
        // 例如,通过 ThreadLocal 或其他方式设置当前数据源
    }

    @After("@annotation(dynamicDS)")
    public void after(JoinPoint joinPoint, DynamicDS dynamicDS) {
        // 清理当前线程中的数据源
        DataSourceContextHolder.clearDataSource();
    }
}

全局捕获异常

BaseErrorInfoInterface.java


public interface BaseErrorInfoInterface {
    /**
     * 错误码
     */
    int getResultCode();

    /**
     * 错误描述
     */
    String getResultMsg();
}

BusinessException.java

public class BusinessException extends RuntimeException  {
    private static final long serialVersionUID = -4879677283847539655L;

    private int errorCode;

    private String errorMessage;

    private String exceptionMessage;

    private Exception exception;

    public BusinessException(String errorMessage) {
        super();
        this.errorMessage = errorMessage;
    }

    public BusinessException(int errorCode, String errorMessage) {
        super();
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public BusinessException(int errorCode, String errorMessage, Exception exception) {
        super();
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
        this.exception = exception;
    }

    public BusinessException(String errorMessage, String exceptionMessage) {
        super();
        this.exceptionMessage = exceptionMessage;
        this.errorMessage = errorMessage;
    }

    public String getExceptionMessage() {
        return exceptionMessage;
    }

    public void setExceptionMessage(String exceptionMessage) {
        this.exceptionMessage = exceptionMessage;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    public Exception getException() {
        return exception;
    }

    public void setException(Exception exception) {
        this.exception = exception;
    }

    public BusinessException(int errorCode, String errorMessage, String exceptionMessage) {
        super();
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
        this.exceptionMessage = exceptionMessage;
    }
}

CommonEnum.java

public enum CommonEnum implements BaseErrorInfoInterface {
    // 数据操作错误定义
    SUCCESS(200, "成功!"),
    BODY_NOT_MATCH(400,"请求的数据格式不符!"),
    SIGNATURE_NOT_MATCH(401,"请求的数字签名不匹配!"),
    NOT_FOUND(404, "未找到该资源!"),
    INTERNAL_SERVER_ERROR(500, "服务器内部错误!"),
    SERVER_BUSY(503,"服务器正忙,请稍后再试!"),
    CLIENT_ABORT_ERROR(600, "客户端中断连接")
    ;

    /** 错误码 */
    private int resultCode;

    /** 错误描述 */
    private String resultMsg;

    CommonEnum(int resultCode, String resultMsg) {
        this.resultCode = resultCode;
        this.resultMsg = resultMsg;
    }

    @Override
    public int getResultCode() {
        return resultCode;
    }

    @Override
    public String getResultMsg() {
        return resultMsg;
    }

}

ErrController.java

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ErrController implements ErrorController {

    public String getErrorPath() {
        // TODO Auto-generated method stub
        return "/error";
    }

    @RequestMapping("/error")
    public boolean handlerError() {
        throw new BusinessException(-1,"页面不存在");
    }

}

GlobalExceptionHandler.java

注意:clientAbortExceptionHandler 用于处理 ClientAbortException 异常。该方法记录了警告级别的日志,而不是错误级别,因为这种异常通常表示客户端主动断开连接,这种情况不一定是服务器端的错误

import org.apache.catalina.connector.ClientAbortException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理自定义的业务异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public Result bizExceptionHandler(HttpServletRequest req, BusinessException e){
        StackTraceElement[] stackTrace = e.getStackTrace();
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement stackTraceElement : stackTrace) {
            sb.append(stackTraceElement.toString()).append("\n");
        }
        logger.error("未知异常:"+e.getErrorMessage()+"!原因是:"+e);
        logger.error("未知异常!錯誤信息是:"+sb.toString());
        return Result.error(e.getErrorCode(),e.getErrorMessage());
    }

    /**
     * 处理空指针的异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =NullPointerException.class)
    @ResponseBody
    public Result exceptionHandler(HttpServletRequest req, NullPointerException e){
        StackTraceElement[] stackTrace = e.getStackTrace();
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement stackTraceElement : stackTrace) {
            sb.append(stackTraceElement.toString()).append("\n");
        }
        logger.error("未知异常!原因是:"+e);
        logger.error("未知异常!錯誤信息是:"+sb.toString());
        return Result.error(CommonEnum.BODY_NOT_MATCH);
    }

    /**
     * 处理 ClientAbortException 异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = ClientAbortException.class)
    @ResponseBody
    public Result clientAbortExceptionHandler(HttpServletRequest req, ClientAbortException e){
        logger.warn("客户端中断连接!异常信息是:" + e.getMessage());
        return Result.error(CommonEnum.CLIENT_ABORT_ERROR);
    }

    /**
     * 处理其他异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public Result exceptionHandler(HttpServletRequest req, Exception e){
        StackTraceElement[] stackTrace = e.getStackTrace();
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement stackTraceElement : stackTrace) {
            sb.append(stackTraceElement.toString()).append("\n");
        }
        logger.error("未知异常!原因是:"+e);
        logger.error("未知异常!錯誤信息是:"+sb.toString());
        return Result.error(CommonEnum.INTERNAL_SERVER_ERROR);
    }
}

MyCustomErrorAttributes.java

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

@Component
public class MyCustomErrorAttributes extends DefaultErrorAttributes  {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
        return errorAttributes;
    }
}

Result.java

import com.alibaba.fastjson.JSONObject;

public class Result {
    /**
     * 响应代码
     */
    private int code;

    /**
     * 响应消息
     */
    private String message;

    /**
     * 响应结果
     */
    private Object result;

    public Result() {
    }

    public Result(BaseErrorInfoInterface errorInfo) {
        this.code = errorInfo.getResultCode();
        this.message = errorInfo.getResultMsg();
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    /**
     * 成功
     *
     * @return
     */
    public static Result success() {
        return success(null);
    }

    /**
     * 成功
     * @param data
     * @return
     */
    public static Result success(Object data) {
        Result rb = new Result();
        rb.setCode(CommonEnum.SUCCESS.getResultCode());
        rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
        rb.setResult(data);
        return rb;
    }

    /**
     * 失败
     */
    public static Result error(BaseErrorInfoInterface errorInfo) {
        Result rb = new Result();
        rb.setCode(errorInfo.getResultCode());
        rb.setMessage(errorInfo.getResultMsg());
        rb.setResult(null);
        return rb;
    }

    /**
     * 失败
     */
    public static Result error(int code, String message) {
        Result rb = new Result();
        rb.setCode(code);
        rb.setMessage(message);
        rb.setResult(null);
        return rb;
    }

    /**
     * 失败
     */
    public static Result error(String message) {
        Result rb = new Result();
        rb.setCode(-1);
        rb.setMessage(message);
        rb.setResult(null);
        return rb;
    }

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }

}

日志

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false">

    <conversionRule conversionWord="ip" converterClass="com.example.util.LogIpConfig" />
    <!-- Log file path -->
    <property name="log.path" value="../ScheduleLogs/" />

    <!-- Console log output -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--            <pattern> [%ip]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
            <pattern> [host:%ip]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- Log file debug output -->
    <appender name="fileRolling_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/%d{yyyy-MM-dd}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--每个文件的大小限制-->
            <MaxFileSize>1GB</MaxFileSize>
            <!--最多保留10天的文件,10天之前的将被清除-->
            <MaxHistory>10</MaxHistory>
            <!--该滚动策略日志的总大小,超过的日志会被清除-->
            <!--            <totalSizeCap>10GB</totalSizeCap>-->
            <!--启动时清理日志文件  此项置灰清理超过保留天数的  也会清理超过总大小的-->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern> [host:%ip]%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n
            </pattern>
        </encoder>
        <!--<filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level>
            <onMatch>DENY</onMatch> <onMismatch>NEUTRAL</onMismatch> </filter> -->
    </appender>
    <!-- Log file error output -->
    <appender name="fileRolling_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/%d{yyyy-MM-dd}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--每个文件的大小限制-->
            <MaxFileSize>1GB</MaxFileSize>
            <!--最多保留10天的文件,10天之前的将被清除-->
            <MaxHistory>10</MaxHistory>
            <!--该滚动策略日志的总大小,超过的日志会被清除-->
            <!--            <totalSizeCap>10GB</totalSizeCap>-->
            <!--启动时清理日志文件  此项置灰清理超过保留天数的  也会清理超过总大小的-->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern> [host:%ip]%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
    <root level="info">
        <!--        <appender-ref ref="console"/>-->
        <appender-ref ref="fileRolling_info" />
        <appender-ref ref="fileRolling_error" />
    </root>
    <logger name="org.springframework" level="INFO"/>
    <logger name="org.mybatis" level="INFO"/>
    <logger name="com.example" level="debug"/>

    <!-- Enable MDC -->
    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>
</configuration>

工具类

LogIpConfig.java

说明:获取前端访问后端的ip地址

import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class LogIpConfig extends ClassicConverter {
    @Override
    public String convert(ILoggingEvent event) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return "127.0.0.1";
        }
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String remoteAddr = "";
        if (request != null) {
            remoteAddr = request.getHeader("X-Forwarded-For");
            if (remoteAddr == null || remoteAddr.trim().isEmpty()) {
                remoteAddr = request.getHeader("X-Real-IP");
            }
            if (remoteAddr == null || remoteAddr.trim().isEmpty()) {
                remoteAddr = request.getRemoteAddr();
            }
        }
        return remoteAddr;
    }
    public static String getIp(){
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return "127.0.0.1";
        }
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String remoteAddr = "";
        if (request != null) {
            remoteAddr = request.getHeader("X-Forwarded-For");
            if (remoteAddr == null || remoteAddr.trim().isEmpty()) {
                remoteAddr = request.getHeader("X-Real-IP");
            }
            if (remoteAddr == null || remoteAddr.trim().isEmpty()) {
                remoteAddr = request.getRemoteAddr();
            }
        }
        return remoteAddr;
    }


}

JobUtil.java

说明:通过post或get请求访问外部接口

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;


@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class JobUtil {



    public static Object postHTTP(String requestId, String colum, String requestIp,String apiUrl) {

        /*
         *第一种方式post请求
         */
//        JSONObject result = new JSONObject();
//        JSONObject jsons = new JSONObject();
//        jsons.put("key", "value");
        JSONObject jsonss = null;
        try {
            URL url = new URL(apiUrl);
            //打开和url之间的连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            PrintWriter out = null;
            //请求方式 这里设置你需要请求的方式
            //conn.setRequestMethod("POST");
            //设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "close");
            //这里设置请求头
            conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
            //设置是否向httpUrlConnection输出,设置是否从httpUrlConnection读入,此外发送post请求必须设置这两个
            //最常用的Http请求无非是get和post,get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet,
            //post与get的 不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。
            conn.setDoOutput(true);
            conn.setDoInput(true);

            // 创建一个 OutputStreamWriter 对象,并指定 UTF-8 编码
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
            //获取URLConnection对象对应的输出流
            out = new PrintWriter(writer);

            out = new PrintWriter(conn.getOutputStream());

            //设置请求参数
            Map map = new HashMap();
            map.put("requestId", requestId);
            map.put("requestIp", requestIp);
            if (!colum.equals("")) //精度的不需要传楼层,报警履历需要传楼层
                map.put("colum",colum);
            String toJSONString = JSON.toJSONString(map);

            //发送请求参数即数据
            out.print(toJSONString);
            //缓冲数据
            out.flush();
            //获取URLConnection对象对应的输入流
            InputStream is = conn.getInputStream();
            //构造一个字符流缓存
            BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
            String str = "";

            while ((str = br.readLine()) != null) {
                jsonss = JSONObject.parseObject(str);
            }
//            System.out.println("获取到的报文数据为:" + jsonss);
            //关闭流
            is.close();
            //断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被 其他线程使用就不切断。
            //固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上 disconnect后正常一些。
            conn.disconnect();
//            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonss;
    }
    /**
     * 以get方式调用对方接口方法
     * @param pathUrl
     */
    public static String getHTTP(String pathUrl){
        BufferedReader br = null;
        String result = "";
        try {
            URL url = new URL(pathUrl);

            //打开和url之间的连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            //设定请求的方法为"GET",默认是GET
            //post与get的不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。
            conn.setRequestMethod("GET");

            //设置30秒连接超时
            conn.setConnectTimeout(30000);
            //设置30秒读取超时
            conn.setReadTimeout(30000);

            // 设置是否向httpUrlConnection输出,因为这个是post请求,参数要放在http正文内,因此需要设为true, 默认情况下是false;
            conn.setDoOutput(true);
            // 设置是否从httpUrlConnection读入,默认情况下是true;
            conn.setDoInput(true);

            // Post请求不能使用缓存(get可以不使用)
            conn.setUseCaches(false);

            //设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");  //维持长链接
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");

            //连接,从上述url.openConnection()至此的配置必须要在connect之前完成,
            conn.connect();

            /**
             * 下面的代码相当于,获取调用第三方http接口后返回的结果
             */
            //获取URLConnection对象对应的输入流
            InputStream is = conn.getInputStream();
            //构造一个字符流缓存
            br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String str = "";
            while ((str = br.readLine()) != null){
                result += str;
            }
//            System.out.println(result);
            //关闭流
            is.close();
            //断开连接,disconnect是在底层tcp socket链接空闲时才切断,如果正在被其他线程使用就不切断。
            conn.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if (br != null){
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
    /**
     * 通用的方法,用post方式与外部系统的api通讯
     * @param requestMap post请求的参数,存在requestMap
     * @param apiUrl post请求的路径
     * @return
     */
    public static Object post(Map requestMap,String apiUrl) {

        /*
         *第一种方式post请求
         */
//        JSONObject result = new JSONObject();
//        JSONObject jsons = new JSONObject();
//        jsons.put("key", "value");
        com.alibaba.fastjson.JSONObject jsonss = null;
        try {
            URL url = new URL(apiUrl);
            //打开和url之间的连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            PrintWriter out = null;
            //请求方式 这里设置你需要请求的方式
            //conn.setRequestMethod("POST");
            //设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "close");
            //这里设置请求头
            conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
            //设置是否向httpUrlConnection输出,设置是否从httpUrlConnection读入,此外发送post请求必须设置这两个
            //最常用的Http请求无非是get和post,get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet,
            //post与get的 不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。
            conn.setDoOutput(true);
            conn.setDoInput(true);

            // 创建一个 OutputStreamWriter 对象,并指定 UTF-8 编码
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
            //获取URLConnection对象对应的输出流
//            out = new PrintWriter(writer);

            out = new PrintWriter(conn.getOutputStream());

            //设置请求参数
//            Map map = new HashMap();
//            map.put("requestId", requestId);
//            map.put("requestIp", requestIp);
            String toJSONString = JSON.toJSONString(requestMap);

            //发送请求参数即数据
            out.print(toJSONString);
            //缓冲数据
            out.flush();
            //获取URLConnection对象对应的输入流
            InputStream is = conn.getInputStream();
            //构造一个字符流缓存
            BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
            String str = "";

            while ((str = br.readLine()) != null) {
                jsonss = JSONObject.parseObject(str);
            }
//            System.out.println("获取到的报文数据为:" + jsonss);
            //关闭流
            is.close();
            //断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被 其他线程使用就不切断。
            //固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上 disconnect后正常一些。
            conn.disconnect();
//            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonss;
    }

    /**
     * 通用的方法,用post方式与外部系统的api通讯
     * @param requestMap post请求的参数,存在requestMap
     * @param apiUrl post请求的路径
     * @return
     */
    public static String post2(Map requestMap,String apiUrl) {

        /*
         *第一种方式post请求
         */
//        JSONObject result = new JSONObject();
        JSONObject jsons = new JSONObject();
        String result = "";
        jsons.put("key", "value");
        com.alibaba.fastjson.JSONObject jsonss = null;
        try {
            URL url = new URL(apiUrl);
            //打开和url之间的连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            PrintWriter out = null;
            //请求方式 这里设置你需要请求的方式
            //conn.setRequestMethod("POST");
            //设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "close");
            //这里设置请求头
            conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
            //设置是否向httpUrlConnection输出,设置是否从httpUrlConnection读入,此外发送post请求必须设置这两个
            //最常用的Http请求无非是get和post,get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet,
            //post与get的 不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。
            conn.setDoOutput(true);
            conn.setDoInput(true);

            // 创建一个 OutputStreamWriter 对象,并指定 UTF-8 编码
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
            //获取URLConnection对象对应的输出流
            out = new PrintWriter(writer);

            out = new PrintWriter(conn.getOutputStream());

            //设置请求参数
//            Map map = new HashMap();
//            map.put("requestId", requestId);
//            map.put("requestIp", requestIp);
            String toJSONString = JSON.toJSONString(requestMap);

            //发送请求参数即数据
            out.print(toJSONString);
            //缓冲数据
            out.flush();
            //获取URLConnection对象对应的输入流
            InputStream is = conn.getInputStream();
            //构造一个字符流缓存
            BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
//            String str = "";
//
//            while ((str = br.readLine()) != null) {
//                jsonss = JSONObject.parseObject(str);
//            }

            StringBuilder response = new StringBuilder(); //读取大量数据时,StringBuilder构建完整的字符串可能会占用较多的内存,因此需要根据实际情况权衡使用。
            String line;
            while ((line = br.readLine()) != null) {
                response.append(line);
            }

            result=response.toString();
//            System.out.println("requestMap = " + toJSONString + ", apiUrl = " + apiUrl);
            //关闭流
            is.close();
            //断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被 其他线程使用就不切断。
            //固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上 disconnect后正常一些。
            conn.disconnect();
//            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param headers
     * @param apiUrl
     * @return
     */
    public static Object postHeaders(Map requestMap,Map<String, String> headers, String apiUrl) {
//        JSONObject jsons = new JSONObject();
//        jsons.put("key", "value");
        com.alibaba.fastjson.JSONObject jsonss = null;
        try {
//                     创建信任所有证书的 TrustManager
            TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        public X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }

                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                        }

                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                        }
                    }
            };

            // 设置 SSL 上下文
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());

            // 设置默认 SSL Socket 工厂
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // 设置默认 HostnameVerifier
            HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);

            URL url = new URL(apiUrl);
            //打开和url之间的连接,使用ssl的HttpsURLConnection!!!(对方需求是hhtps)
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
//            PrintWriter out = null;
            // 关闭 SSL 验证
//            conn.setSSLSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
//            conn.setHostnameVerifier((hostname, session) -> true);
            //请求方式 这里设置你需要请求的方式
            conn.setRequestMethod("POST");
            //设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "close");

            // 设置请求头部
            if (headers != null) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    conn.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }
            //如果存在请求参数,则这样
            if(requestMap!=null)
                conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
            //设置是否向httpUrlConnection输出,设置是否从httpUrlConnection读入,此外发送post请求必须设置这两个
            //最常用的Http请求无非是get和post,get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet,
            //post与get的 不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。
            conn.setDoOutput(true); // 设置是否向连接输出
            conn.setDoInput(true); // 设置是否从连接读入
            conn.setConnectTimeout(5000); // 设置连接超时时间

            // 创建一个 OutputStreamWriter 对象,并指定 UTF-8 编码
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
            //获取URLConnection对象对应的输出流
            writer.write(JSON.toJSONString(requestMap));//发送请求参数即数据
            writer.flush();
            writer.close();

            //获取URLConnection对象对应的输入流
            InputStream is = conn.getInputStream();
            //构造一个字符流缓存
            BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
//            String str = "";
//
//            while ((str = br.readLine()) != null) {
//                jsonss = JSONObject.parseObject(str);
//            }
            System.out.println("获取到的报文数据为:" + jsonss);
//            //关闭流
//            is.close();
            StringBuilder response = new StringBuilder(); //读取大量数据时,StringBuilder构建完整的字符串可能会占用较多的内存,因此需要根据实际情况权衡使用。
            String line;
            while ((line = br.readLine()) != null) {
                response.append(line);
            }
            jsonss=JSON.parseObject(response.toString());
            br.close();
            //断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被 其他线程使用就不切断。
            //固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上 disconnect后正常一些。
            conn.disconnect();
//            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonss;
    }

    /**
     * SSL验证,使用 SSL/TLS 连接时永远信任所有证书
     * @param requestMap
     * @param apiUrl
     * @return
     */
    public static String post3(Map<String,String> requestMap,String apiUrl) {

        /*
         *第一种方式post请求
         */
//        JSONObject result = new JSONObject();
        JSONObject jsons = new JSONObject();
        String result = "";
        jsons.put("key", "value");
        JSONObject jsonss = null;
        try {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> entry : requestMap.entrySet()) {
                if (sb.length() > 0) {
                    sb.append("&");
                }
                sb.append(entry.getKey()).append("=").append(entry.getValue());
            }
            String params = sb.toString();
            TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        public X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }

                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                        }

                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                        }
                    }
            };

            // 设置 SSL 上下文
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());

            // 设置默认 SSL Socket 工厂
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // 设置默认 HostnameVerifier
            HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);

            URL url = new URL(apiUrl);
            //打开和url之间的连接
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            PrintWriter out = null;
            //请求方式 这里设置你需要请求的方式
            conn.setRequestMethod("POST");
            //设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "close");
            //这里设置请求头
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
            //设置是否向httpUrlConnection输出,设置是否从httpUrlConnection读入,此外发送post请求必须设置这两个
            //最常用的Http请求无非是get和post,get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet,
            //post与get的 不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。
            conn.setDoOutput(true);
            conn.setDoInput(true);


            // 获取连接对象对应的输出流
            OutputStream os = conn.getOutputStream();
            // 传递参数
            os.write(params.getBytes("UTF-8"));
            os.flush();
            os.close();
            //获取URLConnection对象对应的输入流
            InputStream is = conn.getInputStream();
            //构造一个字符流缓存
            BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));

            StringBuilder response = new StringBuilder(); //读取大量数据时,StringBuilder构建完整的字符串可能会占用较多的内存,因此需要根据实际情况权衡使用。
            String line;
            while ((line = br.readLine()) != null) {
                response.append(line);
            }

            result=response.toString();
//            System.out.println("requestMap = " + toJSONString + ", apiUrl = " + apiUrl);
//            System.out.println("獲取到的報文數據為:" + result);
            //关闭流
            is.close();
            //断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被 其他线程使用就不切断。
            //固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上 disconnect后正常一些。
            conn.disconnect();
//            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }




}

JobUtil测试类

import com.alibaba.fastjson.JSONObject;
import com.example.util.JobUtil;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@CrossOrigin//添加允許跨域註解
@RestController
public class AlarmCodeMaintenanceController {  

    @RequestMapping("/test1")
    public void test(){
        JSONObject json= (JSONObject)JobUtil.postHTTP(requestId,colum,clientIp,apiUrl);
        System.out.println(json);
    }

    @RequestMapping("/test2")
    public void test(){
        Map<String, String> map2=new HashMap<>();
        map2.put("statDate","2024-06-06");
        map2.put("colum1","colum1");
        map2.put("colum2","colum2");
        String result=JobUtil.post2(map,url);
//        JSONObject jsonObject= (JSONObject) JSONObject.parse(result);
        System.out.println("x"+result);
//        System.out.println(jsonObject);
        List<?> list=JSONObject.parseArray(result);
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

    @RequestMapping("/test3")
    public void test(){
        Map<String, String> map3 = new HashMap<>();
        map3.put("empNo",userId);
        map3.put("pwd",psw);
        map3.put("userIP",clientIp);
        map3.put("authNo",authNo);
        map3.put("tenantCode",tenantCode);
        String result=JobUtil.post3(map3, url);
//        JSONObject jsonObject= (JSONObject) JSONObject.parse(result);
        System.out.println("x"+result);
//        System.out.println(jsonObject);
        
    }

}

PgUtil.java

说明:COPYIN方法配置

import org.postgresql.PGConnection;
import org.postgresql.copy.CopyManager;
import org.postgresql.core.BaseConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;


@Component
public class PgUtil {
//    private static Logger logger = Logger.getLogger(PgUtil.class);
//    @Autowired
//    private  DataSourceConfig dataSourceConfig;
    @Resource
    private DataSource dataSource;


    public  void copyIn(String tableName, List<String> s){
        if(tableName == null || s.isEmpty()){
            return;
        }
        PGConnection pgConnection = null;
        Connection conn = null;
        long affectedRowCount = 0;
        try {
            //获得连接
            conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            if (conn.isWrapperFor(PGConnection.class)) {
                 pgConnection = conn.unwrap(PGConnection.class);
            }

            CopyManager copyManager = new CopyManager((BaseConnection) pgConnection);
            //System.out.println("执行copy....");
            String copyIn = "COPY " + tableName + " FROM STDIN DELIMITER AS '|' ";
            System.out.println(copyIn);
            affectedRowCount = copyManager.copyIn(copyIn, getInputStream(s));
            conn.commit();
            System.out.println(affectedRowCount);
        } catch (Exception e) {
            e.printStackTrace();
//            logger.error("Failed to copy data SQLException: " + e);
        } finally {
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
//        logger.info("affectedRowCount :" + affectedRowCount);
    }


    public void copyInByHandCommit(String tableName,String tableColumnStr, List<String> s){
        if(tableName == null || s.isEmpty()){
            return;
        }
        PGConnection pgConnection = null;
        Connection conn = null;
        Statement statement = null;
        long affectedRowCount = 0;
        try {
            //获得连接
//            conn = DruidUtil.getConnection();
            conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            if (conn.isWrapperFor(PGConnection.class)) {
                pgConnection = conn.unwrap(PGConnection.class);
            }
            statement = conn.createStatement();
            String truncation = "TRUNCATE TABLE " + tableName ;
//            String truncation = "DELETE from " + tableName ;
            statement.executeUpdate(truncation);
            CopyManager copyManager = new CopyManager((BaseConnection) pgConnection);
            String copyIn = "COPY " + tableColumnStr + " FROM STDIN DELIMITER AS '|'";
            affectedRowCount = copyManager.copyIn(copyIn, getInputStream(s));
            conn.commit();
            System.out.println(affectedRowCount); //打印影响行数
        } catch (Exception e) {
            e.printStackTrace();
//            logger.error("Failed to no autoCommit copy data SQLException: " + e);
        } finally {
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
//        logger.info("affectedRowCount :" + affectedRowCount);
    }

    public void copyInBandTmCommit(String tableName,String tableColumnStr, List<String> s,String colum){
        if(tableName == null || s.isEmpty()){
            return;
        }
        PGConnection pgConnection = null;
        Connection conn = null;
        Statement statement = null;
        long affectedRowCount = 0;
        try {
            //获得连接
            //            conn = DruidUtil.getConnection();
            conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            if (conn.isWrapperFor(PGConnection.class)) {
                pgConnection = conn.unwrap(PGConnection.class);
            }
            statement = conn.createStatement();
            String truncation = "DELETE from " + tableName +" where colum='"+colum+"'";
            statement.executeUpdate(truncation);
            CopyManager copyManager = new CopyManager((BaseConnection) pgConnection);
            String copyIn = "COPY " + tableColumnStr + " FROM STDIN DELIMITER AS '|'";
            affectedRowCount = copyManager.copyIn(copyIn, getInputStream(s));
            conn.commit();
            System.out.println(affectedRowCount); //打印影响行数
        } catch (Exception e) {
            e.printStackTrace();
            //            logger.error("Failed to no autoCommit copy data SQLException: " + e);
        } finally {
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        //        logger.info("affectedRowCount :" + affectedRowCount);
    }

    public void copyInBandTmWorkStationCommit(String tableName,String tableColumnStr, List<String> s){
        if(tableName == null || s.isEmpty()){
            return;
        }
        PGConnection pgConnection = null;
        Connection conn = null;
        Statement statement = null;
        long affectedRowCount = 0;
        try {
            //获得连接
            //            conn = DruidUtil.getConnection();
            conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            if (conn.isWrapperFor(PGConnection.class)) {
                pgConnection = conn.unwrap(PGConnection.class);
            }
            statement = conn.createStatement();
            String truncation = "TRUNCATE TABLE " + tableName ;
            statement.executeUpdate(truncation);
            CopyManager copyManager = new CopyManager((BaseConnection) pgConnection);
            String copyIn = "COPY " + tableColumnStr + " FROM STDIN DELIMITER AS '|'";
            affectedRowCount = copyManager.copyIn(copyIn, getInputStream(s));
            conn.commit();
            System.out.println(affectedRowCount); //打印影响行数
        } catch (Exception e) {
            e.printStackTrace();
            //            logger.error("Failed to no autoCommit copy data SQLException: " + e);
        } finally {
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        //        logger.info("affectedRowCount :" + affectedRowCount);
    }

    private static InputStream getInputStream(List<String> list) {
        StringBuilder sb = new StringBuilder();
        for (String data : list) {
            sb.append(data + "\r\n");
        }
        return new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8));
    }

}

定时任务

Component.java

import com.exampl.service.TestService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class Component implements Job {

    @Resource
    private TestService testService;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        testService.updateSchedule();
    }
}

QuartzConfig.java

import com.example.component.Component;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(Component.class)
                .withIdentity("job1")
                .storeDurably()
                .build();
    }

    /**
     * 写法1
     * @return
     */
    /*@Bean
    public Trigger jobTrigger() {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(4) // 每4秒执行一次
                .repeatForever(); // 无限次重复

        return TriggerBuilder.newTrigger()
                .forJob(jobDetail())
                .withIdentity("trigger1")
                .withSchedule(scheduleBuilder)
                .build();
    }*/

    /**
     * 写法2
     * @return
     */
    @Bean
    public Trigger jobTrigger() {
//        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0 30 9 * * ?"); // 每天早上9:30执行一次
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0 30 9,15 * * ? "); // 每天早上9:30和15:30执行一次

        return TriggerBuilder.newTrigger()
                .forJob(jobDetail())
                .withIdentity("trigger2")
                .withSchedule(scheduleBuilder)
                .build();
    }


}

功能代码

TestController.java

import com.example.service.TestService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class TestController {

    @Resource
    private TestService testService;


    @RequestMapping("/updateSchedule")
    public String updateSchedule() {
        return testService.updateSchedule();
    }


}

TestService.java

public interface TestService {
    String updateSchedule();

}

TestServiceImpl.java

import com.exmaple.mapper.testMapper.TestMapper;
import com.exmaple.mapper.test1Mapper.Test1Mapper;
import com.exmaple.mapper.test2Mapper.Test2Mapper;
import com.exmaple.service.TestService;
import com.exmaple.util.PgUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.stream.Collectors;

@Service
public class TestServiceImpl implements TestService{

    private static final Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
    @Value("${cus.msg}")
    private String msg;
    @Resource
    private TestMapper testMapper;
    @Resource
    private Test1Mapper test1Mapper;
    @Resource
    private Test1Mapper test2Mapper;
    @Resource
    private PgUtil pgUtil;

    @Override
    public String updateSchedule() {
        String successMsg = "";
        String errorMsg = "";
        String resultMessage = "";
        String tableName = "schema.tableName";
        String tableColumnStr = "schema.tableName(colum1,colum2,colum3,colum4,colum5)";

        List<Map> messageList = testMapper.getMessage();

        Set<String> messageFilter = messageList1 .stream()
                .map(map -> map.get("colum1") + "|" + map.get("colum2"))
                .collect(Collectors.toSet());

        List<Map<String, String>> messageList1 = test1Mapper.getMessage1("", msg);
        List<Map<String, String>> messageList2 = test2Mapper.getMessage2("", msg);
        List<Map<String, String>> allList = new ArrayList<>(messageList1);
        allList.addAll(messageList2);
        List<String> resultList = allList.stream()
                .filter(map -> messageFilter.contains(map.get("colum1") + "|" + map.get("colum2")))
                .map(map -> {
                    //提取字段值并拼接
                    String rowString = map.get("colum1") + "|" +
                            (map.get("colum2")==null?"\\N":map.get("colum2")) + "|" +     //在这里\\N代表占位符,使用COPY命令将数据插入PostgreSQL表时,\\N会被正确识别为NULL值
                            map.get("colum3") + "|" +
                            map.get("colum4") + "|" +
                            map.get("colum5");
                    return rowString;
                })
                .collect(Collectors.toList());
        try {
            pgUtil.copyInBandTmWorkStationCommit(tableName, tableColumnStr, resultList);
            logger.info("数据(条数:" + resultList.size() + ")临时表插入成功!");
        } catch (Exception e) {
            errorMsg += "数据临时表插入失败!\n";
            logger.info("数据临时表插入失败!");
        }
        try {
            testMapper.insertTest1();
            logger.info("insertTest1数据插入成功!");
        } catch (Exception e) {
            errorMsg += "insertTest1数据插入失败!\n";
            logger.info("insertTest1数据插入失败!");
        }
        try {
            testMapper.insertTest2();
            logger.info("insertTest2数据插入成功!");
        } catch (Exception e) {
            errorMsg += "insertTest2数据插入失败!\n";
            logger.info("insertTest2数据插入失败!");
        }
        
        successMsg += "临时表数据(条数:" + resultList.size() + ")\n数据更新成功\n";
        
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        resultMessage = "successMsg日期:" + sdf.format(new Date()) + "Start\n" +successMsg + "successMsg" +"日期日期:" + sdf.format(new Date()) + "End\n";
        resultMessage += "errorMsg日期:" + sdf.format(new Date()) + "Start\n" + errorMsg + "errorMsg日期日期:" + sdf.format(new Date()) + "End";
        logger.info(resultMessage);
        return resultMessage;
    }

}

TestMapper.java

import com.github.houbb.heaven.annotation.reflect.Param;
import com.example.config.DynamicDS;

import java.util.List;
import java.util.Map;

public interface TestMapper{

    @DynamicDS("")
    List<Map> getMessage();
    @DynamicDS("")
    void insertTest1();
    @DynamicDS("")
    void insertTest2();
    
}

TestMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.testMapper.TestMapper">

    <resultMap id="message" type="map">
        <result column="colum1" property="colum1"/>
        <result column="colum2" property="colum2"/>
    </resultMap>


    <select id="getMessage" resultMap="message">
        
    </select>

    <insert id="insertTest1">
        
    </insert>

    <insert id="insertTest2">
        
    </insert>


</mapper>

Test1Mapper.java

import com.github.houbb.heaven.annotation.reflect.Param;
import com.example.config.DynamicDS;

import java.util.List;
import java.util.Map;

public interface Test1Mapper{


    @DynamicDS("1")
    List<Map<String,String>> getMessage1(@Param("test1") String test1, @Param("msg") String msg);
}

Test1Mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.test1Mapper.Test1Mapper">
    <resultMap id="message1" type="map">
        <result column="colum1" property="colum1"/>
        <result column="colum2" property="colum2"/>
        <result column="colum3" property="colum3"/>
        <result column="colum4" property="colum4"/>
        <result column="colum5" property="colum5"/>
    </resultMap>
    <select id="getMessage1" resultMap="message1" >
        
    </select>
</mapper>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值