新增 RequireAuthAndProxyAspect 切片, 用于确保一些需要鉴权的方法有合法鉴权
This commit is contained in:
@@ -11,7 +11,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
@EnableScheduling
|
||||
@EnableFeignClients
|
||||
@SpringBootApplication
|
||||
@EnableCaching(proxyTargetClass=true)
|
||||
public class EmoneyAutoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package quant.rich.emoney.component;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.*;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -20,9 +20,19 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 注解在受 Spring 管理的类的成员方法上,使其在执行前校验代理状态、鉴权状态
|
||||
* <p>需要开启 AOP:在任意配置类上增加注解:<i><code>@EnableAspectJAutoProxy</code></i>
|
||||
*/
|
||||
@Component
|
||||
@Aspect
|
||||
public class RequireAuthAndProxyAspect {
|
||||
|
||||
/**
|
||||
* 调用链深度,用以判断同线程同类型切点命中的次数
|
||||
*/
|
||||
private static final ThreadLocal<Integer> DEPTH =
|
||||
ThreadLocal.withInitial(() -> 0);
|
||||
|
||||
@Autowired
|
||||
RequestInfoService requestInfoService;
|
||||
@@ -30,9 +40,30 @@ public class RequireAuthAndProxyAspect {
|
||||
@Autowired
|
||||
ProxySettingService proxySettingService;
|
||||
|
||||
@Around("@annotation(quant.rich.emoney.component.RequireAuthAndProxyAspect.RequireAuthAndProxy)")
|
||||
public Object around(ProceedingJoinPoint pjp) throws Throwable {
|
||||
|
||||
@Pointcut("@annotation(quant.rich.emoney.component.RequireAuthAndProxyAspect.RequireAuthAndProxy)")
|
||||
public void pointCut() {}
|
||||
|
||||
@Before("pointCut()")
|
||||
public void before(JoinPoint jp) throws Throwable {
|
||||
int depth = DEPTH.get();
|
||||
if (depth++ == 0) {
|
||||
beforeRoot(jp);
|
||||
}
|
||||
DEPTH.set(depth);
|
||||
}
|
||||
|
||||
@After("pointCut()")
|
||||
public void after() {
|
||||
int depth = DEPTH.get() - 1;
|
||||
if (depth == 0) {
|
||||
DEPTH.remove();
|
||||
} else {
|
||||
DEPTH.set(depth);
|
||||
}
|
||||
}
|
||||
|
||||
public void beforeRoot(JoinPoint jp) throws Throwable {
|
||||
|
||||
ProxySetting defualtProxySetting = proxySettingService.getDefaultProxySetting();
|
||||
|
||||
if (defualtProxySetting == null) {
|
||||
@@ -45,7 +76,7 @@ public class RequireAuthAndProxyAspect {
|
||||
throw new RuntimeException("需要配置默认请求信息");
|
||||
}
|
||||
|
||||
MethodSignature signature = (MethodSignature) pjp.getSignature();
|
||||
MethodSignature signature = (MethodSignature) jp.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
RequireAuthAndProxy annotation = method.getAnnotation(RequireAuthAndProxy.class);
|
||||
|
||||
@@ -60,13 +91,21 @@ public class RequireAuthAndProxyAspect {
|
||||
else if (!EmoneyClient.reloginCheck()) {
|
||||
throw new RuntimeException("检查重鉴权失败");
|
||||
}
|
||||
|
||||
return pjp.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在方法上添加此注解,则进入该方法前先校验 defaultRequestInfo 已鉴权、代理已配置,否则不允许进入方法
|
||||
* <p>需要开启 AOP:在任意配置类上增加注解:<i><code>@EnableAspectJAutoProxy</code></i>
|
||||
* <p>
|
||||
* <b><font color="red">由于 AOP 特性使然, 必须为 public 方法才能生效</font></b>
|
||||
* <p>
|
||||
* 为防止反复重登录验证, 在同一调用链中的校验, 仅在首次调用时进行检查, 如:
|
||||
* A、B 和 C 方法都添加了本注解, A 中调用 B, B 中调用 C, 则仅在 A 方法执行时才进行检查。
|
||||
* <p>
|
||||
* 为了安全起见, 无论 autoLogin 是否为 true, 只要添加了本注解, 在执行方法前, 最终都会进行
|
||||
* EmoneyClient.reloginCheck(), 如果检查重鉴权失败, 被注解的方法将不会执行,
|
||||
* 并由切片方法抛出异常。如果某个方法并不需要严格控制鉴权的, 可以不用本注解,
|
||||
* 而是由在方法内自行编写逻辑来替代。
|
||||
*
|
||||
* @see RequireAuthAndProxyAspect
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@@ -74,7 +113,8 @@ public class RequireAuthAndProxyAspect {
|
||||
@Documented
|
||||
public static @interface RequireAuthAndProxy {
|
||||
/**
|
||||
* 当存在默认请求配置但未鉴权时,是否自动鉴权
|
||||
* 当存在默认请求配置但未鉴权时,是否自动鉴权。由于某些场景下,
|
||||
* 鉴权信息需要系统统一维护,故此区分
|
||||
* @return
|
||||
*/
|
||||
boolean autoLogin() default false;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package quant.rich.emoney.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@@ -17,8 +19,10 @@ import quant.rich.emoney.service.ConfigService;
|
||||
* @author Doghole
|
||||
*
|
||||
*/
|
||||
@EnableAspectJAutoProxy
|
||||
@Configuration
|
||||
@EnableAsync(proxyTargetClass=true)
|
||||
@EnableCaching(proxyTargetClass=true)
|
||||
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
|
||||
@Import(ConfigAutoRegistrar.class)
|
||||
public class EmoneyAutoConfig implements WebMvcConfigurer {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package quant.rich.emoney.pojo;
|
||||
package quant.rich.emoney.pojo.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -8,10 +8,14 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Validated
|
||||
@Data
|
||||
@Accessors(chain=true)
|
||||
public class MultiIndexPlanDetail {
|
||||
@@ -19,11 +23,14 @@ public class MultiIndexPlanDetail {
|
||||
/**
|
||||
* 指标
|
||||
*/
|
||||
@Valid
|
||||
@NotEmpty
|
||||
List<MultiIndexPlanPart> indexes;
|
||||
|
||||
/**
|
||||
* 抓取的 K 线粒度
|
||||
*/
|
||||
@NotEmpty
|
||||
List<Integer> periods;
|
||||
|
||||
/**
|
||||
@@ -55,6 +62,7 @@ public class MultiIndexPlanDetail {
|
||||
return periods;
|
||||
}
|
||||
|
||||
@Validated
|
||||
@Data
|
||||
@Accessors(chain=true)
|
||||
public static class MultiIndexPlanPart {
|
||||
@@ -1,4 +1,4 @@
|
||||
package quant.rich.emoney.pojo;
|
||||
package quant.rich.emoney.pojo.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -23,6 +23,7 @@ import org.jsoup.nodes.Document;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
@@ -71,6 +72,10 @@ public class IndexDetailService {
|
||||
@Autowired
|
||||
RequestInfoService requestInfoService;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
IndexDetailService self;
|
||||
|
||||
static final String filePath = "./conf/extra/indexDetail/";
|
||||
static final ObjectMapper mapper = new ObjectMapper();
|
||||
static final Context jsContext = Context.create("js");
|
||||
@@ -86,25 +91,24 @@ public class IndexDetailService {
|
||||
* @return
|
||||
*/
|
||||
@CacheEvict(cacheNames="@indexDetailService.getIndexDetail(Serializable)", key="#indexCode.toString()")
|
||||
@RequireAuthAndProxy(autoLogin = true)
|
||||
public IndexDetail forceRefreshAndGetIndexDetail(Serializable indexCode) {
|
||||
|
||||
// 刷新的本质就是从网络获取,因为此处已经清理了缓存,所以直接从网络获取后再
|
||||
// 走一次 getIndexDetail,获取到的就是从网络保存到了本地的,此时缓存也更新了
|
||||
|
||||
if (!hasParams(indexCode)) {
|
||||
getNonParamsIndexDetailOnline(indexCode);
|
||||
self.getNonParamsIndexDetailOnline(indexCode);
|
||||
}
|
||||
else {
|
||||
getParamsIndexDetailOnline(indexCode);
|
||||
self.getParamsIndexDetailOnline(indexCode);
|
||||
}
|
||||
|
||||
return getIndexDetail(indexCode);
|
||||
// 此处用 self 是为了在本次调用时就设置缓存
|
||||
return self.getIndexDetail(indexCode);
|
||||
}
|
||||
|
||||
|
||||
@Cacheable(cacheNames="@indexDetailService.getIndexDetail(Serializable)", key="#indexCode.toString()")
|
||||
@RequireAuthAndProxy(autoLogin = true)
|
||||
public IndexDetail getIndexDetail(Serializable indexCode) {
|
||||
|
||||
if (indexCode == null) {
|
||||
@@ -136,7 +140,7 @@ public class IndexDetailService {
|
||||
}
|
||||
}
|
||||
// 从网络获取
|
||||
return getParamsIndexDetailOnline(indexCode);
|
||||
return self.getParamsIndexDetailOnline(indexCode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +150,8 @@ public class IndexDetailService {
|
||||
* @see RequestInfoService#getDefaultRequestInfo()
|
||||
* @return
|
||||
*/
|
||||
private ParamsIndexDetail getParamsIndexDetailOnline(Serializable indexCode) {
|
||||
@RequireAuthAndProxy(autoLogin = true)
|
||||
public ParamsIndexDetail getParamsIndexDetailOnline(Serializable indexCode) {
|
||||
RequestInfo requestInfo = requestInfoService.getDefaultRequestInfo();
|
||||
if (requestInfo == null || StringUtils.isBlank(requestInfo.getAuthorization())) {
|
||||
throw new RuntimeException("无法获取已鉴权的 RequestInfo");
|
||||
@@ -221,26 +226,29 @@ public class IndexDetailService {
|
||||
log.warn("无法获取本地无参数指标说明,将尝试重新从网络获取 indexCode: {}", indexCode, e);
|
||||
}
|
||||
if (detail != null) {
|
||||
loadImages(detail);
|
||||
self.loadImages(detail);
|
||||
saveIndexDetail(detail);
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
// 从网络获取
|
||||
return getNonParamsIndexDetailOnline(indexCode);
|
||||
return self.getNonParamsIndexDetailOnline(indexCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从网络获取指定 indexCode 的无参指标详情
|
||||
* <p>本例用到的 requestInfo 不需要 PatchOkHttp 覆写,但要求鉴权参数拼接到 url 中,故要求鉴权
|
||||
* <p>会一并尝试获取其他在本地未有的无参指标</p>
|
||||
* <p>由于本例需要鉴权, 故在方法上开启了 {@code @RequireAuthAndProxy}, 而本方法被类内其他方法调用, 需要 proxy 才能使 AOP 生效,
|
||||
* 为了使 self 调用成功, 需要在任意配置类上添加 {@code @EnableAspectJAutoProxy(exposeProxy = true)}
|
||||
* @param indexCode
|
||||
* @see RequestInfo#getWebviewUserAgent()
|
||||
* @see RequestInfoService#getDefaultRequestInfo()
|
||||
* @return
|
||||
*/
|
||||
private NonParamsIndexDetail getNonParamsIndexDetailOnline(Serializable indexCode) {
|
||||
String url = buildNonParamsIndexUrl(indexCode);
|
||||
@RequireAuthAndProxy(autoLogin = true)
|
||||
public NonParamsIndexDetail getNonParamsIndexDetailOnline(Serializable indexCode) {
|
||||
String url = self.buildNonParamsIndexUrl(indexCode);
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("Host", "appstatic.emoney.cn")
|
||||
@@ -370,7 +378,7 @@ public class IndexDetailService {
|
||||
String path = getIndexDetailPath(detail);
|
||||
// 判断是否是需求的 detail
|
||||
if (indexCode.toString().equals(detail.getIndexCode())) {
|
||||
loadImages(detail);
|
||||
self.loadImages(detail);
|
||||
targetDetail = detail;
|
||||
}
|
||||
// 清洗内容:凡是文本类型的内容的,都要清洗一遍,判断是否有脚本、
|
||||
@@ -434,7 +442,8 @@ public class IndexDetailService {
|
||||
* @return
|
||||
* @see RequestInfo
|
||||
*/
|
||||
private NonParamsIndexDetail loadImages(NonParamsIndexDetail detail) {
|
||||
@RequireAuthAndProxy(autoLogin = true)
|
||||
public NonParamsIndexDetail loadImages(NonParamsIndexDetail detail) {
|
||||
OkHttpClient client = OkHttpClientProvider.getInstance();
|
||||
for (NonParamsIndexDetailData data : detail.getData()) {
|
||||
String imageUrl = data.getImage();
|
||||
@@ -465,7 +474,7 @@ public class IndexDetailService {
|
||||
.header("Sec-Fetch-Mode", "no-cors")
|
||||
.header("Sec-Fetch-Dest", "image")
|
||||
.header("Accept-Encoding", "gzip, deflate")
|
||||
.header("Referer", buildNonParamsIndexUrl(detail.getIndexCode()))
|
||||
.header("Referer", self.buildNonParamsIndexUrl(detail.getIndexCode()))
|
||||
.header("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7")
|
||||
.build();
|
||||
|
||||
@@ -567,7 +576,8 @@ public class IndexDetailService {
|
||||
* @return
|
||||
* @see RequestInfo
|
||||
*/
|
||||
private String buildNonParamsIndexUrl(Serializable indexCode) {
|
||||
@RequireAuthAndProxy(autoLogin = true)
|
||||
public String buildNonParamsIndexUrl(Serializable indexCode) {
|
||||
|
||||
RequestInfo requestInfo = requestInfoService.getDefaultRequestInfo();
|
||||
if (requestInfo == null || StringUtils.isBlank(requestInfo.getAuthorization())) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import quant.rich.emoney.entity.sqlite.ProxySetting;
|
||||
import quant.rich.emoney.interfaces.IValidator;
|
||||
|
||||
public class ProxySettingValidator implements IValidator, ConstraintValidator<ProxySettingValid, ProxySetting> {
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import quant.rich.emoney.entity.config.DeviceInfoConfig;
|
||||
import quant.rich.emoney.entity.sqlite.RequestInfo;
|
||||
import quant.rich.emoney.interfaces.IValidator;
|
||||
|
||||
public class RequestInfoValidator implements IValidator, ConstraintValidator<RequestInfoValid, RequestInfo> {
|
||||
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
{"id":"46","name":"DKBY","nameCode":"10008500","data":[{"title":"DKBY指标说明:","items":["多空博弈","1.当“多方”线向上与“空方”线“金叉”时为买点。","2.当“多方”线向下与“空方”线“死叉”时为卖点。"],"image":null}],"original":"{\"id\":46,\"data\":[{\"title\":\"DKBY指标说明:\",\"items\":[\"多空博弈\",\"1.当“多方”线向上与“空方”线“金叉”时为买点。\",\"2.当“多方”线向下与“空方”线“死叉”时为卖点。\"]}],\"name\":\"DKBY\",\"nameCode\":\"10008500\"}","indexCode":"10008500","indexName":"DKBY","details":[{"content":"DKBY指标说明:","type":"TITLE"},{"content":"多空博弈","type":"TEXT"},{"content":"1.当“多方”线向上与“空方”线“金叉”时为买点。","type":"TEXT"},{"content":"2.当“多方”线向下与“空方”线“死叉”时为卖点。","type":"TEXT"}]}
|
||||
{
|
||||
"id" : "46",
|
||||
"name" : "DKBY",
|
||||
"nameCode" : "10008500",
|
||||
"data" : [ {
|
||||
"title" : "DKBY指标说明:",
|
||||
"items" : [ "多空博弈", "1.当“多方”线向上与“空方”线“金叉”时为买点。", "2.当“多方”线向下与“空方”线“死叉”时为卖点。" ],
|
||||
"image" : null
|
||||
} ],
|
||||
"original" : "{\"id\":46,\"data\":[{\"title\":\"DKBY指标说明:\",\"items\":[\"多空博弈\",\"1.当“多方”线向上与“空方”线“金叉”时为买点。\",\"2.当“多方”线向下与“空方”线“死叉”时为卖点。\"]}],\"name\":\"DKBY\",\"nameCode\":\"10008500\"}"
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user