Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.1.0</Version>
</PropertyGroup>

<PropertyGroup>
<PackageTags>Bootstrap Blazor WebAssembly wasm UI Components JuHe Ip Locator</PackageTags>
<Description>Bootstrap UI components extensions of JuHeIpLocator</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="$(BBVersion)" />
<PackageReference Include="BootstrapBlazor" Version="10.4.0" />
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component hardcodes BootstrapBlazor version 10.4.0 while every other component in the repository uses $(BBVersion) (currently 10.0.0, defined in src/Directory.Build.props:25). This breaks the convention for centralized dependency version management and could lead to version inconsistencies across the solution. Consider using $(BBVersion) here as well, or updating BBVersion centrally if a newer version is needed.

Suggested change
<PackageReference Include="BootstrapBlazor" Version="10.4.0" />
<PackageReference Include="BootstrapBlazor" Version="$(BBVersion)" />

Copilot uses AI. Check for mistakes.
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

Expand All @@ -7,12 +7,14 @@
namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// BootstrapBlazor 服务扩展类
/// <para lang="zh">BootstrapBlazor 服务扩展类</para>
/// <para lang="en">BootstrapBlazor service extensions</para>
/// </summary>
public static class BootstrapBlazoJuHeIpLocatorExtensions
public static class BootstrapBlazorJuHeIpLocatorExtensions
{
/// <summary>
/// 添加 AzureOpenAIService 服务
/// <para lang="zh">添加聚合搜索引擎 IP 定位器服务</para>
/// <para lang="en">Adds JuHe IP locator service</para>
/// </summary>
/// <param name="services"></param>
public static IServiceCollection AddBootstrapBlazorJuHeIpLocatorService(this IServiceCollection services)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// 聚合搜索引擎 IP 定位器配置类
/// <para lang="zh">聚合搜索引擎 IP 定位器配置类</para>
/// <para lang="en">JuHe IP locator options</para>
/// </summary>
class JuHeIpLocatorOptions
{
/// <summary>
/// 聚合搜索引擎 IP 定位器 AppKey
/// <para lang="zh">获得/设置 聚合搜索引擎 IP 定位器 AppKey</para>
/// <para lang="en">Gets or sets the JuHe IP locator AppKey</para>
/// </summary>
public string Key { get; set; } = "";
public string? Key { get; set; }

/// <summary>
/// 聚合搜索引擎 IP 定位器请求地址
/// <para lang="zh">获得/设置 聚合搜索引擎 IP 定位器请求地址</para>
/// <para lang="en">Gets or sets the JuHe IP locator request URL</para>
/// </summary>
public string Url { get; set; } = "http://apis.juhe.cn/ip/ipNew";
public string? Url { get; set; }

/// <summary>
/// 聚合搜索引擎 IP 定位器请求超时时间 默认 5 秒
/// <para lang="zh">获得/设置 聚合搜索引擎 IP 定位器请求超时时间 默认 5 秒</para>
/// <para lang="en">Gets or sets the JuHe IP locator request timeout, default is 5 seconds</para>
/// </summary>
public TimeSpan Timeout { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net.Http.Json;
using System.Text.Json.Serialization;

namespace BootstrapBlazor.Components;

/// <summary>
/// 聚合搜索引擎 IP 定位器
/// <para lang="zh">聚合搜索引擎 IP 定位器</para>
/// <para lang="en">JuHe IP locator provider</para>
/// </summary>
/// <param name="httpClientFactory"></param>
/// <param name="options"></param>
Expand All @@ -26,6 +25,8 @@ class JuHeIpLocatorProvider(IHttpClientFactory httpClientFactory,

private JuHeIpLocatorOptions? _options;

private const string Url = "http://apis.juhe.cn/ip/ipNewV3";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Consider using HTTPS instead of HTTP for the default JuHe API URL to avoid sending IP data over an unencrypted channel.

This constant currently uses plain HTTP. If ipNewV3 supports HTTPS, please change it to https:// so IP and key data aren’t sent in clear text and to meet modern security practices. If HTTPS truly isn’t available, consider documenting that limitation where this URL is defined.

Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default URL uses http:// (unencrypted) which means the API key in the query parameter key=... will be transmitted in plaintext. Consider using https:// instead if the JuHe API supports it, to protect the API key from being intercepted in transit.

Suggested change
private const string Url = "http://apis.juhe.cn/ip/ipNewV3";
private const string Url = "https://apis.juhe.cn/ip/ipNewV3";

Copilot uses AI. Check for mistakes.

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand All @@ -52,164 +53,42 @@ private JuHeIpLocatorOptions GetOptions()
{
var options = juHeIpLocatorOptions.Value;

try
if (string.IsNullOrEmpty(options.Key))
{
if (string.IsNullOrEmpty(options.Key))
{
throw new InvalidOperationException($"{nameof(JuHeIpLocatorOptions)}:Key not value in appsettings configuration file. 未配置 {nameof(JuHeIpLocatorOptions)}:Key 请在 appsettings.json 中配置 {nameof(JuHeIpLocatorOptions)}:Key");
}
if (string.IsNullOrEmpty(options.Url))
{
options.Url = "http://apis.juhe.cn/ip/ipNewV3";
}
LastError = $"{nameof(JuHeIpLocatorOptions)}:Key not value in appsettings configuration file. 未配置 {nameof(JuHeIpLocatorOptions)}:Key 请在 appsettings.json 中配置 {nameof(JuHeIpLocatorOptions)}:Key";
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The English portion of the error message "Key not value in appsettings configuration file" is grammatically incorrect. It should read something like "Key has no value in appsettings configuration file" or "Key is not configured in appsettings configuration file".

Suggested change
LastError = $"{nameof(JuHeIpLocatorOptions)}:Key not value in appsettings configuration file. 未配置 {nameof(JuHeIpLocatorOptions)}:Key 请在 appsettings.json 中配置 {nameof(JuHeIpLocatorOptions)}:Key";
LastError = $"{nameof(JuHeIpLocatorOptions)}:Key has no value in appsettings configuration file. 未配置 {nameof(JuHeIpLocatorOptions)}:Key 请在 appsettings.json 中配置 {nameof(JuHeIpLocatorOptions)}:Key";

Copilot uses AI. Check for mistakes.
Log(LastError);

}
Comment on lines +56 to 61
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When Key is empty/null, the method logs an error but still returns the options object. The caller LocateByIp then proceeds to construct a URL with an empty key and makes a pointless HTTP request that will fail. Previously, this code threw an InvalidOperationException, which would prevent the HTTP call. Consider either returning early (e.g., returning null and checking in LocateByIp) or short-circuiting the LocateByIp call when Key is missing, to avoid unnecessary network requests.

Copilot uses AI. Check for mistakes.
catch (Exception ex)
if (string.IsNullOrEmpty(options.Url))
{
logger.LogError(ex, "{GetOptions} failed", nameof(GetOptions));
options.Url = Url;
}
return options;
}

/// <summary>
/// 请求获得地理位置接口方法
/// <para lang="zh">请求获得地理位置接口方法</para>
/// <para lang="en">Fetches the geolocation data</para>
/// </summary>
/// <param name="url"></param>
/// <param name="client"></param>
/// <param name="token"></param>
/// <returns></returns>
protected virtual async Task<string?> Fetch(string url, HttpClient client, CancellationToken token)
{
var result = await client.GetFromJsonAsync<LocationResult>(url, token);
var result = await client.GetFromJsonAsync<JuHeLocationResult>(url, token);
if (result != null && result.ErrorCode != 0)
{
logger.LogError("ErrorCode: {ErrorCode} Reason: {Reason}", result.ErrorCode, result.Reason);
LastError = $"ErrorCode: {result.ErrorCode} Reason: {result.Reason}";
Log(LastError);
}
return result?.ToString();
}

/// <summary>
/// LocationResult 结构体
/// </summary>
class LocationResult
private void Log(string? message)
{
/// <summary>
/// 获得/设置 结果状态返回码 为 查询成功 时通讯正常
/// </summary>
public string? Reason { get; set; }

/// <summary>
/// 获得/设置 错误码
/// </summary>
[JsonPropertyName("error_code")]
public int ErrorCode { get; set; }

/// <summary>
/// 获得/设置 定位信息
/// </summary>
public LocationData? Result { get; set; }

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
public override string? ToString()
if (logger.IsEnabled(LogLevel.Error))
{
string? ret = null;
if (ErrorCode == 0)
{
ret = Result?.Country == "中国"
? $"{Result?.Prov}{Result?.City}{Result?.District} {Result?.Isp}"
: $"{Result?.Continent} {Result?.Country} {Result?.City}";
}
return ret;
logger.LogError("{message}", message);
}
}

class LocationData
{
/// <summary>
/// 获得/设置 州
/// </summary>
public string? Continent { get; set; }

/// <summary>
/// 获得/设置 国家
/// </summary>
public string? Country { get; set; }

/// <summary>
/// 获得/设置 邮编
/// </summary>
public string? ZipCode { get; set; }

/// <summary>
/// 获得/设置 时区
/// </summary>
public string? TimeZone { get; set; }

/// <summary>
/// 获得/设置 精度
/// </summary>
public string? Accuracy { get; set; }

/// <summary>
/// 获得/设置 所属
/// </summary>
public string? Owner { get; set; }

/// <summary>
/// 获得/设置 运营商
/// </summary>
public string? Isp { get; set; }

/// <summary>
/// 获得/设置 来源
/// </summary>
public string? Source { get; set; }

/// <summary>
/// 获得/设置 区号
/// </summary>
public string? AreaCode { get; set; }

/// <summary>
/// 获得/设置 行政区划代码
/// </summary>
public string? AdCode { get; set; }

/// <summary>
/// 获得/设置 国家代码
/// </summary>
public string? AsNumber { get; set; }

/// <summary>
/// 获得/设置 经度
/// </summary>
public string? Lat { get; set; }

/// <summary>
/// 获得/设置 纬度
/// </summary>
public string? Lng { get; set; }

/// <summary>
/// 获得/设置 半径
/// </summary>
public string? Radius { get; set; }

/// <summary>
/// 获得/设置 省份
/// </summary>
public string? Prov { get; set; }

/// <summary>
/// 获得/设置 城市
/// </summary>
public string? City { get; set; }

/// <summary>
/// 获得/设置 区县
/// </summary>
public string? District { get; set; }
}
}
Loading