Compare commits

6 Commits

Author SHA1 Message Date
vu-tran
5aef88988e update orders index 2025-08-11 18:44:47 +07:00
vu-tran
02d7b9d027 Merge branch 'develop_red' of gitlab.main99.com:germany/ger-market-java into develop_red 2025-08-11 18:36:23 +07:00
vu-tran
d1fb4bded8 update index 2025-08-11 18:32:16 +07:00
dd5eeede7d feat: add support for fetching recent trading day chart data and optimize caching for index retrieval 2025-08-11 07:29:26 +08:00
vu-tran
f3dd600189 update time for response news 2025-08-11 00:36:45 +07:00
vu-tran
7caaccb580 update get news 2025-08-05 15:51:59 +07:00
7 changed files with 186 additions and 62 deletions

View File

@@ -20,6 +20,12 @@ import cn.stock.market.domain.basic.entity.SiteSetting;
import cn.stock.market.domain.basic.repository.SiteSettingRepository; import cn.stock.market.domain.basic.repository.SiteSettingRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.TimeZone;
@SpringBootApplication(exclude = { @SpringBootApplication(exclude = {
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
RedisAutoConfiguration.class, RedisAutoConfiguration.class,
@@ -31,6 +37,11 @@ public class StockMarketLaunch implements CommandLineRunner {
public static void main(String[] args) { public static void main(String[] args) {
HttpGlobalConfig.setTimeout(45000); HttpGlobalConfig.setTimeout(45000);
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Madrid"));
log.info("JVM ZoneId: {}", ZoneId.systemDefault());
log.info("Sample: now={}, madrid={}",
new Date(),
ZonedDateTime.now(ZoneId.of("Europe/Madrid")));
SpringApplication.run(StockMarketLaunch.class, args); SpringApplication.run(StockMarketLaunch.class, args);
} }

View File

@@ -1063,10 +1063,25 @@ public class StockService {
market.setRate(String.valueOf(stockIndex.getPercentChange())); market.setRate(String.valueOf(stockIndex.getPercentChange()));
vo1.setIndexVo(market); vo1.setIndexVo(market);
List<ChartCandle> kLines = HomeApiIndex.fetchChartData(stockIndex.getId(), 419); // 只获取最近交易日的K线数据智能跳过周末优化性能和网络传输
// List kline = HomeApiIndex.convertToJsonList(kLines); List<ChartCandle> kLines = HomeApiIndex.fetchTodayAndYesterdayChartData(stockIndex.getId());
vo1.setKLine(kLines); vo1.setKLine(kLines);
indexVoList.add(vo1); indexVoList.add(vo1);
List<String> order = Arrays.asList(
"IBEX 35 Index",
"DAX Index",
"Dow Jones Industrial Average",
"S&P 500 Index"
);
Map<String, Integer> orderMap = new HashMap<>();
for (int i = 0; i < order.size(); i++) {
orderMap.put(order.get(i), i);
}
indexVoList.sort(Comparator.comparingInt(o -> orderMap.getOrDefault(o.getIndexVo().getName(), Integer.MAX_VALUE)));
} }
return ServerResponse.createBySuccess(indexVoList); return ServerResponse.createBySuccess(indexVoList);

View File

@@ -9,17 +9,21 @@ import okhttp3.Response;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.time.DayOfWeek;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
public class HomeApiIndex { public class HomeApiIndex {
private static final OkHttpClient client = new OkHttpClient(); private static final OkHttpClient client = new OkHttpClient();
static Config config = SpringUtils.getBean(Config.class); static Config config = SpringUtils.getBean(Config.class);
private static final String API_URL = config.getStockUrlPrefix() + "/api/ger-market/stocks/query-list?symbols=DAX,MDAX,SDXP,HDAX"; private static final String API_URL = config.getStockUrlPrefix() + "/api/ger-market/stocks/query-list?symbols=IBC:BME,DAX,^DJI:NASDAQ,^SPX:NASDAQ";
private static final String BASE_URL = config.getStockUrlPrefix() + "/api/ger-market/chart"; private static final String BASE_URL = config.getStockUrlPrefix() + "/api/ger-market/chart";
public static List<StockIndex> fetchStockIndices() throws Exception { public static List<StockIndex> fetchStockIndices() throws Exception {
@@ -51,10 +55,12 @@ public class HomeApiIndex {
index.setName("DAX Index"); index.setName("DAX Index");
}else if (index.getSymbol().equals("MDAX")) { }else if (index.getSymbol().equals("MDAX")) {
index.setName("MDAX Index"); index.setName("MDAX Index");
}else if (index.getSymbol().equals("HDAX")) { }else if (index.getSymbol().equals("IBC:BME")) {
index.setName("HDAX PERFORMANCE-INDEX"); index.setName("IBEX 35 Index");
} else if (index.getSymbol().equals("SDXP")) { } else if (index.getSymbol().equals("SDXP")) {
index.setName("SDAX Index"); index.setName("SDAX Index");
}else {
index.setName(obj.optString("name"));
} }
index.setExchange(obj.optString("exchange")); index.setExchange(obj.optString("exchange"));
index.setMicCode(obj.optString("mic_code")); index.setMicCode(obj.optString("mic_code"));
@@ -80,10 +86,32 @@ public class HomeApiIndex {
return result; return result;
} }
/**
* 获取图表数据(原方法,保持向后兼容)
*/
public static List<ChartCandle> fetchChartData(String symbol, int amount) throws Exception { public static List<ChartCandle> fetchChartData(String symbol, int amount) throws Exception {
return fetchChartDataWithDateFilter(symbol, amount, false);
}
/**
* 获取最近的交易日K线数据支持跨周末
* @param symbol 股票代码
* @return 最近交易日的K线数据
*/
public static List<ChartCandle> fetchTodayAndYesterdayChartData(String symbol) throws Exception {
return fetchChartDataWithDateFilter(symbol, 168, true); // 获取7天数据确保包含交易日
}
/**
* 获取图表数据,支持日期过滤
* @param symbol 股票代码
* @param amount 数据量
* @param filterRecentDays 是否只返回最近交易日的数据
*/
private static List<ChartCandle> fetchChartDataWithDateFilter(String symbol, int amount, boolean filterRecentDays) throws Exception {
List<ChartCandle> result = new ArrayList<>(); List<ChartCandle> result = new ArrayList<>();
String url = BASE_URL + "?symbol=" + symbol + "&interval=D&amount=" + amount; String url = BASE_URL + "?symbol=" + symbol + "&interval=H&amount=" + amount;
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
@@ -101,22 +129,72 @@ public class HomeApiIndex {
JSONObject json = new JSONObject(body); JSONObject json = new JSONObject(body);
JSONArray data = json.getJSONArray("data"); JSONArray data = json.getJSONArray("data");
for (int i = 0; i < data.length(); i++) { if (filterRecentDays) {
JSONObject obj = data.getJSONObject(i); // 获取最近的交易日期
ChartCandle candle = new ChartCandle(); Set<LocalDate> recentTradingDays = getRecentTradingDays();
long ts = obj.optLong("time"); for (int i = 0; i < data.length(); i++) {
String formattedTime = convertToGermanTime(ts); JSONObject obj = data.getJSONObject(i);
candle.setUpd_date(formattedTime); long ts = obj.optLong("time");
candle.setPrice(getDoubleOrNull(obj, "close")); ZonedDateTime zonedDateTime = Instant.ofEpochSecond(ts).atZone(ZoneId.of("Europe/Berlin"));
LocalDate dataDate = zonedDateTime.toLocalDate();
result.add(candle);
// 只保留最近交易日的数据
if (recentTradingDays.contains(dataDate)) {
ChartCandle candle = new ChartCandle();
String formattedTime = convertToGermanTime(ts);
candle.setUpd_date(formattedTime);
candle.setPrice(getDoubleOrNull(obj, "close"));
result.add(candle);
}
}
} else {
// 不过滤,返回所有数据
for (int i = 0; i < data.length(); i++) {
JSONObject obj = data.getJSONObject(i);
long ts = obj.optLong("time");
String formattedTime = convertToGermanTime(ts);
ChartCandle candle = new ChartCandle();
candle.setUpd_date(formattedTime);
candle.setPrice(getDoubleOrNull(obj, "close"));
result.add(candle);
}
} }
return result; return result;
} }
/**
* 获取最近的交易日期(排除周末)
* @return 最近两个交易日的日期集合
*/
public static Set<LocalDate> getRecentTradingDays() {
Set<LocalDate> tradingDays = new HashSet<>();
ZoneId germanyZone = ZoneId.of("Europe/Berlin");
LocalDate current = LocalDate.now(germanyZone);
int foundDays = 0;
int daysBack = 0;
// 向前查找最近的两个交易日
while (foundDays < 2 && daysBack < 10) { // 最多向前查找10天
LocalDate checkDate = current.minusDays(daysBack);
// 排除周末(周六=6, 周日=7
if (checkDate.getDayOfWeek() != DayOfWeek.SATURDAY &&
checkDate.getDayOfWeek() != DayOfWeek.SUNDAY) {
tradingDays.add(checkDate);
foundDays++;
}
daysBack++;
}
return tradingDays;
}
// public static List<JSONObject> convertToJsonList(List<ChartCandle> candles) { // public static List<JSONObject> convertToJsonList(List<ChartCandle> candles) {
// List<JSONObject> result = new ArrayList<>(); // List<JSONObject> result = new ArrayList<>();
// //

View File

@@ -8,6 +8,7 @@ import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.DynamicUpdate;
@@ -75,6 +76,7 @@ public class SiteNewsPO {
/** /**
* 显示时间 */ * 显示时间 */
@ApiModelProperty("显示时间") @ApiModelProperty("显示时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Europe/Madrid")
Date showTime; Date showTime;
/** /**

View File

@@ -36,6 +36,7 @@ import javax.annotation.PostConstruct;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -311,84 +312,97 @@ public class InvestingTask {
@Scheduled(cron = "0 0 0/3 * * ?") @Scheduled(cron = "0 0 0/3 * * ?")
// @PostConstruct // @PostConstruct
public void getBoerseNews(){ public void getCincoDiasNews() {
String url_request = "https://www.boerse-online.de"; String baseUrl = "https://cincodias.elpais.com";
try { try {
List<SiteNews> results = new ArrayList<>(); List<SiteNews> results = new ArrayList<>();
String listUrl = baseUrl + "/ultimas-noticias/";
String listUrl = url_request + "/nachrichten/1";
Document doc = Jsoup.connect(listUrl) Document doc = Jsoup.connect(listUrl)
.userAgent("Mozilla/5.0") .userAgent("Mozilla/5.0")
.get(); .get();
Elements articles = doc.select("article.article-list-item"); Elements articles = doc.select("article.c");
for (Element article : articles) { for (Element article : articles) {
Element aTag = article.selectFirst("h2 a"); // Title and Link
Element aTag = article.selectFirst("h2.c_t a");
String title = aTag != null ? aTag.text().trim() : null; String title = aTag != null ? aTag.text().trim() : null;
String link = aTag != null ? url_request + aTag.attr("href") : null; String link = aTag != null ? aTag.absUrl("href") : null;
Element imgTag = article.selectFirst("figure a picture img"); // Author
String image = imgTag != null ? imgTag.attr("src") : null; Element authorTag = article.selectFirst("a.c_a_a");
Element timeTag = article.selectFirst("small.article-info time");
Date publishedDate = null;
if (timeTag != null) {
String datetimeAttr = timeTag.attr("datetime");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(datetimeAttr, formatter);
ZoneId berlinZone = ZoneId.of("Europe/Berlin");
publishedDate = Date.from(dateTime.atZone(berlinZone).toInstant());
}
Element authorTag = article.selectFirst("small.article-info strong");
String author = authorTag != null ? authorTag.text().trim() : null; String author = authorTag != null ? authorTag.text().trim() : null;
// Fetch article detail page Element figure = article.selectFirst("figure.c_m a img");
String imageUrl = null;
if (figure != null) {
imageUrl = figure.attr("src");
}
// Date
Date publishedDate = null;
try {
Element timeTag = article.selectFirst("time");
if (timeTag != null) {
String datetimeAttr = timeTag.attr("datetime");
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
ZonedDateTime zonedDateTime = ZonedDateTime.parse(datetimeAttr, formatter);
publishedDate = Date.from(zonedDateTime.toInstant());
}
} catch (Exception e) {
log.warn("Failed to parse published date for article: {}", link);
}
// Summary
String summary = article.selectFirst("p.c_d") != null ? article.selectFirst("p.c_d").text() : null;
// Optional: Get full content from article detail page
String htmlContent = ""; String htmlContent = "";
if (link != null) { if (link != null) {
try { try {
Document detailPage = Jsoup.connect(link) Document detailDoc = Jsoup.connect(link)
.userAgent("Mozilla/5.0") .userAgent("Mozilla/5.0")
.get(); .get();
Element body = detailPage.selectFirst("div.article-body"); // ✅ Extract article main content
Element body = detailDoc.selectFirst("div.a_c.clearfix[data-dtm-region=articulo_cuerpo]");
if (body != null) { if (body != null) {
htmlContent = body.html(); // ✅ inner HTML only htmlContent = body.html();
} }
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error fetching article detail: " + link); log.warn("Error fetching detail page: {}", link);
e.printStackTrace(); e.printStackTrace();
} }
} }
// Build SiteNews object
SiteNews siteNews = new SiteNews(); SiteNews siteNews = new SiteNews();
siteNews.setAddTime(new Date()); siteNews.setAddTime(new Date());
siteNews.setSourceId(link); siteNews.setSourceId(link);
siteNews.setTitle(title); siteNews.setTitle(title);
siteNews.setSourceName("BOERSE"); siteNews.setSourceName("CINCO_DIAS");
siteNews.setDescription(title); siteNews.setDescription(summary != null ? summary : title);
siteNews.setImgurl(image); siteNews.setImgurl(imageUrl);
siteNews.setContent(htmlContent); siteNews.setContent(htmlContent);
siteNews.setStatus(1); siteNews.setStatus(1);
siteNews.setType(1); // Set as financial news type siteNews.setType(1);
siteNews.setViews(0); siteNews.setViews(0);
siteNews.setShowTime(publishedDate); siteNews.setShowTime(publishedDate);
try { try {
newsRepository.save(siteNews); newsRepository.save(siteNews);
log.info("Saved German news : {}", title); log.info("Saved Spanish news: {}", title);
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to save German news {}: {}", link, e.getMessage()); log.warn("Failed to save Spanish news {}: {}", link, e.getMessage());
} }
} }
}catch (Exception e){
log.error("Error fetching article detail: {}", e.getMessage()); } catch (Exception e) {
log.error("Error fetching Spanish news: {}", e.getMessage());
e.printStackTrace(); e.printStackTrace();
} }
} }
/** /**

View File

@@ -244,15 +244,18 @@ public class StockApiController {
} }
@RequestMapping({"getIndiaIndexByToday.do"}) @RequestMapping({"getIndiaIndexByToday.do"})
@ApiOperation(value = "印度--获取指定指数信息", httpMethod = "GET") @ApiOperation(value = "德国--获取指定指数信息最近交易日K线", httpMethod = "GET")
@ResponseBody @ResponseBody
public ServerResponse getIndiaIndexByToday() { public ServerResponse getIndiaIndexByToday() {
String INDEX_CODE = "TODAY_INDEX"; String INDEX_CODE = "TODAY_INDEX";
return RequestCacheUtils.cache("getIndiaIndexByToday.do", INDEX_CODE,6000, (string) -> { // 优化缓存时间为15秒平衡数据实时性和系统性能
// 由于只返回最近交易日数据,可以适当延长缓存时间
return RequestCacheUtils.cache("getIndiaIndexByToday.do", INDEX_CODE, 15000, (string) -> {
try { try {
return this.stockService.getIndexByBtoday(); return this.stockService.getIndexByBtoday();
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); log.error("获取德国指数数据失败: {}", e.getMessage(), e);
throw new RuntimeException("获取指数数据失败: " + e.getMessage(), e);
} }
}); });
} }

View File

@@ -1,11 +1,12 @@
spring: spring:
jpa: jpa:
show-sql: true show-sql: true
# Redis配置 # Redis配置
redis: redis:
host: 43.160.197.177 host: 167.235.39.59
password: redispass123 password: a5v8b86P4mVzFlUqJV
port: 6379 port: 47379
database: 1 database: 1
lettuce: lettuce:
pool: pool:
@@ -17,9 +18,9 @@ spring:
datasource: datasource:
stock-market: stock-market:
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://43.160.197.177:3306/germany_stock?useUnicode=true&characterEncoding=utf-8 url: jdbc:mysql://de-cdb-pj5o5m8b.sql.tencentcdb.com:24110/stock-api?useUnicode=true&characterEncoding=utf-8
username: root username: root
password: mysqlpass123 password: 6QJXv8dA76klnqsWh6f
maxActive: 500 maxActive: 500
testWhileIdle: true testWhileIdle: true
validationQuery: SELECT 1 validationQuery: SELECT 1