|
|
@@ -0,0 +1,108 @@
|
|
|
+package org.dromara.risk.controller;
|
|
|
+
|
|
|
+import cn.dev33.satoken.annotation.SaIgnore;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestParam;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import java.net.URI;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.net.http.HttpClient;
|
|
|
+import java.net.http.HttpRequest;
|
|
|
+import java.net.http.HttpResponse;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 腾讯地图 WebService API 代理(绕过浏览器 CORS 限制)
|
|
|
+ * - /risk/map/search?keyword=... POI 搜索
|
|
|
+ * - /risk/map/reverse?lat=...&lng=... 逆地理(经纬度→地址)
|
|
|
+ * - /risk/map/geocode?address=... 地理编码(地址→经纬度)
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@SaIgnore
|
|
|
+@RestController
|
|
|
+@RequiredArgsConstructor
|
|
|
+@RequestMapping("/risk/map")
|
|
|
+public class MapProxyController {
|
|
|
+
|
|
|
+ @Value("${risk.qqmap.key:${qqmap.key:}}")
|
|
|
+ private String qqmapKey;
|
|
|
+
|
|
|
+ private static final HttpClient HTTP = HttpClient.newBuilder()
|
|
|
+ .connectTimeout(Duration.ofSeconds(5))
|
|
|
+ .build();
|
|
|
+ private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
|
+
|
|
|
+ @GetMapping("/search")
|
|
|
+ public Map<String, Object> search(@RequestParam("keyword") String keyword,
|
|
|
+ @RequestParam(value = "region", required = false) String region) {
|
|
|
+ if (qqmapKey == null || qqmapKey.isBlank()) {
|
|
|
+ return Map.of("status", -1, "message", "后端未配置 risk.qqmap.key");
|
|
|
+ }
|
|
|
+ StringBuilder sb = new StringBuilder("https://apis.map.qq.com/ws/place/v1/search")
|
|
|
+ .append("?keyword=").append(enc(keyword))
|
|
|
+ .append("&page_size=5");
|
|
|
+ // 腾讯 place v1 强制要 boundary,不传就 348
|
|
|
+ if (region != null && !region.isBlank()) {
|
|
|
+ sb.append("&boundary=region(").append(enc(region)).append(",0)");
|
|
|
+ } else {
|
|
|
+ sb.append("&boundary=region(中国,1)");
|
|
|
+ }
|
|
|
+ sb.append("&key=").append(enc(qqmapKey));
|
|
|
+ return call(sb.toString(), "搜索");
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("/reverse")
|
|
|
+ public Map<String, Object> reverse(@RequestParam("lat") Double lat, @RequestParam("lng") Double lng) {
|
|
|
+ if (qqmapKey == null || qqmapKey.isBlank()) {
|
|
|
+ return Map.of("status", -1, "message", "后端未配置 risk.qqmap.key");
|
|
|
+ }
|
|
|
+ String url = "https://apis.map.qq.com/ws/geocoder/v1/"
|
|
|
+ + "?location=" + lat + "," + lng
|
|
|
+ + "&get_poi=0"
|
|
|
+ + "&key=" + enc(qqmapKey);
|
|
|
+ return call(url, "逆地理");
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("/geocode")
|
|
|
+ public Map<String, Object> geocode(@RequestParam("address") String address) {
|
|
|
+ if (qqmapKey == null || qqmapKey.isBlank()) {
|
|
|
+ return Map.of("status", -1, "message", "后端未配置 risk.qqmap.key");
|
|
|
+ }
|
|
|
+ String url = "https://apis.map.qq.com/ws/geocoder/v1/"
|
|
|
+ + "?address=" + enc(address)
|
|
|
+ + "&key=" + enc(qqmapKey);
|
|
|
+ return call(url, "地理编码");
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private Map<String, Object> call(String url, String action) {
|
|
|
+ log.info("[map-proxy] {} -> {}", action, url);
|
|
|
+ try {
|
|
|
+ HttpRequest req = HttpRequest.newBuilder(URI.create(url))
|
|
|
+ .timeout(Duration.ofSeconds(8))
|
|
|
+ .header("User-Agent", "ruoyi-risk/5.6.2")
|
|
|
+ .GET()
|
|
|
+ .build();
|
|
|
+ HttpResponse<String> resp = HTTP.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
|
|
+ if (resp.statusCode() != 200) {
|
|
|
+ return Map.of("status", resp.statusCode(), "message", action + "失败: HTTP " + resp.statusCode());
|
|
|
+ }
|
|
|
+ return MAPPER.readValue(resp.body(), Map.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("腾讯地图 {} 失败: {}", action, e.getMessage());
|
|
|
+ return Map.of("status", -1, "message", action + "失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String enc(String s) {
|
|
|
+ return URLEncoder.encode(s, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+}
|