Commit
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package link.at17.mid.tushare.component;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import link.at17.mid.tushare.system.config.SystemConfig;
|
||||
import link.at17.mid.tushare.system.util.SpringContextHolder;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* OkHttpClient 提供器
|
||||
* @see link.at17.mid.tushare.system.config.SystemConfig
|
||||
* @see okhttp3.internal.http.BridgeInterceptor
|
||||
*/
|
||||
public class OkHttpClientProvider {
|
||||
|
||||
private static volatile SystemConfig systemConfig;
|
||||
|
||||
private static SystemConfig getSystemConfig() {
|
||||
if (systemConfig == null) {
|
||||
synchronized (OkHttpClientProvider.class) {
|
||||
if (systemConfig == null) {
|
||||
systemConfig = SpringContextHolder.getBean(SystemConfig.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return systemConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 SystemConfig 获取一个 OkHttpClient 实例
|
||||
* @return
|
||||
*/
|
||||
public static OkHttpClient getInstance() {
|
||||
return getInstance(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 SystemConfig 获取一个 OkHttpClient 实例
|
||||
* @param builderConsumer 可根据该 consumer 自定义 builder 其他参数,注意 proxy、https 校验等最终仍会根据 systemConfig 情况覆盖
|
||||
* @return
|
||||
*/
|
||||
public static OkHttpClient getInstance(Consumer<OkHttpClient.Builder> builderConsumer) {
|
||||
SystemConfig systemConfig = getSystemConfig();
|
||||
return getInstance(
|
||||
systemConfig.getProxy(),
|
||||
systemConfig.getIgnoreHttpsVerification(),
|
||||
builderConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定代理和是否忽略 https 证书获取一个 OkHttpClient 实例
|
||||
* @param proxy 指定代理
|
||||
* @param ignoreHttpsVerification 是否忽略 https 证书
|
||||
* @return
|
||||
*/
|
||||
public static OkHttpClient getInstance(Proxy proxy, boolean ignoreHttpsVerification) {
|
||||
return getInstance(proxy, ignoreHttpsVerification, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定代理、是否忽略 https 证书和额外 builder 设置获取一个 OkHttpClient 实例
|
||||
* @param proxy 指定代理
|
||||
* @param ignoreHttpsVerification 是否忽略 https 证书
|
||||
* @param builderConsumer 可根据该 consumer 自定义 builder 其他参数,注意 proxy、https 校验等最终仍会根据其他参数覆盖
|
||||
* @return
|
||||
*/
|
||||
public static OkHttpClient getInstance(
|
||||
Proxy proxy,
|
||||
boolean ignoreHttpsVerification,
|
||||
Consumer<OkHttpClient.Builder> builderConsumer) {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
if (builderConsumer != null) {
|
||||
builderConsumer.accept(builder);
|
||||
}
|
||||
builder.proxy(proxy);
|
||||
if (ignoreHttpsVerification) {
|
||||
builder
|
||||
.sslSocketFactory(getSSLSocketFactory(), getX509TrustManager())
|
||||
.hostnameVerifier(getHostnameVerifier());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static HostnameVerifier getHostnameVerifier() {
|
||||
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return hostnameVerifier;
|
||||
}
|
||||
|
||||
private static SSLSocketFactory getSSLSocketFactory() {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, getTrustManager(), new SecureRandom());
|
||||
return sslContext.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static X509TrustManager getX509TrustManager() {
|
||||
X509TrustManager trustManager = null;
|
||||
try {
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
}
|
||||
trustManager = (X509TrustManager) trustManagers[0];
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return trustManager;
|
||||
}
|
||||
|
||||
private static TrustManager[] getTrustManager() {
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
};
|
||||
return trustAllCerts;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package link.at17.mid.tushare.component;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reflections.Reflections;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -13,15 +12,6 @@ import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import link.at17.mid.tushare.annotation.StaticAttribute;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
||||
/**
|
||||
@@ -34,17 +24,6 @@ public class PlatformInterceptor implements HandlerInterceptor {
|
||||
@Autowired
|
||||
Reflections reflections;
|
||||
|
||||
/**
|
||||
* 静态注入的类缓存
|
||||
*/
|
||||
Map<String, Class<?>> staticAttributeClassCache;
|
||||
|
||||
/**
|
||||
* 静态注入的枚举字段缓存
|
||||
*/
|
||||
Map<String, Map<String, Enum<?>>> options = null;
|
||||
Map<String, String> optionNameMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
|
||||
@@ -75,85 +54,6 @@ public class PlatformInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
|
||||
request.setAttribute("request", request); // 把 request 本身放到 attribute 里去,供前端部分位置用
|
||||
|
||||
// 查找添加了 @StaticAttribute 注解的类,将其中的枚举注入前端供使用
|
||||
if (staticAttributeClassCache == null) {
|
||||
log.debug("init static attribute classes.");
|
||||
staticAttributeClassCache = new HashMap<>();
|
||||
|
||||
|
||||
Set<Class<?>> staticClasses = reflections.getTypesAnnotatedWith(StaticAttribute.class);
|
||||
for (Class<?> staticClass : staticClasses) {
|
||||
StaticAttribute sa = staticClass.getAnnotation(StaticAttribute.class);
|
||||
String name = staticClass.getSimpleName();
|
||||
if (StringUtils.isNotBlank(sa.value())) {
|
||||
name = sa.value();
|
||||
}
|
||||
|
||||
if (staticAttributeClassCache.containsKey(name)) {
|
||||
// 缓存中已经存在了这个名字,直接忽略
|
||||
log.warn("StaticAttribute annotation name {} for class {} has been taken, ignore.",
|
||||
name, staticClass.getName());
|
||||
}
|
||||
staticAttributeClassCache.put(name, staticClass);
|
||||
log.debug("{} injected as name {}", staticClass, name);
|
||||
}
|
||||
}
|
||||
else if (staticAttributeClassCache.isEmpty()) {
|
||||
// 跑完一次后,缓存仍然为空,可能不正常,提示一下
|
||||
log.warn("StaticAttributes' staticAttributeClassCache is empty, it's unusual, if you didn't exclude it manually, please check.");
|
||||
}
|
||||
Iterator<Map.Entry<String, Class<?>>> iterator = staticAttributeClassCache.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Class<?>> entry = iterator.next();
|
||||
request.setAttribute(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (options == null) {
|
||||
options = new HashMap<>();
|
||||
Set<Field> enumOptionFields = reflections.getFieldsAnnotatedWith(StaticAttribute.class);
|
||||
for (Field f : enumOptionFields) {
|
||||
if (!f.isAnnotationPresent(StaticAttribute.class)) continue;
|
||||
if (!Enum.class.isAssignableFrom(f.getType())) continue;
|
||||
|
||||
// 拿到注解和名前缀
|
||||
StaticAttribute anno = f.getAnnotation(StaticAttribute.class);
|
||||
String prefix = anno.value().isBlank() ?
|
||||
f.getName() + "Enum" :
|
||||
anno.value();
|
||||
|
||||
prefix = prefix.toUpperCase().charAt(0) + prefix.substring(1);
|
||||
|
||||
if (optionNameMap.containsKey(prefix)) {
|
||||
log.warn("EnumOption name {}:{} has already been taken by {}, please check",
|
||||
prefix, f.getType().getName(), optionNameMap.get(prefix));
|
||||
continue;
|
||||
}
|
||||
|
||||
optionNameMap.put(prefix, f.getType().getName());
|
||||
|
||||
// enum 值列表
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) f.getType();
|
||||
Enum<?>[] constants = enumType.getEnumConstants();
|
||||
|
||||
// 构造 Map<name, constant>
|
||||
Map<String, Enum<?>> optionsMap = new LinkedHashMap<>();
|
||||
for (Enum<?> c : constants) {
|
||||
optionsMap.put(c.name(), c);
|
||||
}
|
||||
|
||||
// 放到 Model 里
|
||||
options.put(prefix, optionsMap);
|
||||
}
|
||||
|
||||
}
|
||||
for (Entry<String, Map<String, Enum<?>>> entry : options.entrySet()) {
|
||||
request.setAttribute(entry.getKey(), entry.getValue());
|
||||
log.debug("Inject enums {}: {} to request {}",
|
||||
entry.getKey(), optionNameMap.get(entry.getKey()),
|
||||
request.getRequestURI());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package link.at17.mid.tushare.component;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reflections.Reflections;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import link.at17.mid.tushare.annotation.StaticAttribute;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
||||
/**
|
||||
* 注入拦截器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class StaticAttributeInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Autowired
|
||||
Reflections reflections;
|
||||
|
||||
/**
|
||||
* 静态注入的类缓存
|
||||
*/
|
||||
Map<String, Class<?>> staticAttributeClassCache;
|
||||
|
||||
/**
|
||||
* 静态注入的枚举字段缓存
|
||||
*/
|
||||
Map<String, Map<String, Enum<?>>> options = null;
|
||||
Map<String, String> optionNameMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
injectNecessary(request, handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为前端模板注入变量
|
||||
* @param request
|
||||
*/
|
||||
public void injectNecessary(HttpServletRequest request, Object handler) {
|
||||
|
||||
// 只在 Controller(HandlerMethod)里才做注入
|
||||
if (!(handler instanceof HandlerMethod)) {
|
||||
// 静态资源、图片、css、js 都会被 ResourceHttpRequestHandler 处理,
|
||||
// 这里一律跳过
|
||||
return;
|
||||
}
|
||||
|
||||
// 排除 @ResponseBody/json 接口
|
||||
HandlerMethod hm = (HandlerMethod) handler;
|
||||
if (hm.hasMethodAnnotation(ResponseBody.class)
|
||||
|| hm.getBeanType().isAnnotationPresent(RestController.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找添加了 @StaticAttribute 注解的类,将其中的枚举注入前端供使用
|
||||
if (staticAttributeClassCache == null) {
|
||||
log.debug("init static attribute classes.");
|
||||
staticAttributeClassCache = new HashMap<>();
|
||||
|
||||
|
||||
Set<Class<?>> staticClasses = reflections.getTypesAnnotatedWith(StaticAttribute.class);
|
||||
for (Class<?> staticClass : staticClasses) {
|
||||
StaticAttribute sa = staticClass.getAnnotation(StaticAttribute.class);
|
||||
String name = staticClass.getSimpleName();
|
||||
if (StringUtils.isNotBlank(sa.value())) {
|
||||
name = sa.value();
|
||||
}
|
||||
|
||||
if (staticAttributeClassCache.containsKey(name)) {
|
||||
// 缓存中已经存在了这个名字,直接忽略
|
||||
log.warn("StaticAttribute 注解的类 {} 指定的名称 {} 已经存在, 旧值会被覆盖",
|
||||
staticClass.getName(),
|
||||
name);
|
||||
}
|
||||
staticAttributeClassCache.put(name, staticClass);
|
||||
log.debug("{} 类以 {} 为名称注入", staticClass, name);
|
||||
}
|
||||
}
|
||||
else if (staticAttributeClassCache.isEmpty()) {
|
||||
// 跑完一次后,缓存仍然为空,可能不正常,提示一下
|
||||
log.warn("初始化后,StaticAttribute 缓存为空,这仅在无任何类被 @StaticAttribute 注解的情况下出现。如果这与您的期望不符,请检查");
|
||||
}
|
||||
Iterator<Map.Entry<String, Class<?>>> iterator = staticAttributeClassCache.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Class<?>> entry = iterator.next();
|
||||
request.setAttribute(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (options == null) {
|
||||
options = new HashMap<>();
|
||||
Set<Field> enumOptionFields = reflections.getFieldsAnnotatedWith(StaticAttribute.class);
|
||||
for (Field f : enumOptionFields) {
|
||||
if (!f.isAnnotationPresent(StaticAttribute.class)) continue;
|
||||
if (!Enum.class.isAssignableFrom(f.getType())) continue;
|
||||
|
||||
// 拿到注解和名前缀
|
||||
StaticAttribute anno = f.getAnnotation(StaticAttribute.class);
|
||||
String prefix = anno.value().isBlank() ?
|
||||
f.getName() + "Enum" :
|
||||
anno.value();
|
||||
|
||||
prefix = prefix.toUpperCase().charAt(0) + prefix.substring(1);
|
||||
|
||||
if (optionNameMap.containsKey(prefix)) {
|
||||
log.warn("EnumOption name {}:{} has already been taken by {}, please check",
|
||||
prefix, f.getType().getName(), optionNameMap.get(prefix));
|
||||
continue;
|
||||
}
|
||||
|
||||
optionNameMap.put(prefix, f.getType().getName());
|
||||
|
||||
// enum 值列表
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) f.getType();
|
||||
Enum<?>[] constants = enumType.getEnumConstants();
|
||||
|
||||
// 构造 Map<name, constant>
|
||||
Map<String, Enum<?>> optionsMap = new LinkedHashMap<>();
|
||||
for (Enum<?> c : constants) {
|
||||
optionsMap.put(c.name(), c);
|
||||
}
|
||||
|
||||
// 放到 Model 里
|
||||
options.put(prefix, optionsMap);
|
||||
}
|
||||
|
||||
}
|
||||
for (Entry<String, Map<String, Enum<?>>> entry : options.entrySet()) {
|
||||
request.setAttribute(entry.getKey(), entry.getValue());
|
||||
log.debug("Inject enums {}: {} to request {}",
|
||||
entry.getKey(), optionNameMap.get(entry.getKey()),
|
||||
request.getRequestURI());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package link.at17.mid.tushare.component;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.reflections.Reflections;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import link.at17.mid.tushare.annotation.StaticAttribute;
|
||||
import link.at17.mid.tushare.data.models.UpdateMethodInfo;
|
||||
import link.at17.mid.tushare.service.UpdateMethodService;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* 更新注入拦截器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class UpdateMethodInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Autowired
|
||||
Reflections reflections;
|
||||
|
||||
Map<String, Class<?>> optionArgCache;
|
||||
|
||||
@Autowired
|
||||
UpdateMethodService updateDataService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
injectNecessary(request, handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为前端模板注入变量
|
||||
* @param request
|
||||
*/
|
||||
public void injectNecessary(HttpServletRequest request, Object handler) {
|
||||
|
||||
// 只在 Controller(HandlerMethod)里才做注入
|
||||
if (!(handler instanceof HandlerMethod)) {
|
||||
// 静态资源、图片、css、js 都会被 ResourceHttpRequestHandler 处理,
|
||||
// 这里一律跳过
|
||||
return;
|
||||
}
|
||||
|
||||
// 排除 @ResponseBody/json 接口
|
||||
HandlerMethod hm = (HandlerMethod) handler;
|
||||
if (hm.hasMethodAnnotation(ResponseBody.class)
|
||||
|| hm.getBeanType().isAnnotationPresent(RestController.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (optionArgCache == null) {
|
||||
|
||||
optionArgCache = new HashMap<>();
|
||||
|
||||
List<UpdateMethodInfo> potentialUpdateMethods = updateDataService.getPotentialUpdateMethodInfos();
|
||||
for (UpdateMethodInfo info : potentialUpdateMethods) {
|
||||
for (UpdateMethodInfo.UpdateParamInfo paramInfo : info.getParams()) {
|
||||
Class<?> typeClass = paramInfo.getTypeClass();
|
||||
if (typeClass == null) continue;
|
||||
if (typeClass.isEnum()) {
|
||||
StaticAttribute sa = typeClass.getAnnotation(StaticAttribute.class);
|
||||
String simpleNmae = typeClass.getSimpleName();
|
||||
if (sa != null) {
|
||||
log.info("UpdateMethod {} 的参数 {}, 类型 {} 已被 @StaticAttribute 注解,不再重复注入",
|
||||
info.getMethodName(), paramInfo.getName(), simpleNmae);
|
||||
continue;
|
||||
}
|
||||
if (optionArgCache.containsKey(simpleNmae)) {
|
||||
log.warn("UpdateMethod {} 参数 {}, 类型 {} 指定的名称 {} 已经存在, 旧值会被覆盖",
|
||||
typeClass,
|
||||
simpleNmae);
|
||||
}
|
||||
optionArgCache.put(simpleNmae, typeClass);
|
||||
log.debug("{} 类以 {} 为名称注入", typeClass, simpleNmae);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (optionArgCache.isEmpty()) {
|
||||
// 跑完一次后,缓存仍然为空,可能不正常,提示一下
|
||||
log.warn("初始化后,Option(s)Arg(s)' 缓存为空,这仅在无任何方法被 UpdateMethod 注解,或 UpdateMethod 不存在枚举类参数的情况下出现。如果这与您的期望不符,请检查");
|
||||
}
|
||||
Iterator<Map.Entry<String, Class<?>>> iterator = optionArgCache.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Class<?>> entry = iterator.next();
|
||||
request.setAttribute(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package link.at17.mid.tushare.component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
|
||||
import link.at17.mid.tushare.data.models.UpdatePlan;
|
||||
import link.at17.mid.tushare.service.UpdatePlanService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 更新计划同步切片器<p>
|
||||
* 主要用于拦截通过 updatePlanService 下的所有 save/update/remove 方法
|
||||
* <p>注意:
|
||||
* <ol>
|
||||
* <li>仅限第一个参数是 {@code UpdatePlan}, {@code Collection<UpdatePlan>} 或 {@code Wrapper<UpdatePlan>} 的方法
|
||||
* <li>由于切片是对 UpdatePlanService 切片, 但切片器内部又调用了 UpdatePlanService,
|
||||
* 如果调用的方法恰好是被切片的方法, 则会进入调用递归导致栈溢出, 需要额外注意.
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
public class UpdatePlanSyncAspect {
|
||||
|
||||
private final UpdatePlanService updatePlanService;
|
||||
|
||||
// 拦截所有 UpdatePlanService 的 save/update/remove 方法
|
||||
@Pointcut("(" +
|
||||
"execution(* com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.save*(..)) || " +
|
||||
"execution(* com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.update*(..)) || " +
|
||||
"execution(* com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.remove*(..)) || " +
|
||||
"execution(* com.baomidou.mybatisplus.extension.service.IService.save*(..)) || " +
|
||||
"execution(* com.baomidou.mybatisplus.extension.service.IService.update*(..)) || " +
|
||||
"execution(* com.baomidou.mybatisplus.extension.service.IService.remove*(..))" +
|
||||
") && " +
|
||||
"target(link.at17.mid.tushare.service.UpdatePlanService)")
|
||||
public void taskOps() {}
|
||||
|
||||
@AfterReturning(pointcut = "taskOps()", returning = "result")
|
||||
public void afterTaskOp(JoinPoint jp, Object result) {
|
||||
|
||||
if (!(result instanceof Boolean bool) || !bool) {
|
||||
// 未成功执行,不进行切片
|
||||
return;
|
||||
}
|
||||
|
||||
Object arg = jp.getArgs()[0];
|
||||
if (arg instanceof UpdatePlan task) {
|
||||
handle(task, jp.getSignature().getName());
|
||||
}
|
||||
else if (arg instanceof Collection<?> coll) {
|
||||
coll.stream()
|
||||
.filter(UpdatePlan.class::isInstance)
|
||||
.map(UpdatePlan.class::cast)
|
||||
.forEach(t -> handle(t, jp.getSignature().getName()));
|
||||
}
|
||||
else if (arg instanceof Wrapper<?> wrapper) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<UpdatePlan> plans = updatePlanService.list((Wrapper<UpdatePlan>)wrapper);
|
||||
plans.stream()
|
||||
.filter(UpdatePlan.class::isInstance)
|
||||
.map(UpdatePlan.class::cast)
|
||||
.forEach(t -> handle(t, jp.getSignature().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(UpdatePlan task, String methodName) {
|
||||
|
||||
// 但凡存在的都应该 reschedule
|
||||
|
||||
try {
|
||||
if (methodName.startsWith("remove")) {
|
||||
updatePlanService.deleteTask(task.getId());
|
||||
log.info("已同步删除 Quartz 任务: [{}]{}", task.getId(), task.getName());
|
||||
}
|
||||
else {
|
||||
updatePlanService.rescheduleTask(task);
|
||||
log.info("已同步更新 Quartz 任务: [{}]{}", task.getId(), task.getName());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("同步 Quartz 调度失败: [{}]{}", task.getId(), task.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user