diff --git a/Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj b/Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj
index d436744b..05bec77b 100644
--- a/Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj
+++ b/Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj
@@ -42,6 +42,7 @@
+
-
+
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Application/Configuration/HttpRemotes.json b/Admin.NET/Admin.NET.Application/Configuration/HttpRemotes.json
index d6c49a10..bd28fc8d 100644
--- a/Admin.NET/Admin.NET.Application/Configuration/HttpRemotes.json
+++ b/Admin.NET/Admin.NET.Application/Configuration/HttpRemotes.json
@@ -9,7 +9,7 @@
"Account": "",
"Password": ""
},
- "HttpBin": { // 测试接口服务配置
+ "HttpBin": { // 获取Ip
"EnabledLog": true,
"UseCookies": false,
"EnabledProxy": false,
@@ -22,6 +22,17 @@
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
},
"Timeout": 5
+ },
+ "WorkWeixin": { // 企业微信
+ "EnabledLog": true,
+ "UseCookies": false,
+ "EnabledProxy": false,
+ "HttpName": "WORK_WEIXIN",
+ "BaseAddress": "https://qyapi.weixin.qq.com",
+ "Headers": {
+ "User-Agent": "WORK_WEIXIN"
+ },
+ "Timeout": 60
}
}
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs b/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs
index 1b546135..6699f4ef 100644
--- a/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs
+++ b/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs
@@ -4,6 +4,7 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+using Admin.NET.Plugin.WorkWeixin;
using Furion.Localization;
using Furion.Logging;
using Microsoft.Extensions.Logging;
@@ -19,11 +20,13 @@ public class TestService : IDynamicApiController
private readonly UserManager _userManager;
private readonly IEventPublisher _eventPublisher;
private readonly ILogger _logger;
+ private readonly WorkWxUserService _workWxUserService;
- public TestService(UserManager userManager, IEventPublisher eventPublisher, ILoggerFactory loggerFactory)
+ public TestService(UserManager userManager, IEventPublisher eventPublisher, WorkWxUserService workWxUserService, ILoggerFactory loggerFactory)
{
_userManager = userManager;
_eventPublisher = eventPublisher;
+ _workWxUserService = workWxUserService;
_logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName); // 日志过滤标识(会写入数据库)
}
@@ -68,6 +71,15 @@ public class TestService : IDynamicApiController
_logger.LogWarning($"信息{DateTime.Now}");
}
+ ///
+ /// 企业微信测试
+ ///
+ ///
+ public async Task> UserIdList()
+ {
+ return (await _workWxUserService.GetUserIdList())?.DeptUser;
+ }
+
///
/// 匿名上传文件测试
///
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.WorkWeixin/Proxy/AppChat/Dto/AppChatHttpOutput.cs b/Admin.NET/Admin.NET.Core/Attribute/CustomJsonPropertyAttribute.cs
similarity index 70%
rename from Admin.NET/Plugins/Admin.NET.Plugin.WorkWeixin/Proxy/AppChat/Dto/AppChatHttpOutput.cs
rename to Admin.NET/Admin.NET.Core/Attribute/CustomJsonPropertyAttribute.cs
index 888912f5..a3a58a01 100644
--- a/Admin.NET/Plugins/Admin.NET.Plugin.WorkWeixin/Proxy/AppChat/Dto/AppChatHttpOutput.cs
+++ b/Admin.NET/Admin.NET.Core/Attribute/CustomJsonPropertyAttribute.cs
@@ -4,14 +4,16 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-namespace Admin.NET.Plugin.WorkWeixin.Proxy;
+namespace Admin.NET.Core;
-public class CreatAppChatOutput : BaseWorkOutput
+///
+/// 自定义Json转换字段名
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class CustomJsonPropertyAttribute(string name) : Attribute
{
///
- /// 群聊的唯一标志
+ /// 序列化名称
///
- [JsonProperty("chatid")]
- [JsonPropertyName("chatid")]
- public string ChatId { get; set; }
+ public string Name { get; } = name;
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Attribute/DateTimeRangeAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/DateTimeRangeAttribute.cs
new file mode 100644
index 00000000..1be389fa
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/Attribute/DateTimeRangeAttribute.cs
@@ -0,0 +1,277 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+///
+/// 用于校验 List<DateTime> 时间范围参数的自定义验证特性
+/// 要求列表包含两个有效 DateTime 值,构成一个时间范围
+///
+///
+/// 示例 1:基础时间范围(起始 < 结束)
+///
+/// public class QueryModel
+/// {
+/// [DateTimeRange]
+/// public List<DateTime> TimeRange { get; set; }
+/// }
+/// // 合法值: [2025-01-01 00:00:00, 2025-01-07 23:59:59]
+///
+///
+/// 示例 2:仅允许日期(时间部分必须为 00:00:00)
+///
+/// [DateTimeRange(DateOnly = true)]
+/// public List<DateTime> DateRange { get; set; }
+/// // 合法值: [2025-01-01 00:00:00, 2025-01-31 00:00:00]
+/// // 非法值: [2025-01-01 08:00:00, ...] → 报错
+///
+///
+/// 示例 3:最小间隔 1 小时(60分钟)
+///
+/// [DateTimeRange(MinInterval = 60)]
+/// public List<DateTime> Duration { get; set; }
+/// // 合法值: 间隔 ≥ 1小时(如 2小时 → "2小时")
+/// // 错误提示: 时间间隔必须至少为 1小时
+///
+///
+/// 示例 4:最大间隔 7 天
+///
+/// [DateTimeRange(MaxInterval = 7 * 24 * 60)] // 7天 = 10080分钟
+/// public List<DateTime> WeeklyRange { get; set; }
+/// // 合法值: 间隔 ≤ 7天
+/// // 错误提示: 时间间隔不能超过 7天
+///
+///
+/// 示例 5:时间范围必须在过去(且可包含当前时间)
+///
+/// [DateTimeRange(DirectionEnum = TimeDirectionEnum.Past, AllowNow = true)]
+/// public List<DateTime> PastEvents { get; set; }
+/// // 合法值: [2025-08-01, 2025-08-20](若今天是 2025-08-20)
+/// // 非法值: [2025-08-21, ...] → 完全在未来
+///
+///
+/// 示例 6:时间范围必须在未来(不能包含当前时间)
+///
+/// [DateTimeRange(DirectionEnum = TimeDirectionEnum.Future, AllowNow = false)]
+/// public List<DateTime> FutureTasks { get; set; }
+/// // 合法值: [2025-08-21 00:00, 2025-08-30 23:59]
+/// // 非法值: [2025-08-20 10:00, ...] → 包含当前时间
+///
+///
+/// 示例 7:允许起止时间相等(单日查询)
+///
+/// [DateTimeRange(AllowEqual = true, DateOnly = true)]
+/// public List<DateTime> SingleDay { get; set; }
+/// // 合法值: [2025-08-20 00:00:00, 2025-08-20 00:00:00]
+///
+///
+/// 示例 8:单元测试中固定“当前时间”
+///
+/// // 用于测试未来/过去逻辑
+/// var attribute = new DateTimeRangeAttribute
+/// {
+/// DirectionEnum = TimeDirectionEnum.Future,
+/// NowProvider = () => new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero)
+/// };
+///
+///
+[SuppressSniffer]
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+public class DateTimeRangeAttribute : ValidationAttribute
+{
+ ///
+ /// 获取或设置时间方向约束
+ ///
+ public TimeDirectionEnum DirectionEnum { get; set; } = TimeDirectionEnum.None;
+
+ ///
+ /// 最小时间间隔(分钟) 0表示无限制
+ ///
+ public int MinInterval { get; set; } = 0;
+
+ ///
+ /// 最大时间间隔(分钟) 0表示无限制
+ ///
+ public int MaxInterval { get; set; } = 0;
+
+ ///
+ /// 是否仅允许日期类型(时间部分必须为 00:00:00)
+ ///
+ public bool DateOnly { get; set; } = false;
+
+ ///
+ /// 是否允许起始时间等于结束时间(即零间隔)
+ /// 默认为 false,即起始时间必须小于结束时间
+ ///
+ public bool AllowEqual { get; set; } = false;
+
+ ///
+ /// 是否允许范围包含当前时间
+ /// 当 Direction 为 Future 或 Past 时,此属性可覆盖默认行为
+ ///
+ public bool AllowNow { get; set; } = true;
+
+ ///
+ /// 获取或设置自定义的“现在”时间提供器,用于单元测试或特定时区场景
+ /// 默认为 DateTimeOffset.Now
+ ///
+ public Func NowProvider { get; set; } = () => DateTimeOffset.Now;
+
+ ///
+ /// 获取格式化的当前时间字符串(用于错误消息)
+ ///
+ private string NowString => NowProvider().ToString("yyyy-MM-dd HH:mm:ss");
+
+ ///
+ /// 执行验证逻辑
+ ///
+ protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
+ {
+ if (value == null) return ValidationResult.Success;
+
+ var list = value as List;
+ if (list == null) return new ValidationResult($"'{validationContext.DisplayName}' 必须是日期时间列表类型");
+
+ if (list.Count != 2) return new ValidationResult($"'{validationContext.DisplayName}' 必须包含两个日期时间值");
+
+ var start = list[0];
+ var end = list[1];
+
+ // 验证 DateTime 值的有效性
+ if (!IsValidDateTime(start) || !IsValidDateTime(end)) return new ValidationResult($"'{validationContext.DisplayName}' 包含无效的日期时间值");
+
+ // 检查 DateOnly 约束
+ if (DateOnly)
+ {
+ if (start.TimeOfDay != TimeSpan.Zero) return new ValidationResult($"'{validationContext.DisplayName}' 起始时间需为日期格式(00:00:00),当前为 {start:yyyy-MM-dd HH:mm:ss}");
+
+ if (end.TimeOfDay != TimeSpan.Zero) return new ValidationResult($"'{validationContext.DisplayName}' 结束时间需为日期格式(00:00:00),当前为 {end:yyyy-MM-dd HH:mm:ss}");
+ }
+
+ // 检查时间顺序
+ if (AllowEqual)
+ {
+ if (start > end) return new ValidationResult($"'{validationContext.DisplayName}' 起始时间不能晚于结束时间");
+ }
+ else
+ {
+ if (start >= end) return new ValidationResult($"'{validationContext.DisplayName}' 起始时间必须早于结束时间");
+ }
+
+ // 检查时间方向
+ var now = NowProvider().DateTime;
+ var directionResult = ValidateDirection(start, end, now, validationContext.DisplayName);
+ if (directionResult != null) return directionResult;
+
+ // 检查最小和最大间隔
+ var intervalResult = ValidateTimeIntervals(start, end, validationContext.DisplayName);
+ if (intervalResult != null) return intervalResult;
+
+ return ValidationResult.Success;
+ }
+
+ ///
+ /// 验证时间方向约束
+ ///
+ private ValidationResult? ValidateDirection(DateTime start, DateTime end, DateTime now, string? displayName)
+ {
+ return DirectionEnum switch
+ {
+ TimeDirectionEnum.Future when !AllowNow && (start <= now || end <= now) =>
+ new ValidationResult($"'{displayName}' 必须在 {NowString} 之后"),
+
+ TimeDirectionEnum.Future when AllowNow && (start < now && end < now) =>
+ new ValidationResult($"'{displayName}' 必须是未来时间"),
+
+ TimeDirectionEnum.Past when !AllowNow && (start >= now || end >= now) =>
+ new ValidationResult($"'{displayName}' 必须在 {NowString} 之前"),
+
+ TimeDirectionEnum.Past when AllowNow && (start > now && end > now) =>
+ new ValidationResult($"'{displayName}' 必须是过去时间"),
+
+ _ => null
+ };
+ }
+
+ ///
+ /// 验证最小和最大时间间隔
+ ///
+ private ValidationResult? ValidateTimeIntervals(DateTime start, DateTime end, string? displayName)
+ {
+ var interval = end - start;
+ var totalMinutes = (int)interval.TotalMinutes;
+
+ // 验证最小间隔
+ if (MinInterval > 0)
+ {
+ if (MinInterval < 0) return new ValidationResult($"'{displayName}' 最小间隔不能为负数");
+
+ if (totalMinutes < MinInterval)
+ {
+ var required = TimeSpan.FromMinutes(MinInterval);
+ var actual = TimeSpan.FromMinutes(totalMinutes);
+ return new ValidationResult($"'{displayName}'至少间隔 {required.FormatTimeSpanText(2)}");
+ }
+ }
+
+ // 验证最大间隔
+ if (MaxInterval > 0)
+ {
+ if (MaxInterval < 0) return new ValidationResult($"'{displayName}'最大间隔不能为负数");
+
+ if (totalMinutes > MaxInterval)
+ {
+ var allowed = TimeSpan.FromMinutes(MaxInterval);
+ var actual = TimeSpan.FromMinutes(totalMinutes);
+ return new ValidationResult($"'{displayName}'最多间隔 {allowed.FormatTimeSpanText(2)}");
+ }
+ }
+
+ // 验证最小不能大于最大
+ if (MinInterval > 0 && MaxInterval > 0 && MinInterval > MaxInterval)
+ {
+ var minSpan = TimeSpan.FromMinutes(MinInterval);
+ var maxSpan = TimeSpan.FromMinutes(MaxInterval);
+ return new ValidationResult($"'{displayName}' 最小间隔({minSpan.FormatTimeSpanText(2)})不能大于最大间隔({maxSpan.FormatTimeSpanText(2)})");
+ }
+
+ return null;
+ }
+
+ ///
+ /// 验证 DateTime 值是否在合理范围内
+ ///
+ private static bool IsValidDateTime(DateTime dt)
+ {
+ return dt >= new DateTime(1900, 1, 1) && dt <= new DateTime(2100, 12, 31);
+ }
+}
+
+///
+/// 指定范围必须是未来时间、过去时间,还是不限制
+///
+[SuppressSniffer]
+[Description("时间方向枚举")]
+public enum TimeDirectionEnum
+{
+ ///
+ /// 不限制时间方向
+ ///
+ [Description("不限")]
+ None,
+
+ ///
+ /// 范围必须在当前时间之后(未来)
+ ///
+ [Description("未来")]
+ Future,
+
+ ///
+ /// 范围必须在当前时间之前(过去)
+ ///
+ [Description("过去")]
+ Past
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Attribute/HttpRemoteApiAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/HttpRemoteApiAttribute.cs
new file mode 100644
index 00000000..2a1cac5e
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/Attribute/HttpRemoteApiAttribute.cs
@@ -0,0 +1,61 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+///
+/// 远程请求接口特性
+///
+[SuppressSniffer]
+[AttributeUsage(AttributeTargets.Class)]
+public class HttpRemoteApiAttribute : Attribute
+{
+ ///
+ /// 方法名
+ ///
+ [Required]
+ public string Action { get; set; }
+
+ ///
+ /// 描述
+ ///
+ [Required]
+ public string Desc { get; set; }
+
+ ///
+ /// 请求方式
+ ///
+ [Required]
+ public HttpMethodEnum HttpMethod { get; set; } = HttpMethodEnum.Post;
+
+ ///
+ /// 参数类型
+ ///
+ public HttpParameterTypeEnum? Type { get; set; }
+}
+
+///
+/// 远程请求接口参数类型枚举
+///
+[SuppressSniffer]
+[Description("远程请求接口参数类型枚举")]
+public enum HttpParameterTypeEnum
+{
+ [Description("查询")]
+ Query = 1,
+
+ [Description("表单")]
+ FormData = 2,
+
+ [Description("JSON")]
+ Json = 3,
+
+ [Description("XML")]
+ Xml = 4,
+
+ [Description("自定义")]
+ Custom = 5
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Const/CommonConst.cs b/Admin.NET/Admin.NET.Core/Const/CommonConst.cs
index ef4638e9..2c52438c 100644
--- a/Admin.NET/Admin.NET.Core/Const/CommonConst.cs
+++ b/Admin.NET/Admin.NET.Core/Const/CommonConst.cs
@@ -36,4 +36,15 @@ public class CommonConst
/// 事件-发送异常邮件
///
public const string SendErrorMail = "Send:ErrorMail";
+
+ ///
+ /// 远程请求配置客户端名称键名
+ ///
+ public const string HttpRemoteClientName = "__HTTP_CLIENT_NAME__";
+
+ ///
+ /// 远程请求请求头接口描述键值
+ ///
+ public const string HttpRemoteApiDescHeaderName = "__HTTP_CLIENT_API_DESC__";
+
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Entity/SysLogHttp.cs b/Admin.NET/Admin.NET.Core/Entity/SysLogHttp.cs
index c6eb69be..f8218e50 100644
--- a/Admin.NET/Admin.NET.Core/Entity/SysLogHttp.cs
+++ b/Admin.NET/Admin.NET.Core/Entity/SysLogHttp.cs
@@ -16,6 +16,20 @@ namespace Admin.NET.Core;
[LogTable]
public partial class SysLogHttp : EntityBaseId
{
+ ///
+ /// 客户端名称
+ ///
+ [SugarColumn(ColumnDescription = "客户端名称", Length = 32)]
+ [MaxLength(32)]
+ public string? HttpClientName { get; set; }
+
+ ///
+ /// 接口描述
+ ///
+ [SugarColumn(ColumnDescription = "接口描述", Length = 32)]
+ [MaxLength(32)]
+ public string? HttpApiDesc { get; set; }
+
///
/// 请求方式
///
diff --git a/Admin.NET/Admin.NET.Core/Extension/HttpRemotesExtension.cs b/Admin.NET/Admin.NET.Core/Extension/HttpRemotesExtension.cs
index dcaf2dd3..be696d1c 100644
--- a/Admin.NET/Admin.NET.Core/Extension/HttpRemotesExtension.cs
+++ b/Admin.NET/Admin.NET.Core/Extension/HttpRemotesExtension.cs
@@ -34,4 +34,14 @@ public static class HttpRemotesExtension {
}
return services;
}
+
+ ///
+ /// 设置Http远程服务选项
+ ///
+ public static HttpRequestBuilder SetHttpOptions(this HttpRequestBuilder builder, HttpRemoteItem option, string apiDesc = "")
+ {
+ builder.SetHttpClientName(option.HttpName);
+ builder.WithHeader(CommonConst.HttpRemoteApiDescHeaderName, apiDesc, replace:true);
+ return builder;
+ }
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs b/Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs
index df3c7ef0..420c51ba 100644
--- a/Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs
+++ b/Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs
@@ -55,7 +55,28 @@ public static partial class ObjectExtension
///
public static string ToQueryString(this Dictionary dict, bool urlEncode = true)
{
- return string.Join("&", dict.Select(p => $"{(urlEncode ? p.Key?.UrlEncode() : "")}={(urlEncode ? p.Value?.UrlEncode() : "")}"));
+ return string.Join("&", dict.Select(p => $"{(urlEncode ? p.Key?.UrlEncode() : p.Key)}={(urlEncode ? p.Value?.UrlEncode() : p.Value)}"));
+ }
+
+ ///
+ /// 根据[CustomJsonProperty]特性转换属性为Query参数
+ ///
+ ///
+ ///
+ ///
+ public static string ToCustomJsonPropertyQueryString(this object obj, bool urlEncode = true)
+ {
+ var properties = obj.GetType().GetProperties();
+ return string.Join("&", properties.Select(p =>
+ {
+ var name = p.GetCustomAttribute()?.Name ?? p.Name.ToFirstLetterLowerCase();
+ var val = p.GetValue(obj)?.ToString();
+ if (!urlEncode) return $"{name}={val}";
+
+ val = val?.UrlEncode();
+ name = name?.UrlEncode();
+ return $"{name}={val}";
+ }));
}
///
diff --git a/Admin.NET/Admin.NET.Core/Logging/HttpLoggingHandler.cs b/Admin.NET/Admin.NET.Core/Logging/HttpLoggingHandler.cs
index c59cb126..3a7c780f 100644
--- a/Admin.NET/Admin.NET.Core/Logging/HttpLoggingHandler.cs
+++ b/Admin.NET/Admin.NET.Core/Logging/HttpLoggingHandler.cs
@@ -11,7 +11,6 @@ namespace Admin.NET.Core;
///
public class HttpLoggingHandler : DelegatingHandler, ITransient
{
- private const string HttpName = "__HTTP_CLIENT_NAME__";
private readonly Dictionary _enabledLogMap;
private readonly SysConfigService _sysConfigService;
private readonly IEventPublisher _eventPublisher;
@@ -35,11 +34,17 @@ public class HttpLoggingHandler : DelegatingHandler, ITransient
if (!enabledLog) return await base.SendAsync(request, cancellationToken);
// 判断当前配置日志开关
- request.Options.TryGetValue(HttpName, out var clientName);
+ request.Options.TryGetValue(CommonConst.HttpRemoteClientName, out var clientName);
if (!string.IsNullOrWhiteSpace(clientName)) enabledLog = _enabledLogMap.GetOrDefault(clientName);
if (!enabledLog) return await base.SendAsync(request, cancellationToken);
var sysLogHttp = new SysLogHttp();
+ sysLogHttp.HttpClientName = clientName;
+
+ // 获取接口描述,并移除
+ sysLogHttp.HttpApiDesc = request.Headers.FirstOrDefault(u => u.Key == CommonConst.HttpRemoteApiDescHeaderName).Value?.FirstOrDefault();
+ request.Headers.Remove(CommonConst.HttpRemoteApiDescHeaderName);
+
sysLogHttp.HttpMethod = request.Method.Method;
sysLogHttp.RequestUrl = request.RequestUri?.ToString();
sysLogHttp.RequestHeaders = request.Headers.ToDictionary(u => u.Key, u => u.Value.Join(";")).ToJson();
diff --git a/Admin.NET/Admin.NET.Core/Option/HttpRemotesOptions.cs b/Admin.NET/Admin.NET.Core/Option/HttpRemotesOptions.cs
index 2faf4f6e..c4c3d704 100644
--- a/Admin.NET/Admin.NET.Core/Option/HttpRemotesOptions.cs
+++ b/Admin.NET/Admin.NET.Core/Option/HttpRemotesOptions.cs
@@ -20,6 +20,11 @@ public sealed class HttpRemotesOptions : IConfigurableOptions
/// 获取Ip地址接口
///
public HttpRemoteItem HttpBin { get; set; }
+
+ ///
+ /// 企业微信接口配置
+ ///
+ public HttpRemoteItem WorkWeixin { get; set; }
}
///
diff --git a/Admin.NET/Admin.NET.Core/Service/Const/Dto/ConstOutput.cs b/Admin.NET/Admin.NET.Core/Service/Const/Dto/ConstOutput.cs
index 698f9577..d40ccf99 100644
--- a/Admin.NET/Admin.NET.Core/Service/Const/Dto/ConstOutput.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Const/Dto/ConstOutput.cs
@@ -18,6 +18,11 @@ public class ConstOutput
///
public dynamic Code { get; set; }
+ ///
+ /// 描述
+ ///
+ public string Desc { get; set; }
+
///
/// 扩展字段
///
diff --git a/Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs b/Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs
index 61f59c9b..bb62a440 100644
--- a/Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs
@@ -64,6 +64,7 @@ public class SysConstService : IDynamicApiController, ITransient
.Select(u => new ConstOutput
{
Name = u.Name,
+ Desc = u.GetCustomAttribute()?.Description,
Code = isEnum ? (int)u.GetValue(BindingFlags.Instance)! : u.GetValue(BindingFlags.Instance)
}).ToList();
_sysCacheService.Set(key, constList);
diff --git a/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpInput.cs b/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpInput.cs
index d9c208b0..33c1dea7 100644
--- a/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpInput.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpInput.cs
@@ -87,6 +87,16 @@ public class PageLogHttpInput : BasePageInput
///
public string SearchKey { get; set; }
+ ///
+ /// 客户端名称
+ ///
+ public string HttpClientName { get; set; }
+
+ ///
+ /// 请求接口描述
+ ///
+ public string HttpApiDesc { get; set; }
+
///
/// 请求方式
///
diff --git a/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpOutput.cs b/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpOutput.cs
index aab058f8..e41d70df 100644
--- a/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpOutput.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Log/Dto/LogHttpOutput.cs
@@ -16,6 +16,16 @@ public class PageLogHttpOutput
///
public long? Id { get; set; }
+ ///
+ /// 客户端名称
+ ///
+ public string HttpClientName { get; set; }
+
+ ///
+ /// 请求接口描述
+ ///
+ public string HttpApiDesc { get; set; }
+
///
/// 请求方式
///
diff --git a/Admin.NET/Admin.NET.Core/Service/Log/SysLogHttpService.cs b/Admin.NET/Admin.NET.Core/Service/Log/SysLogHttpService.cs
index 24554c46..ca662981 100644
--- a/Admin.NET/Admin.NET.Core/Service/Log/SysLogHttpService.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Log/SysLogHttpService.cs
@@ -32,7 +32,9 @@ public class SysLogHttpService : IDynamicApiController, ITransient
{
input.Keyword = input.Keyword?.Trim();
var query = _sysLogHttpRep.AsQueryable()
- .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.RequestUrl.Contains(input.Keyword) || u.RequestBody.Contains(input.Keyword) || u.ResponseBody.Contains(input.Keyword) || u.Exception.Contains(input.Keyword))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.HttpClientName.Contains(input.Keyword) || u.HttpApiDesc.Contains(input.Keyword) || u.RequestUrl.Contains(input.Keyword) || u.RequestBody.Contains(input.Keyword) || u.ResponseBody.Contains(input.Keyword) || u.Exception.Contains(input.Keyword))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.HttpClientName), u => u.HttpClientName.Contains(input.HttpClientName.Trim()))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.HttpApiDesc), u => u.HttpApiDesc.Contains(input.HttpApiDesc.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.RequestUrl), u => u.RequestUrl.Contains(input.RequestUrl.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.RequestBody), u => u.RequestBody.Contains(input.RequestBody.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.ResponseBody), u => u.ResponseBody.Contains(input.ResponseBody.Trim()))
@@ -55,6 +57,9 @@ public class SysLogHttpService : IDynamicApiController, ITransient
public async Task> GetList([FromQuery] PageLogHttpInput input)
{
return await _sysLogHttpRep.AsQueryable()
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.HttpClientName.Contains(input.Keyword) || u.HttpApiDesc.Contains(input.Keyword) || u.RequestUrl.Contains(input.Keyword) || u.RequestBody.Contains(input.Keyword) || u.ResponseBody.Contains(input.Keyword) || u.Exception.Contains(input.Keyword))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.HttpClientName), u => u.HttpClientName.Contains(input.HttpClientName.Trim()))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.HttpApiDesc), u => u.HttpApiDesc.Contains(input.HttpApiDesc.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.RequestUrl?.Trim()), u => u.RequestUrl.Contains(input.RequestUrl.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.RequestBody?.Trim()), u => u.RequestBody.Contains(input.RequestBody.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.ResponseBody?.Trim()), u => u.ResponseBody.Contains(input.ResponseBody.Trim()))
diff --git a/Admin.NET/Admin.NET.Core/Utils/CustomJsonPropertyConverter.cs b/Admin.NET/Admin.NET.Core/Utils/CustomJsonPropertyConverter.cs
new file mode 100644
index 00000000..c6166862
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/Utils/CustomJsonPropertyConverter.cs
@@ -0,0 +1,225 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Unicode;
+
+namespace Admin.NET.Core;
+
+///
+/// 自定义JSON属性名称转换器
+///
+public class CustomJsonPropertyConverter : JsonConverter