当前位置:实例文章 » 其他实例» [文章]No6.从零搭建spring-cloud-alibaba微服务框架,实现fegin、gateway、springevent等(一);

No6.从零搭建spring-cloud-alibaba微服务框架,实现fegin、gateway、springevent等(一);

发布人:清晨敲代码 发布时间:2022-12-09 10:07 阅读次数:7

代码地址与接口看总目录:【学习笔记】记录冷冷-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.添加微服务内部调用不鉴权逻辑;

目录

A1.将服务系统注册到nacos注册中心;

遇见的问题:

A2.通过nacos实现配置动态更新;

A3.添加fegin服务,实现服务之间调用;

遇到的问题:

A4.添加网关(学会使用webflux,学会添加过滤器);

B1.添加网关

注意这两个地方:predicates、StripPrefix

B2.使用webflux添加端点

B3.网关添加过滤器

C1.GatewayFilter过滤器

C2.GlobalFilter过滤器

A5.添加log服务,通过springevent实现,并使用注解使用;

B1.Slf4j和logback简单使用

B2.接口级的日志记录


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

相关标签:

免责声明

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱290110527@qq.com删除。

Top