diff --git a/src/main/java/quant/rich/emoney/client/EmoneyClient.java b/src/main/java/quant/rich/emoney/client/EmoneyClient.java index d39ae6a..37a4edb 100644 --- a/src/main/java/quant/rich/emoney/client/EmoneyClient.java +++ b/src/main/java/quant/rich/emoney/client/EmoneyClient.java @@ -29,7 +29,8 @@ import quant.rich.emoney.util.SpringContextHolder; import okhttp3.OkHttpClient; /** - * 益盟操盘手基本请求客户端,提供基本功能 + * 益盟操盘手基本请求客户端,提供基本功能,如登录和请求。 + *

本类一般只提供静态方法,故不能也不该实例化本类。具体请求内容需要自己封装。整个系统共用一套鉴权,所以不要复制本例 *

请求头顺序

*

*

+ * 选股策略的抓取的筛选器自带股票过滤。其他抓取应该可以选股策略抓取的结果、tushare 的直接结果作为抓取的范围。 * 未来可能存在更多的计划任务, 所以需要考虑如何设计才能更好地兼容后续添加的任务类型 */ @Data @@ -38,7 +30,7 @@ public class Plan { * 键 ID */ @TableId(type = IdType.AUTO) - private String planId; + private Integer planId; /** * 计划任务表达式 @@ -49,23 +41,17 @@ public class Plan { * 计划名称 */ private String planName; - - /** - * 指标代码 - */ - private String indexCode; /** - * 需要抓取的指标周期 + * 计划类型 */ - @TableField(typeHandler = CommaListTypeHandler.class) - private List periods; + private PlanType planType; /** * 参数 */ @TableField(typeHandler = JsonStringTypeHandler.class) - private JsonNode params; + private JsonNode detail; /** * 是否启用 @@ -77,34 +63,5 @@ public class Plan { * 反之无论是否为交易日都执行 */ private Boolean openDayCheck; - - /** - * 设置抓取周期并去重 - * @param periods - * @return - */ - public Plan setPeriods(List periods) { - if (CollectionUtils.isEmpty(periods)) { - this.periods = Collections.emptyList(); - return this; - } - Set hashSet = new HashSet<>(); - periods.forEach(s -> { - if (StringUtils.isNotBlank(s)) { - hashSet.add(s.trim()); - } - }); - this.periods = new ArrayList<>(hashSet); - return this; - } - - /** - * 获取抓取周期。已去重 - * @return - */ - public List getPeriods() { - setPeriods(periods); - return periods; - } } diff --git a/src/main/java/quant/rich/emoney/enums/PlanType.java b/src/main/java/quant/rich/emoney/enums/PlanType.java new file mode 100644 index 0000000..cf7dac1 --- /dev/null +++ b/src/main/java/quant/rich/emoney/enums/PlanType.java @@ -0,0 +1,20 @@ +package quant.rich.emoney.enums; + +/** + * 计划任务类型枚举 + */ +public enum PlanType { + + /** + * 单指标 + */ + SINGLE_INDEX, + /** + * 多指标 + */ + MULTI_INDEX, + + STOCK_STRATEGY, + STOCK_PICKING + +} diff --git a/src/main/java/quant/rich/emoney/mapper/postgre/EmoneyIndexMapper.java b/src/main/java/quant/rich/emoney/mapper/postgre/EmoneyIndexMapper.java index ef3ecc8..2e95a96 100644 --- a/src/main/java/quant/rich/emoney/mapper/postgre/EmoneyIndexMapper.java +++ b/src/main/java/quant/rich/emoney/mapper/postgre/EmoneyIndexMapper.java @@ -1,6 +1,8 @@ package quant.rich.emoney.mapper.postgre; +import java.util.Collection; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Component; import com.baomidou.dynamic.datasource.annotation.DS; @@ -13,4 +15,7 @@ import quant.rich.emoney.entity.postgre.EmoneyIndex; @DS("postgre") public interface EmoneyIndexMapper extends BaseMapper { + int insertOrUpdateBatch( + @Param("list") Collection list, + @Param("batchSize") int batchSize); } diff --git a/src/main/java/quant/rich/emoney/pojo/MultiIndexPlanDetail.java b/src/main/java/quant/rich/emoney/pojo/MultiIndexPlanDetail.java new file mode 100644 index 0000000..0b9e55e --- /dev/null +++ b/src/main/java/quant/rich/emoney/pojo/MultiIndexPlanDetail.java @@ -0,0 +1,72 @@ +package quant.rich.emoney.pojo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.util.CollectionUtils; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain=true) +public class MultiIndexPlanDetail { + + /** + * 指标 + */ + List indexes; + + /** + * 抓取的 K 线粒度 + */ + List periods; + + /** + * 设置抓取周期并去重 + * @param periods + * @return + */ + public MultiIndexPlanDetail setPeriods(List periods) { + if (CollectionUtils.isEmpty(periods)) { + this.periods = Collections.emptyList(); + return this; + } + Set hashSet = new HashSet<>(); + periods.forEach(period -> { + if (period != null) { + hashSet.add(period); + } + }); + this.periods = new ArrayList<>(hashSet); + return this; + } + + /** + * 获取抓取周期。已去重 + * @return + */ + public List getPeriods() { + setPeriods(periods); + return periods; + } + + @Data + @Accessors(chain=true) + public static class MultiIndexPlanPart { + + /** + * 单指标代码 + */ + String indexCode; + + /** + * 单指标参数 + */ + Map params; + } +} diff --git a/src/main/java/quant/rich/emoney/pojo/SingleIndexPlanDetail.java b/src/main/java/quant/rich/emoney/pojo/SingleIndexPlanDetail.java new file mode 100644 index 0000000..7b242d3 --- /dev/null +++ b/src/main/java/quant/rich/emoney/pojo/SingleIndexPlanDetail.java @@ -0,0 +1,62 @@ +package quant.rich.emoney.pojo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.util.CollectionUtils; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain=true) +public class SingleIndexPlanDetail { + + /** + * 单指标代码 + */ + String indexCode; + + /** + * 单指标参数 + */ + Map params; + + /** + * 抓取的 K 线粒度 + */ + List periods; + + /** + * 设置抓取周期并去重 + * @param periods + * @return + */ + public SingleIndexPlanDetail setPeriods(List periods) { + if (CollectionUtils.isEmpty(periods)) { + this.periods = Collections.emptyList(); + return this; + } + Set hashSet = new HashSet<>(); + periods.forEach(period -> { + if (period != null) { + hashSet.add(period); + } + }); + this.periods = new ArrayList<>(hashSet); + return this; + } + + /** + * 获取抓取周期。已去重 + * @return + */ + public List getPeriods() { + setPeriods(periods); + return periods; + } +} diff --git a/src/main/java/quant/rich/emoney/service/postgre/EmoneyIndexService.java b/src/main/java/quant/rich/emoney/service/postgre/EmoneyIndexService.java new file mode 100644 index 0000000..560beb9 --- /dev/null +++ b/src/main/java/quant/rich/emoney/service/postgre/EmoneyIndexService.java @@ -0,0 +1,20 @@ +package quant.rich.emoney.service.postgre; + +import java.util.Collection; + +import org.springframework.transaction.annotation.Transactional; + +import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; + +import quant.rich.emoney.config.PostgreMybatisConfig; +import quant.rich.emoney.entity.postgre.EmoneyIndex; +import quant.rich.emoney.mapper.postgre.EmoneyIndexMapper; + +public class EmoneyIndexService extends PostgreServiceImpl { + + @Override + @Transactional(transactionManager = PostgreMybatisConfig.POSTGRE_TRANSACTION_MANAGER, rollbackFor = Exception.class) + public boolean saveOrUpdateBatch(Collection entityList, int batchSize) { + return SqlHelper.retBool(baseMapper.insertOrUpdateBatch(entityList, batchSize)); + } +} diff --git a/src/main/java/quant/rich/emoney/tushare/TushareDataServiceClient.java b/src/main/java/quant/rich/emoney/tushare/TushareDataServiceClient.java index cccdd12..ae15ea8 100644 --- a/src/main/java/quant/rich/emoney/tushare/TushareDataServiceClient.java +++ b/src/main/java/quant/rich/emoney/tushare/TushareDataServiceClient.java @@ -6,10 +6,16 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; +import feign.Headers; + /** * 对接 TushareDataService 的 Feign 客户端 + *

+ * 服务器上配置了 fail2ban 对默认 User-Agent + * 拦截(Java/*),所以这里自定义 User-Agent */ -@FeignClient(name="tushare-data-service-client", url="http://localhost:9999") +@Headers("User-Agent: At17DataService/1.0") +@FeignClient(name="tushare-data-service-client", url="https://tushare.database.at17.link") public interface TushareDataServiceClient { @GetMapping("/api/v1/common/stockInfo/list") diff --git a/src/main/java/quant/rich/emoney/util/ArithmeticCaptchaGen.java b/src/main/java/quant/rich/emoney/util/ArithmeticCaptchaGen.java new file mode 100644 index 0000000..1c135e6 --- /dev/null +++ b/src/main/java/quant/rich/emoney/util/ArithmeticCaptchaGen.java @@ -0,0 +1,164 @@ +package quant.rich.emoney.util; +import java.security.SecureRandom; + +public class ArithmeticCaptchaGen { + + private static final SecureRandom RND = new SecureRandom(); + private static final char[] OPS = new char[]{'+', '-', '×', '÷'}; + + public static class Captcha { + public final int a; + public final int b; + public final char op; + + public Captcha(int a, int b, char op) { + this.a = a; + this.b = b; + this.op = op; + } + + public String expr() { + return a + " " + op + " " + b; + } + + public int answer() { + return switch (op) { + case '+' -> a + b; + case '-' -> a - b; + case '×' -> a * b; + case '÷' -> a / b; // 保证整除 + default -> throw new IllegalStateException("Unexpected op: " + op); + }; + } + + @Override + public String toString() { + return expr() + " = ?"; + } + } + + /** 对外:随机生成一题 */ + public static Captcha generate() { + char op = OPS[RND.nextInt(OPS.length)]; + return generate(op); + } + + /** 对外:指定运算符生成 */ + public static Captcha generate(char op) { + return switch (op) { + case '+' -> genAddCarryAllowedButIfCarryUnitsZero(); + case '-' -> genSubNoBorrowNonNegative(); + case '×' -> genMulNoCarryOneDigit(); + case '÷' -> genDivExact(); + default -> throw new IllegalArgumentException("Unsupported op: " + op); + }; + } + + // ---------------- +:允许进位;若发生进位,则结果个位必须为 0;且和 <= 100 ---------------- + private static Captcha genAddCarryAllowedButIfCarryUnitsZero() { + // 仍保留 100 的特殊题(不是必须,但能保证覆盖 100) + if (RND.nextInt(20) == 0) { // 5% 概率出 100 + if (RND.nextBoolean()) return new Captcha(100, 0, '+'); + return new Captcha(0, 100, '+'); + } + + while (true) { + int a = RND.nextInt(101); // 0..100 + int b = RND.nextInt(101); // 0..100 + int sum = a + b; + if (sum > 100) continue; + + if (carryHappened(a, b)) { + // 发生进位:个位必须为 0 + if (sum % 10 == 0) return new Captcha(a, b, '+'); + } else { + // 未发生进位:不限制个位 + return new Captcha(a, b, '+'); + } + } + } + + /** 判断十进制逐位相加是否发生过进位(任意一位) */ + private static boolean carryHappened(int a, int b) { + int carry = 0; + while (a > 0 || b > 0) { + int da = a % 10; + int db = b % 10; + int s = da + db + carry; + if (s >= 10) return true; + carry = 0; // 因为 s<10 时 carry 必为 0 + a /= 10; + b /= 10; + } + return false; + } + + // ---------------- -:无退位,差 >= 0 ---------------- + private static Captcha genSubNoBorrowNonNegative() { + while (true) { + int a = RND.nextInt(100); // 0..99 + int b = RND.nextInt(100); // 0..99 + + int aT = a / 10, aU = a % 10; + int bT = b / 10, bU = b % 10; + + // 无退位:每一位都要 a>=b;且整体差>=0 + if (aU >= bU && aT >= bT) { + return new Captcha(a, b, '-'); // 此时 a>=b 自然成立 + } + } + } + + // ---------------- *:无进位;一元为 0..9;另一元每位*d < 10 ---------------- + private static Captcha genMulNoCarryOneDigit() { + while (true) { + int d = RND.nextInt(10); // 0..9 其中一个因子 + int x = RND.nextInt(100); // 另一元 0..99(你可自行放大范围) + + // 如果 d=0,任何 x 都满足 + if (d == 0) return orderMulOperands(d, x); + + // 对 x 的每一位要求:digit*d < 10 才不会在该位产生进位 + if (allDigitsMulLessThan10(x, d)) { + return orderMulOperands(d, x); + } + } + } + + private static Captcha orderMulOperands(int d, int x) { + // 随机决定把 0..9 放左边还是右边 + if (RND.nextBoolean()) return new Captcha(d, x, '×'); + return new Captcha(x, d, '×'); + } + + private static boolean allDigitsMulLessThan10(int x, int d) { + // x 是非负 + if (x == 0) return true; + int t = x; + while (t > 0) { + int digit = t % 10; + if (digit * d >= 10) return false; + t /= 10; + } + return true; + } + + // ---------------- /:必须整除 ---------------- + private static Captcha genDivExact() { + // 控制题目大小: dividend <= 100 + while (true) { + int b = 1 + RND.nextInt(10); // 除数 1..10 + int q = RND.nextInt(21); // 商 0..20 + int a = b * q; // 被除数 + if (a <= 100) return new Captcha(a, b, '÷'); + } + } + + // demo + public static void main(String[] args) { + for (int i = 0; i < 100; i++) { + Captcha c = generate(); + System.out.println(c.expr() + " = " + c.answer()); + } + } +} \ No newline at end of file diff --git a/src/main/java/quant/rich/emoney/util/ChunkRandomIter.java b/src/main/java/quant/rich/emoney/util/ChunkRandomIter.java new file mode 100644 index 0000000..4a90a96 --- /dev/null +++ b/src/main/java/quant/rich/emoney/util/ChunkRandomIter.java @@ -0,0 +1,28 @@ +package quant.rich.emoney.util; +import java.util.*; + +public class ChunkRandomIter { + + final static Random RANDOM = new Random(); + + public static List splitShuffleAndFlatten(List list, int n) { + int size = list.size(); + if (n <= 0) throw new IllegalArgumentException("n must be > 0"); + n = Math.min(n, size == 0 ? 1 : size); + + List parts = new ArrayList<>(n); + + int base = size / n; // 每份至少 base 个 + int extra = size % n; // 前 extra 份多一个 + + int idx = 0; + for (int i = 0; i < n; i++) { + int partSize = base + (i < extra ? 1 : 0); + List part = new ArrayList<>(list.subList(idx, idx + partSize)); + Collections.shuffle(part, RANDOM); + parts.addAll(part); + idx += partSize; + } + return parts; + } +} \ No newline at end of file diff --git a/src/main/java/quant/rich/emoney/util/DateUtils.java b/src/main/java/quant/rich/emoney/util/DateUtils.java index 9380e52..6300813 100644 --- a/src/main/java/quant/rich/emoney/util/DateUtils.java +++ b/src/main/java/quant/rich/emoney/util/DateUtils.java @@ -1,5 +1,6 @@ package quant.rich.emoney.util; +import java.time.DateTimeException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -55,4 +56,40 @@ public final class DateUtils { LocalDate date = LocalDate.parse(value, formatter); return date; } + + /** + * 将数字类型日期转换成相应的 LocalDateTime + *

+ *

  • 2309191130 → 2023-09-19 11:30:00 + *
  • 230919 → 2023-09-19 + */ + public static LocalDateTime longDatetimeToLocalDateTime(long dateTime) { + if (dateTime <= 0) { + throw new IllegalArgumentException("dateTime must be positive, got: " + dateTime); + } + + try { + if (dateTime > 99_999_999L) { + // yyMMddHHmm 例如:2309191130 -> 2023-09-19 11:30 + int yy = (int) (dateTime / 100_000_000L); // 23 + int year = 2000 + yy; // 2023 + int month = (int) ((dateTime % 100_000_000L) / 1_000_000L); // 09 + int day = (int) ((dateTime % 1_000_000L) / 10_000L); // 19 + int hour = (int) ((dateTime % 10_000L) / 100L); // 11 + int minute = (int) (dateTime % 100L); // 30 + + return LocalDateTime.of(year, month, day, hour, minute, 0); + } else { + // yyyyMMdd 例如:20230919 -> 2023-09-19 00:00 + int year = (int) (dateTime / 10_000L); // 2023 + int month = (int) ((dateTime % 10_000L) / 100L); // 09 + int day = (int) (dateTime % 100L); // 19 + + return LocalDateTime.of(year, month, day, 0, 0, 0); + } + } catch (DateTimeException ex) { + // 月/日/时/分越界会进这里(比如 month=13) + throw new IllegalArgumentException("Invalid dateTime value: " + dateTime, ex); + } + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8d88f91..fc5b1b2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,10 +19,7 @@ spring: devtools: restart: enabled: true - additional-exclude: - - '**/*.html' - - '**/*.js' - - '**/*.css' + additional-exclude: '**/*.html,**/*.js,**/*.css' additional-paths: lib/ jackson: date-format: yyyy-MM-dd HH:mm:ss diff --git a/src/main/resources/database.db b/src/main/resources/database.db index 303b6a6..1a6a79f 100644 Binary files a/src/main/resources/database.db and b/src/main/resources/database.db differ diff --git a/src/main/resources/mapper/postgre/EmoneyIndex.xml b/src/main/resources/mapper/postgre/EmoneyIndex.xml index 0e8820c..8a0d30d 100644 --- a/src/main/resources/mapper/postgre/EmoneyIndex.xml +++ b/src/main/resources/mapper/postgre/EmoneyIndex.xml @@ -3,11 +3,14 @@ "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > - - INSERT INTO emoney_index - (ts_code, trade_date, "value", index_param, index_name, line_name, line_shape, data_period) - VALUES - + + + + + INSERT INTO emoney_index + (ts_code, trade_date, "value", index_param, index_name, line_name, line_shape, data_period) + VALUES + (#{item.tsCode}, #{item.date}, #{item.value}, @@ -17,15 +20,25 @@ #{item.lineShape}, #{item.dataPeriod} ) - - ON CONFLICT (ts_code, trade_date, data_period, index_name, index_param, line_name ) DO UPDATE SET - ts_code = EXCLUDED.ts_code, - trade_date = EXCLUDED.trade_date, - "value" = EXCLUDED."value", - index_param = EXCLUDED.index_param, - index_name = EXCLUDED.index_name, - line_shape = EXCLUDED.line_shape, - data_period = EXCLUDED.data_period + + + + ON CONFLICT (ts_code, trade_date, data_period, index_name, index_param, line_name ) + DO UPDATE SET + ts_code = EXCLUDED.ts_code, + trade_date = EXCLUDED.trade_date, + "value" = EXCLUDED."value", + index_param = EXCLUDED.index_param, + index_name = EXCLUDED.index_name, + line_shape = EXCLUDED.line_shape, + data_period = EXCLUDED.data_period; + + + + , + + + + + + +
    + +
    + + +
    +
    + + + + + \ No newline at end of file diff --git a/src/main/resources/webpage/admin/v1/manage/plan/editStockStrategy.html b/src/main/resources/webpage/admin/v1/manage/plan/editStockStrategy.html new file mode 100644 index 0000000..6a3b670 --- /dev/null +++ b/src/main/resources/webpage/admin/v1/manage/plan/editStockStrategy.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/webpage/admin/v1/manage/plan/include.html b/src/main/resources/webpage/admin/v1/manage/plan/include.html deleted file mode 100644 index 3b974f8..0000000 --- a/src/main/resources/webpage/admin/v1/manage/plan/include.html +++ /dev/null @@ -1,300 +0,0 @@ - - -
    - - - - -
    - - \ No newline at end of file diff --git a/src/main/resources/webpage/admin/v1/manage/plan/index.html b/src/main/resources/webpage/admin/v1/manage/plan/index.html index d8d693a..2490c98 100644 --- a/src/main/resources/webpage/admin/v1/manage/plan/index.html +++ b/src/main/resources/webpage/admin/v1/manage/plan/index.html @@ -9,79 +9,121 @@
    -

    - - 计划任务新增 -

    - +

    + + 计划任务新增 +

    - -
    + +
    - + }) + } + }) + }); // layui.use + function openEditForm(planId) { + window.editLayer = Helper.openR({ + type: 2, + title: '编辑计划任务', + btn: ['提交', '关闭'], + yes: function (index, layero) { + const iframe = document.querySelector('iframe'); + const doc = iframe.contentDocument || iframe.contentWindow.document; + doc.querySelector('[lay-filter="submitPlan"]').click(); + }, + area: '40%', + content: '/admin/v1/manage/plan/edit' + (planId !== undefined ? '?planId=' + planId : '') + }); + } + \ No newline at end of file diff --git a/src/test/java/quant/rich/EmoneyAutoApplicationTests.java b/src/test/java/quant/rich/EmoneyAutoApplicationTests.java index 55fabde..41b6e1d 100644 --- a/src/test/java/quant/rich/EmoneyAutoApplicationTests.java +++ b/src/test/java/quant/rich/EmoneyAutoApplicationTests.java @@ -25,7 +25,7 @@ class EmoneyAutoApplicationTests { @Test void contextLoads() { - EmoneyClient.relogin(); + EmoneyClient.reloginCheck(); CandleStickWithIndex_Request request = new CandleStickWithIndex_Request(); diff --git a/src/test/java/quant/rich/EmoneyIndexScraper.java b/src/test/java/quant/rich/EmoneyIndexScraper.java index cf28bf0..6d84a7e 100644 --- a/src/test/java/quant/rich/EmoneyIndexScraper.java +++ b/src/test/java/quant/rich/EmoneyIndexScraper.java @@ -65,7 +65,7 @@ public class EmoneyIndexScraper { @Test void test() { - EmoneyClient.relogin(); + EmoneyClient.reloginCheck(); String url = buildUrl(10002800); diff --git a/src/test/java/quant/rich/emoney/EmoneyStrategyMarkTests.java b/src/test/java/quant/rich/emoney/EmoneyStrategyMarkTests.java index 08510c5..ad97608 100644 --- a/src/test/java/quant/rich/emoney/EmoneyStrategyMarkTests.java +++ b/src/test/java/quant/rich/emoney/EmoneyStrategyMarkTests.java @@ -25,7 +25,7 @@ class EmoneyStrategyMarkTests { @Test void contextLoads() { - EmoneyClient.relogin(); + EmoneyClient.reloginCheck(); StrategyMark_Request request = new StrategyMark_Request(); request.setGoodsId(600325); diff --git a/src/test/java/quant/rich/emoney/StockStrategyUpdateTest.java b/src/test/java/quant/rich/emoney/StockStrategyUpdateTest.java new file mode 100644 index 0000000..49c9035 --- /dev/null +++ b/src/test/java/quant/rich/emoney/StockStrategyUpdateTest.java @@ -0,0 +1,48 @@ +package quant.rich.emoney; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.slf4j.Slf4j; +import quant.rich.emoney.service.postgre.StockStrategyService; + +/** + * 测试从本地读取包含 stock strategy 的 json, 转换为列表并更新到数据库中 + */ +@SpringBootTest +@ContextConfiguration(classes = EmoneyAutoApplication.class) +@RunWith(SpringJUnit4ClassRunner.class) +@Slf4j +public class StockStrategyUpdateTest { + + @Autowired + ObjectMapper objectMapper; + + @Autowired + StockStrategyService stockStrategyService; + + @Test + void test() throws IOException, InterruptedException, ExecutionException { + + // 首先从 tushare-data-service feign 客户端取数据 + + + String str = Files.readString(Path.of("C:\\Users\\Administrator\\Desktop\\stock_strategy.json")); + JsonNode node = objectMapper.readTree(str); + Future future = stockStrategyService.updateByQueryResponseAsync(node); + future.get(); + } +}