新项目搭建方案
如何快速的开始一个新项目,最高效的做法是复制一份现有生产环境的项目,修改一下包名称。这样代码是经过考验的,且很多基础功能的代码可以直接复用。
kpay支付项目,想从零开始搭建,包括技术选型,架构设计。
搭建过程中遇到的问题
- swagger访问404:根本原因module未生成target,需引入到其他模块中,其他原因springmvc拦截掉了swagger-ui.html的请求等
- druid监控后台报404:需引入druid-spring-boot-starter依赖,而不是druid
搭建过程中需要深入的知识点TODO
- 类加载机制:建module,搭建swagger的时候碰到
- druid相关知识:https://github.com/alibaba/druid
- mybatisplus相关知识:https://baomidou.com/ https://mybatis.plus/guide/
- spring aop
- maven pom
- 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
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-starter
、spring-boot-starter-test
、mybatis-plus-boot-starter
、lombok
、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