Browse Source

!5 集成skywalking
Merge pull request !5 from WangLH/featskywalking

芋道源码 3 years ago
parent
commit
135acaf7ba
23 changed files with 741 additions and 88 deletions
  1. 2 1
      README.md
  2. 2 0
      bin/deploy.sh
  3. 12 1
      pom.xml
  4. 1 1
      ruoyi-ui/src/views/infra/skywalking/index.vue
  5. 26 0
      ruoyi-ui/src/views/infra/skywalking/log.vue
  6. 254 2
      sql/ruoyi-vue-pro.sql
  7. 56 0
      src/main/java/cn/iocoder/dashboard/framework/tracer/config/TracerAutoConfiguration.java
  8. 14 0
      src/main/java/cn/iocoder/dashboard/framework/tracer/config/TracerProperties.java
  9. 42 0
      src/main/java/cn/iocoder/dashboard/framework/tracer/core/annotation/BizTrace.java
  10. 70 0
      src/main/java/cn/iocoder/dashboard/framework/tracer/core/aop/BizTraceAspect.java
  11. 33 0
      src/main/java/cn/iocoder/dashboard/framework/tracer/core/filter/TraceFilter.java
  12. 9 17
      src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java
  13. 2 2
      src/main/java/cn/iocoder/dashboard/framework/tracer/package-info.java
  14. 1 0
      src/main/java/cn/iocoder/dashboard/framework/web/core/enums/FilterOrderEnum.java
  15. 1 0
      src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java
  16. 4 4
      src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java
  17. 46 46
      src/main/java/cn/iocoder/dashboard/util/sping/SpringAopUtils.java
  18. 82 0
      src/main/java/cn/iocoder/dashboard/util/sping/SpringExpressionUtils.java
  19. 1 1
      src/main/resources/application-dev.yaml
  20. 1 1
      src/main/resources/application-local.yaml
  21. 76 0
      src/main/resources/logback-spring.xml
  22. 4 4
      src/test/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuServiceTest.java
  23. 2 8
      src/test/java/cn/iocoder/dashboard/modules/system/service/permission/SysRoleServiceTest.java

+ 2 - 1
README.md

@@ -56,7 +56,8 @@
 |  | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
 |  | Redis 监控 |监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
 | 🚀 |Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
-| 🚀 | 链路追踪 | 基于 SkyWalking 实现性能监控,特别是链路的追踪 |
+| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
+| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
 | 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 |
 | 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 |
 | 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 |

+ 2 - 0
bin/deploy.sh

@@ -26,6 +26,8 @@ JAVA_OPS="-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$HE
 # SkyWalking Agent 配置
 export SW_AGENT_NAME=$SERVER_NAME
 export SW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.0.84:11800
+export SW_GRPC_LOG_SERVER_HOST=192.168.0.84
+export SW_AGENT_TRACE_IGNORE_PATH="Redisson/PING,/actuator/**,/admin/**"
 export JAVA_AGENT=-javaagent:/work/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar
 
 # 备份

+ 12 - 1
pom.xml

@@ -38,7 +38,8 @@
         <lock4j.version>2.2.0</lock4j.version>
         <resilience4j.version>1.7.0</resilience4j.version>
         <!-- 监控相关 -->
-        <skywalking.version>8.3.0</skywalking.version>
+        <skywalking.version>8.5.0</skywalking.version>
+        <logback.encoder.version>6.1</logback.encoder.version>
         <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
         <!-- 工具类相关 -->
         <lombok.version>1.16.14</lombok.version>
@@ -190,6 +191,16 @@
             <artifactId>apm-toolkit-trace</artifactId>
             <version>${skywalking.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>apm-toolkit-logback-1.x</artifactId>
+            <version>${skywalking.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>apm-toolkit-opentracing</artifactId>
+            <version>${skywalking.version}</version>
+        </dependency>
 
         <dependency>
             <groupId>de.codecentric</groupId>

+ 1 - 1
ruoyi-ui/src/views/infra/skywalking/index.vue

@@ -8,7 +8,7 @@ export default {
   name: "SkyWalking",
   data() {
     return {
-      src: "http://skywalking.shop.iocoder.cn", // TODO 芋艿,后续改成配置读取
+      src: "http://skywalking.shop.iocoder.cn/trace", // TODO 芋艿,后续改成配置读取
       height: document.documentElement.clientHeight - 94.5 + "px;",
       loading: true
     };

+ 26 - 0
ruoyi-ui/src/views/infra/skywalking/log.vue

@@ -0,0 +1,26 @@
+<template>
+  <div v-loading="loading" :style="'height:'+ height">
+    <iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
+  </div>
+</template>
+<script>
+export default {
+  name: "SkyWalking-Log",
+  data() {
+    return {
+      src: "http://skywalking.shop.iocoder.cn/log", // TODO 芋艿,后续改成配置读取
+      height: document.documentElement.clientHeight - 94.5 + "px;",
+      loading: true
+    };
+  },
+  mounted: function() {
+    setTimeout(() => {
+      this.loading = false;
+    }, 230);
+    const that = this;
+    window.onresize = function temp() {
+      that.height = document.documentElement.clientHeight - 94.5 + "px;";
+    };
+  }
+};
+</script>

File diff suppressed because it is too large
+ 254 - 2
sql/ruoyi-vue-pro.sql


+ 56 - 0
src/main/java/cn/iocoder/dashboard/framework/tracer/config/TracerAutoConfiguration.java

@@ -0,0 +1,56 @@
+package cn.iocoder.dashboard.framework.tracer.config;
+
+import cn.iocoder.dashboard.framework.tracer.core.aop.BizTraceAspect;
+import cn.iocoder.dashboard.framework.tracer.core.filter.TraceFilter;
+import cn.iocoder.dashboard.framework.web.core.enums.FilterOrderEnum;
+import io.opentracing.Tracer;
+import org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Tracer 配置类
+ *
+ * @author mashu
+ */
+@Configuration
+@ConditionalOnClass({BizTraceAspect.class})
+@EnableConfigurationProperties(TracerProperties.class)
+@ConditionalOnProperty(prefix = "yudao.tracer", value = "enable", matchIfMissing = true)
+public class TracerAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean
+    public TracerProperties bizTracerProperties() {
+        return new TracerProperties();
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public BizTraceAspect bizTracingAop() {
+        return new BizTraceAspect();
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public Tracer tracer() {
+        return new SkywalkingTracer();
+    }
+
+    /**
+     * 创建 TraceFilter 过滤器,响应 header 设置 traceId
+     */
+    @Bean
+    public FilterRegistrationBean<TraceFilter> traceFilter() {
+        FilterRegistrationBean<TraceFilter> registrationBean = new FilterRegistrationBean<>();
+        registrationBean.setFilter(new TraceFilter());
+        registrationBean.setOrder(FilterOrderEnum.TRACE_FILTER);
+        return registrationBean;
+    }
+
+}

+ 14 - 0
src/main/java/cn/iocoder/dashboard/framework/tracer/config/TracerProperties.java

@@ -0,0 +1,14 @@
+package cn.iocoder.dashboard.framework.tracer.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * BizTracer配置类
+ *
+ * @author 麻薯
+ */
+@ConfigurationProperties("yudao.tracer")
+@Data
+public class TracerProperties {
+}

+ 42 - 0
src/main/java/cn/iocoder/dashboard/framework/tracer/core/annotation/BizTrace.java

@@ -0,0 +1,42 @@
+package cn.iocoder.dashboard.framework.tracer.core.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 打印业务编号 / 业务类型注解
+ *
+ * 使用时,需要设置 SkyWalking OAP Server 的 application.yaml 配置文件,修改 SW_SEARCHABLE_TAG_KEYS 配置项,
+ * 增加 biz.type 和 biz.id 两值,然后重启 SkyWalking OAP Server 服务器。
+ *
+ * @author 麻薯
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface BizTrace {
+
+    /**
+     * 业务编号 tag 名
+     */
+    String ID_TAG = "biz.id";
+    /**
+     * 业务类型 tag 名
+     */
+    String TYPE_TAG = "biz.type";
+
+    /**
+     * @return 操作名
+     */
+    String operationName() default "";
+
+    /**
+     * @return 业务编号
+     */
+    String id();
+
+    /**
+     * @return 业务类型
+     */
+    String type();
+
+}

+ 70 - 0
src/main/java/cn/iocoder/dashboard/framework/tracer/core/aop/BizTraceAspect.java

@@ -0,0 +1,70 @@
+package cn.iocoder.dashboard.framework.tracer.core.aop;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.dashboard.framework.tracer.core.annotation.BizTrace;
+import cn.iocoder.dashboard.util.sping.SpringExpressionUtils;
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+
+/**
+ * {@link BizTrace} 切面,记录业务链路
+ *
+ * @author mashu
+ */
+@Aspect
+@Slf4j
+public class BizTraceAspect {
+
+    private static final String BIZ_OPERATION_NAME_PREFIX = "Biz/";
+
+    @Resource
+    private Tracer tracer;
+
+    @Around(value = "@annotation(trace)")
+    public Object around(ProceedingJoinPoint joinPoint, BizTrace trace) throws Throwable {
+        // 创建 span
+        String operationName = getOperationName(joinPoint, trace);
+        Span span = tracer.buildSpan(operationName).startManual();
+        try {
+            // 执行原有方法
+            return joinPoint.proceed();
+        } finally {
+            // 设置 Span 的 biz 属性
+            setBizTag(span, joinPoint, trace);
+            // 完成 Span
+            span.finish();
+        }
+    }
+
+    private String getOperationName(ProceedingJoinPoint joinPoint, BizTrace trace) {
+        // 自定义操作名
+        if (StrUtil.isNotEmpty(trace.operationName())) {
+            return BIZ_OPERATION_NAME_PREFIX + trace.operationName();
+        }
+        // 默认操作名,使用方法名
+        return BIZ_OPERATION_NAME_PREFIX
+                + joinPoint.getSignature().getDeclaringType().getSimpleName()
+                + "/" + joinPoint.getSignature().getName();
+    }
+
+    private void setBizTag(Span span, ProceedingJoinPoint joinPoint, BizTrace trace) {
+        try {
+            Map<String, Object> result = SpringExpressionUtils.parseExpressions(joinPoint, asList(trace.type(), trace.id()));
+            span.setTag(BizTrace.TYPE_TAG, MapUtil.getStr(result, trace.type()));
+            span.setTag(BizTrace.ID_TAG, MapUtil.getStr(result, trace.id()));
+        } catch (Exception ex) {
+            log.error("[setBizTag][解析 bizType 与 bizId 发生异常]", ex);
+        }
+    }
+
+}

+ 33 - 0
src/main/java/cn/iocoder/dashboard/framework/tracer/core/filter/TraceFilter.java

@@ -0,0 +1,33 @@
+package cn.iocoder.dashboard.framework.tracer.core.filter;
+
+import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Trace 过滤器,打印 traceId 到 header 中返回
+ *
+ * @author 芋道源码
+ */
+public class TraceFilter extends OncePerRequestFilter {
+
+    /**
+     * Header 名 - 链路追踪编号
+     */
+    private static final String HEADER_NAME_TRACE_ID = "trace-id";
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+        // 设置响应 traceId
+        response.addHeader(HEADER_NAME_TRACE_ID, TracerUtils.getTraceId());
+        // 继续过滤
+        chain.doFilter(request, response);
+    }
+
+}

+ 9 - 17
src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java

@@ -1,10 +1,7 @@
 package cn.iocoder.dashboard.framework.tracer.core.util;
 
-import cn.hutool.core.util.StrUtil;
 import org.apache.skywalking.apm.toolkit.trace.TraceContext;
 
-import java.util.UUID;
-
 /**
  * 链路追踪工具类
  *
@@ -13,24 +10,19 @@ import java.util.UUID;
 public class TracerUtils {
 
     /**
-     * 获得链路追踪编号
-     *
-     * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
-     *
-     * 默认情况下,我们使用 Apache SkyWalking 的 traceId 作为链路追踪编号。当然,可能会存在并未引入 Skywalking 的情况,此时使用 UUID 。
+     * 私有化构造方法
+     */
+    private TracerUtils() {
+    }
+
+    /**
+     * 获得链路追踪编号,直接返回 SkyWalking 的 TraceId。
+     * 如果不存在的话为空字符串!!!
      *
      * @return 链路追踪编号
      */
     public static String getTraceId() {
-        // 通过 SkyWalking 获取链路编号
-        try {
-            String traceId = TraceContext.traceId();
-            if (StrUtil.isNotBlank(traceId)) {
-                return traceId;
-            }
-        } catch (Throwable ignore) {}
-        // TODO 芋艿 多次调用会问题
-        return UUID.randomUUID().toString();
+        return TraceContext.traceId();
     }
 
 }

+ 2 - 2
src/main/java/cn/iocoder/dashboard/framework/tracer/package-info.java

@@ -1,6 +1,6 @@
 /**
- * 链路追踪
+ * 使用 SkyWalking 组件,作为链路追踪、日志中心。
  *
- * 主要目的,是生成全局的链路追踪编号
+ * @author 芋道源码
  */
 package cn.iocoder.dashboard.framework.tracer;

+ 1 - 0
src/main/java/cn/iocoder/dashboard/framework/web/core/enums/FilterOrderEnum.java

@@ -9,6 +9,7 @@ public interface FilterOrderEnum {
 
     int CORS_FILTER = Integer.MIN_VALUE;
 
+    int TRACE_FILTER = CORS_FILTER + 1;
 
     int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
 

+ 1 - 0
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java

@@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.controller.auth;
 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
 import cn.iocoder.dashboard.common.pojo.CommonResult;
 import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
+import cn.iocoder.dashboard.framework.tracer.core.annotation.BizTrace;
 import cn.iocoder.dashboard.modules.system.controller.auth.vo.auth.SysAuthLoginReqVO;
 import cn.iocoder.dashboard.modules.system.controller.auth.vo.auth.SysAuthLoginRespVO;
 import cn.iocoder.dashboard.modules.system.controller.auth.vo.auth.SysAuthMenuRespVO;

+ 4 - 4
src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java

@@ -5,12 +5,11 @@ import cn.iocoder.dashboard.common.pojo.CommonResult;
 import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
+import cn.iocoder.dashboard.framework.tracer.core.annotation.BizTrace;
 import cn.iocoder.dashboard.modules.tool.controller.test.vo.*;
 import cn.iocoder.dashboard.modules.tool.convert.test.ToolTestDemoConvert;
 import cn.iocoder.dashboard.modules.tool.dal.dataobject.test.ToolTestDemoDO;
 import cn.iocoder.dashboard.modules.tool.service.test.ToolTestDemoService;
-import com.baomidou.lock.annotation.Lock4j;
-import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
@@ -66,7 +65,7 @@ public class ToolTestDemoController {
     @ApiOperation("获得测试示例")
     @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
     @PreAuthorize("@ss.hasPermission('tool:test-demo:query')")
-    @Lock4j // 分布式锁
+//    @Lock4j // 分布式锁
     public CommonResult<ToolTestDemoRespVO> getTestDemo(@RequestParam("id") Long id) {
         if (true) { // 测试分布式锁
             ThreadUtil.sleep(5, TimeUnit.SECONDS);
@@ -79,7 +78,8 @@ public class ToolTestDemoController {
     @ApiOperation("获得测试示例列表")
     @ApiImplicitParam(name = "ids", value = "编号列表", required = true, dataTypeClass = List.class)
     @PreAuthorize("@ss.hasPermission('tool:test-demo:query')")
-    @RateLimiter(name = "backendA")
+//    @RateLimiter(name = "backendA")
+    @BizTrace(id = "#ids", type = "'user'")
     public CommonResult<List<ToolTestDemoRespVO>> getTestDemoList(@RequestParam("ids") Collection<Long> ids) {
         List<ToolTestDemoDO> list = testDemoService.getTestDemoList(ids);
         return success(ToolTestDemoConvert.INSTANCE.convertList(list));

+ 46 - 46
src/test/java/cn/iocoder/dashboard/util/AopTargetUtils.java → src/main/java/cn/iocoder/dashboard/util/sping/SpringAopUtils.java

@@ -1,46 +1,46 @@
-package cn.iocoder.dashboard.util;
-
-import cn.hutool.core.bean.BeanUtil;
-import org.springframework.aop.framework.AdvisedSupport;
-import org.springframework.aop.framework.AopProxy;
-import org.springframework.aop.support.AopUtils;
-
-/**
- * Spring AOP 工具类
- *
- * 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
- */
-public class AopTargetUtils {
-
-    /**
-     * 获取代理的目标对象
-     *
-     * @param proxy 代理对象
-     * @return 目标对象
-     */
-    public static Object getTarget(Object proxy) throws Exception {
-        // 不是代理对象
-        if (!AopUtils.isAopProxy(proxy)) {
-            return proxy;
-        }
-        // Jdk 代理
-        if (AopUtils.isJdkDynamicProxy(proxy)) {
-            return getJdkDynamicProxyTargetObject(proxy);
-        }
-        // Cglib 代理
-        return getCglibProxyTargetObject(proxy);
-    }
-
-    private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
-        Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
-        AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
-        return advisedSupport.getTargetSource().getTarget();
-    }
-
-    private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
-        AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
-        AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
-        return advisedSupport.getTargetSource().getTarget();
-    }
-
-}
+package cn.iocoder.dashboard.util.sping;
+
+import cn.hutool.core.bean.BeanUtil;
+import org.springframework.aop.framework.AdvisedSupport;
+import org.springframework.aop.framework.AopProxy;
+import org.springframework.aop.support.AopUtils;
+
+/**
+ * Spring AOP 工具类
+ *
+ * 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
+ */
+public class SpringAopUtils {
+
+    /**
+     * 获取代理的目标对象
+     *
+     * @param proxy 代理对象
+     * @return 目标对象
+     */
+    public static Object getTarget(Object proxy) throws Exception {
+        // 不是代理对象
+        if (!AopUtils.isAopProxy(proxy)) {
+            return proxy;
+        }
+        // Jdk 代理
+        if (AopUtils.isJdkDynamicProxy(proxy)) {
+            return getJdkDynamicProxyTargetObject(proxy);
+        }
+        // Cglib 代理
+        return getCglibProxyTargetObject(proxy);
+    }
+
+    private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
+        Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
+        AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
+        return advisedSupport.getTargetSource().getTarget();
+    }
+
+    private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
+        AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
+        AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
+        return advisedSupport.getTargetSource().getTarget();
+    }
+
+}

+ 82 - 0
src/main/java/cn/iocoder/dashboard/util/sping/SpringExpressionUtils.java

@@ -0,0 +1,82 @@
+package cn.iocoder.dashboard.util.sping;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Spring EL 表达式的工具类
+ *
+ * @author mashu
+ */
+public class SpringExpressionUtils {
+
+    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
+    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
+
+    private SpringExpressionUtils() {
+    }
+
+    /**
+     * 从切面中,单个解析 EL 表达式的结果
+     *
+     * @param joinPoint  切面点
+     * @param expressionString EL 表达式数组
+     * @return 执行界面
+     */
+    public static Object parseExpression(ProceedingJoinPoint joinPoint, String expressionString) {
+        Map<String, Object> result = parseExpressions(joinPoint, Collections.singletonList(expressionString));
+        return result.get(expressionString);
+    }
+
+    /**
+     * 从切面中,批量解析 EL 表达式的结果
+     *
+     * @param joinPoint   切面点
+     * @param expressionStrings EL 表达式数组
+     * @return 结果,key 为表达式,value 为对应值
+     */
+    public static Map<String, Object> parseExpressions(ProceedingJoinPoint joinPoint, List<String> expressionStrings) {
+        // 如果为空,则不进行解析
+        if (CollUtil.isEmpty(expressionStrings)) {
+            return MapUtil.newHashMap();
+        }
+
+        // 第一步,构建解析的上下文 EvaluationContext
+        // 通过 joinPoint 获取被注解方法
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        Method method = methodSignature.getMethod();
+        // 使用 spring 的 ParameterNameDiscoverer 获取方法形参名数组
+        String[] paramNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
+        // Spring 的表达式上下文对象
+        EvaluationContext context = new StandardEvaluationContext();
+        // 给上下文赋值
+        if (ArrayUtil.isNotEmpty(paramNames)) {
+            Object[] args = joinPoint.getArgs();
+            for (int i = 0; i < paramNames.length; i++) {
+                context.setVariable(paramNames[i], args[i]);
+            }
+        }
+
+        // 第二步,逐个参数解析
+        Map<String, Object> result = MapUtil.newHashMap(expressionStrings.size(), true);
+        expressionStrings.forEach(key -> {
+            Object value = EXPRESSION_PARSER.parseExpression(key).getValue(context);
+            result.put(key, value);
+        });
+        return result;
+    }
+}

+ 1 - 1
src/main/resources/application-dev.yaml

@@ -145,7 +145,7 @@ spring:
 # 日志文件配置
 logging:
   file:
-    path: ${user.home}/logs/ # 日志文件的路径
+    name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
 
 --- #################### 芋道相关配置 ####################
 

+ 1 - 1
src/main/resources/application-local.yaml

@@ -145,7 +145,7 @@ spring:
 # 日志文件配置
 logging:
   file:
-    path: ${user.home}/logs/ # 日志文件的路径
+    name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
 
 --- #################### 芋道相关配置 ####################
 

+ 76 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,76 @@
+<configuration>
+    <!-- 引用 Spring Boot 的 logback 基础配置 -->
+    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
+    <!-- 变量 yudao.info.base-package,基础业务包 -->
+    <springProperty scope="context" name="yudao.info.base-package" source="yudao.info.base-package"/>
+    <!-- 格式化输出:%d 表示日期,%X{tid} SkWalking 链路追踪编号,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度,%msg:日志消息,%n是换行符 -->
+    <property name="PATTERN_DEFAULT" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%thread] [%tid] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
+
+    <!-- 控制台 Appender -->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
+        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
+            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
+                <pattern>${PATTERN_DEFAULT}</pattern>
+            </layout>
+        </encoder>
+    </appender>
+
+    <!-- 文件 Appender -->
+    <!-- 参考 Spring Boot 的 file-appender.xml 编写 -->
+    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
+            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
+                <pattern>${PATTERN_DEFAULT}</pattern>
+            </layout>
+        </encoder>
+        <!-- 日志文件名 -->
+        <file>${LOG_FILE}</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- 滚动后的日志文件名 -->
+            <fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
+            <!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
+            <cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
+            <!-- 日志文件,到达多少容量,进行滚动 -->
+            <maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
+            <!-- 日志文件的总大小,0 表示不限制 -->
+            <totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
+            <!-- 日志文件的保留天数 -->
+            <maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory>
+        </rollingPolicy>
+    </appender>
+    <!-- 异步写入日志,提升性能 -->
+    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志。默认的,如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能。默认值为 256 -->
+        <queueSize>256</queueSize>
+        <appender-ref ref="FILE"/>
+    </appender>
+
+    <!-- SkyWalking GRPC 日志收集,实现日志中心。注意:SkyWalking 8.4.0 版本开始支持 -->
+    <appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
+        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
+            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
+                <pattern>${PATTERN_DEFAULT}</pattern>
+            </layout>
+        </encoder>
+    </appender>
+
+    <!-- 本地环境 -->
+    <springProfile name="local">
+        <logger name="${yudao.info.base-package}" level="INFO" additivity="false">
+            <appender-ref ref="STDOUT"/>
+            <appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
+            <appender-ref ref="ASYNC"/>  <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
+        </logger>
+    </springProfile>
+    <!-- 其它环境 -->
+    <springProfile name="default">
+        <logger name="${yudao.info.base-package}" level="INFO" additivity="false">
+            <appender-ref ref="STDOUT"/>
+            <appender-ref ref="ASYNC"/>
+            <appender-ref ref="GRPC"/>
+        </logger>
+    </springProfile>
+
+</configuration>

+ 4 - 4
src/test/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuServiceTest.java

@@ -12,7 +12,7 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysMenuMapper;
 import cn.iocoder.dashboard.modules.system.enums.permission.MenuTypeEnum;
 import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysMenuProducer;
 import cn.iocoder.dashboard.modules.system.service.permission.impl.SysMenuServiceImpl;
-import cn.iocoder.dashboard.util.AopTargetUtils;
+import cn.iocoder.dashboard.util.sping.SpringAopUtils;
 import cn.iocoder.dashboard.util.RandomUtils;
 import cn.iocoder.dashboard.util.object.ObjectUtils;
 import com.google.common.collect.Multimap;
@@ -57,7 +57,7 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
         sysMenuService.initLocalCache();
 
         // 获取代理对象
-        SysMenuServiceImpl target = (SysMenuServiceImpl) AopTargetUtils.getTarget(sysMenuService);
+        SysMenuServiceImpl target = (SysMenuServiceImpl) SpringAopUtils.getTarget(sysMenuService);
 
         Map<Long, SysMenuDO> menuCache =
                 (Map<Long, SysMenuDO>) BeanUtil.getFieldValue(target, "menuCache");
@@ -227,7 +227,7 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
     public void testListMenusFromCache_success() throws Exception {
         Map<Long, SysMenuDO> mockCacheMap = new HashMap<>();
         //获取代理对象
-        SysMenuServiceImpl target = (SysMenuServiceImpl) AopTargetUtils.getTarget(sysMenuService);
+        SysMenuServiceImpl target = (SysMenuServiceImpl) SpringAopUtils.getTarget(sysMenuService);
         BeanUtil.setFieldValue(target, "menuCache", mockCacheMap);
 
         Map<Long, SysMenuDO> idMenuMap = new HashMap<>();
@@ -256,7 +256,7 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
     public void testListMenusFromCache2_success() throws Exception {
         Map<Long, SysMenuDO> mockCacheMap = new HashMap<>();
         //获取代理对象
-        SysMenuServiceImpl target = (SysMenuServiceImpl) AopTargetUtils.getTarget(sysMenuService);
+        SysMenuServiceImpl target = (SysMenuServiceImpl) SpringAopUtils.getTarget(sysMenuService);
         BeanUtil.setFieldValue(target, "menuCache", mockCacheMap);
 
         Map<Long, SysMenuDO> idMenuMap = new HashMap<>();

+ 2 - 8
src/test/java/cn/iocoder/dashboard/modules/system/service/permission/SysRoleServiceTest.java

@@ -13,14 +13,8 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysRoleMapper;
 import cn.iocoder.dashboard.modules.system.enums.permission.SysRoleTypeEnum;
 import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysRoleProducer;
 import cn.iocoder.dashboard.modules.system.service.permission.impl.SysRoleServiceImpl;
-import cn.iocoder.dashboard.util.AopTargetUtils;
-import cn.iocoder.dashboard.util.AssertUtils;
-import cn.iocoder.dashboard.util.RandomUtils;
-import cn.iocoder.dashboard.util.object.ObjectUtils;
-import com.google.common.collect.Sets;
-import org.junit.jupiter.api.Assertions;
+import cn.iocoder.dashboard.util.sping.SpringAopUtils;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
@@ -63,7 +57,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
 
         //断言
         //获取代理对象
-        SysRoleServiceImpl target = (SysRoleServiceImpl) AopTargetUtils.getTarget(sysRoleService);
+        SysRoleServiceImpl target = (SysRoleServiceImpl) SpringAopUtils.getTarget(sysRoleService);
 
         Map<Long, SysRoleDO> roleCache = (Map<Long, SysRoleDO>) BeanUtil.getFieldValue(target, "roleCache");
         assertPojoEquals(roleDO1, roleCache.get(roleDO1.getId()));

Some files were not shown because too many files changed in this diff