Initial commit: Java股票数据获取项目
- 基于Tushare Pro Java SDK 2.0.5-RELEASE版本 - 实现股票基本信息和日线行情数据获取 - 使用MyBatis进行数据库操作 - 支持定时任务调度 - Spring Boot框架集成 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
ef5cbc22dc
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(mvn clean compile:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/MarsCodeWorkspaceAppSettings.xml
Normal file
6
.idea/MarsCodeWorkspaceAppSettings.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
|
||||
<option name="progress" value="1.0" />
|
||||
</component>
|
||||
</project>
|
||||
18
.idea/dataSources.xml
Normal file
18
.idea/dataSources.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="stock_data@fnv4.skdbj.email" uuid="434a0422-4a51-4522-9765-fecbb4acba4a">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://fnv4.skdbj.email:15340/stock_data</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.resource.type" value="Deployment" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/encodings.xml
Normal file
7
.idea/encodings.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/inspectionProfiles/Project_Default.xml
Normal file
5
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
</profile>
|
||||
</component>
|
||||
14
.idea/misc.xml
Normal file
14
.idea/misc.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="ms-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/sqldialects.xml
Normal file
6
.idea/sqldialects.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources/sql/init.sql" dialect="MySQL" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
73
CLAUDE.md
Normal file
73
CLAUDE.md
Normal file
@ -0,0 +1,73 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是一个定时抓取股票数据的Java项目,通过调用Tushare Pro接口获取股票数据并存储到数据库中。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **Java**: 主要编程语言
|
||||
- **Maven**: 项目构建和依赖管理
|
||||
- **Tushare Pro Java SDK**: 股票数据获取接口
|
||||
- **MyBatis**: ORM框架,用于数据库操作
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 构建和运行
|
||||
```bash
|
||||
# 编译项目
|
||||
mvn compile
|
||||
|
||||
# 打包项目
|
||||
mvn package
|
||||
|
||||
# 运行主程序
|
||||
mvn exec:java -Dexec.mainClass="com.sjz.App"
|
||||
|
||||
# 运行测试
|
||||
mvn test
|
||||
|
||||
# 清理构建文件
|
||||
mvn clean
|
||||
```
|
||||
|
||||
### 开发相关
|
||||
```bash
|
||||
# 生成项目依赖树
|
||||
mvn dependency:tree
|
||||
|
||||
# 查看项目信息
|
||||
mvn help:effective-pom
|
||||
|
||||
# 更新依赖
|
||||
mvn dependency:resolve
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/main/java/com/sjz/
|
||||
├── App.java # 主入口类
|
||||
├── config/ # 配置类
|
||||
├── service/ # 业务逻辑层
|
||||
├── mapper/ # MyBatis数据访问层
|
||||
├── model/ # 数据模型类
|
||||
├── task/ # 定时任务
|
||||
└── util/ # 工具类
|
||||
|
||||
src/test/java/com/sjz/ # 测试类
|
||||
src/main/resources/ # 配置文件和MyBatis映射文件
|
||||
```
|
||||
|
||||
## 开发注意事项
|
||||
|
||||
- Tushare Pro接口需要配置有效的API token
|
||||
- 数据库连接信息需要在配置文件中正确设置
|
||||
- 注意Tushare接口的调用频率限制,避免超出限制
|
||||
- 股票数据的更新通常是交易日进行,需要考虑非交易日的处理
|
||||
|
||||
## 数据库
|
||||
|
||||
项目使用MyBatis进行数据库操作,相关的SQL映射文件通常位于`src/main/resources/mapper/`目录下。
|
||||
165
README.md
Normal file
165
README.md
Normal file
@ -0,0 +1,165 @@
|
||||
# 股票数据抓取项目
|
||||
|
||||
基于Tushare Pro接口的股票数据定时抓取系统,使用Spring Boot + MyBatis + MySQL技术栈。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🔄 **定时抓取**: 支持定时获取股票基本信息和日线行情数据
|
||||
- 📊 **数据存储**: 使用MySQL存储股票数据,支持批量插入和去重
|
||||
- ⚡ **高性能**: 使用HikariCP连接池和MyBatis优化数据库操作
|
||||
- 🔧 **灵活配置**: 支持通过配置文件自定义抓取策略和数据库连接
|
||||
- 📈 **多时间周期**: 支持日线、周线、月线数据抓取
|
||||
- 🗃️ **数据清理**: 自动清理历史数据,控制存储空间
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **Java 8+**: 主要编程语言
|
||||
- **Spring Boot 2.7**: 应用框架
|
||||
- **MyBatis**: ORM框架
|
||||
- **MySQL**: 数据存储
|
||||
- **Tushare Pro SDK**: 股票数据接口
|
||||
- **Quartz**: 定时任务调度
|
||||
- **HikariCP**: 数据库连接池
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
- JDK 8+
|
||||
- Maven 3.6+
|
||||
- MySQL 5.7+
|
||||
|
||||
### 2. 数据库初始化
|
||||
|
||||
执行SQL脚本创建数据库和表:
|
||||
|
||||
```sql
|
||||
source src/main/resources/sql/init.sql
|
||||
```
|
||||
|
||||
### 3. 配置文件修改
|
||||
|
||||
编辑 `src/main/resources/application.properties`:
|
||||
|
||||
```properties
|
||||
# Tushare配置
|
||||
tushare.token=your_actual_tushare_token
|
||||
|
||||
# 数据库配置
|
||||
db.url=jdbc:mysql://localhost:3306/stock_data?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
|
||||
db.username=your_username
|
||||
db.password=your_password
|
||||
```
|
||||
|
||||
### 4. 编译和运行
|
||||
|
||||
```bash
|
||||
# 编译项目
|
||||
mvn clean compile
|
||||
|
||||
# 打包项目
|
||||
mvn clean package
|
||||
|
||||
# 运行应用程序
|
||||
java -jar target/go-stock-1.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 命令行参数
|
||||
|
||||
```bash
|
||||
# 启动应用程序并运行定时任务
|
||||
java -jar go-stock.jar
|
||||
|
||||
# 手动获取股票基本信息
|
||||
java -jar go-stock.jar fetch-basic
|
||||
|
||||
# 手动获取股票日线行情数据
|
||||
java -jar go-stock.jar fetch-daily
|
||||
|
||||
# 清理历史数据,保留60天
|
||||
java -jar go-stock.jar clean-data 60
|
||||
|
||||
# 显示帮助信息
|
||||
java -jar go-stock.jar help
|
||||
```
|
||||
|
||||
### 定时任务配置
|
||||
|
||||
在 `application.properties` 中配置定时任务:
|
||||
|
||||
```properties
|
||||
# 启用定时任务
|
||||
task.enabled=true
|
||||
|
||||
# 股票基本信息获取时间(每天凌晨1点)
|
||||
task.cron.stock.basic=0 0 1 * * ?
|
||||
|
||||
# 股票日线数据获取时间(每个交易日下午3点30分)
|
||||
task.cron.stock.daily=0 30 15 * * ?
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/main/java/com/sjz/
|
||||
├── App.java # 主程序入口
|
||||
├── config/ # 配置类
|
||||
│ ├── DatabaseConfig.java # 数据库配置
|
||||
│ └── TushareConfig.java # Tushare配置
|
||||
├── mapper/ # 数据访问层
|
||||
│ ├── StockBasicMapper.java
|
||||
│ └── StockDailyMapper.java
|
||||
├── model/ # 数据模型
|
||||
│ ├── StockBasic.java
|
||||
│ └── StockDaily.java
|
||||
├── service/ # 业务逻辑层
|
||||
│ ├── StockBasicService.java
|
||||
│ └── StockDailyService.java
|
||||
├── task/ # 定时任务
|
||||
│ └── StockDataScheduler.java
|
||||
└── util/ # 工具类
|
||||
├── ConfigUtil.java
|
||||
└── DateUtil.java
|
||||
|
||||
src/main/resources/
|
||||
├── application.properties # 配置文件
|
||||
├── mybatis-config.xml # MyBatis配置
|
||||
└── sql/init.sql # 数据库初始化脚本
|
||||
```
|
||||
|
||||
## API限制说明
|
||||
|
||||
Tushare Pro接口有以下限制:
|
||||
|
||||
- 每分钟请求次数限制:根据用户等级不同
|
||||
- 每次返回记录数限制:通常5000条
|
||||
- 建议在请求间添加适当延时,避免触发限制
|
||||
|
||||
## 监控和日志
|
||||
|
||||
- 使用SLF4J + Logback记录运行日志
|
||||
- 数据库操作记录详细日志
|
||||
- 定时任务执行状态监控
|
||||
- 错误信息记录和报警
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **Tushare Token**: 需要在Tushare官网注册并获取有效的API Token
|
||||
2. **交易日**: 系统会自动判断交易日,非交易日不会执行数据抓取
|
||||
3. **数据去重**: 使用唯一键约束确保数据不重复
|
||||
4. **数据备份**: 建议定期备份数据库
|
||||
5. **监控告警**: 建议配置监控告警,及时发现问题
|
||||
|
||||
## 扩展功能
|
||||
|
||||
- 添加更多数据源
|
||||
- 实现数据分析和统计功能
|
||||
- 添加Web管理界面
|
||||
- 支持更多时间周期数据
|
||||
- 添加邮件通知功能
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目仅供学习和研究使用,请遵守相关API使用条款。
|
||||
134
pom.xml
Normal file
134
pom.xml
Normal file
@ -0,0 +1,134 @@
|
||||
<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>
|
||||
|
||||
<groupId>com.sjz</groupId>
|
||||
<artifactId>go-stock</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>go-stock</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<version>3.1.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit 测试框架 -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Tushare Pro Java SDK -->
|
||||
<dependency>
|
||||
<groupId>com.github.qhh6eq</groupId>
|
||||
<artifactId>tushare-pro-java-sdk</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Spring Boot Starter -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL连接器 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
|
||||
<!-- HikariCP 连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>5.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志框架 -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON处理 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.15.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 定时任务 -->
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Spring Boot Maven Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.7.14</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<mainClass>com.sjz.App</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Compiler Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Surefire Plugin for running tests -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Test.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
102
src/main/java/com/sjz/App.java
Normal file
102
src/main/java/com/sjz/App.java
Normal file
@ -0,0 +1,102 @@
|
||||
package com.sjz;
|
||||
|
||||
import com.sjz.task.StockDataScheduler;
|
||||
import com.sjz.util.ConfigUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* 股票数据抓取应用程序启动类
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = "com.sjz")
|
||||
@EnableTransactionManagement
|
||||
public class App implements CommandLineRunner {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(App.class);
|
||||
|
||||
@Autowired
|
||||
private StockDataScheduler stockDataScheduler;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 验证必要的配置
|
||||
ConfigUtil.validateRequiredProperties(
|
||||
"tushare.token",
|
||||
"db.url",
|
||||
"db.username",
|
||||
"db.password"
|
||||
);
|
||||
|
||||
logger.info("正在启动股票数据抓取应用程序...");
|
||||
SpringApplication.run(App.class, args);
|
||||
logger.info("股票数据抓取应用程序启动完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("应用程序启动失败", e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
logger.info("股票数据抓取应用程序正在运行...");
|
||||
|
||||
if (args.length > 0) {
|
||||
String command = args[0];
|
||||
switch (command.toLowerCase()) {
|
||||
case "fetch-basic":
|
||||
logger.info("手动触发股票基本信息获取...");
|
||||
stockDataScheduler.triggerFetchStockBasic();
|
||||
break;
|
||||
case "fetch-daily":
|
||||
logger.info("手动触发股票日线行情数据获取...");
|
||||
stockDataScheduler.triggerFetchStockDaily();
|
||||
break;
|
||||
case "clean-data":
|
||||
int daysToKeep = args.length > 1 ? Integer.parseInt(args[1]) : 90;
|
||||
logger.info("手动触发历史数据清理,保留 {} 天数据...", daysToKeep);
|
||||
stockDataScheduler.triggerCleanHistoryData(daysToKeep);
|
||||
break;
|
||||
case "help":
|
||||
printUsage();
|
||||
break;
|
||||
default:
|
||||
logger.warn("未知命令: {}", command);
|
||||
printUsage();
|
||||
}
|
||||
} else {
|
||||
// 没有命令行参数时,显示帮助信息
|
||||
printUsage();
|
||||
|
||||
// 启动定时任务
|
||||
logger.info("定时任务已启动,将按照配置的时间执行数据抓取任务");
|
||||
}
|
||||
}
|
||||
|
||||
private void printUsage() {
|
||||
System.out.println("\n=== 股票数据抓取应用程序使用说明 ===");
|
||||
System.out.println("启动命令: java -jar go-stock.jar [命令]");
|
||||
System.out.println();
|
||||
System.out.println("可用命令:");
|
||||
System.out.println(" 无参数 - 启动应用程序并运行定时任务");
|
||||
System.out.println(" fetch-basic - 手动获取股票基本信息");
|
||||
System.out.println(" fetch-daily - 手动获取股票日线行情数据");
|
||||
System.out.println(" clean-data [days] - 清理历史数据,默认保留90天");
|
||||
System.out.println(" help - 显示此帮助信息");
|
||||
System.out.println();
|
||||
System.out.println("示例:");
|
||||
System.out.println(" java -jar go-stock.jar fetch-basic");
|
||||
System.out.println(" java -jar go-stock.jar fetch-daily");
|
||||
System.out.println(" java -jar go-stock.jar clean-data 60");
|
||||
System.out.println("====================================\n");
|
||||
}
|
||||
}
|
||||
81
src/main/java/com/sjz/config/DatabaseConfig.java
Normal file
81
src/main/java/com/sjz/config/DatabaseConfig.java
Normal file
@ -0,0 +1,81 @@
|
||||
package com.sjz.config;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
|
||||
import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 数据库配置类
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@MapperScan("com.sjz.mapper")
|
||||
public class DatabaseConfig {
|
||||
|
||||
/**
|
||||
* 创建数据源
|
||||
*/
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
|
||||
config.setJdbcUrl("jdbc:mysql://localhost:3306/stock_data?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false");
|
||||
config.setUsername("root");
|
||||
config.setPassword("your_password_here");
|
||||
|
||||
// 连接池配置
|
||||
config.setMaximumPoolSize(10);
|
||||
config.setMinimumIdle(5);
|
||||
config.setIdleTimeout(300000);
|
||||
config.setConnectionTimeout(20000);
|
||||
config.setMaxLifetime(1200000);
|
||||
|
||||
return new HikariDataSource(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SqlSessionFactory
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
|
||||
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
|
||||
sessionFactory.setDataSource(dataSource);
|
||||
|
||||
// 设置MyBatis配置文件位置
|
||||
sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
|
||||
|
||||
// 设置Mapper XML文件位置
|
||||
sessionFactory.setMapperLocations(
|
||||
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
|
||||
|
||||
// 设置类型别名包
|
||||
sessionFactory.setTypeAliasesPackage("com.sjz.model");
|
||||
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务管理器
|
||||
*/
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager(DataSource dataSource) {
|
||||
return new DataSourceTransactionManager(dataSource);
|
||||
}
|
||||
}
|
||||
37
src/main/java/com/sjz/config/TushareConfig.java
Normal file
37
src/main/java/com/sjz/config/TushareConfig.java
Normal file
@ -0,0 +1,37 @@
|
||||
package com.sjz.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import com.github.tusharepro.core.TusharePro;
|
||||
import com.github.tusharepro.core.TushareProService;
|
||||
|
||||
/**
|
||||
* Tushare配置类
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@Configuration
|
||||
public class TushareConfig {
|
||||
|
||||
@Value("${tushare.token}")
|
||||
private String tushareToken;
|
||||
|
||||
@Value("${tushare.api.url}")
|
||||
private String apiUrl;
|
||||
|
||||
/**
|
||||
* 初始化Tushare Pro全局配置
|
||||
*/
|
||||
@Bean
|
||||
public TusharePro initTusharePro() {
|
||||
TusharePro tusharePro = new TusharePro.Builder()
|
||||
.setToken(tushareToken)
|
||||
.build();
|
||||
|
||||
// 设置全局配置
|
||||
TusharePro.setGlobal(tusharePro);
|
||||
|
||||
return tusharePro;
|
||||
}
|
||||
}
|
||||
57
src/main/java/com/sjz/mapper/StockBasicMapper.java
Normal file
57
src/main/java/com/sjz/mapper/StockBasicMapper.java
Normal file
@ -0,0 +1,57 @@
|
||||
package com.sjz.mapper;
|
||||
|
||||
import com.sjz.model.StockBasic;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 股票基本信息Mapper接口
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@Mapper
|
||||
public interface StockBasicMapper {
|
||||
|
||||
/**
|
||||
* 批量插入股票基本信息
|
||||
*/
|
||||
@Insert("<script>" +
|
||||
"INSERT INTO stock_basic (ts_code, symbol, name, area, industry, market, list_date, create_time, update_time) " +
|
||||
"VALUES " +
|
||||
"<foreach collection='list' item='item' separator=','>" +
|
||||
"(#{item.tsCode}, #{item.symbol}, #{item.name}, #{item.area}, #{item.industry}, #{item.market}, #{item.listDate}, NOW(), NOW())" +
|
||||
"</foreach>" +
|
||||
" ON DUPLICATE KEY UPDATE " +
|
||||
"name = VALUES(name), area = VALUES(area), industry = VALUES(industry), " +
|
||||
"market = VALUES(market), list_date = VALUES(list_date), update_time = NOW()" +
|
||||
"</script>")
|
||||
int batchInsert(@Param("list") List<StockBasic> stockBasics);
|
||||
|
||||
/**
|
||||
* 根据TS代码查询股票基本信息
|
||||
*/
|
||||
@Select("SELECT ts_code, symbol, name, area, industry, market, list_date, create_time, update_time " +
|
||||
"FROM stock_basic WHERE ts_code = #{tsCode}")
|
||||
StockBasic selectByTsCode(@Param("tsCode") String tsCode);
|
||||
|
||||
/**
|
||||
* 查询所有股票基本信息
|
||||
*/
|
||||
@Select("SELECT ts_code, symbol, name, area, industry, market, list_date, create_time, update_time " +
|
||||
"FROM stock_basic ORDER BY ts_code")
|
||||
List<StockBasic> selectAll();
|
||||
|
||||
/**
|
||||
* 根据市场类型查询股票列表
|
||||
*/
|
||||
@Select("SELECT ts_code, symbol, name, area, industry, market, list_date, create_time, update_time " +
|
||||
"FROM stock_basic WHERE market = #{market} ORDER BY ts_code")
|
||||
List<StockBasic> selectByMarket(@Param("market") String market);
|
||||
|
||||
/**
|
||||
* 统计股票数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM stock_basic")
|
||||
int count();
|
||||
}
|
||||
68
src/main/java/com/sjz/mapper/StockDailyMapper.java
Normal file
68
src/main/java/com/sjz/mapper/StockDailyMapper.java
Normal file
@ -0,0 +1,68 @@
|
||||
package com.sjz.mapper;
|
||||
|
||||
import com.sjz.model.StockDaily;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 股票日线行情Mapper接口
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@Mapper
|
||||
public interface StockDailyMapper {
|
||||
|
||||
/**
|
||||
* 批量插入股票日线行情数据
|
||||
*/
|
||||
@Insert("<script>" +
|
||||
"INSERT INTO stock_daily (ts_code, trade_date, open, high, low, close, pre_close, change, pct_chg, vol, amount, create_time) " +
|
||||
"VALUES " +
|
||||
"<foreach collection='list' item='item' separator=','>" +
|
||||
"(#{item.tsCode}, #{item.tradeDate}, #{item.open}, #{item.high}, #{item.low}, #{item.close}, " +
|
||||
"#{item.preClose}, #{item.change}, #{item.pctChg}, #{item.vol}, #{item.amount}, NOW())" +
|
||||
"</foreach>" +
|
||||
" ON DUPLICATE KEY UPDATE " +
|
||||
"open = VALUES(open), high = VALUES(high), low = VALUES(low), close = VALUES(close), " +
|
||||
"pre_close = VALUES(pre_close), change = VALUES(change), pct_chg = VALUES(pct_chg), " +
|
||||
"vol = VALUES(vol), amount = VALUES(amount)" +
|
||||
"</script>")
|
||||
int batchInsert(@Param("list") List<StockDaily> stockDailies);
|
||||
|
||||
/**
|
||||
* 根据TS代码和交易日期查询
|
||||
*/
|
||||
@Select("SELECT ts_code, trade_date, open, high, low, close, pre_close, change, pct_chg, vol, amount, create_time " +
|
||||
"FROM stock_daily WHERE ts_code = #{tsCode} AND trade_date = #{tradeDate}")
|
||||
StockDaily selectByTsCodeAndDate(@Param("tsCode") String tsCode, @Param("tradeDate") String tradeDate);
|
||||
|
||||
/**
|
||||
* 根据TS代码查询指定日期范围内的数据
|
||||
*/
|
||||
@Select("SELECT ts_code, trade_date, open, high, low, close, pre_close, change, pct_chg, vol, amount, create_time " +
|
||||
"FROM stock_daily WHERE ts_code = #{tsCode} AND trade_date >= #{startDate} AND trade_date <= #{endDate} " +
|
||||
"ORDER BY trade_date")
|
||||
List<StockDaily> selectByTsCodeAndDateRange(@Param("tsCode") String tsCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/**
|
||||
* 根据交易日期查询所有股票数据
|
||||
*/
|
||||
@Select("SELECT ts_code, trade_date, open, high, low, close, pre_close, change, pct_chg, vol, amount, create_time " +
|
||||
"FROM stock_daily WHERE trade_date = #{tradeDate} ORDER BY ts_code")
|
||||
List<StockDaily> selectByTradeDate(@Param("tradeDate") String tradeDate);
|
||||
|
||||
/**
|
||||
* 获取指定股票的最新交易日期
|
||||
*/
|
||||
@Select("SELECT MAX(trade_date) FROM stock_daily WHERE ts_code = #{tsCode}")
|
||||
String selectLatestTradeDateByTsCode(@Param("tsCode") String tsCode);
|
||||
|
||||
/**
|
||||
* 删除指定日期之前的数据(数据清理)
|
||||
*/
|
||||
@Delete("DELETE FROM stock_daily WHERE trade_date < #{beforeDate}")
|
||||
int deleteBeforeDate(@Param("beforeDate") String beforeDate);
|
||||
}
|
||||
109
src/main/java/com/sjz/model/StockBasic.java
Normal file
109
src/main/java/com/sjz/model/StockBasic.java
Normal file
@ -0,0 +1,109 @@
|
||||
package com.sjz.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 股票基本信息模型
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
public class StockBasic {
|
||||
private String tsCode; // TS代码
|
||||
private String symbol; // 股票代码
|
||||
private String name; // 股票名称
|
||||
private String area; // 所在地域
|
||||
private String industry; // 所属行业
|
||||
private String market; // 市场类型
|
||||
private String listDate; // 上市日期
|
||||
private Date createTime; // 创建时间
|
||||
private Date updateTime; // 更新时间
|
||||
|
||||
// 构造函数
|
||||
public StockBasic() {}
|
||||
|
||||
// Getter和Setter方法
|
||||
public String getTsCode() {
|
||||
return tsCode;
|
||||
}
|
||||
|
||||
public void setTsCode(String tsCode) {
|
||||
this.tsCode = tsCode;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
public void setArea(String area) {
|
||||
this.area = area;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public String getMarket() {
|
||||
return market;
|
||||
}
|
||||
|
||||
public void setMarket(String market) {
|
||||
this.market = market;
|
||||
}
|
||||
|
||||
public String getListDate() {
|
||||
return listDate;
|
||||
}
|
||||
|
||||
public void setListDate(String listDate) {
|
||||
this.listDate = listDate;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(Date updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StockBasic{" +
|
||||
"tsCode='" + tsCode + '\'' +
|
||||
", symbol='" + symbol + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", area='" + area + '\'' +
|
||||
", industry='" + industry + '\'' +
|
||||
", market='" + market + '\'' +
|
||||
", listDate='" + listDate + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
137
src/main/java/com/sjz/model/StockDaily.java
Normal file
137
src/main/java/com/sjz/model/StockDaily.java
Normal file
@ -0,0 +1,137 @@
|
||||
package com.sjz.model;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 股票日线行情数据模型
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
public class StockDaily {
|
||||
private String tsCode; // TS代码
|
||||
private String tradeDate; // 交易日期
|
||||
private BigDecimal open; // 开盘价
|
||||
private BigDecimal high; // 最高价
|
||||
private BigDecimal low; // 最低价
|
||||
private BigDecimal close; // 收盘价
|
||||
private BigDecimal preClose;// 前收盘价
|
||||
private BigDecimal change; // 涨跌额
|
||||
private BigDecimal pctChg; // 涨跌幅
|
||||
private BigDecimal vol; // 成交量(手)
|
||||
private BigDecimal amount; // 成交额(千元)
|
||||
private Date createTime; // 创建时间
|
||||
|
||||
// 构造函数
|
||||
public StockDaily() {}
|
||||
|
||||
// Getter和Setter方法
|
||||
public String getTsCode() {
|
||||
return tsCode;
|
||||
}
|
||||
|
||||
public void setTsCode(String tsCode) {
|
||||
this.tsCode = tsCode;
|
||||
}
|
||||
|
||||
public String getTradeDate() {
|
||||
return tradeDate;
|
||||
}
|
||||
|
||||
public void setTradeDate(String tradeDate) {
|
||||
this.tradeDate = tradeDate;
|
||||
}
|
||||
|
||||
public BigDecimal getOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
public void setOpen(BigDecimal open) {
|
||||
this.open = open;
|
||||
}
|
||||
|
||||
public BigDecimal getHigh() {
|
||||
return high;
|
||||
}
|
||||
|
||||
public void setHigh(BigDecimal high) {
|
||||
this.high = high;
|
||||
}
|
||||
|
||||
public BigDecimal getLow() {
|
||||
return low;
|
||||
}
|
||||
|
||||
public void setLow(BigDecimal low) {
|
||||
this.low = low;
|
||||
}
|
||||
|
||||
public BigDecimal getClose() {
|
||||
return close;
|
||||
}
|
||||
|
||||
public void setClose(BigDecimal close) {
|
||||
this.close = close;
|
||||
}
|
||||
|
||||
public BigDecimal getPreClose() {
|
||||
return preClose;
|
||||
}
|
||||
|
||||
public void setPreClose(BigDecimal preClose) {
|
||||
this.preClose = preClose;
|
||||
}
|
||||
|
||||
public BigDecimal getChange() {
|
||||
return change;
|
||||
}
|
||||
|
||||
public void setChange(BigDecimal change) {
|
||||
this.change = change;
|
||||
}
|
||||
|
||||
public BigDecimal getPctChg() {
|
||||
return pctChg;
|
||||
}
|
||||
|
||||
public void setPctChg(BigDecimal pctChg) {
|
||||
this.pctChg = pctChg;
|
||||
}
|
||||
|
||||
public BigDecimal getVol() {
|
||||
return vol;
|
||||
}
|
||||
|
||||
public void setVol(BigDecimal vol) {
|
||||
this.vol = vol;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StockDaily{" +
|
||||
"tsCode='" + tsCode + '\'' +
|
||||
", tradeDate='" + tradeDate + '\'' +
|
||||
", open=" + open +
|
||||
", high=" + high +
|
||||
", low=" + low +
|
||||
", close=" + close +
|
||||
", pctChg=" + pctChg +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
106
src/main/java/com/sjz/service/StockBasicService.java
Normal file
106
src/main/java/com/sjz/service/StockBasicService.java
Normal file
@ -0,0 +1,106 @@
|
||||
package com.sjz.service;
|
||||
|
||||
import com.github.tusharepro.core.TushareProService;
|
||||
import com.github.tusharepro.core.entity.StockBasicEntity;
|
||||
import com.github.tusharepro.core.http.Request;
|
||||
import com.sjz.mapper.StockBasicMapper;
|
||||
import com.sjz.model.StockBasic;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 股票基本信息服务
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class StockBasicService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(StockBasicService.class);
|
||||
|
||||
@Autowired
|
||||
private StockBasicMapper stockBasicMapper;
|
||||
|
||||
// 注意:TushareProService现在是静态类,不需要注入
|
||||
|
||||
/**
|
||||
* 获取并保存股票基本信息
|
||||
*/
|
||||
public void fetchAndSaveStockBasic() {
|
||||
try {
|
||||
logger.info("开始获取股票基本信息...");
|
||||
|
||||
// 使用新的API调用方式获取股票基本信息
|
||||
List<StockBasicEntity> stockBasicEntities = TushareProService.stockBasic(
|
||||
new Request<StockBasicEntity>() {}
|
||||
.allFields()
|
||||
.param(com.github.tusharepro.core.bean.StockBasic.Params.list_status.value("L"))); // 只获取上市状态的股票
|
||||
|
||||
if (stockBasicEntities == null || stockBasicEntities.isEmpty()) {
|
||||
logger.warn("未获取到股票基本信息数据");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("成功获取到 {} 条股票基本信息", stockBasicEntities.size());
|
||||
|
||||
// 转换为本地实体类
|
||||
List<StockBasic> stockBasics = stockBasicEntities.stream()
|
||||
.map(entity -> {
|
||||
StockBasic stockBasic = new StockBasic();
|
||||
stockBasic.setTsCode(entity.getTsCode());
|
||||
stockBasic.setSymbol(entity.getSymbol());
|
||||
stockBasic.setName(entity.getName());
|
||||
stockBasic.setArea(entity.getArea());
|
||||
stockBasic.setIndustry(entity.getIndustry());
|
||||
stockBasic.setMarket(entity.getMarket());
|
||||
stockBasic.setListDate(entity.getListDate() != null ? entity.getListDate().toString() : null);
|
||||
return stockBasic;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 批量保存到数据库
|
||||
if (!stockBasics.isEmpty()) {
|
||||
int count = stockBasicMapper.batchInsert(stockBasics);
|
||||
logger.info("成功保存 {} 条股票基本信息", count);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("获取股票基本信息失败", e);
|
||||
throw new RuntimeException("获取股票基本信息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有股票基本信息
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<StockBasic> getAllStockBasic() {
|
||||
return stockBasicMapper.selectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据市场类型获取股票列表
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<StockBasic> getStockByMarket(String market) {
|
||||
return stockBasicMapper.selectByMarket(market);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据TS代码获取股票信息
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public StockBasic getStockByTsCode(String tsCode) {
|
||||
return stockBasicMapper.selectByTsCode(tsCode);
|
||||
}
|
||||
}
|
||||
192
src/main/java/com/sjz/service/StockDailyService.java
Normal file
192
src/main/java/com/sjz/service/StockDailyService.java
Normal file
@ -0,0 +1,192 @@
|
||||
package com.sjz.service;
|
||||
|
||||
import com.sjz.mapper.StockBasicMapper;
|
||||
import com.sjz.mapper.StockDailyMapper;
|
||||
import com.sjz.model.StockBasic;
|
||||
import com.sjz.model.StockDaily;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.github.tusharepro.core.TushareProService;
|
||||
import com.github.tusharepro.core.entity.DailyEntity;
|
||||
import com.github.tusharepro.core.http.Request;
|
||||
import com.github.tusharepro.core.bean.Daily;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 股票日线行情服务
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class StockDailyService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(StockDailyService.class);
|
||||
|
||||
@Autowired
|
||||
private StockDailyMapper stockDailyMapper;
|
||||
|
||||
@Autowired
|
||||
private StockBasicMapper stockBasicMapper;
|
||||
|
||||
// 注意:TushareProService现在是静态类,不需要注入
|
||||
|
||||
/**
|
||||
* 获取并保存股票日线行情数据
|
||||
*/
|
||||
public void fetchAndSaveStockDaily() {
|
||||
try {
|
||||
logger.info("开始获取股票日线行情数据...");
|
||||
|
||||
// 获取所有股票基本信息
|
||||
List<StockBasic> stockBasics = stockBasicMapper.selectAll();
|
||||
if (stockBasics.isEmpty()) {
|
||||
logger.warn("没有找到股票基本信息,请先执行股票基本信息获取任务");
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
String tradeDate = sdf.format(new Date()); // 获取当前日期作为交易日期
|
||||
|
||||
int totalCount = 0;
|
||||
int batchSize = 100; // 每批处理100只股票
|
||||
|
||||
// 分批处理股票
|
||||
for (int i = 0; i < stockBasics.size(); i += batchSize) {
|
||||
int endIndex = Math.min(i + batchSize, stockBasics.size());
|
||||
List<StockBasic> batchStocks = stockBasics.subList(i, endIndex);
|
||||
|
||||
List<StockDaily> stockDailies = fetchStockDailyData(batchStocks, tradeDate);
|
||||
|
||||
if (!stockDailies.isEmpty()) {
|
||||
int count = stockDailyMapper.batchInsert(stockDailies);
|
||||
totalCount += count;
|
||||
logger.info("批次 {}-{}: 保存了 {} 条日线数据", i, endIndex, count);
|
||||
}
|
||||
|
||||
// 避免请求过于频繁
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
logger.info("总共获取并保存了 {} 条股票日线行情数据", totalCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("获取股票日线行情数据失败", e);
|
||||
throw new RuntimeException("获取股票日线行情数据失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定股票列表的日线数据
|
||||
*/
|
||||
private List<StockDaily> fetchStockDailyData(List<StockBasic> stockBasics, String tradeDate) throws IOException {
|
||||
List<StockDaily> stockDailies = new ArrayList<>();
|
||||
|
||||
// 构建TS代码字符串
|
||||
StringBuilder tsCodes = new StringBuilder();
|
||||
for (int i = 0; i < stockBasics.size(); i++) {
|
||||
if (i > 0) {
|
||||
tsCodes.append(",");
|
||||
}
|
||||
tsCodes.append(stockBasics.get(i).getTsCode());
|
||||
}
|
||||
|
||||
// 使用新的API调用方式获取日线数据
|
||||
List<DailyEntity> dailyEntities = TushareProService.daily(
|
||||
new Request<DailyEntity>() {}
|
||||
.allFields()
|
||||
.param(Daily.Params.ts_code.value(tsCodes.toString()))
|
||||
.param(Daily.Params.trade_date.value(tradeDate)));
|
||||
|
||||
if (dailyEntities != null && !dailyEntities.isEmpty()) {
|
||||
stockDailies = dailyEntities.stream()
|
||||
.map(entity -> {
|
||||
StockDaily stockDaily = new StockDaily();
|
||||
stockDaily.setTsCode(entity.getTsCode());
|
||||
stockDaily.setTradeDate(entity.getTradeDate() != null ? entity.getTradeDate().toString() : null);
|
||||
|
||||
// 处理数值字段
|
||||
if (entity.getOpen() != null) {
|
||||
stockDaily.setOpen(BigDecimal.valueOf(entity.getOpen()));
|
||||
}
|
||||
if (entity.getHigh() != null) {
|
||||
stockDaily.setHigh(BigDecimal.valueOf(entity.getHigh()));
|
||||
}
|
||||
if (entity.getLow() != null) {
|
||||
stockDaily.setLow(BigDecimal.valueOf(entity.getLow()));
|
||||
}
|
||||
if (entity.getClose() != null) {
|
||||
stockDaily.setClose(BigDecimal.valueOf(entity.getClose()));
|
||||
}
|
||||
if (entity.getPreClose() != null) {
|
||||
stockDaily.setPreClose(BigDecimal.valueOf(entity.getPreClose()));
|
||||
}
|
||||
if (entity.getChange() != null) {
|
||||
stockDaily.setChange(BigDecimal.valueOf(entity.getChange()));
|
||||
}
|
||||
if (entity.getPctChg() != null) {
|
||||
stockDaily.setPctChg(BigDecimal.valueOf(entity.getPctChg()));
|
||||
}
|
||||
if (entity.getVol() != null) {
|
||||
stockDaily.setVol(BigDecimal.valueOf(entity.getVol()));
|
||||
}
|
||||
if (entity.getAmount() != null) {
|
||||
stockDaily.setAmount(BigDecimal.valueOf(entity.getAmount()));
|
||||
}
|
||||
|
||||
return stockDaily;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return stockDailies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定股票的日线数据
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<StockDaily> getStockDailyByTsCodeAndDateRange(String tsCode, String startDate, String endDate) {
|
||||
return stockDailyMapper.selectByTsCodeAndDateRange(tsCode, startDate, endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的所有股票日线数据
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<StockDaily> getStockDailyByTradeDate(String tradeDate) {
|
||||
return stockDailyMapper.selectByTradeDate(tradeDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定股票的最新交易日期
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public String getLatestTradeDateByTsCode(String tsCode) {
|
||||
return stockDailyMapper.selectLatestTradeDateByTsCode(tsCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理历史数据(保留指定天数的数据)
|
||||
*/
|
||||
@Transactional
|
||||
public int cleanHistoryData(int daysToKeep) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -daysToKeep);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
String beforeDate = sdf.format(calendar.getTime());
|
||||
|
||||
int count = stockDailyMapper.deleteBeforeDate(beforeDate);
|
||||
logger.info("清理了 {} 天前的 {} 条历史数据", daysToKeep, count);
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
118
src/main/java/com/sjz/task/StockDataScheduler.java
Normal file
118
src/main/java/com/sjz/task/StockDataScheduler.java
Normal file
@ -0,0 +1,118 @@
|
||||
package com.sjz.task;
|
||||
|
||||
import com.sjz.service.StockBasicService;
|
||||
import com.sjz.service.StockDailyService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 股票数据定时任务调度器
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
@Component
|
||||
@EnableScheduling
|
||||
public class StockDataScheduler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(StockDataScheduler.class);
|
||||
|
||||
@Autowired
|
||||
private StockBasicService stockBasicService;
|
||||
|
||||
@Autowired
|
||||
private StockDailyService stockDailyService;
|
||||
|
||||
@Value("${task.enabled:true}")
|
||||
private boolean taskEnabled;
|
||||
|
||||
/**
|
||||
* 每天凌晨1点获取股票基本信息(每周执行一次即可)
|
||||
*/
|
||||
@Scheduled(cron = "${task.cron.stock.basic:0 0 1 * * ?}")
|
||||
public void fetchStockBasic() {
|
||||
if (!taskEnabled) {
|
||||
logger.info("定时任务已禁用,跳过股票基本信息获取");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("开始执行股票基本信息获取定时任务...");
|
||||
try {
|
||||
stockBasicService.fetchAndSaveStockBasic();
|
||||
logger.info("股票基本信息获取定时任务执行完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("股票基本信息获取定时任务执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每个交易日下午3点30分获取股票日线行情数据
|
||||
*/
|
||||
@Scheduled(cron = "${task.cron.stock.daily:0 30 15 * * ?}")
|
||||
public void fetchStockDaily() {
|
||||
if (!taskEnabled) {
|
||||
logger.info("定时任务已禁用,跳过股票日线行情数据获取");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("开始执行股票日线行情数据获取定时任务...");
|
||||
try {
|
||||
stockDailyService.fetchAndSaveStockDaily();
|
||||
logger.info("股票日线行情数据获取定时任务执行完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("股票日线行情数据获取定时任务执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每周日凌晨2点清理历史数据(保留90天)
|
||||
*/
|
||||
@Scheduled(cron = "0 0 2 * * SUN")
|
||||
public void cleanHistoryData() {
|
||||
if (!taskEnabled) {
|
||||
logger.info("定时任务已禁用,跳过历史数据清理");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("开始执行历史数据清理定时任务...");
|
||||
try {
|
||||
stockDailyService.cleanHistoryData(90); // 保留90天的数据
|
||||
logger.info("历史数据清理定时任务执行完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("历史数据清理定时任务执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发股票基本信息获取(用于测试)
|
||||
*/
|
||||
public void triggerFetchStockBasic() {
|
||||
logger.info("手动触发股票基本信息获取任务...");
|
||||
fetchStockBasic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发股票日线行情数据获取(用于测试)
|
||||
*/
|
||||
public void triggerFetchStockDaily() {
|
||||
logger.info("手动触发股票日线行情数据获取任务...");
|
||||
fetchStockDaily();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发历史数据清理(用于测试)
|
||||
*/
|
||||
public void triggerCleanHistoryData(int daysToKeep) {
|
||||
logger.info("手动触发历史数据清理任务,保留 {} 天数据...", daysToKeep);
|
||||
try {
|
||||
stockDailyService.cleanHistoryData(daysToKeep);
|
||||
logger.info("手动历史数据清理任务执行完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("手动历史数据清理任务执行失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/main/java/com/sjz/util/ConfigUtil.java
Normal file
128
src/main/java/com/sjz/util/ConfigUtil.java
Normal file
@ -0,0 +1,128 @@
|
||||
package com.sjz.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 配置工具类
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
public class ConfigUtil {
|
||||
|
||||
private static final Properties properties = new Properties();
|
||||
|
||||
static {
|
||||
try (InputStream is = ConfigUtil.class.getClassLoader().getResourceAsStream("application.properties")) {
|
||||
if (is != null) {
|
||||
properties.load(is);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load application.properties", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置值
|
||||
*/
|
||||
public static String getProperty(String key) {
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public static String getProperty(String key, String defaultValue) {
|
||||
return properties.getProperty(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取整数配置值
|
||||
*/
|
||||
public static int getIntProperty(String key, int defaultValue) {
|
||||
String value = getProperty(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔配置值
|
||||
*/
|
||||
public static boolean getBooleanProperty(String key, boolean defaultValue) {
|
||||
String value = getProperty(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长整数配置值
|
||||
*/
|
||||
public static long getLongProperty(String key, long defaultValue) {
|
||||
String value = getProperty(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查必要的配置是否存在
|
||||
*/
|
||||
public static void validateRequiredProperties(String... requiredKeys) {
|
||||
for (String key : requiredKeys) {
|
||||
String value = getProperty(key);
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new RuntimeException("Required property '" + key + "' is missing or empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Tushare Token
|
||||
*/
|
||||
public static String getTushareToken() {
|
||||
String token = getProperty("tushare.token");
|
||||
if (token == null || token.equals("your_tushare_token_here")) {
|
||||
throw new RuntimeException("Tushare token is not configured properly");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库URL
|
||||
*/
|
||||
public static String getDatabaseUrl() {
|
||||
return getProperty("db.url");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库用户名
|
||||
*/
|
||||
public static String getDatabaseUsername() {
|
||||
return getProperty("db.username");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库密码
|
||||
*/
|
||||
public static String getDatabasePassword() {
|
||||
String password = getProperty("db.password");
|
||||
if (password == null || password.equals("your_password_here")) {
|
||||
throw new RuntimeException("Database password is not configured properly");
|
||||
}
|
||||
return password;
|
||||
}
|
||||
}
|
||||
124
src/main/java/com/sjz/util/DateUtil.java
Normal file
124
src/main/java/com/sjz/util/DateUtil.java
Normal file
@ -0,0 +1,124 @@
|
||||
package com.sjz.util;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 日期工具类
|
||||
*
|
||||
* @author sjz
|
||||
*/
|
||||
public class DateUtil {
|
||||
|
||||
public static final String DATE_FORMAT_YYYYMMDD = "yyyyMMdd";
|
||||
public static final String DATE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd";
|
||||
public static final String DATETIME_FORMAT_YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 获取当前日期字符串
|
||||
*/
|
||||
public static String getCurrentDateString(String format) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format);
|
||||
return sdf.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期字符串(yyyyMMdd格式)
|
||||
*/
|
||||
public static String getCurrentDateString() {
|
||||
return getCurrentDateString(DATE_FORMAT_YYYYMMDD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定天数前的日期字符串
|
||||
*/
|
||||
public static String getDateBefore(int days, String format) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -days);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format);
|
||||
return sdf.format(calendar.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定天数前的日期字符串(yyyyMMdd格式)
|
||||
*/
|
||||
public static String getDateBefore(int days) {
|
||||
return getDateBefore(days, DATE_FORMAT_YYYYMMDD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定天数后的日期字符串
|
||||
*/
|
||||
public static String getDateAfter(int days, String format) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, days);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format);
|
||||
return sdf.format(calendar.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定天数后的日期字符串(yyyyMMdd格式)
|
||||
*/
|
||||
public static String getDateAfter(int days) {
|
||||
return getDateAfter(days, DATE_FORMAT_YYYYMMDD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式转换
|
||||
*/
|
||||
public static String convertDateFormat(String date, String fromFormat, String toFormat) throws ParseException {
|
||||
SimpleDateFormat fromSdf = new SimpleDateFormat(fromFormat);
|
||||
SimpleDateFormat toSdf = new SimpleDateFormat(toFormat);
|
||||
Date parsedDate = fromSdf.parse(date);
|
||||
return toSdf.format(parsedDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为交易日(简单的周一到周五检查)
|
||||
* 注意:这里只是简单的判断,实际的交易日需要排除节假日
|
||||
*/
|
||||
public static boolean isTradeDay(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
|
||||
return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为交易日(简单的周一到周五检查)
|
||||
*/
|
||||
public static boolean isTradeDay(String dateString) throws ParseException {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_YYYYMMDD);
|
||||
Date date = sdf.parse(dateString);
|
||||
return isTradeDay(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的交易日(向前查找)
|
||||
*/
|
||||
public static String getRecentTradeDate() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int daysBack = 0;
|
||||
|
||||
do {
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -1);
|
||||
daysBack++;
|
||||
} while (daysBack <= 10 && !isTradeDay(calendar.getTime())); // 最多向前查找10天
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_YYYYMMDD);
|
||||
return sdf.format(calendar.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个日期之间的天数差
|
||||
*/
|
||||
public static int daysBetween(String startDate, String endDate, String format) throws ParseException {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format);
|
||||
Date start = sdf.parse(startDate);
|
||||
Date end = sdf.parse(endDate);
|
||||
long diff = end.getTime() - start.getTime();
|
||||
return (int) (diff / (24 * 60 * 60 * 1000));
|
||||
}
|
||||
}
|
||||
31
src/main/resources/application.properties
Normal file
31
src/main/resources/application.properties
Normal file
@ -0,0 +1,31 @@
|
||||
# Tushare配置
|
||||
tushare.token=2876ea85cb005fb5fa17c809a98174f2d5aae8b1f830110a5ead6211
|
||||
tushare.api.url=https://api.tushare.pro
|
||||
|
||||
# 数据库配置
|
||||
db.driver=com.mysql.cj.jdbc.Driver
|
||||
db.url=jdbc:mysql://fnv4.skdbj.email:15340/stock_data?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
|
||||
db.username=stock
|
||||
db.password=stock
|
||||
|
||||
# 连接池配置
|
||||
db.pool.maximumPoolSize=10
|
||||
db.pool.minimumIdle=5
|
||||
db.pool.idleTimeout=300000
|
||||
db.pool.connectionTimeout=20000
|
||||
db.pool.maxLifetime=1200000
|
||||
|
||||
# MyBatis配置
|
||||
mybatis.configLocation=mybatis-config.xml
|
||||
mybatis.mapperLocations=mapper/*.xml
|
||||
mybatis.typeAliasesPackage=com.sjz.model
|
||||
|
||||
# 定时任务配置
|
||||
task.enabled=true
|
||||
task.cron.stock.basic=0 0 1 * * ?
|
||||
task.cron.stock.daily=0 30 15 * * ?
|
||||
|
||||
# 日志配置
|
||||
logging.level.root=INFO
|
||||
logging.level.com.sjz=DEBUG
|
||||
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
|
||||
29
src/main/resources/mybatis-config.xml
Normal file
29
src/main/resources/mybatis-config.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名自动映射 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 开启二级缓存 -->
|
||||
<setting name="cacheEnabled" value="true"/>
|
||||
<!-- 设置超时时间 -->
|
||||
<setting name="defaultStatementTimeout" value="30"/>
|
||||
<!-- 设置获取数据的策略 -->
|
||||
<setting name="defaultFetchSize" value="100"/>
|
||||
<!-- 设置日志实现 -->
|
||||
<setting name="logImpl" value="SLF4J"/>
|
||||
</settings>
|
||||
|
||||
<typeAliases>
|
||||
<!-- 实体类别名包扫描 -->
|
||||
<package name="com.sjz.model"/>
|
||||
</typeAliases>
|
||||
|
||||
<typeHandlers>
|
||||
<!-- 类型处理器 -->
|
||||
</typeHandlers>
|
||||
|
||||
<plugins>
|
||||
<!-- 分页插件等 -->
|
||||
</plugins>
|
||||
</configuration>
|
||||
118
src/main/resources/sql/init.sql
Normal file
118
src/main/resources/sql/init.sql
Normal file
@ -0,0 +1,118 @@
|
||||
-- 股票数据抓取项目数据库初始化脚本
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS stock_data DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE stock_data;
|
||||
|
||||
-- 股票基本信息表
|
||||
CREATE TABLE IF NOT EXISTS stock_basic (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
ts_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'TS代码',
|
||||
symbol VARCHAR(20) NOT NULL COMMENT '股票代码',
|
||||
name VARCHAR(100) NOT NULL COMMENT '股票名称',
|
||||
area VARCHAR(50) COMMENT '所在地域',
|
||||
industry VARCHAR(100) COMMENT '所属行业',
|
||||
market VARCHAR(20) COMMENT '市场类型',
|
||||
list_date VARCHAR(20) COMMENT '上市日期',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX idx_ts_code (ts_code),
|
||||
INDEX idx_symbol (symbol),
|
||||
INDEX idx_market (market),
|
||||
INDEX idx_industry (industry)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='股票基本信息表';
|
||||
|
||||
-- 股票日线行情表
|
||||
CREATE TABLE IF NOT EXISTS stock_daily (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
ts_code VARCHAR(20) NOT NULL COMMENT 'TS代码',
|
||||
trade_date VARCHAR(20) NOT NULL COMMENT '交易日期',
|
||||
open DECIMAL(10,3) COMMENT '开盘价',
|
||||
high DECIMAL(10,3) COMMENT '最高价',
|
||||
low DECIMAL(10,3) COMMENT '最低价',
|
||||
close DECIMAL(10,3) COMMENT '收盘价',
|
||||
pre_close DECIMAL(10,3) COMMENT '前收盘价',
|
||||
change DECIMAL(10,3) COMMENT '涨跌额',
|
||||
pct_chg DECIMAL(8,3) COMMENT '涨跌幅(%)',
|
||||
vol DECIMAL(15,2) COMMENT '成交量(手)',
|
||||
amount DECIMAL(18,2) COMMENT '成交额(千元)',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UNIQUE KEY uk_ts_trade_date (ts_code, trade_date),
|
||||
INDEX idx_ts_code (ts_code),
|
||||
INDEX idx_trade_date (trade_date),
|
||||
INDEX idx_ts_trade_date (ts_code, trade_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='股票日线行情表';
|
||||
|
||||
-- 股票周线行情表
|
||||
CREATE TABLE IF NOT EXISTS stock_weekly (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
ts_code VARCHAR(20) NOT NULL COMMENT 'TS代码',
|
||||
trade_date VARCHAR(20) NOT NULL COMMENT '交易日期',
|
||||
open DECIMAL(10,3) COMMENT '开盘价',
|
||||
high DECIMAL(10,3) COMMENT '最高价',
|
||||
low DECIMAL(10,3) COMMENT '最低价',
|
||||
close DECIMAL(10,3) COMMENT '收盘价',
|
||||
pre_close DECIMAL(10,3) COMMENT '前收盘价',
|
||||
change DECIMAL(10,3) COMMENT '涨跌额',
|
||||
pct_chg DECIMAL(8,3) COMMENT '涨跌幅(%)',
|
||||
vol DECIMAL(15,2) COMMENT '成交量(手)',
|
||||
amount DECIMAL(18,2) COMMENT '成交额(千元)',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UNIQUE KEY uk_ts_trade_date (ts_code, trade_date),
|
||||
INDEX idx_ts_code (ts_code),
|
||||
INDEX idx_trade_date (trade_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='股票周线行情表';
|
||||
|
||||
-- 股票月线行情表
|
||||
CREATE TABLE IF NOT EXISTS stock_monthly (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
ts_code VARCHAR(20) NOT NULL COMMENT 'TS代码',
|
||||
trade_date VARCHAR(20) NOT NULL COMMENT '交易日期',
|
||||
open DECIMAL(10,3) COMMENT '开盘价',
|
||||
high DECIMAL(10,3) COMMENT '最高价',
|
||||
low DECIMAL(10,3) COMMENT '最低价',
|
||||
close DECIMAL(10,3) COMMENT '收盘价',
|
||||
pre_close DECIMAL(10,3) COMMENT '前收盘价',
|
||||
change DECIMAL(10,3) COMMENT '涨跌额',
|
||||
pct_chg DECIMAL(8,3) COMMENT '涨跌幅(%)',
|
||||
vol DECIMAL(15,2) COMMENT '成交量(手)',
|
||||
amount DECIMAL(18,2) COMMENT '成交额(千元)',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UNIQUE KEY uk_ts_trade_date (ts_code, trade_date),
|
||||
INDEX idx_ts_code (ts_code),
|
||||
INDEX idx_trade_date (trade_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='股票月线行情表';
|
||||
|
||||
-- 数据获取日志表
|
||||
CREATE TABLE IF NOT EXISTS fetch_log (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
fetch_type VARCHAR(50) NOT NULL COMMENT '获取类型(basic/daily/weekly/monthly)',
|
||||
fetch_date VARCHAR(20) NOT NULL COMMENT '获取日期',
|
||||
start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '开始时间',
|
||||
end_time TIMESTAMP NULL COMMENT '结束时间',
|
||||
status VARCHAR(20) NOT NULL COMMENT '状态(running/success/failed)',
|
||||
total_count INT DEFAULT 0 COMMENT '总记录数',
|
||||
success_count INT DEFAULT 0 COMMENT '成功记录数',
|
||||
error_count INT DEFAULT 0 COMMENT '失败记录数',
|
||||
error_message TEXT COMMENT '错误信息',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
INDEX idx_fetch_type (fetch_type),
|
||||
INDEX idx_fetch_date (fetch_date),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='数据获取日志表';
|
||||
|
||||
-- 插入示例数据(可选)
|
||||
-- INSERT INTO stock_basic (ts_code, symbol, name, area, industry, market, list_date) VALUES
|
||||
-- ('000001.SZ', '000001', '平安银行', '深圳', '银行', '主板', '19910403'),
|
||||
-- ('000002.SZ', '000002', '万科A', '深圳', '房地产', '主板', '19910129');
|
||||
|
||||
-- 创建索引以优化查询性能
|
||||
-- 为日线表添加复合索引
|
||||
CREATE INDEX idx_daily_ts_trade_date ON stock_daily(ts_code, trade_date);
|
||||
CREATE INDEX idx_daily_trade_date_ts ON stock_daily(trade_date, ts_code);
|
||||
|
||||
-- 为基本信息表添加全文索引(如果需要搜索股票名称)
|
||||
-- ALTER TABLE stock_basic ADD FULLTEXT INDEX ft_name (name);
|
||||
|
||||
-- 显示表结构
|
||||
SHOW TABLES;
|
||||
38
src/test/java/com/sjz/AppTest.java
Normal file
38
src/test/java/com/sjz/AppTest.java
Normal file
@ -0,0 +1,38 @@
|
||||
package com.sjz;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class AppTest
|
||||
extends TestCase
|
||||
{
|
||||
/**
|
||||
* Create the test case
|
||||
*
|
||||
* @param testName name of the test case
|
||||
*/
|
||||
public AppTest( String testName )
|
||||
{
|
||||
super( testName );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the suite of tests being tested
|
||||
*/
|
||||
public static Test suite()
|
||||
{
|
||||
return new TestSuite( AppTest.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rigourous Test :-)
|
||||
*/
|
||||
public void testApp()
|
||||
{
|
||||
assertTrue( true );
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user