| | |
| | | import java.math.BigDecimal; |
| | | import java.net.HttpURLConnection; |
| | | import java.net.URL; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.locks.ReentrantLock; |
| | | |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private static final ReentrantLock lock = new ReentrantLock(); |
| | | private static String lastResult = null; |
| | | private static long lastExecuteTime = 0; |
| | | // 缓存条目类 |
| | | private static class CacheEntry { |
| | | String result; |
| | | long lastExecuteTime; |
| | | |
| | | CacheEntry(String result, long lastExecuteTime) { |
| | | this.result = result; |
| | | this.lastExecuteTime = lastExecuteTime; |
| | | } |
| | | } |
| | | |
| | | // 按pid存储缓存 |
| | | private static final Map<String, CacheEntry> cacheMap = new ConcurrentHashMap<>(); |
| | | |
| | | // 按pid存储锁对象,实现细粒度锁 |
| | | private static final Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>(); |
| | | |
| | | private static final long MIN_INTERVAL_MS = 3000; |
| | | |
| | | public static String doPost(String pid) { |
| | | // 快速检查缓存 |
| | | // 1. 快速检查缓存 |
| | | long currentTime = System.currentTimeMillis(); |
| | | if (lastResult != null && (currentTime - lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return lastResult; |
| | | CacheEntry cached = cacheMap.get(pid); |
| | | |
| | | if (cached != null && (currentTime - cached.lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return cached.result; |
| | | } |
| | | |
| | | // 需要获取锁执行新请求 |
| | | return executeWithLock(pid); |
| | | } |
| | | // 2. 获取该pid对应的锁 |
| | | ReentrantLock pidLock = lockMap.computeIfAbsent(pid, k -> new ReentrantLock()); |
| | | |
| | | private static String executeWithLock(String pid) { |
| | | String apiUrl = PropertiesUtil.getProperty("JS_IN_HTTP_URL") + "stock?key=" + PropertiesUtil.getProperty("JS_IN_KEY"); |
| | | |
| | | lock.lock(); |
| | | // 3. 加锁执行 |
| | | pidLock.lock(); |
| | | try { |
| | | // 双检锁:再次检查缓存 |
| | | long currentTime = System.currentTimeMillis(); |
| | | if (lastResult != null && (currentTime - lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return lastResult; |
| | | // 4. 双重检查 |
| | | currentTime = System.currentTimeMillis(); |
| | | cached = cacheMap.get(pid); |
| | | |
| | | if (cached != null && (currentTime - cached.lastExecuteTime) < MIN_INTERVAL_MS) { |
| | | return cached.result; |
| | | } |
| | | |
| | | // 执行POST请求 |
| | | String result = null; |
| | | try { |
| | | URL url = new URL(apiUrl); |
| | | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| | | // 5. 执行实际请求 |
| | | String result = executePostRequest(pid); |
| | | |
| | | connection.setRequestMethod("POST"); |
| | | connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); |
| | | connection.setDoOutput(true); |
| | | |
| | | String postData = "pid=" + pid; |
| | | |
| | | try (OutputStream os = connection.getOutputStream()) { |
| | | byte[] input = postData.getBytes("utf-8"); |
| | | os.write(input, 0, input.length); |
| | | } |
| | | |
| | | BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); |
| | | String inputLine; |
| | | StringBuilder response = new StringBuilder(); |
| | | |
| | | while ((inputLine = in.readLine()) != null) { |
| | | response.append(inputLine); |
| | | } |
| | | in.close(); |
| | | |
| | | result = response.toString(); |
| | | |
| | | // 更新缓存 |
| | | lastResult = result; |
| | | lastExecuteTime = System.currentTimeMillis(); |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | // 请求失败时返回缓存 |
| | | if (lastResult != null) { |
| | | return lastResult; |
| | | } |
| | | // 6. 更新缓存 |
| | | if (result != null) { |
| | | cacheMap.put(pid, new CacheEntry(result, System.currentTimeMillis())); |
| | | } else if (cached != null) { |
| | | // 请求失败,返回旧缓存 |
| | | return cached.result; |
| | | } |
| | | |
| | | return result; |
| | | |
| | | } finally { |
| | | lock.unlock(); |
| | | pidLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | private static String executePostRequest(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; // 返回null表示请求失败 |
| | | } |
| | | } |
| | | |
| | | public static void setCacheCompanies(Stock stock,String companiesInfo){ |
| | | RedisShardedPoolUtils.set(RedisKeyConstant.RK_COMPANY_INFO+":"+stock.getStockType()+":"+stock.getStockCode(), |
| | | new Gson().toJson(companiesInfo)); |
| | | } |
| | | |
| | | |
| | | public static JSONObject getCacheCompanies(Stock stock){ |
| | | String companiesInfo = RedisShardedPoolUtils.get(RedisKeyConstant.RK_COMPANY_INFO+":"+stock.getStockType()+":"+stock.getStockCode()); |
| | | if(companiesInfo.isEmpty()){ |
| | | return null; |
| | | } |
| | | return JSONObject.parseObject(companiesInfo); |
| | | } |
| | | |
| | | |
| | | |
| | | } |