mi-assessment/server/MiAssessment/src/MiAssessment.Core/Services/IpLocationService.cs
2026-02-03 14:25:01 +08:00

189 lines
6.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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