package com.nq.service.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.nq.dao.StockDzMapper; import com.nq.dao.StockMapper; import com.nq.dao.StockSettingMapper; import com.nq.enums.EConfigKey; import com.nq.pojo.*; import com.nq.service.IPriceServices; import com.nq.service.IStockConfigServices; import com.nq.utils.PropertiesUtil; import com.nq.utils.http.HttpClientRequest; import com.nq.utils.redis.RedisKeyUtil; import com.nq.utils.timeutil.TimeUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.*; import java.lang.reflect.Type; import java.math.BigDecimal; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.fasterxml.jackson.databind.ObjectMapper; @Service public class PriceServicesImpl implements IPriceServices { @Resource StockSettingMapper stockSettingMapper; @Resource StockMapper stockMapper; @Autowired IStockConfigServices iStockConfigServices; @Resource StockDzMapper stockDZMapper; @Override public BigDecimal getNowPrice(String stockCode) { Stock stock = stockMapper.selectOne(new QueryWrapper().eq("stock_code",stockCode)); if(null == stock){ return BigDecimal.ZERO; } StockSetting stockSetting = stockSettingMapper.selectOne(new QueryWrapper().eq("stock_code",stockCode)); if(stockSetting != null){ Date newDate = new Date(); DateTime startTime = DateUtil.parseDateTime(stockSetting.getStartTime()); DateTime endTime = DateUtil.parseDateTime(stockSetting.getEndTime()); if(newDate.after(startTime) && newDate.before(endTime)){ // if(TimeUtil.isTradingHour(stockSetting.getStartTime(),stockSetting.getEndTime())){ if(stockSetting.getType().equals("0")){ return new BigDecimal(stockSetting.getPrice()); }else{ String s = doPost(stock.getStockCode()); if(null != s){ Map stringObjectMap = jsonToMap(s); return new BigDecimal(stringObjectMap.get("Last").toString()).multiply(new BigDecimal(stockSetting.getPrice())); } } } } String s = doPost(stock.getStockCode()); if(null != s) { Map stringObjectMap = jsonToMap(s); return new BigDecimal(stringObjectMap.get("Last").toString()); } return BigDecimal.ZERO; } @Override public Map getNewStock(String stockCode) { Stock stock = stockMapper.selectOne(new QueryWrapper().eq("stock_code",stockCode)); String s = doPost(stock.getStockCode()); if(null != s){ Map stringObjectMap = jsonToMap(s); return stringObjectMap; } return null; } public static Map jsonToMap(String json) { ObjectMapper objectMapper = new ObjectMapper(); try { Object[] array = objectMapper.readValue(json, Object[].class); Gson gson = new Gson(); String s = gson.toJson(array[0]); Map map = objectMapper.readValue(s, Map.class); return map; } catch (JsonProcessingException e) { throw new RuntimeException(e); } } // 缓存条目类 private static class CacheEntry { String result; long lastExecuteTime; CacheEntry(String result, long lastExecuteTime) { this.result = result; this.lastExecuteTime = lastExecuteTime; } } // 按pid存储缓存条目 private static final Map cacheMap = new ConcurrentHashMap<>(); // 按pid存储锁对象,实现细粒度锁 private static final Map lockMap = new ConcurrentHashMap<>(); private static final long MIN_INTERVAL_MS = 3000; public static String doPost(String pid) { long currentTime = System.currentTimeMillis(); // 1. 快速检查缓存(无锁) CacheEntry cached = cacheMap.get(pid); if (cached != null && (currentTime - cached.lastExecuteTime) < MIN_INTERVAL_MS) { return cached.result; } // 2. 获取该pid对应的锁对象 Object pidLock = lockMap.computeIfAbsent(pid, k -> new Object()); // 3. 同步块内再次检查 synchronized (pidLock) { currentTime = System.currentTimeMillis(); cached = cacheMap.get(pid); if (cached != null && (currentTime - cached.lastExecuteTime) < MIN_INTERVAL_MS) { return cached.result; } // 4. 执行POST请求 String newResult = doActualPost(pid); // 5. 更新该pid的缓存 if (newResult != null) { cacheMap.put(pid, new CacheEntry(newResult, System.currentTimeMillis())); return newResult; } else if (cached != null) { // 请求失败,返回旧缓存 return cached.result; } return "{\"error\":\"请求失败且无缓存数据\"}"; } } private static String doActualPost(String pid) { String apiUrl = PropertiesUtil.getProperty("JS_IN_HTTP_URL") + "stock?key=" + PropertiesUtil.getProperty("JS_IN_KEY"); try { URL url = new URL(apiUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setDoOutput(true); connection.setConnectTimeout(5000); connection.setReadTimeout(10000); String postData = "pid=" + pid; try (OutputStream os = connection.getOutputStream()) { byte[] input = postData.getBytes("utf-8"); os.write(input, 0, input.length); } // 读取响应 try (BufferedReader in = new BufferedReader( new InputStreamReader(connection.getInputStream()))) { StringBuilder response = new StringBuilder(); String inputLine; while ((inputLine = in.readLine()) != null) { response.append(inputLine); } return response.toString(); } } catch (Exception e) { e.printStackTrace(); // 不再直接返回旧缓存,由上层处理 return null; } } @Override public boolean isLimitUpBuy(String stockCode) { Stock stock = stockMapper.selectOne(new QueryWrapper().eq("stock_code",stockCode)); StockRealTimeBean stockRealTimeBean = RedisKeyUtil.getCacheRealTimeStock(stock); BigDecimal pcp = new BigDecimal(stockRealTimeBean.getPcp()); StockConfig stockConfig = iStockConfigServices.queryByKey(EConfigKey.LIMIT_UP_POINT.getCode()); if(stockConfig == null){ return true; } if(pcp.compareTo(new BigDecimal(0))<0){ return true; } if(new BigDecimal(stockConfig.getCValue()).compareTo(pcp)<=0){ StockConfig limitConfig = iStockConfigServices.queryByKey(EConfigKey.LIMIT_UP_IS_BUY.getCode()); if(limitConfig.getCValue().equals("1")){ return true; } return false; } return true; } @Override public boolean isLimitDownSell(String stockCode) { Stock stock = stockMapper.selectOne(new QueryWrapper().eq("stock_code",stockCode)); StockRealTimeBean stockRealTimeBean = RedisKeyUtil.getCacheRealTimeStock(stock); BigDecimal pcp = new BigDecimal(stockRealTimeBean.getPcp()); StockConfig stockConfig = iStockConfigServices.queryByKey(EConfigKey.LIMIT_DOWN_POINT.getCode()); if(stockConfig == null){ return true; } if(pcp.compareTo(new BigDecimal(stockConfig.getCValue()))<=0){ StockConfig limitConfig = iStockConfigServices.queryByKey(EConfigKey.LIMIT_DOWN_IS_SELL.getCode()); if(!limitConfig.getCValue().equals("1")){ return false; } } return true; } }