前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >20201001_kpay支付项目搭建

20201001_kpay支付项目搭建

原创
作者头像
雨粒石
修改2020-10-22 11:48:44
1.3K0
修改2020-10-22 11:48:44
举报

新项目搭建方案

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

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模块引入依赖

<!-- 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模块创建配置类

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,可以考虑加如下配置

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

最后的解决办法:删除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增加

spring:
  profiles:
    active: @profileActive@
    include: dev,test,prod

多配置文件,根pom中增加

<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里面

kpay-dao的pom文件增加依赖

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

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

kpay-dao的pom文件增加依赖

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

application-dev.yml增加druid配置

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#%E6%B7%BB%E5%8A%A0%E4%BE%9D%E8%B5%96

<dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.4.0</version>
</dependency>

mysql数据库配置

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模块加入依赖

       <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>

增加自动生成配置类

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();
    }

}

启动类上增加注解扫描

@MapperScan("com.dsw.kpay.dao.mapper.*")

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

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

        <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增加配置

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序列化方式,增加配置类

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中自带了如下的依赖

   <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>标签

<?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([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/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中增加配置

logging:
  config: classpath:logback-spring.xml

增加aop日志切面

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唯一标识

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

logback-dev.xml中增加配置

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

项目打包部署

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

            <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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 新项目搭建方案
    • 搭建过程中遇到的问题
      • 搭建过程中需要深入的知识点TODO
        • 从零搭建基础项目
          • 引入swagger
            • 修改application.properties为application.yml
              • 引入druid
                • 引入mybatisplus
                  • 新加自动生成实体类,mapper接口模块
                  • 启动类上增加注解扫描
                  • 比较复杂的sql,自定义实体类,接口,xml文件,同时需引入扫描配置路径
                • 引入redis
                  • 引入日志处理
                    • 项目打包部署
                    相关产品与服务
                    人脸支付
                    腾讯云人脸支付(Face Recognition Payment,FRP)基于腾讯优图先进的人脸识别技术,结合3D 结构光摄像头和整机设备,为您提供高可用高安全的刷脸支付模组和刷脸支付整机。您可以集成腾讯云人脸支付模组组装刷脸支付整机,或直接购买腾讯云刷脸支付整机。人脸支付的使用,可有效提升用户日常购物消费体验,提高商家的交易效率,带动零售业发展。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档