diff --git a/src/main/java/quant/rich/emoney/EmoneyAutoApplication.java b/src/main/java/quant/rich/emoney/EmoneyAutoApplication.java index 5f839cb..5422ce6 100644 --- a/src/main/java/quant/rich/emoney/EmoneyAutoApplication.java +++ b/src/main/java/quant/rich/emoney/EmoneyAutoApplication.java @@ -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) { diff --git a/src/main/java/quant/rich/emoney/component/RequireAuthAndProxyAspect.java b/src/main/java/quant/rich/emoney/component/RequireAuthAndProxyAspect.java index 77b48c7..1a72341 100644 --- a/src/main/java/quant/rich/emoney/component/RequireAuthAndProxyAspect.java +++ b/src/main/java/quant/rich/emoney/component/RequireAuthAndProxyAspect.java @@ -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 管理的类的成员方法上,使其在执行前校验代理状态、鉴权状态 + *

需要开启 AOP:在任意配置类上增加注解:@EnableAspectJAutoProxy + */ @Component @Aspect public class RequireAuthAndProxyAspect { + + /** + * 调用链深度,用以判断同线程同类型切点命中的次数 + */ + private static final ThreadLocal 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 已鉴权、代理已配置,否则不允许进入方法 - *

需要开启 AOP:在任意配置类上增加注解:@EnableAspectJAutoProxy + *

+ * 由于 AOP 特性使然, 必须为 public 方法才能生效 + *

+ * 为防止反复重登录验证, 在同一调用链中的校验, 仅在首次调用时进行检查, 如: + * A、B 和 C 方法都添加了本注解, A 中调用 B, B 中调用 C, 则仅在 A 方法执行时才进行检查。 + *

+ * 为了安全起见, 无论 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; diff --git a/src/main/java/quant/rich/emoney/config/EmoneyAutoConfig.java b/src/main/java/quant/rich/emoney/config/EmoneyAutoConfig.java index ff0903d..5212dee 100644 --- a/src/main/java/quant/rich/emoney/config/EmoneyAutoConfig.java +++ b/src/main/java/quant/rich/emoney/config/EmoneyAutoConfig.java @@ -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 { diff --git a/src/main/java/quant/rich/emoney/pojo/MultiIndexPlanDetail.java b/src/main/java/quant/rich/emoney/pojo/dto/MultiIndexPlanDetail.java similarity index 82% rename from src/main/java/quant/rich/emoney/pojo/MultiIndexPlanDetail.java rename to src/main/java/quant/rich/emoney/pojo/dto/MultiIndexPlanDetail.java index 0b9e55e..8c79c49 100644 --- a/src/main/java/quant/rich/emoney/pojo/MultiIndexPlanDetail.java +++ b/src/main/java/quant/rich/emoney/pojo/dto/MultiIndexPlanDetail.java @@ -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 indexes; /** * 抓取的 K 线粒度 */ + @NotEmpty List periods; /** @@ -55,6 +62,7 @@ public class MultiIndexPlanDetail { return periods; } + @Validated @Data @Accessors(chain=true) public static class MultiIndexPlanPart { diff --git a/src/main/java/quant/rich/emoney/pojo/SingleIndexPlanDetail.java b/src/main/java/quant/rich/emoney/pojo/dto/SingleIndexPlanDetail.java similarity index 92% rename from src/main/java/quant/rich/emoney/pojo/SingleIndexPlanDetail.java rename to src/main/java/quant/rich/emoney/pojo/dto/SingleIndexPlanDetail.java index 7b242d3..7901a59 100644 --- a/src/main/java/quant/rich/emoney/pojo/SingleIndexPlanDetail.java +++ b/src/main/java/quant/rich/emoney/pojo/dto/SingleIndexPlanDetail.java @@ -1,4 +1,4 @@ -package quant.rich.emoney.pojo; +package quant.rich.emoney.pojo.dto; import java.util.ArrayList; import java.util.Collections; diff --git a/src/main/java/quant/rich/emoney/service/IndexDetailService.java b/src/main/java/quant/rich/emoney/service/IndexDetailService.java index 207298e..da450d2 100644 --- a/src/main/java/quant/rich/emoney/service/IndexDetailService.java +++ b/src/main/java/quant/rich/emoney/service/IndexDetailService.java @@ -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 的无参指标详情 *

本例用到的 requestInfo 不需要 PatchOkHttp 覆写,但要求鉴权参数拼接到 url 中,故要求鉴权 *

会一并尝试获取其他在本地未有的无参指标

+ *

由于本例需要鉴权, 故在方法上开启了 {@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())) { diff --git a/src/main/java/quant/rich/emoney/validator/ProxySettingValidator.java b/src/main/java/quant/rich/emoney/validator/ProxySettingValidator.java index 798d35f..e036719 100644 --- a/src/main/java/quant/rich/emoney/validator/ProxySettingValidator.java +++ b/src/main/java/quant/rich/emoney/validator/ProxySettingValidator.java @@ -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 { diff --git a/src/main/java/quant/rich/emoney/validator/RequestInfoValidator.java b/src/main/java/quant/rich/emoney/validator/RequestInfoValidator.java index 8d579a9..8433a61 100644 --- a/src/main/java/quant/rich/emoney/validator/RequestInfoValidator.java +++ b/src/main/java/quant/rich/emoney/validator/RequestInfoValidator.java @@ -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 { diff --git a/src/main/resources/conf/extra/indexDetail/nonParams/10008500.json b/src/main/resources/conf/extra/indexDetail/nonParams/10008500.json index a855d3d..4f0d5d9 100644 --- a/src/main/resources/conf/extra/indexDetail/nonParams/10008500.json +++ b/src/main/resources/conf/extra/indexDetail/nonParams/10008500.json @@ -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"}]} \ No newline at end of file +{ + "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\"}" +} \ No newline at end of file diff --git a/src/main/resources/database.db b/src/main/resources/database.db index 1a6a79f..95dd959 100644 Binary files a/src/main/resources/database.db and b/src/main/resources/database.db differ