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) + "...";
}
}
}