using System; using System.Diagnostics; using System.Globalization; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.TencentLocation; using NCC.FriendlyException; using Newtonsoft.Json.Linq; namespace NCC.Extend { /// /// 腾讯位置服务(逆地址解析) /// [ApiDescriptionSettings(Tag = "Extend", Name = "LqTencentLocation", Order = 210)] [Route("api/Extend/[controller]")] [ApiController] public class LqTencentLocationService : IDynamicApiController, ITransient { private readonly IConfiguration _configuration; private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; /// /// 初始化腾讯位置服务 /// /// 配置 /// HttpClient 工厂 /// 日志 public LqTencentLocationService( IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger logger) { _configuration = configuration; _httpClientFactory = httpClientFactory; _logger = logger; } /// /// 腾讯位置服务逆地址解析(经纬度 → 文字地址) /// /// /// 说明: /// - 后端从配置读取腾讯位置服务 Key,不下发到前端 /// - 调用腾讯接口:/ws/geocoder/v1/(逆地址解析) /// /// 示例请求: /// ```json /// { /// "latitude": 30.572269, /// "longitude": 104.066541 /// } /// ``` /// /// 参数说明: /// - latitude: 纬度(-90~90) /// - longitude: 经度(-180~180) /// /// 纬度 /// 经度 /// 原始坐标与解析后的地址 /// 解析成功 /// 参数错误 / Key 缺失 / 腾讯接口业务错误 /// 服务器内部错误 [HttpGet("ReverseGeocode")] public async Task ReverseGeocode( [FromQuery] decimal? latitude, [FromQuery] decimal? longitude) { if (!latitude.HasValue || !longitude.HasValue) throw NCCException.Oh("latitude/longitude 不能为空"); if (latitude.Value < -90 || latitude.Value > 90) throw NCCException.Oh("latitude 超出范围(-90~90)"); if (longitude.Value < -180 || longitude.Value > 180) throw NCCException.Oh("longitude 超出范围(-180~180)"); var key = _configuration["NCC_App:TencentLbs:Key"] ?? _configuration["NCC_APP:TencentLbs:Key"]; if (string.IsNullOrWhiteSpace(key)) throw NCCException.Oh("腾讯位置服务 Key 未配置,请设置配置项 NCC_App:TencentLbs:Key(或环境变量映射)"); var timeoutSecondsRaw = _configuration["NCC_App:TencentLbs:TimeoutSeconds"] ?? _configuration["NCC_APP:TencentLbs:TimeoutSeconds"]; var timeoutSeconds = 5; if (!string.IsNullOrWhiteSpace(timeoutSecondsRaw) && int.TryParse(timeoutSecondsRaw, out var ts) && ts > 0) timeoutSeconds = ts; var latStr = latitude.Value.ToString(CultureInfo.InvariantCulture); var lngStr = longitude.Value.ToString(CultureInfo.InvariantCulture); var url = $"https://apis.map.qq.com/ws/geocoder/v1/?location={latStr},{lngStr}&key={Uri.EscapeDataString(key)}&get_poi=0"; var sw = Stopwatch.StartNew(); try { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); var client = _httpClientFactory.CreateClient(); using var req = new HttpRequestMessage(HttpMethod.Get, url); using var resp = await client.SendAsync(req, HttpCompletionOption.ResponseContentRead, cts.Token); var body = await resp.Content.ReadAsStringAsync(cts.Token); if (!resp.IsSuccessStatusCode) { _logger.LogWarning( "腾讯逆地址解析HTTP失败 status={Status} lat={Lat} lng={Lng} costMs={CostMs} body={Body}", (int)resp.StatusCode, latStr, lngStr, sw.ElapsedMilliseconds, Truncate(body, 1200)); throw NCCException.Oh($"腾讯逆地址解析 HTTP 请求失败:{(int)resp.StatusCode}"); } var json = JObject.Parse(body); var status = json["status"]?.Value() ?? -1; var message = json["message"]?.ToString() ?? ""; if (status != 0) { _logger.LogWarning( "腾讯逆地址解析业务失败 status={Status} msg={Msg} lat={Lat} lng={Lng} costMs={CostMs} body={Body}", status, message, latStr, lngStr, sw.ElapsedMilliseconds, Truncate(body, 1200)); throw NCCException.Oh($"腾讯逆地址解析失败:{(message.Length > 0 ? message : "未知错误")}"); } var address = json["result"]?["address"]?.ToString(); if (string.IsNullOrWhiteSpace(address)) { _logger.LogWarning( "腾讯逆地址解析返回无address lat={Lat} lng={Lng} costMs={CostMs} body={Body}", latStr, lngStr, sw.ElapsedMilliseconds, Truncate(body, 1200)); throw NCCException.Oh("腾讯逆地址解析返回地址为空"); } _logger.LogInformation( "腾讯逆地址解析成功 lat={Lat} lng={Lng} costMs={CostMs}", latStr, lngStr, sw.ElapsedMilliseconds); return new TencentReverseGeocodeOutput { location = new TencentLocationDto { latitude = latitude.Value, longitude = longitude.Value }, address = address }; } catch (OperationCanceledException ex) { _logger.LogWarning( ex, "腾讯逆地址解析超时 lat={Lat} lng={Lng} costMs={CostMs} timeoutSeconds={TimeoutSeconds}", latStr, lngStr, sw.ElapsedMilliseconds, timeoutSeconds); throw NCCException.Oh($"腾讯逆地址解析超时({timeoutSeconds}s)"); } catch (AppFriendlyException) { throw; } catch (Exception ex) { _logger.LogError( ex, "腾讯逆地址解析异常 lat={Lat} lng={Lng} costMs={CostMs}", latStr, lngStr, sw.ElapsedMilliseconds); throw NCCException.Oh($"腾讯逆地址解析异常:{ex.Message}"); } } private static string Truncate(string text, int maxLen) { if (string.IsNullOrEmpty(text)) return text; return text.Length <= maxLen ? text : text.Substring(0, maxLen) + "..."; } } }