| | |
| | | import com.google.common.collect.Lists; |
| | | import com.google.common.collect.Maps; |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.JsonObject; |
| | | import com.google.gson.reflect.TypeToken; |
| | | import com.nq.common.ServerResponse; |
| | | import com.nq.config.StockPoll; |
| | |
| | | import java.net.URL; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | | import java.util.concurrent.CompletableFuture; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.locks.ReentrantLock; |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | |
| | | |
| | | @Autowired |
| | | IPriceServices iPriceServices; |
| | | |
| | | |
| | | public ServerResponse getMarket() { |
| | | String market_url = PropertiesUtil.getProperty("sina.market.url"); |
| | |
| | | @Override |
| | | public ServerResponse getStockByType(int pageNum, int pageSize, String orderBy, String keyWords, String stockType, HttpServletRequest request) { |
| | | List<Stock> stockList = new ArrayList<>(); |
| | | if(stockType.equals(EStockType.IN.getCode())){ |
| | | PageHelper.startPage(pageNum, pageSize); |
| | | stockList.addAll(stockMapper.findStockByType(orderBy,stockType,keyWords)); |
| | | }else{ |
| | | PageHelper.startPage(pageNum, pageSize); |
| | | stockList.addAll(stockMapper.findStockTypeDz(orderBy,EStockType.IN.getCode(), keyWords)); |
| | | } |
| | | |
| | | |
| | | |
| | | PageHelper.startPage(pageNum, pageSize); |
| | | stockList.addAll(stockMapper.findStockByType(orderBy,stockType,keyWords)); |
| | | List<StockListVO> stockListVOS = Lists.newArrayList(); |
| | | if (stockList.size() > 0){ |
| | | stockListVOS.addAll(Objects.requireNonNull(StockApi.getStockReailTimes(stockList))); |
| | | } |
| | | for (int i = 0; i <stockListVOS.size() ; i++) { |
| | | stockListVOS.get(i).setNowPrice(iPriceServices.getNowPrice(stockListVOS.get(i).getCode(),stockType).toString()); |
| | | } |
| | | RPageInfo pageInfo = new RPageInfo(); |
| | | pageInfo.setList(stockListVOS); |
| | |
| | | stockVO.setName(stock.getStockName()); |
| | | stockVO.setSpell(stock.getStockSpell()); |
| | | stockVO.setGid(stock.getStockGid().toUpperCase()); |
| | | Map<String, Object> newStock = iPriceServices.getNewStock(stock.getStockCode()); |
| | | stockVO.setHcrate(newStock.get("ChgPct").toString().replaceAll("%", "")); |
| | | stockVO.setToday_max(newStock.get("High").toString()); |
| | | stockVO.setToday_min(newStock.get("Low").toString()); |
| | | if(null != cacheBaseStock){ |
| | | stockVO.setOpen_px(cacheBaseStock.getOpen()); |
| | | stockVO.setPreclose_px(cacheBaseStock.getPrevClose()); |
| | |
| | | map.put("introduction", introduction); |
| | | map.put("stock", stockVO); |
| | | return ServerResponse.createBySuccess(map); |
| | | } |
| | | |
| | | public Map getSingleStock(String code) { |
| | | if (StringUtils.isBlank(code)) |
| | | return null; |
| | | Stock stock = stockMapper.findStockByCode(code); |
| | | DataStockBean cacheBaseStock = RedisKeyUtil.getCacheBaseStock(stock); |
| | | Integer depositAmt = 0; |
| | | String introduction = null; |
| | | StockVO stockVO = StockApi.assembleInStockVO(stock); |
| | | stockVO.setDepositAmt(depositAmt); |
| | | stockVO.setNowPrice(iPriceServices.getNowPrice(stock.getStockCode()).toString()); |
| | | stockVO.setType(stock.getStockType()); |
| | | stockVO.setId(stock.getId().intValue()); |
| | | stockVO.setCode(stock.getStockCode()); |
| | | stockVO.setName(stock.getStockName()); |
| | | stockVO.setSpell(stock.getStockSpell()); |
| | | stockVO.setGid(stock.getStockGid().toUpperCase()); |
| | | if(null != cacheBaseStock){ |
| | | stockVO.setOpen_px(cacheBaseStock.getOpen()); |
| | | stockVO.setPreclose_px(cacheBaseStock.getPrevClose()); |
| | | } |
| | | Map map = Maps.newHashMap(); |
| | | map.put("introduction", introduction); |
| | | map.put("stock", stockVO); |
| | | return map; |
| | | } |
| | | |
| | | |
| | |
| | | @lombok.Data |
| | | class kData { |
| | | long t; |
| | | BigDecimal c; |
| | | BigDecimal o; |
| | | BigDecimal h; |
| | | BigDecimal l; |
| | | BigDecimal v; |
| | | BigDecimal vo; |
| | | String c; |
| | | String o; |
| | | String h; |
| | | String l; |
| | | String v; |
| | | String vo; |
| | | } |
| | | |
| | | |
| | | /*股票日线-K线*/ |
| | | |
| | | // 缓存键类,包含三个参数 |
| | | 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() { |
| | | int result = pid.hashCode(); |
| | | result = 31 * result + interval.hashCode(); |
| | | result = 31 * result + stockType.hashCode(); |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "CacheKey{pid='" + pid + "', interval='" + interval + "', stockType='" + stockType + "'}"; |
| | | } |
| | | } |
| | | |
| | | // 缓存条目类 |
| | | private static class CacheEntry { |
| | | Object result; |
| | | long lastExecuteTime; |
| | | |
| | | CacheEntry(Object result, long lastExecuteTime) { |
| | | this.result = result; |
| | | this.lastExecuteTime = lastExecuteTime; |
| | | } |
| | | } |
| | | |
| | | // 缓存存储:每个CacheKey对应一个CacheEntry |
| | | private static final Map<CacheKey, CacheEntry> cacheMap = new ConcurrentHashMap<>(); |
| | | |
| | | // 锁对象存储:每个CacheKey有自己的锁 |
| | | private static final Map<CacheKey, Object> lockMap = new ConcurrentHashMap<>(); |
| | | |
| | | private static final long MIN_INTERVAL_MS = 3000; |
| | | |
| | | @Override |
| | | public Object getKData(String pid, String interval, String stockType) { |
| | | EStockType eStockType = EStockType.getEStockTypeByCode(stockType); |
| | | if(eStockType == EStockType.IN){ |
| | | Object object = HttpUtil.get(eStockType.stockUrl + "api/all/getKData.do?pid=" + pid + "&interval=" + interval + "&stockType=in"); |
| | | Gson gson = new Gson(); |
| | | List<kData> dataList = gson.fromJson(object.toString(), new TypeToken<List<kData>>(){}.getType()); |
| | | CacheKey cacheKey = new CacheKey(pid, interval, stockType); |
| | | long currentTime = System.currentTimeMillis(); |
| | | |
| | | Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>().eq(Stock::getStockCode, pid).eq(Stock::getStockType, "IN")); |
| | | BigDecimal nowPrice = iPriceServices.getNowPrice(stock.getStockCode()); |
| | | // 修改 List 中的最后一条数据 |
| | | kData lastData = dataList.get(dataList.size() - 1); |
| | | lastData.setC(nowPrice); // 修改c字段的值 |
| | | // 1. 快速检查缓存(无锁) |
| | | CacheEntry cached = cacheMap.get(cacheKey); |
| | | if (cached != null && (currentTime - cached.lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return cached.result; |
| | | } |
| | | |
| | | // 2. 获取该缓存键对应的锁 |
| | | Object keyLock = lockMap.computeIfAbsent(cacheKey, k -> new Object()); |
| | | |
| | | // 3. 同步块内再次检查 |
| | | synchronized (keyLock) { |
| | | currentTime = System.currentTimeMillis(); |
| | | cached = cacheMap.get(cacheKey); |
| | | |
| | | if (cached != null && (currentTime - cached.lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return cached.result; |
| | | } |
| | | |
| | | // 4. 获取新数据 |
| | | Object newResult = doGetKData(pid, interval, stockType); |
| | | |
| | | // 5. 更新缓存 |
| | | if (newResult != null) { |
| | | cacheMap.put(cacheKey, new CacheEntry(newResult, System.currentTimeMillis())); |
| | | return newResult; |
| | | } else if (cached != null) { |
| | | // 请求失败,返回旧缓存 |
| | | return cached.result; |
| | | } |
| | | |
| | | return "{\"error\":\"获取K线数据失败\"}"; |
| | | } |
| | | } |
| | | |
| | | private Object doGetKData(String pid, String interval, String stockType) { |
| | | try { |
| | | EStockType eStockType = EStockType.getEStockTypeByCode(stockType); |
| | | String url = eStockType.stockUrl + "kline?pid=" + pid + |
| | | "&interval=" + interval + "&key=" + eStockType.stockKey; |
| | | |
| | | Object object = HttpUtil.get(url); |
| | | Gson gson = new Gson(); |
| | | List<kData> dataList = gson.fromJson(object.toString(), |
| | | new TypeToken<List<kData>>(){}.getType()); |
| | | |
| | | // 补充实时数据 |
| | | if (dataList != null && !dataList.isEmpty()) { |
| | | enrichWithRealTimeData(pid, dataList); |
| | | } |
| | | |
| | | return gson.toJson(dataList); |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | return null; // 返回null表示失败 |
| | | } |
| | | return HttpUtil.get(eStockType.stockUrl + "kline?pid=" + pid + "&interval=" + interval + "&key=" + eStockType.stockKey); |
| | | } |
| | | |
| | | private void enrichWithRealTimeData(String pid, List<kData> dataList) { |
| | | try { |
| | | 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"); |
| | | |
| | | 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()); |
| | | } |
| | | } catch (Exception e) { |
| | | // 实时数据获取失败不影响K线数据返回 |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | return ServerResponse.createBySuccess(this.stockMapper.selectByPrimaryKey(stockId)); |
| | | } |
| | | |
| | | public ServerResponse<PageInfo> listByAdmin(Integer showState, Integer lockState, String code, String name, String stockPlate, String stockType, int pageNum, int pageSize, HttpServletRequest request) { |
| | | public ServerResponse<PageInfo> listByAdmin(String stockGid,Integer showState, Integer lockState, String code, String name, String stockPlate, String stockType, int pageNum, int pageSize, HttpServletRequest request) { |
| | | PageHelper.startPage(pageNum, pageSize); |
| | | List<Stock> stockList = this.stockMapper.listByAdmin(showState, lockState, code, name, stockPlate, stockType); |
| | | List<Stock> stockList = this.stockMapper.listByAdmin(stockGid,showState, lockState, code, name, stockPlate, stockType); |
| | | List<StockAdminListVO> stockAdminListVOS = Lists.newArrayList(); |
| | | for (Stock stock : stockList) { |
| | | StockAdminListVO stockAdminListVO = assembleStockAdminListVO(stock); |