feat: initialize Evergreen Health backend

This commit is contained in:
fuenmao
2026-09-21 14:08:22 +08:00
commit 8553df5dd2
493 changed files with 75649 additions and 0 deletions
+193
View File
@@ -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; }
}
+75
View File
@@ -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; }
}
+20
View File
@@ -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; }
}
+27
View File
@@ -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; }
}
+57
View File
@@ -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; }
}
+19
View File
@@ -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);
}
}
+290
View File
@@ -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;
}
}
+503
View File
@@ -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 "未知";
}
}
+550
View File
@@ -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();
}
}
+120
View File
@@ -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);
}
}
}
}
+416
View File
@@ -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);
}
}
+147
View File
@@ -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 = "请从列表中选择一个有效的选项";
}
}
}
+125
View File
@@ -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();
}
}
+471
View File
@@ -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;
}
}
+151
View File
@@ -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;
}
}
+59
View File
@@ -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();
}
}
+70
View File
@@ -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;
}
}
+57
View File
@@ -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;
}
}
+28
View File
@@ -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;
}
}
+36
View File
@@ -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);
}
}
+212
View File
@@ -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();
}
}
}
+133
View File
@@ -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(留空表示默认租户)"
});
}
}
+53
View File
@@ -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();
}
}
+99
View File
@@ -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);
}
}