运维页面
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
package com.yunhai.bschclassmanage.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 重试反射重放工具:根据审计/同步日志中记录的 classMethod + 入参,
|
||||
* 通过 Spring Bean 反射重放原始调用。代理安全(支持 CGLIB / JDK 动态代理)。
|
||||
*/
|
||||
public final class ReflectRetryUtil {
|
||||
|
||||
private ReflectRetryUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 无法由日志重建、重放时须置 null 的参数类型(servlet / Spring MVC 相关)。
|
||||
* 这些参数原本由容器注入,日志记录时已过滤,重放时无法还原。
|
||||
*/
|
||||
private static final List<Class<?>> SKIP_TYPES = Arrays.asList(
|
||||
javax.servlet.http.HttpServletRequest.class,
|
||||
javax.servlet.http.HttpServletResponse.class,
|
||||
javax.servlet.http.HttpSession.class,
|
||||
org.springframework.ui.Model.class,
|
||||
org.springframework.ui.ModelMap.class,
|
||||
org.springframework.web.servlet.ModelAndView.class,
|
||||
org.springframework.validation.BindingResult.class,
|
||||
org.springframework.validation.Errors.class,
|
||||
org.springframework.web.bind.support.SessionStatus.class,
|
||||
Principal.class,
|
||||
Locale.class,
|
||||
InputStream.class,
|
||||
OutputStream.class
|
||||
);
|
||||
|
||||
/** 该参数类型是否应在重放时置 null(容器注入、无法由日志重建) */
|
||||
public static boolean isSkippable(Class<?> type) {
|
||||
if (type == null) return false;
|
||||
for (Class<?> s : SKIP_TYPES) {
|
||||
if (s.isAssignableFrom(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 去掉 CGLIB 代理类名后缀($$EnhancerBySpringCGLIB$$...),还原原始类名 */
|
||||
public static String stripProxySuffix(String className) {
|
||||
if (className == null) return null;
|
||||
int cglib = className.indexOf("$$");
|
||||
if (cglib > 0) return className.substring(0, cglib);
|
||||
return className; // JDK 代理($ProxyN) 原样返回,交由 bean 扫描兜底
|
||||
}
|
||||
|
||||
/** 定位结果:目标 Bean + 其上的原始 Method */
|
||||
public static final class Resolved {
|
||||
public final Object bean;
|
||||
public final Method method;
|
||||
|
||||
public Resolved(Object bean, Method method) {
|
||||
this.bean = bean;
|
||||
this.method = method;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 classMethod(类名.方法名) 与「可重建入参个数」定位 Spring Bean + Method。
|
||||
* 优先按原始类直接 getBean;失败(JDK 代理 / 类名丢失)则扫描所有 Bean,
|
||||
* 按「类名(去包)匹配 + 方法名 + 有效参数个数」唯一确定。
|
||||
*
|
||||
* @param effectiveArgCount 非 servlet/容器注入参数的个数(与日志中入参个数一致)
|
||||
*/
|
||||
public static Resolved resolveBeanAndMethod(ApplicationContext ctx, String classMethod, int effectiveArgCount) {
|
||||
if (classMethod == null || classMethod.lastIndexOf('.') < 0) {
|
||||
throw new IllegalArgumentException("无效的 classMethod:" + classMethod);
|
||||
}
|
||||
int dot = classMethod.lastIndexOf('.');
|
||||
String className = classMethod.substring(0, dot);
|
||||
String methodName = classMethod.substring(dot + 1);
|
||||
String stripped = stripProxySuffix(className);
|
||||
|
||||
// 1) 按原始类直接定位(CGLIB 场景最常见)
|
||||
try {
|
||||
Class<?> clazz = Class.forName(stripped);
|
||||
Object bean = ctx.getBean(clazz);
|
||||
Method m = findMethod(clazz, methodName, effectiveArgCount);
|
||||
if (m != null) return new Resolved(bean, m);
|
||||
} catch (Throwable ignored) {
|
||||
// 类名解析失败 / bean 不存在 → 走扫描兜底
|
||||
}
|
||||
|
||||
// 2) 扫描 Bean 兜底(JDK 动态代理 / 类名不完整)
|
||||
String simpleName = stripped.substring(stripped.lastIndexOf('.') + 1);
|
||||
Resolved best = null;
|
||||
int matchCount = 0;
|
||||
for (String name : ctx.getBeanDefinitionNames()) {
|
||||
Object bean;
|
||||
try {
|
||||
bean = ctx.getBean(name);
|
||||
} catch (Throwable t) {
|
||||
continue;
|
||||
}
|
||||
if (bean == null) continue;
|
||||
Class<?> userClass = ClassUtils.getUserClass(AopUtils.getTargetClass(bean));
|
||||
if (!userClass.getSimpleName().equals(simpleName)) continue;
|
||||
Method m = findMethod(userClass, methodName, effectiveArgCount);
|
||||
if (m != null) {
|
||||
matchCount++;
|
||||
best = new Resolved(bean, m);
|
||||
}
|
||||
}
|
||||
if (best != null && matchCount == 1) return best;
|
||||
if (best != null) {
|
||||
throw new com.yunhai.platform.utils.exception.DException("匹配到多个候选 Bean(" + methodName + "),无法唯一确定,请检查 classMethod");
|
||||
}
|
||||
throw new com.yunhai.platform.utils.exception.DException("未找到可重试的目标方法:" + classMethod + " (有效入参数=" + effectiveArgCount + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 clazz 上按「方法名 + 有效参数个数」定位唯一方法。
|
||||
* 有效参数个数 = 排除 servlet/容器注入参数后的个数(与日志入参个数一致)。
|
||||
*/
|
||||
public static Method findMethod(Class<?> clazz, String methodName, int effectiveArgCount) {
|
||||
for (Method m : clazz.getMethods()) {
|
||||
if (!m.getName().equals(methodName)) continue;
|
||||
int eff = 0;
|
||||
for (Parameter p : m.getParameters()) {
|
||||
if (!isSkippable(p.getType())) eff++;
|
||||
}
|
||||
if (eff == effectiveArgCount) return m;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得可用于反射调用的 Method:优先用代理 Bean 自身的方法对象,
|
||||
* 失败则回退到原始 Method(覆盖 JDK 代理泛型擦除等边界情况)。
|
||||
*/
|
||||
public static Method getInvokeMethod(Object bean, Method target) {
|
||||
try {
|
||||
return bean.getClass().getMethod(target.getName(), target.getParameterTypes());
|
||||
} catch (NoSuchMethodException e) {
|
||||
for (Method m : bean.getClass().getMethods()) {
|
||||
if (m.getName().equals(target.getName())
|
||||
&& m.getParameterCount() == target.getParameterCount()) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将日志中存储的入参值 raw 转换为目标参数类型 param。
|
||||
* 支持:基本类型/包装、String、枚举、List/Collection、POJO。
|
||||
*/
|
||||
public static Object convert(Object raw, Parameter param) {
|
||||
if (raw == null) return null;
|
||||
Class<?> type = param.getType();
|
||||
if (type.isAssignableFrom(raw.getClass())) return raw;
|
||||
if (type == String.class) return raw.toString();
|
||||
if (type == Integer.class || type == int.class) {
|
||||
return raw instanceof Number ? ((Number) raw).intValue() : Integer.valueOf(raw.toString().trim());
|
||||
}
|
||||
if (type == Long.class || type == long.class) {
|
||||
return raw instanceof Number ? ((Number) raw).longValue() : Long.valueOf(raw.toString().trim());
|
||||
}
|
||||
if (type == Double.class || type == double.class) {
|
||||
return raw instanceof Number ? ((Number) raw).doubleValue() : Double.valueOf(raw.toString().trim());
|
||||
}
|
||||
if (type == Float.class || type == float.class) {
|
||||
return raw instanceof Number ? ((Number) raw).floatValue() : Float.valueOf(raw.toString().trim());
|
||||
}
|
||||
if (type == Boolean.class || type == boolean.class) {
|
||||
return Boolean.parseBoolean(raw.toString().trim());
|
||||
}
|
||||
if (type.isEnum()) {
|
||||
return Enum.valueOf((Class<Enum>) type, raw.toString().trim());
|
||||
}
|
||||
if (List.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type)) {
|
||||
Type elemType = Object.class;
|
||||
if (param.getParameterizedType() instanceof ParameterizedType) {
|
||||
Type[] actual = ((ParameterizedType) param.getParameterizedType()).getActualTypeArguments();
|
||||
if (actual.length > 0) elemType = actual[0];
|
||||
}
|
||||
if (elemType == Integer.class) return JSON.parseArray(raw.toString(), Integer.class);
|
||||
if (elemType instanceof Class) return JSON.parseArray(raw.toString(), (Class<?>) elemType);
|
||||
return JSON.parseArray(raw.toString());
|
||||
}
|
||||
// 其余按 POJO 反序列化
|
||||
return JSON.parseObject(raw.toString(), type);
|
||||
}
|
||||
|
||||
/** 统一把反射调用异常转译为运行时异常(DException 透传) */
|
||||
public static RuntimeException rethrow(Throwable ex) {
|
||||
if (ex instanceof InvocationTargetException) {
|
||||
Throwable cause = ((InvocationTargetException) ex).getCause();
|
||||
if (cause instanceof com.yunhai.platform.utils.exception.DException)
|
||||
throw (com.yunhai.platform.utils.exception.DException) cause;
|
||||
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
|
||||
throw new com.yunhai.platform.utils.exception.DException(
|
||||
cause != null ? cause.getMessage() : "重试执行失败");
|
||||
}
|
||||
if (ex instanceof com.yunhai.platform.utils.exception.DException)
|
||||
throw (com.yunhai.platform.utils.exception.DException) ex;
|
||||
if (ex instanceof RuntimeException) throw (RuntimeException) ex;
|
||||
throw new com.yunhai.platform.utils.exception.DException("重试失败:" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user