mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-22 07:11:52 +08:00
commit
135acaf7ba
@ -56,7 +56,8 @@
|
||||
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
|
||||
| | Redis 监控 |监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
|
||||
| 🚀 |Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
|
||||
| 🚀 | 链路追踪 | 基于 SkyWalking 实现性能监控,特别是链路的追踪 |
|
||||
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
|
||||
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
|
||||
| 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 |
|
||||
| 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 |
|
||||
| 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 |
|
||||
|
@ -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
|
||||
|
||||
# 备份
|
||||
|
13
pom.xml
13
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>
|
||||
|
@ -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
ruoyi-ui/src/views/infra/skywalking/log.vue
Normal file
26
ruoyi-ui/src/views/infra/skywalking/log.vue
Normal file
@ -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 one or more lines are too long
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 链路追踪
|
||||
* 使用 SkyWalking 组件,作为链路追踪、日志中心。
|
||||
*
|
||||
* 主要目的,是生成全局的链路追踪编号
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package cn.iocoder.dashboard.framework.tracer;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -145,7 +145,7 @@ spring:
|
||||
# 日志文件配置
|
||||
logging:
|
||||
file:
|
||||
path: ${user.home}/logs/ # 日志文件的路径
|
||||
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
|
@ -145,7 +145,7 @@ spring:
|
||||
# 日志文件配置
|
||||
logging:
|
||||
file:
|
||||
path: ${user.home}/logs/ # 日志文件的路径
|
||||
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
|
76
src/main/resources/logback-spring.xml
Normal file
76
src/main/resources/logback-spring.xml
Normal file
@ -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>
|
@ -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<>();
|
||||
|
@ -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()));
|
||||
|
Loading…
Reference in New Issue
Block a user