代码地址与接口看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客
之前只零碎的学习过spring-cloud-alibaba,并没有全面了解过,这次学习pig框架时,想着可以根据这个项目学习一下,练练手,于是断断续续的用了几天时间搭建了一下基础框架。目前就先重点记录一下遇到的问题吧,毕竟流程也不是特别复杂,就是有的东西没遇到过了解的也不深~
本篇文章包括:
1.将服务系统注册到nacos注册中心;
2.通过nacos实现配置动态更新;
3.添加fegin服务,实现服务之间调用;
4.添加网关(学会使用webflux,学会添加过滤器);
5.添加log服务,通过springevent实现,并使用注解使用(使用AOP);
剩余包括(会有变动):
6.添加 mysql 数据库调用,并使用mybatis-plus操作;
7.添加用户认证,基于oauth2的自定义密码模式(会涉及到redis配置与使用);
8.添加用户权限校验等逻辑;
9.添加微服务内部调用不鉴权逻辑;
目录
注意这两个地方:predicates、StripPrefix
A5.添加log服务,通过springevent实现,并使用注解使用;
A1.将服务系统注册到nacos注册中心;
以pig-auth举例,重点是导入对应的依赖包并且进行注册配置。
之后启动nacos,然后运行pig-auth程序并访问端点。端点可以正常访问,并且可以在nacos操作页面的服务列表查看到我们的pig-auth服务。
这一步骤很简单。
----------pom.xml
<dependencies>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- 看情况添加 -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
</build>
----------application.yml
spring:
application:
# 如果引用这个,需要加上<resources>配置
name: @artifactId@
cloud:
nacos:
discovery:
# ${NACOS_HOST:pig-auth}:表示启动时如果有NACOS_HOST属性值则用这个,如果没有则用pig-auth
server-addr: ${NACOS_HOST:pig-auth}:${NACOS_PORT:48848}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
遇见的问题:
问题:
注意,在创建项目的时候,发现pom中依赖引入后没有报错,但是无法import Class,一开始以为是 .iml 有问题(因为一开始生成的内容和别的包不一致),后来发现不是这个问题。
原因:
最终发现的是创建模块时没有自动进行相关配置(一般是会成功的,但是会有这种特殊情况):看下面的图二
1.模块的pom文件被maven忽略了;
2.没有给对应的文件夹设置make dictionary;
3.没有生成iml文件;
其中3.会根据模块的操作自定生成,一般不会有问题,那就先解决1.2.。
图二:创建模块时没有自动进行相关配置产生的情况
解决:
1.的原因是在maven中忽略掉了模块项目的pom文件,我们可以在:File——>Settings——>Maven——>ignored Files- 将有对勾的模块项目的pom点掉即可。看下面图三。
2.的原因是没有自动设置,那我们就手动在项目的java文件上右击,选择Mark Directory As——>Source roots ;在项目的resource文件上右击,选择Mark Directory As——>Resource roots ;看下面图四。
进行完上面两步操作后,iml一般就会生成,不用管他,直接添加依赖就可以操作项目了。
图三:修改maven ignored Files
图四:设置Mark Directory As
A2.通过nacos实现配置动态更新;
以pig-auth举例,重点是导入对应的依赖包,设置使用config配置,并且添加config配置。
这一步骤也很简单。
------------pom.xml
<!--新增一个依赖包-->
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<profiles>
<profile>
<id>dev</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
</properties>
<activation>
<!-- 默认环境 -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>
-----------application.yml
spring:
application:
# 如果引用这个,需要加上<resources>配置
name: @artifactId@ #等同于 pig-auth
cloud:
nacos:
discovery:
# ${NACOS_HOST:pig-auth}:表示启动时如果有NACOS_HOST属性值则用这个,如果没有则用pig-auth
server-addr: ${NACOS_HOST:pig-auth}:${NACOS_PORT:48848}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
# 添加这个配置,说明要引入注册中心的配置文件
config:
import:
# 设置要使用的nacos配置中心的配置文件
- optional:nacos:application-@profiles.active@.yml
# 如果使用@profiles.active@,需要在pom文件中配置<profiles>
- optional:nacos:${spring.application.name}-@profiles.active@.yml # 等同于 pig-auth-dev.yml
--------------打开nacos操作页面
找到配置管理下的配置列表,进行新增两个配置,里面的Data ID:要和上面config.import 的文件名一致。
例如,我们在application.yml中设置server:port: 3001,然后去nacos中添加如下一个配置application-dev.yml 并设置server:port: 3002,启动程序就会发现程序占用的端口是3002。
需要注意一点是,如果在程序已启动的状态下修改 nacos 配置文件中的配置,那么运行的程序使用到对应配置时会拿到最新修改的哦。
不过如果在程序已启动的状态下修改nacos中的 server:port: 3002 ,是并不会影响已运行的程序。
A3.添加fegin服务,实现服务之间调用;
以pig-auth举例,有几点操作:1.导入对应的依赖包;2.添加远程调用接口和提供方的目标接口,和消费方的调用接口;3.开启Feign的远程调用。
这一步骤也很简单。
-----------pom.xml (这里新创建了一个模块依赖这些包 pig-common-fegin)
<dependencies>
<!--feign 依赖:进行调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡依赖:选择要调用的服务-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
-----------提供方的目标接口在 pig-upms-biz
@RequestMapping("/upms")
@RestController
public class TestController {
@GetMapping("/test")
public String require() {
return "Hello , 欢迎访问 upms ! ";
}
@SysLog(value = "upms测试param")
@GetMapping("/test/param")
public String param(String name ,Integer id) {
return "Hello , 欢迎访问 upms ! 您的id="+id+",您的name="+name;
}
@SysLog(value = "upms测试body")
@PostMapping("/test/body")
public SysUser body(@RequestBody SysUser sysUser) {
return sysUser;
}
@SysLog(value = "upms测试get")
@GetMapping("/test/get/{id}")
public String get(@PathVariable("id") Integer id) {
return "Hello , 欢迎访问 upms ! "+id+"的用户信息是XXX";
}
}
-----------远程调用接口在 pig-upms-api
@FeignClient(contextId = "remoteTestService", value = ServiceNameConstants.UMPS_SERVICE)
public interface RemoteTestService {
@GetMapping("/upms/test")
String require() ;
@GetMapping("/upms/test/param")
String param(@RequestParam("name")String name , @RequestParam("id")Integer id) ;
@PostMapping("/upms/test/body")
SysUser body(SysUser sysUser) ;
@GetMapping("/upms/test/get/{id}")
public String get(@PathVariable("id") Integer id) ;
}
-----------消费方的调用接口在 pig-auth
@RequiredArgsConstructor
@RestController
@RequestMapping("/token")
public class TestController {
private final RemoteTestService remoteTestService;
@GetMapping("/test")
public String require() {
return "Hello , 欢迎访问 auth ! ";
}
@GetMapping("/test/param")
public String param(String name ,Integer id) {
return remoteTestService.param(name,id);
}
@PostMapping("/test/body")
public SysUser body(@RequestBody SysUser sysUser) {
return remoteTestService.body(sysUser);
}
@GetMapping("/test/get/{id}")
public String get(@PathVariable("id") Integer id) {
return remoteTestService.get(id);
}
}
------------pig-auth 启动类
//由于远程调用文件在另一个包里面(包名不一致),所以需要设置扫描路径
@EnableFeignClients(basePackages = "com.pig4cloud.pig")
@EnableDiscoveryClient
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class PigAuthApplication {
public static void main(String[] args) {
SpringApplication.run(PigAuthApplication.class, args);
}
}
成功访问!
遇到的问题:
在使用远程调用中,如果传递日期类型,会有入参出参问题。
比如我们使用post方式提交json类型的日期数据时,入参日期格式必须是:
{
"id":123,
"name":"你好",
"createTime":"2022-08-09T14:45:55",
"time":"2022-08-09T14:45:55"
}
出参是:
{
"id": 123,
"name": "你好",
"createTime": "2022-08-09T14:45:55",
"time": "2022-08-09T14:45:55.000+00:00"
}
所以就需要调整json格式的转化,详情见 pig-common-core 包下的 JacksonConfiguration类。之后再深入研究。
A4.添加网关(学会使用webflux,学会添加过滤器);
B1.添加网关
以pig-gateway举例,有几点操作:1.导入对应的依赖包;需要注意的是网关依赖包使用的是webflux,所以不能和webmvc共用。2.添加网关配置文件;3.(可忽略)如果有需要提前过滤就添加过滤器,如果网管也有端点就添加端点(例如可以在这里设置验证码逻辑);
-----------------pom.xml
<dependencies>
<!--gateway 网关依赖,内置 webflux 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- LB 负载均衡扩展 ,不添加无法通过lb路由到指定微服务(看nacos配置的uri: lb://pig-auth)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
--------------------application.yml
server:
port: 9998
spring:
application:
name: @artifactId@
cloud:
nacos:
discovery:
# NACOS_HOST 这种写法是通过 启动java时携带的参数注入的
server-addr: ${NACOS_HOST:pig-register}:${NACOS_PORT:48848} # 127.0.0.1:48848 Nacos Server地址信息 : 用8847端口时,nacos提示端口占用但是没有占用,所以更改为4万端口
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
config:
import:
# optional表示允许配置服务器连接不成功时启动微服务(如导入的文件不存在时),可选
- optional:nacos:application-@profiles.active@.yml
- optional:nacos:${spring.application.name}-@profiles.active@.yml
--------------------nacos上的 pig-gateway-dev.xml
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
routes:
# 认证中心
- id: pig-auth
uri: lb://pig-auth
predicates:
- Path=/auth/**
filters:
- StripPrefix=1 #去掉特定前缀个数
#UPMS 模块
- id: pig-upms-biz
uri: lb://pig-upms-biz
predicates:
- Path=/admin/**
filters:
- StripPrefix=1 #去掉特定前缀个数
注意这两个地方:predicates、StripPrefix
predicates:
- Path=/auth/**
filters:
- StripPrefix=1 #去掉特定前缀个数
predicates是断言,对路径进行匹配的,只有符合这个断言的才会进入这个路由;
filters是过滤器,- StripPrefix=1会修改网关过滤器的参数,意思是在调用下一个请求前从请求中去掉N个路径;
首先来看pig-auth的请求路径是:127.0.0.1:3001/token/test;
然后我们添加了网关断言路径为:127.0.0.1:9998/auth/****** 格式的;
此时如果我们要访问pig-auth请求,访问路径就是127.0.0.1:9998/auth/token/test;只有这样才会进入pig-auth路由。
但是进入路由向auth服务发起请求时路径也会携带 /auth/ !但是auth没有/auth/token/test路径的资源,这就会导致 404 ,找不到资源!
所以在匹配中断言后,需要去掉 /auth/ 路径,就直接设置 - StripPrefix=1就可以了。
最后启动 gateway、auth 两个程序,并访问 127.0.0.1:9998/auth/token/test
B2.使用webflux添加端点
有两个重点类,一个是HandlerFunction接口,类似于响应处理者,接收ServerRequest,返回ServerResponse;一个是RouterFunction会把请求url和HandlerFunction对应起来;
其中还有一个Mono类,HandlerFunction接口的handle()返回的类型就是Mono;
整个开发过程有几步:
1.创建HandlerFunction,实现输入ServerRequest,输出ServerResponse;
2.创建RouterFunction,把请求url和HandlerFunction对应起来;
3.把RouterFunction交给容器Server处理。
---------------------1.创建HandlerFunction
//方式一:通过实现接口创建HandlerFunction
@Component
public class ImageCodeHandler implements HandlerFunction<ServerResponse> {
//返回图片类型
@SneakyThrows
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
FileInputStream input = new FileInputStream("C:\\Users\\Administrator\\Desktop\\1.jpg");
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
int ch;
while((ch = input.read()) != -1){
os.write(ch);
}
// 统一服务器接口调用的响应
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.IMAGE_JPEG)
.body(BodyInserters.fromResource(new ByteArrayResource(os.toByteArray())));
}
}
//方式二:通过创建Mono来使用HandlerFunction
@Component
public class TestHandler {
// 返回包含时间字符串的ServerResponse
public Mono<ServerResponse> getTime(ServerRequest serverRequest) {
MultiValueMap<String, String> query = serverRequest.queryParams();
return ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(Mono.just("Now is " + new SimpleDateFormat("HH:mm:ss").format(new Date())), String.class);
}
// 返回包含日期字符串的ServerResponse
public Mono<ServerResponse> getDate(ServerRequest serverRequest) {
return ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(Mono.just("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date())), String.class);
}
}
--------------------2.创建RouterFunction;3.并生成bean
//
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
public class RouterFunctionConfiguration {
private final ImageCodeHandler imageCodeHandler;
private final TestHandler testHandler;
@Bean
public RouterFunction<ServerResponse> routerFunction() {
return RouterFunctions
.route(RequestPredicates.path("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), imageCodeHandler)
.andRoute(GET("/time"), testHandler::getTime) //此处是按照 Lambda 简化格式写的 ,由于只有一行代码,所以可以直接写代码
.andRoute(GET("/date"), (serverRequest) -> testHandler.getDate(serverRequest)); //此处是按照 Lambda 格式写的
}
}
成功调用 127.0.0.1:9998/time?date=123:
B3.网关添加过滤器
网关过滤器分为两种,一种是GlobalFilter全局过滤器,作用在所有路由上,不用特殊配置,访问任意路由都会经过该过滤器。一种是GatewayFilter过滤器,配置在指定路由上,访问指定路由时才会经过该过滤器。
比如说验证码校验只有授权的时候才会使用,那验证码校验逻辑就使用GatewayFilter类型,然后只配置给授权路由;
比如说路由访问日志记录是所有路由都需要记录,那访问日志记录逻辑就使用GlobalFilter类型,只需要将bean交给容器就可以了;
C1.GatewayFilter过滤器
//----------------GatewayFilter过滤器
//1.继承AbstractGatewayFilterFactory
@Slf4j
@RequiredArgsConstructor
public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
boolean isAuthToken = CharSequenceUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL);
// 不是登录请求,直接向下执行
if (!isAuthToken) {
return chain.filter(exchange);
}
try {
//校验验证码
checkCode(request);
}
catch (Exception e) {
//若有异常则返回ServerHttpResponse类型,输出为 Json 格式
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
final String errMsg = e.getMessage();
return response.writeWith(Mono.create(monoSink -> {
try {
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = objectMapper.writeValueAsBytes(R.failed(errMsg));
DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
monoSink.success(dataBuffer);
}
catch (JsonProcessingException jsonProcessingException) {
log.error("对象输出异常", jsonProcessingException);
monoSink.error(jsonProcessingException);
}
}));
}
return chain.filter(exchange);
};
}
@SneakyThrows
private void checkCode(ServerHttpRequest request) {
String code = request.getQueryParams().getFirst("code");
if (CharSequenceUtil.isBlank(code)) {
throw new RuntimeException("验证码不能为空");
}
//校验验证码实际业务逻辑
}
}
//2.添加到容器中
@Configuration(proxyBeanMethods = false)
public class GatewayConfiguration {
@Bean
public ValidateCodeGatewayFilter validateCodeGatewayFilter() {
return new ValidateCodeGatewayFilter();
}
}
//3.配置到相关路由上 application.yml
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
routes:
# 认证中心
- id: pig-auth
uri: lb://pig-auth
predicates:
- Path=/auth/**
filters:
- StripPrefix=1 #去掉特定前缀个数
# 验证码处理
- ValidateCodeGatewayFilter
访问登录请求的路径 127.0.0.1:9998/auth/token/test/param ,一定得是这个哈,因为在过滤器中有个逻辑,只有登录请求才会被拦截哦~
现在就做了一个简单逻辑,被拦截后如果没有code参数,就抛出异常~
C2.GlobalFilter过滤器
//-------------------GlobalFilter过滤器
//1.实现 GlobalFilter,并添加到容器中
@Slf4j
@Component
public class ApiLoggingFilter implements GlobalFilter, Ordered{
private static final String START_TIME = "startTime";
private static final String X_REAL_IP = "X-Real-IP";// nginx需要配置
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (log.isDebugEnabled()) {
String info = String.format("Method:{%s} Host:{%s} Path:{%s} Query:{%s}",
exchange.getRequest().getMethod().name(), exchange.getRequest().getURI().getHost(),
exchange.getRequest().getURI().getPath(), exchange.getRequest().getQueryParams());
log.debug(info);
}
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(START_TIME);
if (startTime != null) {
Long executeTime = (System.currentTimeMillis() - startTime);
List<String> ips = exchange.getRequest().getHeaders().get(X_REAL_IP);
String ip = ips != null ? ips.get(0) : null;
String api = exchange.getRequest().getURI().getRawPath();
int code = 500;
if (exchange.getResponse().getStatusCode() != null) {
code = exchange.getResponse().getStatusCode().value();
}
// 当前仅记录日志,后续可以添加日志队列,来过滤请求慢的接口
if (log.isDebugEnabled()) {
log.debug("来自IP地址:{}的请求接口:{},响应状态码:{},请求耗时:{}ms", ip, api, code, executeTime);
}
}
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
不需要额外的配置,访问任意接口都会经过这个过滤器,但是要注意,全局过滤器过滤器的路由指的是网关路由!我们自己在网关中写的端点不会被拦截!比如访问127.0.0.1:9998/code,就不会进入网关的过滤器链,不会通过过滤器~
A5.添加log服务,通过springevent实现,并使用注解使用;
B1.Slf4j和logback简单使用
首先来看单纯的日志打印:
springboot默认使用Slf4j和logback,Slf4j是日志框架,不实现日志功能,仅整合日志,方便使用日志;logback是具体的日志实现框架;
使用方式很简单,就是类中添加 Logger log属性(也可以导入依赖包lombok,然后直接使用注解@Slf4j),然后直接用 log.debug()、log.info()调用就可以啦。
添加日志输出语句后,还需要设置日志输出格式logback.xml(不设置也可以用默认的)和输出level。
1.添加@Slf4j 或者 Logger log = LoggerFactory.getLogger(XXX.class);然后使用 log 调用方法;
2.在resources中添加 logback.xml 配置文件(可以看看SpringBoot2.x整合slf4j+logback日志框架_孔子-说的博客-CSDN博客_springboot整合logback)
3.在application.yml中配置日志级别;
B2.接口级的日志记录
上面的是打印日志的操作,但是我们不是单纯的要打印日志。
日志的很大的作用是记录程序执行的操作,以方便追查bug。而针对每次操作的日志就很有用了。比如需要记录某数据的删除操作,我们可以通过上面的三步进行日志记录,但是这会非常麻烦,而且存在代码入侵,耦合度很高。
所以我们使用AOP切面编程,通过添加注解的方式来降低日志输出耦合。
1.创建自定义日志注解(入参为描述);
2.创建切面类,添加切点和通知,在通知里面添加日志输出;
3.将自定义日志注解添加到接口上;
4.将切面类添加到容器中;
在上面这个记录日志的过程中,我们会发现,对于日志来说,一种是方便监管的日志,就是平台方进行监控管理查看的,一种是用户的业务日志,就是用户登陆系统后进行的操作记录,方便用户进行查看;
所以我们在生成系统日志的情况下,可能也会需要生成业务日志,这就会需要保存到数据库中等操作,为了使代码简洁,代码解耦,我们可以使用Spring Event,就是观察者模式,就是以发布订阅。(消息队列也是用来解耦的,但是成本太大,所以简单的情况下就用Spring Event)。
步骤就会变成:
1.创建日志事件,继承ApplicationEvent,代用父类构造函数;
2.创建异步日志监听事件,具体的业务日志操作在这里实现;
3.创建自定义日志注解(入参为日志描述);
4.创建切面类,添加切点和通知,在通知里面添加监管日志输出,并且发布日志事件(由于发需要使用ApplicationContext来进行发布,我们就可以创建一个SpringContextHolder来专门操作ApplicationContext);
5.将自定义日志注解添加到接口上;
6.将切面类、异步日志监听事件添加到容器中;
//除第五步,其余的都在 pig-common-log 模块中
//1.创建日志事件,继承ApplicationEvent,代用父类构造函数;
public class SysLogEvent extends ApplicationEvent {
public SysLogEvent(Object source) {
super(source);
}
}
//2.创建异步日志监听事件,具体的业务日志操作在这里实现;
@Slf4j
@RequiredArgsConstructor
public class SysLogListener {
@Async //设置为异步的,如果使用了这个注解记得要加上@EnableAsync,开启异步
@Order
@EventListener(SysLogEvent.class) //设置监听的事件
public void saveSysLog(SysLogEvent event) {
SysLog sysLog = (SysLog) event.getSource();
//这里编写将日志进行存储等业务,方便后期使用
log.info("将日志进行存储等操作,方便后期使用,存储的日志事件:{}",sysLog);
}
}
//3.创建自定义日志注解(入参为日志描述);
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* 描述
* @return {String}
*/
String value() default "";
}
//4.创建切面类,添加切点和通知,在通知里面添加监管日志输出,并且发布日志事件(由于发需要使用ApplicationContext来进行发布,我们就可以创建一个SpringContextHolder来专门操作ApplicationContext);
@Aspect
@Slf4j
public class SysLogAspect {
//
@Around("@annotation(sysLog)")
@SneakyThrows
public Object around(ProceedingJoinPoint point, com.pig4cloud.pig.common.log.annotation.SysLog sysLog) {
String strClassName = point.getTarget().getClass().getName();
String strMethodName = point.getSignature().getName();
//记录系统访问日志,也可以包括操作用户、入参、出参等
log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);
String value = sysLog.value();
SysLog logVo = SysLogUtils.getSysLog();
logVo.setTitle(value);
//记录执行开始时间
Long startTime = System.currentTimeMillis();
Object obj;
try {
//执行具体业务
obj = point.proceed();
} catch (Exception e) {
logVo.setType(LogTypeEnum.ERROR.getType());
logVo.setException(e.getMessage());
throw e;
} finally {
Long endTime = System.currentTimeMillis();
logVo.setTime(endTime - startTime);
// 发送异步日志事件(无论是否有异常都记录)
SpringContextHolder.publishEvent(new SysLogEvent(logVo));
}
return obj;
}
}
//5.将自定义日志注解添加到接口上;
@SysLog(value = "upms测试param")
@GetMapping("/test/param")
public String param(String name ,Integer id) {
return "Hello , 欢迎访问 upms ! 您的id="+id+",您的name="+name;
}
//6.将切面类、异步日志监听事件添加到容器中;
@EnableAsync
@RequiredArgsConstructor
@ConditionalOnWebApplication
@Configuration(proxyBeanMethods = false)
public class LogAutoConfiguration {
@Bean
public SysLogListener sysLogListener() {
return new SysLogListener();
}
@Bean
public SysLogAspect sysLogAspect() {
return new SysLogAspect();
}
}
由于这是在pig-common-log中添加的,使用这个包的模块无法通过包名扫描不到配置类,就需要在 resources->META-INF->spring.factories里面添加下面代码,用来加载bean:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pig4cloud.pig.common.log.LogAutoConfiguration