| | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | | import java.util.concurrent.CompletableFuture; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | |
| | | |
| | | /*股票日线-K线*/ |
| | | // 只需要缓存一个最近返回的Object |
| | | private static Object lastResult = null; |
| | | private static long lastExecuteTime = 0; |
| | | // 缓存键类,用于组合多个参数 |
| | | private static class CacheKey { |
| | | private final String pid; |
| | | private final String interval; |
| | | private final String stockType; |
| | | |
| | | public CacheKey(String pid, String interval, String stockType) { |
| | | this.pid = pid; |
| | | this.interval = interval; |
| | | this.stockType = stockType; |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object o) { |
| | | if (this == o) return true; |
| | | if (o == null || getClass() != o.getClass()) return false; |
| | | CacheKey cacheKey = (CacheKey) o; |
| | | return pid.equals(cacheKey.pid) && |
| | | interval.equals(cacheKey.interval) && |
| | | stockType.equals(cacheKey.stockType); |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return 31 * pid.hashCode() + 13 * interval.hashCode() + stockType.hashCode(); |
| | | } |
| | | } |
| | | |
| | | private static class CacheEntry { |
| | | Object result; |
| | | long lastExecuteTime; |
| | | } |
| | | |
| | | // 缓存映射:每个CacheKey对应一个CacheEntry |
| | | private static final Map<CacheKey, CacheEntry> cacheMap = new ConcurrentHashMap<>(); |
| | | private static final long MIN_INTERVAL_MS = 3000; |
| | | private static final Object cacheLock = new Object(); |
| | | |
| | | // 为每个缓存键提供独立的锁对象 |
| | | private static final Map<CacheKey, Object> keyLocks = new ConcurrentHashMap<>(); |
| | | |
| | | @Override |
| | | public Object getKData(String pid, String interval, String stockType) { |
| | | CacheKey cacheKey = new CacheKey(pid, interval, stockType); |
| | | long currentTime = System.currentTimeMillis(); |
| | | |
| | | // 第一次快速检查(不加锁) |
| | | if (lastResult != null && (currentTime - lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return lastResult; |
| | | // 1. 先快速检查缓存 |
| | | CacheEntry entry = cacheMap.get(cacheKey); |
| | | if (entry != null && (currentTime - entry.lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return entry.result; |
| | | } |
| | | |
| | | // 同步块内再次检查并更新 |
| | | synchronized (cacheLock) { |
| | | // 2. 获取该缓存键对应的锁 |
| | | Object lock = keyLocks.computeIfAbsent(cacheKey, k -> new Object()); |
| | | |
| | | // 3. 双重检查锁定 |
| | | synchronized (lock) { |
| | | currentTime = System.currentTimeMillis(); |
| | | if (lastResult != null && (currentTime - lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return lastResult; |
| | | entry = cacheMap.get(cacheKey); |
| | | |
| | | if (entry != null && (currentTime - entry.lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return entry.result; |
| | | } |
| | | |
| | | // 获取新数据 |
| | | // 4. 获取新数据 |
| | | Object newResult = doGetKData(pid, interval, stockType); |
| | | |
| | | // 更新缓存 |
| | | lastResult = newResult; |
| | | lastExecuteTime = System.currentTimeMillis(); |
| | | // 5. 更新缓存 |
| | | CacheEntry newEntry = new CacheEntry(); |
| | | newEntry.result = newResult; |
| | | newEntry.lastExecuteTime = System.currentTimeMillis(); |
| | | cacheMap.put(cacheKey, newEntry); |
| | | |
| | | return newResult; |
| | | } |
| | |
| | | |
| | | private Object doGetKData(String pid, String interval, String stockType) { |
| | | EStockType eStockType = EStockType.getEStockTypeByCode(stockType); |
| | | Object object = HttpUtil.get(eStockType.stockUrl + "kline?pid=" + pid + "&interval=" + interval + "&key=" + eStockType.stockKey); |
| | | Object object = HttpUtil.get(eStockType.stockUrl + "kline?pid=" + pid + |
| | | "&interval=" + interval + "&key=" + eStockType.stockKey); |
| | | Gson gson = new Gson(); |
| | | List<kData> dataList = gson.fromJson(object.toString(), new TypeToken<List<kData>>(){}.getType()); |
| | | |
| | | Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>().eq(Stock::getStockCode, pid).eq(Stock::getStockType, "IN")); |
| | | Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>() |
| | | .eq(Stock::getStockCode, pid) |
| | | .eq(Stock::getStockType, "IN")); |
| | | |
| | | if (stock != null) { |
| | | BigDecimal nowPrice = iPriceServices.getNowPrice(stock.getStockCode()); |
| | | Map singleStock = getSingleStock(stock.getStockCode()); |
| | | StockVO stockVO = (StockVO)singleStock.get("stock"); |
| | | |
| | | if (dataList != null && !dataList.isEmpty()) { |
| | | kData lastData = dataList.get(dataList.size() - 1); |
| | | lastData.setC(nowPrice.toString()); |
| | | lastData.setO(stockVO.getOpen_px()); |
| | | lastData.setH(stockVO.getToday_max()); |
| | | lastData.setL(stockVO.getToday_min()); |
| | | } |
| | | } |
| | | |
| | | return gson.toJson(dataList); |
| | | } |