using System.Text.Json; using MiAssessment.Core.Interfaces; using MiAssessment.Model.Models.Auth; using Microsoft.Extensions.Logging; namespace MiAssessment.Core.Services; /// /// IP地理位置服务实现 - 使用高德地图API /// public class IpLocationService : IIpLocationService { private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly AmapSettings _amapSettings; // 高德地图IP定位API端点 private const string AmapIpLocationUrl = "https://restapi.amap.com/v3/ip"; public IpLocationService(HttpClient httpClient, ILogger logger, AmapSettings amapSettings) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _amapSettings = amapSettings ?? throw new ArgumentNullException(nameof(amapSettings)); } /// /// 根据IP地址获取地理位置信息 /// /// IP地址 /// IP地理位置结果 public async Task GetLocationAsync(string ip) { if (string.IsNullOrWhiteSpace(ip)) { _logger.LogWarning("GetLocationAsync called with empty IP address"); return new IpLocationResult { Success = false, ErrorMessage = "IP地址不能为空" }; } // 检查是否为本地IP地址 if (IsLocalIpAddress(ip)) { _logger.LogInformation("Local IP address detected: {Ip}, returning empty location", ip); return new IpLocationResult { Success = true, Province = null, City = null, Adcode = null }; } try { var url = $"{AmapIpLocationUrl}?key={_amapSettings.ApiKey}&ip={ip}"; _logger.LogInformation("Calling Amap API to get location for IP: {Ip}", ip); var response = await _httpClient.GetAsync(url); var content = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { _logger.LogError("Amap API returned error status {StatusCode}: {Content}", response.StatusCode, content); return new IpLocationResult { Success = false, ErrorMessage = "高德地图API调用失败" }; } using var jsonDoc = JsonDocument.Parse(content); var root = jsonDoc.RootElement; // 检查API返回状态 var status = root.TryGetProperty("status", out var statusProp) ? statusProp.GetString() : null; if (status != "1") { var info = root.TryGetProperty("info", out var infoProp) ? infoProp.GetString() : "未知错误"; _logger.LogWarning("Amap API returned error: {Info}", info); return new IpLocationResult { Success = false, ErrorMessage = $"IP定位失败: {info}" }; } // 提取省份、城市和区域编码 var province = GetStringOrNull(root, "province"); var city = GetStringOrNull(root, "city"); var adcode = GetStringOrNull(root, "adcode"); _logger.LogInformation("Successfully retrieved location for IP {Ip}: Province={Province}, City={City}, Adcode={Adcode}", ip, province, city, adcode); return new IpLocationResult { Success = true, Province = province, City = city, Adcode = adcode }; } catch (HttpRequestException ex) { _logger.LogError(ex, "HTTP request error when calling Amap API for IP: {Ip}", ip); return new IpLocationResult { Success = false, ErrorMessage = "网络连接失败" }; } catch (JsonException ex) { _logger.LogError(ex, "JSON parsing error when processing Amap API response for IP: {Ip}", ip); return new IpLocationResult { Success = false, ErrorMessage = "响应数据格式错误" }; } catch (Exception ex) { _logger.LogError(ex, "Unexpected error when calling Amap API for IP: {Ip}", ip); return new IpLocationResult { Success = false, ErrorMessage = "系统错误" }; } } /// /// 检查是否为本地IP地址 /// private static bool IsLocalIpAddress(string ip) { return ip == "127.0.0.1" || ip == "::1" || ip.StartsWith("192.168.") || ip.StartsWith("10.") || ip.StartsWith("172.16.") || ip.StartsWith("172.17.") || ip.StartsWith("172.18.") || ip.StartsWith("172.19.") || ip.StartsWith("172.20.") || ip.StartsWith("172.21.") || ip.StartsWith("172.22.") || ip.StartsWith("172.23.") || ip.StartsWith("172.24.") || ip.StartsWith("172.25.") || ip.StartsWith("172.26.") || ip.StartsWith("172.27.") || ip.StartsWith("172.28.") || ip.StartsWith("172.29.") || ip.StartsWith("172.30.") || ip.StartsWith("172.31."); } /// /// 从JSON元素中获取字符串值,如果是空数组则返回null /// private static string? GetStringOrNull(JsonElement element, string propertyName) { if (!element.TryGetProperty(propertyName, out var prop)) { return null; } // 高德API在某些情况下会返回空数组[]而不是字符串 if (prop.ValueKind == JsonValueKind.Array) { return null; } if (prop.ValueKind == JsonValueKind.String) { var value = prop.GetString(); return string.IsNullOrWhiteSpace(value) ? null : value; } return null; } }