feat: fix frontend

This commit is contained in:
2025-06-21 16:55:45 +08:00
parent b52a9befee
commit 7cf5070288
17 changed files with 3071 additions and 393 deletions

View File

@@ -0,0 +1,19 @@
2025-06-21 15:10:59 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Starting AgriculturalStockPlatformApplication using Java 17.0.10 on WIN11 with PID 11416 (D:\VScodeProject\work_4\backend\target\classes started by shenjianZ in D:\VScodeProject\work_4\backend)
2025-06-21 15:10:59 [main] INFO c.a.s.AgriculturalStockPlatformApplication - No active profile set, falling back to 1 default profile: "default"
2025-06-21 15:11:00 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2025-06-21 15:11:00 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.63]
2025-06-21 15:11:00 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2025-06-21 15:11:01 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
2025-06-21 15:11:01 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.9.Final
2025-06-21 15:11:01 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-06-21 15:11:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2025-06-21 15:11:01 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2025-06-21 15:11:01 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-06-21 15:11:02 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-06-21 15:11:03 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-06-21 15:11:04 [main] WARN c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.agricultural.stock.entity.TechnicalIndicator".
2025-06-21 15:11:04 [main] WARN c.b.m.c.injector.DefaultSqlInjector - class com.agricultural.stock.entity.TechnicalIndicator ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
2025-06-21 15:11:04 [main] INFO c.a.s.AgriculturalStockPlatformApplication - Started AgriculturalStockPlatformApplication in 5.394 seconds (JVM running for 5.767)
2025-06-21 15:11:08 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-21 15:14:01 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2025-06-21 15:14:01 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

View File

@@ -7,7 +7,9 @@ import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springdoc.core.GroupedOpenApi;
import java.util.Arrays;
import java.util.List;
/**
@@ -40,7 +42,7 @@ public class OpenApiConfig {
.license(new License()
.name("MIT License")
.url("https://opensource.org/licenses/MIT")))
.servers(List.of(
.servers(Arrays.asList(
new Server()
.url("http://localhost:8080")
.description("本地开发环境"),
@@ -49,4 +51,28 @@ public class OpenApiConfig {
.description("生产环境")
));
}
@Bean
public GroupedOpenApi allApi() {
return GroupedOpenApi.builder()
.group("all-apis")
.pathsToMatch("/**")
.build();
}
@Bean
public GroupedOpenApi stockApi() {
return GroupedOpenApi.builder()
.group("stock-apis")
.pathsToMatch("/api/stock/**")
.build();
}
@Bean
public GroupedOpenApi marketApi() {
return GroupedOpenApi.builder()
.group("market-apis")
.pathsToMatch("/api/market/**")
.build();
}
}

View File

@@ -167,6 +167,26 @@ public class StockController {
}
}
/**
* 获取股票详情
*/
@GetMapping("/detail/{stockCode}")
@Operation(summary = "获取股票详情")
public Result<StockData> getStockDetail(
@Parameter(description = "股票代码") @PathVariable String stockCode) {
try {
StockData stockDetail = stockService.getStockDetail(stockCode);
if (stockDetail != null) {
return Result.success(stockDetail);
} else {
return Result.error("未找到该股票的详情数据");
}
} catch (Exception e) {
log.error("获取股票{}详情失败", stockCode, e);
return Result.error("获取股票详情失败: " + e.getMessage());
}
}
/**
* 搜索股票
*/

View File

@@ -79,6 +79,14 @@ public interface StockService {
* @return 预测数据列表
*/
List<StockData> getStockPrediction(String stockCode, Integer days);
/**
* 获取股票详情
*
* @param stockCode 股票代码
* @return 股票详情
*/
StockData getStockDetail(String stockCode);
/**
* 搜索股票

View File

@@ -251,9 +251,174 @@ public class StockServiceImpl implements StockService {
@Override
public List<StockData> getStockPrediction(String stockCode, Integer days) {
// 这里应该调用预测模型,暂时返回空列表
log.info("股票预测功能暂未实现,股票代码: {}, 预测天数: {}", stockCode, days);
return new ArrayList<>();
try {
log.info("生成股票预测数据,股票代码: {}, 预测天数: {}", stockCode, days);
// 获取历史数据用于预测
List<StockData> historyData = stockDataMapper.getStockHistoryData(stockCode, 30);
if (historyData.isEmpty()) {
log.warn("未找到股票{}的历史数据,无法生成预测", stockCode);
return new ArrayList<>();
}
// 获取最新数据作为预测基础
StockData latestData = historyData.get(0); // getStockHistoryData返回的是按时间降序排列
// 计算历史价格变化趋势
BigDecimal avgChange = calculateAverageChange(historyData);
BigDecimal volatility = calculateVolatility(historyData);
List<StockData> predictionList = new ArrayList<>();
// 生成预测数据
BigDecimal currentPrice = latestData.getClosePrice();
LocalDateTime currentDate = LocalDateTime.now();
for (int i = 1; i <= days; i++) {
StockData prediction = new StockData();
// 基本信息
prediction.setStockCode(stockCode);
prediction.setStockName(latestData.getStockName());
prediction.setTradeDate(currentDate.plusDays(i));
// 简单预测模型:基于历史平均变化和随机波动
BigDecimal randomFactor = BigDecimal.valueOf((Math.random() - 0.5) * 2 * volatility.doubleValue());
BigDecimal predictedChange = avgChange.add(randomFactor);
// 计算预测价格
BigDecimal predictedPrice = currentPrice.multiply(
BigDecimal.ONE.add(predictedChange.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP))
);
prediction.setOpenPrice(currentPrice);
prediction.setHighPrice(predictedPrice.multiply(BigDecimal.valueOf(1.05)));
prediction.setLowPrice(predictedPrice.multiply(BigDecimal.valueOf(0.95)));
prediction.setClosePrice(predictedPrice);
// 计算涨跌幅
BigDecimal changeAmount = predictedPrice.subtract(currentPrice);
BigDecimal changePercent = changeAmount.divide(currentPrice, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
prediction.setChangeAmount(changeAmount);
prediction.setChangePercent(changePercent);
// 预测成交量(基于历史平均值)
double avgVolume = historyData.stream()
.map(StockData::getVolume)
.filter(Objects::nonNull)
.mapToLong(Long::longValue)
.average()
.orElse(100000L);
prediction.setVolume(Math.round(avgVolume * (0.8 + Math.random() * 0.4)));
// 预测成交额
if (prediction.getVolume() != null) {
prediction.setTurnover(predictedPrice.multiply(BigDecimal.valueOf(prediction.getVolume())));
}
// 市值
if (latestData.getMarketCap() != null && latestData.getClosePrice() != null && latestData.getClosePrice().compareTo(BigDecimal.ZERO) != 0) {
prediction.setMarketCap(latestData.getMarketCap()
.multiply(predictedPrice)
.divide(latestData.getClosePrice(), 2, RoundingMode.HALF_UP));
}
predictionList.add(prediction);
currentPrice = predictedPrice; // 更新当前价格为下一天的基础
}
return predictionList;
} catch (Exception e) {
log.error("生成股票{}预测数据失败", stockCode, e);
return new ArrayList<>();
}
}
/**
* 计算历史价格平均变化率
*/
private BigDecimal calculateAverageChange(List<StockData> historyData) {
if (historyData.size() < 2) {
return BigDecimal.ZERO;
}
BigDecimal totalChange = BigDecimal.ZERO;
int count = 0;
// 数据是按时间降序排列的,所以要反过来计算
for (int i = historyData.size() - 1; i > 0; i--) {
StockData current = historyData.get(i - 1); // 更新的数据
StockData previous = historyData.get(i); // 更早的数据
if (current.getClosePrice() != null && previous.getClosePrice() != null && previous.getClosePrice().compareTo(BigDecimal.ZERO) != 0) {
BigDecimal change = current.getClosePrice().subtract(previous.getClosePrice())
.divide(previous.getClosePrice(), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
totalChange = totalChange.add(change);
count++;
}
}
return count > 0 ? totalChange.divide(BigDecimal.valueOf(count), 4, RoundingMode.HALF_UP) : BigDecimal.ZERO;
}
/**
* 计算价格波动率
*/
private BigDecimal calculateVolatility(List<StockData> historyData) {
if (historyData.size() < 2) {
return BigDecimal.valueOf(2.0); // 默认波动率
}
List<BigDecimal> changes = new ArrayList<>();
// 数据是按时间降序排列的,所以要反过来计算
for (int i = historyData.size() - 1; i > 0; i--) {
StockData current = historyData.get(i - 1); // 更新的数据
StockData previous = historyData.get(i); // 更早的数据
if (current.getClosePrice() != null && previous.getClosePrice() != null && previous.getClosePrice().compareTo(BigDecimal.ZERO) != 0) {
BigDecimal change = current.getClosePrice().subtract(previous.getClosePrice())
.divide(previous.getClosePrice(), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
changes.add(change);
}
}
if (changes.isEmpty()) {
return BigDecimal.valueOf(2.0);
}
// 计算标准差
BigDecimal mean = changes.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(changes.size()), 4, RoundingMode.HALF_UP);
BigDecimal variance = changes.stream()
.map(change -> change.subtract(mean).pow(2))
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(changes.size()), 4, RoundingMode.HALF_UP);
return BigDecimal.valueOf(Math.sqrt(variance.doubleValue()));
}
@Override
public StockData getStockDetail(String stockCode) {
try {
List<StockData> stockList = stockDataMapper.getLatestStockData();
return stockList.stream()
.filter(stock -> stock.getStockCode().equals(stockCode))
.findFirst()
.orElse(null);
} catch (Exception e) {
log.error("获取股票{}详情失败", stockCode, e);
return null;
}
}
@Override

View File

@@ -71,7 +71,9 @@ springdoc:
path: /swagger-ui
enabled: true
config-url: /v3/api-docs/swagger-config
urls-primary-name: default
disable-swagger-default-url: true
url: /v3/api-docs
urls-primary-name: all-apis
packages-to-scan: com.agricultural.stock.controller
paths-to-match: /api/**