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;
}
}