189 lines
6.3 KiB
C#
189 lines
6.3 KiB
C#
using System.Text.Json;
|
||
using MiAssessment.Core.Interfaces;
|
||
using MiAssessment.Model.Models.Auth;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
namespace MiAssessment.Core.Services;
|
||
|
||
/// <summary>
|
||
/// IP地理位置服务实现 - 使用高德地图API
|
||
/// </summary>
|
||
public class IpLocationService : IIpLocationService
|
||
{
|
||
private readonly HttpClient _httpClient;
|
||
private readonly ILogger<IpLocationService> _logger;
|
||
private readonly AmapSettings _amapSettings;
|
||
|
||
// 高德地图IP定位API端点
|
||
private const string AmapIpLocationUrl = "https://restapi.amap.com/v3/ip";
|
||
|
||
public IpLocationService(HttpClient httpClient, ILogger<IpLocationService> logger, AmapSettings amapSettings)
|
||
{
|
||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||
_amapSettings = amapSettings ?? throw new ArgumentNullException(nameof(amapSettings));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据IP地址获取地理位置信息
|
||
/// </summary>
|
||
/// <param name="ip">IP地址</param>
|
||
/// <returns>IP地理位置结果</returns>
|
||
public async Task<IpLocationResult> 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 = "系统错误"
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否为本地IP地址
|
||
/// </summary>
|
||
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.");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从JSON元素中获取字符串值,如果是空数组则返回null
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|