feat: initialize Evergreen Health backend
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 全局规范化结果
|
||||
/// </summary>
|
||||
[UnifyModel(typeof(AdminResult<>))]
|
||||
public class AdminResultProvider : IUnifyResultProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// JWT 授权异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, msg: metadata.Errors), UnifyContext.GetSerializerSettings(context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, msg: metadata.Errors), UnifyContext.GetSerializerSettings(context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnSucceeded(ActionExecutedContext context, object data)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data), UnifyContext.GetSerializerSettings(context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, msg: metadata.ValidationResult), UnifyContext.GetSerializerSettings(context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 特定状态码返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
|
||||
{
|
||||
// 设置响应状态码
|
||||
UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
// 处理 401 状态码
|
||||
case StatusCodes.Status401Unauthorized:
|
||||
var msg = "401 登录已过期,请重新登录";
|
||||
// 若存在身份验证失败消息,则返回消息内容
|
||||
if (context.Items.TryGetValue(SignatureAuthenticationDefaults.AuthenticateFailMsgKey, out var authFailMsg))
|
||||
msg = authFailMsg + "";
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, msg: msg),
|
||||
App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
|
||||
break;
|
||||
// 处理 403 状态码
|
||||
case StatusCodes.Status403Forbidden:
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, msg: "403 禁止访问,没有权限"),
|
||||
App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
|
||||
break;
|
||||
// 处理 302 状态码
|
||||
case StatusCodes.Status302Found:
|
||||
if (context.Response.Headers.TryGetValue("Location", out var redirectUrl))
|
||||
{
|
||||
context.Response.Redirect(redirectUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = "302 跳转失败,没有提供 Location 头信息";
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, msg: errorMessage),
|
||||
App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回成功结果集
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static AdminResult<object> Ok(string message, object data = default)
|
||||
{
|
||||
return RESTfulResult(StatusCodes.Status200OK, true, data, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回失败结果集
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static AdminResult<object> Error(string message, int code = StatusCodes.Status400BadRequest, object data = default)
|
||||
{
|
||||
return RESTfulResult(code, false, data, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回 RESTful 风格结果集
|
||||
/// </summary>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="succeeded"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
private static AdminResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object msg = default)
|
||||
{
|
||||
//// 统一返回值脱敏处理
|
||||
//if (data?.GetType() == typeof(String))
|
||||
//{
|
||||
// data = App.GetRequiredService<ISensitiveDetectionProvider>().ReplaceAsync(data.ToString(), '*').GetAwaiter().GetResult();
|
||||
//}
|
||||
//else if (data?.GetType() == typeof(JsonResult))
|
||||
//{
|
||||
// data = App.GetRequiredService<ISensitiveDetectionProvider>().ReplaceAsync(JSON.Serialize(data), '*').GetAwaiter().GetResult();
|
||||
//}
|
||||
|
||||
return new AdminResult<object>
|
||||
{
|
||||
Code = statusCode,
|
||||
Message = msg is null or string ? (msg + "") : JSON.Serialize(msg),
|
||||
Result = data,
|
||||
Type = succeeded ? "success" : "error",
|
||||
Extras = UnifyContext.Take(),
|
||||
Time = DateTime.Now
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局返回结果
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class AdminResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类型success、warning、error
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public T Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public object Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
public DateTime Time { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 模糊查询条件
|
||||
/// </summary>
|
||||
public class Search
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段名称集合
|
||||
/// </summary>
|
||||
public List<string> Fields { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键字
|
||||
/// </summary>
|
||||
public string? Keyword { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 筛选过滤条件
|
||||
/// </summary>
|
||||
public class Filter
|
||||
{
|
||||
/// <summary>
|
||||
/// 过滤条件
|
||||
/// </summary>
|
||||
public FilterLogicEnum? Logic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 筛选过滤条件子项
|
||||
/// </summary>
|
||||
public IEnumerable<Filter>? Filters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段名称
|
||||
/// </summary>
|
||||
public string? Field { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 逻辑运算符
|
||||
/// </summary>
|
||||
public FilterOperatorEnum? Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段值
|
||||
/// </summary>
|
||||
public object? Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 过滤条件基类
|
||||
/// </summary>
|
||||
public abstract class BaseFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 模糊查询条件
|
||||
/// </summary>
|
||||
public Search? Search { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模糊查询关键字
|
||||
/// </summary>
|
||||
public string? Keyword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 筛选过滤条件
|
||||
/// </summary>
|
||||
public Filter? Filter { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 主键Id输入参数
|
||||
/// </summary>
|
||||
public class BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Id不能为空")]
|
||||
[DataValidation(ValidationTypes.Numeric)]
|
||||
public virtual long Id { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 数据导入输入参数
|
||||
/// </summary>
|
||||
public class BaseImportInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录Id
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
[ExporterHeader(IsIgnore = true)]
|
||||
public virtual long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
[ExporterHeader("错误信息", ColumnIndex = 9999, IsBold = true, IsAutoFit = true)]
|
||||
public virtual string Error { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 全局分页查询输入参数
|
||||
/// </summary>
|
||||
public class BasePageInput : BaseFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前页码
|
||||
/// </summary>
|
||||
[DataValidation(ValidationTypes.Numeric)]
|
||||
public virtual int Page { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 页码容量
|
||||
/// </summary>
|
||||
//[Range(0, 100, ErrorMessage = "页码容量超过最大限制")]
|
||||
[DataValidation(ValidationTypes.Numeric)]
|
||||
public virtual int PageSize { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// 排序字段
|
||||
/// </summary>
|
||||
public virtual string Field { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序方向
|
||||
/// </summary>
|
||||
public virtual string Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 降序排序
|
||||
/// </summary>
|
||||
public virtual string DescStr { get; set; } = "descending";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局分页查询输入参数(带时间)
|
||||
/// </summary>
|
||||
public class BasePageTimeInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 设置状态输入参数
|
||||
/// </summary>
|
||||
public class BaseStatusInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
[Enum]
|
||||
public StatusEnum Status { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// JSON时间序列化yyyy-MM-dd HH:mm:ss
|
||||
/// </summary>
|
||||
public class ChinaDateTimeConverter : DateTimeConverterBase
|
||||
{
|
||||
private static readonly IsoDateTimeConverter DtConverter = new() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
return DtConverter.ReadJson(reader, objectType, existingValue, serializer);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
DtConverter.WriteJson(writer, value, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON时间序列化yyyy-MM-dd HH:mm
|
||||
/// </summary>
|
||||
public class ChinaDateTimeConverterHH : DateTimeConverterBase
|
||||
{
|
||||
private static readonly IsoDateTimeConverter DtConverter = new() { DateTimeFormat = "yyyy-MM-dd HH:mm" };
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
return DtConverter.ReadJson(reader, objectType, existingValue, serializer);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
DtConverter.WriteJson(writer, value, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON时间序列化yyyy-MM-dd
|
||||
/// </summary>
|
||||
public class ChinaDateTimeConverterDate : DateTimeConverterBase
|
||||
{
|
||||
private static readonly IsoDateTimeConverter DtConverter = new() { DateTimeFormat = "yyyy-MM-dd" };
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
return DtConverter.ReadJson(reader, objectType, existingValue, serializer);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
DtConverter.WriteJson(writer, value, serializer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using DbType = SqlSugar.DbType;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成帮助类
|
||||
/// </summary>
|
||||
public static class CodeGenUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换大驼峰法命名
|
||||
/// </summary>
|
||||
/// <param name="columnName">字段名</param>
|
||||
/// <param name="dbColumnNames">EntityBase 实体属性名称</param>
|
||||
/// <returns></returns>
|
||||
public static string CamelColumnName(string columnName, string[] dbColumnNames)
|
||||
{
|
||||
if (columnName.Contains('_'))
|
||||
{
|
||||
var arrColName = columnName.Split('_');
|
||||
var sb = new StringBuilder();
|
||||
foreach (var col in arrColName)
|
||||
{
|
||||
if (col.Length > 0)
|
||||
sb.Append(col[..1].ToUpper() + col[1..].ToLower());
|
||||
}
|
||||
columnName = sb.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyName = dbColumnNames.FirstOrDefault(c => c.ToLower() == columnName.ToLower());
|
||||
if (!string.IsNullOrEmpty(propertyName))
|
||||
{
|
||||
columnName = propertyName;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnName = columnName[..1].ToUpper() + columnName[1..].ToLower();
|
||||
}
|
||||
}
|
||||
return columnName;
|
||||
}
|
||||
|
||||
// 根据数据库类型来处理对应的数据字段类型
|
||||
public static string ConvertDataType(DbColumnInfo dbColumnInfo, DbType dbType = DbType.Custom)
|
||||
{
|
||||
if (dbType == DbType.Custom)
|
||||
dbType = App.GetOptions<DbConnectionOptions>().ConnectionConfigs[0].DbType;
|
||||
|
||||
var dataType = dbType switch
|
||||
{
|
||||
DbType.Oracle => ConvertDataTypeOracleSql(string.IsNullOrEmpty(dbColumnInfo.OracleDataType) ? dbColumnInfo.DataType : dbColumnInfo.OracleDataType, dbColumnInfo.Length, dbColumnInfo.Scale),
|
||||
DbType.PostgreSQL => ConvertDataTypePostgresSql(dbColumnInfo.DataType),
|
||||
_ => ConvertDataTypeDefault(dbColumnInfo.DataType),
|
||||
};
|
||||
return dataType + (dbColumnInfo.IsNullable ? "?" : "");
|
||||
}
|
||||
|
||||
public static string ConvertDataTypeOracleSql(string dataType, int? length, int? scale)
|
||||
{
|
||||
switch (dataType.ToLower())
|
||||
{
|
||||
case "interval year to month": return "int";
|
||||
|
||||
case "interval day to second": return "TimeSpan";
|
||||
|
||||
case "smallint": return "Int16";
|
||||
|
||||
case "int":
|
||||
case "integer": return "int";
|
||||
|
||||
case "long": return "long";
|
||||
|
||||
case "float": return "float";
|
||||
|
||||
case "decimal": return "decimal";
|
||||
|
||||
case "number":
|
||||
if (length == null) return "decimal";
|
||||
return scale switch
|
||||
{
|
||||
> 0 => "decimal",
|
||||
0 or null when length is > 1 and < 12 => "int",
|
||||
0 or null when length > 11 => "long",
|
||||
_ => length == 1 ? "bool" : "decimal"
|
||||
};
|
||||
|
||||
case "char":
|
||||
case "clob":
|
||||
case "nclob":
|
||||
case "nchar":
|
||||
case "nvarchar":
|
||||
case "varchar":
|
||||
case "nvarchar2":
|
||||
case "varchar2":
|
||||
case "rowid":
|
||||
return "string";
|
||||
|
||||
case "timestamp":
|
||||
case "timestamp with time zone":
|
||||
case "timestamptz":
|
||||
case "timestamp without time zone":
|
||||
case "date":
|
||||
case "time":
|
||||
case "time with time zone":
|
||||
case "timetz":
|
||||
case "time without time zone":
|
||||
return "DateTime";
|
||||
|
||||
case "bfile":
|
||||
case "blob":
|
||||
case "raw":
|
||||
return "byte[]";
|
||||
|
||||
default:
|
||||
return "object";
|
||||
}
|
||||
}
|
||||
|
||||
//PostgresSQL数据类型对应的字段类型
|
||||
public static string ConvertDataTypePostgresSql(string dataType)
|
||||
{
|
||||
switch (dataType)
|
||||
{
|
||||
case "int2":
|
||||
case "smallint":
|
||||
return "Int16";
|
||||
|
||||
case "int4":
|
||||
case "integer":
|
||||
return "int";
|
||||
|
||||
case "int8":
|
||||
case "bigint":
|
||||
return "long";
|
||||
|
||||
case "float4":
|
||||
case "real":
|
||||
return "float";
|
||||
|
||||
case "float8":
|
||||
case "double precision":
|
||||
return "double";
|
||||
|
||||
case "numeric":
|
||||
case "decimal":
|
||||
case "path":
|
||||
case "point":
|
||||
case "polygon":
|
||||
case "interval":
|
||||
case "lseg":
|
||||
case "macaddr":
|
||||
case "money":
|
||||
return "decimal";
|
||||
|
||||
case "boolean":
|
||||
case "bool":
|
||||
case "box":
|
||||
case "bytea":
|
||||
return "bool";
|
||||
|
||||
case "varchar":
|
||||
case "character varying":
|
||||
case "geometry":
|
||||
case "name":
|
||||
case "text":
|
||||
case "char":
|
||||
case "character":
|
||||
case "cidr":
|
||||
case "circle":
|
||||
case "tsquery":
|
||||
case "tsvector":
|
||||
case "txid_snapshot":
|
||||
case "xml":
|
||||
case "json":
|
||||
return "string";
|
||||
|
||||
case "uuid":
|
||||
return "Guid";
|
||||
|
||||
case "timestamp":
|
||||
case "timestamp with time zone":
|
||||
case "timestamptz":
|
||||
case "timestamp without time zone":
|
||||
case "date":
|
||||
case "time":
|
||||
case "time with time zone":
|
||||
case "timetz":
|
||||
case "time without time zone":
|
||||
return "DateTime";
|
||||
|
||||
case "bit":
|
||||
case "bit varying":
|
||||
return "byte[]";
|
||||
|
||||
case "varbit":
|
||||
return "byte";
|
||||
|
||||
default:
|
||||
return "object";
|
||||
}
|
||||
}
|
||||
|
||||
public static string ConvertDataTypeDefault(string dataType)
|
||||
{
|
||||
return dataType.ToLower() switch
|
||||
{
|
||||
"tinytext" or "mediumtext" or "longtext" or "mid" or "text" or "varchar" or "char" or "nvarchar" or "nchar" or "string" or "timestamp" => "string",
|
||||
"int" or "integer" or "int32" => "int",
|
||||
"smallint" => "Int16",
|
||||
//"tinyint" => "byte",
|
||||
"tinyint" => "bool", // MYSQL
|
||||
"bigint" or "int64" => "long",
|
||||
"bit" or "boolean" => "bool",
|
||||
"money" or "smallmoney" or "numeric" or "decimal" => "decimal",
|
||||
"real" => "Single",
|
||||
"datetime" or "datetime2" or "smalldatetime" => "DateTime",
|
||||
"date" => "DateOnly",// MYSQL
|
||||
"time" => "TimeOnly",// MYSQL
|
||||
"float" or "double" => "double",
|
||||
"image" or "binary" or "varbinary" => "byte[]",
|
||||
"uniqueidentifier" => "Guid",
|
||||
_ => "object",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型转显示类型
|
||||
/// </summary>
|
||||
/// <param name="dataType"></param>
|
||||
/// <returns></returns>
|
||||
public static string DataTypeToEff(string dataType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dataType)) return "";
|
||||
return dataType.TrimEnd('?') switch
|
||||
{
|
||||
"string" => "Input",
|
||||
"int" => "InputNumber",
|
||||
"long" => "Input",
|
||||
"float" => "InputNumber",
|
||||
"double" => "InputNumber",
|
||||
"decimal" => "InputNumber",
|
||||
"bool" => "Switch",
|
||||
"Guid" => "Input",
|
||||
"DateTime" => "DatePicker",
|
||||
_ => "Input",
|
||||
};
|
||||
}
|
||||
|
||||
// 是否通用字段
|
||||
public static bool IsCommonColumn(string columnName)
|
||||
{
|
||||
var columnList = new List<string>()
|
||||
{
|
||||
nameof(EntityBaseOrg.OrgId),
|
||||
nameof(EntityBaseTenant.TenantId),
|
||||
nameof(EntityBase.CreateTime),
|
||||
nameof(EntityBase.UpdateTime),
|
||||
nameof(EntityBase.CreateUserId),
|
||||
nameof(EntityBase.UpdateUserId),
|
||||
nameof(EntityBase.CreateUserName),
|
||||
nameof(EntityBase.UpdateUserName),
|
||||
nameof(EntityBaseDel.IsDelete)
|
||||
};
|
||||
return columnList.Contains(columnName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型的PropertyInfo列表
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static PropertyInfo[] GetPropertyInfoArray(Type type)
|
||||
{
|
||||
PropertyInfo[] props = null;
|
||||
try
|
||||
{
|
||||
props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
return props;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using IPTools.Core;
|
||||
using Magicodes.ExporterAndImporter.Core.Models;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 通用工具类
|
||||
/// </summary>
|
||||
public static class CommonUtil
|
||||
{
|
||||
private static readonly SysCacheService SysCacheService = App.GetRequiredService<SysCacheService>();
|
||||
private static readonly SysFileService SysFileService = App.GetRequiredService<SysFileService>();
|
||||
private static readonly SqlSugarRepository<SysDictData> SysDictDataRep = App.GetRequiredService<SqlSugarRepository<SysDictData>>();
|
||||
|
||||
/// <summary>
|
||||
/// 根据字符串获取固定整型哈希值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <param name="startNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetFixedHashCode(string str, long startNumber = 0)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str)) return 0;
|
||||
unchecked
|
||||
{
|
||||
int hash1 = (5381 << 16) + 5381;
|
||||
int hash2 = hash1;
|
||||
for (int i = 0; i < str.Length; i += 2)
|
||||
{
|
||||
hash1 = ((hash1 << 5) + hash1) ^ str[i];
|
||||
if (i == str.Length - 1) break;
|
||||
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
|
||||
}
|
||||
return startNumber + Math.Abs(hash1 + (hash2 * 1566083941));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成百分数
|
||||
/// </summary>
|
||||
/// <param name="passCount"></param>
|
||||
/// <param name="allCount"></param>
|
||||
/// <returns></returns>
|
||||
public static string ExecPercent(decimal passCount, decimal allCount)
|
||||
{
|
||||
string res = "";
|
||||
if (allCount > 0)
|
||||
{
|
||||
var value = (double)Math.Round(passCount / allCount * 100, 1);
|
||||
if (value < 0)
|
||||
res = Math.Round(value + 5 / Math.Pow(10, 0 + 1), 0, MidpointRounding.AwayFromZero).ToString();
|
||||
else
|
||||
res = Math.Round(value, 0, MidpointRounding.AwayFromZero).ToString();
|
||||
}
|
||||
if (res == "") res = "0";
|
||||
return res + "%";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取服务地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetLocalhost()
|
||||
{
|
||||
string result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}";
|
||||
|
||||
// 代理模式:获取真正的本机地址
|
||||
// X-Original-Host=原始请求
|
||||
// X-Forwarded-Server=从哪里转发过来
|
||||
if (App.HttpContext.Request.Headers.ContainsKey("Origin")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com
|
||||
result = $"{App.HttpContext.Request.Headers["Origin"]}";
|
||||
else if (App.HttpContext.Request.Headers.ContainsKey("X-Original")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com
|
||||
result = $"{App.HttpContext.Request.Headers["X-Original"]}";
|
||||
else if (App.HttpContext.Request.Headers.ContainsKey("X-Original-Host"))
|
||||
result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Headers["X-Original-Host"]}";
|
||||
return result + (string.IsNullOrWhiteSpace(App.Settings.VirtualPath) ? "" : App.Settings.VirtualPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对象序列化XML
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string SerializeObjectToXml<T>(T obj)
|
||||
{
|
||||
if (obj == null) return string.Empty;
|
||||
|
||||
var xs = new XmlSerializer(obj.GetType());
|
||||
var stream = new MemoryStream();
|
||||
var setting = new XmlWriterSettings
|
||||
{
|
||||
Encoding = new UTF8Encoding(false), // 不包含BOM
|
||||
Indent = true // 设置格式化缩进
|
||||
};
|
||||
using (var writer = XmlWriter.Create(stream, setting))
|
||||
{
|
||||
var ns = new XmlSerializerNamespaces();
|
||||
ns.Add("", ""); // 去除默认命名空间
|
||||
xs.Serialize(writer, obj, ns);
|
||||
}
|
||||
return Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转XML格式
|
||||
/// </summary>
|
||||
/// <param name="xmlStr"></param>
|
||||
/// <returns></returns>
|
||||
public static XElement SerializeStringToXml(string xmlStr)
|
||||
{
|
||||
try
|
||||
{
|
||||
return XElement.Parse(xmlStr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出模板Excel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<IActionResult> ExportExcelTemplate<T>(string fileName = null) where T : class, new()
|
||||
{
|
||||
IImporter importer = new ExcelImporter();
|
||||
var res = await importer.GenerateTemplateBytes<T>();
|
||||
|
||||
return new FileContentResult(res, "application/octet-stream") { FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? typeof(T).Name : fileName)}.xlsx" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出数据excel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<IActionResult> ExportExcelData<T>(ICollection<T> data, string fileName = null) where T : class, new()
|
||||
{
|
||||
var export = new ExcelExporter();
|
||||
var res = await export.ExportAsByteArray<T>(data);
|
||||
|
||||
return new FileContentResult(res, "application/octet-stream") { FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? typeof(T).Name : fileName)}.xlsx" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出数据excel,包括字典转换
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<IActionResult> ExportExcelData<TSource, TTarget>(ISugarQueryable<TSource> query, Func<TSource, TTarget, TTarget> action = null)
|
||||
where TSource : class, new() where TTarget : class, new()
|
||||
{
|
||||
var propMappings = GetExportPropertMap<TSource, TTarget>();
|
||||
var data = query.ToList();
|
||||
//相同属性复制值,字典值转换
|
||||
var result = new List<TTarget>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
var newData = new TTarget();
|
||||
foreach (var dict in propMappings)
|
||||
{
|
||||
var targetProp = dict.Value.Item3;
|
||||
if (targetProp != null)
|
||||
{
|
||||
var propertyInfo = dict.Value.Item2;
|
||||
var sourceVal = propertyInfo.GetValue(item, null);
|
||||
if (sourceVal == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var map = dict.Value.Item1;
|
||||
if (map != null && map.TryGetValue(sourceVal, out string newVal1))
|
||||
{
|
||||
targetProp.SetValue(newData, newVal1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targetProp.PropertyType.FullName == propertyInfo.PropertyType.FullName)
|
||||
{
|
||||
targetProp.SetValue(newData, sourceVal);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = sourceVal.ToString().ParseTo(targetProp.PropertyType);
|
||||
targetProp.SetValue(newData, newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action != null)
|
||||
{
|
||||
newData = action(item, newData);
|
||||
}
|
||||
}
|
||||
result.Add(newData);
|
||||
}
|
||||
var export = new ExcelExporter();
|
||||
var res = await export.ExportAsByteArray(result);
|
||||
|
||||
return new FileContentResult(res, "application/octet-stream") { FileDownloadName = typeof(TTarget).Name + ".xlsx" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入数据Excel
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<ICollection<T>> ImportExcelData<T>([Required] IFormFile file) where T : class, new()
|
||||
{
|
||||
IImporter importer = new ExcelImporter();
|
||||
var res = await importer.Import<T>(file.OpenReadStream());
|
||||
var message = string.Empty;
|
||||
|
||||
if (!res.HasError) return res.Data;
|
||||
|
||||
if (res.Exception != null)
|
||||
message += $"\r\n{res.Exception.Message}";
|
||||
foreach (DataRowErrorInfo drErrorInfo in res.RowErrors)
|
||||
{
|
||||
int rowNum = drErrorInfo.RowIndex;
|
||||
foreach (var item in drErrorInfo.FieldErrors)
|
||||
message += $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)";
|
||||
}
|
||||
message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList());
|
||||
throw Oops.Oh("导入异常:" + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入Excel数据并错误标记
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="importResultCallback"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<ICollection<T>> ImportExcelData<T>([Required] IFormFile file, Func<ImportResult<T>, ImportResult<T>> importResultCallback = null) where T : class, new()
|
||||
{
|
||||
IImporter importer = new ExcelImporter();
|
||||
var resultStream = new MemoryStream();
|
||||
var res = await importer.Import<T>(file.OpenReadStream(), resultStream, importResultCallback);
|
||||
resultStream.Seek(0, SeekOrigin.Begin);
|
||||
var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value;
|
||||
|
||||
SysCacheService.Remove(CacheConst.KeyExcelTemp + userId);
|
||||
SysCacheService.Set(CacheConst.KeyExcelTemp + userId, resultStream, TimeSpan.FromMinutes(5));
|
||||
|
||||
var message = string.Empty;
|
||||
if (!res.HasError) return res.Data;
|
||||
|
||||
if (res.Exception != null)
|
||||
message += $"\r\n{res.Exception.Message}";
|
||||
foreach (DataRowErrorInfo drErrorInfo in res.RowErrors)
|
||||
{
|
||||
message = drErrorInfo.FieldErrors.Aggregate(message, (current, item) => current + $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)");
|
||||
}
|
||||
if (res.TemplateErrors.Count > 0)
|
||||
message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList());
|
||||
|
||||
if (message.Length > 200)
|
||||
message = message.Substring(0, 200) + "...\r\n异常过多,建议下载错误标记文件查看详细错误信息并重新导入。";
|
||||
throw Oops.Oh("导入异常:" + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入数据Excel
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<List<T>> ImportExcelDataAsync<T>([Required] IFormFile file) where T : class, new()
|
||||
{
|
||||
var newFile = await SysFileService.UploadFile(new UploadFileInput { File = file });
|
||||
|
||||
await using var fileStream = await SysFileService.GetFileStream(newFile);
|
||||
|
||||
IImporter importer = new ExcelImporter();
|
||||
var res = await importer.Import<T>(fileStream);
|
||||
|
||||
// 删除文件
|
||||
_ = SysFileService.DeleteFile(new BaseIdInput { Id = newFile.Id });
|
||||
|
||||
if (res == null)
|
||||
throw Oops.Oh("导入数据为空");
|
||||
if (res.Exception != null)
|
||||
throw Oops.Oh("导入异常:" + res.Exception);
|
||||
if (res.TemplateErrors?.Count > 0)
|
||||
throw Oops.Oh("模板异常:" + res.TemplateErrors.Select(x => $"[{x.RequireColumnName}]{x.Message}").Join("\n"));
|
||||
|
||||
return res.Data.ToList();
|
||||
}
|
||||
|
||||
// 例:List<Dm_ApplyDemo> ls = CommonUtil.ParseList<Dm_ApplyDemoInport, Dm_ApplyDemo>(importResult.Data);
|
||||
/// <summary>
|
||||
/// 对象转换 含字典转换
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource"></typeparam>
|
||||
/// <typeparam name="TTarget"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static List<TTarget> ParseList<TSource, TTarget>(IEnumerable<TSource> data, Func<TSource, TTarget, TTarget> action = null) where TTarget : new()
|
||||
{
|
||||
var propMappings = GetImportPropertMap<TSource, TTarget>();
|
||||
// 相同属性复制值,字典值转换
|
||||
var result = new List<TTarget>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
var newData = new TTarget();
|
||||
foreach (var dict in propMappings)
|
||||
{
|
||||
var targeProp = dict.Value.Item3;
|
||||
if (targeProp != null)
|
||||
{
|
||||
var propertyInfo = dict.Value.Item2;
|
||||
var sourceVal = propertyInfo.GetValue(item, null);
|
||||
if (sourceVal == null)
|
||||
continue;
|
||||
|
||||
var map = dict.Value.Item1;
|
||||
if (map != null && map.ContainsKey(sourceVal.ToString()))
|
||||
{
|
||||
var newVal = map[sourceVal.ToString()];
|
||||
targeProp.SetValue(newData, newVal);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targeProp.PropertyType.FullName == propertyInfo.PropertyType.FullName)
|
||||
{
|
||||
targeProp.SetValue(newData, sourceVal);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = sourceVal.ToString().ParseTo(targeProp.PropertyType);
|
||||
targeProp.SetValue(newData, newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action != null)
|
||||
newData = action(item, newData);
|
||||
|
||||
if (newData != null)
|
||||
result.Add(newData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取导入属性映射
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource"></typeparam>
|
||||
/// <typeparam name="TTarget"></typeparam>
|
||||
/// <returns>整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 </returns>
|
||||
private static Dictionary<string, Tuple<Dictionary<string, object>, PropertyInfo, PropertyInfo>> GetImportPropertMap<TSource, TTarget>() where TTarget : new()
|
||||
{
|
||||
// 整理导入对象的属性名称,<字典数据,原属性信息,目标属性信息>
|
||||
var propMappings = new Dictionary<string, Tuple<Dictionary<string, object>, PropertyInfo, PropertyInfo>>();
|
||||
|
||||
var dictService = App.GetRequiredService<SqlSugarRepository<SysDictData>>();
|
||||
var tSourceProps = typeof(TSource).GetProperties().ToList();
|
||||
var tTargetProps = typeof(TTarget).GetProperties().ToDictionary(u => u.Name);
|
||||
foreach (var propertyInfo in tSourceProps)
|
||||
{
|
||||
var attrs = propertyInfo.GetCustomAttribute<ImportDictAttribute>();
|
||||
if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode))
|
||||
{
|
||||
var targetProp = tTargetProps[attrs.TargetPropName];
|
||||
var mappingValues = dictService.Context.Queryable<SysDictType, SysDictData>((u, a) =>
|
||||
new JoinQueryInfos(JoinType.Inner, u.Id == a.DictTypeId))
|
||||
.Where(u => u.Code == attrs.TypeCode)
|
||||
.Where((u, a) => u.Status == StatusEnum.Enable && a.Status == StatusEnum.Enable)
|
||||
.Select((u, a) => new
|
||||
{
|
||||
Label = a.Label,
|
||||
Value = a.Value
|
||||
}).ToList()
|
||||
.ToDictionary(u => u.Label, u => u.Value.ParseTo(targetProp.PropertyType));
|
||||
propMappings.Add(propertyInfo.Name, new Tuple<Dictionary<string, object>, PropertyInfo, PropertyInfo>(mappingValues, propertyInfo, targetProp));
|
||||
}
|
||||
else
|
||||
{
|
||||
propMappings.Add(propertyInfo.Name, new Tuple<Dictionary<string, object>, PropertyInfo, PropertyInfo>(
|
||||
null, propertyInfo, tTargetProps.ContainsKey(propertyInfo.Name) ? tTargetProps[propertyInfo.Name] : null));
|
||||
}
|
||||
}
|
||||
|
||||
return propMappings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取导出属性映射
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource"></typeparam>
|
||||
/// <typeparam name="TTarget"></typeparam>
|
||||
/// <returns>整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 </returns>
|
||||
private static Dictionary<string, Tuple<Dictionary<object, string>, PropertyInfo, PropertyInfo>> GetExportPropertMap<TSource, TTarget>() where TTarget : new()
|
||||
{
|
||||
// 整理导入对象的属性名称,<字典数据,原属性信息,目标属性信息>
|
||||
var propMappings = new Dictionary<string, Tuple<Dictionary<object, string>, PropertyInfo, PropertyInfo>>();
|
||||
|
||||
var targetProps = typeof(TTarget).GetProperties().ToList();
|
||||
var sourceProps = typeof(TSource).GetProperties().ToDictionary(u => u.Name);
|
||||
foreach (var propertyInfo in targetProps)
|
||||
{
|
||||
var attrs = propertyInfo.GetCustomAttribute<ImportDictAttribute>();
|
||||
if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode))
|
||||
{
|
||||
var targetProp = sourceProps[attrs.TargetPropName];
|
||||
var mappingValues = SysDictDataRep.Context.Queryable<SysDictType, SysDictData>((u, a) =>
|
||||
new JoinQueryInfos(JoinType.Inner, u.Id == a.DictTypeId))
|
||||
.Where(u => u.Code == attrs.TypeCode)
|
||||
.Where((u, a) => u.Status == StatusEnum.Enable && a.Status == StatusEnum.Enable)
|
||||
.Select((u, a) => new
|
||||
{
|
||||
a.Label,
|
||||
a.Value
|
||||
}).ToList()
|
||||
.ToDictionary(u => u.Value.ParseTo(targetProp.PropertyType), u => u.Label);
|
||||
propMappings.Add(propertyInfo.Name, new Tuple<Dictionary<object, string>, PropertyInfo, PropertyInfo>(mappingValues, targetProp, propertyInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
propMappings.Add(propertyInfo.Name, new Tuple<Dictionary<object, string>, PropertyInfo, PropertyInfo>(
|
||||
null, sourceProps.TryGetValue(propertyInfo.Name, out PropertyInfo prop) ? prop : null, propertyInfo));
|
||||
}
|
||||
}
|
||||
|
||||
return propMappings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取属性映射
|
||||
/// </summary>
|
||||
/// <typeparam name="TTarget"></typeparam>
|
||||
/// <returns>整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 </returns>
|
||||
private static Dictionary<string, Tuple<string, string>> GetExportDictMap<TTarget>() where TTarget : new()
|
||||
{
|
||||
// 整理导入对象的属性名称,目标属性名,字典Code
|
||||
var propMappings = new Dictionary<string, Tuple<string, string>>();
|
||||
var tTargetProps = typeof(TTarget).GetProperties();
|
||||
foreach (var propertyInfo in tTargetProps)
|
||||
{
|
||||
var attrs = propertyInfo.GetCustomAttribute<ImportDictAttribute>();
|
||||
if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode))
|
||||
{
|
||||
propMappings.Add(propertyInfo.Name, new Tuple<string, string>(attrs.TargetPropName, attrs.TypeCode));
|
||||
}
|
||||
}
|
||||
|
||||
return propMappings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析IP地址
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <returns></returns>
|
||||
public static (string ipLocation, double? longitude, double? latitude) GetIpAddress(string ip)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ipInfo = IpTool.SearchWithI18N(ip); // 国际化查询,默认中文 中文zh-CN、英文en
|
||||
var addressList = new List<string>() { ipInfo.Country, ipInfo.Province, ipInfo.City, ipInfo.NetworkOperator };
|
||||
return (string.Join(" ", addressList.Where(u => u != "0" && !string.IsNullOrWhiteSpace(u)).ToList()), ipInfo.Longitude, ipInfo.Latitude); // 去掉0及空并用空格连接
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 不做处理
|
||||
}
|
||||
return ("未知", 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取客户端设备信息(操作系统+浏览器)
|
||||
/// </summary>
|
||||
/// <param name="userAgent"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetClientDeviceInfo(string userAgent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (userAgent != null)
|
||||
{
|
||||
var client = Parser.GetDefault().Parse(userAgent);
|
||||
if (client.Device.IsSpider)
|
||||
return "爬虫";
|
||||
return $"{client.OS.Family} {client.OS.Major} {client.OS.Minor}" +
|
||||
$"|{client.UA.Family} {client.UA.Major}.{client.UA.Minor} / {client.Device.Family}";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,550 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class ComputerUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 内存信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MemoryMetrics GetComputerInfo()
|
||||
{
|
||||
MemoryMetrics memoryMetrics;
|
||||
if (IsMacOS())
|
||||
{
|
||||
memoryMetrics = MemoryMetricsClient.GetMacOSMetrics();
|
||||
}
|
||||
else if (IsUnix())
|
||||
{
|
||||
memoryMetrics = MemoryMetricsClient.GetUnixMetrics();
|
||||
}
|
||||
else
|
||||
{
|
||||
memoryMetrics = MemoryMetricsClient.GetWindowsMetrics();
|
||||
}
|
||||
memoryMetrics.FreeRam = Math.Round(memoryMetrics.Free / 1024, 2) + "GB";
|
||||
memoryMetrics.UsedRam = Math.Round(memoryMetrics.Used / 1024, 2) + "GB";
|
||||
memoryMetrics.TotalRam = Math.Round(memoryMetrics.Total / 1024, 2) + "GB";
|
||||
memoryMetrics.RamRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total) + "%";
|
||||
var cpuRates = GetCPURates();
|
||||
if (cpuRates != null)
|
||||
{
|
||||
memoryMetrics.CpuRates = cpuRates.Select(u => Math.Ceiling(u.ParseToDouble()) + "%").ToList();
|
||||
}
|
||||
memoryMetrics.CpuRate = memoryMetrics.CpuRates[0];
|
||||
return memoryMetrics;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取正确的操作系统版本(Linux获取发行版本)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static String GetOSInfo()
|
||||
{
|
||||
string operation = string.Empty;
|
||||
if (IsMacOS())
|
||||
{
|
||||
var output = ShellUtil.Bash("sw_vers | awk 'NR<=2{printf \"%s \", $NF}'");
|
||||
if (output != null)
|
||||
{
|
||||
operation = output.Replace("%", string.Empty);
|
||||
}
|
||||
}
|
||||
else if (IsUnix())
|
||||
{
|
||||
var output = ShellUtil.Bash("awk -F= '/^VERSION_ID/ {print $2}' /etc/os-release | tr -d '\"'");
|
||||
operation = output ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
operation = RuntimeInformation.OSDescription;
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 磁盘信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<DiskInfo> GetDiskInfos()
|
||||
{
|
||||
var diskInfos = new List<DiskInfo>();
|
||||
if (IsMacOS())
|
||||
{
|
||||
var output = ShellUtil.Bash(@"df -m | awk '/^\/dev\/disk/ {print $1,$2,$3,$4,$5}'");
|
||||
var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (disks.Length < 1) return diskInfos;
|
||||
foreach (var item in disks)
|
||||
{
|
||||
var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (disk.Length < 5) continue;
|
||||
|
||||
var diskInfo = new DiskInfo()
|
||||
{
|
||||
DiskName = disk[0],
|
||||
TypeName = ShellUtil.Bash("diskutil info " + disk[0] + " | awk '/File System Personality/ {print $4}'").Replace("\n", string.Empty),
|
||||
TotalSize = Math.Round(long.Parse(disk[1]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
Used = Math.Round(long.Parse(disk[2]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
AvailableFreeSpace = Math.Round(long.Parse(disk[3]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
AvailablePercent = decimal.Parse(disk[4].Replace("%", ""))
|
||||
};
|
||||
diskInfos.Add(diskInfo);
|
||||
}
|
||||
}
|
||||
else if (IsUnix())
|
||||
{
|
||||
var output = ShellUtil.Bash(@"df -mT | awk '/^\/dev\/(sd|vd|xvd|nvme|sda|vda|mapper)/ {print $1,$2,$3,$4,$5,$6}'");
|
||||
var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (disks.Length < 1) return diskInfos;
|
||||
|
||||
//var rootDisk = disks[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
//if (rootDisk == null || rootDisk.Length < 1)
|
||||
// return diskInfos;
|
||||
|
||||
foreach (var item in disks)
|
||||
{
|
||||
var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (disk.Length < 6) continue;
|
||||
|
||||
var diskInfo = new DiskInfo()
|
||||
{
|
||||
DiskName = disk[0],
|
||||
TypeName = disk[1],
|
||||
TotalSize = Math.Round(long.Parse(disk[2]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
Used = Math.Round(long.Parse(disk[3]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
AvailableFreeSpace = Math.Round(long.Parse(disk[4]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
AvailablePercent = decimal.Parse(disk[5].Replace("%", ""))
|
||||
};
|
||||
diskInfos.Add(diskInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var driveList = DriveInfo.GetDrives().Where(u => u.IsReady);
|
||||
foreach (var item in driveList)
|
||||
{
|
||||
if (item.DriveType == DriveType.CDRom) continue;
|
||||
var diskInfo = new DiskInfo()
|
||||
{
|
||||
DiskName = item.Name,
|
||||
TypeName = item.DriveType.ToString(),
|
||||
TotalSize = Math.Round(item.TotalSize / 1024 / 1024 / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
AvailableFreeSpace = Math.Round(item.AvailableFreeSpace / 1024 / 1024 / 1024.0m, 2, MidpointRounding.AwayFromZero),
|
||||
};
|
||||
diskInfo.Used = diskInfo.TotalSize - diskInfo.AvailableFreeSpace;
|
||||
diskInfo.AvailablePercent = decimal.Ceiling(diskInfo.Used / (decimal)diskInfo.TotalSize * 100);
|
||||
diskInfos.Add(diskInfo);
|
||||
}
|
||||
}
|
||||
return diskInfos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取外网IP地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetIpFromOnline()
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = "https://4.ipw.cn";
|
||||
var httpRemoteService = App.GetRequiredService<IHttpRemoteService>();
|
||||
var ip = httpRemoteService.GetAsString(url);
|
||||
var (ipLocation, _, _) = CommonUtil.GetIpAddress(ip);
|
||||
return ip + " " + ipLocation;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "unknow";
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsUnix()
|
||||
{
|
||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
}
|
||||
|
||||
public static bool IsMacOS()
|
||||
{
|
||||
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
}
|
||||
|
||||
public static List<string> GetCPURates()
|
||||
{
|
||||
var cpuRates = new List<string>();
|
||||
string output = "";
|
||||
if (IsMacOS())
|
||||
{
|
||||
output = ShellUtil.Bash("top -l 1 | grep \"CPU usage\" | awk '{print $3 + $5}'");
|
||||
cpuRates.Add(output.Trim());
|
||||
}
|
||||
else if (IsUnix())
|
||||
{
|
||||
output = ShellUtil.Bash("awk '{u=$2+$4; t=$2+$4+$5; if (NR==1){u1=u; t1=t;} else print ($2+$4-u1) * 100 / (t-t1); }' <(grep 'cpu ' /proc/stat) <(sleep 1;grep 'cpu ' /proc/stat)");
|
||||
cpuRates.Add(output.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
output = ShellUtil.PowerShell("Get-CimInstance -ClassName Win32_Processor | Select-Object LoadPercentage");
|
||||
output = output.Replace("@", string.Empty).Replace("{", string.Empty).Replace("}", string.Empty).Replace("=", string.Empty).Trim();
|
||||
}
|
||||
cpuRates.AddRange(output.Replace("LoadPercentage", string.Empty).Trim().Split("\r\r\n"));
|
||||
}
|
||||
return cpuRates;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统运行时间
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetRunTime()
|
||||
{
|
||||
string runTime = string.Empty;
|
||||
string output = "";
|
||||
if (IsMacOS())
|
||||
{
|
||||
// macOS 获取系统启动时间:
|
||||
// sysctl -n kern.boottime | awk '{print $4}' | tr -d ','
|
||||
// 返回:1705379131
|
||||
// 使用date格式化即可
|
||||
output = ShellUtil.Bash("date -r $(sysctl -n kern.boottime | awk '{print $4}' | tr -d ',') +\"%Y-%m-%d %H:%M:%S\"").Trim();
|
||||
runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
|
||||
}
|
||||
else if (IsUnix())
|
||||
{
|
||||
output = ShellUtil.Bash("date -d \"$(awk -F. '{print $1}' /proc/uptime) second ago\" +\"%Y-%m-%d %H:%M:%S\"").Trim();
|
||||
runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
output = ShellUtil.Cmd("wmic", "OS get LastBootUpTime/Value");
|
||||
string[] outputArr = output.Split('=', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (outputArr.Length == 2)
|
||||
runTime = DateTimeUtil.FormatTime((DateTime.Now - outputArr[1].Split('.')[0].ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
output = ShellUtil.PowerShell("Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object LastBootUpTime");
|
||||
output = output.Replace("LastBootUpTime", string.Empty).Replace("@", string.Empty).Replace("{", string.Empty).Replace("}", string.Empty).Replace("=", string.Empty).Trim();
|
||||
runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
|
||||
}
|
||||
}
|
||||
return runTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内存信息
|
||||
/// </summary>
|
||||
public class MemoryMetrics
|
||||
{
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public double Total { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public double Used { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public double Free { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已用内存
|
||||
/// </summary>
|
||||
public string UsedRam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CPU使用率%
|
||||
/// </summary>
|
||||
public List<string> CpuRates { get; set; }
|
||||
|
||||
public string CpuRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总内存 GB
|
||||
/// </summary>
|
||||
public string TotalRam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内存使用率 %
|
||||
/// </summary>
|
||||
public string RamRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 空闲内存
|
||||
/// </summary>
|
||||
public string FreeRam { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 磁盘信息
|
||||
/// </summary>
|
||||
public class DiskInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 磁盘名
|
||||
/// </summary>
|
||||
public string DiskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类型名
|
||||
/// </summary>
|
||||
public string TypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总剩余
|
||||
/// </summary>
|
||||
public decimal TotalFree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总量
|
||||
/// </summary>
|
||||
public decimal TotalSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已使用
|
||||
/// </summary>
|
||||
public decimal Used { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可使用
|
||||
/// </summary>
|
||||
public decimal AvailableFreeSpace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用百分比
|
||||
/// </summary>
|
||||
public decimal AvailablePercent { get; set; }
|
||||
}
|
||||
|
||||
public class MemoryMetricsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// windows系统获取内存信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MemoryMetrics GetWindowsMetrics()
|
||||
{
|
||||
string output = "";
|
||||
var metrics = new MemoryMetrics();
|
||||
try
|
||||
{
|
||||
output = ShellUtil.Cmd("wmic", "OS get FreePhysicalMemory,TotalVisibleMemorySize /Value");
|
||||
var lines = output.Trim().Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lines.Length <= 1) return metrics;
|
||||
|
||||
var freeMemoryParts = lines[0].Split('=', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
var totalMemoryParts = lines[1].Split('=', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
metrics.Total = Math.Round(double.Parse(totalMemoryParts[1]) / 1024, 0);
|
||||
metrics.Free = Math.Round(double.Parse(freeMemoryParts[1]) / 1024, 0);//m
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
output = ShellUtil.PowerShell("Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object FreePhysicalMemory, TotalVisibleMemorySize");
|
||||
output = output.Replace("@", string.Empty).Replace("{", string.Empty).Replace("}", string.Empty).Trim();
|
||||
var lines = output.Trim().Split(';', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 跳过表头与分隔线(通常为前两行)
|
||||
if (lines.Length >= 2)
|
||||
{
|
||||
// 解析并转换为MB(原单位为KB)
|
||||
metrics.Free = Math.Round(double.Parse(lines[0].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries)[1]) / 1024, 0);
|
||||
metrics.Total = Math.Round(double.Parse(lines[1].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries)[1]) / 1024, 0);
|
||||
}
|
||||
}
|
||||
metrics.Used = metrics.Total - metrics.Free;
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unix系统获取
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MemoryMetrics GetUnixMetrics()
|
||||
{
|
||||
string output = ShellUtil.Bash("awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {print total,available}' /proc/meminfo");
|
||||
var metrics = new MemoryMetrics();
|
||||
var memory = output.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (memory.Length != 2) return metrics;
|
||||
|
||||
metrics.Total = double.Parse(memory[0]) / 1024;
|
||||
metrics.Free = double.Parse(memory[1]) / 1024;
|
||||
metrics.Used = metrics.Total - metrics.Free;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// macOS系统获取
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MemoryMetrics GetMacOSMetrics()
|
||||
{
|
||||
var metrics = new MemoryMetrics();
|
||||
//物理内存大小
|
||||
var total = ShellUtil.Bash("sysctl -n hw.memsize | awk '{printf \"%.2f\", $1/1024/1024}'");
|
||||
metrics.Total = float.Parse(total.Replace("%", string.Empty));
|
||||
//TODO:占用内存,检查效率
|
||||
var free = ShellUtil.Bash("top -l 1 -s 0 | awk '/PhysMem/ {print $6+$8}'");
|
||||
metrics.Free = float.Parse(free);
|
||||
metrics.Used = metrics.Total - metrics.Free;
|
||||
return metrics;
|
||||
}
|
||||
}
|
||||
|
||||
public class ShellUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// linux 系统命令
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <returns></returns>
|
||||
public static string Bash(string command)
|
||||
{
|
||||
var escapedArgs = command.Replace("\"", "\\\"");
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/bin/bash",
|
||||
Arguments = $"-c \"{escapedArgs}\"",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
string result = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
process.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// windows CMD 系统命令
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
public static string Cmd(string fileName, string args)
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true
|
||||
};
|
||||
|
||||
var output = string.Empty;
|
||||
using (var process = Process.Start(info))
|
||||
{
|
||||
output = process.StandardOutput.ReadToEnd();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Windows POWERSHELL 系统命令
|
||||
/// </summary>
|
||||
/// <param name="script"></param>
|
||||
/// <returns></returns>
|
||||
public static string PowerShell(string script)
|
||||
{
|
||||
using var PowerShellInstance = System.Management.Automation.PowerShell.Create();
|
||||
PowerShellInstance.AddScript(script);
|
||||
var PSOutput = PowerShellInstance.Invoke();
|
||||
|
||||
var output = new StringBuilder();
|
||||
foreach (var outputItem in PSOutput)
|
||||
{
|
||||
output.AppendLine(outputItem.ToString());
|
||||
}
|
||||
return output.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class ShellHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Linux 系统命令
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <returns></returns>
|
||||
public static string Bash(string command)
|
||||
{
|
||||
var escapedArgs = command.Replace("\"", "\\\"");
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/bin/bash",
|
||||
Arguments = $"-c \"{escapedArgs}\"",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
string result = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
process.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Windows CMD 系统命令
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
public static string Cmd(string fileName, string args)
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true
|
||||
};
|
||||
|
||||
var output = string.Empty;
|
||||
using (var process = Process.Start(info))
|
||||
{
|
||||
output = process.StandardOutput.ReadToEnd();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Windows POWERSHELL 系统命令
|
||||
/// </summary>
|
||||
/// <param name="script"></param>
|
||||
/// <returns></returns>
|
||||
public static string PowerShell(string script)
|
||||
{
|
||||
using var PowerShellInstance = System.Management.Automation.PowerShell.Create();
|
||||
PowerShellInstance.AddScript(script);
|
||||
var PSOutput = PowerShellInstance.Invoke();
|
||||
|
||||
var output = new StringBuilder();
|
||||
foreach (var outputItem in PSOutput)
|
||||
{
|
||||
output.AppendLine(outputItem.BaseObject.ToString());
|
||||
}
|
||||
return output.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public class CryptogramUtil
|
||||
{
|
||||
public static readonly bool StrongPassword = App.GetConfig<bool>("Cryptogram:StrongPassword"); // 是否开启密码强度验证
|
||||
public static readonly string PasswordStrengthValidation = App.GetConfig<string>("Cryptogram:PasswordStrengthValidation"); // 密码强度验证正则表达式
|
||||
public static readonly string PasswordStrengthValidationMsg = App.GetConfig<string>("Cryptogram:PasswordStrengthValidationMsg"); // 密码强度验证提示
|
||||
public static readonly string CryptoType = App.GetConfig<string>("Cryptogram:CryptoType"); // 加密类型
|
||||
public static readonly string PublicKey = App.GetConfig<string>("Cryptogram:PublicKey"); // 公钥
|
||||
public static readonly string PrivateKey = App.GetConfig<string>("Cryptogram:PrivateKey"); // 私钥
|
||||
|
||||
public static readonly string SM4_key = "0123456789abcdeffedcba9876543210";
|
||||
public static readonly string SM4_iv = "595298c7c6fd271f0402f804c33d3f66";
|
||||
|
||||
/// <summary>
|
||||
/// 加密
|
||||
/// </summary>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string Encrypt(string plainText)
|
||||
{
|
||||
if (CryptoType == CryptogramEnum.MD5.ToString())
|
||||
{
|
||||
return MD5Encryption.Encrypt(plainText);
|
||||
}
|
||||
else if (CryptoType == CryptogramEnum.SM2.ToString())
|
||||
{
|
||||
return SM2Encrypt(plainText);
|
||||
}
|
||||
else if (CryptoType == CryptogramEnum.SM4.ToString())
|
||||
{
|
||||
return SM4EncryptECB(plainText);
|
||||
}
|
||||
return plainText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解密
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
public static string Decrypt(string cipherText)
|
||||
{
|
||||
if (CryptoType == CryptogramEnum.SM2.ToString())
|
||||
{
|
||||
return SM2Decrypt(cipherText);
|
||||
}
|
||||
else if (CryptoType == CryptogramEnum.SM4.ToString())
|
||||
{
|
||||
return SM4DecryptECB(cipherText);
|
||||
}
|
||||
return cipherText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM2加密
|
||||
/// </summary>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM2Encrypt(string plainText)
|
||||
{
|
||||
return GMUtil.SM2Encrypt(PublicKey, plainText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM2解密
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM2Decrypt(string cipherText)
|
||||
{
|
||||
return GMUtil.SM2Decrypt(PrivateKey, cipherText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4加密(ECB)
|
||||
/// </summary>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4EncryptECB(string plainText)
|
||||
{
|
||||
return GMUtil.SM4EncryptECB(SM4_key, plainText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4解密(ECB)
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4DecryptECB(string cipherText)
|
||||
{
|
||||
return GMUtil.SM4DecryptECB(SM4_key, cipherText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4加密(CBC)
|
||||
/// </summary>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4EncryptCBC(string plainText)
|
||||
{
|
||||
return GMUtil.SM4EncryptCBC(SM4_key, SM4_iv, plainText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4解密(CBC)
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4DecryptCBC(string cipherText)
|
||||
{
|
||||
return GMUtil.SM4DecryptCBC(SM4_key, SM4_iv, cipherText);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义属性名称转换器
|
||||
/// </summary>
|
||||
public class CustomJsonPropertyConverter : JsonConverter<object>
|
||||
{
|
||||
public static readonly JsonSerializerOptions Options = new()
|
||||
{
|
||||
Converters = { new CustomJsonPropertyConverter() },
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
// 缓存类型信息避免重复反射
|
||||
private static readonly ConcurrentDictionary<Type, IReadOnlyList<PropertyMeta>> PropertyCache = new();
|
||||
|
||||
// 日期时间格式化配置
|
||||
private readonly string _dateTimeFormat;
|
||||
|
||||
public CustomJsonPropertyConverter(string dateTimeFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
{
|
||||
_dateTimeFormat = dateTimeFormat;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return PropertyCache.GetOrAdd(typeToConvert, type =>
|
||||
type.GetProperties()
|
||||
.Where(p => p.GetCustomAttribute<CustomJsonPropertyAttribute>() != null)
|
||||
.Select(p => new PropertyMeta(p))
|
||||
.ToList().AsReadOnly()
|
||||
).Count > 0;
|
||||
}
|
||||
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var jsonDoc = JsonDocument.ParseValue(ref reader);
|
||||
var instance = Activator.CreateInstance(typeToConvert);
|
||||
var properties = PropertyCache.GetOrAdd(typeToConvert, BuildPropertyMeta);
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
if (jsonDoc.RootElement.TryGetProperty(prop.JsonName, out var value))
|
||||
{
|
||||
object propertyValue;
|
||||
|
||||
// 特殊处理日期时间类型
|
||||
if (IsDateTimeType(prop.PropertyType))
|
||||
{
|
||||
propertyValue = HandleDateTimeValue(value, prop.PropertyType);
|
||||
}
|
||||
else
|
||||
{
|
||||
propertyValue = JsonSerializer.Deserialize(
|
||||
value.GetRawText(),
|
||||
prop.PropertyType,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
prop.SetValue(instance, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
var properties = PropertyCache.GetOrAdd(value.GetType(), BuildPropertyMeta);
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var propertyValue = prop.GetValue(value);
|
||||
|
||||
writer.WritePropertyName(prop.JsonName);
|
||||
|
||||
// 特殊处理日期时间类型
|
||||
if (propertyValue != null && IsDateTimeType(prop.PropertyType))
|
||||
{
|
||||
writer.WriteStringValue(FormatDateTime(propertyValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonSerializer.Serialize(writer, propertyValue, options);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<PropertyMeta> BuildPropertyMeta(Type type)
|
||||
{
|
||||
return type.GetProperties()
|
||||
.Select(p => new PropertyMeta(p))
|
||||
.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
private object HandleDateTimeValue(JsonElement value, Type targetType)
|
||||
{
|
||||
var dateStr = value.GetString();
|
||||
if (string.IsNullOrEmpty(dateStr)) return null;
|
||||
|
||||
var date = DateTime.Parse(dateStr);
|
||||
return targetType == typeof(DateTimeOffset)
|
||||
? new DateTimeOffset(date)
|
||||
: (object)date;
|
||||
}
|
||||
|
||||
private string FormatDateTime(object dateTime)
|
||||
{
|
||||
return dateTime switch
|
||||
{
|
||||
DateTime dt => dt.ToString(_dateTimeFormat),
|
||||
DateTimeOffset dto => dto.ToString(_dateTimeFormat),
|
||||
_ => dateTime?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsDateTimeType(Type type)
|
||||
{
|
||||
var actualType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
return actualType == typeof(DateTime) || actualType == typeof(DateTimeOffset);
|
||||
}
|
||||
|
||||
private class PropertyMeta
|
||||
{
|
||||
private readonly PropertyInfo _property;
|
||||
private readonly Func<object, object> _getter;
|
||||
private readonly Action<object, object> _setter;
|
||||
|
||||
public string JsonName { get; }
|
||||
public Type PropertyType => _property.PropertyType;
|
||||
|
||||
public PropertyMeta(PropertyInfo property)
|
||||
{
|
||||
_property = property;
|
||||
JsonName = property.GetCustomAttribute<CustomJsonPropertyAttribute>()?.Name ?? property.Name;
|
||||
|
||||
// 编译表达式树优化属性访问
|
||||
var instanceParam = Expression.Parameter(typeof(object), "instance");
|
||||
var valueParam = Expression.Parameter(typeof(object), "value");
|
||||
|
||||
// Getter
|
||||
var getterExpr = Expression.Lambda<Func<object, object>>(
|
||||
Expression.Convert(
|
||||
Expression.Property(
|
||||
Expression.Convert(instanceParam, property.DeclaringType),
|
||||
property),
|
||||
typeof(object)),
|
||||
instanceParam);
|
||||
_getter = getterExpr.Compile();
|
||||
|
||||
// Setter
|
||||
if (property.CanWrite)
|
||||
{
|
||||
var setterExpr = Expression.Lambda<Action<object, object>>(
|
||||
Expression.Assign(
|
||||
Expression.Property(
|
||||
Expression.Convert(instanceParam, property.DeclaringType),
|
||||
property),
|
||||
Expression.Convert(valueParam, property.PropertyType)),
|
||||
instanceParam, valueParam);
|
||||
_setter = setterExpr.Compile();
|
||||
}
|
||||
}
|
||||
|
||||
public object GetValue(object instance) => _getter(instance);
|
||||
|
||||
public void SetValue(object instance, object value)
|
||||
{
|
||||
if (_setter != null)
|
||||
{
|
||||
_setter(instance, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 时间帮助类
|
||||
/// </summary>
|
||||
public class DateTimeUtil
|
||||
{
|
||||
public readonly DateTime Date;
|
||||
|
||||
private DateTimeUtil(TimeSpan timeSpan = default)
|
||||
{
|
||||
Date = DateTime.Now.AddTicks(timeSpan.Ticks);
|
||||
}
|
||||
|
||||
private DateTimeUtil(DateTime time)
|
||||
{
|
||||
Date = time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实例化类
|
||||
/// </summary>
|
||||
/// <param name="timeSpan"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTimeUtil Init(TimeSpan timeSpan = default)
|
||||
{
|
||||
return new DateTimeUtil(timeSpan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实例化类
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTimeUtil Init(DateTime time)
|
||||
{
|
||||
return new DateTimeUtil(time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据unix时间戳的长度自动判断是秒还是以毫秒为单位
|
||||
/// </summary>
|
||||
/// <param name="unixTime"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertUnixTime(long unixTime)
|
||||
{
|
||||
// 判断时间戳长度
|
||||
bool isMilliseconds = unixTime > 9999999999;
|
||||
|
||||
if (isMilliseconds)
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(unixTime).ToLocalTime().DateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(unixTime).ToLocalTime().DateTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取开始时间
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <param name="days"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetBeginTime(DateTime? dateTime, int days = 0)
|
||||
{
|
||||
return dateTime == DateTime.MinValue || dateTime == null ? DateTime.Now.AddDays(days) : (DateTime)dateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳转本地时间-时间戳精确到秒
|
||||
/// </summary>
|
||||
public static DateTime ToLocalTimeDateBySeconds(long unix)
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(unix).ToLocalTime().DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间转时间戳Unix-时间戳精确到秒
|
||||
/// </summary>
|
||||
public static long ToUnixTimestampBySeconds(DateTime dt)
|
||||
{
|
||||
return new DateTimeOffset(dt).ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳转本地时间-时间戳精确到毫秒
|
||||
/// </summary>
|
||||
public static DateTime ToLocalTimeDateByMilliseconds(long unix)
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(unix).ToLocalTime().DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间转时间戳Unix-时间戳精确到毫秒
|
||||
/// </summary>
|
||||
public static long ToUnixTimestampByMilliseconds(DateTime dt)
|
||||
{
|
||||
return new DateTimeOffset(dt).ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 毫秒转天时分秒
|
||||
/// </summary>
|
||||
/// <param name="ms">TotalMilliseconds</param>
|
||||
/// <param name="isSimplify">是否简化显示</param>
|
||||
/// <returns></returns>
|
||||
public static string FormatTime(long ms, bool isSimplify = false)
|
||||
{
|
||||
int ss = 1000;
|
||||
int mi = ss * 60;
|
||||
int hh = mi * 60;
|
||||
int dd = hh * 24;
|
||||
|
||||
long day = ms / dd;
|
||||
long hour = (ms - day * dd) / hh;
|
||||
long minute = (ms - day * dd - hour * hh) / mi;
|
||||
long second = (ms - day * dd - hour * hh - minute * mi) / ss;
|
||||
//long milliSecond = ms - day * dd - hour * hh - minute * mi - second * ss;
|
||||
|
||||
string sDay = day < 10 ? "0" + day : "" + day; //天
|
||||
string sHour = hour < 10 ? "0" + hour : "" + hour;//小时
|
||||
string sMinute = minute < 10 ? "0" + minute : "" + minute;//分钟
|
||||
string sSecond = second < 10 ? "0" + second : "" + second;//秒
|
||||
//string sMilliSecond = milliSecond < 10 ? "0" + milliSecond : "" + milliSecond;//毫秒
|
||||
//sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : "" + sMilliSecond;
|
||||
|
||||
if (!isSimplify)
|
||||
return $"{sDay} 天 {sHour} 小时 {sMinute} 分 {sSecond} 秒";
|
||||
else
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (day > 0)
|
||||
result = $"{sDay}天";
|
||||
if (hour > 0)
|
||||
result = $"{result}{sHour}小时";
|
||||
if (minute > 0)
|
||||
result = $"{result}{sMinute}分";
|
||||
if (!result.IsNullOrEmpty())
|
||||
result = $"{result}{sSecond}秒";
|
||||
else
|
||||
result = $"{sSecond}秒";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取unix时间戳
|
||||
/// </summary>
|
||||
/// <param name="dt"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimeStamp(DateTime dt)
|
||||
{
|
||||
return ((DateTimeOffset)dt).ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日期天的最小时间
|
||||
/// </summary>
|
||||
/// <param name="dt"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetDayMinDate(DateTime dt)
|
||||
{
|
||||
return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日期天的最大时间
|
||||
/// </summary>
|
||||
/// <param name="dt"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
public static DateTime GetDayMaxDate(DateTime dt)
|
||||
{
|
||||
return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据日期是否在当前年份来格式化日期
|
||||
/// </summary>
|
||||
/// <param name="dt"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatDateTime(DateTime? dt)
|
||||
{
|
||||
return dt == null ? string.Empty : dt.Value.ToString(dt.Value.Year == DateTime.Now.Year ? "MM-dd HH:mm" : "yyyy-MM-dd HH:mm");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日期范围00:00:00 - 23:59:59
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<DateTime> GetTodayTimeList(DateTime time)
|
||||
{
|
||||
return new List<DateTime>
|
||||
{
|
||||
Convert.ToDateTime(time.ToString("D")),
|
||||
Convert.ToDateTime(time.AddDays(1).ToString("D")).AddSeconds(-1)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取星期几
|
||||
/// </summary>
|
||||
/// <param name="dt"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetWeekByDate(DateTime dt)
|
||||
{
|
||||
var day = new[] { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };
|
||||
return day[Convert.ToInt32(dt.DayOfWeek.ToString("d"))];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取这个月的第几周
|
||||
/// </summary>
|
||||
/// <param name="daytime"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetWeekNumInMonth(DateTime daytime)
|
||||
{
|
||||
int dayInMonth = daytime.Day;
|
||||
// 本月第一天
|
||||
DateTime firstDay = daytime.AddDays(1 - daytime.Day);
|
||||
// 本月第一天是周几
|
||||
int weekday = (int)firstDay.DayOfWeek == 0 ? 7 : (int)firstDay.DayOfWeek;
|
||||
// 本月第一周有几天
|
||||
int firstWeekEndDay = 7 - (weekday - 1);
|
||||
// 当前日期和第一周之差
|
||||
int diffday = dayInMonth - firstWeekEndDay;
|
||||
diffday = diffday > 0 ? diffday : 1;
|
||||
// 当前是第几周,若整除7就减一天
|
||||
return ((diffday % 7) == 0 ? (diffday / 7 - 1) : (diffday / 7)) + 1 + (dayInMonth > firstWeekEndDay ? 1 : 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取今天的时间范围
|
||||
/// </summary>
|
||||
/// <returns>返回包含开始时间和结束时间的元组</returns>
|
||||
public (DateTime Start, DateTime End) GetTodayRange()
|
||||
{
|
||||
var start = Date.Date; // 当天开始时间
|
||||
var end = start.AddDays(1).AddSeconds(-1); // 当天结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本月的时间范围
|
||||
/// </summary>
|
||||
/// <returns>返回包含开始时间和结束时间的元组</returns>
|
||||
public (DateTime Start, DateTime End) GetMonthRange()
|
||||
{
|
||||
return (GetFirstDayOfMonth(), GetLastDayOfMonth());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本月的第一天开始时间
|
||||
/// </summary>
|
||||
/// <returns>返回当月的第一天</returns>
|
||||
public DateTime GetFirstDayOfMonth()
|
||||
{
|
||||
return new DateTime(Date.Year, Date.Month, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本月的最后一天截至时间
|
||||
/// </summary>
|
||||
/// <returns>返回当月的最后一天</returns>
|
||||
public DateTime GetLastDayOfMonth()
|
||||
{
|
||||
var firstDayOfNextMonth = new DateTime(Date.Year, Date.Month, 1).AddMonths(1);
|
||||
return firstDayOfNextMonth.AddSeconds(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取今年的时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetYearRange()
|
||||
{
|
||||
return (GetFirstDayOfYear(), GetLastDayOfYear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取今年的第一天时间范围
|
||||
/// </summary>
|
||||
public DateTime GetFirstDayOfYear()
|
||||
{
|
||||
return new DateTime(Date.Year, 1, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取今年的最后一天时间范围
|
||||
/// </summary>
|
||||
public DateTime GetLastDayOfYear()
|
||||
{
|
||||
return new DateTime(Date.Year, 12, 31, 23, 59, 59);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取前天时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetDayBeforeYesterdayRange()
|
||||
{
|
||||
var start = Date.Date.AddDays(-2); // 前天开始时间
|
||||
var end = start.AddDays(1).AddSeconds(-1); // 前天结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取昨天时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetYesterdayRange()
|
||||
{
|
||||
var start = Date.Date.AddDays(-1); // 昨天开始时间
|
||||
var end = start.AddDays(1).AddSeconds(-1); // 昨天结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取上一周时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetLastWeekRange()
|
||||
{
|
||||
// 计算上周的天数差
|
||||
var daysToSubtract = (int)Date.DayOfWeek + 7; // 确保周日也能正确计算
|
||||
var start = Date.Date.AddDays(-daysToSubtract); // 上周第一天
|
||||
var end = start.AddDays(7).AddSeconds(-1); // 上周最后一天
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本周时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetThisWeekRange()
|
||||
{
|
||||
// 计算本周的天数差
|
||||
var daysToSubtract = (int)Date.DayOfWeek;
|
||||
var start = Date.Date.AddDays(-daysToSubtract); // 本周第一天
|
||||
var end = start.AddDays(7).AddSeconds(-1); // 本周最后一天
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取上月时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetLastMonthRange()
|
||||
{
|
||||
var firstDayOfLastMonth = new DateTime(Date.Year, Date.Month, 1).AddMonths(-1); // 上月第一天
|
||||
var lastDayOfLastMonth = firstDayOfLastMonth.AddMonths(1).AddSeconds(-1); // 上月最后一天
|
||||
return (firstDayOfLastMonth, lastDayOfLastMonth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取近3天的时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetLast3DaysRange()
|
||||
{
|
||||
var start = Date.Date.AddDays(-2); // 3天前的开始时间
|
||||
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取近7天的时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetLast7DaysRange()
|
||||
{
|
||||
var start = Date.Date.AddDays(-6); // 7天前的开始时间
|
||||
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取近15天的时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetLast15DaysRange()
|
||||
{
|
||||
var start = Date.Date.AddDays(-14); // 15天前的开始时间
|
||||
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取近3个月的时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetLast3MonthsRange()
|
||||
{
|
||||
var start = Date.Date.AddMonths(-3); // 3个月前的开始时间
|
||||
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取上半年的时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetFirstHalfYearRange()
|
||||
{
|
||||
var start = new DateTime(Date.Year, 1, 1); // 上半年开始时间
|
||||
var end = new DateTime(Date.Year, 6, 30, 23, 59, 59); // 上半年结束时间
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下半年的时间范围
|
||||
/// </summary>
|
||||
public (DateTime Start, DateTime End) GetSecondHalfYearRange()
|
||||
{
|
||||
var start = new DateTime(Date.Year, 7, 1); // 下半年开始时间
|
||||
var end = new DateTime(Date.Year, 12, 31, 23, 59, 59); // 下半年结束时间
|
||||
return (start, end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using OfficeOpenXml;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public class ExcelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据导入
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static IActionResult ImportData<IN, T>(IFormFile file, Action<List<IN>, Action<StorageableResult<T>, List<IN>, List<T>>> action) where IN : BaseImportInput, new() where T : EntityBaseId, new()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = CommonUtil.ImportExcelDataAsync<IN>(file).Result ?? throw Oops.Oh("有效数据为空");
|
||||
result.ForEach(u => u.Id = YitIdHelper.NextId());
|
||||
|
||||
var tasks = new List<Task>();
|
||||
action.Invoke(result, (storageable, pageItems, rows) =>
|
||||
{
|
||||
// 标记校验信息
|
||||
tasks.Add(Task.Run(() =>
|
||||
{
|
||||
if (!storageable.TotalList.Any()) return;
|
||||
|
||||
// 通过Id标记校验信息
|
||||
var itemMap = pageItems.ToDictionary(u => u.Id, u => u);
|
||||
foreach (var item in storageable.TotalList)
|
||||
{
|
||||
var temp = itemMap.GetValueOrDefault(item.Item.Id);
|
||||
if (temp != null) temp.Error ??= item.StorageMessage;
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// 等待所有标记验证信息任务完成
|
||||
Task.WhenAll(tasks).GetAwaiter().GetResult();
|
||||
|
||||
// 仅导出错误记录
|
||||
var errorList = result.Where(u => !string.IsNullOrWhiteSpace(u.Error)).ToList();
|
||||
if (!errorList.Any())
|
||||
return new JsonResult(AdminResultProvider.Ok("导入成功"));
|
||||
return ExportData(errorList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new JsonResult(AdminResultProvider.Error(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出Xlsx数据
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static IActionResult ExportData(dynamic list, string fileName = "导入记录")
|
||||
{
|
||||
var exporter = new ExcelExporter();
|
||||
var fs = new MemoryStream(exporter.ExportAsByteArray(list).GetAwaiter().GetResult());
|
||||
return new XlsxFileResult(stream: fs, fileDownloadName: $"{fileName}-{DateTime.Now:yyyy-MM-dd_HHmmss}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据类型导出Xlsx模板
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <param name="filename"></param>
|
||||
/// <param name="addListValidationFun"></param>
|
||||
/// <returns></returns>
|
||||
public static IActionResult ExportTemplate<T>(IEnumerable<T> list, string filename = "导入模板", Func<ExcelWorksheet, PropertyInfo, IEnumerable<string>> addListValidationFun = null)
|
||||
{
|
||||
using var package = new ExcelPackage((ExportData(list, filename) as XlsxFileResult)!.Stream);
|
||||
var worksheet = package.Workbook.Worksheets[0];
|
||||
|
||||
// 创建一个隐藏的sheet,用于添加下拉列表
|
||||
var dropdownSheet = package.Workbook.Worksheets.Add("下拉数据");
|
||||
dropdownSheet.Hidden = eWorkSheetHidden.Hidden;
|
||||
|
||||
var sysDictTypeService = App.GetService<SysDictTypeService>();
|
||||
foreach (var prop in typeof(T).GetProperties())
|
||||
{
|
||||
var propType = prop.PropertyType;
|
||||
|
||||
var headerAttr = prop.GetCustomAttribute<ExporterHeaderAttribute>();
|
||||
var isNullableEnum = propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(Nullable<>) && Nullable.GetUnderlyingType(propType).IsEnum();
|
||||
if (isNullableEnum) propType = Nullable.GetUnderlyingType(propType);
|
||||
if (headerAttr == null) continue;
|
||||
|
||||
// 获取列序号
|
||||
var columnIndex = 0;
|
||||
foreach (var item in worksheet.Cells[1, 1, 1, worksheet.Dimension.End.Column])
|
||||
if (++columnIndex > 0 && item.Text.Equals(headerAttr.DisplayName)) break;
|
||||
if (columnIndex <= 0) continue;
|
||||
|
||||
// 优先从代理函数中获取下列列表,若为空且字段为枚举型,则填充枚举项为下列列表,若为字典字段,则填充字典值value列表为下列列表
|
||||
var dataList = addListValidationFun?.Invoke(worksheet, prop)?.ToList();
|
||||
if (dataList == null)
|
||||
{
|
||||
// 填充枚举项为下列列表
|
||||
if (propType.IsEnum())
|
||||
{
|
||||
dataList = propType.EnumToList()?.Select(it => it.Describe).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 获取字段上的字典特性
|
||||
var dict = prop.GetCustomAttribute<DictAttribute>();
|
||||
if (dict != null)
|
||||
{
|
||||
// 填充字典值value为下列列表
|
||||
dataList = sysDictTypeService.GetDataList(new GetDataDictTypeInput { Code = dict.DictTypeCode })
|
||||
.Result?.Select(x => x.Label).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dataList != null)
|
||||
{
|
||||
// 添加下拉列表
|
||||
AddListValidation(dropdownSheet, columnIndex, dataList);
|
||||
dropdownSheet.Cells[1, columnIndex, dataList.Count, columnIndex].LoadFromCollection(dataList);
|
||||
}
|
||||
}
|
||||
|
||||
package.Save();
|
||||
package.Stream.Position = 0;
|
||||
return new XlsxFileResult(stream: package.Stream, fileDownloadName: $"{filename}-{DateTime.Now:yyyy-MM-dd_HHmmss}");
|
||||
|
||||
void AddListValidation(ExcelWorksheet dropdownSheet, int columnIndex, List<string> dataList)
|
||||
{
|
||||
var validation = worksheet.DataValidations.AddListValidation(worksheet.Cells[2, columnIndex, ExcelPackage.MaxRows, columnIndex].Address);
|
||||
validation!.Formula.ExcelFormula = "=" + dropdownSheet.Cells[1, columnIndex, dataList.Count, columnIndex].FullAddressAbsolute;
|
||||
validation.ShowErrorMessage = true;
|
||||
validation.ErrorTitle = "无效输入";
|
||||
validation.Error = "请从列表中选择一个有效的选项";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 文件帮助类
|
||||
/// </summary>
|
||||
public static class FileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试删除文件/目录
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryDelete(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) return false;
|
||||
if (Directory.Exists(path)) Directory.Delete(path, recursive: true);
|
||||
else File.Delete(path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制目录
|
||||
/// </summary>
|
||||
/// <param name="sourceDir"></param>
|
||||
/// <param name="destinationDir"></param>
|
||||
/// <param name="overwrite"></param>
|
||||
public static void CopyDirectory(string sourceDir, string destinationDir, bool overwrite = false)
|
||||
{
|
||||
// 检查源目录是否存在
|
||||
if (!Directory.Exists(sourceDir)) throw new DirectoryNotFoundException("Source directory not found: " + sourceDir);
|
||||
|
||||
// 如果目标目录不存在,则创建它
|
||||
if (!Directory.Exists(destinationDir)) Directory.CreateDirectory(destinationDir!);
|
||||
|
||||
// 获取源目录下的所有文件并复制它们
|
||||
foreach (string file in Directory.GetFiles(sourceDir))
|
||||
{
|
||||
string name = Path.GetFileName(file);
|
||||
string dest = Path.Combine(destinationDir, name);
|
||||
File.Copy(file, dest, overwrite);
|
||||
}
|
||||
|
||||
// 递归复制所有子目录
|
||||
foreach (string directory in Directory.GetDirectories(sourceDir))
|
||||
{
|
||||
string name = Path.GetFileName(directory);
|
||||
string dest = Path.Combine(destinationDir, name);
|
||||
CopyDirectory(directory, dest, overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在文件倒数第lastIndex个identifier前插入内容(备份原文件)
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="insertContent">要插入的内容</param>
|
||||
/// <param name="identifier">标识符号</param>
|
||||
/// <param name="lastIndex">倒数第几个标识符</param>
|
||||
/// <param name="createBackup">是否创建备份文件</param>
|
||||
public static async Task InsertsStringAtSpecifiedLocationInFile(string filePath, string insertContent, char identifier, int lastIndex, bool createBackup = false)
|
||||
{
|
||||
// 参数校验
|
||||
if (lastIndex < 1) throw new ArgumentOutOfRangeException(nameof(lastIndex));
|
||||
if (identifier == 0) throw new ArgumentException("标识符不能为空字符");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException("目标文件不存在", filePath);
|
||||
|
||||
// 创建备份文件
|
||||
if (createBackup)
|
||||
{
|
||||
string backupPath = $"{filePath}.bak_{DateTime.Now:yyyyMMddHHmmss}";
|
||||
File.Copy(filePath, backupPath, true);
|
||||
}
|
||||
|
||||
using var reader = new StreamReader(filePath, Encoding.UTF8);
|
||||
var content = await reader.ReadToEndAsync();
|
||||
reader.Close();
|
||||
// 逆向查找算法
|
||||
int index = content.LastIndexOf(identifier);
|
||||
if (index == -1)
|
||||
{
|
||||
throw new ArgumentException($"文件中未包含{identifier}");
|
||||
}
|
||||
|
||||
int resIndex = content.LastIndexOf(identifier, index - lastIndex);
|
||||
if (resIndex == -1)
|
||||
{
|
||||
throw new ArgumentException($"文件中{identifier}不足{lastIndex}个");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(content);
|
||||
sb = sb.Insert(resIndex, insertContent);
|
||||
await WriteToFileAsync(filePath, sb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入文件内容
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="sb"></param>
|
||||
public static async Task WriteToFileAsync(string filePath, StringBuilder sb)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||
await using var writer = new StreamWriter(filePath, false, new UTF8Encoding(false)); // 无BOM
|
||||
await writer.WriteAsync(sb.ToString());
|
||||
writer.Close();
|
||||
Console.WriteLine($"文件【{filePath}】写入完成");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Org.BouncyCastle.Asn1;
|
||||
using Org.BouncyCastle.Asn1.GM;
|
||||
using Org.BouncyCastle.Asn1.X9;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Digests;
|
||||
using Org.BouncyCastle.Crypto.Engines;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Math;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using Org.BouncyCastle.Utilities.Encoders;
|
||||
using Org.BouncyCastle.X509;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/**
|
||||
*
|
||||
* 用BC的注意点:
|
||||
* 这个版本的BC对SM3withSM2的结果为asn1格式的r和s,如果需要直接拼接的r||s需要自己转换。下面rsAsn1ToPlainByteArray、rsPlainByteArrayToAsn1就在干这事。
|
||||
* 这个版本的BC对SM2的结果为C1||C2||C3,据说为旧标准,新标准为C1||C3||C2,用新标准的需要自己转换。下面(被注释掉的)changeC1C2C3ToC1C3C2、changeC1C3C2ToC1C2C3就在干这事。java版的高版本有加上C1C3C2,csharp版没准以后也会加,但目前还没有,java版的目前可以初始化时“ SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);”。
|
||||
*
|
||||
*/
|
||||
|
||||
public class GM
|
||||
{
|
||||
private static X9ECParameters x9ECParameters = GMNamedCurves.GetByName("sm2p256v1");
|
||||
private static ECDomainParameters ecDomainParameters = new(x9ECParameters.Curve, x9ECParameters.G, x9ECParameters.N);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param msg
|
||||
* @param userId
|
||||
* @param privateKey
|
||||
* @return r||s,直接拼接byte数组的rs
|
||||
*/
|
||||
|
||||
public static byte[] SignSm3WithSm2(byte[] msg, byte[] userId, AsymmetricKeyParameter privateKey)
|
||||
{
|
||||
return RsAsn1ToPlainByteArray(SignSm3WithSm2Asn1Rs(msg, userId, privateKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param msg
|
||||
* @param userId
|
||||
* @param privateKey
|
||||
* @return rs in <b>asn1 format</b>
|
||||
*/
|
||||
|
||||
public static byte[] SignSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, AsymmetricKeyParameter privateKey)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner("SM3withSM2");
|
||||
signer.Init(true, new ParametersWithID(privateKey, userId));
|
||||
signer.BlockUpdate(msg, 0, msg.Length);
|
||||
byte[] sig = signer.GenerateSignature();
|
||||
return sig;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param msg
|
||||
* @param userId
|
||||
* @param rs r||s,直接拼接byte数组的rs
|
||||
* @param publicKey
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static bool VerifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, AsymmetricKeyParameter publicKey)
|
||||
{
|
||||
if (rs == null || msg == null || userId == null) return false;
|
||||
if (rs.Length != RS_LEN * 2) return false;
|
||||
return VerifySm3WithSm2Asn1Rs(msg, userId, RsPlainByteArrayToAsn1(rs), publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param msg
|
||||
* @param userId
|
||||
* @param rs in <b>asn1 format</b>
|
||||
* @param publicKey
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static bool VerifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] sign, AsymmetricKeyParameter publicKey)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner("SM3withSM2");
|
||||
signer.Init(false, new ParametersWithID(publicKey, userId));
|
||||
signer.BlockUpdate(msg, 0, msg.Length);
|
||||
return signer.VerifySignature(sign);
|
||||
}
|
||||
|
||||
/**
|
||||
* bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2
|
||||
* @param c1c2c3
|
||||
* @return
|
||||
*/
|
||||
|
||||
private static byte[] ChangeC1C2C3ToC1C3C2(byte[] c1c2c3)
|
||||
{
|
||||
int c1Len = (x9ECParameters.Curve.FieldSize + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
|
||||
const int c3Len = 32; //new SM3Digest().getDigestSize();
|
||||
byte[] result = new byte[c1c2c3.Length];
|
||||
Buffer.BlockCopy(c1c2c3, 0, result, 0, c1Len); //c1
|
||||
Buffer.BlockCopy(c1c2c3, c1c2c3.Length - c3Len, result, c1Len, c3Len); //c3
|
||||
Buffer.BlockCopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.Length - c1Len - c3Len); //c2
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密
|
||||
* @param c1c3c2
|
||||
* @return
|
||||
*/
|
||||
|
||||
private static byte[] ChangeC1C3C2ToC1C2C3(byte[] c1c3c2)
|
||||
{
|
||||
int c1Len = (x9ECParameters.Curve.FieldSize + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
|
||||
const int c3Len = 32; //new SM3Digest().GetDigestSize();
|
||||
byte[] result = new byte[c1c3c2.Length];
|
||||
Buffer.BlockCopy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65
|
||||
Buffer.BlockCopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.Length - c1Len - c3Len); //c2
|
||||
Buffer.BlockCopy(c1c3c2, c1Len, result, c1c3c2.Length - c3Len, c3Len); //c3
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* c1||c3||c2
|
||||
* @param data
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static byte[] Sm2Decrypt(byte[] data, AsymmetricKeyParameter key)
|
||||
{
|
||||
return Sm2DecryptOld(ChangeC1C3C2ToC1C2C3(data), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* c1||c3||c2
|
||||
* @param data
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static byte[] Sm2Encrypt(byte[] data, AsymmetricKeyParameter key)
|
||||
{
|
||||
return ChangeC1C2C3ToC1C3C2(Sm2EncryptOld(data, key));
|
||||
}
|
||||
|
||||
/**
|
||||
* c1||c2||c3
|
||||
* @param data
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static byte[] Sm2EncryptOld(byte[] data, AsymmetricKeyParameter pubkey)
|
||||
{
|
||||
SM2Engine sm2Engine = new SM2Engine();
|
||||
sm2Engine.Init(true, new ParametersWithRandom(pubkey, new SecureRandom()));
|
||||
return sm2Engine.ProcessBlock(data, 0, data.Length);
|
||||
}
|
||||
|
||||
/**
|
||||
* c1||c2||c3
|
||||
* @param data
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static byte[] Sm2DecryptOld(byte[] data, AsymmetricKeyParameter key)
|
||||
{
|
||||
SM2Engine sm2Engine = new SM2Engine();
|
||||
sm2Engine.Init(false, key);
|
||||
return sm2Engine.ProcessBlock(data, 0, data.Length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static byte[] Sm3(byte[] bytes)
|
||||
{
|
||||
SM3Digest digest = new();
|
||||
digest.BlockUpdate(bytes, 0, bytes.Length);
|
||||
byte[] result = DigestUtilities.DoFinal(digest);
|
||||
return result;
|
||||
}
|
||||
|
||||
private const int RS_LEN = 32;
|
||||
|
||||
private static byte[] BigIntToFixexLengthBytes(BigInteger rOrS)
|
||||
{
|
||||
// for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
|
||||
// r and s are the result of mod n, so they should be less than n and have length<=32
|
||||
byte[] rs = rOrS.ToByteArray();
|
||||
if (rs.Length == RS_LEN) return rs;
|
||||
else if (rs.Length == RS_LEN + 1 && rs[0] == 0) return Arrays.CopyOfRange(rs, 1, RS_LEN + 1);
|
||||
else if (rs.Length < RS_LEN)
|
||||
{
|
||||
byte[] result = new byte[RS_LEN];
|
||||
Arrays.Fill(result, (byte)0);
|
||||
Buffer.BlockCopy(rs, 0, result, RS_LEN - rs.Length, rs.Length);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("err rs: " + Hex.ToHexString(rs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s
|
||||
* @param rsDer rs in asn1 format
|
||||
* @return sign result in plain byte array
|
||||
*/
|
||||
|
||||
private static byte[] RsAsn1ToPlainByteArray(byte[] rsDer)
|
||||
{
|
||||
Asn1Sequence seq = Asn1Sequence.GetInstance(rsDer);
|
||||
byte[] r = BigIntToFixexLengthBytes(DerInteger.GetInstance(seq[0]).Value);
|
||||
byte[] s = BigIntToFixexLengthBytes(DerInteger.GetInstance(seq[1]).Value);
|
||||
byte[] result = new byte[RS_LEN * 2];
|
||||
Buffer.BlockCopy(r, 0, result, 0, r.Length);
|
||||
Buffer.BlockCopy(s, 0, result, RS_LEN, s.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式
|
||||
* @param sign in plain byte array
|
||||
* @return rs result in asn1 format
|
||||
*/
|
||||
|
||||
private static byte[] RsPlainByteArrayToAsn1(byte[] sign)
|
||||
{
|
||||
if (sign.Length != RS_LEN * 2) throw new ArgumentException("err rs. ");
|
||||
BigInteger r = new BigInteger(1, Arrays.CopyOfRange(sign, 0, RS_LEN));
|
||||
BigInteger s = new BigInteger(1, Arrays.CopyOfRange(sign, RS_LEN, RS_LEN * 2));
|
||||
Asn1EncodableVector v = new Asn1EncodableVector
|
||||
{
|
||||
new DerInteger(r),
|
||||
new DerInteger(s)
|
||||
};
|
||||
|
||||
return new DerSequence(v).GetEncoded("DER");
|
||||
}
|
||||
|
||||
// 生成公私匙对
|
||||
public static AsymmetricCipherKeyPair GenerateKeyPair()
|
||||
{
|
||||
ECKeyPairGenerator kpGen = new();
|
||||
kpGen.Init(new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom()));
|
||||
return kpGen.GenerateKeyPair();
|
||||
}
|
||||
|
||||
public static ECPrivateKeyParameters GetPrivatekeyFromD(BigInteger d)
|
||||
{
|
||||
return new ECPrivateKeyParameters(d, ecDomainParameters);
|
||||
}
|
||||
|
||||
public static ECPublicKeyParameters GetPublickeyFromXY(BigInteger x, BigInteger y)
|
||||
{
|
||||
return new ECPublicKeyParameters(x9ECParameters.Curve.CreatePoint(x, y), ecDomainParameters);
|
||||
}
|
||||
|
||||
public static AsymmetricKeyParameter GetPublickeyFromX509File(FileInfo file)
|
||||
{
|
||||
FileStream fileStream = null;
|
||||
try
|
||||
{
|
||||
//file.DirectoryName + "\\" + file.Name
|
||||
fileStream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read);
|
||||
X509Certificate certificate = new X509CertificateParser().ReadCertificate(fileStream);
|
||||
return certificate.GetPublicKey();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//log.Error(file.Name + "读取失败,异常:" + e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fileStream != null)
|
||||
fileStream.Close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public class Sm2Cert
|
||||
{
|
||||
public AsymmetricKeyParameter privateKey;
|
||||
public AsymmetricKeyParameter publicKey;
|
||||
public string certId;
|
||||
}
|
||||
|
||||
private static byte[] ToByteArray(int i)
|
||||
{
|
||||
byte[] byteArray = new byte[4];
|
||||
byteArray[0] = (byte)(i >> 24);
|
||||
byteArray[1] = (byte)((i & 0xFFFFFF) >> 16);
|
||||
byteArray[2] = (byte)((i & 0xFFFF) >> 8);
|
||||
byteArray[3] = (byte)(i & 0xFF);
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组拼接
|
||||
*
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
|
||||
private static byte[] Join(params byte[][] byteArrays)
|
||||
{
|
||||
List<byte> byteSource = new();
|
||||
for (int i = 0; i < byteArrays.Length; i++)
|
||||
{
|
||||
byteSource.AddRange(byteArrays[i]);
|
||||
}
|
||||
byte[] data = byteSource.ToArray();
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 密钥派生函数
|
||||
*
|
||||
* @param Z
|
||||
* @param klen
|
||||
* 生成klen字节数长度的密钥
|
||||
* @return
|
||||
*/
|
||||
|
||||
private static byte[] KDF(byte[] Z, int klen)
|
||||
{
|
||||
int ct = 1;
|
||||
int end = (int)Math.Ceiling(klen * 1.0 / 32);
|
||||
List<byte> byteSource = new();
|
||||
|
||||
for (int i = 1; i < end; i++)
|
||||
{
|
||||
byteSource.AddRange(Sm3(Join(Z, ToByteArray(ct))));
|
||||
ct++;
|
||||
}
|
||||
byte[] last = Sm3(Join(Z, ToByteArray(ct)));
|
||||
if (klen % 32 == 0)
|
||||
{
|
||||
byteSource.AddRange(last);
|
||||
}
|
||||
else
|
||||
byteSource.AddRange(Arrays.CopyOfRange(last, 0, klen % 32));
|
||||
return byteSource.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] Sm4DecryptCBC(byte[] keyBytes, byte[] cipher, byte[] iv, string algo)
|
||||
{
|
||||
if (keyBytes.Length != 16) throw new ArgumentException("err key length");
|
||||
if (cipher.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length");
|
||||
|
||||
KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
|
||||
IBufferedCipher c = CipherUtilities.GetCipher(algo);
|
||||
if (iv == null) iv = ZeroIv(algo);
|
||||
c.Init(false, new ParametersWithIV(key, iv));
|
||||
return c.DoFinal(cipher);
|
||||
}
|
||||
|
||||
public static byte[] Sm4EncryptCBC(byte[] keyBytes, byte[] plain, byte[] iv, string algo)
|
||||
{
|
||||
if (keyBytes.Length != 16) throw new ArgumentException("err key length");
|
||||
if (plain.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length");
|
||||
|
||||
KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
|
||||
IBufferedCipher c = CipherUtilities.GetCipher(algo);
|
||||
if (iv == null) iv = ZeroIv(algo);
|
||||
c.Init(true, new ParametersWithIV(key, iv));
|
||||
return c.DoFinal(plain);
|
||||
}
|
||||
|
||||
public static byte[] Sm4EncryptECB(byte[] keyBytes, byte[] plain, string algo)
|
||||
{
|
||||
if (keyBytes.Length != 16) throw new ArgumentException("err key length");
|
||||
//NoPadding 的情况下需要校验数据长度是16的倍数.
|
||||
if (plain.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length");
|
||||
|
||||
KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
|
||||
IBufferedCipher c = CipherUtilities.GetCipher(algo);
|
||||
c.Init(true, key);
|
||||
return c.DoFinal(plain);
|
||||
}
|
||||
|
||||
public static byte[] Sm4DecryptECB(byte[] keyBytes, byte[] cipher, string algo)
|
||||
{
|
||||
if (keyBytes.Length != 16) throw new ArgumentException("err key length");
|
||||
if (cipher.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length");
|
||||
|
||||
KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
|
||||
IBufferedCipher c = CipherUtilities.GetCipher(algo);
|
||||
c.Init(false, key);
|
||||
return c.DoFinal(cipher);
|
||||
}
|
||||
|
||||
public const string SM4_ECB_NOPADDING = "SM4/ECB/NoPadding";
|
||||
public const string SM4_CBC_NOPADDING = "SM4/CBC/NoPadding";
|
||||
public const string SM4_ECB_PKCS7PADDING = "SM4/ECB/PKCS7Padding";
|
||||
public const string SM4_CBC_PKCS7PADDING = "SM4/CBC/PKCS7Padding";
|
||||
|
||||
/**
|
||||
* cfca官网CSP沙箱导出的sm2文件
|
||||
* @param pem 二进制原文
|
||||
* @param pwd 密码
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static Sm2Cert ReadSm2File(byte[] pem, string pwd)
|
||||
{
|
||||
Sm2Cert sm2Cert = new();
|
||||
|
||||
Asn1Sequence asn1Sequence = (Asn1Sequence)Asn1Object.FromByteArray(pem);
|
||||
// ASN1Integer asn1Integer = (ASN1Integer) asn1Sequence.getObjectAt(0); //version=1
|
||||
Asn1Sequence priSeq = (Asn1Sequence)asn1Sequence[1];//private key
|
||||
Asn1Sequence pubSeq = (Asn1Sequence)asn1Sequence[2];//public key and x509 cert
|
||||
|
||||
// ASN1ObjectIdentifier sm2DataOid = (ASN1ObjectIdentifier) priSeq.getObjectAt(0);
|
||||
// ASN1ObjectIdentifier sm4AlgOid = (ASN1ObjectIdentifier) priSeq.getObjectAt(1);
|
||||
Asn1OctetString priKeyAsn1 = (Asn1OctetString)priSeq[2];
|
||||
byte[] key = KDF(System.Text.Encoding.UTF8.GetBytes(pwd), 32);
|
||||
byte[] priKeyD = Sm4DecryptCBC(Arrays.CopyOfRange(key, 16, 32),
|
||||
priKeyAsn1.GetOctets(),
|
||||
Arrays.CopyOfRange(key, 0, 16), SM4_CBC_PKCS7PADDING);
|
||||
sm2Cert.privateKey = GetPrivatekeyFromD(new BigInteger(1, priKeyD));
|
||||
// log.Info(Hex.toHexString(priKeyD));
|
||||
|
||||
// ASN1ObjectIdentifier sm2DataOidPub = (ASN1ObjectIdentifier) pubSeq.getObjectAt(0);
|
||||
Asn1OctetString pubKeyX509 = (Asn1OctetString)pubSeq[1];
|
||||
X509Certificate x509 = new X509CertificateParser().ReadCertificate(pubKeyX509.GetOctets());
|
||||
sm2Cert.publicKey = x509.GetPublicKey();
|
||||
sm2Cert.certId = x509.SerialNumber.ToString(10); //这里转10进制,有啥其他进制要求的自己改改
|
||||
return sm2Cert;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cert
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static Sm2Cert ReadSm2X509Cert(byte[] cert)
|
||||
{
|
||||
Sm2Cert sm2Cert = new();
|
||||
|
||||
X509Certificate x509 = new X509CertificateParser().ReadCertificate(cert);
|
||||
sm2Cert.publicKey = x509.GetPublicKey();
|
||||
sm2Cert.certId = x509.SerialNumber.ToString(10); //这里转10进制,有啥其他进制要求的自己改改
|
||||
return sm2Cert;
|
||||
}
|
||||
|
||||
public static byte[] ZeroIv(string algo)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher(algo);
|
||||
int blockSize = cipher.GetBlockSize();
|
||||
byte[] iv = new byte[blockSize];
|
||||
Arrays.Fill(iv, (byte)0);
|
||||
return iv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Math;
|
||||
using Org.BouncyCastle.Utilities.Encoders;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// GM工具类
|
||||
/// </summary>
|
||||
public class GMUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// SM2加密
|
||||
/// </summary>
|
||||
/// <param name="publicKeyHex"></param>
|
||||
/// <param name="data_string"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM2Encrypt(string publicKeyHex, string data_string)
|
||||
{
|
||||
// 如果是130位公钥,.NET使用的话,把开头的04截取掉
|
||||
if (publicKeyHex.Length == 130)
|
||||
{
|
||||
publicKeyHex = publicKeyHex.Substring(2, 128);
|
||||
}
|
||||
// 公钥X,前64位
|
||||
string x = publicKeyHex.Substring(0, 64);
|
||||
// 公钥Y,后64位
|
||||
string y = publicKeyHex.Substring(64);
|
||||
// 获取公钥对象
|
||||
AsymmetricKeyParameter publicKey1 = GM.GetPublickeyFromXY(new BigInteger(x, 16), new BigInteger(y, 16));
|
||||
// Sm2Encrypt: C1C3C2
|
||||
// Sm2EncryptOld: C1C2C3
|
||||
byte[] digestByte = GM.Sm2Encrypt(Encoding.UTF8.GetBytes(data_string), publicKey1);
|
||||
string strSM2 = Hex.ToHexString(digestByte);
|
||||
return strSM2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM2解密
|
||||
/// </summary>
|
||||
/// <param name="privateKey_string"></param>
|
||||
/// <param name="encryptedData_string"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM2Decrypt(string privateKey_string, string encryptedData_string)
|
||||
{
|
||||
if (!encryptedData_string.StartsWith("04"))
|
||||
encryptedData_string = "04" + encryptedData_string;
|
||||
BigInteger d = new(privateKey_string, 16);
|
||||
// 先拿到私钥对象,用ECPrivateKeyParameters 或 AsymmetricKeyParameter 都可以
|
||||
// ECPrivateKeyParameters bcecPrivateKey = GmUtil.GetPrivatekeyFromD(d);
|
||||
AsymmetricKeyParameter bcecPrivateKey = GM.GetPrivatekeyFromD(d);
|
||||
byte[] byToDecrypt = Hex.Decode(encryptedData_string);
|
||||
byte[] byDecrypted = GM.Sm2Decrypt(byToDecrypt, bcecPrivateKey);
|
||||
string strDecrypted = Encoding.UTF8.GetString(byDecrypted);
|
||||
return strDecrypted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4加密(ECB)
|
||||
/// </summary>
|
||||
/// <param name="key_string"></param>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4EncryptECB(string key_string, string plainText)
|
||||
{
|
||||
byte[] key = Hex.Decode(key_string);
|
||||
byte[] bs = GM.Sm4EncryptECB(key, Encoding.UTF8.GetBytes(plainText), GM.SM4_ECB_PKCS7PADDING);//NoPadding 的情况下需要校验数据长度是16的倍数. 使用 HandleSm4Padding 处理
|
||||
return Hex.ToHexString(bs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4解密(ECB)
|
||||
/// </summary>
|
||||
/// <param name="key_string"></param>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4DecryptECB(string key_string, string cipherText)
|
||||
{
|
||||
byte[] key = Hex.Decode(key_string);
|
||||
byte[] bs = GM.Sm4DecryptECB(key, Hex.Decode(cipherText), GM.SM4_ECB_PKCS7PADDING);
|
||||
return Encoding.UTF8.GetString(bs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4加密(CBC)
|
||||
/// </summary>
|
||||
/// <param name="key_string"></param>
|
||||
/// <param name="iv_string"></param>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4EncryptCBC(string key_string, string iv_string, string plainText)
|
||||
{
|
||||
byte[] key = Hex.Decode(key_string);
|
||||
byte[] iv = Hex.Decode(iv_string);
|
||||
byte[] bs = GM.Sm4EncryptCBC(key, Encoding.UTF8.GetBytes(plainText), iv, GM.SM4_CBC_PKCS7PADDING);
|
||||
return Hex.ToHexString(bs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SM4解密(CBC)
|
||||
/// </summary>
|
||||
/// <param name="key_string"></param>
|
||||
/// <param name="iv_string"></param>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
public static string SM4DecryptCBC(string key_string, string iv_string, string cipherText)
|
||||
{
|
||||
byte[] key = Hex.Decode(key_string);
|
||||
byte[] iv = Hex.Decode(iv_string);
|
||||
byte[] bs = GM.Sm4DecryptCBC(key, Hex.Decode(cipherText), iv, GM.SM4_CBC_PKCS7PADDING);
|
||||
return Encoding.UTF8.GetString(bs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 补足 16 进制字符串的 0 字符,返回不带 0x 的16进制字符串
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="mode">1表示加密,0表示解密</param>
|
||||
/// <returns></returns>
|
||||
private static byte[] HandleSm4Padding(byte[] input, int mode)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] ret = (byte[])null;
|
||||
if (mode == 1)
|
||||
{
|
||||
int p = 16 - input.Length % 16;
|
||||
ret = new byte[input.Length + p];
|
||||
Array.Copy(input, 0, ret, 0, input.Length);
|
||||
for (int i = 0; i < p; i++)
|
||||
{
|
||||
ret[input.Length + i] = (byte)p;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int p = input[input.Length - 1];
|
||||
ret = new byte[input.Length - p];
|
||||
Array.Copy(input, 0, ret, 0, input.Length - p);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gitee接口帮助类
|
||||
/// </summary>
|
||||
public class GiteeHelper
|
||||
{
|
||||
private const string BaseUrl = "https://gitee.com/api/v5/repos/";
|
||||
private static readonly HttpClient Client = new();
|
||||
|
||||
/// <summary>
|
||||
/// 下载仓库 zip
|
||||
/// </summary>
|
||||
/// <remarks>https://gitee.com/api/v5/swagger#/getV5ReposOwnerRepoZipball</remarks>
|
||||
/// <returns></returns>
|
||||
public static async Task<Stream> DownloadRepoZip(string owner, string repo, string accessToken = null, string @ref = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(owner)) throw Oops.Bah($"参数 {nameof(owner)} 不能为空");
|
||||
if (string.IsNullOrWhiteSpace(repo)) throw Oops.Bah($"参数 {nameof(repo)} 不能为空");
|
||||
var query = BuilderQueryString(new
|
||||
{
|
||||
access_token = accessToken,
|
||||
@ref
|
||||
});
|
||||
return await Client.GetStreamAsync($"{BaseUrl}{owner}/{repo}/zipball?{query}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建Query参数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string BuilderQueryString([System.Diagnostics.CodeAnalysis.NotNull] object obj)
|
||||
{
|
||||
if (obj == null) return string.Empty;
|
||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||
foreach (var prop in obj.GetType().GetProperties())
|
||||
{
|
||||
var val = prop.GetValue(obj);
|
||||
if (val == null) continue;
|
||||
|
||||
// 以元组形式校验参数集
|
||||
var name = prop.Name.Trim('@');
|
||||
if (val is Tuple<object, string> { Item1: not null } tuple)
|
||||
{
|
||||
if (!tuple.Item2.Split(",").Any(x => x.Trim().Equals(tuple.Item1))) throw Oops.Oh($"参数 {name} 的值只能为:{tuple.Item2}");
|
||||
query[name] = tuple.Item1.ToString();
|
||||
continue;
|
||||
}
|
||||
query[name] = val.ToString();
|
||||
}
|
||||
return query.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using MiniExcelLibs;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class MiniExcelUtil
|
||||
{
|
||||
private const string SheetName = "ImportTemplate";
|
||||
private const string DirectoryName = "export";
|
||||
|
||||
/// <summary>
|
||||
/// 导出模板Excel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<IActionResult> ExportExcelTemplate<T>(string fileName = null) where T : class, new()
|
||||
{
|
||||
var values = Array.Empty<T>();
|
||||
// 在内存中当开辟空间
|
||||
var memoryStream = new MemoryStream();
|
||||
// 将数据写到内存当中
|
||||
await memoryStream.SaveAsAsync(values, sheetName: SheetName);
|
||||
// 从0的位置开始写入
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? typeof(T).Name : fileName)}.xlsx"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取导入数据Excel
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IEnumerable<T>> GetImportExcelData<T>([Required] IFormFile file) where T : class, new()
|
||||
{
|
||||
using MemoryStream stream = new MemoryStream();
|
||||
await file.CopyToAsync(stream);
|
||||
var res = await stream.QueryAsync<T>(sheetName: SheetName);
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取导出数据excel地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetExportDataExcelUrl<T>(IEnumerable<T> exportData) where T : class, new()
|
||||
{
|
||||
var fileName = string.Format("{0}.xlsx", YitIdHelper.NextId());
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(App.WebHostEnvironment.WebRootPath, DirectoryName);
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
var filePath = Path.Combine(path, fileName);
|
||||
await MiniExcel.SaveAsAsync(filePath, exportData, overwriteFile: true);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
throw Oops.Oh("出现错误:" + error);
|
||||
}
|
||||
var host = CommonUtil.GetLocalhost();
|
||||
return $"{host}/{DirectoryName}/{fileName}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义序列化提供器 Newtonsoft.Json 实现
|
||||
/// </summary>
|
||||
public class NewtonsoftJsonSerializerProvider : IJsonSerializerProvider, ISingleton
|
||||
{
|
||||
/// <summary>
|
||||
/// 序列化对象
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="jsonSerializerOptions"></param>
|
||||
/// <returns></returns>
|
||||
public string Serialize(object value, object jsonSerializerOptions = null)
|
||||
{
|
||||
return JsonConvert.SerializeObject(value, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化字符串
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="json"></param>
|
||||
/// <param name="jsonSerializerOptions"></param>
|
||||
/// <returns></returns>
|
||||
public T Deserialize<T>(string json, object jsonSerializerOptions = null)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化字符串
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <param name="returnType"></param>
|
||||
/// <param name="jsonSerializerOptions"></param>
|
||||
/// <returns></returns>
|
||||
public object Deserialize(string json, Type returnType, object jsonSerializerOptions = null)
|
||||
{
|
||||
return JsonConvert.DeserializeObject(json, returnType, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回读取全局配置的 JSON 选项
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object GetSerializerOptions()
|
||||
{
|
||||
return App.GetOptions<MvcNewtonsoftJsonOptions>()?.SerializerSettings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 树形节点
|
||||
/// </summary>
|
||||
public class TreeNode
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Pid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<TreeNode> Children { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据路径数组生成树结构
|
||||
/// </summary>
|
||||
public class PathTreeBuilder
|
||||
{
|
||||
private int _nextId = 1;
|
||||
|
||||
public TreeNode BuildTree(List<string> paths)
|
||||
{
|
||||
var root = new TreeNode { Id = 1, Pid = 0, Name = "文件目录" }; // 根节点
|
||||
var dict = new Dictionary<string, TreeNode>();
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
TreeNode currentNode = root;
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var key = currentNode.Id + "_" + part; // 生成唯一键
|
||||
if (!dict.ContainsKey(key))
|
||||
{
|
||||
var newNode = new TreeNode
|
||||
{
|
||||
Id = _nextId++,
|
||||
Pid = currentNode.Id,
|
||||
Name = part
|
||||
};
|
||||
currentNode.Children.Add(newNode);
|
||||
dict[key] = newNode;
|
||||
}
|
||||
currentNode = dict[key]; // 更新当前节点
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 反射工具类
|
||||
/// </summary>
|
||||
public static class ReflectionUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取字段特性
|
||||
/// </summary>
|
||||
/// <param name="field"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static T GetDescriptionValue<T>(this FieldInfo field) where T : Attribute
|
||||
{
|
||||
// 获取字段的指定特性,不包含继承中的特性
|
||||
object[] customAttributes = field.GetCustomAttributes(typeof(T), false);
|
||||
|
||||
// 如果没有数据返回null
|
||||
return customAttributes.Length > 0 ? (T)customAttributes[0] : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 正则校验
|
||||
/// </summary>
|
||||
public static class RegularValidate
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证密码规则
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ValidatePassword(string password)
|
||||
{
|
||||
var regex = new Regex(@"
|
||||
(?=.*[0-9]) #必须包含数字
|
||||
(?=.*[a-z]) #必须包含小写
|
||||
(?=.*[A-Z]) #必须包含大写
|
||||
(?=([\x21-\x7e]+)[^a-zA-Z0-9]) #必须包含特殊符号
|
||||
.{8,30} #至少8个字符,最多30个字符
|
||||
", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
//如果要求必须包含小写、大写字母,则上面的(?=.*[a-zA-Z]) 要改为:
|
||||
/*
|
||||
* (?=.*[a-z])
|
||||
* (?=.*[A-Z])
|
||||
*/
|
||||
return regex.IsMatch(password);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Renci.SshNet;
|
||||
|
||||
namespace Admin.NET.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// SSH/Sftp 工具类
|
||||
/// </summary>
|
||||
public class SSHHelper : IDisposable
|
||||
{
|
||||
private readonly SftpClient _sftp;
|
||||
|
||||
public SSHHelper(string host, int port, string user, string password)
|
||||
{
|
||||
_sftp = new SftpClient(host, port, user, password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接
|
||||
/// </summary>
|
||||
private void Connect()
|
||||
{
|
||||
if (!_sftp.IsConnected)
|
||||
_sftp.Connect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否存在同名文件
|
||||
/// </summary>
|
||||
/// <param name="ftpFileName"></param>
|
||||
/// <returns></returns>
|
||||
public bool Exists(string ftpFileName)
|
||||
{
|
||||
Connect();
|
||||
|
||||
return _sftp.Exists(ftpFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <param name="ftpFileName"></param>
|
||||
public void DeleteFile(string ftpFileName)
|
||||
{
|
||||
Connect();
|
||||
|
||||
_sftp.DeleteFile(ftpFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载到指定目录
|
||||
/// </summary>
|
||||
/// <param name="ftpFileName"></param>
|
||||
/// <param name="localFileName"></param>
|
||||
public void DownloadFile(string ftpFileName, string localFileName)
|
||||
{
|
||||
Connect();
|
||||
|
||||
using (Stream fileStream = File.OpenWrite(localFileName))
|
||||
{
|
||||
_sftp.DownloadFile(ftpFileName, fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取字节
|
||||
/// </summary>
|
||||
/// <param name="ftpFileName"></param>
|
||||
/// <returns></returns>
|
||||
public byte[] ReadAllBytes(string ftpFileName)
|
||||
{
|
||||
Connect();
|
||||
|
||||
return _sftp.ReadAllBytes(ftpFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取流
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public Stream OpenRead(string path)
|
||||
{
|
||||
return _sftp.Open(path, FileMode.Open, FileAccess.Read);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 继续下载
|
||||
/// </summary>
|
||||
/// <param name="ftpFileName"></param>
|
||||
/// <param name="localFileName"></param>
|
||||
public void DownloadFileWithResume(string ftpFileName, string localFileName)
|
||||
{
|
||||
DownloadFile(ftpFileName, localFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重命名
|
||||
/// </summary>
|
||||
/// <param name="oldPath"></param>
|
||||
/// <param name="newPath"></param>
|
||||
public void RenameFile(string oldPath, string newPath)
|
||||
{
|
||||
_sftp.RenameFile(oldPath, newPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定目录下文件
|
||||
/// </summary>
|
||||
/// <param name="folder"></param>
|
||||
/// <param name="filters"></param>
|
||||
/// <returns></returns>
|
||||
public List<string> GetFileList(string folder, IEnumerable<string> filters)
|
||||
{
|
||||
Connect();
|
||||
|
||||
var files = new List<string>();
|
||||
var sftpFiles = _sftp.ListDirectory(folder);
|
||||
foreach (var file in sftpFiles)
|
||||
{
|
||||
if (file.IsRegularFile && filters.Any(f => file.Name.EndsWith(f)))
|
||||
files.Add(file.Name);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传指定目录文件
|
||||
/// </summary>
|
||||
/// <param name="localFileName"></param>
|
||||
/// <param name="ftpFileName"></param>
|
||||
public void UploadFile(string localFileName, string ftpFileName)
|
||||
{
|
||||
Connect();
|
||||
|
||||
var dir = Path.GetDirectoryName(ftpFileName);
|
||||
CreateDir(_sftp, dir);
|
||||
using (var fileStream = new FileStream(localFileName, FileMode.Open))
|
||||
{
|
||||
_sftp.UploadFile(fileStream, ftpFileName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传字节
|
||||
/// </summary>
|
||||
/// <param name="bs"></param>
|
||||
/// <param name="ftpFileName"></param>
|
||||
public void UploadFile(byte[] bs, string ftpFileName)
|
||||
{
|
||||
Connect();
|
||||
|
||||
var dir = Path.GetDirectoryName(ftpFileName);
|
||||
CreateDir(_sftp, dir);
|
||||
_sftp.WriteAllBytes(ftpFileName, bs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传流
|
||||
/// </summary>
|
||||
/// <param name="fileStream"></param>
|
||||
/// <param name="ftpFileName"></param>
|
||||
public void UploadFile(Stream fileStream, string ftpFileName)
|
||||
{
|
||||
Connect();
|
||||
|
||||
var dir = Path.GetDirectoryName(ftpFileName);
|
||||
CreateDir(_sftp, dir);
|
||||
_sftp.UploadFile(fileStream, ftpFileName);
|
||||
fileStream.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建目录
|
||||
/// </summary>
|
||||
/// <param name="sftp"></param>
|
||||
/// <param name="dir"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
private void CreateDir(SftpClient sftp, string dir)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dir);
|
||||
|
||||
if (sftp.Exists(dir)) return;
|
||||
|
||||
var index = dir.LastIndexOfAny(new char[] { '/', '\\' });
|
||||
if (index > 0)
|
||||
{
|
||||
var p = dir[..index];
|
||||
if (!sftp.Exists(p))
|
||||
CreateDir(sftp, p);
|
||||
sftp.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放对象
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_sftp == null) return;
|
||||
|
||||
if (_sftp.IsConnected)
|
||||
_sftp.Disconnect();
|
||||
_sftp.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// 安全的基本数学运算方法类
|
||||
/// </summary>
|
||||
public static class SafeMath
|
||||
{
|
||||
/// <summary>
|
||||
/// 安全加法
|
||||
/// </summary>
|
||||
/// <param name="left">左操作数</param>
|
||||
/// <param name="right">右操作数</param>
|
||||
/// <param name="precision">保留小数位数</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <param name="throwOnError">是否抛出异常</param>
|
||||
/// <returns></returns>
|
||||
public static T Add<T>(object left, object right, int precision = 2, T defaultValue = default, bool throwOnError = true) where T : struct, IComparable, IConvertible, IFormattable
|
||||
{
|
||||
return PerformOperation(left, right, (a, b) => a + b, precision, defaultValue, throwOnError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全减法
|
||||
/// </summary>
|
||||
/// <param name="left">左操作数</param>
|
||||
/// <param name="right">右操作数</param>
|
||||
/// <param name="precision">保留小数位数</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <param name="throwOnError">是否抛出异常</param>
|
||||
public static T Sub<T>(object left, object right, int precision = 2, T defaultValue = default, bool throwOnError = true) where T : struct, IComparable, IConvertible, IFormattable
|
||||
{
|
||||
return PerformOperation(left, right, (a, b) => a - b, precision, defaultValue, throwOnError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全乘法
|
||||
/// </summary>
|
||||
/// <param name="left">左操作数</param>
|
||||
/// <param name="right">右操作数</param>
|
||||
/// <param name="precision">保留小数位数</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <param name="throwOnError">是否抛出异常</param>
|
||||
public static T Mult<T>(object left, object right, int precision = 2, T defaultValue = default, bool throwOnError = true) where T : struct, IComparable, IConvertible, IFormattable
|
||||
{
|
||||
return PerformOperation(left, right, (a, b) => a * b, precision, defaultValue, throwOnError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全除法
|
||||
/// </summary>
|
||||
/// <param name="left">左操作数</param>
|
||||
/// <param name="right">右操作数</param>
|
||||
/// <param name="precision">保留小数位数</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <param name="throwOnDivideByZero">是否抛出除以零异常</param>
|
||||
public static T Div<T>(object left, object right, int precision = 2, T defaultValue = default, bool throwOnDivideByZero = true) where T : struct, IComparable, IConvertible, IFormattable
|
||||
{
|
||||
return PerformOperation(left, right, (a, b) =>
|
||||
{
|
||||
if (b != 0) return a / b;
|
||||
if (throwOnDivideByZero) throw new DivideByZeroException("除数不能为0");
|
||||
return SafeConvert<decimal>(defaultValue);
|
||||
}, precision, defaultValue, throwOnDivideByZero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全类型转换
|
||||
/// </summary>
|
||||
/// <param name="value">数据源</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
public static T SafeConvert<T>(object value, T defaultValue = default) where T : struct, IComparable, IConvertible, IFormattable
|
||||
{
|
||||
if (value == null) return defaultValue;
|
||||
try
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行数学运算
|
||||
/// </summary>
|
||||
private static T PerformOperation<T>(object left, object right, Func<decimal, decimal, decimal> operation, int precision, T defaultValue, bool throwOnError) where T : struct, IComparable, IConvertible, IFormattable
|
||||
{
|
||||
try
|
||||
{
|
||||
decimal leftValue = ConvertToDecimal(left);
|
||||
decimal rightValue = ConvertToDecimal(right);
|
||||
|
||||
decimal result = operation(leftValue, rightValue);
|
||||
return SafeConvert(Math.Round(result, precision, MidpointRounding.AwayFromZero), defaultValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (throwOnError) throw;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将输入值转换为 decimal
|
||||
/// </summary>
|
||||
public static decimal ConvertToDecimal(object value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
null => 0m,
|
||||
int intValue => intValue,
|
||||
float floatValue => (decimal)floatValue,
|
||||
double doubleValue => (decimal)doubleValue,
|
||||
decimal decimalValue => decimalValue,
|
||||
long longValue => longValue,
|
||||
short shortValue => shortValue,
|
||||
byte byteValue => byteValue,
|
||||
string stringValue when decimal.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal parsedValue) => parsedValue, // 尝试解析字符串
|
||||
_ => throw new InvalidCastException($"不支持的类型: {value.GetType().Name}")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 租户头部参数过滤器
|
||||
/// </summary>
|
||||
public class TenantHeaderOperationFilter : IOperationFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用租户头部参数过滤器
|
||||
/// </summary>
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
operation.Parameters ??= [];
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = ClaimConst.TenantId,
|
||||
In = ParameterLocation.Header,
|
||||
Schema = new OpenApiSchema { Type = JsonSchemaType.String },
|
||||
Required = false,
|
||||
AllowEmptyValue = true,
|
||||
Description = "租户ID(留空表示默认租户)"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 3DES文件加解密
|
||||
/// </summary>
|
||||
public static class TripleDES
|
||||
{
|
||||
/// <summary>
|
||||
/// 加密文件
|
||||
/// </summary>
|
||||
/// <param name="inputFile">待加密文件路径</param>
|
||||
/// <param name="outputFile">加密后的文件路径</param>
|
||||
/// <param name="password">密码 (24位长度)</param>
|
||||
[Obsolete]
|
||||
public static void EncryptFile(string inputFile, string outputFile, string password)
|
||||
{
|
||||
using var ties = new TripleDESCryptoServiceProvider();
|
||||
ties.Mode = CipherMode.ECB;
|
||||
ties.Padding = PaddingMode.PKCS7;
|
||||
ties.Key = Encoding.UTF8.GetBytes(password);
|
||||
using var inputFileStream = new FileStream(inputFile, FileMode.Open);
|
||||
using var encryptedFileStream = new FileStream(outputFile, FileMode.Create);
|
||||
using var cryptoStream = new CryptoStream(encryptedFileStream, ties.CreateEncryptor(), CryptoStreamMode.Write);
|
||||
inputFileStream.CopyTo(cryptoStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加密文件
|
||||
/// </summary>
|
||||
/// <param name="inputFile">加密的文件路径</param>
|
||||
/// <param name="outputFile">解密后的文件路径</param>
|
||||
/// <param name="password">密码 (24位长度)</param>
|
||||
[Obsolete]
|
||||
public static void DecryptFile(string inputFile, string outputFile, string password)
|
||||
{
|
||||
using var ties = new TripleDESCryptoServiceProvider();
|
||||
ties.Mode = CipherMode.ECB;
|
||||
ties.Padding = PaddingMode.PKCS7;
|
||||
ties.Key = Encoding.UTF8.GetBytes(password);
|
||||
using var encryptedFileStream = new FileStream(inputFile, FileMode.Open);
|
||||
using var decryptedFileStream = new FileStream(outputFile, FileMode.Create);
|
||||
using var cryptoStream = new CryptoStream(encryptedFileStream, ties.CreateDecryptor(), CryptoStreamMode.Read);
|
||||
cryptoStream.CopyTo(decryptedFileStream);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件类型
|
||||
/// </summary>
|
||||
public static class VerifyFileExtensionName
|
||||
{
|
||||
private static readonly IDictionary<string, string> DicsExt = new Dictionary<string, string>();
|
||||
private static readonly IDictionary<string, HashSet<int>> ExtDics = new Dictionary<string, HashSet<int>>();
|
||||
|
||||
static VerifyFileExtensionName()
|
||||
{
|
||||
DicsExt.Add("FFD8FFE0", ".jpg");
|
||||
DicsExt.Add("FFD8FFE1", ".jpg");
|
||||
DicsExt.Add("89504E47", ".png");
|
||||
DicsExt.Add("47494638", ".gif");
|
||||
DicsExt.Add("49492A00", ".tif");
|
||||
DicsExt.Add("424D", ".bmp");
|
||||
|
||||
// PS和CAD
|
||||
DicsExt.Add("38425053", ".psd");
|
||||
DicsExt.Add("41433130", ".dwg"); // CAD
|
||||
DicsExt.Add("252150532D41646F6265", ".ps");
|
||||
|
||||
// 办公文档类
|
||||
DicsExt.Add("D0CF11E0", ".ppt,.doc,.xls"); // ppt、doc、xls
|
||||
DicsExt.Add("504B0304", ".pptx,.docx,.xlsx"); // pptx、docx、xlsx
|
||||
|
||||
/* 注意由于文本文档录入内容过多,则读取文件头时较为多变-START */
|
||||
DicsExt.Add("0D0A0D0A", ".txt"); // txt
|
||||
DicsExt.Add("0D0A2D2D", ".txt"); // txt
|
||||
DicsExt.Add("0D0AB4B4", ".txt"); // txt
|
||||
DicsExt.Add("B4B4BDA8", ".txt"); // 文件头部为汉字
|
||||
DicsExt.Add("73646673", ".txt"); // txt,文件头部为英文字母
|
||||
DicsExt.Add("32323232", ".txt"); // txt,文件头部内容为数字
|
||||
DicsExt.Add("0D0A09B4", ".txt"); // txt,文件头部内容为数字
|
||||
DicsExt.Add("3132330D", ".txt"); // txt,文件头部内容为数字
|
||||
/* 注意由于文本文档录入内容过多,则读取文件头时较为多变-END */
|
||||
|
||||
DicsExt.Add("7B5C727466", ".rtf"); // 日记本
|
||||
|
||||
DicsExt.Add("255044462D312E", ".pdf");
|
||||
|
||||
// 视频或音频类
|
||||
DicsExt.Add("3026B275", ".wma");
|
||||
DicsExt.Add("57415645", ".wav");
|
||||
DicsExt.Add("41564920", ".avi");
|
||||
DicsExt.Add("4D546864", ".mid");
|
||||
DicsExt.Add("2E524D46", ".rm");
|
||||
DicsExt.Add("000001BA", ".mpg");
|
||||
DicsExt.Add("000001B3", ".mpg");
|
||||
DicsExt.Add("6D6F6F76", ".mov");
|
||||
DicsExt.Add("3026B2758E66CF11", ".asf");
|
||||
|
||||
// 压缩包
|
||||
DicsExt.Add("52617221", ".rar");
|
||||
DicsExt.Add("504B03040A000000", ".zip");
|
||||
DicsExt.Add("504B030414000000", ".zip");
|
||||
DicsExt.Add("1F8B08", ".gz");
|
||||
|
||||
// 程序文件
|
||||
DicsExt.Add("3C3F786D6C", ".xml");
|
||||
DicsExt.Add("68746D6C3E", ".html");
|
||||
DicsExt.Add("04034b50", ".apk");
|
||||
//dics_ext.Add("7061636B", ".java");
|
||||
//dics_ext.Add("3C254020", ".jsp");
|
||||
//dics_ext.Add("4D5A9000", ".exe");
|
||||
|
||||
DicsExt.Add("44656C69766572792D646174653A", ".eml"); // 邮件
|
||||
DicsExt.Add("5374616E64617264204A", ".mdb"); // Access数据库文件
|
||||
|
||||
DicsExt.Add("46726F6D", ".mht");
|
||||
DicsExt.Add("4D494D45", ".mhtml");
|
||||
|
||||
foreach (var dics in DicsExt)
|
||||
{
|
||||
foreach (var ext in dics.Value.Split(","))
|
||||
{
|
||||
if (!ExtDics.ContainsKey(ext))
|
||||
ExtDics.Add(ext, new HashSet<int> { dics.Key.Length / 2 });
|
||||
else
|
||||
ExtDics[ext].Add(dics.Key.Length / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件格式和文件内容格式是否一致
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsSameType(Stream stream, string suffix = ".jpg")
|
||||
{
|
||||
if (stream == null)
|
||||
return false;
|
||||
|
||||
suffix = suffix.ToLower();
|
||||
if (!ExtDics.TryGetValue(suffix, out HashSet<int> dic)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var len in dic)
|
||||
{
|
||||
byte[] b = new byte[len];
|
||||
stream.ReadExactly(b);
|
||||
// string fileType = System.Text.Encoding.UTF8.GetString(b);
|
||||
string fileKey = GetFileHeader(b);
|
||||
if (DicsExt.ContainsKey(fileKey))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件转换成的字节数组获取文件头信息
|
||||
* @param 文件路径
|
||||
* @return 文件头信息
|
||||
*/
|
||||
|
||||
private static string GetFileHeader(byte[] b)
|
||||
{
|
||||
string value = BytesToHexString(b);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将要读取文件头信息的文件的byte数组转换成string类型表示
|
||||
* 下面这段代码就是用来对文件类型作验证的方法,
|
||||
* 将字节数组的前四位转换成16进制字符串,并且转换的时候,要先和0xFF做一次与运算。
|
||||
* 这是因为,整个文件流的字节数组中,有很多是负数,进行了与运算后,可以将前面的符号位都去掉,
|
||||
* 这样转换成的16进制字符串最多保留两位,如果是正数又小于10,那么转换后只有一位,
|
||||
* 需要在前面补0,这样做的目的是方便比较,取完前四位这个循环就可以终止了
|
||||
* @param src要读取文件头信息的文件的byte数组
|
||||
* @return 文件头信息
|
||||
*/
|
||||
|
||||
private static string BytesToHexString(byte[] src)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
if (src == null || src.Length <= 0)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
{
|
||||
// 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写
|
||||
string hVal = Convert.ToString(src[i] & 0xFF, 16).ToUpper();
|
||||
if (hVal.Length < 2) builder.Append(0);
|
||||
builder.Append(hVal);
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Excel文件ActionResult
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class XlsxFileResult<T> : XlsxFileResultBase where T : class, new()
|
||||
{
|
||||
public string FileDownloadName { get; }
|
||||
public ICollection<T> Data { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="fileDownloadName"></param>
|
||||
public XlsxFileResult(ICollection<T> data, string fileDownloadName = null)
|
||||
{
|
||||
FileDownloadName = fileDownloadName;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public override async Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
var exporter = new ExcelExporter();
|
||||
var bytes = await exporter.ExportAsByteArray(Data);
|
||||
var fs = new MemoryStream(bytes);
|
||||
await DownloadExcelFileAsync(context, fs, FileDownloadName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class XlsxFileResult : XlsxFileResultBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="fileDownloadName"></param>
|
||||
public XlsxFileResult(Stream stream, string fileDownloadName = null)
|
||||
{
|
||||
Stream = stream;
|
||||
FileDownloadName = fileDownloadName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="fileDownloadName"></param>
|
||||
|
||||
public XlsxFileResult(byte[] bytes, string fileDownloadName = null)
|
||||
{
|
||||
Stream = new MemoryStream(bytes);
|
||||
FileDownloadName = fileDownloadName;
|
||||
}
|
||||
|
||||
public Stream Stream { get; protected set; }
|
||||
public string FileDownloadName { get; protected set; }
|
||||
|
||||
public override async Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
await DownloadExcelFileAsync(context, Stream, FileDownloadName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基类
|
||||
/// </summary>
|
||||
public class XlsxFileResultBase : ActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载Excel文件
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="downloadFileName"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task DownloadExcelFileAsync(ActionContext context, Stream stream, string downloadFileName)
|
||||
{
|
||||
var response = context.HttpContext.Response;
|
||||
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
|
||||
downloadFileName ??= Guid.NewGuid().ToString("N") + ".xlsx";
|
||||
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(downloadFileName))) downloadFileName += ".xlsx";
|
||||
|
||||
context.HttpContext.Response.Headers.Append("Content-Disposition", new[] { "attachment; filename=" + HttpUtility.UrlEncode(downloadFileName) });
|
||||
await stream.CopyToAsync(context.HttpContext.Response.Body);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user