1
0

Compare commits

...

9 Commits

41 changed files with 470 additions and 154 deletions

View File

@@ -1,5 +1,11 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JSCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
</JSCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="100" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="100" />
@@ -25,7 +31,6 @@
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
</TypeScriptCodeStyleSettings>
<codeStyleSettings language="CSS">

15
.idea/compiler.xml generated
View File

@@ -8,7 +8,7 @@
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
</profile>
<profile name="Annotation profile for leopard" enabled="true">
<profile name="Annotation profile for leopard-core" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
@@ -86,11 +86,18 @@
<entry name="$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.4/objenesis-3.4.jar" />
<entry name="$MAVEN_REPOSITORY$/org/javassist/javassist/3.30.2-GA/javassist-3.30.2-GA.jar" />
</processorPath>
<module name="leopard-core" />
</profile>
<profile name="Annotation profile for leopard" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/org/springframework/boot/spring-boot-configuration-processor/3.5.0/spring-boot-configuration-processor-3.5.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.38/lombok-1.18.38.jar" />
</processorPath>
<module name="leopard-server" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="leopard-core" target="17" />
</bytecodeTargetLevel>
</component>
</project>

24
.idea/dataSources.xml generated
View File

@@ -27,5 +27,29 @@
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="postgres@192.168.31.127" uuid="f7d817dc-8c9c-479f-b469-583df17cb013">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://192.168.31.127:6785/postgres</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="leopard@192.168.31.127" uuid="4b5dd0f8-26c8-49e2-b794-d464461c121a">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://192.168.31.127:6785/leopard</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/4b5dd0f8-26c8-49e2-b794-d464461c121a/console.sql" value="4b5dd0f8-26c8-49e2-b794-d464461c121a" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/a8b9cd0a-335e-42ae-991a-f2733200afbf/console.sql" value="a8b9cd0a-335e-42ae-991a-f2733200afbf" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f7d817dc-8c9c-479f-b469-583df17cb013/console.sql" value="f7d817dc-8c9c-479f-b469-583df17cb013" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/leopard-core/leopard-core.iml" filepath="$PROJECT_DIR$/leopard-core/leopard-core.iml" />
</modules>
</component>
</project>

65
leopard-core/pom.xml Normal file
View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>leopard</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>leopard-core</artifactId>
<dependencies>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
<path>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>6.6.8.Final</version>
</path>
<path>
<groupId>io.github.openfeign.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>7.0</version>
<classifier>jpa</classifier>
</path>
<path>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,11 @@
package com.lanyuanxiaoyao.leopard.core;
/**
* 静态字段
*
* @author lanyuanxiaoyao
* @version 20250829
*/
public interface Constants {
String DATABASE_PREFIX = "leopard_";
}

View File

@@ -0,0 +1,11 @@
package com.lanyuanxiaoyao.leopard.core;
import com.blinkfox.fenix.EnableFenix;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableFenix
@EntityScan(basePackageClasses = {JpaConfiguration.class})
public class JpaConfiguration {
}

View File

@@ -1,10 +1,11 @@
package com.lanyuanxiaoyao.leopard.server.entity;
package com.lanyuanxiaoyao.leopard.core.entity;
import com.lanyuanxiaoyao.leopard.server.Constants;
import com.lanyuanxiaoyao.leopard.core.Constants;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDate;
@@ -53,6 +54,7 @@ public class Daily extends SimpleEntity {
private Double factor;
@ManyToOne
@JoinColumn(nullable = false)
@ToString.Exclude
private Stock stock;
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.leopard.server.entity;
package com.lanyuanxiaoyao.leopard.core.entity;
import com.lanyuanxiaoyao.leopard.server.Constants;
import com.lanyuanxiaoyao.leopard.server.entity.base.SimpleEnum;
import com.lanyuanxiaoyao.leopard.core.Constants;
import com.lanyuanxiaoyao.leopard.core.entity.base.SimpleEnum;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
@@ -11,6 +11,7 @@ import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -55,6 +56,8 @@ public class Stock extends SimpleEntity {
private Market market;
@Comment("行业")
private String industry;
@Comment("上市日期")
private LocalDate listedDate;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.leopard.server.entity;
package com.lanyuanxiaoyao.leopard.core.entity;
import com.lanyuanxiaoyao.leopard.server.Constants;
import com.lanyuanxiaoyao.leopard.server.entity.base.SimpleEnum;
import com.lanyuanxiaoyao.leopard.core.Constants;
import com.lanyuanxiaoyao.leopard.core.entity.base.SimpleEnum;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.leopard.server.entity;
package com.lanyuanxiaoyao.leopard.core.entity;
import com.lanyuanxiaoyao.leopard.server.Constants;
import com.lanyuanxiaoyao.leopard.core.Constants;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.leopard.server.entity.base;
package com.lanyuanxiaoyao.leopard.core.entity.base;
/**
* @author lanyuanxiaoyao

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.leopard.server.repository;
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.server.entity.Daily;
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import java.time.LocalDate;
import java.util.List;
@@ -11,4 +11,7 @@ import org.springframework.stereotype.Repository;
public interface DailyRepository extends SimpleRepository<Daily> {
@Query("select distinct daily.tradeDate from Daily daily")
List<LocalDate> findDistinctTradeDate();
@Query("select distinct daily.tradeDate from Daily daily where daily.stock.id = ?1")
List<LocalDate> findDistinctTradeDateByStockId(Long stockId);
}

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.leopard.server.repository;
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.server.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import java.util.List;
import org.springframework.data.jpa.repository.Query;

View File

@@ -0,0 +1,18 @@
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.core.entity.Task;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
/**
* @author lanyuanxiaoyao
* @version 20250829
*/
@Repository
public interface TaskRepository extends SimpleRepository<Task> {
@Modifying
@Query("update Task task set task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.FAILURE where task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.RUNNING")
void updateAllRunningTaskToFailure();
}

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.leopard.server.repository;
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate;
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.stereotype.Repository;

View File

@@ -2,24 +2,21 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>leopard</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>leopard</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>leopard-server</artifactId>
<dependencies>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<artifactId>leopard-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -63,6 +60,11 @@
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
@@ -80,22 +82,6 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
<path>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>6.6.8.Final</version>
</path>
<path>
<groupId>io.github.openfeign.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>7.0</version>
<classifier>jpa</classifier>
</path>
<path>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

View File

@@ -1,6 +1,5 @@
package com.lanyuanxiaoyao.leopard.server;
import com.blinkfox.fenix.EnableFenix;
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
import com.yomahub.liteflow.core.FlowExecutor;
import jakarta.annotation.Resource;
@@ -16,8 +15,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
* @version 20250828
*/
@Slf4j
@SpringBootApplication
@EnableFenix
@SpringBootApplication(scanBasePackages = "com.lanyuanxiaoyao.leopard")
@EnableJpaAuditing
public class LeopardServerApplication implements ApplicationRunner {
public static void main(String[] args) {
@@ -31,7 +29,7 @@ public class LeopardServerApplication implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
executor.execute2RespWithEL("THEN(update_daily)");
// executor.execute2RespWithEL("THEN(update_daily)");
// executor.execute2RespWithEL("THEN(update_stock)");
}
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.leopard.server.controller;
import com.lanyuanxiaoyao.leopard.server.entity.Stock;
import com.lanyuanxiaoyao.leopard.server.entity.Task;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.entity.Task;
import com.lanyuanxiaoyao.leopard.server.service.StockService;
import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService;
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
@@ -26,6 +26,10 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("constants")
public class CommonOptionsController {
public static final String COLOR_PRIMARY = "bg-primary";
public static final String COLOR_SUCCESS = "bg-success";
public static final String COLOR_WARNING = "bg-yellow-800";
public static final String COLOR_ERROR = "bg-danger";
private static final List<String> COLORS = List.of(
"bg-black",
"bg-primary",
@@ -124,19 +128,22 @@ public class CommonOptionsController {
field
));
case "task_status" -> GlobalResponse.responseSuccess(buildMapping(
Arrays.stream(Task.Status.values())
.map(status -> new Mapping(status.name(), status.getChineseName()))
.toList(),
List.of(
new Mapping(Task.Status.RUNNING.name(), Task.Status.RUNNING.getChineseName(), COLOR_PRIMARY),
new Mapping(Task.Status.SUCCESS.name(), Task.Status.SUCCESS.getChineseName(), COLOR_SUCCESS),
new Mapping(Task.Status.FAILURE.name(), Task.Status.FAILURE.getChineseName(), COLOR_ERROR),
new Mapping(Task.Status.CANCELED.name(), Task.Status.CANCELED.getChineseName(), COLOR_WARNING)
),
field
));
case "trigger_status" -> GlobalResponse.responseSuccess(buildMapping(
List.of(
new Mapping(Trigger.TriggerState.NONE.name(), ""),
new Mapping(Trigger.TriggerState.NORMAL.name(), "正常"),
new Mapping(Trigger.TriggerState.PAUSED.name(), "暂停"),
new Mapping(Trigger.TriggerState.COMPLETE.name(), "完成"),
new Mapping(Trigger.TriggerState.ERROR.name(), "错误"),
new Mapping(Trigger.TriggerState.BLOCKED.name(), "阻塞")
new Mapping(Trigger.TriggerState.NORMAL.name(), "正常", COLOR_PRIMARY),
new Mapping(Trigger.TriggerState.PAUSED.name(), "暂停", COLOR_WARNING),
new Mapping(Trigger.TriggerState.COMPLETE.name(), "完成", COLOR_SUCCESS),
new Mapping(Trigger.TriggerState.ERROR.name(), "错误", COLOR_ERROR),
new Mapping(Trigger.TriggerState.BLOCKED.name(), "阻塞", COLOR_WARNING)
),
field
));
@@ -148,8 +155,7 @@ public class CommonOptionsController {
var map = mappings
.stream()
.collect(Collectors.toMap(Mapping::name, mapping -> {
var color = COLORS.get(Math.abs(mapping.name.hashCode()) % COLORS.size());
return "<span class='label %s'>%s</span>".formatted(color, mapping.label());
return "<span class='label %s'>%s</span>".formatted(mapping.color(), mapping.label());
}));
map.put("*", "<span class='label bg-gray-300'>%s</span>".formatted(field));
return map;
@@ -158,6 +164,9 @@ public class CommonOptionsController {
public record Option(String label, Object value) {
}
public record Mapping(String name, String label) {
public record Mapping(String name, String label, String color) {
public Mapping(String name, String label) {
this(name, label, COLORS.get(Math.abs(name.hashCode()) % COLORS.size()));
}
}
}

View File

@@ -1,9 +1,10 @@
package com.lanyuanxiaoyao.leopard.server.controller;
import com.lanyuanxiaoyao.leopard.server.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.server.service.StockService;
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
import java.time.LocalDate;
import java.util.function.Function;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -36,7 +37,8 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
stock.getName(),
stock.getFullname(),
stock.getMarket(),
stock.getIndustry()
stock.getIndustry(),
stock.getListedDate()
);
}
@@ -56,7 +58,8 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
String name,
String fullname,
Stock.Market market,
String industry
String industry,
LocalDate listedDate
) {
}
}

View File

@@ -1,9 +1,13 @@
package com.lanyuanxiaoyao.leopard.server.controller;
import com.lanyuanxiaoyao.leopard.server.entity.Task;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.lanyuanxiaoyao.leopard.core.entity.Task;
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.function.Function;
@@ -46,30 +50,51 @@ public class TaskController extends SimpleControllerSupport<Task, Void, TaskCont
throw new UnsupportedOperationException();
}
@Override
protected Function<Task, ListItem> listItemMapper() {
return task -> new ListItem(
task.getId(),
task.getName(),
task.getDescription(),
task.getStatus(),
task.getLaunchedTime(),
task.getFinishedTime()
private TaskCost calculateCost(LocalDateTime start, LocalDateTime finish) {
if (ObjectUtil.isNull(start) || ObjectUtil.isNull(finish)) {
return new TaskCost(null, null);
}
var duration = Duration.between(start, finish).toMillis();
return new TaskCost(
duration,
DateUtil.formatBetween(duration, BetweenFormatter.Level.SECOND)
);
}
@Override
protected Function<Task, ListItem> listItemMapper() {
return task -> {
var cost = calculateCost(task.getLaunchedTime(), task.getFinishedTime());
return new ListItem(
task.getId(),
task.getName(),
task.getDescription(),
task.getStatus(),
task.getLaunchedTime(),
task.getFinishedTime(),
cost.cost(),
cost.costText()
);
};
}
@Override
protected Function<Task, DetailItem> detailItemMapper() {
return task -> new DetailItem(
task.getId(),
task.getName(),
task.getDescription(),
task.getStatus(),
task.getError(),
task.getResult(),
task.getLaunchedTime(),
task.getFinishedTime()
);
return task -> {
var cost = calculateCost(task.getLaunchedTime(), task.getFinishedTime());
return new DetailItem(
task.getId(),
task.getName(),
task.getDescription(),
task.getStatus(),
task.getError(),
task.getResult(),
task.getLaunchedTime(),
task.getFinishedTime(),
cost.cost(),
cost.costText()
);
};
}
public record ListItem(
@@ -78,7 +103,9 @@ public class TaskController extends SimpleControllerSupport<Task, Void, TaskCont
String description,
Task.Status status,
LocalDateTime launchedTime,
LocalDateTime finishedTime
LocalDateTime finishedTime,
Long cost,
String costText
) {
}
@@ -90,10 +117,15 @@ public class TaskController extends SimpleControllerSupport<Task, Void, TaskCont
String error,
String result,
LocalDateTime launchedTime,
LocalDateTime finishedTime
LocalDateTime finishedTime,
Long cost,
String costText
) {
}
public record ExecuteRequest(Long templateId, Map<String, Object> params) {
}
public record TaskCost(Long cost, String costText) {
}
}

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.leopard.server.controller;
import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate;
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService;
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
import java.util.function.Function;

View File

@@ -1,13 +0,0 @@
package com.lanyuanxiaoyao.leopard.server.repository;
import com.lanyuanxiaoyao.leopard.server.entity.Task;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.stereotype.Repository;
/**
* @author lanyuanxiaoyao
* @version 20250829
*/
@Repository
public interface TaskRepository extends SimpleRepository<Task> {
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.leopard.server.service;
import com.lanyuanxiaoyao.leopard.server.entity.Daily;
import com.lanyuanxiaoyao.leopard.server.repository.DailyRepository;
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import java.time.LocalDate;
import java.util.List;
@@ -19,4 +19,8 @@ public class DailyService extends SimpleServiceSupport<Daily> {
public List<LocalDate> findDistinctTradeDate() {
return dailyRepository.findDistinctTradeDate();
}
public List<LocalDate> findDistinctTradeDateByStockId(Long stockId) {
return dailyRepository.findDistinctTradeDateByStockId(stockId);
}
}

View File

@@ -2,6 +2,7 @@ package com.lanyuanxiaoyao.leopard.server.service;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.lanyuanxiaoyao.leopard.server.service.task.TaskMonitorNodes;
import com.yomahub.liteflow.core.FlowExecutor;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -95,7 +96,8 @@ public class QuartzService {
if (ObjectUtil.isNotNull(templateId)) {
var template = taskTemplateService.detail(templateId);
var params = (Map<String, Object>) dataMap.getOrDefault("params", Map.of());
flowExecutor.execute2Resp(template.getChain(), params, context);
var monitorContext = new TaskMonitorNodes.TaskMonitorContext(template);
flowExecutor.execute2Resp(template.getChain(), params, monitorContext);
}
}
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.leopard.server.service;
import com.lanyuanxiaoyao.leopard.server.entity.Stock;
import com.lanyuanxiaoyao.leopard.server.repository.StockRepository;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import java.util.List;
import lombok.extern.slf4j.Slf4j;

View File

@@ -1,12 +1,15 @@
package com.lanyuanxiaoyao.leopard.server.service;
import com.lanyuanxiaoyao.leopard.server.entity.Task;
import com.lanyuanxiaoyao.leopard.server.repository.TaskRepository;
import com.lanyuanxiaoyao.leopard.core.entity.Task;
import com.lanyuanxiaoyao.leopard.core.repository.TaskRepository;
import com.lanyuanxiaoyao.leopard.server.service.task.TaskMonitorNodes;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import com.yomahub.liteflow.core.FlowExecutor;
import jakarta.transaction.Transactional;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
/**
@@ -16,15 +19,24 @@ import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TaskService extends SimpleServiceSupport<Task> {
private final TaskRepository taskRepository;
private final TaskTemplateService taskTemplateService;
private final FlowExecutor flowExecutor;
public TaskService(TaskRepository repository, TaskTemplateService taskTemplateService, @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") FlowExecutor flowExecutor) {
super(repository);
this.taskRepository = repository;
this.taskTemplateService = taskTemplateService;
this.flowExecutor = flowExecutor;
}
@Transactional(rollbackOn = Throwable.class)
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
log.warn("更新所有未完成的任务状态为失败");
taskRepository.updateAllRunningTaskToFailure();
}
public void execute(Long templateId, Map<String, Object> params) {
var template = taskTemplateService.detail(templateId);
var context = new TaskMonitorNodes.TaskMonitorContext(template);

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.leopard.server.service;
import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate;
import com.lanyuanxiaoyao.leopard.server.repository.TaskTemplateRepository;
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
import com.lanyuanxiaoyao.leopard.core.repository.TaskTemplateRepository;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import org.springframework.stereotype.Service;

View File

@@ -45,8 +45,8 @@ public class TuShareService {
public TuShareResponse stockList() {
var response = HttpUtil.post(API_URL, buildRequest(
"stock_basic",
Map.of("list_status", "L"),
List.of("ts_code", "name", "fullname", "exchange", "industry")
Map.of("list_status", "L", "market", "主板", "exchange", "SSE,SZSE"),
List.of("ts_code", "name", "fullname", "exchange", "industry", "list_date")
));
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
if (tuShareResponse.code != 0) {

View File

@@ -0,0 +1,77 @@
package com.lanyuanxiaoyao.leopard.server.service.task;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.server.service.DailyService;
import com.lanyuanxiaoyao.leopard.server.service.StockService;
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@LiteflowComponent("check_daily")
public class CheckDailyNode extends NodeComponent {
private final StockService stockService;
private final DailyService dailyService;
private final TuShareService tuShareService;
public CheckDailyNode(StockService stockService, DailyService dailyService, TuShareService tuShareService) {
this.stockService = stockService;
this.dailyService = dailyService;
this.tuShareService = tuShareService;
}
@Override
public void process() {
var reports = new ArrayList<MissedTradeReport>();
var stocks = stockService.list();
var exchanges = stocks.stream().map(Stock::getMarket).distinct().toList();
for (Stock.Market exchange : exchanges) {
var nowDate = LocalDate.now();
var allTradeDates = tuShareService.tradeDateList(exchange.name())
.data()
.items()
.stream()
.map(item -> LocalDate.parse(item.get(0), TuShareService.TRADE_FORMAT))
.filter(date -> date.isBefore(nowDate) || date.isEqual(nowDate))
.toList();
for (Stock stock : stocks) {
if (exchange.equals(stock.getMarket())) {
var existsTradeDates = dailyService.findDistinctTradeDateByStockId(stock.getId());
var missedTradeDates = allTradeDates.stream()
.filter(date -> date.isBefore(stock.getListedDate()))
.filter(date -> !existsTradeDates.contains(date))
.toList();
if (ObjectUtil.isNotEmpty(missedTradeDates)) {
reports.add(new MissedTradeReport(
stock.getCode(),
stock.getName(),
missedTradeDates
));
}
}
}
}
if (ObjectUtil.isNotEmpty(reports)) {
var context = getContextBean(TaskMonitorNodes.TaskMonitorContext.class);
context.setTaskResult(
reports.stream()
.map(report -> StrUtil.format("{}{})缺少如下交易日数据:{}", report.name(), report.code(), report.missedTradeDates().stream().map(LocalDate::toString).collect(Collectors.joining(", "))))
.collect(Collectors.joining("\n"))
);
}
}
public record MissedTradeReport(
String code,
String name,
List<LocalDate> missedTradeDates
) {
}
}

View File

@@ -2,8 +2,8 @@ package com.lanyuanxiaoyao.leopard.server.service.task;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.leopard.server.entity.Task;
import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate;
import com.lanyuanxiaoyao.leopard.core.entity.Task;
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.annotation.LiteflowFact;

View File

@@ -1,10 +1,9 @@
package com.lanyuanxiaoyao.leopard.server.service.task;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.leopard.server.entity.Daily;
import com.lanyuanxiaoyao.leopard.server.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.server.service.DailyService;
import com.lanyuanxiaoyao.leopard.server.service.StockService;
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
@@ -52,50 +51,49 @@ public class UpdateDailyNode extends NodeComponent {
.filter(date -> !existsTradeDates.contains(date))
.sorted()
.toList();
log.info("Target: {}", targetDates);
var stocks = stockService.list();
var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
for (LocalDate tradeDate : targetDates) {
log.info("Trade date: {}", tradeDate);
var factorResponse = tuShareService.factorList(tradeDate);
var factorMap = new HashMap<String, Double>();
for (List<String> item : factorResponse.data().items()) {
factorMap.put(item.get(0), Double.valueOf(item.get(2)));
}
log.info("Factor: {}", factorMap);
var response = tuShareService.dailyList(tradeDate);
transactionTemplate.execute(status -> {
try {
var count = 0;
for (List<String> item : response.data().items()) {
var code = item.get(0);
if (stocksMap.containsKey(code)) {
count++;
var stock = stocksMap.get(code);
var factor = factorMap.get(code);
var daily = new Daily();
daily.setTradeDate(LocalDate.parse(item.get(1), TuShareService.TRADE_FORMAT));
daily.setOpen(Double.valueOf(item.get(2)));
daily.setHigh(Double.valueOf(item.get(3)));
daily.setLow(Double.valueOf(item.get(4)));
daily.setClose(Double.valueOf(item.get(5)));
daily.setPreviousClose(Double.valueOf(item.get(6)));
daily.setPriceChangeAmount(Double.valueOf(item.get(7)));
daily.setPriceFluctuationRange(Double.valueOf(item.get(8)));
daily.setVolume(Double.valueOf(item.get(9)));
daily.setTurnover(Double.valueOf(item.get(10)));
daily.setOpen(item.get(2) == null ? null : Double.valueOf(item.get(2)));
daily.setHigh(item.get(3) == null ? null : Double.valueOf(item.get(3)));
daily.setLow(item.get(4) == null ? null : Double.valueOf(item.get(4)));
daily.setClose(item.get(5) == null ? null : Double.valueOf(item.get(5)));
daily.setPreviousClose(item.get(6) == null ? null : Double.valueOf(item.get(6)));
daily.setPriceChangeAmount(item.get(7) == null ? null : Double.valueOf(item.get(7)));
daily.setPriceFluctuationRange(item.get(8) == null ? null : Double.valueOf(item.get(8)));
daily.setVolume(item.get(9) == null ? null : Double.valueOf(item.get(9)));
daily.setTurnover(item.get(10) == null ? null : Double.valueOf(item.get(10)));
daily.setFactor(factor);
daily.setStock(stock);
log.info("Daily: {}", daily);
dailyService.save(daily);
}
}
log.info("Trade date: {} {}", tradeDate, count);
return true;
} catch (Exception exception) {
log.error("Error", exception);
status.setRollbackOnly();
return false;
}
});
ThreadUtil.safeSleep(1000);
}
}
}

View File

@@ -1,12 +1,13 @@
package com.lanyuanxiaoyao.leopard.server.service.task;
import cn.hutool.core.util.EnumUtil;
import com.lanyuanxiaoyao.leopard.server.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.server.service.StockService;
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.stream.Collectors;
@@ -35,12 +36,14 @@ public class UpdateStockNode extends NodeComponent {
var fullname = item.get(2);
var market = EnumUtil.fromString(Stock.Market.class, item.get(3));
var industry = item.get(4);
var listedDate = LocalDate.parse(item.get(5), TuShareService.TRADE_FORMAT);
if (stocksMap.containsKey(code)) {
var stock = stocksMap.get(code);
stock.setName(name);
stock.setFullname(fullname);
stock.setMarket(market);
stock.setIndustry(industry);
stock.setListedDate(listedDate);
} else {
var stock = new Stock();
stock.setCode(code);
@@ -48,6 +51,7 @@ public class UpdateStockNode extends NodeComponent {
stock.setFullname(fullname);
stock.setMarket(market);
stock.setIndustry(industry);
stock.setListedDate(listedDate);
stocks.add(stock);
}
targetCodes.add(code);

View File

@@ -9,18 +9,31 @@ spring:
async:
request-timeout: 3600000
datasource:
url: jdbc:mysql://mysql.lanyuanxiaoyao.com:43780/leopard?useSSL=false
# url: jdbc:mysql://mysql.lanyuanxiaoyao.com:43780/leopard?useSSL=false
# url: jdbc:mysql://192.168.31.127:3780/leopard?useSSL=false
url: jdbc:postgresql://192.168.31.127:6785/leopard
username: leopard
password: '9NEzFzovnddf@PyEP?e*AYAWnCyd7UhYwQK$pJf>7?ccFiN^x4$eKEZ5~E<7<+~X'
driver-class-name: com.mysql.cj.jdbc.Driver
# driver-class-name: com.mysql.cj.jdbc.Driver
driver-class-name: org.postgresql.Driver
jpa:
generate-ddl: true
properties:
hibernate:
dialect: org.hibernate.dialect.PostgresPlusDialect
quartz:
wait-for-jobs-to-complete-on-shutdown: true
startup-delay: 30s
job-store-type: jdbc
jdbc:
platform: mysql
# platform: mysql
platform: postgres
# initialize-schema: always
properties:
org:
quartz:
jobStore:
driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
fenix:
print-banner: false
liteflow:

View File

@@ -7,4 +7,7 @@
<chain id="update_daily_information">
CATCH(THEN(task_start, update_daily, task_end)).DO(task_error)
</chain>
<chain id="check_daily">
CATCH(THEN(task_start, check_daily, task_end)).DO(task_error)
</chain>
</flow>

View File

@@ -9,14 +9,18 @@ Content-Type: application/json
"api_name": "stock_basic",
"token": "{{api_key}}",
"params": {
"list_status": "D,P,L"
"list_status": "L",
"market": "主板",
"exchange": "SSE,SZSE"
},
"fields": [
"ts_code",
"name",
"fullname",
"exchange",
"industry"
"industry",
"market",
"list_date"
]
}
@@ -54,7 +58,7 @@ Content-Type: application/json
"api_name": "daily",
"token": "{{api_key}}",
"params": {
"trade_date": "19901219"
"trade_date": "19931213"
},
"fields": [
"ts_code",

View File

@@ -3,6 +3,7 @@ import {
amisRender,
commonInfo,
crudCommonOptions,
date,
paginationTemplate,
remoteMappings,
remoteOptions,
@@ -123,6 +124,12 @@ function StockList() {
label: '行业',
width: 150,
},
{
label: '上市日期',
width: 100,
align: 'center',
...date('listedDate'),
},
{
type: 'operation',
label: '操作',

View File

@@ -41,6 +41,13 @@ function TaskList() {
width: 100,
...remoteMappings('task_status', 'status'),
},
{
label: '耗时',
type: 'tpl',
align: 'center',
width: 150,
tpl: "${IF(costText, costText, '/')}",
},
{
name: 'launchedTime',
label: '启动时间',

View File

@@ -254,6 +254,13 @@ export function time(field: string) {
}
}
export function date(field: string) {
return {
type: 'tpl',
tpl: `\${IF(${field}, DATETOSTR(${field}, 'YYYY-MM-DD'), '/')}`,
}
}
export function pictureFromIds(field: string) {
return `\${ARRAYMAP(${field},id => '${commonInfo.baseUrl}/upload/download/' + id)}`
}

28
pom.xml
View File

@@ -4,12 +4,13 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>leopard</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>leopard</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>leopard-core</module>
<module>leopard-server</module>
</modules>
@@ -38,6 +39,12 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>leopard-core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template</artifactId>
@@ -95,4 +102,17 @@
</pluginManagement>
</build>
<distributionManagement>
<repository>
<id>${releases.id}</id>
<name>${releases.name}</name>
<url>${releases.url}</url>
</repository>
<snapshotRepository>
<id>${snapshots.id}</id>
<name>${snapshots.name}</name>
<url>${snapshots.url}</url>
</snapshotRepository>
</distributionManagement>
</project>