DriverFBTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/dao/GDKeyMapper.java
New file @@ -0,0 +1,31 @@ package com.stylefeng.guns.modular.system.dao; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import java.util.List; /** * 高德地图key管理 Mapper(注解方式,无需XML) */ public interface GDKeyMapper { /** * 查询可用key(启用、未删除、未用完) */ @Select("SELECT gdKey FROM t_gaode_account WHERE status = 1 AND delFlag = 0 AND useStatus <> 3") List<String> selectAvailableKeys(); /** * 查询已用完的key(用于每日凌晨重置校验) */ @Select("SELECT gdKey FROM t_gaode_account WHERE status = 1 AND delFlag = 0 AND useStatus = 3") List<String> selectExhaustedKeys(); /** * 更新key的使用状态 */ @Update("UPDATE t_gaode_account SET useStatus = #{useStatus} WHERE gdKey = #{gdKey} AND delFlag = 0") int updateUseStatus(@Param("gdKey") String gdKey, @Param("useStatus") Integer useStatus); } DriverFBTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/util/GDFalconUtil.java
@@ -14,21 +14,26 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.util.Map; /** * 高德猎鹰服务 * key次数用完时自动切换可用key(切换后自动重建serverId) */ @Component public class GDFalconUtil implements ApplicationRunner { private String key = "c473445a931ee8c42eef8b63fc3602c5"; @Autowired private RestTemplate restTemplate; @Autowired private GDKeyManager gdKeyManager; private static String serverId = null; /** * 当前serverId对应的key(key切换后需重建serverId) */ private static volatile String currentKey = null; public String getServerId() { @@ -46,20 +51,137 @@ } /** * 初始化创建服务 */ public void init(){ if(serverId == null){ JSONArray jsonArray = this.selectServer(); if(null == jsonArray){ serverId = this.createServer("server"); }else{ serverId = jsonArray.getJSONObject(0).getString("sid"); if (gdKeyManager == null || restTemplate == null) { // 非容器实例(如 GunsApplication.main 中 new 出来的对象),由容器内bean完成初始化 return; } ensureServerId(); } } /** * 参数构造器:key与sid切换后需按最新值重新构造 */ private interface ParamsBuilder { MultiValueMap<String, Object> build(String key, String sid); } /** * 确保serverId与当前使用的key对应: * key切换后自动查询/重建serverId;key用完自动切换可用key重试 */ private synchronized String ensureServerId(){ int maxAttempts = 5; String lastKey = gdKeyManager.getKey(); for (int i = 0; i < maxAttempts; i++) { String gdKey = gdKeyManager.getKey(); lastKey = gdKey; if (serverId != null && gdKey.equals(currentKey)) { return gdKey; } // 1. 查询该key下的服务列表 JSONArray jsonArray = null; try { String url = "https://tsapi.amap.com/v1/track/service/list?key=" + gdKey; String forObject = restTemplate.getForObject(url, String.class); JSONObject jsonObject = JSON.parseObject(forObject); if (GDKeyManager.isFalconExhausted(jsonObject)) { gdKeyManager.reportExhausted(gdKey); serverId = null; continue; } if (jsonObject.getIntValue("errcode") == 10000) { JSONObject data = jsonObject.getJSONObject("data"); if (null != data) { jsonArray = data.getJSONArray("results"); } } } catch (Exception e) { loggerWarn("查询猎鹰服务列表异常", e); } if (jsonArray != null && jsonArray.size() > 0) { serverId = jsonArray.getJSONObject(0).getString("sid"); currentKey = gdKey; return gdKey; } // 2. 无服务:创建新服务 String sid = createServerInternal("server", gdKey); if (sid != null) { serverId = sid; currentKey = gdKey; return gdKey; } // 3. 创建失败(可能key用完):切换key重试 gdKeyManager.reportExhausted(gdKey); serverId = null; } return lastKey; } private void loggerWarn(String msg, Exception e) { System.err.println("[GDFalconUtil] " + msg + ":" + e.getMessage()); } /** * 执行猎鹰POST表单请求:key次数用完时自动切换key并重建serverId重试 */ private String doFalconPost(String url, ParamsBuilder builder) { String lastResp = null; int maxAttempts = 5; for (int i = 0; i < maxAttempts; i++) { String gdKey = ensureServerId(); String sid = serverId; MultiValueMap<String, Object> params = builder.build(gdKey, sid); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, headers); String resp = restTemplate.postForObject(url, requestEntity, String.class); lastResp = resp; JSONObject jsonObject; try { jsonObject = JSON.parseObject(resp); } catch (Exception e) { return resp; } if (GDKeyManager.isFalconExhausted(jsonObject)) { gdKeyManager.reportExhausted(gdKey); serverId = null; continue; } return resp; } return lastResp; } /** * 执行猎鹰GET请求:key次数用完时自动切换可用key重试 */ private String doFalconGet(String baseUrl) { String lastResp = null; int maxAttempts = 5; for (int i = 0; i < maxAttempts; i++) { String gdKey = gdKeyManager.getKey(); String url = baseUrl + (baseUrl.contains("?") ? "&" : "?") + "key=" + gdKey; String resp = restTemplate.getForObject(url, String.class); lastResp = resp; JSONObject jsonObject; try { jsonObject = JSON.parseObject(resp); } catch (Exception e) { return resp; } if (GDKeyManager.isFalconExhausted(jsonObject)) { gdKeyManager.reportExhausted(gdKey); continue; } return resp; } return lastResp; } @@ -68,6 +190,13 @@ * @return */ public String createServer(String name){ return createServerInternal(name, gdKeyManager.getKey()); } /** * 创建新的服务(内部直连,供ensureServerId使用) */ private String createServerInternal(String name, String key){ String url = "https://tsapi.amap.com/v1/track/service/add"; //调用推送 HttpHeaders headers = new HttpHeaders(); @@ -91,24 +220,6 @@ /** * 查询服务 * @return */ private JSONArray selectServer(){ String url = "https://tsapi.amap.com/v1/track/service/list?key=" + key; String forObject = restTemplate.getForObject(url, String.class); JSONObject jsonObject = JSON.parseObject(forObject); if(jsonObject.getIntValue("errcode") == 10000){ JSONObject data = jsonObject.getJSONObject("data"); if(null != data){ return data.getJSONArray("results"); } } return null; } /** * 创建终端数据 * @param name * @return @@ -118,18 +229,13 @@ if(tid != null){ return tid; } String url = "https://tsapi.amap.com/v1/track/terminal/add"; //调用推送 HttpHeaders headers = new HttpHeaders(); // 以表单的方式提交 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //将请求头部和参数合成一个请求 MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("key", key); params.add("sid", serverId); params.add("name", name); HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, headers); String s = restTemplate.postForObject(url, requestEntity , String.class); String s = doFalconPost("https://tsapi.amap.com/v1/track/terminal/add", (key, sid) -> { MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("key", key); params.add("sid", sid); params.add("name", name); return params; }); JSONObject jsonObject = JSON.parseObject(s); if(jsonObject.getIntValue("errcode") == 10000){ JSONObject data = jsonObject.getJSONObject("data"); @@ -146,8 +252,7 @@ * @return */ public String selectTerminal(String name){ String url = "https://tsapi.amap.com/v1/track/terminal/list?key=" + key + "&name=" + name; String forObject = restTemplate.getForObject(url, String.class); String forObject = doFalconGet("https://tsapi.amap.com/v1/track/terminal/list?name=" + name); JSONObject jsonObject = JSON.parseObject(forObject); if(jsonObject.getIntValue("errcode") == 10000){ JSONObject data = jsonObject.getJSONObject("data"); @@ -172,18 +277,13 @@ * @return */ public String createTrack(String tid){ String url = "https://tsapi.amap.com/v1/track/trace/add"; //调用推送 HttpHeaders headers = new HttpHeaders(); // 以表单的方式提交 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //将请求头部和参数合成一个请求 MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("key", key); params.add("sid", serverId); params.add("tid", tid); HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, headers); String s = restTemplate.postForObject(url, requestEntity , String.class); String s = doFalconPost("https://tsapi.amap.com/v1/track/trace/add", (key, sid) -> { MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("key", key); params.add("sid", sid); params.add("tid", tid); return params; }); JSONObject jsonObject = JSON.parseObject(s); if(jsonObject.getIntValue("errcode") == 10000){ return jsonObject.getString("data"); @@ -199,28 +299,18 @@ * @param trid 轨迹id */ public void deleteTrack(String tid, String trid){ String url = "https://tsapi.amap.com/v1/track/trace/delete"; //调用推送 HttpHeaders headers = new HttpHeaders(); // 以表单的方式提交 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //将请求头部和参数合成一个请求 MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("key", key); params.add("sid", serverId); params.add("tid", tid); params.add("trid", trid); HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, headers); String s = restTemplate.postForObject(url, requestEntity , String.class); String s = doFalconPost("https://tsapi.amap.com/v1/track/trace/delete", (key, sid) -> { MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("key", key); params.add("sid", sid); params.add("tid", tid); params.add("trid", trid); return params; }); JSONObject jsonObject = JSON.parseObject(s); if(jsonObject.getIntValue("errcode") != 10000){ System.err.println(jsonObject.getString("errmsg")); } } // // public Map<String, Object> queryTrsearch(){ // // } } DriverFBTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/util/GDKeyManager.java
New file @@ -0,0 +1,349 @@ package com.stylefeng.guns.modular.system.util; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.stylefeng.guns.modular.system.dao.GDKeyMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; /** * 高德地图key管理器 * <p> * 功能: * 1. key池管理:从 t_gaode_account 表加载可用key(启用、未删除、未用完),轮询获取; * 表为空或全部用尽时降级使用兜底key(原硬编码key)。 * 2. 自动切换:调用方检测到key"次数用完"后调用 reportExhausted 上报, * 管理器将key移出内存池并置库表 useStatus=3(已用完)。 * 3. 每日凌晨重置校验:每天 00:00 将 useStatus=3 的key重置为未使用并放回池, * 随后调用高德最轻量接口校验配额是否真正恢复;未恢复的key重新标记已用完, * 进入延迟重试队列,每30分钟校验一次直到恢复额度。 * 4. 兜底key:当天用完仅内存禁用,次日凌晨自动恢复;不写库(表内无对应记录)。 */ @Component public class GDKeyManager { private static final Logger logger = LoggerFactory.getLogger(GDKeyManager.class); /** * 兜底key(原硬编码key):司机端 */ public static final String FALLBACK_KEY = "39b31fa22955efa8fce9eed487241db9"; /** * 配额校验接口:最轻量的通用Web服务接口,每次仅消耗1次配额 */ private static final String VERIFY_URL = "https://restapi.amap.com/v3/geocode/geo?output=JSON&address=%E5%8C%97%E4%BA%AC%E5%B8%82&key="; @Autowired private GDKeyMapper gdKeyMapper; @Autowired private RestTemplate restTemplate; /** * 内存可用key池(refreshPool整体替换,保证getKey不会看到clear/addAll中间态) */ private volatile List<String> availableKeys = new CopyOnWriteArrayList<>(); /** * 轮询下标 */ private final AtomicInteger index = new AtomicInteger(0); /** * 延迟校验队列:key -> 已重试次数(每日凌晨校验未恢复的key) */ private final ConcurrentHashMap<String, Integer> pendingVerifyKeys = new ConcurrentHashMap<>(); /** * 兜底key当日是否已用完 */ private volatile boolean fallbackExhausted = false; /** * 已标记"使用中"(useStatus=2)的key集合,避免热路径每次取用都写库 */ private final Set<String> inUseKeys = ConcurrentHashMap.newKeySet(); @PostConstruct public void init() { refreshPool(); } /** * 刷新可用key池 */ public synchronized void refreshPool() { try { List<String> keys = gdKeyMapper.selectAvailableKeys(); List<String> newPool = new CopyOnWriteArrayList<>(); if (keys != null) { newPool.addAll(keys); } // 整体替换,避免clear/addAll中间态导致getKey短暂看到空池误降级兜底key availableKeys = newPool; logger.info("[GDKeyManager] 高德key池刷新完成,可用key数:{}", newPool.size()); } catch (Exception e) { // 表不存在(用户端/司机端未建表时)等异常:降级使用兜底key,不影响系统运行 logger.warn("[GDKeyManager] 加载高德key失败(t_gaode_account表可能不存在),降级使用兜底key", e); availableKeys = new CopyOnWriteArrayList<>(); } } /** * 获取一个可用key:池非空轮询池内key;池空使用兜底key(当日未用完时) */ public String getKey() { List<String> pool = availableKeys; if (!pool.isEmpty()) { String key = pickFromPool(pool); markInUse(key); return key; } if (!fallbackExhausted) { return FALLBACK_KEY; } // 兜底key当日也已用完:尝试刷新池(可能有新录入的key) refreshPool(); pool = availableKeys; if (!pool.isEmpty()) { String key = pickFromPool(pool); markInUse(key); return key; } return FALLBACK_KEY; } /** * 从内存池轮询取一个key(使用调用方传入的池快照,避免与定时刷新交叉导致越界) */ private String pickFromPool(List<String> pool) { int i = Math.abs(index.getAndIncrement() % pool.size()); return pool.get(i); } /** * 将表内key标记为"使用中"(useStatus=2): * 仅key首次被取用时写库一次;兜底key不写库;写库失败不影响取key流程。 */ private void markInUse(String key) { if (key == null || FALLBACK_KEY.equals(key) || inUseKeys.contains(key)) { return; } try { gdKeyMapper.updateUseStatus(key, 2); inUseKeys.add(key); logger.info("[GDKeyManager] 高德key[{}]开始使用,已标记为使用中", key); } catch (Exception e) { // 表不存在等异常:本次不标记,下次取用再尝试,不影响业务 logger.warn("[GDKeyManager] 标记高德key[{}]使用中失败(表可能不存在),不影响使用", key); } } /** * 定时刷新key池:每5分钟一次,让管理后台新录入的key及时生效 */ @Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 60 * 1000) public void scheduledRefreshPool() { refreshPool(); } /** * 上报某个key次数用完: * 表内key -> 移出内存池 + 置库表 useStatus=3; * 兜底key -> 仅内存当日禁用(不写库),次日凌晨自动恢复。 */ public void reportExhausted(String usedKey) { if (usedKey == null || usedKey.isEmpty()) { return; } availableKeys.remove(usedKey); inUseKeys.remove(usedKey); if (FALLBACK_KEY.equals(usedKey)) { fallbackExhausted = true; logger.warn("[GDKeyManager] 兜底key次数已用完,今日禁用,次日凌晨自动恢复"); return; } try { gdKeyMapper.updateUseStatus(usedKey, 3); logger.warn("[GDKeyManager] 高德key[{}]次数用完,已标记为已用完并移出key池", usedKey); } catch (Exception e) { logger.warn("[GDKeyManager] 更新高德key[{}]使用状态失败(表可能不存在),仅内存剔除", usedKey); } } /** * 判断Web服务API响应是否为"次数用完/不可用"(依据infocode) */ public static boolean isWebKeyExhausted(JSONObject resp) { if (resp == null) { return false; } String infocode = resp.getString("infocode"); if (infocode == null || infocode.isEmpty()) { return false; } switch (infocode) { case "10001": // key不正确或过期 case "10003": // 超过每日配额 case "10004": // 访问过于频繁 case "10005": // 超出每秒访问量限制 case "10010": // 权限不足 case "10012": // 服务未开放/权限不足 case "10019": // 使用配额已用尽 case "10020": // 并发超限 case "10044": // 签名错误/未开通 return true; default: return false; } } /** * 判断猎鹰API响应是否为"次数用完"(依据errcode/errmsg) */ public static boolean isFalconExhausted(JSONObject resp) { if (resp == null) { return false; } int errcode = resp.getIntValue("errcode"); if (errcode == 10000) { return false; } if (errcode == 20001) { return true; // 权限不足 } String errmsg = resp.getString("errmsg"); if (errmsg == null) { return false; } return errmsg.contains("配额") || errmsg.contains("上限") || errmsg.contains("次数") || errmsg.contains("用完") || errmsg.contains("超限") || errmsg.contains("quota") || errmsg.contains("limit"); } // ==================== 每日凌晨重置 + API校验恢复额度 ==================== /** * 每日凌晨 00:00 自动执行: * 1. 将 useStatus=3(已用完)的key重置为未使用并放回内存池; * 2. 调用高德API校验配额是否真正恢复; * 3. 未恢复的key重新标记已用完,进入延迟重试队列,直到恢复额度。 */ @Scheduled(cron = "0 0 0 * * ?") public void dailyResetAndVerify() { logger.info("[GDKeyManager] 开始每日高德key配额重置校验"); // 兜底key当日禁用标记次日自动恢复 fallbackExhausted = false; List<String> exhaustedKeys; try { exhaustedKeys = gdKeyMapper.selectExhaustedKeys(); } catch (Exception e) { logger.warn("[GDKeyManager] 查询已用完key失败(表可能不存在),跳过重置校验", e); return; } if (exhaustedKeys == null || exhaustedKeys.isEmpty()) { logger.info("[GDKeyManager] 无已用完的key,跳过重置校验"); return; } for (String gdKey : exhaustedKeys) { // 1. 先重置为未使用 try { gdKeyMapper.updateUseStatus(gdKey, 1); } catch (Exception e) { logger.warn("[GDKeyManager] 重置key[{}]使用状态失败", gdKey, e); } // 2. 调用API校验是否真正恢复额度 boolean recovered = verifyQuota(gdKey); if (recovered) { if (!availableKeys.contains(gdKey)) { availableKeys.add(gdKey); } pendingVerifyKeys.remove(gdKey); logger.info("[GDKeyManager] 高德key[{}]配额已恢复,重新加入key池", gdKey); } else { // 未恢复:重新标记已用完,进入延迟重试队列 try { gdKeyMapper.updateUseStatus(gdKey, 3); } catch (Exception e) { logger.warn("[GDKeyManager] 回写key[{}]已用完状态失败", gdKey, e); } availableKeys.remove(gdKey); pendingVerifyKeys.put(gdKey, 0); logger.warn("[GDKeyManager] 高德key[{}]配额尚未恢复,进入延迟重试队列", gdKey); } } } /** * 延迟重试任务:每30分钟校验一次待恢复的key,直到恢复额度为止 */ @Scheduled(fixedDelay = 30 * 60 * 1000, initialDelay = 30 * 60 * 1000) public void retryPendingKeys() { if (pendingVerifyKeys.isEmpty()) { return; } for (String gdKey : new ArrayList<>(pendingVerifyKeys.keySet())) { boolean recovered = verifyQuota(gdKey); if (recovered) { try { gdKeyMapper.updateUseStatus(gdKey, 1); } catch (Exception e) { logger.warn("[GDKeyManager] 恢复key[{}]使用状态失败", gdKey, e); } if (!availableKeys.contains(gdKey)) { availableKeys.add(gdKey); } pendingVerifyKeys.remove(gdKey); logger.info("[GDKeyManager] 高德key[{}]延迟校验通过,配额已恢复,重新加入key池", gdKey); } else { int count = pendingVerifyKeys.merge(gdKey, 1, Integer::sum); logger.warn("[GDKeyManager] 高德key[{}]配额仍未恢复(已校验{}次),继续等待", gdKey, count); } } } /** * 调用高德最轻量接口校验配额是否恢复 * * @return true=已恢复(或无法判断,保守放行);false=明确未恢复(配额类错误) */ private boolean verifyQuota(String gdKey) { try { String resp = restTemplate.getForObject(VERIFY_URL + gdKey, String.class); JSONObject json = JSON.parseObject(resp); String status = json.getString("status"); String infocode = json.getString("infocode"); if ("1".equals(status) && "10000".equals(infocode)) { return true; } // 权限类错误:该key未开通Web服务校验权限(如猎鹰专用key),无法判断配额,按已恢复处理 if ("10044".equals(infocode) || "10012".equals(infocode) || "10002".equals(infocode) || "10010".equals(infocode)) { logger.info("[GDKeyManager] 高德key[{}]无Web服务校验权限(infocode={}),按已恢复处理", gdKey, infocode); return true; } // 配额类错误:明确未恢复 if ("10001".equals(infocode) || "10003".equals(infocode) || "10019".equals(infocode)) { return false; } // 其他错误(限流/网络等):保守放行,真实调用失败时会被自动切换机制再次标记 return true; } catch (Exception e) { logger.warn("[GDKeyManager] 校验key[{}]配额异常,按已恢复处理", gdKey, e); return true; } } } DriverFBTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/util/GDMapElectricFenceUtil.java
@@ -33,9 +33,98 @@ @Autowired private IGDInterfaceService gdInterfaceService; private String key = "c473445a931ee8c42eef8b63fc3602c5"; @Autowired private GDKeyManager gdKeyManager; private JSONArray jsonArray = new JSONArray(); /** * 执行GET请求:key次数用完时自动切换可用key重试 */ private String doGetWithRetry(String baseUrl) { String lastResp = null; int maxAttempts = 5; for (int i = 0; i < maxAttempts; i++) { String key = gdKeyManager.getKey(); String url = baseUrl + (baseUrl.contains("?") ? "&" : "?") + "key=" + key; String resp = restTemplate.getForObject(url, String.class); lastResp = resp; JSONObject jsonObject; try { jsonObject = JSON.parseObject(resp); } catch (Exception e) { return resp; } if (GDKeyManager.isWebKeyExhausted(jsonObject)) { gdKeyManager.reportExhausted(key); continue; } return resp; } return lastResp; } /** * 执行POST请求(JSON体):key次数用完时自动切换可用key重试 */ private String doPostJsonWithRetry(String baseUrl, String jsonBody) { String lastResp = null; int maxAttempts = 5; HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); headers.add("Accept", MediaType.APPLICATION_JSON.toString()); for (int i = 0; i < maxAttempts; i++) { String key = gdKeyManager.getKey(); String url = baseUrl + (baseUrl.contains("?") ? "&" : "?") + "key=" + key; HttpEntity<String> formEntity = new HttpEntity<>(jsonBody, headers); String resp = restTemplate.postForObject(url, formEntity, String.class); lastResp = resp; JSONObject jsonObject; try { jsonObject = JSON.parseObject(resp); } catch (Exception e) { return resp; } if (GDKeyManager.isWebKeyExhausted(jsonObject)) { gdKeyManager.reportExhausted(key); continue; } return resp; } return lastResp; } /** * 执行PATCH请求(JSON体):key次数用完时自动切换可用key重试 */ private String doPatchJsonWithRetry(String baseUrl, String jsonBody) { String lastResp = null; int maxAttempts = 5; HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); headers.add("Accept", MediaType.APPLICATION_JSON.toString()); for (int i = 0; i < maxAttempts; i++) { String key = gdKeyManager.getKey(); String url = baseUrl + (baseUrl.contains("?") ? "&" : "?") + "key=" + key; HttpEntity<String> formEntity = new HttpEntity<>(jsonBody, headers); String resp = restTemplate.patchForObject(url, formEntity, String.class); lastResp = resp; JSONObject jsonObject; try { jsonObject = JSON.parseObject(resp); } catch (Exception e) { return resp; } if (GDKeyManager.isWebKeyExhausted(jsonObject)) { gdKeyManager.reportExhausted(key); continue; } return resp; } return lastResp; } @Bean @@ -81,14 +170,7 @@ json.put("alert_condition", "enter;leave"); json.put("desc", id); HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); headers.add("Accept", MediaType.APPLICATION_JSON.toString()); HttpEntity<String> formEntity = new HttpEntity<>(json.toString(), headers); String url = "https://restapi.amap.com/v4/geofence/meta?key=" + key; String js = restTemplate.postForObject(url, formEntity, String.class); String js = doPostJsonWithRetry("https://restapi.amap.com/v4/geofence/meta", json.toString()); JSONObject jsonObject = JSON.parseObject(js); JSONObject data = jsonObject.getJSONObject("data"); String status = data.getString("status"); @@ -148,8 +230,8 @@ * 查询以创建的所有电子围栏 */ public String findElectricFenc(String center, String radius, String points){ String url = "https://restapi.amap.com/v4/geofence/meta?key=" + key; String forObject = restTemplate.getForObject(url, String.class); String url = "https://restapi.amap.com/v4/geofence/meta"; String forObject = doGetWithRetry(url); JSONObject jsonObject = JSON.parseObject(forObject); JSONObject data = jsonObject.getJSONObject("data"); @@ -185,8 +267,8 @@ if(page == 1){ jsonArray = new JSONArray(); } String url = "https://restapi.amap.com/v4/geofence/meta?key=" + key + "&page_no=" + page + "&page_size=" + 30; String forObject = restTemplate.getForObject(url, String.class); String url = "https://restapi.amap.com/v4/geofence/meta?page_no=" + page + "&page_size=" + 30; String forObject = doGetWithRetry(url); JSONObject jsonObject = JSON.parseObject(forObject); JSONObject data = jsonObject.getJSONObject("data"); JSONArray rs_list = data.getJSONArray("rs_list"); @@ -275,14 +357,8 @@ json.put("alert_condition", "enter;leave"); json.put("desc", id + "围栏更新描述"); HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); headers.add("Accept", MediaType.APPLICATION_JSON.toString()); HttpEntity<String> formEntity = new HttpEntity<>(json.toString(), headers); String url = "https://restapi.amap.com/v4/geofence/meta?key=" + key + "&gid=" + gid; String s = restTemplate.patchForObject(url, formEntity, String.class); String url = "https://restapi.amap.com/v4/geofence/meta?gid=" + gid; String s = doPatchJsonWithRetry(url, json.toString()); System.out.println(s); // gdInterfaceService.saveData("https://restapi.amap.com/v4/geofence/meta", "更新电子围栏"); @@ -304,8 +380,8 @@ diu = "B78F99DD-2496-4A43-843B-172CDA2D9798"; } locations = locations + "," + (new Date().getTime() / 1000); String url = "https://restapi.amap.com/v4/geofence/status?key=" + key + "&diu=" + diu + "&locations=" + locations; String forObject = restTemplate.getForObject(url, String.class); String url = "https://restapi.amap.com/v4/geofence/status?diu=" + diu + "&locations=" + locations; String forObject = doGetWithRetry(url); JSONObject jsonObject = JSON.parseObject(forObject); List<String> ids = new ArrayList<>(); JSONObject data = jsonObject.getJSONObject("data"); @@ -334,7 +410,7 @@ * @return */ public boolean deleteElectricFenc(String gid){ restTemplate.delete("https://restapi.amap.com/v4/geofence/meta?key={1}&gid={2}", key, gid); restTemplate.delete("https://restapi.amap.com/v4/geofence/meta?key={1}&gid={2}", gdKeyManager.getKey(), gid); System.out.println("删除:" ); // gdInterfaceService.saveData("https://restapi.amap.com/v4/geofence/meta", "删除电子围栏"); @@ -351,9 +427,9 @@ * @return */ public Map<String, String> getDistance(String origins, String destination, Integer type){ String url = "https://restapi.amap.com/v3/distance?key=" + key + "&origins=" + origins + "&destination=" + destination + String url = "https://restapi.amap.com/v3/distance?origins=" + origins + "&destination=" + destination + "&type=" + type; String forObject = restTemplate.getForObject(url, String.class); String forObject = doGetWithRetry(url); JSONObject jsonObject = JSON.parseObject(forObject); String status = jsonObject.getString("status"); @@ -381,9 +457,9 @@ * @return */ public Map<String, String> getDriving(String origins, String destination, String waypoints){ String url = "https://restapi.amap.com/v3/direction/driving?key=" + key + "&origin=" + origins + "&destination=" + destination + String url = "https://restapi.amap.com/v3/direction/driving?origin=" + origins + "&destination=" + destination + "&waypoints=" + waypoints + "&extensions=base&strategy=0"; String forObject = restTemplate.getForObject(url, String.class); String forObject = doGetWithRetry(url); JSONObject jsonObject = JSON.parseObject(forObject); String status = jsonObject.getString("status"); DriverFBTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/util/GDMapGeocodingUtil.java
@@ -20,13 +20,40 @@ @Component public class GDMapGeocodingUtil { private String key = "c473445a931ee8c42eef8b63fc3602c5"; @Autowired private RestTemplate restTemplate; @Autowired private IGDInterfaceService gdInterfaceService; @Autowired private GDKeyManager gdKeyManager; /** * 执行GET请求:key次数用完时自动切换可用key重试 */ private String doGetWithRetry(String baseUrl) { String lastResp = null; int maxAttempts = 5; for (int i = 0; i < maxAttempts; i++) { String key = gdKeyManager.getKey(); String url = baseUrl + (baseUrl.contains("?") ? "&" : "?") + "key=" + key; String resp = restTemplate.getForObject(url, String.class); lastResp = resp; JSONObject jsonObject; try { jsonObject = JSON.parseObject(resp); } catch (Exception e) { return resp; } if (GDKeyManager.isWebKeyExhausted(jsonObject)) { gdKeyManager.reportExhausted(key); continue; } return resp; } return lastResp; } /** @@ -55,9 +82,9 @@ return map; } String url = "https://restapi.amap.com/v3/geocode/geo?key=" + key + "&output=JSON"; String url = "https://restapi.amap.com/v3/geocode/geo?output=JSON"; url += "&address=" + province + (ToolUtil.isNotEmpty(city) ? city : "") + (ToolUtil.isNotEmpty(county) ? county : "") + (ToolUtil.isNotEmpty(address) ? address : ""); String forObject = restTemplate.getForObject(url, String.class); String forObject = doGetWithRetry(url); JSONObject jsonObject = JSON.parseObject(forObject); String status = jsonObject.getString("status"); List<String> list = new ArrayList<>(); @@ -85,8 +112,8 @@ * @throws Exception */ public Map<String, String> geocode(String lon, String lan) throws Exception{ String url = "https://restapi.amap.com/v3/geocode/regeo?key=" + key + "&location=" + lon + "," + lan; String forObject = restTemplate.getForObject(url, String.class); String url = "https://restapi.amap.com/v3/geocode/regeo?location=" + lon + "," + lan; String forObject = doGetWithRetry(url); JSONObject jsonObject = JSON.parseObject(forObject); Map<String, String> map = new HashMap<>();