20201001_kpay支付项目搭建

2020-10-22 11:48:44 浏览数 (2)

新项目搭建方案

如何快速的开始一个新项目,最高效的做法是复制一份现有生产环境的项目,修改一下包名称。这样代码是经过考验的,且很多基础功能的代码可以直接复用。

kpay支付项目,想从零开始搭建,包括技术选型,架构设计。

搭建过程中遇到的问题

  1. swagger访问404:根本原因module未生成target,需引入到其他模块中,其他原因springmvc拦截掉了swagger-ui.html的请求等
  2. druid监控后台报404:需引入druid-spring-boot-starter依赖,而不是druid

搭建过程中需要深入的知识点TODO

  1. 类加载机制:建module,搭建swagger的时候碰到
  2. druid相关知识:https://github.com/alibaba/druid
  3. mybatisplus相关知识:https://baomidou.com/ https://mybatis.plus/guide/
  4. spring aop
  5. maven pom
  6. swagger3:https://mp.weixin.qq.com/s/mbcb-z8L-qkngV0xx_WdQA

从零搭建基础项目

1. 阿里云脚手架:https://start.aliyun.com/bootstrap.html?spm=a2ck6.17690074.0.0.503c5bb4Lv1cUl

选择分层结构进行初始化,疑问点

1)manager模块做什么用的

2)arthas工具如何使用

2.spring脚手架:https://start.spring.io/

不能生成模块化的项目结构,所以采用阿里云的脚手架

引入swagger

config模块引入依赖

代码语言:javascript复制
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

config模块创建配置类

代码语言:javascript复制
package com.dsw.kpay.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

/**
 * 标题、简要说明. <br>
 * 类详细说明.
 * <p>
 * Copyright: Copyright (c) 2020-10-09 : 14:36
 * <p>
 * Company: kpay
 * <p>
 *
 * @author dengsiwen
 * @version 1.0.0
 * @email 491408802@qq.com
 **/
@Configuration
@EnableSwagger2
public class Swagger2Config {
    /**
     * 第三方回调接口
     *
     * @return
     */
    @Bean
    public Docket createCallBackApi() {
        List<Parameter> ps = new ArrayList<>();
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo("第三方回调接口", "微信,支付宝,京东,银联等支付渠道回调接口"))
                .globalOperationParameters(ps).groupName("第三方回调接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.dsw.kpay.web.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 支付,退款等相关接口
     *
     * @return
     */
    @Bean
    public Docket createExpressApi() {
        List<Parameter> ps = new ArrayList<>();
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo("支付,退款等相关接口", "支付,退款等相关接口"))
                .globalOperationParameters(ps).groupName("支付,退款等相关接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.dsw.kpay.web.expose"))
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo(String title, String desc) {
        return new ApiInfoBuilder()
                .title(title)
                .description(desc)
                .termsOfServiceUrl("http://wwww.kpay.com/")
                .version("1.0")
                .build();
    }
}

正常来讲,上面两步就已经配置好springboot2 swagger2

如何报404,可以考虑加如下配置

代码语言:javascript复制
package com.dsw.kpay.api.model;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * 标题、简要说明. <br>
 * 类详细说明.
 * <p>
 * Copyright: Copyright (c) 2020-10-09 : 16:06
 * <p>
 * Company: kpay
 * <p>
 *
 * @author dengsiwen
 * @version 1.0.0
 * @email 491408802@qq.com
 **/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {

        // 解决静态资源无法访问
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");

        // 解决swagger无法访问
        registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");

        // 解决swagger的js文件无法访问
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

    }
}

但从零搭建项目时,碰到了一个坑,用阿里云脚手架初始化项目后,新增了一个module:kpay-config,上面的几个操作都是放在该module下,项目启动时,新创建的kpay-config无法生成target,导致访问swagger-ui.html时,一直报404

代码语言:javascript复制
最后的解决办法:删除idea文件夹和各模块下的impl文件,然后清除idea缓存:file->invalidate caches/restart
此操作非正确解决方法,其根本原因是kpay-config新建好以后,未引入到其他模块中,所以启动项目时,未生成target目录
需要将新增加的模块引入到其他模块中
TODO:类加载机制

创建多模块项目

创建多模块项目创建多模块项目

选择maven

选择maven选择maven

GroupId:包名

ArtifactId:模块名

填写模块名填写模块名

刚开始创建的时候,没有产生.iml文件 ,java目录也没变颜色,需要import一下pom文件

此时重新启动项目,kpay-generator模块下,不会生成target目录,原因是未将新模块引入到其他模块中,但需要避免模块A引入模块B,模块B再引入模块A的情况发生

修改application.properties为application.yml

多配置文件,application.yml中放不变的配置,application-dev.yml/application-test.yml/application-prod.yml中放数据库连接等配置,环境不同配置不一样,最终生产环境由运维控制

多配置文件,application.yml增加

代码语言:javascript复制
spring:
  profiles:
    active: @profileActive@
    include: dev,test,prod

多配置文件,根pom中增加

代码语言:javascript复制
<profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <profileActive>dev</profileActive>
                <profilePkg>开发环境</profilePkg>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <profileActive>prod</profileActive>
                <profilePkg>生产环境</profilePkg>
            </properties>
        </profile>
    </profiles>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>application.yml</include>
                </includes>
            </resource>
            <!--在本地跑把这个注释放开-->
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>application-${profileActive}.yml</include>
                    <!--<include>logback-spring-${profileActive}.xml</include>-->
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <delimiters>
                        <delimiter>@</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!--如果想在没有web.xml文件的情况下构建WAR,请设置为false。-->
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

引入druid

jar包,<druid.version>1.2.1</druid.version>写在跟pom里面

代码语言:javascript复制
kpay-dao的pom文件增加依赖

<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid</artifactId>
     <version>${druid.version}</version>
</dependency>

引入上面的jar,访问druid监控页面时,会报404,改成如下

代码语言:javascript复制
kpay-dao的pom文件增加依赖

dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid-spring-boot-starter</artifactId>
     <version>1.1.22</version>
</dependency>

application-dev.yml增加druid配置

代码语言:javascript复制
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/kpay?characterEncoding=utf8&autoReconnect=true&rewritebatchedstatements=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    druid:
      #初始化大小
      initialSize: 5
      #最小值
      minIdle: 5
      #最大值
      maxActive: 20
      #最大等待时间,配置获取连接等待超时,时间单位都是毫秒ms
      maxWait: 60000
      #配置间隔多久才进行一次检测,检测需要关闭的空闲连接
      timeBetweenEvictionRunsMillis: 60000
      #配置一个连接在池中最小生存的时间
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,
      #'wall'用于防火墙,SpringBoot中没有log4j,我改成了log4j2
      filters: stat,wall,log4j2
      #最大PSCache连接
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
      # 配置StatFilter
      web-stat-filter:
        #默认为false,设置为true启动
        enabled: true
        url-pattern: "/*"
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      #配置StatViewServlet
      stat-view-servlet:
        url-pattern: "/druid/*"
        #允许那些ip
        allow: 127.0.0.1
        login-username: admin
        login-password: 123456
        #禁止那些ip
        deny: 192.168.1.102
        #是否可以重置
        reset-enable: true
        #启用
        enabled: true

引入mybatisplus

引入spring-boot-starterspring-boot-starter-testmybatis-plus-boot-starterlombok、mysql等

参考:https://mybatis.plus/guide/quick-start.html#添加依赖

代码语言:javascript复制
<dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.4.0</version>
</dependency>

mysql数据库配置

代码语言:javascript复制
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/sc_ry?characterEncoding=utf8&autoReconnect=true&rewritebatchedstatements=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
    username: admin
    password: admin@123

新加自动生成实体类,mapper接口模块

kpay-generator模块加入依赖

代码语言:javascript复制
       <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

增加自动生成配置类

代码语言:javascript复制
package com.dsw.kpay.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.apache.commons.lang3.StringUtils;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 标题、简要说明. <br>
 * 类详细说明.
 * <p>
 * Copyright: Copyright (c) 2020-10-11 : 19:05
 * <p>
 * Company: kpay
 * <p>
 *
 * @author dengsiwen
 * @version 1.0.0
 * @email 491408802@qq.com
 **/
public class CodeGenerator {
    private static final Path main = Paths.get(System.getProperty("user.dir"), "kpay-dao", "src", "main").normalize();
    private static final String[] prefix =new String[]{"sc_","t_"};

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入"   tip   ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的"   tip   "!");
    }

    public static void main(String[] args) {
        System.out.println(System.getProperty("user.dir"));
        System.out.println(main);

        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = Paths.get(main.toString(), "java").toString();
        gc.setOutputDir(projectPath);
        gc.setAuthor("dengsiwen");
        gc.setOpen(false);
        //是否覆盖文件
        gc.setFileOverride(true);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/kpay?useUnicode=true&useSSL=false&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("p@ssw0rd");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.dsw.kpay.dao");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return main   "/resources/mapper/"   pc.getModuleName()
                          "/"   tableInfo.getEntityName()   "Mapper"   StringPool.DOT_XML;
            }
        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录,自定义目录用");
                if (fileType == FileType.MAPPER) {
                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
                    return !new File(filePath).exists();
                }
                // 允许生成模板文件
                return true;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        // templateConfig.setEntity("templates/entity2.java");
         templateConfig.setService(null);
         templateConfig.setServiceImpl(null);
         templateConfig.setController(null);

        templateConfig.setXml("");
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//        strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        // 公共父类
//        strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
//        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(prefix);
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

}

启动类上增加注解扫描

代码语言:javascript复制
@MapperScan("com.dsw.kpay.dao.mapper.*")

比较复杂的sql,自定义实体类,接口,xml文件,同时需引入扫描配置路径

代码语言:javascript复制
mybatis-plus:
  mapper-locations: classpath:com/dsw/kpay/dao/mapper/ext/*.xml
  configuration:
    map-underscore-to-camel-case: true
    #mybatis-plus配置控制台打印完整带参数SQL语句
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

引入redis

redis操作方式:jedis,redisson,lettuce,spring-data-redis

spring-data-redis引入jar

代码语言:javascript复制
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.8.0</version>
        </dependency>

yml增加配置

代码语言:javascript复制
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    #连接超时时间(2.0中该参数的类型为Duration,这里在配置的时候需要指明单位:
    timeout: 2000ms
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        #连接池中的最大空闲连接
        max-idle: 8
        #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        #连接池中的最小空闲连接
        min-idle: 0

redisTemplate默认是jdk序列化方式,增加配置类

代码语言:javascript复制
package com.dsw.kpay.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis的配置类
 * SDR项目操作Redis的话需要使用RedisTemplate对象
 * 但是该对象默认使用的数据序列化方法是JDK的,可能会存在特殊字符
 *
 * key和hashKey 我们推荐使用String序列化
 * value 我们推荐是JSON存储,使用Json序列化
 * <p>
 * Copyright: Copyright (c) 2020-10-12 : 15:37
 * <p>
 * Company: kpay
 * <p>
 *
 * @author dengsiwen
 * @version 1.0.0
 * @email 491408802@qq.com
 **/
@Configuration
public class RedisConfig {
    /**
     * @Bean:创建对象放到IOC容器中
     * RedisConnectionFactory:Redis的连接工厂,根据application.yml文件中的Redis的配置做Redis连接和连接池的管理
     * 该对象在项目初始化时被创建,有一个实现类是  LettuceConnectionFactory
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        //创建原生的RedisTemplate对象
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<String,Object>();
        //设置连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);
        // 使用Jackson2JsonRedisSerialize替换 默认的JDK序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //创建JSON序列化的对象,value使用JSON序列化方式
        ObjectMapper om = new ObjectMapper();
        //设置序列化的策略
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //启用默认的类型
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //引用JSON序列化
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //创建String序列化,我们的key使用String序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用JSON
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用JSON
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //使用该配置
        redisTemplate.afterPropertiesSet();
        //返回修改后的RedisTemplate对象
        return redisTemplate;
    }
}

新增redis工具类

引入日志处理

slf4j logback,不需要引入依赖,spring-boot-starter-parent中自带了如下的依赖

代码语言:javascript复制
   <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-to-slf4j</artifactId>
      <version>2.10.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jul-to-slf4j</artifactId>
      <version>1.7.25</version>
      <scope>compile</scope>
    </dependency>

增加配置文件logback-spring.xml:官方推荐使用的xml名字的格式为:logback-spring.xml而不是logback.xml,因为带spring后缀的可以使用<springProfile>标签

代码语言:javascript复制
<?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="/Users/dengsiwen/logs/kpay" />

    <!-- 彩色日志(IDE下载插件才可以生效) -->
    <!-- 彩色日志依赖的渲染类 -->
    <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}) [%X{logTrackId}] {faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([.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}/log_debug.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{logTrackId}] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/debug/log-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}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{logTrackId}] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-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}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{logTrackId}] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-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}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{logTrackId}] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-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.nmys.view" level="debug"/>
    </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>

    <!--生产环境:输出到文件-->
    <!--<springProfile name="pro">-->
    <!--<root level="info">-->
    <!--<appender-ref ref="CONSOLE" />-->
    <!--<appender-ref ref="DEBUG_FILE" />-->
    <!--<appender-ref ref="INFO_FILE" />-->
    <!--<appender-ref ref="ERROR_FILE" />-->
    <!--<appender-ref ref="WARN_FILE" />-->
    <!--</root>-->
    <!--</springProfile>-->

</configuration>

yml中增加配置

代码语言:javascript复制
logging:
  config: classpath:logback-spring.xml

增加aop日志切面

代码语言:javascript复制
package com.dsw.kpay.web.aop;

import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
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.lang.reflect.Method;
import java.util.Arrays;
import java.util.UUID;

/**
 * web请求出入参日志. <br>
 * 类详细说明.
 * <p>
 * Copyright: Copyright (c) 2020-10-13 : 10:38
 * <p>
 * Company: kpay
 * <p>
 *
 * @author dengsiwen
 * @version 1.0.0
 * @email 491408802@qq.com
 **/
@Component
@Aspect
@Slf4j
public class WebLogAspect {

    private static final String LOG_TRACK_ID="logTrackId";

    //创建切面,拦截所有控制器下的所有方法
    @Pointcut("execution(* com.dsw.kpay.web..*.*(..))")
    public void log() {
    }


    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        //获取ip,url
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        //通过切面对象获取请求方法名和请求参数
        String classMethod = signature.getDeclaringTypeName()   "."
                  signature.getName();

        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        String params = "";
        if (args != null && paramNames != null) {
            for (int i = 0; i < args.length; i  ) {
                params  = " "   paramNames[i]   ": "   args[i];
            }
        }

        RequestLog requestLog = new RequestLog(url, ip, classMethod, params);

        log.info("Request : {}", requestLog);

        String uuid = UUID.randomUUID().toString();
        log.info("put requestId ({}) to logger", uuid);
        MDC.put(LOG_TRACK_ID, uuid);
    }

    @AfterReturning(returning = "result", pointcut = "log()")
    public void doAfterReturn(Object result) {
        log.info("Response : {}", JSON.toJSONString(result));
        String uuid = MDC.get(LOG_TRACK_ID);
        log.info("remove requestId ({}) from logger", uuid);
        MDC.remove(LOG_TRACK_ID);
    }

    //封装日志信息类
    private class RequestLog {
        private String url;
        private String ip;
        private String classMethod;
        private String params;

        public RequestLog(String url, String ip, String classMethod, String params) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.params = params;
        }

        @Override
        public String toString() {
            return "{"  
                    "url='"   url   '''  
                    ", ip='"   ip   '''  
                    ", classMethod='"   classMethod   '''  
                    ", args="   params  
                    '}';
        }
    }
}

WebLogAspect类中增加MDC唯一标识

代码语言:javascript复制
String uuid = UUID.randomUUID().toString();
log.info("put requestId ({}) to logger", uuid);
MDC.put(LOG_TRACK_ID, uuid);

logback-dev.xml中增加配置

代码语言:javascript复制
[%X{logTrackId}]
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{logTrackId}] %-5level %logger{50} - %msg%n</pattern>

项目打包部署

maven打包编译的时候排除test测试类

代码语言:javascript复制
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>

打包命令:mvn clean package

启动命令:java -jar xxx.jar

0 人点赞