LqTencentLocationService.cs
7.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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
{
/// <summary>
/// 腾讯位置服务(逆地址解析)
/// </summary>
[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<LqTencentLocationService> _logger;
/// <summary>
/// 初始化腾讯位置服务
/// </summary>
/// <param name="configuration">配置</param>
/// <param name="httpClientFactory">HttpClient 工厂</param>
/// <param name="logger">日志</param>
public LqTencentLocationService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
ILogger<LqTencentLocationService> logger)
{
_configuration = configuration;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
/// <summary>
/// 腾讯位置服务逆地址解析(经纬度 → 文字地址)
/// </summary>
/// <remarks>
/// 说明:
/// - 后端从配置读取腾讯位置服务 Key,不下发到前端
/// - 调用腾讯接口:/ws/geocoder/v1/(逆地址解析)
///
/// 示例请求:
/// ```json
/// {
/// "latitude": 30.572269,
/// "longitude": 104.066541
/// }
/// ```
///
/// 参数说明:
/// - latitude: 纬度(-90~90)
/// - longitude: 经度(-180~180)
/// </remarks>
/// <param name="latitude">纬度</param>
/// <param name="longitude">经度</param>
/// <returns>原始坐标与解析后的地址</returns>
/// <response code="200">解析成功</response>
/// <response code="400">参数错误 / Key 缺失 / 腾讯接口业务错误</response>
/// <response code="500">服务器内部错误</response>
[HttpGet("ReverseGeocode")]
public async Task<TencentReverseGeocodeOutput> 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<int?>() ?? -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) + "...";
}
}
}