From a9be87598b11cb291082fb11274cee7c244ec00a Mon Sep 17 00:00:00 2001 From: liuzhen <2506966308@qq.com> Date: Mon, 14 Jul 2025 14:53:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=8E=A5=E5=8F=A3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/CommonWorker/CommonWorker.csproj | 10 +- code/DG.Core/AutoIoc/LifeCycle.cs | 15 + code/DG.Core/DG.Core.csproj | 16 + code/DG.Core/Dependency/IScopedDependency.cs | 12 + .../Dependency/ISingletonDependency.cs | 12 + .../Dependency/ITransientDependency.cs | 12 + code/DG.Core/Exceptions/ApiException.cs | 23 + code/DG.Core/Exceptions/DGException.cs | 23 + code/DG.Core/Extensions/HttpExtensions.cs | 20 + .../Extensions/JsonOptionsExtensions.cs | 40 + .../Extensions/LinqMethodExtensions.cs | 28 + .../Extensions/PredicateExtensionses.cs | 106 +++ .../DG.Core/Extensions/SystemKeyExtensions.cs | 145 +++ code/DG.Core/HttpClient.cs | 759 +++++++++++++++ code/DG.Core/IHttpClient.cs | 39 + .../DG.Core/IMvcBuilderApiResultExtensions.cs | 51 + code/DG.Core/Mapper/IMapper.cs | 15 + code/DG.Core/Mapper/Mapper.cs | 46 + code/DG.Core/Mapper/MapperExtendsions.cs | 49 + code/DG.Core/Mapper/MapperManager.cs | 15 + code/DG.Core/Mapper/NotMapAttribute.cs | 15 + .../Result/ApiExceptionFilterAttribute.cs | 49 + code/DG.Core/Result/ApiResourceFilter.cs | 37 + code/DG.Core/Result/ApiResult.cs | 101 ++ .../Result/ApiResultFilterAttribute.cs | 69 ++ .../Result/ApiResultFilterForbidAttribute.cs | 15 + code/DG.Core/Result/ApiResultGeneric.cs | 35 + code/DG.Core/Result/IApiResult.cs | 23 + code/DG.Core/Result/IApiResultGeneric.cs | 17 + code/DG.Core/Result/PageResult.cs | 45 + code/DG.Core/Result/SearchPageBase.cs | 36 + code/DG.Core/Result/SelectItem.cs | 21 + .../Security/ApiSecurityAsyncFilter.cs | 133 +++ code/DG.Core/Security/ApiSecurityAttribute.cs | 20 + code/DG.Core/Security/ClientKey.cs | 35 + code/DG.Core/ServiceCollectionExtensions.cs | 65 ++ .../ApiSignatureAsyncFilterAttribute.cs | 138 +++ .../ApiSignatureFilterForbidAttribute.cs | 19 + .../ApiTimeSecurityAsyncFilter.cs | 171 ++++ .../TimeSecurity/ApiTimeSecurityAttribute.cs | 20 + .../Validation/DecimalValidationAttribute.cs | 58 ++ .../Validation/IntValidationAttribute.cs | 58 ++ .../Validation/LongValidationAttribute.cs | 58 ++ .../DG.EntityFramework.csproj | 15 + code/DG.EntityFramework/DbContextProvider.cs | 23 + code/DG.EntityFramework/EFCoreOptions.cs | 38 + .../EFCoreOptionsExtension.cs | 28 + code/DG.EntityFramework/EFDbContext.cs | 26 + code/DG.EntityFramework/EFLoggerProvider.cs | 43 + code/DG.EntityFramework/IDbContextProvider.cs | 18 + .../Repository/BaseRepository.cs | 394 ++++++++ .../Repository/IBaseRepository.cs | 64 ++ .../Repository/IRepositoryBase.cs | 127 +++ .../Repository/RepositoryBase.cs | 298 ++++++ .../ServiceCollectionExtensions.cs | 39 + .../UnitOfWork/IUnitOfWorkCompleteHandle.cs | 36 + .../UnitOfWork/IUnitOfWorkManager.cs | 33 + .../UnitOfWork/UnitOfWorkCompleteHandle.cs | 45 + .../UnitOfWorkCompleteScopeHandle.cs | 45 + .../UnitOfWork/UnitOfWorkManager.cs | 42 + code/DG.EntityFramework/UnitOfWorkOptions.cs | 49 + code/DG.Redis/DG.Redis.csproj | 21 + code/DG.Redis/Manager/IRedisManager.cs | 298 ++++++ code/DG.Redis/Manager/RedisManager.cs | 375 ++++++++ code/DG.Redis/RedisClient.cs | 26 + code/DG.Redis/RedisOptions.cs | 30 + code/DG.Redis/ServiceCollectionExtensions.cs | 31 + code/DG.Tool/AesUtil.cs | 79 ++ .../Attributes/ExportColumnNameAttribute.cs | 22 + .../Attributes/NotExportColumnAttribute.cs | 16 + code/DG.Tool/BlowFish.cs | 21 + code/DG.Tool/Blowfish/BlowfishCBC.cs | 379 ++++++++ code/DG.Tool/Blowfish/BlowfishCFB.cs | 405 ++++++++ code/DG.Tool/Blowfish/BlowfishECB.cs | 717 ++++++++++++++ code/DG.Tool/Blowfish/BlowfishSimple.cs | 165 ++++ code/DG.Tool/DG.Tool.csproj | 16 + code/DG.Tool/DateTimeTool.cs | 164 ++++ code/DG.Tool/EnumHelper.cs | 55 ++ code/DG.Tool/ExcelHelper.cs | 897 ++++++++++++++++++ code/DG.Tool/HttpHelper.cs | 101 ++ code/DG.Tool/JsonHelper.cs | 46 + code/DG.Tool/PhoneHelper.cs | 434 +++++++++ code/DG.Tool/ResUtil.cs | 259 +++++ code/DG.Tool/SecurityHelper.cs | 143 +++ code/DG.Tool/SignHelper.cs | 61 ++ code/DG.Tool/TimeHelper.cs | 365 +++++++ code/DG.Tool/Utility.cs | 254 +++++ .../EmployeeDepartmentDetailServices.csproj | 6 +- .../ResourceFlowWorker.csproj | 7 +- code/ToDoWorker/ToDoWorker.csproj | 10 +- code/WeworkUserWorker/WeworkUserWorker.csproj | 9 +- code/Zxd.Core.Domain/Dto/Dg/Sms_RecordsDto.cs | 82 ++ code/Zxd.Core.Domain/Impl/ISmsRecordDomain.cs | 14 + code/Zxd.Core.Domain/SmsRecordDomain.cs | 58 ++ code/Zxd.Core.Domain/Zxd.Core.Domain.csproj | 15 +- code/Zxd.Core.Shared/Zxd.Core.Shared.csproj | 2 +- .../Controllers/ActivityController.cs | 3 +- .../Controllers/CustomerController.cs | 25 +- code/Zxd.Core.WebApi/Zxd.Core.WebApi.csproj | 5 + code/Zxd.Core.sln | 28 + code/Zxd.Crm.Domain/Zxd.Crm.Domain.csproj | 6 +- code/Zxd.Domain/Zxd.Domain.csproj | 7 +- code/Zxd.Entity/dg/Sms_Channel.cs | 15 + code/Zxd.Entity/dg/Sms_Records.cs | 79 ++ code/Zxd.EntityFramework/DgDbContext.cs | 45 + .../Zxd.EntityFramework.csproj | 2 +- lib/Crm.Core.Shared.dll | Bin 0 -> 7168 bytes lib/DG.Worker.dll | Bin 0 -> 12800 bytes 108 files changed, 9794 insertions(+), 41 deletions(-) create mode 100644 code/DG.Core/AutoIoc/LifeCycle.cs create mode 100644 code/DG.Core/DG.Core.csproj create mode 100644 code/DG.Core/Dependency/IScopedDependency.cs create mode 100644 code/DG.Core/Dependency/ISingletonDependency.cs create mode 100644 code/DG.Core/Dependency/ITransientDependency.cs create mode 100644 code/DG.Core/Exceptions/ApiException.cs create mode 100644 code/DG.Core/Exceptions/DGException.cs create mode 100644 code/DG.Core/Extensions/HttpExtensions.cs create mode 100644 code/DG.Core/Extensions/JsonOptionsExtensions.cs create mode 100644 code/DG.Core/Extensions/LinqMethodExtensions.cs create mode 100644 code/DG.Core/Extensions/PredicateExtensionses.cs create mode 100644 code/DG.Core/Extensions/SystemKeyExtensions.cs create mode 100644 code/DG.Core/HttpClient.cs create mode 100644 code/DG.Core/IHttpClient.cs create mode 100644 code/DG.Core/IMvcBuilderApiResultExtensions.cs create mode 100644 code/DG.Core/Mapper/IMapper.cs create mode 100644 code/DG.Core/Mapper/Mapper.cs create mode 100644 code/DG.Core/Mapper/MapperExtendsions.cs create mode 100644 code/DG.Core/Mapper/MapperManager.cs create mode 100644 code/DG.Core/Mapper/NotMapAttribute.cs create mode 100644 code/DG.Core/Result/ApiExceptionFilterAttribute.cs create mode 100644 code/DG.Core/Result/ApiResourceFilter.cs create mode 100644 code/DG.Core/Result/ApiResult.cs create mode 100644 code/DG.Core/Result/ApiResultFilterAttribute.cs create mode 100644 code/DG.Core/Result/ApiResultFilterForbidAttribute.cs create mode 100644 code/DG.Core/Result/ApiResultGeneric.cs create mode 100644 code/DG.Core/Result/IApiResult.cs create mode 100644 code/DG.Core/Result/IApiResultGeneric.cs create mode 100644 code/DG.Core/Result/PageResult.cs create mode 100644 code/DG.Core/Result/SearchPageBase.cs create mode 100644 code/DG.Core/Result/SelectItem.cs create mode 100644 code/DG.Core/Security/ApiSecurityAsyncFilter.cs create mode 100644 code/DG.Core/Security/ApiSecurityAttribute.cs create mode 100644 code/DG.Core/Security/ClientKey.cs create mode 100644 code/DG.Core/ServiceCollectionExtensions.cs create mode 100644 code/DG.Core/Signature/ApiSignatureAsyncFilterAttribute.cs create mode 100644 code/DG.Core/Signature/ApiSignatureFilterForbidAttribute.cs create mode 100644 code/DG.Core/TimeSecurity/ApiTimeSecurityAsyncFilter.cs create mode 100644 code/DG.Core/TimeSecurity/ApiTimeSecurityAttribute.cs create mode 100644 code/DG.Core/Validation/DecimalValidationAttribute.cs create mode 100644 code/DG.Core/Validation/IntValidationAttribute.cs create mode 100644 code/DG.Core/Validation/LongValidationAttribute.cs create mode 100644 code/DG.EntityFramework/DG.EntityFramework.csproj create mode 100644 code/DG.EntityFramework/DbContextProvider.cs create mode 100644 code/DG.EntityFramework/EFCoreOptions.cs create mode 100644 code/DG.EntityFramework/EFCoreOptionsExtension.cs create mode 100644 code/DG.EntityFramework/EFDbContext.cs create mode 100644 code/DG.EntityFramework/EFLoggerProvider.cs create mode 100644 code/DG.EntityFramework/IDbContextProvider.cs create mode 100644 code/DG.EntityFramework/Repository/BaseRepository.cs create mode 100644 code/DG.EntityFramework/Repository/IBaseRepository.cs create mode 100644 code/DG.EntityFramework/Repository/IRepositoryBase.cs create mode 100644 code/DG.EntityFramework/Repository/RepositoryBase.cs create mode 100644 code/DG.EntityFramework/ServiceCollectionExtensions.cs create mode 100644 code/DG.EntityFramework/UnitOfWork/IUnitOfWorkCompleteHandle.cs create mode 100644 code/DG.EntityFramework/UnitOfWork/IUnitOfWorkManager.cs create mode 100644 code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteHandle.cs create mode 100644 code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteScopeHandle.cs create mode 100644 code/DG.EntityFramework/UnitOfWork/UnitOfWorkManager.cs create mode 100644 code/DG.EntityFramework/UnitOfWorkOptions.cs create mode 100644 code/DG.Redis/DG.Redis.csproj create mode 100644 code/DG.Redis/Manager/IRedisManager.cs create mode 100644 code/DG.Redis/Manager/RedisManager.cs create mode 100644 code/DG.Redis/RedisClient.cs create mode 100644 code/DG.Redis/RedisOptions.cs create mode 100644 code/DG.Redis/ServiceCollectionExtensions.cs create mode 100644 code/DG.Tool/AesUtil.cs create mode 100644 code/DG.Tool/Attributes/ExportColumnNameAttribute.cs create mode 100644 code/DG.Tool/Attributes/NotExportColumnAttribute.cs create mode 100644 code/DG.Tool/BlowFish.cs create mode 100644 code/DG.Tool/Blowfish/BlowfishCBC.cs create mode 100644 code/DG.Tool/Blowfish/BlowfishCFB.cs create mode 100644 code/DG.Tool/Blowfish/BlowfishECB.cs create mode 100644 code/DG.Tool/Blowfish/BlowfishSimple.cs create mode 100644 code/DG.Tool/DG.Tool.csproj create mode 100644 code/DG.Tool/DateTimeTool.cs create mode 100644 code/DG.Tool/EnumHelper.cs create mode 100644 code/DG.Tool/ExcelHelper.cs create mode 100644 code/DG.Tool/HttpHelper.cs create mode 100644 code/DG.Tool/JsonHelper.cs create mode 100644 code/DG.Tool/PhoneHelper.cs create mode 100644 code/DG.Tool/ResUtil.cs create mode 100644 code/DG.Tool/SecurityHelper.cs create mode 100644 code/DG.Tool/SignHelper.cs create mode 100644 code/DG.Tool/TimeHelper.cs create mode 100644 code/DG.Tool/Utility.cs create mode 100644 code/Zxd.Core.Domain/Dto/Dg/Sms_RecordsDto.cs create mode 100644 code/Zxd.Core.Domain/Impl/ISmsRecordDomain.cs create mode 100644 code/Zxd.Core.Domain/SmsRecordDomain.cs create mode 100644 code/Zxd.Entity/dg/Sms_Channel.cs create mode 100644 code/Zxd.Entity/dg/Sms_Records.cs create mode 100644 code/Zxd.EntityFramework/DgDbContext.cs create mode 100644 lib/Crm.Core.Shared.dll create mode 100644 lib/DG.Worker.dll diff --git a/code/CommonWorker/CommonWorker.csproj b/code/CommonWorker/CommonWorker.csproj index 1d901bd..eb9d719 100644 --- a/code/CommonWorker/CommonWorker.csproj +++ b/code/CommonWorker/CommonWorker.csproj @@ -9,11 +9,6 @@ - - - - - @@ -29,6 +24,11 @@ + + + + + diff --git a/code/DG.Core/AutoIoc/LifeCycle.cs b/code/DG.Core/AutoIoc/LifeCycle.cs new file mode 100644 index 0000000..55176aa --- /dev/null +++ b/code/DG.Core/AutoIoc/LifeCycle.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public enum LifeCycle + { + Scoped = 0x1, + Singleton = 0x2, + Transient = 0x3, + } +} diff --git a/code/DG.Core/DG.Core.csproj b/code/DG.Core/DG.Core.csproj new file mode 100644 index 0000000..cf9bea8 --- /dev/null +++ b/code/DG.Core/DG.Core.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + True + 1.1.9 + + + + + + + + \ No newline at end of file diff --git a/code/DG.Core/Dependency/IScopedDependency.cs b/code/DG.Core/Dependency/IScopedDependency.cs new file mode 100644 index 0000000..33f3fff --- /dev/null +++ b/code/DG.Core/Dependency/IScopedDependency.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public interface IScopedDependency + { + } +} diff --git a/code/DG.Core/Dependency/ISingletonDependency.cs b/code/DG.Core/Dependency/ISingletonDependency.cs new file mode 100644 index 0000000..762d8b7 --- /dev/null +++ b/code/DG.Core/Dependency/ISingletonDependency.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public interface ISingletonDependency + { + } +} diff --git a/code/DG.Core/Dependency/ITransientDependency.cs b/code/DG.Core/Dependency/ITransientDependency.cs new file mode 100644 index 0000000..d76b34e --- /dev/null +++ b/code/DG.Core/Dependency/ITransientDependency.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public interface ITransientDependency + { + } +} diff --git a/code/DG.Core/Exceptions/ApiException.cs b/code/DG.Core/Exceptions/ApiException.cs new file mode 100644 index 0000000..5e5e370 --- /dev/null +++ b/code/DG.Core/Exceptions/ApiException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class ApiException : DGException + { + public ApiException(string? message) + : base(message) + { + + } + + public ApiException(string? message, int code) + : base(message) + { + Data.Add("code", code); + } + } +} diff --git a/code/DG.Core/Exceptions/DGException.cs b/code/DG.Core/Exceptions/DGException.cs new file mode 100644 index 0000000..3015f72 --- /dev/null +++ b/code/DG.Core/Exceptions/DGException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class DGException : Exception + { + public DGException(string? message) + : base(message) + { + + } + + public DGException(string? message, int code) + : base(message) + { + Data.Add("code", code); + } + } +} diff --git a/code/DG.Core/Extensions/HttpExtensions.cs b/code/DG.Core/Extensions/HttpExtensions.cs new file mode 100644 index 0000000..706209b --- /dev/null +++ b/code/DG.Core/Extensions/HttpExtensions.cs @@ -0,0 +1,20 @@ + +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public static class HttpExtensions + { + public static string GetCorrelationId(this HttpContext httpContext) + { + httpContext.Request.Headers.TryGetValue("Cko-Correlation-Id", out StringValues correlationId); + return correlationId.FirstOrDefault() ?? httpContext.TraceIdentifier; + } + } +} diff --git a/code/DG.Core/Extensions/JsonOptionsExtensions.cs b/code/DG.Core/Extensions/JsonOptionsExtensions.cs new file mode 100644 index 0000000..19c66b5 --- /dev/null +++ b/code/DG.Core/Extensions/JsonOptionsExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class JsonOptionsExtensions : JsonConverter + { + private readonly string Format; + public JsonOptionsExtensions(string format = "yyyy-MM-dd HH:mm:ss") + { + Format = format; + } + public override void Write(Utf8JsonWriter writer, DateTime date, JsonSerializerOptions options) + { + writer.WriteStringValue(date.ToString(Format)); + } + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // 获取时间类型的字符串 + var dt = reader.GetString(); + if (!string.IsNullOrEmpty(dt)) + { + //将日期与时间之间的"T"替换为一个空格,将结尾的"Z"去掉,否则会报错 + dt = dt.Replace("T", " ").Replace("Z", ""); + //取到秒,毫秒内容也要去掉,经过测试,不去掉会报错 + if (dt.Length > 19) + { + dt = dt.Substring(0, 19); + } + return DateTime.ParseExact(dt, Format, null); + } + return DateTime.Now; + } + } +} diff --git a/code/DG.Core/Extensions/LinqMethodExtensions.cs b/code/DG.Core/Extensions/LinqMethodExtensions.cs new file mode 100644 index 0000000..5951569 --- /dev/null +++ b/code/DG.Core/Extensions/LinqMethodExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public static class LinqMethodExtensions + { + /// + /// 使用自定linq扩展执行排序,查询,分页功能 item1: 未分页结果,item2:分页后的结果 + /// + /// + /// + /// + /// + public static IQueryable UseCoditionFind(this IQueryable source, bool condition, Action> action) + { + if (condition) + { + action(source); + } + + return source; + } + } +} diff --git a/code/DG.Core/Extensions/PredicateExtensionses.cs b/code/DG.Core/Extensions/PredicateExtensionses.cs new file mode 100644 index 0000000..9667cd1 --- /dev/null +++ b/code/DG.Core/Extensions/PredicateExtensionses.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public static class PredicateExtensionses + { + public static Expression> True() { return f => true; } + + public static Expression> False() { return f => false; } + + public static Expression> And(this Expression> expLeft, Expression> expRight) + { + var candidateExpr = Expression.Parameter(typeof(T), "candidate"); + var parameterReplacer = new ParameterReplacer(candidateExpr); + + var left = parameterReplacer.Replace(expLeft.Body); + var right = parameterReplacer.Replace(expRight.Body); + var body = Expression.And(left, right); + + return Expression.Lambda>(body, candidateExpr); + } + + public static Expression> Or(this Expression> expLeft, Expression> expRight) + { + var candidateExpr = Expression.Parameter(typeof(T), "candidate"); + var parameterReplacer = new ParameterReplacer(candidateExpr); + + var left = parameterReplacer.Replace(expLeft.Body); + var right = parameterReplacer.Replace(expRight.Body); + var body = Expression.OrElse(left, right); + + return Expression.Lambda>(body, candidateExpr); + } + /// + /// And ((a or b )and (x or d))关系,但是and后面里面的关系是Or的关系,如 a.resid='' and ((a.channel>=1 and a.channel<10) or (a.channel>=50 and a.channel<60)) + /// + /// + /// + /// + /// + public static Expression> AndListOr(this Expression> expLeft, Expression>[] predicates) + { + var candidateExpr = Expression.Parameter(typeof(T), "candidate"); + var parameterReplacer = new ParameterReplacer(candidateExpr); + var left = parameterReplacer.Replace(expLeft.Body); + Expression> lambda = predicates[0]; + for (int i = 1; i < predicates.Length; i++) + { + lambda = lambda.Or(predicates[i]); + } + var right = parameterReplacer.Replace(lambda.Body); + var body = Expression.And(left, right); + + return Expression.Lambda>(body, candidateExpr); + } + + /// + /// 传入条件之间为OR查询 + /// + /// + /// + /// + /// + public static IQueryable WhereOR(this IQueryable source, params Expression>[] predicates) + { + if (source == null) throw new ArgumentNullException("source"); + if (predicates == null) throw new ArgumentNullException("predicates"); + if (predicates.Length == 0) return source.Where(x => true); + if (predicates.Length == 1) return source.Where(predicates[0]); + + var param = Expression.Parameter(typeof(T), "x"); + Expression body = Expression.Invoke(predicates[0], param); + for (int i = 1; i < predicates.Length; i++) + { + body = Expression.OrElse(body, Expression.Invoke(predicates[i], param)); + } + var lambda = Expression.Lambda>(body, param); + return source.Where(lambda); + } + } + + internal class ParameterReplacer : ExpressionVisitor + { + public ParameterReplacer(ParameterExpression paramExpr) + { + this.ParameterExpression = paramExpr; + } + + public ParameterExpression ParameterExpression { get; private set; } + + public Expression Replace(Expression expr) + { + return this.Visit(expr); + } + + protected override Expression VisitParameter(ParameterExpression p) + { + return this.ParameterExpression; + } + } +} diff --git a/code/DG.Core/Extensions/SystemKeyExtensions.cs b/code/DG.Core/Extensions/SystemKeyExtensions.cs new file mode 100644 index 0000000..3d03812 --- /dev/null +++ b/code/DG.Core/Extensions/SystemKeyExtensions.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public static class SystemKeyExtensions + { + /// + /// If extensions + /// + /// + /// + /// + /// + /// + public static T If(this T t, bool condition, Action action) where T : class + { + if (condition) + { + action(t); + } + + return t; + } + + /// + /// If extensions + /// + /// + /// + /// + /// + /// + public static T If(this T t, Predicate predicate, Action action) where T : class + { + if (t == null) + { + throw new ArgumentNullException(); + } + + if (predicate(t)) + { + action(t); + } + + return t; + } + + /// + /// If extensions + /// + /// + /// + /// + /// + /// + public static T If(this T t, bool condition, Func func) where T : class => condition ? func(t) : t; + + /// + /// If extensions + /// + /// + /// + /// + /// + /// + public static T If(this T t, Predicate predicate, Func func) where T : class => predicate(t) ? func(t) : t; + + /// + /// If and else extensions + /// + /// + /// + /// + /// + /// + /// + public static T IfAndElse(this T t, bool condition, Action ifAction, Action elseAction) where T : class + { + if (condition) + { + ifAction(t); + } + else + { + elseAction(t); + } + + return t; + } + + /// + /// If and else extensions + /// + /// + /// + /// + /// + /// + /// + public static T IfAndElse(this T t, Predicate predicate, Action ifAction, Action elseAction) where T : class + { + if (t == null) + { + throw new ArgumentNullException(); + } + + if (predicate(t)) + { + ifAction(t); + } + else + { + elseAction(t); + } + + return t; + } + + /// + /// If and else extensions + /// + /// + /// + /// + /// + /// + /// + public static T IfAndElse(this T t, bool condition, Func ifFunc, Func elseFunc) where T : class => condition ? ifFunc(t) : elseFunc(t); + + /// + /// If and else extensions + /// + /// + /// + /// + /// + /// + /// + public static T IfAndElse(this T t, Predicate predicate, Func ifFunc, Func elseFunc) where T : class => predicate(t) ? ifFunc(t) : elseFunc(t); + } +} diff --git a/code/DG.Core/HttpClient.cs b/code/DG.Core/HttpClient.cs new file mode 100644 index 0000000..23b28fb --- /dev/null +++ b/code/DG.Core/HttpClient.cs @@ -0,0 +1,759 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; +using System.Threading.Tasks; +using System.Web; + +namespace DG.Core +{ + public class HttpClient : IHttpClient + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private static LogLevel _logLevel = LogLevel.Debug; + + public HttpClient(IHttpClientFactory httpClientFactory, + ILogger logger) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + } + + private static JsonSerializerOptions options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + public void ChangeLogLevel(LogLevel logLevel) + { + _logLevel = logLevel; + } + + private void Log(string message) + { + _logger.Log(_logLevel, message); + } + + /// + /// Post Security + /// + /// + /// + /// + /// + /// + /// + /// + public async Task PostSecurityAsync(string url, object data, string clientid, string accessKey, string iv) + { + try + { + var timeStamp = GetTimeStamp(); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + var param = JsonSerializer.Serialize(data, options); + var bodyJson = EncryptByAES(param, accessKey, iv); + var sign = SignData(bodyJson, accessKey); + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.TryAddWithoutValidation("clientid", clientid); + client.DefaultRequestHeaders.Add("sign", sign); + var httpData = new StringContent(bodyJson, Encoding.UTF8, "application/json"); + Log($"POST 请求Url:{url}, Body:{bodyJson}"); + var httpResponse = await client.PostAsync($"{url}", httpData); + var stream = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{stream}"); + var response = JsonSerializer.Deserialize(stream, options); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST 方法请求错误!"); + throw; + } + } + + private static string SignData(string ciphertext, string accessKey) + { + Encoding utf = new UTF8Encoding(); + HMACMD5 hmac = new HMACMD5(utf.GetBytes(accessKey)); + byte[] hashValue = hmac.ComputeHash(utf.GetBytes(ciphertext)); + return Convert.ToBase64String(hashValue); + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task PostSecurityAsync(string url, object param, object data, string clientid, string accessKey) + { + try + { + var timeStamp = GetTimeStamp(); + + var paramStr = JsonSerializer.Serialize(param, options); + var bodyJson = JsonSerializer.Serialize(data, options); + var content = EncyptData(paramStr, accessKey); + var sign = SignData(content, accessKey); + var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(30); + var httpData = new StringContent(bodyJson, Encoding.UTF8, "application/json"); + url = $"{url}?content={HttpUtility.UrlEncode(content)}&sign={HttpUtility.UrlEncode(sign, Encoding.UTF8)}&clientid={clientid}"; + Log($"POST 请求Url:{url}, Body:{bodyJson}"); + var httpResponse = await client.PostAsync(url, httpData); + var stream = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{stream}"); + var response = JsonSerializer.Deserialize(stream, options); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST 方法请求错误!"); + throw; + } + } + + /// + /// Post Security + /// + /// + /// + /// + /// + /// + /// + /// + public async Task PostSecurityAsync(string url, object data, string clientid, string accessKey, string iv) + { + try + { + var timeStamp = GetTimeStamp(); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + var param = JsonSerializer.Serialize(data, options); + var bodyJson = EncryptByAES(param, accessKey, iv); + var sign = SignData(bodyJson, accessKey); + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.TryAddWithoutValidation("clientid", clientid); + client.DefaultRequestHeaders.Add("sign", sign); + var httpData = new StringContent(bodyJson, Encoding.UTF8, "application/json"); + Log($"POST 请求Url:{url}, Body:{bodyJson}"); + var httpResponse = await client.PostAsync($"{url}", httpData); + var response = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{response}"); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST 方法请求错误!"); + throw; + } + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task PostSecurityAsync(string url, object param, object data, string clientid, string accessKey) + { + try + { + var timeStamp = GetTimeStamp(); + var paramStr = JsonSerializer.Serialize(param, options); + var bodyJson = JsonSerializer.Serialize(data, options); + var content = EncyptData(paramStr, accessKey); + var sign = SignData(content, accessKey); + var client = _httpClientFactory.CreateClient(); + var httpData = new StringContent(bodyJson, Encoding.UTF8, "application/json"); + url = $"{url}?content={HttpUtility.UrlEncode(content)}&sign={HttpUtility.UrlEncode(sign, Encoding.UTF8)}&clientid={clientid}"; + Log($"POST 请求Url:{url}, Body:{bodyJson}"); + var httpResponse = await client.PostAsync(url, httpData); + var response = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{response}"); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST 方法请求错误!"); + throw; + } + } + + public async Task UploadFileAsync(string url, string fileName, string fullName, Dictionary? headers = null) + { + try + { + var buffer = await File.ReadAllBytesAsync(fullName); + var client = _httpClientFactory.CreateClient(); + if (headers != null) + { + foreach (var header in headers) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + ByteArrayContent fileContent = new ByteArrayContent(buffer); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "file", FileName = fileName }; + MultipartFormDataContent content = new MultipartFormDataContent + { + fileContent + }; + Log($"UploadFile 文件上传,请求Url:{url}"); + var httpResponse = await client.PostAsync(url, content); + var stream = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{stream}"); + var response = JsonSerializer.Deserialize(stream, options); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "UploadFile 方法请求错误!"); + throw; + } + } + + public async Task UploadFileAsync(string url, string fileName, string fullName, Dictionary? headers = null) + { + try + { + var buffer = await File.ReadAllBytesAsync(fullName); + var client = _httpClientFactory.CreateClient(); + if (headers != null) + { + foreach (var header in headers) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + ByteArrayContent fileContent = new ByteArrayContent(buffer); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "file", FileName = fileName }; + MultipartFormDataContent content = new MultipartFormDataContent + { + fileContent + }; + Log($"UploadFile 文件上传,请求Url:{url}"); + var httpResponse = await client.PostAsync(url, content); + var response = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{response}"); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "UploadFile 方法请求错误!"); + throw; + } + } + + #region 正常请求 + + /// + /// Post + /// + /// + /// + /// + /// + /// + /// + /// + public async Task PostAsync2(string url, string data, string? appId = "", string? appSecret = "", string? mediaType = "application/json") + { + // _logger.LogInformation("卧槽,进来了。1111"); + try + { + var client = _httpClientFactory.CreateClient(); + //var options = new JsonSerializerOptions + //{ + // DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + // PropertyNameCaseInsensitive = true + //}; + // _logger.LogInformation("卧槽,进来了。"); + var bodyJson = data; + if (!string.IsNullOrEmpty(appId)) + { + client.DefaultRequestHeaders.Add("appid", appId); + } + if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret)) + { + var timeStamp = GetTimeStamp(); + var sign = CreateSign(appId, bodyJson, appSecret, timeStamp); + var authorization = $"{appId}:{sign}"; + client.DefaultRequestHeaders.TryAddWithoutValidation("authorization", authorization); + client.DefaultRequestHeaders.Add("timestamps", timeStamp); + } + var httpData = new StringContent(bodyJson, Encoding.UTF8, mediaType); + _logger.LogInformation($"POST 请求Url:{url}, Body:{bodyJson}"); + var httpResponse = await client.PostAsync($"{url}", httpData); + var stream = await httpResponse.Content.ReadAsStringAsync(); + _logger.LogInformation($"请求结果:{stream}"); + var response = JsonSerializer.Deserialize(stream, options); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST 方法请求错误!"); + throw; + } + } + + /// + /// Post + /// + /// + /// + /// + /// + /// + /// + /// + public async Task PostAsync(string url, object? data = null, string? appId = "", string? appSecret = "", string? mediaType = "application/json") + { + try + { + var client = _httpClientFactory.CreateClient(); + //var options = new JsonSerializerOptions + //{ + // DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + // PropertyNameCaseInsensitive = true + //}; + //_logger.LogInformation("卧槽,进来了。"); + var bodyJson = data != null ? Newtonsoft.Json.JsonConvert.SerializeObject(data) : ""; + if (!string.IsNullOrEmpty(appId)) + { + client.DefaultRequestHeaders.Add("appid", appId); + } + if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret)) + { + var timeStamp = GetTimeStamp(); + var sign = CreateSign(appId, bodyJson, appSecret, timeStamp); + var authorization = $"{appId}:{sign}"; + client.DefaultRequestHeaders.TryAddWithoutValidation("authorization", authorization); + client.DefaultRequestHeaders.Add("timestamps", timeStamp); + } + var httpData = new StringContent(bodyJson, Encoding.UTF8, mediaType); + _logger.LogInformation($"POST 请求Url:{url}, Body:{bodyJson}"); + var httpResponse = await client.PostAsync($"{url}", httpData); + var stream = await httpResponse.Content.ReadAsStringAsync(); + _logger.LogInformation($"请求结果:{stream}"); + var response = Newtonsoft.Json.JsonConvert.DeserializeObject(stream); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST 方法请求错误!"); + throw; + } + } + + public async Task PostAsync(string url, object? data = null, string? appId = "", string? appSecret = "", string? mediaType = "application/json") + { + try + { + var client = _httpClientFactory.CreateClient(); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + var bodyJson = data != null ? JsonSerializer.Serialize(data, options) : ""; + if (!string.IsNullOrEmpty(appId)) + { + client.DefaultRequestHeaders.Add("appid", appId); + } + if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret)) + { + var timeStamp = GetTimeStamp(); + var sign = CreateSign(appId, bodyJson, appSecret, timeStamp); + var authorization = $"{appId}:{sign}"; + client.DefaultRequestHeaders.TryAddWithoutValidation("authorization", authorization); + client.DefaultRequestHeaders.Add("timestamps", timeStamp); + } + var httpData = new StringContent(bodyJson, Encoding.UTF8, mediaType); + Log($"POST 请求Url:{url}, Body:{bodyJson}"); + var httpResponse = await client.PostAsync($"{url}", httpData); + var response = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{response}"); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST 方法请求错误!"); + throw; + } + } + + /// + /// Get + /// + /// + /// + /// + /// + /// + public async Task GetAsync(string url, string appId = "", string appSecret = "", int timeout = 10000) + { + try + { + var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromMilliseconds(timeout); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + if (!string.IsNullOrEmpty(appId)) + { + client.DefaultRequestHeaders.Add("appid", appId); + } + if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret)) + { + var uri = new Uri(url); + var query = uri.Query; + var param = new Dictionary(); + if (query != null) + { + foreach (var item in query.Split('&')) + { + var sp = item.Split("="); + if (sp.Count() > 1) + { + param.Add(sp[0].Replace("?", ""), sp[1]); + } + } + } + var timeStamp = GetTimeStamp(); + param = param.OrderBy(m => m.Key).ToDictionary(m => m.Key, n => n.Value); + var paramStr = JsonSerializer.Serialize(param, options); + var sign = CreateSign(appId, paramStr, appSecret, timeStamp); + + var authorization = $"{appId}:{sign}"; + client.DefaultRequestHeaders.TryAddWithoutValidation("authorization", authorization); + client.DefaultRequestHeaders.Add("timestamps", timeStamp); + } + Log($"GET 请求Url:{url}"); + var httpResponse = await client.GetAsync($"{url}"); + var stream = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{stream}"); + var response = JsonSerializer.Deserialize(stream, options); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "GET 方法请求错误!"); + throw; + } + } + + /// + /// Get + /// + /// + /// + /// + /// + /// + /// + public async Task GetAsync(string url, Dictionary param, string appId = "", string appSecret = "") + { + try + { + var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(30); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + var urlParam = string.Join("&", param.Select(m => m.Key + "=" + m.Value)); + if (url.IndexOf('?') > -1) + { + url += urlParam; + } + else + { + url = url + "?" + urlParam; + } + if (!string.IsNullOrEmpty(appId)) + { + client.DefaultRequestHeaders.Add("appid", appId); + } + if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret)) + { + var timeStamp = GetTimeStamp(); + param = param.OrderBy(m => m.Key).ToDictionary(m => m.Key, n => n.Value); + var paramStr = JsonSerializer.Serialize(param, options); + var sign = CreateSign(appId, paramStr, appSecret, timeStamp); + var authorization = $"{appId}:{sign}"; + client.DefaultRequestHeaders.TryAddWithoutValidation("authorization", authorization); + client.DefaultRequestHeaders.Add("timestamps", timeStamp); + } + Log($"GET 请求Url:{url}"); + var httpResponse = await client.GetAsync($"{url}"); + var stream = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{stream}"); + var response = JsonSerializer.Deserialize(stream, options); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "GET 方法请求错误!"); + throw; + } + } + + /// + /// Get + /// + /// + /// + /// + /// + /// + public async Task GetAsync(string url, string appId = "", string appSecret = "") + { + try + { + var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(30); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret)) + { + var uri = new Uri(url); + var query = uri.Query; + var param = new Dictionary(); + if (query != null) + { + foreach (var item in query.Split('&')) + { + var sp = item.Split("="); + if (sp.Count() > 1) + { + param.Add(sp[0].Replace("?", ""), sp[1]); + } + } + } + var timeStamp = GetTimeStamp(); + param = param.OrderBy(m => m.Key).ToDictionary(m => m.Key, n => n.Value); + var paramStr = JsonSerializer.Serialize(param, options); + var sign = CreateSign(appId, paramStr, appSecret, timeStamp); + + var authorization = $"{appId}:{sign}"; + client.DefaultRequestHeaders.TryAddWithoutValidation("authorization", authorization); + client.DefaultRequestHeaders.Add("timestamps", timeStamp); + } + Log($"GET 请求Url:{url}"); + var httpResponse = await client.GetAsync($"{url}"); + var response = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{response}"); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "GET 方法请求错误!"); + throw; + } + } + + /// + /// Get + /// + /// + /// + /// + /// + /// + /// + public async Task GetAsync(string url, Dictionary param, string appId = "", string appSecret = "") + { + try + { + var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(30); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + var urlParam = string.Join("&", param.Select(m => m.Key + "=" + m.Value)); + if (url.IndexOf('?') > -1) + { + url += urlParam; + } + else + { + url = url + "?" + urlParam; + } + if (!string.IsNullOrEmpty(appId)) + { + client.DefaultRequestHeaders.Add("appid", appId); + } + if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret)) + { + var timeStamp = GetTimeStamp(); + param = param.OrderBy(m => m.Key).ToDictionary(m => m.Key, n => n.Value); + var paramStr = JsonSerializer.Serialize(param, options); + var sign = CreateSign(appId, paramStr, appSecret, timeStamp); + var authorization = $"{appId}:{sign}"; + client.DefaultRequestHeaders.TryAddWithoutValidation("authorization", authorization); + client.DefaultRequestHeaders.Add("timestamps", timeStamp); + } + Log($"GET 请求Url:{url}"); + var httpResponse = await client.GetAsync($"{url}"); + var response = await httpResponse.Content.ReadAsStringAsync(); + Log($"请求结果:{response}"); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "GET 方法请求错误!"); + throw; + } + } + + /// + /// 生成签名 + /// + /// + /// + /// + /// + /// + private static string CreateSign(string appId, string bodyJson, string secret, string timestamps) + { + var enStrList = new string[] { appId, bodyJson, secret, timestamps }; + Array.Sort(enStrList, string.CompareOrdinal); + var enStr = string.Join("", enStrList); + var md = GetMd5Hash(enStr); + return md; + } + + /// + /// 计算 md5 + /// + /// + /// + private static string GetMd5Hash(string enCode) + { + string res = ""; + byte[] data = Encoding.GetEncoding("utf-8").GetBytes(enCode); + MD5 md5 = MD5.Create(); + byte[] bytes = md5.ComputeHash(data); + for (int i = 0; i < bytes.Length; i++) + { + res += bytes[i].ToString("x2"); + } + return res; + } + + /// + /// 获取时间戳 + /// + /// + public static string GetTimeStamp() + { + TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds).ToString(); + } + + /// + /// 加密 + /// + /// + /// + /// + private static string EncyptData(string ciphertext, string accessKey) + { + SymmetricAlgorithm des = DES.Create(); + + Encoding utf = new UTF8Encoding(); + byte[] key = utf.GetBytes(accessKey); + byte[] iv = { 0x75, 0x70, 0x63, 0x68, 0x69, 0x6e, 0x61, 0x31 }; + ICryptoTransform encryptor = des.CreateEncryptor(key, iv); + byte[] data = utf.GetBytes(ciphertext); + byte[] encData = encryptor.TransformFinalBlock(data, 0, data.Length); + return Convert.ToBase64String(encData); + } + + /// + /// 解密 + /// + /// + /// + /// + private static string DecyptData(string cryptograph, string accessKey) + { + SymmetricAlgorithm des = DES.Create(); + + Encoding utf = new UTF8Encoding(); + byte[] key = utf.GetBytes(accessKey); + byte[] iv = { 0x75, 0x70, 0x63, 0x68, 0x69, 0x6e, 0x61, 0x31 }; + ICryptoTransform decryptor = des.CreateDecryptor(key, iv); + byte[] encData = Convert.FromBase64String(cryptograph); + byte[] data = decryptor.TransformFinalBlock(encData, 0, encData.Length); + return utf.GetString(data); + } + + /// + /// AES加密算法 + /// + /// 明文字符串 + /// 字符串 + private static string EncryptByAES(string input, string key, string iv) + { + if (string.IsNullOrWhiteSpace(input)) + { + return input; + } + Aes aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + aes.FeedbackSize = 128; + aes.Key = Encoding.UTF8.GetBytes(key); + aes.IV = Encoding.UTF8.GetBytes(iv); + ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); + using MemoryStream msEncrypt = new(); + using CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write); + using (StreamWriter swEncrypt = new(csEncrypt)) + { + swEncrypt.Write(input); + } + byte[] bytes = msEncrypt.ToArray(); + return Convert.ToBase64String(bytes); + } + + #endregion 正常请求 + } +} \ No newline at end of file diff --git a/code/DG.Core/IHttpClient.cs b/code/DG.Core/IHttpClient.cs new file mode 100644 index 0000000..2cef7c9 --- /dev/null +++ b/code/DG.Core/IHttpClient.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public interface IHttpClient + { + void ChangeLogLevel(LogLevel logLevel); + + Task PostSecurityAsync(string url, object data, string clientid, string accessKey, string iv); + + Task PostSecurityAsync(string url, object param, object data, string clientid, string accessKey); + + Task PostSecurityAsync(string url, object data, string clientid, string accessKey, string iv); + + Task PostSecurityAsync(string url, object param, object data, string clientid, string accessKey); + + Task PostAsync(string url, object? data = null, string? appId = "", string? appSecret = "", string? mediaType = "application/json"); + Task PostAsync2(string url, string data, string? appId = "", string? appSecret = "", string? mediaType = "application/json"); + + Task PostAsync(string url, object? data = null, string? appId = "", string? appSecret = "", string? mediaType = "application/json"); + + Task GetAsync(string url, string appId = "", string appSecret = "", int timeout = 10000); + + Task GetAsync(string url, Dictionary param, string appId = "", string appSecret = ""); + + Task GetAsync(string url, string appId = "", string appSecret = ""); + + Task GetAsync(string url, Dictionary param, string appId = "", string appSecret = ""); + + Task UploadFileAsync(string url, string fileName, string fullName, Dictionary? headers = null); + + Task UploadFileAsync(string url, string fileName, string fullName, Dictionary? headers = null); + } +} \ No newline at end of file diff --git a/code/DG.Core/IMvcBuilderApiResultExtensions.cs b/code/DG.Core/IMvcBuilderApiResultExtensions.cs new file mode 100644 index 0000000..e554d35 --- /dev/null +++ b/code/DG.Core/IMvcBuilderApiResultExtensions.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public static class IMvcBuilderApiResultExtensions + { + /// + /// 启用API标准返回值模式 + /// + /// + /// + /// + public static IMvcBuilder AddApiResult(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.AddMvcOptions(options => { + options.Filters.Add(typeof(ApiExceptionFilterAttribute)); + options.Filters.Add(typeof(ApiResultFilterAttribute)); + }); + } + + /// + /// 启用API签名模式 + /// + /// + /// + /// + public static IMvcBuilder AddApiSignature(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.AddMvcOptions(options => { + options.Filters.Add(typeof(ApiSecurityAsyncFilter)); + options.Filters.Add(typeof(ApiSignatureAsyncFilterAttribute)); + options.Filters.Add(typeof(ApiTimeSecurityAsyncFilter)); + }); + } + } +} diff --git a/code/DG.Core/Mapper/IMapper.cs b/code/DG.Core/Mapper/IMapper.cs new file mode 100644 index 0000000..c3b8c5b --- /dev/null +++ b/code/DG.Core/Mapper/IMapper.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public interface IMapper + { + TTarget Map(TSource source); + + List Map(List source); + } +} diff --git a/code/DG.Core/Mapper/Mapper.cs b/code/DG.Core/Mapper/Mapper.cs new file mode 100644 index 0000000..f3491bb --- /dev/null +++ b/code/DG.Core/Mapper/Mapper.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + internal class Mapper + { + public static readonly Mapper Instance = new Mapper(); + + private Mapper() + { + } + + public TTarget Map(TSource source) => CacheModel.Invoke(source); + + public List Map(List sources) => sources.AsParallel().Select(CacheModel.Invoke).ToList(); + + internal class CacheModel + { + private static readonly Func Func; + + static CacheModel() + { + var parameterExpression = Expression.Parameter(typeof(TSource), "x"); + var sourcePropNames = typeof(TSource).GetProperties() + .Where(x => !x.IsDefined(typeof(NotMapAttribute), true)) + .Select(x => x.Name) + .ToArray(); + + var memberBindings = typeof(TTarget).GetProperties() + .Where(x => x.CanWrite && sourcePropNames.Any(y => y.ToUpper() == x.Name.ToUpper())) + .Select(x => Expression.Bind(typeof(TTarget).GetProperty(x.Name), + Expression.Property(parameterExpression, + typeof(TSource).GetProperty(sourcePropNames.FirstOrDefault(y => y.ToUpper() == x.Name.ToUpper()))))); + + Func = Expression.Lambda>(Expression.MemberInit(Expression.New(typeof(TTarget)), memberBindings), parameterExpression).Compile(); + } + + public static TTarget Invoke(TSource source) => Func(source); + } + } +} diff --git a/code/DG.Core/Mapper/MapperExtendsions.cs b/code/DG.Core/Mapper/MapperExtendsions.cs new file mode 100644 index 0000000..f341807 --- /dev/null +++ b/code/DG.Core/Mapper/MapperExtendsions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public static class MapperExtendsions + { + /// + /// 映射到 + /// + /// + /// + /// + /// + public static TTarget Map(this TSource source) => Mapper.Instance.Map(source); + + /// + /// 映射到 + /// + /// + /// + /// + /// + public static List Map(this List sources) => Mapper.Instance.Map(sources); + + /// + /// 复制到 + /// + /// + /// + /// + /// + /// 因为重名了,所以对方法取别名,同 MapTo + public static TTarget Replicate(this TSource source) => source.Map(); + + /// + /// 复制到 + /// + /// + /// + /// + /// + /// 因为重名了,所以对方法取别名,同 MapTo + public static List Replicate(this List sources) => sources.Map(); + } +} diff --git a/code/DG.Core/Mapper/MapperManager.cs b/code/DG.Core/Mapper/MapperManager.cs new file mode 100644 index 0000000..b0895da --- /dev/null +++ b/code/DG.Core/Mapper/MapperManager.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class MapperManager : IMapper + { + public TTarget Map(TSource source) => source.Map(); + + public List Map(List source) => source.Map(); + } +} diff --git a/code/DG.Core/Mapper/NotMapAttribute.cs b/code/DG.Core/Mapper/NotMapAttribute.cs new file mode 100644 index 0000000..d45ab19 --- /dev/null +++ b/code/DG.Core/Mapper/NotMapAttribute.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class NotMapAttribute : Attribute + { + public NotMapAttribute() + { + } + } +} diff --git a/code/DG.Core/Result/ApiExceptionFilterAttribute.cs b/code/DG.Core/Result/ApiExceptionFilterAttribute.cs new file mode 100644 index 0000000..acf66ad --- /dev/null +++ b/code/DG.Core/Result/ApiExceptionFilterAttribute.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace DG.Core +{ + /// + /// 表示处理API异常的筛选器。 + /// + public class ApiExceptionFilterAttribute : Attribute, IExceptionFilter + { + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger + public ApiExceptionFilterAttribute(ILogger logger) + { + _logger = logger; + } + + /// + /// Called when [exception]. + /// + /// The context. + public void OnException(ExceptionContext context) + { + + _logger.LogError(0, context.Exception, $"ip={context.HttpContext.Connection.RemoteIpAddress}, path={context.HttpContext.Request.Path}, error={JsonSerializer.Serialize(context.Exception.Message)}"); + + if (context.Exception.Data != null && context.Exception.Data["code"] != null) + { + var code = (int)context.Exception.Data["code"]; + context.Result = new ObjectResult(ApiResult.Failed(context.Exception.Message, code)); + } + else + { + context.Result = new ObjectResult(ApiResult.Failed(context.Exception.Message)); + } + + + context.ExceptionHandled = true; + + + } + } +} diff --git a/code/DG.Core/Result/ApiResourceFilter.cs b/code/DG.Core/Result/ApiResourceFilter.cs new file mode 100644 index 0000000..dc664a4 --- /dev/null +++ b/code/DG.Core/Result/ApiResourceFilter.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class ApiResourceFilter : Attribute, IResourceFilter + { + public void OnResourceExecuted(ResourceExecutedContext context) + { + if (!context.ModelState.IsValid) + { + var errors = string.Empty; + foreach (var key in context.ModelState.Keys) + { + var modelState = context.ModelState[key]; + foreach (var error in modelState.Errors) + { + errors += string.IsNullOrEmpty(errors) ? error.ErrorMessage + : $",{error.ErrorMessage}"; + } + } + context.Result = new ObjectResult(ApiResult.Failed(errors)); + } + // 执行完后的操作 + } + + public void OnResourceExecuting(ResourceExecutingContext context) + { + // 执行中的过滤器管道 + } + } +} diff --git a/code/DG.Core/Result/ApiResult.cs b/code/DG.Core/Result/ApiResult.cs new file mode 100644 index 0000000..331c771 --- /dev/null +++ b/code/DG.Core/Result/ApiResult.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class ApiResult : IApiResult + { + + /// + /// Represents an empty . + /// + public static readonly IApiResult Empty = new ApiResult + { + Code = 0 + }; + + /// + /// Gets or sets the status code. + /// + /// The status code. + [JsonPropertyName("code")] + public int Code { get; set; } + + /// + /// Gets or sets the message. + /// + /// The message. + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// + /// Creates a new instance of by the specified result. + /// + /// The type of the result. + /// The result. + /// An instance inherited from interface. + public static IApiResult Succeed(TData data) => new ApiResult + { + Code = 0, + Data = data + }; + + /// + /// Creates a new instance of by the specified error message. + /// + /// The message. + /// The status code + /// An instance inherited from interface. + public static IApiResult Failed(string message, int? code = null) => new ApiResult + { + Code = code ?? -1, + Message = message + }; + + /// + /// Creates a new instance of by the specified error message. + /// + /// The type of the result. + /// The error result. + /// The message. + /// The status code. + /// An instance inherited from interface. + public static IApiResult Failed(TData data, string message, int? code = null) => new ApiResult + { + Code = code ?? -1, + Message = message, + Data = data + }; + + /// + /// Creates a new instance of by the specified status code and message. + /// + /// The status code. + /// The message. + /// An instance inherited from interface. + public static IApiResult From(int code, string message = null) => new ApiResult + { + Code = code, + Message = message + }; + + /// + /// Creates a new instance of by the specified result. + /// + /// The type of the result. + /// The result. + /// The status code. + /// The message. + /// An instance inherited from interface. + public static IApiResult From(TData data, int code, string message) => new ApiResult + { + Code = code, + Message = message, + Data = data + }; + } +} diff --git a/code/DG.Core/Result/ApiResultFilterAttribute.cs b/code/DG.Core/Result/ApiResultFilterAttribute.cs new file mode 100644 index 0000000..c944991 --- /dev/null +++ b/code/DG.Core/Result/ApiResultFilterAttribute.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace DG.Core +{ + public class ApiResultFilterAttribute : ResultFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + if (context.Filters.Any(filterMetadata => filterMetadata.GetType() == typeof(ApiResultFilterForbidAttribute))) + { + return; + } + switch (context.Result) + { + case ObjectResult result: + { + // this include OkObjectResult, NotFoundObjectResult, BadRequestObjectResult, CreatedResult (lose Location) + var objectResult = result; + if (objectResult.Value == null) + { + context.Result = new ObjectResult(ApiResult.Empty); + } + else if (objectResult.Value is ValidationProblemDetails validationProblemDetails) + { + var errors = string.Empty; + foreach(var error in validationProblemDetails.Errors) + { + errors += string.IsNullOrEmpty(errors) ? error.Value.First() + : $",{error.Value.First()}"; + } + context.Result = new ObjectResult(ApiResult.Failed(errors)); + } + else if (!(objectResult.Value is IApiResult)) + { + if (objectResult.DeclaredType != null) + { + var apiResult = Activator.CreateInstance( + typeof(ApiResult<>).MakeGenericType(objectResult.DeclaredType), objectResult.Value, objectResult.StatusCode); + context.Result = new ObjectResult(apiResult); + } + else + { + context.Result = objectResult; + } + + } + + break; + } + case EmptyResult _: + // return void or Task + context.Result = new ObjectResult(ApiResult.Empty); + break; + + case ContentResult result: + context.Result = new ObjectResult(ApiResult.Succeed(result.Content)); + break; + + case StatusCodeResult result: + // this include OKResult, NoContentResult, UnauthorizedResult, NotFoundResult, BadRequestResult + context.Result = new ObjectResult(ApiResult.From(result.StatusCode)); + break; + } + } + } +} diff --git a/code/DG.Core/Result/ApiResultFilterForbidAttribute.cs b/code/DG.Core/Result/ApiResultFilterForbidAttribute.cs new file mode 100644 index 0000000..b56586c --- /dev/null +++ b/code/DG.Core/Result/ApiResultFilterForbidAttribute.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class ApiResultFilterForbidAttribute : ResultFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { } + } +} diff --git a/code/DG.Core/Result/ApiResultGeneric.cs b/code/DG.Core/Result/ApiResultGeneric.cs new file mode 100644 index 0000000..9c17e4d --- /dev/null +++ b/code/DG.Core/Result/ApiResultGeneric.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class ApiResult : ApiResult, IApiResult + { + /// + /// Initializes a new instance of the class. + /// + public ApiResult() { } + + /// + /// Initializes a new instance of the class. + /// + /// The result. + /// The status code. + public ApiResult(TData data, int? code) + { + Code = code ?? 0; + Data = data; + } + + /// + /// Gets or sets the result. + /// + /// The result. + [JsonPropertyName("data")] + public TData Data { get; set; } + } +} diff --git a/code/DG.Core/Result/IApiResult.cs b/code/DG.Core/Result/IApiResult.cs new file mode 100644 index 0000000..e2532e5 --- /dev/null +++ b/code/DG.Core/Result/IApiResult.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public interface IApiResult + { + /// + /// Gets or sets the status code. + /// + /// The status code. + int Code { get; set; } + + /// + /// Gets or sets the message. + /// + /// The message. + string Message { get; set; } + } +} diff --git a/code/DG.Core/Result/IApiResultGeneric.cs b/code/DG.Core/Result/IApiResultGeneric.cs new file mode 100644 index 0000000..3b6c735 --- /dev/null +++ b/code/DG.Core/Result/IApiResultGeneric.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public interface IApiResult : IApiResult + { + /// + /// Gets or sets the result. + /// + /// The result. + TData Data { get; set; } + } +} diff --git a/code/DG.Core/Result/PageResult.cs b/code/DG.Core/Result/PageResult.cs new file mode 100644 index 0000000..851c690 --- /dev/null +++ b/code/DG.Core/Result/PageResult.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class PageResult where TData : class + { + public PageResult(int pageIndex, int pageSize, int total, IList? data) + { + PageIndex = pageIndex; + PageSize = pageSize; + Total = total; + Data = data; + TotalCount = total == 0 ? 0 : (Total / PageSize) + (Total % PageSize) > 0 ? 1 : 0; + } + + /// + /// 页数 + /// + public int PageIndex { get; set; } + + /// + /// 分页大小 + /// + public int PageSize { get; set; } + + /// + /// 总数量 + /// + public int Total { get; set; } + + /// + /// 分页总数量 + /// + public int TotalCount { get; set; } + + /// + /// 数据 + /// + public IList? Data { get; set; } + } +} diff --git a/code/DG.Core/Result/SearchPageBase.cs b/code/DG.Core/Result/SearchPageBase.cs new file mode 100644 index 0000000..f918027 --- /dev/null +++ b/code/DG.Core/Result/SearchPageBase.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class SearchPageBase + { + /// + /// 页数 + /// + public int PageIndex { get; set; } + + /// + /// 分页大小 + /// + public int PageSize { get; set; } + + /// + /// 排序字段,支持逗号隔开 + /// + public string? Sort { get; set; } + + /// + /// 升降序,Asc/Desc + /// + public string? Order { get; set; } + + /// + /// 是否导出 + /// + public bool? Export { get; set; } + } +} diff --git a/code/DG.Core/Result/SelectItem.cs b/code/DG.Core/Result/SelectItem.cs new file mode 100644 index 0000000..87a22fc --- /dev/null +++ b/code/DG.Core/Result/SelectItem.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class SelectItem + { + public SelectItem(object key, object value) + { + Key = key; + Value = value; + } + + public object Key { get; set; } + + public object? Value { get; set; } + } +} diff --git a/code/DG.Core/Security/ApiSecurityAsyncFilter.cs b/code/DG.Core/Security/ApiSecurityAsyncFilter.cs new file mode 100644 index 0000000..e8f6512 --- /dev/null +++ b/code/DG.Core/Security/ApiSecurityAsyncFilter.cs @@ -0,0 +1,133 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json; +using Microsoft.Extensions.Configuration; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; + +using Microsoft.Extensions.Configuration; + +using Microsoft.Extensions.Logging; +using System.Text.Json.Serialization; + +namespace DG.Core +{ + public class ApiSecurityAsyncFilter : IAsyncAuthorizationFilter + { + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public ApiSecurityAsyncFilter(IConfiguration configuration, + ILogger logger) + { + _configuration = configuration; + _logger = logger; + } + + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + if (!context.Filters.Any(filterMetadata => filterMetadata.GetType() == typeof(ApiSecurityAttribute))) + { + return; + } + var request = context.HttpContext.Request; + if (!request.Method.ToLower().Equals("post")) + { + context.Result = new ObjectResult(ApiResult.Failed("ApiSecurityAsyncFilter只支持POST方法!", 10004)); + return; + } + var clientKeys = _configuration.GetSection("ClientKey").Get>(); + if (clientKeys == null || !clientKeys.Any()) + { + context.Result = new ObjectResult(ApiResult.Failed("ClientKey没有配置!", 10003)); + return; + } + var clientid = request.Headers["clientid"].ToString(); + var sign = request.Headers["sign"].ToString(); + if (string.IsNullOrEmpty(clientid) || string.IsNullOrEmpty(sign)) + { + context.Result = new ObjectResult(ApiResult.Failed("请求头clientid或sign不能为空!", 10003)); + return; + } + var client = clientKeys.First(x => x.Id == clientid); + request.EnableBuffering(); + var stream = request.Body; + var buffer = new byte[request.ContentLength.Value]; + await stream.ReadAsync(buffer); + var bodyJson = Encoding.UTF8.GetString(buffer); + stream.Position = 0; + var signData = SignData(bodyJson, client.NewAccessKey); + if (!signData.Equals(sign)) + { + context.Result = new ObjectResult(ApiResult.Failed("签名不合法!", 10001)); + return; + } + + try + { + var contextAes = DecryptByAES(client, bodyJson); + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + + var dataContext = Encoding.UTF8.GetBytes(contextAes); + var requestBodyStream = new MemoryStream();//创建一个流 + requestBodyStream.Seek(0, SeekOrigin.Begin);//设置从0开始读取 + requestBodyStream.Write(dataContext, 0, dataContext.Length);//把修改写入流中 + request.Body = requestBodyStream;//把修改后的内容赋值给请求body + request.Body.Seek(0, SeekOrigin.Begin); + request.Body.Position = 0; + } + catch (Exception ex) + { + _logger.LogInformation(ex, "报错解密"); + } + } + + private static string SignData(string ciphertext, string accessKey) + { + Encoding utf = new UTF8Encoding(); + HMACMD5 hmac = new HMACMD5(utf.GetBytes(accessKey)); + byte[] hashValue = hmac.ComputeHash(utf.GetBytes(ciphertext)); + return Convert.ToBase64String(hashValue); + } + + private static string DecryptByAES(ClientKey client, string bodyJson) + { + return DecryptByAES(bodyJson, client.NewAccessKey, client.Vi); + } + + /// + /// AES解密 + /// + /// 密文字节数组 + /// 返回解密后的字符串 + private static string DecryptByAES(string input, string key, string iv) + { + if (string.IsNullOrWhiteSpace(input)) + { + return input; + } + var buffer = Convert.FromBase64String(input); + using Aes aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + aes.FeedbackSize = 128; + aes.Key = Encoding.UTF8.GetBytes(key); + aes.IV = Encoding.UTF8.GetBytes(iv); + ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + using MemoryStream msEncrypt = new(buffer); + using CryptoStream csEncrypt = new(msEncrypt, decryptor, CryptoStreamMode.Read); + using StreamReader srEncrypt = new(csEncrypt); + return srEncrypt.ReadToEnd(); + } + } +} \ No newline at end of file diff --git a/code/DG.Core/Security/ApiSecurityAttribute.cs b/code/DG.Core/Security/ApiSecurityAttribute.cs new file mode 100644 index 0000000..fc9747b --- /dev/null +++ b/code/DG.Core/Security/ApiSecurityAttribute.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + /// + /// 安全API + /// + public class ApiSecurityAttribute : Attribute, IFilterMetadata + { + public ApiSecurityAttribute() + { + + } + } +} diff --git a/code/DG.Core/Security/ClientKey.cs b/code/DG.Core/Security/ClientKey.cs new file mode 100644 index 0000000..e347176 --- /dev/null +++ b/code/DG.Core/Security/ClientKey.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + internal class ClientKey + { + public ClientKey(string id, string name, string accessKey, string vi, string newAccessKey) + { + Id = id; + Name = name; + AccessKey = accessKey; + Vi = vi; + NewAccessKey = newAccessKey; + } + + public ClientKey() + { + + } + + public string Id { get; set; } + + public string Name { get; set; } + + public string AccessKey { get; set; } + + public string Vi { get; set; } + + public string NewAccessKey { get; set; } + } +} diff --git a/code/DG.Core/ServiceCollectionExtensions.cs b/code/DG.Core/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..ec61c92 --- /dev/null +++ b/code/DG.Core/ServiceCollectionExtensions.cs @@ -0,0 +1,65 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace DG.Core +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddMapper(this IServiceCollection services) + => services.AddSingleton(); + + /// + /// Add auto ioc services + /// + /// + /// + /// + /// + public static IServiceCollection AddAutoIoc(this IServiceCollection services, Type baseType, LifeCycle lifeCycle) + { + if (!baseType.IsInterface) + { + throw new TypeLoadException("The status code must be an enumerated type"); + } + + var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; + var referencedAssemblies = System.IO.Directory.GetFiles(path, "*.dll").Select(Assembly.LoadFrom).ToArray(); + var types = referencedAssemblies + .SelectMany(a => a.DefinedTypes) + .Select(type => type.AsType()) + .Where(x => x != baseType && baseType.IsAssignableFrom(x)).ToArray(); + var implementTypes = types.Where(x => x.IsClass).ToArray(); + var interfaceTypes = types.Where(x => x.IsInterface).ToArray(); + foreach (var implementType in implementTypes) + { + var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType)); + if (interfaceType != null) + switch (lifeCycle) + { + case LifeCycle.Singleton: + services.AddSingleton(interfaceType, implementType); + break; + + case LifeCycle.Transient: + services.AddTransient(interfaceType, implementType); + break; + + case LifeCycle.Scoped: + services.AddScoped(interfaceType, implementType); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(lifeCycle), lifeCycle, null); + } + } + return services; + } + + + public static IServiceCollection AddDGHttpClient(this IServiceCollection services) + { + return services.AddHttpClient() + .AddTransient(); + } + } +} diff --git a/code/DG.Core/Signature/ApiSignatureAsyncFilterAttribute.cs b/code/DG.Core/Signature/ApiSignatureAsyncFilterAttribute.cs new file mode 100644 index 0000000..2d88d7d --- /dev/null +++ b/code/DG.Core/Signature/ApiSignatureAsyncFilterAttribute.cs @@ -0,0 +1,138 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json; +using Microsoft.Extensions.Configuration; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using System.Text.Json.Serialization; + +namespace DG.Core +{ + public class ApiSignatureAsyncFilterAttribute : IAsyncAuthorizationFilter + { + private readonly IConfiguration _configuration; + + public ApiSignatureAsyncFilterAttribute(IConfiguration configuration) + { + _configuration = configuration; + } + + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + if (context.Filters.Any(filterMetadata => + filterMetadata.GetType() == typeof(ApiSignatureFilterForbidAttribute) || + filterMetadata.GetType() == typeof(ApiSecurityAttribute) || + filterMetadata.GetType() == typeof(ApiTimeSecurityAttribute))) + { + return; + } + var request = context.HttpContext.Request; + var appId = _configuration.GetSection("SignConfig:AppId").Value; + var secret = _configuration.GetSection("SignConfig:Secret").Value; + if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(secret)) + { + context.Result = new ObjectResult(ApiResult.Failed("appId或secret没有配置!", 10003)); + return; + } + + var authorization = request.Headers["Authorization"].ToString(); + var timestamps = request.Headers["timestamps"].ToString(); + if (string.IsNullOrEmpty(authorization) || string.IsNullOrEmpty(timestamps)) + { + context.Result = new ObjectResult(ApiResult.Failed("请求头authorization或timestamps不能为空!", 10003)); + return; + } + string? bodyJson; + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + if (request.Method.ToLower().Equals("get") || request.Method.ToLower().Equals("delete")) + { + var query = request.Query; + var parames = new Dictionary(); + foreach (var item in query) + { + parames.Add(item.Key, item.Value.ToString()); + } + parames = parames.OrderBy(m => m.Key).ToDictionary(m => m.Key, n => n.Value); + bodyJson = JsonSerializer.Serialize(parames, options); + } + else + { + request.EnableBuffering(); + var stream = request.Body; + var buffer = new byte[request.ContentLength.Value]; + await stream.ReadAsync(buffer); + bodyJson = Encoding.UTF8.GetString(buffer); + stream.Position = 0; + } + + var md = CreateSign(appId, bodyJson, secret, timestamps); + if (authorization != $"{appId}:{md}") + { + context.Result = new ObjectResult(ApiResult.Failed("签名不合法!", 10001)); + } + else + { + var nowTime = GetTimeStamp(); + var diff = Convert.ToInt32(nowTime) - Convert.ToInt32(timestamps); + if (diff > 1800) + { + context.Result = new ObjectResult(ApiResult.Failed("签名已过期!", 10002)); + } + } + } + + /// + /// 生成签名 + /// + /// + /// + /// + /// + /// + private static string CreateSign(string appId, string bodyJson, string secret, string timestamps) + { + var enStrList = new string[] { appId, bodyJson, secret, timestamps }; + Array.Sort(enStrList, string.CompareOrdinal); + var enStr = string.Join("", enStrList); + var md = GetMd5Hash(enStr); + return md; + } + + /// + /// 计算 md5 + /// + /// + /// + private static string GetMd5Hash(string enCode) + { + string res = ""; + byte[] data = Encoding.GetEncoding("utf-8").GetBytes(enCode); + MD5 md5 = MD5.Create(); + byte[] bytes = md5.ComputeHash(data); + for (int i = 0; i < bytes.Length; i++) + { + res += bytes[i].ToString("x2"); + } + return res; + } + + /// + /// 获取时间戳 + /// + /// + public static string GetTimeStamp() + { + TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds).ToString(); + } + } +} diff --git a/code/DG.Core/Signature/ApiSignatureFilterForbidAttribute.cs b/code/DG.Core/Signature/ApiSignatureFilterForbidAttribute.cs new file mode 100644 index 0000000..7b96aec --- /dev/null +++ b/code/DG.Core/Signature/ApiSignatureFilterForbidAttribute.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + /// + /// API屏蔽签名 + /// + public class ApiSignatureFilterForbidAttribute : Attribute, IAuthorizationFilter + { + public void OnAuthorization(AuthorizationFilterContext context) + { + } + } +} diff --git a/code/DG.Core/TimeSecurity/ApiTimeSecurityAsyncFilter.cs b/code/DG.Core/TimeSecurity/ApiTimeSecurityAsyncFilter.cs new file mode 100644 index 0000000..a520f12 --- /dev/null +++ b/code/DG.Core/TimeSecurity/ApiTimeSecurityAsyncFilter.cs @@ -0,0 +1,171 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json; +using Microsoft.Extensions.Configuration; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; + +using Microsoft.Extensions.Configuration; + +using Microsoft.Extensions.Logging; +using System.Text.Json.Serialization; +using System.Collections.Specialized; + +namespace DG.Core +{ + public class ApiTimeSecurityAsyncFilter : IAsyncAuthorizationFilter + { + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public ApiTimeSecurityAsyncFilter(IConfiguration configuration, + ILogger logger) + { + _configuration = configuration; + _logger = logger; + } + + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + if (!context.Filters.Any(filterMetadata => filterMetadata.GetType() == typeof(ApiTimeSecurityAttribute))) + { + return; + } + var request = context.HttpContext.Request; + var clientKeys = _configuration.GetSection("ClientKey").Get>(); + if (clientKeys == null || !clientKeys.Any()) + { + context.Result = new ObjectResult(ApiResult.Failed("ClientKey没有配置!", 10003)); + return; + } + var clientid = request.Headers["clientid"].ToString(); + var sign = request.Headers["sign"].ToString(); + var timestamps = request.Headers["timestamps"].ToString(); + if (string.IsNullOrEmpty(clientid) || string.IsNullOrEmpty(sign) || string.IsNullOrEmpty(timestamps)) + { + context.Result = new ObjectResult(ApiResult.Failed("请求头clientid或sign或timestamps不能为空!", 10003)); + return; + } + var client = clientKeys.First(x => x.Id == clientid); + + string? bodyJson; + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true + }; + if (request.Method.ToLower().Equals("get") || request.Method.ToLower().Equals("delete")) + { + var query = request.Query; + var parames = new Dictionary(); + foreach (var item in query) + { + parames.Add(item.Key, item.Value.ToString()); + } + bodyJson = JsonSerializer.Serialize(parames, options); + } + else + { + request.EnableBuffering(); + var stream = request.Body; + var buffer = new byte[request.ContentLength.Value]; + await stream.ReadAsync(buffer); + bodyJson = Encoding.UTF8.GetString(buffer); + stream.Position = 0; + + var content = JsonSerializer.Deserialize(bodyJson); + bodyJson = content.Content; + } + var signData = SignData(bodyJson, client.NewAccessKey); + if (!signData.Equals(sign)) + { + context.Result = new ObjectResult(ApiResult.Failed("签名不合法!", 10001)); + return; + } + else + { + var nowTime = GetTimeStamp(); + var diff = Convert.ToInt32(nowTime) - Convert.ToInt32(timestamps); + if (diff > 1800) + { + context.Result = new ObjectResult(ApiResult.Failed("签名已过期!", 10002)); + } + } + + try + { + var contextAes = DecryptByAES(client, bodyJson); + + var dataContext = Encoding.UTF8.GetBytes(contextAes); + var requestBodyStream = new MemoryStream();//创建一个流 + requestBodyStream.Seek(0, SeekOrigin.Begin);//设置从0开始读取 + requestBodyStream.Write(dataContext, 0, dataContext.Length);//把修改写入流中 + request.Body = requestBodyStream;//把修改后的内容赋值给请求body + request.Body.Seek(0, SeekOrigin.Begin); + request.Body.Position = 0; + } + catch (Exception ex) + { + _logger.LogInformation(ex, "报错解密"); + } + } + + private static string SignData(string ciphertext, string accessKey) + { + Encoding utf = new UTF8Encoding(); + HMACMD5 hmac = new HMACMD5(utf.GetBytes(accessKey)); + byte[] hashValue = hmac.ComputeHash(utf.GetBytes(ciphertext)); + return Convert.ToBase64String(hashValue); + } + + private static string DecryptByAES(ClientKey client, string bodyJson) + { + return DecryptByAES(bodyJson, client.NewAccessKey, client.Vi); + } + + /// + /// AES解密 + /// + /// 密文字节数组 + /// 返回解密后的字符串 + private static string DecryptByAES(string input, string key, string iv) + { + if (string.IsNullOrWhiteSpace(input)) + { + return input; + } + var buffer = Convert.FromBase64String(input); + using Aes aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + aes.FeedbackSize = 128; + aes.Key = Encoding.UTF8.GetBytes(key); + aes.IV = Encoding.UTF8.GetBytes(iv); + ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + using MemoryStream msEncrypt = new(buffer); + using CryptoStream csEncrypt = new(msEncrypt, decryptor, CryptoStreamMode.Read); + using StreamReader srEncrypt = new(csEncrypt); + return srEncrypt.ReadToEnd(); + } + + /// + /// 获取时间戳 + /// + /// + public static string GetTimeStamp() + { + TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds).ToString(); + } + } + + public class ContentDto + { + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/code/DG.Core/TimeSecurity/ApiTimeSecurityAttribute.cs b/code/DG.Core/TimeSecurity/ApiTimeSecurityAttribute.cs new file mode 100644 index 0000000..3ac63b5 --- /dev/null +++ b/code/DG.Core/TimeSecurity/ApiTimeSecurityAttribute.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + /// + /// 安全API + /// + public class ApiTimeSecurityAttribute : Attribute, IFilterMetadata + { + public ApiTimeSecurityAttribute() + { + + } + } +} diff --git a/code/DG.Core/Validation/DecimalValidationAttribute.cs b/code/DG.Core/Validation/DecimalValidationAttribute.cs new file mode 100644 index 0000000..2716441 --- /dev/null +++ b/code/DG.Core/Validation/DecimalValidationAttribute.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class DecimalValidationAttribute : ValidationAttribute + { + public int? Length { get; } + private bool LengthError { get; set; } + + public DecimalValidationAttribute() + { + } + + public DecimalValidationAttribute(int? length) + { + Length = length; + } + + /// + /// IsValid 为 false 时,提示得 error 信息 + /// + /// + /// + public override string FormatErrorMessage(string name) + { + var lengthMessage = LengthError ? $",且长度不能超过{Length}" : ""; + return $"{name}必须输入数字{lengthMessage},请重新输入!"; + } + + /// + /// 验证当前字段得结果 + /// + /// + /// + public override bool IsValid(object? value) + { + if (value == null) return true; + if (decimal.TryParse(Convert.ToString(value), out decimal num)) + { + if (Length != null && num.ToString().Length > Length) + { + LengthError = true; + return false; + } + return true; + } + else + { + return false; + } + } + } +} diff --git a/code/DG.Core/Validation/IntValidationAttribute.cs b/code/DG.Core/Validation/IntValidationAttribute.cs new file mode 100644 index 0000000..9d33c76 --- /dev/null +++ b/code/DG.Core/Validation/IntValidationAttribute.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class IntValidationAttribute : ValidationAttribute + { + public int? Length { get; } + private bool LengthError { get; set; } + + public IntValidationAttribute() + { + } + + public IntValidationAttribute(int? length) + { + Length = length; + } + + /// + /// IsValid 为 false 时,提示得 error 信息 + /// + /// + /// + public override string FormatErrorMessage(string name) + { + var lengthMessage = LengthError ? $",且长度不能超过{Length}" : ""; + return $"{name}必须输入数字{lengthMessage},请重新输入!"; + } + + /// + /// 验证当前字段得结果 + /// + /// + /// + public override bool IsValid(object? value) + { + if (value == null) return true; + if (int.TryParse(Convert.ToString(value), out int num)) + { + if (Length != null && num.ToString().Length > Length) + { + LengthError = true; + return false; + } + return true; + } + else + { + return false; + } + } + } +} diff --git a/code/DG.Core/Validation/LongValidationAttribute.cs b/code/DG.Core/Validation/LongValidationAttribute.cs new file mode 100644 index 0000000..367f549 --- /dev/null +++ b/code/DG.Core/Validation/LongValidationAttribute.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Core +{ + public class LongValidationAttribute : ValidationAttribute + { + public int? Length { get; } + private bool LengthError { get; set; } + + public LongValidationAttribute() + { + } + + public LongValidationAttribute(int? length) + { + Length = length; + } + + /// + /// IsValid 为 false 时,提示得 error 信息 + /// + /// + /// + public override string FormatErrorMessage(string name) + { + var lengthMessage = LengthError ? $",且长度不能超过{Length}" : ""; + return $"{name}必须输入数字{lengthMessage},请重新输入!"; + } + + /// + /// 验证当前字段得结果 + /// + /// + /// + public override bool IsValid(object? value) + { + if (value == null) return true; + if (long.TryParse(Convert.ToString(value), out long num)) + { + if (Length != null && num.ToString().Length > Length) + { + LengthError = true; + return false; + } + return true; + } + else + { + return false; + } + } + } +} diff --git a/code/DG.EntityFramework/DG.EntityFramework.csproj b/code/DG.EntityFramework/DG.EntityFramework.csproj new file mode 100644 index 0000000..8c260d6 --- /dev/null +++ b/code/DG.EntityFramework/DG.EntityFramework.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + True + 1.0.32 + + + + + + + diff --git a/code/DG.EntityFramework/DbContextProvider.cs b/code/DG.EntityFramework/DbContextProvider.cs new file mode 100644 index 0000000..e72d7b2 --- /dev/null +++ b/code/DG.EntityFramework/DbContextProvider.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; + +namespace DG.EntityFramework +{ + public class DbContextProvider : IDbContextProvider + where TDbContext : DbContext + { + private readonly TDbContext _dbContext; + + public DbContextProvider(TDbContext dbContext) + { + _dbContext = dbContext; + } + + public TDbContext GetDbContext() + { + return _dbContext; + } + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/EFCoreOptions.cs b/code/DG.EntityFramework/EFCoreOptions.cs new file mode 100644 index 0000000..342fe23 --- /dev/null +++ b/code/DG.EntityFramework/EFCoreOptions.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Text; + +namespace DG.EntityFramework +{ + /// + /// Entity Framework Core Options + /// + public class EFCoreOptions + where TDbContext : DbContext + { + /// + /// Gets or sets the database's connection string that will be used to store database entities. + /// + public string ConnectionString { get; set; } + + /// + /// + /// + public DbConnection ExistingConnection { get; internal set; } + + /// + /// + /// + public DbContextOptionsBuilder DbContextOptions { get; } + + internal string Version { get; set; } + + public EFCoreOptions() + { + DbContextOptions = new DbContextOptionsBuilder(); + Version = "1.0.0"; + } + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/EFCoreOptionsExtension.cs b/code/DG.EntityFramework/EFCoreOptionsExtension.cs new file mode 100644 index 0000000..0472444 --- /dev/null +++ b/code/DG.EntityFramework/EFCoreOptionsExtension.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace DG.EntityFramework +{ + public class EFCoreOptionsExtension + where TDbContext : DbContext + { + private readonly Action> _configure; + + public EFCoreOptionsExtension(Action> configure) + { + _configure = configure; + } + + public void AddServices(IServiceCollection services) + { + var options = new EFCoreOptions(); + _configure(options); + services.AddDbContext(); + services.Configure(_configure); + services.AddScoped>(); + services.AddScoped>(); + services.AddScoped, DbContextProvider>(); + } + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/EFDbContext.cs b/code/DG.EntityFramework/EFDbContext.cs new file mode 100644 index 0000000..6c61b1f --- /dev/null +++ b/code/DG.EntityFramework/EFDbContext.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace DG.EntityFramework +{ + public class EFDbContext : DbContext + { + public EFDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") + { + var loggerFactory = new LoggerFactory(); + loggerFactory.AddProvider(new EFLoggerProvider()); + optionsBuilder.UseLoggerFactory(loggerFactory); + } + + base.OnConfiguring(optionsBuilder); + } + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/EFLoggerProvider.cs b/code/DG.EntityFramework/EFLoggerProvider.cs new file mode 100644 index 0000000..e0968a3 --- /dev/null +++ b/code/DG.EntityFramework/EFLoggerProvider.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.Logging; + +namespace DG.EntityFramework +{ + public class EFLoggerProvider : ILoggerProvider + { + public ILogger CreateLogger(string categoryName) => new EFLogger(categoryName); + + public void Dispose() + { + } + } + + public class EFLogger : ILogger + { + private readonly string categoryName; + + public EFLogger(string categoryName) => this.categoryName = categoryName; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + //ef core执行数据库查询时的categoryName为Microsoft.EntityFrameworkCore.Database.Command,日志级别为Information + if (categoryName != "Microsoft.EntityFrameworkCore.Database.Command" || + logLevel != LogLevel.Information) return; + var logContent = formatter(state, exception); + Console.WriteLine("<------------------ sql start ------------------>"); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("sql: "); + Console.ResetColor(); + Console.Write(logContent); + Console.ResetColor(); + Console.WriteLine(); + Console.WriteLine("<------------------ sql end ------------------>"); + } + + public IDisposable BeginScope(TState state) => null; + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/IDbContextProvider.cs b/code/DG.EntityFramework/IDbContextProvider.cs new file mode 100644 index 0000000..6503b12 --- /dev/null +++ b/code/DG.EntityFramework/IDbContextProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; + +namespace DG.EntityFramework +{ + /// + /// + /// + /// + public interface IDbContextProvider + where TDbContext : DbContext + { + /// + /// + /// + /// + TDbContext GetDbContext(); + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/Repository/BaseRepository.cs b/code/DG.EntityFramework/Repository/BaseRepository.cs new file mode 100644 index 0000000..e3371f9 --- /dev/null +++ b/code/DG.EntityFramework/Repository/BaseRepository.cs @@ -0,0 +1,394 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; +using System.Data; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; + + +namespace DG.EntityFramework +{ + public class BaseRepository : IBaseRepository + where TDbContext : DbContext + { + private readonly IServiceProvider _serviceProvider; + private readonly TDbContext _context; + public BaseRepository( + IServiceProvider serviceProvider, + TDbContext dbContext + ) + { + _serviceProvider = serviceProvider; + _context = dbContext; + } + public IRepositoryBase GetRepository() + where TEntity : class + => _serviceProvider.GetRequiredService>(); + + + public async Task BeginTransactionAsync() + => await _context.Database.BeginTransactionAsync(); + + /// + /// 创建SqlCommand + /// + /// + /// + /// + /// + private async Task CreateCommand(string sql, params object[] parameters) + { + var conn = _context.Database.GetDbConnection(); + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(); + } + var cmd = conn.CreateCommand(); + cmd.CommandText = sql; + cmd.Parameters.AddRange(parameters); + return cmd; + } + + /// + /// 执行存储过程的扩展方法ExecuteSqlCommand + /// + /// + /// + /// + /// + public async Task ExecuteSqlCommandNonQueryAsync(CommandType commandType, string sql, [NotNull] params object[] parameters) + { + //创建SqlCommand + var command = await CreateCommand(sql, parameters); + var conn = _context.Database.GetDbConnection(); + var transaction = _context.Database.CurrentTransaction?.GetDbTransaction(); + + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(); + } + _context.Database.UseTransaction(transaction); + command.CommandType = commandType; + if (transaction != null) + { + command.Transaction = transaction; + } + return await command.ExecuteNonQueryAsync(); + } + + public async Task> ExecuteSqlCommandAsync(CommandType commandType, string sql, [NotNull] params object[] parameters) + where T : class, new() + { + //创建SqlCommand + var command = await CreateCommand(sql, parameters); + var conn = _context.Database.GetDbConnection(); + var transaction = _context.Database.CurrentTransaction?.GetDbTransaction(); + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(); + } + _context.Database.UseTransaction(transaction); + command.CommandType = commandType; + if (transaction != null) + { + command.Transaction = transaction; + } + var reader = await command.ExecuteReaderAsync(); + List list = new(); + Type type = typeof(T); + if (reader.HasRows) + { + + while (reader.Read()) + { + var note = Activator.CreateInstance(type); + var columns = reader.GetColumnSchema(); + foreach (var item in type.GetProperties()) + { + if (!columns.Any(x => x.ColumnName.ToLower() == item.Name.ToLower())) continue; + var value = reader[item.Name]; + if (!item.CanWrite || value is DBNull || value == DBNull.Value) continue; + try + { + #region SetValue + switch (item.PropertyType.ToString()) + { + case "System.String": + item.SetValue(note, Convert.ToString(value), null); + break; + case "System.Int32": + item.SetValue(note, Convert.ToInt32(value), null); + break; + case "System.Int64": + item.SetValue(note, Convert.ToInt64(value), null); + break; + case "System.DateTime": + item.SetValue(note, Convert.ToDateTime(value), null); + break; + case "System.Boolean": + item.SetValue(note, Convert.ToBoolean(value), null); + break; + case "System.Double": + item.SetValue(note, Convert.ToDouble(value), null); + break; + case "System.Decimal": + item.SetValue(note, Convert.ToDecimal(value), null); + break; + default: + item.SetValue(note, value, null); + break; + } + #endregion + } + catch + { + //throw (new Exception(ex.Message)); + } + } + + list.Add(note as T); + } + await reader.CloseAsync(); + await conn.CloseAsync(); + return list; + } + return list; + } + + public async Task> ExecuteSqlToListAsync(string sql, [MaybeNull] params object[] parameters) + where T : class, new() + { + //创建SqlCommand + var command = await CreateCommand(sql, parameters); + var conn = _context.Database.GetDbConnection(); + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(); + } + if (conn.State == ConnectionState.Open) + { + var reader = await command.ExecuteReaderAsync(); + List list = new(); + Type type = typeof(T); + if (reader.HasRows) + { + + while (reader.Read()) + { + var note = Activator.CreateInstance(type); + var columns = reader.GetColumnSchema(); + foreach (var item in type.GetProperties()) + { + if (!columns.Any(x => x.ColumnName.ToLower() == item.Name.ToLower())) continue; + var value = reader[item.Name]; + if (!item.CanWrite || value is DBNull || value == DBNull.Value) continue; + try + { + #region SetValue + switch (item.PropertyType.ToString()) + { + case "System.String": + item.SetValue(note, Convert.ToString(value), null); + break; + case "System.Int32": + case "System.Nullable`1[System.Int32]": + item.SetValue(note, Convert.ToInt32(value), null); + break; + case "System.Int64": + case "System.Nullable`1[System.Int64]": + item.SetValue(note, Convert.ToInt64(value), null); + break; + case "System.DateTime": + case "System.Nullable`1[System.DateTime]": + item.SetValue(note, Convert.ToDateTime(value), null); + break; + case "System.Boolean": + case "System.Nullable`1[System.Boolean]": + item.SetValue(note, Convert.ToBoolean(value), null); + break; + case "System.Double": + case "System.Nullable`1[System.Double]": + item.SetValue(note, Convert.ToDouble(value), null); + break; + case "System.Decimal": + case "System.Nullable`1[System.Decimal]": + item.SetValue(note, Convert.ToDecimal(value), null); + break; + default: + item.SetValue(note, value, null); + break; + } + #endregion + } + catch + { + //throw (new Exception(ex.Message)); + } + } + + list.Add(note as T); + } + await reader.CloseAsync(); + await conn.CloseAsync(); + return list; + } + return list; + + } + return new List(); + } + + public async Task ExecuteSqlToEntityAsync(string sql, [MaybeNull] params object[] parameters) + where T : class, new() + { + //创建SqlCommand + var command = await CreateCommand(sql, parameters); + var conn = _context.Database.GetDbConnection(); + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(); + } + if (conn.State == ConnectionState.Open) + { + var reader = await command.ExecuteReaderAsync(); + T entity = new(); + Type type = typeof(T); + if (reader.HasRows) + { + + while (reader.Read()) + { + var note = Activator.CreateInstance(type); + var columns = reader.GetColumnSchema(); + foreach (var item in type.GetProperties()) + { + if (!columns.Any(x => x.ColumnName.ToLower() == item.Name.ToLower())) continue; + var value = reader[item.Name]; + if (!item.CanWrite || value is DBNull || value == DBNull.Value) continue; + try + { + #region SetValue + switch (item.PropertyType.ToString()) + { + case "System.String": + item.SetValue(note, Convert.ToString(value), null); + break; + case "System.Int32": + case "System.Nullable`1[System.Int32]": + item.SetValue(note, Convert.ToInt32(value), null); + break; + case "System.Int64": + case "System.Nullable`1[System.Int64]": + item.SetValue(note, Convert.ToInt64(value), null); + break; + case "System.DateTime": + case "System.Nullable`1[System.DateTime]": + item.SetValue(note, Convert.ToDateTime(value), null); + break; + case "System.Boolean": + case "System.Nullable`1[System.Boolean]": + item.SetValue(note, Convert.ToBoolean(value), null); + break; + case "System.Double": + case "System.Nullable`1[System.Double]": + item.SetValue(note, Convert.ToDouble(value), null); + break; + case "System.Decimal": + case "System.Nullable`1[System.Decimal]": + item.SetValue(note, Convert.ToDecimal(value), null); + break; + default: + item.SetValue(note, value, null); + break; + } + #endregion + } + catch + { + //throw (new Exception(ex.Message)); + } + } + + entity = note as T; + } + await reader.CloseAsync(); + await conn.CloseAsync(); + return entity; + } + return entity; + + } + return new T(); + } + + public async Task ExecuteSqlToCountAsync(string sql, [MaybeNull] params object[] parameters) + { + //创建SqlCommand + var command = await CreateCommand(sql, parameters); + var conn = _context.Database.GetDbConnection(); + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(); + } + if (conn.State == ConnectionState.Open) + { + var reader = await command.ExecuteReaderAsync(); + if (reader.HasRows) + { + + while (reader.Read()) + { + var columns = reader.GetColumnSchema(); + var value = reader[0]; + if (value is int) + { + return (int)value; + } + } + await reader.CloseAsync(); + await conn.CloseAsync(); + return 0; + } + return 0; + + } + return 0; + } + + public async Task ExecuteSqlToCountLongAsync(string sql, [MaybeNull] params object[] parameters) + { + //创建SqlCommand + var command = await CreateCommand(sql, parameters); + var conn = _context.Database.GetDbConnection(); + var result = 0L; + if (conn.State != ConnectionState.Open) + { + await conn.OpenAsync(); + } + if (conn.State == ConnectionState.Open) + { + var reader = await command.ExecuteReaderAsync(); + if (reader.HasRows) + { + + while (reader.Read()) + { + var value = reader[0]; + if (value is long) + { + result = (long)value; + } + } + } + await reader.CloseAsync(); + await conn.CloseAsync(); + } + return result; + } + + public async Task BeginTransactionAsync(IsolationLevel isolationLevel) + => await _context.Database.BeginTransactionAsync(isolationLevel); + + + } +} diff --git a/code/DG.EntityFramework/Repository/IBaseRepository.cs b/code/DG.EntityFramework/Repository/IBaseRepository.cs new file mode 100644 index 0000000..e3edbc6 --- /dev/null +++ b/code/DG.EntityFramework/Repository/IBaseRepository.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.EntityFramework +{ + public interface IBaseRepository + where TDbContext : DbContext + { + IRepositoryBase GetRepository() + where TEntity : class; + + /// + /// 执行存储过程的扩展方法ExecuteSqlCommand + /// + /// + /// + /// + /// + Task ExecuteSqlCommandNonQueryAsync(CommandType commandType, string sql, [NotNull] params object[] parameters); + + /// + /// 执行存储过程的扩展方法ExecuteSqlCommand + /// + /// + /// + /// + /// + /// + Task> ExecuteSqlCommandAsync(CommandType commandType, string sql, [NotNull] params object[] parameters) + where T : class, new(); + + /// + /// 开启事务 + /// + /// + Task BeginTransactionAsync(); + + + /// + /// + /// + /// + /// + Task BeginTransactionAsync(IsolationLevel isolationLevel); + + Task> ExecuteSqlToListAsync(string sql, [MaybeNull] params object[] parameters) + where T : class, new(); + + Task ExecuteSqlToEntityAsync(string sql, [MaybeNull] params object[] parameters) + where T : class, new(); + + Task ExecuteSqlToCountAsync(string sql, [MaybeNull] params object[] parameters); + + Task ExecuteSqlToCountLongAsync(string sql, [MaybeNull] params object[] parameters); + } +} diff --git a/code/DG.EntityFramework/Repository/IRepositoryBase.cs b/code/DG.EntityFramework/Repository/IRepositoryBase.cs new file mode 100644 index 0000000..de8025f --- /dev/null +++ b/code/DG.EntityFramework/Repository/IRepositoryBase.cs @@ -0,0 +1,127 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace DG.EntityFramework +{ + public interface IRepositoryBase + where TDbContext : DbContext + where TEntity : class + { + #region Qurey + + /// + /// Used to get a IQueryable that is used to retrieve entities from entire table. + /// + /// IQueryable to be used to select entities from database + IQueryable Query(); + + /// + /// Used to get a IQueryable that is used to retrieve entities from entire table. + /// + /// + /// IQueryable to be used to select entities from database + IQueryable QueryIncluding(params Expression>[] propertySelectors); + + /// + /// Used to query a array of entities from datatable + /// + /// Array of entities + Task QueryArrayAsync(); + + /// + /// Used to query a array of entities from datatable by predicate + /// + /// + /// Array of entities + //Task QueryArrayAsync(Expression> predicate); + + /// + /// Used to query a list of entities from datatable + /// + /// List of entities + Task> QueryListAsync(); + + /// + /// Used to query a list of entities from datatable by predicate + /// + /// + /// List of entities + //Task> QueryListAsync(Expression> predicate); + + /// + /// Used to query a single entity from datatable by predicate + /// + /// + /// Entity + Task SingleAsync(Expression> predicate = null); + + /// + /// Gets an entity with given given predicate or null if not found. + /// + /// Predicate to filter entities + Task FirstOrDefaultAsync(Expression> predicate = null); + + #endregion Qurey + + #region Insert + + /// + /// Insert a new entity + /// + /// + /// + /// Inserted entity + Task InsertAsync(TEntity entity, bool submit = true); + + Task> BatchInsertAsync(List entities, bool submit = true); + + #endregion Insert + + #region Update + + /// + /// Updates an existing entity + /// + /// + /// + /// + Task UpdateAsync(TEntity entity, bool submit = true); + + Task UpdateAsync(TEntity entity, Expression> expression, bool submit = true); + + Task BatchUpdateAsync(List entities, bool submit = true); + + Task BatchUpdateAsync(List entities, Expression> expression, bool submit = true); + #endregion Update + + #region Delete + + /// + /// Deletes an entity + /// + /// Entity + /// + /// Entity to be deleted + Task DeleteAsync(TEntity entity, bool submit = true); + + Task BatchDeleteAsync(List entities, bool submit = true); + + #endregion Delete + + #region Expression + + TDbContext Context { get; } + + DbSet Table { get; } + + void Attach(TEntity entity); + + #endregion Expression + } +} diff --git a/code/DG.EntityFramework/Repository/RepositoryBase.cs b/code/DG.EntityFramework/Repository/RepositoryBase.cs new file mode 100644 index 0000000..2c9077b --- /dev/null +++ b/code/DG.EntityFramework/Repository/RepositoryBase.cs @@ -0,0 +1,298 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace DG.EntityFramework +{ + public class RepositoryBase : IRepositoryBase + where TDbContext : DbContext + where TEntity : class + { + /// + /// + /// + public TDbContext Context { get; } + + /// + /// + /// + public DbSet Table => Context.Set(); + + /// + /// + /// + /// + public RepositoryBase(TDbContext context) + { + Context = context; + } + + #region Qurey + + /// + /// Used to get a IQueryable that is used to retrieve entities from entire table. + /// + /// IQueryable to be used to select entities from database + public IQueryable Query() + { + if (!(Context.Set() is IQueryable query)) + throw new Exception($"{typeof(TEntity)} TEntity cannot be empty!"); + return query; + } + + /// + /// Used to get a IQueryable that is used to retrieve entities from entire table. + /// + /// + /// IQueryable to be used to select entities from database + public IQueryable QueryIncluding(params Expression>[] propertySelectors) + { + if (propertySelectors == null || !propertySelectors.Any()) + { + return Query(); + } + + var query = Query(); + + return propertySelectors.Aggregate(query, (current, propertySelector) => current.Include(propertySelector)); + } + + /// + /// Used to query a array of entities from data table + /// + /// Array of entities + public async Task QueryArrayAsync() + { + return await Query().ToArrayAsync(); + } + + /// + /// Used to query a array of entities from data table by predicate + /// + /// + /// Array of entities + public async Task QueryArrayAsync(Expression> predicate) + { + return await Query().Where(predicate).ToArrayAsync(); + } + + /// + /// Used to query a list of entities from data table + /// + /// List of entities + public async Task> QueryListAsync() + { + return await Query().ToListAsync(); + } + + /// + /// Used to query a list of entities from data table by predicate + /// + /// + /// List of entities + public async Task> QueryListAsync(Expression> predicate = null) + { + return await Query().Where(predicate).ToListAsync(); + } + + /// + /// Used to query a single entity from datatable by predicate + /// + /// + /// Entity + public async Task SingleAsync(Expression> predicate = null) + { + if (predicate == null) + { + return await Query().SingleAsync(); + } + return await Query().SingleAsync(predicate); + } + + /// + /// Gets an entity with given given predicate or null if not found. + /// + /// Predicate to filter entities + public async Task FirstOrDefaultAsync(Expression> predicate = null) + { + if (predicate == null) + { + return await Query().FirstOrDefaultAsync(); + } + return await Query().FirstOrDefaultAsync(predicate); + } + + #endregion Qurey + + #region Insert + + /// + /// Insert a new entity + /// + /// + /// + /// Inserted entity + public virtual async Task InsertAsync(TEntity entity, bool submit = true) + { + AttachIfNot(entity); + Context.Entry(entity).State = EntityState.Added; + if (submit) + { + await Context.SaveChangesAsync(); + } + + return entity; + } + + public virtual async Task> BatchInsertAsync(List entities, bool submit = true) + { + foreach (var entity in entities) + { + AttachIfNot(entity); + Context.Entry(entity).State = EntityState.Added; + } + if (submit) + { + await Context.SaveChangesAsync(); + } + + return entities; + } + + #endregion Insert + + #region Update + + /// + /// Updates an existing entity + /// + /// + /// + /// + public virtual async Task UpdateAsync(TEntity entity, bool submit = true) + { + Attach(entity); + Context.Entry(entity).State = EntityState.Modified; + if (submit) + { + await Context.SaveChangesAsync(); + } + return await Task.FromResult(entity); + } + + public virtual async Task UpdateAsync(TEntity entity, Expression> expression, bool submit = true) + { + Attach(entity); + var entry = Context.Entry(entity); + //entry.State = EntityState.Unchanged; + foreach (var proInfo in expression.GetPropertyAccessList()) + { + if (!string.IsNullOrEmpty(proInfo.Name)) + entry.Property(proInfo.Name).IsModified = true; + } + if (submit) + { + await Context.SaveChangesAsync(); + } + return await Task.FromResult(entity); + } + + public virtual async Task BatchUpdateAsync(List entities, bool submit = true) + { + foreach(var entity in entities) + { + Attach(entity); + Context.Entry(entity).State = EntityState.Modified; + } + if (submit) + { + await Context.SaveChangesAsync(); + } + } + + public virtual async Task BatchUpdateAsync(List entities, Expression> expression, bool submit = true) + { + foreach (var entity in entities) + { + Attach(entity); + var entry = Context.Entry(entity); + //entry.State = EntityState.Unchanged; + foreach (var proInfo in expression.GetPropertyAccessList()) + { + if (!string.IsNullOrEmpty(proInfo.Name)) + //4.4将每个 被修改的属性的状态 设置为已修改状态;后面生成update语句时,就只为已修改的属性 更新 + entry.Property(proInfo.Name).IsModified = true; + } + } + if (submit) + { + await Context.SaveChangesAsync(); + } + } + + #endregion Update + + #region Delete + + /// + /// Deletes an entity + /// + /// Entity + /// + /// Entity to be deleted + public virtual async Task DeleteAsync(TEntity entity, bool submit = true) + { + AttachIfNot(entity); + await Task.FromResult(Table.Remove(entity)); + if (submit) + { + await Context.SaveChangesAsync(); + } + } + + public virtual async Task BatchDeleteAsync(List entities, bool submit = true) + { + foreach (var entity in entities) + { + AttachIfNot(entity); + Context.Entry(entity).State = EntityState.Deleted; + } + Table.RemoveRange(entities); + if (submit) + { + await Context.SaveChangesAsync(); + } + } + #endregion Delete + + #region Expression + + /// + /// + /// + /// + public void AttachIfNot(TEntity entity) + { + if (!Table.Local.Contains(entity)) + { + Table.Attach(entity); + } + } + + public void Attach(TEntity entity) + { + Table.Attach(entity); + } + #endregion Expression + } +} diff --git a/code/DG.EntityFramework/ServiceCollectionExtensions.cs b/code/DG.EntityFramework/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..86068f0 --- /dev/null +++ b/code/DG.EntityFramework/ServiceCollectionExtensions.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace DG.EntityFramework +{ + /// + /// Extensions method + /// + public static class ServiceCollectionExtensions + { + /// + /// Add EntityFramework + /// + /// + /// + /// + /// + /// + public static IServiceCollection AddDGEntityFramework(this IServiceCollection services, + Action options) + where TDbContext : DbContext + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + services.AddDbContext(options, ServiceLifetime.Scoped, ServiceLifetime.Scoped); + services.AddScoped>(); + services.AddScoped, DbContextProvider>(); + services.AddScoped(typeof(IRepositoryBase<,>), typeof(RepositoryBase<,>)); + services.AddScoped(typeof(IBaseRepository<>), typeof(BaseRepository<>)); + return services; + } + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/UnitOfWork/IUnitOfWorkCompleteHandle.cs b/code/DG.EntityFramework/UnitOfWork/IUnitOfWorkCompleteHandle.cs new file mode 100644 index 0000000..76416c0 --- /dev/null +++ b/code/DG.EntityFramework/UnitOfWork/IUnitOfWorkCompleteHandle.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.EntityFramework +{ + /// + /// Used to complete a unit of work. This interface can not be injected or directly used. + /// + public interface IUnitOfWorkCompleteHandle : IDisposable + { + /// + /// Completes this unit of work. It saves all changes and commit transaction if exists. + /// + void Complete(); + + /// + /// Completes this unit of work. It saves all changes and commit transaction if exists. + /// + /// + Task CompleteAsync(); + + /// + /// + /// + void Rollback(); + + /// + /// + /// + /// + Task RollbackAsync(); + } +} diff --git a/code/DG.EntityFramework/UnitOfWork/IUnitOfWorkManager.cs b/code/DG.EntityFramework/UnitOfWork/IUnitOfWorkManager.cs new file mode 100644 index 0000000..58870a9 --- /dev/null +++ b/code/DG.EntityFramework/UnitOfWork/IUnitOfWorkManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace DG.EntityFramework +{ + /// + /// Unit of work manager. Used to begin and control a unit of work. + /// + public interface IUnitOfWorkManager + { + /// + /// Begins a new unit of work. + /// + /// A handle to be able to complete the unit of work + IUnitOfWorkCompleteHandle Begin(); + + /// + /// Begins a new unit of work. + /// + /// A handle to be able to complete the unit of work + IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope); + + /// + /// Begins a new unit of work. + /// + /// A handle to be able to complete the unit of work + IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options); + } +} diff --git a/code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteHandle.cs b/code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteHandle.cs new file mode 100644 index 0000000..951fefc --- /dev/null +++ b/code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteHandle.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace DG.EntityFramework +{ + public class UnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle + where TDbContext : DbContext + { + private readonly IDbContextTransaction _dbContextTransaction; + + public UnitOfWorkCompleteHandle(IDbContextTransaction dbContextTransaction) + { + _dbContextTransaction = dbContextTransaction ?? throw new ArgumentNullException(nameof(dbContextTransaction)); + } + + public void Complete() + { + _dbContextTransaction.Commit(); + } + + public async Task CompleteAsync() + { + await _dbContextTransaction.CommitAsync(); + } + + public void Rollback() + { + _dbContextTransaction.Rollback(); + } + + public async Task RollbackAsync() + { + await _dbContextTransaction.RollbackAsync(); + } + + public void Dispose() + { + _dbContextTransaction.Dispose(); + } + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteScopeHandle.cs b/code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteScopeHandle.cs new file mode 100644 index 0000000..1b5cf46 --- /dev/null +++ b/code/DG.EntityFramework/UnitOfWork/UnitOfWorkCompleteScopeHandle.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace DG.EntityFramework +{ + public class UnitOfWorkCompleteScopeHandle : IUnitOfWorkCompleteHandle + where TDbContext : DbContext + { + private readonly TransactionScope _transactionScope; + + public UnitOfWorkCompleteScopeHandle(TransactionScope transactionScope) + { + _transactionScope = transactionScope ?? throw new ArgumentNullException(nameof(transactionScope)); + } + + public void Complete() + { + _transactionScope.Complete(); + } + + public async Task CompleteAsync() + { + await Task.Run(() => _transactionScope.Complete()); + } + + public void Rollback() + { + _transactionScope.Dispose(); + } + + public async Task RollbackAsync() + { + await Task.Run(() => _transactionScope.Dispose()); + } + + public void Dispose() + { + _transactionScope.Dispose(); + } + } +} diff --git a/code/DG.EntityFramework/UnitOfWork/UnitOfWorkManager.cs b/code/DG.EntityFramework/UnitOfWork/UnitOfWorkManager.cs new file mode 100644 index 0000000..036f7e5 --- /dev/null +++ b/code/DG.EntityFramework/UnitOfWork/UnitOfWorkManager.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Threading.Tasks; +using System.Transactions; + +namespace DG.EntityFramework +{ + public class UnitOfWorkManager : IUnitOfWorkManager + where TDbContext : DbContext + { + private readonly TDbContext _dbContext; + + public UnitOfWorkManager(TDbContext dbContext) + { + _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); + } + + public IUnitOfWorkCompleteHandle Begin() + { + var tran = _dbContext.Database.BeginTransaction(); + var handle = new UnitOfWorkCompleteHandle(tran); + return handle; + } + + public IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope) + { + return Begin(new UnitOfWorkOptions { Scope = scope }); + } + + public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options) + { + var scope = new TransactionScope( + options.Scope.GetValueOrDefault(), + new TransactionOptions { + Timeout = options.Timeout.GetValueOrDefault(), + IsolationLevel = options.IsolationLevel.GetValueOrDefault() + }); + var handle = new UnitOfWorkCompleteScopeHandle(scope); + return handle; + } + } +} \ No newline at end of file diff --git a/code/DG.EntityFramework/UnitOfWorkOptions.cs b/code/DG.EntityFramework/UnitOfWorkOptions.cs new file mode 100644 index 0000000..201e583 --- /dev/null +++ b/code/DG.EntityFramework/UnitOfWorkOptions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Transactions; + +namespace DG.EntityFramework +{ + /// + /// Unit of work options. + /// + public class UnitOfWorkOptions + { + /// + /// Creates a new UnitOfWorkOptions object. + /// + public UnitOfWorkOptions() + { + IsTransactional = true; + Scope = TransactionScopeOption.Required; + Timeout = TimeSpan.FromMinutes(2); + IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; + } + + /// + /// Scope option. + /// + public TransactionScopeOption? Scope { get; set; } + + /// + /// Is this Unit of work transactional? Uses default value if not supplied. + /// + public bool? IsTransactional { get; set; } + + /// + /// Timeout of Unit of work As milliseconds. Uses default value if not supplied. + /// + public TimeSpan? Timeout { get; set; } + + /// + /// If this Unit of work is transactional, this option indicated the isolation level of the transaction. Uses default value if not supplied. + /// + public IsolationLevel? IsolationLevel { get; set; } + + /// + /// This option should be set to System.Transactions.TransactionScopeAsyncFlowOption.Enabled if unit of work is used in an async scope. + /// + public TransactionScopeAsyncFlowOption? AsyncFlowOption { get; set; } + } +} diff --git a/code/DG.Redis/DG.Redis.csproj b/code/DG.Redis/DG.Redis.csproj new file mode 100644 index 0000000..ddb7a89 --- /dev/null +++ b/code/DG.Redis/DG.Redis.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + True + 1.0.17 + + + + + + + + + + + + + diff --git a/code/DG.Redis/Manager/IRedisManager.cs b/code/DG.Redis/Manager/IRedisManager.cs new file mode 100644 index 0000000..056bd82 --- /dev/null +++ b/code/DG.Redis/Manager/IRedisManager.cs @@ -0,0 +1,298 @@ +namespace DG.Redis +{ + /// + /// Redis manage interface + /// + public interface IRedisManager + { + /// + /// Redis connection + /// + /// + void RedisConnection(string name, string connStr, int DefaultDatabase); + + /// + /// Get database + /// + /// + IDatabase GetDatabase(string name = ""); + + /// + /// Add a new entity + /// + /// Entity + /// Key + /// Entity + /// Cache time + /// + bool Set(string key, TEntity entity, TimeSpan? cacheTime = null, string name = ""); + + /// + /// Add a new array + /// + /// Entity + /// Key + /// An array + /// Cache time + /// + bool Set(string key, TEntity[] entities, TimeSpan? cacheTime = null, string name = ""); + + /// + /// Add a new list + /// + /// Entity + /// Key + /// An list + /// Cache time + /// + bool Set(string key, List entities, TimeSpan? cacheTime = null, string name = ""); + + /// + /// Add a new entity + /// + /// Entity + /// Key + /// Entity + /// Cache time + /// + Task SetAsync(string key, TEntity entity, TimeSpan? cacheTime = null, string name = ""); + + /// + /// Add a new array + /// + /// Entity + /// Key + /// An array + /// Cache time + /// + Task SetAsync(string key, TEntity[] entities, TimeSpan? cacheTime = null, string name = ""); + + /// + /// Add a new list + /// + /// entity + /// key + /// a list + /// cache time + /// + Task SetAsync(string key, List entities, TimeSpan? cacheTime = null, string name = ""); + + /// + /// Get list tatal + /// + /// Key + /// + long Count(string key, string name = ""); + + /// + /// Get list tatal + /// + /// Key + /// + Task CountAsync(string key, string name = ""); + + /// + /// 判断在缓存中是否存在该key的缓存数据 + /// + /// + /// + bool Exists(string key, string name = ""); + + /// + /// 判断是否存在 + /// + /// + /// + Task ExistsAsync(string key, string name = ""); + + /// + /// Set cache time + /// + /// Key + /// Cache time + /// + bool Expire(string key, TimeSpan cacheTime, string name = ""); + + /// + /// Set cache time + /// + /// Key + /// Cache time + /// + /// + Task ExpireAsync(string key, TimeSpan cacheTime, string name = ""); + + /// + /// Remove by key + /// + /// Key + /// + bool Remove(string key, string name = ""); + + /// + /// Remove by key array + /// + /// Key array + /// + bool Remove(string[] keys, string name = ""); + + /// + /// Remove by key list + /// + /// Key list + /// + bool Remove(List keys, string name = ""); + + /// + /// Remove by key + /// + /// Key + /// + Task RemoveAsync(string key, string name = ""); + + /// + /// Remove by key array + /// + /// Key array + /// + Task RemoveAsync(string[] keys, string name = ""); + + /// + /// Remove by key list + /// + /// Key list + /// + Task RemoveAsync(List keys, string name = ""); + + /// + /// Blocking Dequeue + /// + /// + /// + TEntity BlockingDequeue(string key, string name = ""); + + /// + /// Blocking Dequeue + /// + /// + /// + Task BlockingDequeueAsync(string key, string name = ""); + + /// + /// Dequeue entity + /// + /// + /// + /// + TEntity Dequeue(string key, string name = ""); + + /// + /// Dequeue entity + /// + /// + /// + /// + Task DequeueAsync(string key, string name = ""); + + /// + /// Enqueue entity + /// + /// + /// + void Enqueue(string key, TEntity entity, string name = ""); + + /// + /// Enqueue entity + /// + /// + /// + Task EnqueueAsync(string key, TEntity entity, string name = ""); + + /// + /// Increment + /// + /// + /// + /// 三种命令模式 + /// Sync,同步模式会直接阻塞调用者,但是显然不会阻塞其他线程。 + /// Async,异步模式直接走的是Task模型。 + /// Fire - and - Forget,就是发送命令,然后完全不关心最终什么时候完成命令操作。 + /// 即发即弃:通过配置 CommandFlags 来实现即发即弃功能,在该实例中该方法会立即返回,如果是string则返回null 如果是int则返回0.这个操作将会继续在后台运行,一个典型的用法页面计数器的实现: + /// + /// + long Increment(string key, string name = ""); + + /// + /// Increment + /// + /// + /// + Task IncrementAsync(string key, string name = ""); + + /// + /// Decrement + /// + /// + /// + /// + long Decrement(string key, string value, string name = ""); + + /// + /// Decrement + /// + /// + /// + /// + Task DecrementAsync(string key, string value, string name = ""); + + /// + /// Get an entity + /// + /// + /// + /// + TEntity Get(string key, string name = ""); + + /// + /// Get an list + /// + /// + /// + /// + List GetList(string key, string name = ""); + + /// + /// Get an array + /// + /// + /// + /// + TEntity[] GetArray(string key, string name = ""); + + /// + /// Get an entity + /// + /// + /// + /// + Task GetAsync(string key, string name = ""); + + Task GetHashAsync(string key, string name = ""); + + /// + /// Get an list + /// + /// + /// + /// + Task> GetListAsync(string key, string name = ""); + + /// + /// Get an array + /// + /// + /// + /// + Task GetArrayAsync(string key, string name = ""); + } +} diff --git a/code/DG.Redis/Manager/RedisManager.cs b/code/DG.Redis/Manager/RedisManager.cs new file mode 100644 index 0000000..c4baef1 --- /dev/null +++ b/code/DG.Redis/Manager/RedisManager.cs @@ -0,0 +1,375 @@ +using System.Text.Json; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Text.Encodings.Web; +using Microsoft.Extensions.Configuration; +using System.Xml.Linq; +using System.Reflection; + +namespace DG.Redis +{ + public class RedisManager : IRedisManager + { + private readonly ILogger _logger; + private List<(string, string, int, ConnectionMultiplexer)> _connQueue = new(); + + public static JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + public RedisManager( + ILogger logger) + { + _logger = logger; + var redisOptions = RedisClient.Default.ConfigurationManager.GetSection("Redis").Get>(); + foreach (var option in redisOptions) + { + var connStr = string.Format("{0}:{1},allowAdmin=true,abortConnect=false,password={2},defaultdatabase={3}", + option.HostName, + option.Port, + option.Password, + option.DefaultDatabase + ); + var options = ConfigurationOptions.Parse(connStr); + RedisConnection(option.Name, connStr, option.DefaultDatabase); + } + } + + public void RedisConnection(string name, string connStr, int DefaultDatabase) + { + try + { + _logger.LogDebug($"Redis config: {connStr}"); + _connQueue.Add((name, connStr, DefaultDatabase, ConnectionMultiplexer.Connect(connStr))); + _logger.LogInformation($"Redis name: [{name}] manager started!"); + } + catch (Exception ex) + { + _logger.LogError($"Redis name: [{name}] connection error: {ex.Message}"); + throw; + } + } + + public IDatabase GetDatabase(string name = "") + { + try + { + var con = _connQueue.FirstOrDefault(); + if (!string.IsNullOrEmpty(name)) + { + con = _connQueue.FirstOrDefault(x => x.Item1 == name); + return con.Item4.GetDatabase(con.Item3); + } + return con.Item4.GetDatabase(con.Item3); + } + catch + { + var con = _connQueue.FirstOrDefault(x => x.Item1 == name); + var conn = ConnectionMultiplexer.Connect(con.Item2); + _logger.LogInformation("Redis manager reconnection!"); + return conn.GetDatabase(con.Item3); + } + } + + public bool Set(string key, TEntity entity, TimeSpan? cacheTime = null, string name = "") + { + if (Exists(key, name)) + { + Remove(key, name); + } + var result = GetDatabase(name).StringSet(key, JsonSerializer.Serialize(entity, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + })); + if (cacheTime != null) + { + return result && Expire(key, cacheTime.Value, name); + } + return result; + } + + public bool Set(string key, TEntity[] entities, TimeSpan? cacheTime = null, string name = "") + { + if (Exists(key, name)) + { + Remove(key, name); + } + var redisValues = entities.Select(p => (RedisValue)(JsonSerializer.Serialize(p, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }))).ToArray(); + var result = GetDatabase(name).SetAdd(key, redisValues) == redisValues.Length; + if (cacheTime != null) + { + return result && Expire(key, cacheTime.Value, name); + } + return result; + } + + public bool Set(string key, List entities, TimeSpan? cacheTime = null, string name = "") + { + if (Exists(key, name)) + { + Remove(key, name); + } + return Set(key, entities.ToArray(), cacheTime, name); + } + + public async Task SetAsync(string key, TEntity entity, TimeSpan? cacheTime = null, string name = "") + { + if (await ExistsAsync(key, name)) + { + await RemoveAsync(key, name); + } + var result = await GetDatabase(name).StringSetAsync(key, JsonSerializer.Serialize(entity, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + })); + if (cacheTime != null) + { + return result && await ExpireAsync(key, cacheTime.Value, name); + } + return result; + } + + public async Task SetAsync(string key, TEntity[] entities, TimeSpan? cacheTime = null, string name = "") + { + if (await ExistsAsync(key, name)) + { + await RemoveAsync(key, name); + } + var redisValues = entities.Select(p => (RedisValue)(JsonSerializer.Serialize(p, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }))).ToArray(); + var result = await GetDatabase(name).SetAddAsync(key, redisValues) == redisValues.Length; + if (cacheTime != null) + { + return result && await ExpireAsync(key, cacheTime.Value, name); + } + return result; + } + + public async Task SetAsync(string key, List entities, TimeSpan? cacheTime = null, string name = "") + { + if (await ExistsAsync(key, name)) + { + await RemoveAsync(key, name); + } + return await SetAsync(key, entities.ToArray(), cacheTime, name); + } + + public long Count(string key, string name = "") + { + return GetDatabase(name).ListLength(key); + } + + public async Task CountAsync(string key, string name = "") + { + return await GetDatabase(name).ListLengthAsync(key); + } + + public bool Exists(string key, string name = "") + { + return GetDatabase(name).KeyExists(key); + } + + public async Task ExistsAsync(string key, string name = "") + { + return await GetDatabase(name).KeyExistsAsync(key); + } + + public bool Expire(string key, TimeSpan cacheTime, string name = "") + { + return GetDatabase(name).KeyExpire(key, DateTime.Now.AddSeconds(int.Parse(cacheTime.TotalSeconds.ToString()))); + } + + public async Task ExpireAsync(string key, TimeSpan cacheTime, string name = "") + { + return await GetDatabase(name).KeyExpireAsync(key, DateTime.Now.AddSeconds(int.Parse(cacheTime.TotalSeconds.ToString()))); + } + + public bool Remove(string key, string name = "") + { + return GetDatabase(name).KeyDelete(key); + } + + public bool Remove(string[] keys, string name = "") + { + var redisKeys = keys.Select(p => (RedisKey)(JsonSerializer.Serialize(p, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }))).ToArray(); + return GetDatabase(name).KeyDelete(redisKeys) == redisKeys.Length; + } + + public bool Remove(List keys, string name = "") + { + return Remove(keys.ToArray(), name); + } + + public async Task RemoveAsync(string key, string name = "") + { + return await GetDatabase(name).KeyDeleteAsync(key); + } + + public async Task RemoveAsync(string[] keys, string name = "") + { + var redisKeys = keys.Select(p => (RedisKey)(JsonSerializer.Serialize(p, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }))).ToArray(); + return await GetDatabase(name).KeyDeleteAsync(redisKeys) == redisKeys.Length; + } + + public async Task RemoveAsync(List keys, string name = "") + { + return await RemoveAsync(keys.ToArray(), name); + } + + public TEntity BlockingDequeue(string key, string name = "") + { + return JsonSerializer.Deserialize(GetDatabase(name).ListRightPop(key)); + } + + public async Task BlockingDequeueAsync(string key, string name = "") + { + return JsonSerializer.Deserialize(await GetDatabase(name).ListRightPopAsync(key)); + } + + public TEntity Dequeue(string key, string name = "") + { + return JsonSerializer.Deserialize(GetDatabase(name).ListLeftPop(key)); + } + + public async Task DequeueAsync(string key, string name = "") + { + return JsonSerializer.Deserialize(await GetDatabase(name).ListLeftPopAsync(key)); + } + + public void Enqueue(string key, TEntity entity, string name = "") + { + GetDatabase(name).ListLeftPush(key, JsonSerializer.Serialize(entity, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + })); + } + + public async Task EnqueueAsync(string key, TEntity entity, string name = "") + { + await GetDatabase(name).ListLeftPushAsync(key, JsonSerializer.Serialize(entity, new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + })); + } + + public long Increment(string key, string name = "") + { + return GetDatabase(name).StringIncrement(key, flags: CommandFlags.FireAndForget); + } + + public async Task IncrementAsync(string key, string name = "") + { + return await GetDatabase(name).StringIncrementAsync(key, flags: CommandFlags.FireAndForget); + } + + public long Decrement(string key, string value, string name = "") + { + return GetDatabase(name).HashDecrement(key, value, flags: CommandFlags.FireAndForget); + } + + public async Task DecrementAsync(string key, string value, string name = "") + { + return await GetDatabase(name).HashDecrementAsync(key, value, flags: CommandFlags.FireAndForget); + } + + public TEntity Get(string key, string name = "") + { + if (!Exists(key, name)) + { + return default; + } + var result = GetDatabase(name).StringGet(key); + return JsonSerializer.Deserialize(result); + } + + public List GetList(string key, string name = "") + { + if (!Exists(key, name)) + { + return null; + } + var result = GetDatabase(name).SetMembers(key); + return result.Select(p => JsonSerializer.Deserialize(p, jsonSerializerOptions)).ToList(); + } + + public TEntity[] GetArray(string key, string name = "") + { + if (!Exists(key, name)) + { + return null; + } + var result = GetDatabase(name).SetMembers(key); + return result.Select(p => JsonSerializer.Deserialize(p, jsonSerializerOptions)).ToArray(); + } + + public async Task GetAsync(string key, string name = "") + { + if (!await ExistsAsync(key, name)) + { + return default; + } + var result = await GetDatabase(name).StringGetAsync(key); + return JsonSerializer.Deserialize(result, jsonSerializerOptions); + } + + public async Task GetHashAsync(string key, string name = "") + { + if (!await ExistsAsync(key, name)) + { + return default; + } + var result = await GetDatabase(name).HashGetAllAsync(key); + var entity = ConvertFromRedis(result); + return entity; + } + + private T ConvertFromRedis(HashEntry[] hashEntries) + { + PropertyInfo[] properties = typeof(T).GetProperties(); + var obj = Activator.CreateInstance(typeof(T)); + foreach (var property in properties) + { + HashEntry entry = hashEntries.FirstOrDefault(g => g.Name.ToString().ToLower().Equals(property.Name.ToLower())); + if (entry.Equals(new HashEntry())) continue; + property.SetValue(obj, Convert.ChangeType(entry.Value.ToString(), property.PropertyType)); + } + return (T)obj; + } + + public async Task> GetListAsync(string key, string name = "") + { + if (!await ExistsAsync(key, name)) + { + return null; + } + var result = await GetDatabase(name).SetMembersAsync(key); + return result.Select(p => JsonSerializer.Deserialize(p, jsonSerializerOptions)).ToList(); + } + + public async Task GetArrayAsync(string key, string name = "") + { + if (!await ExistsAsync(key, name)) + { + return null; + } + var result = await GetDatabase(name).SetMembersAsync(key); + return result.Select(p => JsonSerializer.Deserialize(p, jsonSerializerOptions)).ToArray(); + } + } +} diff --git a/code/DG.Redis/RedisClient.cs b/code/DG.Redis/RedisClient.cs new file mode 100644 index 0000000..3193531 --- /dev/null +++ b/code/DG.Redis/RedisClient.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Redis +{ + public class RedisClient + { + public void ReadFromConfiguration(IConfiguration configuration) + { + ConfigurationManager = configuration; + } + + public IConfiguration ConfigurationManager { get; private set; } + + private static readonly Lazy _defaultClient = new(() => new RedisClient()); + + public static RedisClient Default + { + get { return _defaultClient.Value; } + } + } +} diff --git a/code/DG.Redis/RedisOptions.cs b/code/DG.Redis/RedisOptions.cs new file mode 100644 index 0000000..d53eea6 --- /dev/null +++ b/code/DG.Redis/RedisOptions.cs @@ -0,0 +1,30 @@ +namespace DG.Redis +{ + /// + /// Redis config Options + /// + public class RedisOptions + { + public string Name { get; set; } + + /// + /// Host + /// + public string HostName { get; set; } + + /// + /// Port + /// + public string Port { get; set; } + + /// + /// Password + /// + public string Password { get; set; } + + /// + /// Default Database + /// + public int DefaultDatabase { get; set; } + } +} \ No newline at end of file diff --git a/code/DG.Redis/ServiceCollectionExtensions.cs b/code/DG.Redis/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..e6d9a32 --- /dev/null +++ b/code/DG.Redis/ServiceCollectionExtensions.cs @@ -0,0 +1,31 @@ +using DG.Redis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions method + /// + public static class ServiceCollectionExtensions + { + + /// + /// Redis service registered + /// + /// + /// + /// + public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration) + { + RedisClient.Default.ReadFromConfiguration(configuration); + services.AddSingleton(); + return services; + } + } +} diff --git a/code/DG.Tool/AesUtil.cs b/code/DG.Tool/AesUtil.cs new file mode 100644 index 0000000..3fe1131 --- /dev/null +++ b/code/DG.Tool/AesUtil.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public class AesUtil + { + /// + /// AES加密算法 + /// + /// 明文字符串 + /// 字符串 + public static string EncryptByAES(string input, string key, string iv) + { + if (string.IsNullOrWhiteSpace(input)) + { + return input; + } + using (RijndaelManaged rijndaelManaged = new RijndaelManaged()) + { + rijndaelManaged.Mode = CipherMode.CBC; + rijndaelManaged.Padding = PaddingMode.PKCS7; + rijndaelManaged.FeedbackSize = 128; + rijndaelManaged.Key = Encoding.UTF8.GetBytes(key); + rijndaelManaged.IV = Encoding.UTF8.GetBytes(iv); + ICryptoTransform encryptor = rijndaelManaged.CreateEncryptor(rijndaelManaged.Key, rijndaelManaged.IV); + using (MemoryStream msEncrypt = new MemoryStream()) + { + using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) + { + using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) + { + swEncrypt.Write(input); + } + byte[] bytes = msEncrypt.ToArray(); + return Convert.ToBase64String(bytes); + } + } + } + } + + /// + /// AES解密 + /// + /// 密文字节数组 + /// 返回解密后的字符串 + public static string DecryptByAES(string input, string key, string iv) + { + if (string.IsNullOrWhiteSpace(input)) + { + return input; + } + var buffer = Convert.FromBase64String(input); + using (RijndaelManaged rijndaelManaged = new RijndaelManaged()) + { + rijndaelManaged.Mode = CipherMode.CBC; + rijndaelManaged.Padding = PaddingMode.PKCS7; + rijndaelManaged.FeedbackSize = 128; + rijndaelManaged.Key = Encoding.UTF8.GetBytes(key); + rijndaelManaged.IV = Encoding.UTF8.GetBytes(iv); + ICryptoTransform decryptor = rijndaelManaged.CreateDecryptor(rijndaelManaged.Key, rijndaelManaged.IV); + using (MemoryStream msEncrypt = new MemoryStream(buffer)) + { + using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, decryptor, CryptoStreamMode.Read)) + { + using (StreamReader srEncrypt = new StreamReader(csEncrypt)) + { + return srEncrypt.ReadToEnd(); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/code/DG.Tool/Attributes/ExportColumnNameAttribute.cs b/code/DG.Tool/Attributes/ExportColumnNameAttribute.cs new file mode 100644 index 0000000..1f0383d --- /dev/null +++ b/code/DG.Tool/Attributes/ExportColumnNameAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool.Attributes +{ + /// + /// 导出列的别名 + /// + [AttributeUsage(AttributeTargets.Property)] + public class ExportColumnNameAttribute : Attribute + { + public string? Name { get; set; } + + public ExportColumnNameAttribute(string? name) + { + Name = name; + } + } +} diff --git a/code/DG.Tool/Attributes/NotExportColumnAttribute.cs b/code/DG.Tool/Attributes/NotExportColumnAttribute.cs new file mode 100644 index 0000000..4d31071 --- /dev/null +++ b/code/DG.Tool/Attributes/NotExportColumnAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool +{ + /// + /// 不需要导出的列 + /// + [AttributeUsage(AttributeTargets.Property)] + public class NotExportColumnAttribute : Attribute + { + } +} diff --git a/code/DG.Tool/BlowFish.cs b/code/DG.Tool/BlowFish.cs new file mode 100644 index 0000000..417c61c --- /dev/null +++ b/code/DG.Tool/BlowFish.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class BlowFish + { + public static string BlowFishKey = "upchina6"; //Common.Utility.GetSettingByKey("BlowFishKey"); + public static string BlowFishIV = "upchina1";// Common.Utility.GetSettingByKey("BlowFishIV"); + + public static string Encode(string authInfohqRights) + { + var iv = Encoding.UTF8.GetBytes(BlowFishIV); + var blowFish = new BlowfishSimple(BlowFishKey, iv); + return blowFish.Encrypt(authInfohqRights, iv); + } + } +} diff --git a/code/DG.Tool/Blowfish/BlowfishCBC.cs b/code/DG.Tool/Blowfish/BlowfishCBC.cs new file mode 100644 index 0000000..65d5457 --- /dev/null +++ b/code/DG.Tool/Blowfish/BlowfishCBC.cs @@ -0,0 +1,379 @@ +/* + Copyright 2001-2007 Markus Hahn + All rights reserved. See documentation for license details. +*/ + + +namespace DG.Tool +{ + + /// Blowfish CBC implementation. + /// Use this class to encrypt or decrypt byte arrays or a single blocks + /// with Blowfish in CBC (Cipher Block Block Chaining) mode. This is the recommended + /// way to use Blowfish.NET, unless certain requirements (e.g. moving block without + /// decryption) exist. + /// + public class BlowfishCBC : BlowfishECB + { + // we store the IV as two 32bit integers, to void packing and + // unpacking inbetween the handling of data chunks + uint ivHi; + uint ivLo; + + /// The current initialization vector (IV), which measures one block. + public byte[] IV + { + set + { + SetIV(value, 0); + } + + get + { + byte[] result = new byte[BLOCK_SIZE]; + GetIV(result, 0); + return result; + } + } + + /// Sets the initialization vector (IV) with an offset. + /// The buffer containing the new IV material. + /// Where the IV material starts. + public void SetIV(byte[] buf, int ofs) + { + this.ivHi = (((uint)buf[ofs]) << 24) | + (((uint)buf[ofs + 1]) << 16) | + (((uint)buf[ofs + 2]) << 8) | + buf[ofs + 3]; + + this.ivLo = (((uint)buf[ofs + 4]) << 24) | + (((uint)buf[ofs + 5]) << 16) | + (((uint)buf[ofs + 6]) << 8) | + buf[ofs + 7]; + } + + /// Gets the current IV material (of the size of one block). + /// The buffer to copy the IV to. + /// Where to start copying. + public void GetIV(byte[] buf, int ofs) + { + uint ivHi = this.ivHi; + uint ivLo = this.ivLo; + + buf[ofs++] = (byte)(ivHi >> 24); + buf[ofs++] = (byte)(ivHi >> 16); + buf[ofs++] = (byte)(ivHi >> 8); + buf[ofs++] = (byte)ivHi; + + buf[ofs++] = (byte)(ivLo >> 24); + buf[ofs++] = (byte)(ivLo >> 16); + buf[ofs++] = (byte)(ivLo >> 8); + buf[ofs] = (byte)ivLo; + } + + /// Default constructor. + /// The IV needs to be assigned after the instance has been created! + /// + public BlowfishCBC(byte[] key, int ofs, int len) : base(key, ofs, len) + { + } + + /// Zero key constructor. + /// After construction you need to initialize the instance and then apply the IV. + public BlowfishCBC() : base(null, 0, 0) + { + } + + /// + public new void Invalidate() + { + base.Invalidate(); + + this.ivHi = this.ivLo = 0; + } + + /// + public new void EncryptBlock( + uint hi, + uint lo, + out uint outHi, + out uint outLo) + { + byte[] block = this.block; + + block[0] = (byte)(hi >> 24); + block[1] = (byte)(hi >> 16); + block[2] = (byte)(hi >> 8); + block[3] = (byte)hi; + block[4] = (byte)(lo >> 24); + block[5] = (byte)(lo >> 16); + block[6] = (byte)(lo >> 8); + block[7] = (byte)lo; + + Encrypt(block, 0, block, 0, BLOCK_SIZE); + + outHi = (((uint)block[0]) << 24) | + (((uint)block[1]) << 16) | + (((uint)block[2]) << 8) | + block[3]; + + outLo = (((uint)block[4]) << 24) | + (((uint)block[5]) << 16) | + (((uint)block[6]) << 8) | + block[7]; + } + + /// + public new void DecryptBlock( + uint hi, + uint lo, + out uint outHi, + out uint outLo) + { + byte[] block = this.block; + + block[0] = (byte)(hi >> 24); + block[1] = (byte)(hi >> 16); + block[2] = (byte)(hi >> 8); + block[3] = (byte)hi; + block[4] = (byte)(lo >> 24); + block[5] = (byte)(lo >> 16); + block[6] = (byte)(lo >> 8); + block[7] = (byte)lo; + + Decrypt(block, 0, block, 0, BLOCK_SIZE); + + outHi = (((uint)block[0]) << 24) | + (((uint)block[1]) << 16) | + (((uint)block[2]) << 8) | + block[3]; + + outLo = (((uint)block[4]) << 24) | + (((uint)block[5]) << 16) | + (((uint)block[6]) << 8) | + block[7]; + } + + /// + public new int Encrypt( + byte[] dataIn, + int posIn, + byte[] dataOut, + int posOut, + int count) + { + int end; + + uint[] sbox1 = this.sbox1; + uint[] sbox2 = this.sbox2; + uint[] sbox3 = this.sbox3; + uint[] sbox4 = this.sbox4; + + uint[] pbox = this.pbox; + + uint pbox00 = pbox[0]; + uint pbox01 = pbox[1]; + uint pbox02 = pbox[2]; + uint pbox03 = pbox[3]; + uint pbox04 = pbox[4]; + uint pbox05 = pbox[5]; + uint pbox06 = pbox[6]; + uint pbox07 = pbox[7]; + uint pbox08 = pbox[8]; + uint pbox09 = pbox[9]; + uint pbox10 = pbox[10]; + uint pbox11 = pbox[11]; + uint pbox12 = pbox[12]; + uint pbox13 = pbox[13]; + uint pbox14 = pbox[14]; + uint pbox15 = pbox[15]; + uint pbox16 = pbox[16]; + uint pbox17 = pbox[17]; + + uint hi = this.ivHi; + uint lo = this.ivLo; + + count &= ~(BLOCK_SIZE - 1); + + end = posIn + count; + + while (posIn < end) + { + hi ^= (((uint)dataIn[posIn]) << 24) | + (((uint)dataIn[posIn + 1]) << 16) | + (((uint)dataIn[posIn + 2]) << 8) | + dataIn[posIn + 3]; + + lo ^= (((uint)dataIn[posIn + 4]) << 24) | + (((uint)dataIn[posIn + 5]) << 16) | + (((uint)dataIn[posIn + 6]) << 8) | + dataIn[posIn + 7]; + + posIn += 8; + + hi ^= pbox00; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox01; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox02; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox03; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox04; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox05; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox06; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox07; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox08; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox09; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox10; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox11; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox12; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox13; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox14; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox15; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox16; + + uint swap = lo ^ pbox17; + lo = hi; + hi = swap; + + dataOut[posOut] = (byte)(hi >> 24); + dataOut[posOut + 1] = (byte)(hi >> 16); + dataOut[posOut + 2] = (byte)(hi >> 8); + dataOut[posOut + 3] = (byte)hi; + + dataOut[posOut + 4] = (byte)(lo >> 24); + dataOut[posOut + 5] = (byte)(lo >> 16); + dataOut[posOut + 6] = (byte)(lo >> 8); + dataOut[posOut + 7] = (byte)lo; + + posOut += 8; + } + + this.ivHi = hi; + this.ivLo = lo; + + return count; + } + + /// + public new int Decrypt( + byte[] dataIn, + int posIn, + byte[] dataOut, + int posOut, + int count) + { + int end; + uint hi, lo, hiBak, loBak; + + uint[] sbox1 = this.sbox1; + uint[] sbox2 = this.sbox2; + uint[] sbox3 = this.sbox3; + uint[] sbox4 = this.sbox4; + + uint[] pbox = this.pbox; + + uint pbox00 = pbox[0]; + uint pbox01 = pbox[1]; + uint pbox02 = pbox[2]; + uint pbox03 = pbox[3]; + uint pbox04 = pbox[4]; + uint pbox05 = pbox[5]; + uint pbox06 = pbox[6]; + uint pbox07 = pbox[7]; + uint pbox08 = pbox[8]; + uint pbox09 = pbox[9]; + uint pbox10 = pbox[10]; + uint pbox11 = pbox[11]; + uint pbox12 = pbox[12]; + uint pbox13 = pbox[13]; + uint pbox14 = pbox[14]; + uint pbox15 = pbox[15]; + uint pbox16 = pbox[16]; + uint pbox17 = pbox[17]; + + uint ivHi = this.ivHi; + uint ivLo = this.ivLo; + + count &= ~(BLOCK_SIZE - 1); + + end = posIn + count; + + while (posIn < end) + { + hi = hiBak = (((uint)dataIn[posIn]) << 24) | + (((uint)dataIn[posIn + 1]) << 16) | + (((uint)dataIn[posIn + 2]) << 8) | + dataIn[posIn + 3]; + + lo = loBak = (((uint)dataIn[posIn + 4]) << 24) | + (((uint)dataIn[posIn + 5]) << 16) | + (((uint)dataIn[posIn + 6]) << 8) | + dataIn[posIn + 7]; + posIn += 8; + + hi ^= pbox17; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox16; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox15; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox14; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox13; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox12; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox11; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox10; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox09; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox08; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox07; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox06; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox05; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox04; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox03; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox02; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox01; + + lo ^= ivHi ^ pbox00; + hi ^= ivLo; + + dataOut[posOut] = (byte)(lo >> 24); + dataOut[posOut + 1] = (byte)(lo >> 16); + dataOut[posOut + 2] = (byte)(lo >> 8); + dataOut[posOut + 3] = (byte)lo; + + dataOut[posOut + 4] = (byte)(hi >> 24); + dataOut[posOut + 5] = (byte)(hi >> 16); + dataOut[posOut + 6] = (byte)(hi >> 8); + dataOut[posOut + 7] = (byte)hi; + + ivHi = hiBak; + ivLo = loBak; + + posOut += 8; + } + + this.ivHi = ivHi; + this.ivLo = ivLo; + + return count; + } + + /// + public new object Clone() + { + BlowfishCBC result; + + result = new BlowfishCBC(); + + result.pbox = (uint[])this.pbox.Clone(); + + result.sbox1 = (uint[])this.sbox1.Clone(); + result.sbox2 = (uint[])this.sbox2.Clone(); + result.sbox3 = (uint[])this.sbox3.Clone(); + result.sbox4 = (uint[])this.sbox4.Clone(); + + result.block = (byte[])this.block.Clone(); + + result.isWeakKey = this.isWeakKey; + + result.ivHi = this.ivHi; + result.ivLo = this.ivLo; + + return result; + } + } +} diff --git a/code/DG.Tool/Blowfish/BlowfishCFB.cs b/code/DG.Tool/Blowfish/BlowfishCFB.cs new file mode 100644 index 0000000..362dd8e --- /dev/null +++ b/code/DG.Tool/Blowfish/BlowfishCFB.cs @@ -0,0 +1,405 @@ +/* + Copyright 2001-2007 Markus Hahn + All rights reserved. See documentation for license details. +*/ + +using System; + +namespace DG.Tool +{ + + /// Blowfish CFB implementation. + /// Use this class to encrypt or decrypt byte arrays in CFB (Cipher Feedback) mode. + /// Useful if you don't want to deal with padding of blocks (in comparsion to CBC), however + /// a safe initialization vector (IV) is still needed. Notice that the data does not have to + /// be block-aligned in comparsion to ECB and CBC, so byte-per-byte streaming is possible. + /// + public class BlowfishCFB : BlowfishECB + { + byte[] iv = new byte[BLOCK_SIZE]; + int ivBytesLeft = 0; + + /// The current initialization vector (IV), which measures one block. + public byte[] IV + { + set + { + SetIV(value, 0); + } + + get + { + byte[] result = new byte[BLOCK_SIZE]; + GetIV(result, 0); + return result; + } + } + + /// Sets the initialization vector (IV) with an offset. + /// The buffer containing the new IV material. + /// Where the IV material starts. + public void SetIV(byte[] buf, int ofs) + { + Array.Copy(buf, ofs, this.iv, 0, this.iv.Length); + this.ivBytesLeft = 0; + } + + /// Gets the current IV material (of the size of one block). + /// The buffer to copy the IV to. + /// Where to start copying. + public void GetIV(byte[] buf, int ofs) + { + Array.Copy(this.iv, 0, buf, ofs, this.iv.Length); + } + + /// Default constructor. + /// The IV needs to be assigned after the instance has been created! + /// + public BlowfishCFB(byte[] key, int ofs, int len) : base(key, ofs, len) + { + } + + /// Zero key constructor. + /// After construction you need to initialize the instance and then apply the IV. + public BlowfishCFB() : base(null, 0, 0) + { + } + + /// + public new void Invalidate() + { + base.Invalidate(); + + Array.Clear(this.iv, 0, this.iv.Length); + } + + /// + public new int Encrypt( + byte[] dataIn, + int posIn, + byte[] dataOut, + int posOut, + int count) + { + int end = posIn + count; + + byte[] iv = this.iv; + + int ivBytesLeft = this.ivBytesLeft; + int ivPos = iv.Length - ivBytesLeft; + + // consume what's left in the IV buffer, but make sure to keep the new + // ciphertext in a round-robin fashion (since it represents the new IV) + if (ivBytesLeft >= count) + { + // what we have is enough to deal with the request + for (; posIn < end; posIn++, posOut++, ivPos++) + { + iv[ivPos] = dataOut[posOut] = (byte)(dataIn[posIn] ^ iv[ivPos]); + } + this.ivBytesLeft = iv.Length - ivPos; + return count; + } + for (; ivPos < BLOCK_SIZE; posIn++, posOut++, ivPos++) + { + iv[ivPos] = dataOut[posOut] = (byte)(dataIn[posIn] ^ iv[ivPos]); + } + count -= ivBytesLeft; + + uint[] sbox1 = this.sbox1; + uint[] sbox2 = this.sbox2; + uint[] sbox3 = this.sbox3; + uint[] sbox4 = this.sbox4; + + uint[] pbox = this.pbox; + + uint pbox00 = pbox[0]; + uint pbox01 = pbox[1]; + uint pbox02 = pbox[2]; + uint pbox03 = pbox[3]; + uint pbox04 = pbox[4]; + uint pbox05 = pbox[5]; + uint pbox06 = pbox[6]; + uint pbox07 = pbox[7]; + uint pbox08 = pbox[8]; + uint pbox09 = pbox[9]; + uint pbox10 = pbox[10]; + uint pbox11 = pbox[11]; + uint pbox12 = pbox[12]; + uint pbox13 = pbox[13]; + uint pbox14 = pbox[14]; + uint pbox15 = pbox[15]; + uint pbox16 = pbox[16]; + uint pbox17 = pbox[17]; + + // now load the current IV into 32bit integers for speed + uint hi = (((uint)iv[0]) << 24) | + (((uint)iv[1]) << 16) | + (((uint)iv[2]) << 8) | + iv[3]; + + uint lo = (((uint)iv[4]) << 24) | + (((uint)iv[5]) << 16) | + (((uint)iv[6]) << 8) | + iv[7]; + + // we deal with the even part first + int rest = count % BLOCK_SIZE; + end -= rest; + + for (; ; ) + { + // need to create new IV material no matter what + hi ^= pbox00; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox01; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox02; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox03; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox04; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox05; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox06; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox07; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox08; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox09; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox10; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox11; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox12; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox13; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox14; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox15; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox16; + + uint swap = lo ^ pbox17; + lo = hi; + hi = swap; + + if (posIn >= end) + { + // exit right in the middle so we always have new IV material for the rest below + break; + } + + hi ^= (((uint)dataIn[posIn]) << 24) | + (((uint)dataIn[posIn + 1]) << 16) | + (((uint)dataIn[posIn + 2]) << 8) | + dataIn[posIn + 3]; + + lo ^= (((uint)dataIn[posIn + 4]) << 24) | + (((uint)dataIn[posIn + 5]) << 16) | + (((uint)dataIn[posIn + 6]) << 8) | + dataIn[posIn + 7]; + + posIn += 8; + + // now stream out the whole block + dataOut[posOut] = (byte)(hi >> 24); + dataOut[posOut + 1] = (byte)(hi >> 16); + dataOut[posOut + 2] = (byte)(hi >> 8); + dataOut[posOut + 3] = (byte)hi; + + dataOut[posOut + 4] = (byte)(lo >> 24); + dataOut[posOut + 5] = (byte)(lo >> 16); + dataOut[posOut + 6] = (byte)(lo >> 8); + dataOut[posOut + 7] = (byte)lo; + + posOut += 8; + } + + // store back the new IV + iv[0] = (byte)(hi >> 24); + iv[1] = (byte)(hi >> 16); + iv[2] = (byte)(hi >> 8); + iv[3] = (byte)hi; + iv[4] = (byte)(lo >> 24); + iv[5] = (byte)(lo >> 16); + iv[6] = (byte)(lo >> 8); + iv[7] = (byte)lo; + + // emit the rest + for (int i = 0; i < rest; i++) + { + iv[i] = dataOut[posOut + i] = (byte)(dataIn[posIn + i] ^ iv[i]); + } + + this.ivBytesLeft = iv.Length - rest; + + return count; + } + + /// + public new int Decrypt( + byte[] dataIn, + int posIn, + byte[] dataOut, + int posOut, + int count) + { + // same as above except that the ciphertext (input data) is what we keep... + + int end = posIn + count; + + byte[] iv = this.iv; + + int ivBytesLeft = this.ivBytesLeft; + int ivPos = iv.Length - ivBytesLeft; + + if (ivBytesLeft >= count) + { + for (; posIn < end; posIn++, posOut++, ivPos++) + { + int b = dataIn[posIn]; + dataOut[posOut] = (byte)(b ^ iv[ivPos]); + dataIn[posIn] = (byte)b; + } + this.ivBytesLeft = iv.Length - ivPos; + return count; + } + for (; ivPos < BLOCK_SIZE; posIn++, posOut++, ivPos++) + { + iv[ivPos] = dataOut[posOut] = (byte)(dataIn[posIn] ^ iv[ivPos]); + } + count -= ivBytesLeft; + + uint[] sbox1 = this.sbox1; + uint[] sbox2 = this.sbox2; + uint[] sbox3 = this.sbox3; + uint[] sbox4 = this.sbox4; + + uint[] pbox = this.pbox; + + uint pbox00 = pbox[0]; + uint pbox01 = pbox[1]; + uint pbox02 = pbox[2]; + uint pbox03 = pbox[3]; + uint pbox04 = pbox[4]; + uint pbox05 = pbox[5]; + uint pbox06 = pbox[6]; + uint pbox07 = pbox[7]; + uint pbox08 = pbox[8]; + uint pbox09 = pbox[9]; + uint pbox10 = pbox[10]; + uint pbox11 = pbox[11]; + uint pbox12 = pbox[12]; + uint pbox13 = pbox[13]; + uint pbox14 = pbox[14]; + uint pbox15 = pbox[15]; + uint pbox16 = pbox[16]; + uint pbox17 = pbox[17]; + + uint hi = (((uint)iv[0]) << 24) | + (((uint)iv[1]) << 16) | + (((uint)iv[2]) << 8) | + iv[3]; + + uint lo = (((uint)iv[4]) << 24) | + (((uint)iv[5]) << 16) | + (((uint)iv[6]) << 8) | + iv[7]; + + int rest = count % BLOCK_SIZE; + end -= rest; + + for (; ; ) + { + hi ^= pbox00; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox01; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox02; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox03; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox04; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox05; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox06; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox07; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox08; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox09; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox10; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox11; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox12; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox13; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox14; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox15; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox16; + + uint swap = lo ^ pbox17; + lo = hi; + hi = swap; + + if (posIn >= end) + { + break; + } + + uint chi = (((uint)dataIn[posIn]) << 24) | + (((uint)dataIn[posIn + 1]) << 16) | + (((uint)dataIn[posIn + 2]) << 8) | + dataIn[posIn + 3]; + + uint clo = (((uint)dataIn[posIn + 4]) << 24) | + (((uint)dataIn[posIn + 5]) << 16) | + (((uint)dataIn[posIn + 6]) << 8) | + dataIn[posIn + 7]; + + posIn += 8; + + hi ^= chi; + lo ^= clo; + + dataOut[posOut] = (byte)(hi >> 24); + dataOut[posOut + 1] = (byte)(hi >> 16); + dataOut[posOut + 2] = (byte)(hi >> 8); + dataOut[posOut + 3] = (byte)hi; + + dataOut[posOut + 4] = (byte)(lo >> 24); + dataOut[posOut + 5] = (byte)(lo >> 16); + dataOut[posOut + 6] = (byte)(lo >> 8); + dataOut[posOut + 7] = (byte)lo; + + posOut += 8; + + hi = chi; + lo = clo; + } + + iv[0] = (byte)(hi >> 24); + iv[1] = (byte)(hi >> 16); + iv[2] = (byte)(hi >> 8); + iv[3] = (byte)hi; + iv[4] = (byte)(lo >> 24); + iv[5] = (byte)(lo >> 16); + iv[6] = (byte)(lo >> 8); + iv[7] = (byte)lo; + + for (int i = 0; i < rest; i++) + { + int b = dataIn[posIn + i]; + dataOut[posOut + i] = (byte)(b ^ iv[i]); + iv[i] = (byte)b; + } + + this.ivBytesLeft = iv.Length - rest; + + return count; + } + + /// + public new object Clone() + { + BlowfishCFB result; + + result = new BlowfishCFB(); + + result.pbox = (uint[])this.pbox.Clone(); + + result.sbox1 = (uint[])this.sbox1.Clone(); + result.sbox2 = (uint[])this.sbox2.Clone(); + result.sbox3 = (uint[])this.sbox3.Clone(); + result.sbox4 = (uint[])this.sbox4.Clone(); + + result.block = (byte[])this.block.Clone(); + + result.isWeakKey = this.isWeakKey; + + result.iv = (byte[])this.iv.Clone(); + + return result; + } + } +} diff --git a/code/DG.Tool/Blowfish/BlowfishECB.cs b/code/DG.Tool/Blowfish/BlowfishECB.cs new file mode 100644 index 0000000..e3a6bc4 --- /dev/null +++ b/code/DG.Tool/Blowfish/BlowfishECB.cs @@ -0,0 +1,717 @@ +/* + Copyright 2001-2007 Markus Hahn + All rights reserved. See documentation for license details. +*/ + +using System; + +namespace DG.Tool +{ + /// Blowfish ECB implementation. + /// Use this class to encrypt or decrypt byte arrays or a single + /// block with Blowfish in the ECB (Electronic Code Book) mode. The key + /// length can be flexible from zero up to 56 bytes. + public class BlowfishECB : ICloneable + { + /// The maximum and recommended key size in bytes. + public const int MAX_KEY_LENGTH = 56; + + /// The block size in bytes. + public const int BLOCK_SIZE = 8; + + const int PBOX_ENTRIES = 18; + const int SBOX_ENTRIES = 256; + + #region Cipher State + + /// Runtime p-box. + protected uint[] pbox = new uint[PBOX_ENTRIES]; + /// Runtime s-box #1. + protected uint[] sbox1 = new uint[SBOX_ENTRIES]; + /// Runtime s-box #2. + protected uint[] sbox2 = new uint[SBOX_ENTRIES]; + /// Runtime s-box #3. + protected uint[] sbox3 = new uint[SBOX_ENTRIES]; + /// Runtime s-box #4. + protected uint[] sbox4 = new uint[SBOX_ENTRIES]; + /// Single block cache. + protected byte[] block = new byte[BLOCK_SIZE]; + /// 1 if a weak key was detected, 0 if not and -1 if it hasn't + /// been determined yet. + protected int isWeakKey; + + #endregion + + /// To check if the key used is weak. + /// If a key is weak it means that eventually an attack is easier to apply than + /// just a simple brute force on keys. Due to the randomness in the key setup process + /// such a case however is unlikely to happen, yet checking after each setup might still + /// be the preferred way. In the case of a weak key detected a simple recreation with a + /// different key (or just a different salt value) is the recommended solution. For + /// performance reasons we don't do the weak key check during the initialization, but on + /// demand only, and then only once to determine the flag. + public bool IsWeakKey + { + get + { + if (-1 == this.isWeakKey) + { + this.isWeakKey = 0; + + int i, j; + for (i = 0; i < SBOX_ENTRIES - 1; i++) + { + j = i + 1; + while (j < SBOX_ENTRIES) + { + if ((this.sbox1[i] == this.sbox1[j]) | + (this.sbox2[i] == this.sbox2[j]) | + (this.sbox3[i] == this.sbox3[j]) | + (this.sbox4[i] == this.sbox4[j])) break; + else j++; + } + if (j < SBOX_ENTRIES) + { + this.isWeakKey = 1; + break; + } + } + } + + return (1 == this.isWeakKey); + } + } + + /// Resets the instance with new or initial key material. Allows the switch of + /// keys at runtime without any new internal object allocation. + /// The buffer with the key material. + /// Position at which the key material starts in the buffer. + /// Size of the key material, up to MAX_KEY_LENGTH bytes. + public void Initialize(byte[] key, int ofs, int len) + { + this.isWeakKey = -1; + + Array.Copy(PBOX_INIT, 0, this.pbox, 0, PBOX_INIT.Length); + Array.Copy(SBOX_INIT_1, 0, this.sbox1, 0, SBOX_INIT_1.Length); + Array.Copy(SBOX_INIT_2, 0, this.sbox2, 0, SBOX_INIT_2.Length); + Array.Copy(SBOX_INIT_3, 0, this.sbox3, 0, SBOX_INIT_3.Length); + Array.Copy(SBOX_INIT_4, 0, this.sbox4, 0, SBOX_INIT_4.Length); + + if (0 == len) + { + return; + } + + int keyPos = ofs; + int keyEnd = ofs + len; + uint build = 0; + + for (int i = 0; i < PBOX_ENTRIES; i++) + { + for (int j = 0; j < 4; j++) + { + build = (build << 8) | key[keyPos]; + + if (++keyPos == keyEnd) + { + keyPos = ofs; + } + } + this.pbox[i] ^= build; + } + + uint hi = 0; + uint lo = 0; + + uint[] box = this.pbox; + for (int i = 0; i < PBOX_ENTRIES; i += 2) + { + EncryptBlock(hi, lo, out hi, out lo); + box[i] = hi; + box[i + 1] = lo; + } + box = this.sbox1; + for (int i = 0; i < SBOX_ENTRIES; i += 2) + { + EncryptBlock(hi, lo, out hi, out lo); + box[i] = hi; + box[i + 1] = lo; + } + box = this.sbox2; + for (int i = 0; i < SBOX_ENTRIES; i += 2) + { + EncryptBlock(hi, lo, out hi, out lo); + box[i] = hi; + box[i + 1] = lo; + } + box = this.sbox3; + for (int i = 0; i < SBOX_ENTRIES; i += 2) + { + EncryptBlock(hi, lo, out hi, out lo); + box[i] = hi; + box[i + 1] = lo; + } + box = this.sbox4; + for (int i = 0; i < SBOX_ENTRIES; i += 2) + { + EncryptBlock(hi, lo, out hi, out lo); + box[i] = hi; + box[i + 1] = lo; + } + } + + /// Zero constructor, properly initializes an instance. Initialize afterwards + /// to set up a valid key! + public BlowfishECB() + { + Initialize(null, 0, 0); + } + + /// + public BlowfishECB(byte[] key, int ofs, int len) + { + Initialize(key, ofs, len); + } + + /// Deletes all internal data structures and invalidates this instance. + /// Call this method as soon as the work with a particular instance is done, + /// so no sensitive translated key material remains. The instance is invalid after this + /// call and usage can lead to unexpected results! + public void Invalidate() + { + Array.Clear(this.pbox, 0, this.pbox.Length); + + Array.Clear(this.sbox1, 0, this.sbox1.Length); + Array.Clear(this.sbox2, 0, this.sbox2.Length); + Array.Clear(this.sbox3, 0, this.sbox3.Length); + Array.Clear(this.sbox4, 0, this.sbox4.Length); + + Array.Clear(this.block, 0, this.block.Length); + } + + static readonly byte[] TEST_KEY = { 0x1c, 0x58, 0x7f, 0x1c, 0x13, 0x92, 0x4f, 0xef }; + static readonly uint[] TEST_VECTOR_PLAIN = { 0x30553228, 0x6d6f295a }; + static readonly uint[] TEST_VECTOR_CIPHER = { 0x55cb3774, 0xd13ef201 }; + + /// To execute a selftest. + /// Call this method to make sure that the implemenation is able to produce + /// valid output according to the specification. This should usually be done at process + /// startup time, before the usage of this class and its inherited variants. + /// True if the selftest passed or false is it failed. In such a case you must + /// not use the cipher to avoid data corruption! + public static bool RunSelfTest() + { + uint hi = TEST_VECTOR_PLAIN[0]; + uint lo = TEST_VECTOR_PLAIN[1]; + + BlowfishECB bfe = new BlowfishECB(TEST_KEY, 0, TEST_KEY.Length); + + bfe.EncryptBlock(hi, lo, out hi, out lo); + + if ((TEST_VECTOR_CIPHER[0] != hi) || + (TEST_VECTOR_CIPHER[1] != lo)) + { + return false; + } + + bfe.DecryptBlock(hi, lo, out hi, out lo); + + if ((TEST_VECTOR_PLAIN[0] != hi) || + (TEST_VECTOR_PLAIN[1] != lo)) + { + return false; + } + + return true; + } + + /// Encrypts a single block. + /// The high 32bit word of the block. + /// The low 32bit word of the block. + /// Where to put the encrypted high word. + /// Where to put the encrypted low word. + public void EncryptBlock( + uint hi, + uint lo, + out uint outHi, + out uint outLo) + { + byte[] block = this.block; + + block[0] = (byte)(hi >> 24); + block[1] = (byte)(hi >> 16); + block[2] = (byte)(hi >> 8); + block[3] = (byte)hi; + block[4] = (byte)(lo >> 24); + block[5] = (byte)(lo >> 16); + block[6] = (byte)(lo >> 8); + block[7] = (byte)lo; + + Encrypt(block, 0, block, 0, BLOCK_SIZE); + + outHi = (((uint)block[0]) << 24) | + (((uint)block[1]) << 16) | + (((uint)block[2]) << 8) | + block[3]; + + outLo = (((uint)block[4]) << 24) | + (((uint)block[5]) << 16) | + (((uint)block[6]) << 8) | + block[7]; + } + + /// Decrypts a single block. + /// The high 32bit word of the block. + /// The low 32bit word of the block. + /// Where to put the decrypted high word. + /// Where to put the decrypted low word. + public void DecryptBlock( + uint hi, + uint lo, + out uint outHi, + out uint outLo) + { + byte[] block = this.block; + + block[0] = (byte)(hi >> 24); + block[1] = (byte)(hi >> 16); + block[2] = (byte)(hi >> 8); + block[3] = (byte)hi; + block[4] = (byte)(lo >> 24); + block[5] = (byte)(lo >> 16); + block[6] = (byte)(lo >> 8); + block[7] = (byte)lo; + + Decrypt(block, 0, block, 0, BLOCK_SIZE); + + outHi = (((uint)block[0]) << 24) | + (((uint)block[1]) << 16) | + (((uint)block[2]) << 8) | + block[3]; + + outLo = (((uint)block[4]) << 24) | + (((uint)block[5]) << 16) | + (((uint)block[6]) << 8) | + block[7]; + } + + /// Encrypts byte buffers. + /// Use this method to encrypt bytes from one array to another one. You can also + /// use the same array for input and output. Note that the number of bytes must be adjusted + /// to the block size of the algorithm. Overlapping bytes will not be encrypted. No check for + /// buffer overflows are made. + /// The input buffer. + /// Where to start reading in the input buffer. + /// The output buffer. + /// Where to start writing to the output buffer. + /// The number ob bytes to encrypt. + /// The number of bytes processed. + public int Encrypt( + byte[] dataIn, + int posIn, + byte[] dataOut, + int posOut, + int count) + { + uint[] sbox1 = this.sbox1; + uint[] sbox2 = this.sbox2; + uint[] sbox3 = this.sbox3; + uint[] sbox4 = this.sbox4; + + uint[] pbox = this.pbox; + + uint pbox00 = pbox[0]; + uint pbox01 = pbox[1]; + uint pbox02 = pbox[2]; + uint pbox03 = pbox[3]; + uint pbox04 = pbox[4]; + uint pbox05 = pbox[5]; + uint pbox06 = pbox[6]; + uint pbox07 = pbox[7]; + uint pbox08 = pbox[8]; + uint pbox09 = pbox[9]; + uint pbox10 = pbox[10]; + uint pbox11 = pbox[11]; + uint pbox12 = pbox[12]; + uint pbox13 = pbox[13]; + uint pbox14 = pbox[14]; + uint pbox15 = pbox[15]; + uint pbox16 = pbox[16]; + uint pbox17 = pbox[17]; + + count &= ~(BLOCK_SIZE - 1); + + int end = posIn + count; + + while (posIn < end) + { + uint hi = (((uint)dataIn[posIn]) << 24) | + (((uint)dataIn[posIn + 1]) << 16) | + (((uint)dataIn[posIn + 2]) << 8) | + dataIn[posIn + 3]; + + uint lo = (((uint)dataIn[posIn + 4]) << 24) | + (((uint)dataIn[posIn + 5]) << 16) | + (((uint)dataIn[posIn + 6]) << 8) | + dataIn[posIn + 7]; + posIn += 8; + + hi ^= pbox00; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox01; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox02; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox03; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox04; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox05; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox06; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox07; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox08; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox09; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox10; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox11; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox12; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox13; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox14; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox15; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox16; + + lo ^= pbox17; + + dataOut[posOut] = (byte)(lo >> 24); + dataOut[posOut + 1] = (byte)(lo >> 16); + dataOut[posOut + 2] = (byte)(lo >> 8); + dataOut[posOut + 3] = (byte)lo; + + dataOut[posOut + 4] = (byte)(hi >> 24); + dataOut[posOut + 5] = (byte)(hi >> 16); + dataOut[posOut + 6] = (byte)(hi >> 8); + dataOut[posOut + 7] = (byte)hi; + + posOut += 8; + } + + return count; + } + + /// Decrypts single bytes. + /// Use this method to decrypt bytes from one array to another one. You can also use + /// the same array for input and output. Note that the number of bytes must be adjusted to the + /// block size of the algorithm. Overlapping bytes will not be decrypted. No check for buffer + /// overflows are made. + /// The input buffer. + /// Where to start reading in the input buffer. + /// The output buffer. + /// Where to start writing to the output buffer. + /// Number ob bytes to decrypt. + /// The number of bytes processed. + public int Decrypt( + byte[] dataIn, + int posIn, + byte[] dataOut, + int posOut, + int count) + { + uint[] sbox1 = this.sbox1; + uint[] sbox2 = this.sbox2; + uint[] sbox3 = this.sbox3; + uint[] sbox4 = this.sbox4; + + uint[] pbox = this.pbox; + + uint pbox00 = pbox[0]; + uint pbox01 = pbox[1]; + uint pbox02 = pbox[2]; + uint pbox03 = pbox[3]; + uint pbox04 = pbox[4]; + uint pbox05 = pbox[5]; + uint pbox06 = pbox[6]; + uint pbox07 = pbox[7]; + uint pbox08 = pbox[8]; + uint pbox09 = pbox[9]; + uint pbox10 = pbox[10]; + uint pbox11 = pbox[11]; + uint pbox12 = pbox[12]; + uint pbox13 = pbox[13]; + uint pbox14 = pbox[14]; + uint pbox15 = pbox[15]; + uint pbox16 = pbox[16]; + uint pbox17 = pbox[17]; + + count &= ~(BLOCK_SIZE - 1); + + int end = posIn + count; + + while (posIn < end) + { + uint hi = (((uint)dataIn[posIn]) << 24) | + (((uint)dataIn[posIn + 1]) << 16) | + (((uint)dataIn[posIn + 2]) << 8) | + dataIn[posIn + 3]; + + uint lo = (((uint)dataIn[posIn + 4]) << 24) | + (((uint)dataIn[posIn + 5]) << 16) | + (((uint)dataIn[posIn + 6]) << 8) | + dataIn[posIn + 7]; + posIn += 8; + + hi ^= pbox17; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox16; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox15; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox14; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox13; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox12; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox11; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox10; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox09; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox08; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox07; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox06; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox05; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox04; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox03; + lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox02; + hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox01; + + lo ^= pbox00; + + dataOut[posOut] = (byte)(lo >> 24); + dataOut[posOut + 1] = (byte)(lo >> 16); + dataOut[posOut + 2] = (byte)(lo >> 8); + dataOut[posOut + 3] = (byte)lo; + + dataOut[posOut + 4] = (byte)(hi >> 24); + dataOut[posOut + 5] = (byte)(hi >> 16); + dataOut[posOut + 6] = (byte)(hi >> 8); + dataOut[posOut + 7] = (byte)hi; + + posOut += 8; + } + + return count; + } + + /// Cloning can be very useful if you need multiple instances all using + /// the same key, since the expensive cipher setup will be bypassed. + /// + public object Clone() + { + BlowfishECB result = new BlowfishECB(); + + result.pbox = (uint[])this.pbox.Clone(); + + result.sbox1 = (uint[])this.sbox1.Clone(); + result.sbox2 = (uint[])this.sbox2.Clone(); + result.sbox3 = (uint[])this.sbox3.Clone(); + result.sbox4 = (uint[])this.sbox4.Clone(); + + result.block = (byte[])this.block.Clone(); + + result.isWeakKey = this.isWeakKey; + + return result; + } + + #region Boxes Initialization Data + + /// The P-box initialization data. + protected static readonly uint[] PBOX_INIT = + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, + 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b + }; + + /// The first S-box initialization data. + protected static readonly uint[] SBOX_INIT_1 = + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, + 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, + 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, + 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, + 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, + 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, + 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, + 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, + 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, + 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, + 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, + 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, + 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, + 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, + 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, + 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, + 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, + 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, + 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, + 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, + 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, + 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }; + + /// The second S-box initialization data. + protected static readonly uint[] SBOX_INIT_2 = + { + 0x4b7a70e9, 0xb5b32944, + 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, + 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, + 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, + 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, + 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, + 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, + 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, + 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, + 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, + 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, + 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, + 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, + 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, + 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, + 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, + 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, + 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, + 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, + 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, + 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, + 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, + 0xe6e39f2b, 0xdb83adf7 + }; + + /// The third S-box initialization data. + protected static readonly uint[] SBOX_INIT_3 = + { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, + 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, + 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, + 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, + 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, + 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, + 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, + 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, + 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, + 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, + 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, + 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, + 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, + 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, + 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, + 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, + 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, + 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, + 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, + 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, + 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, + 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }; + + /// The fourth S-box initialization data. + protected static readonly uint[] SBOX_INIT_4 = + { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, + 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, + 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, + 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, + 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, + 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, + 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, + 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, + 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, + 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, + 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, + 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, + 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, + 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, + 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, + 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, + 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, + 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, + 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, + 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, + 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, + 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + }; + #endregion + } +} diff --git a/code/DG.Tool/Blowfish/BlowfishSimple.cs b/code/DG.Tool/Blowfish/BlowfishSimple.cs new file mode 100644 index 0000000..0e691ed --- /dev/null +++ b/code/DG.Tool/Blowfish/BlowfishSimple.cs @@ -0,0 +1,165 @@ +/* + Copyright 2001-2007 Markus Hahn + All rights reserved. See documentation for license details. +*/ + +using System; +using System.Text; + +namespace DG.Tool +{ + + /// An easy-to-use-string encryption solution using Blowfish/CBC. + /// As a simple solution for developers, who want nothing more than protect + /// single strings with a password, this class provides the necessary functionality. + /// The password (aka as key) is hashed using the SHA-1 implementation of the .NET + /// framework. The random number generator for the CBC initialization vector (IV) + /// and BASE64 are used from the framework's security services. + public class BlowfishSimple + { + BlowfishCBC bfc; + + private byte[] iv; + + private byte[] IV + { + get + { + if (iv == null || iv.Length < 8) + { + return Encoding.UTF8.GetBytes("Blowfish"); + } + return iv; + } + set { iv = value; } + } + + static byte[] TransformKey(String key) + { + UTF8Encoding ue = new UTF8Encoding(); + return ue.GetBytes(key); + } + + /// Empty constructor. Before using the instance you MUST call Initialize(), + /// otherwise any result or behavior is unpredictable! + public BlowfishSimple() + { + } + + /// Default constructor. + /// The string which is used as the key material (aka as + /// password or passphrase). Internally the UTF-8 representation of this string + /// is used, hashed with SHA-1. The result is then a 160bit binary key. Notice + /// that this transformation will not make weak (meaning short or easily guessable) + /// keys any safer! + public BlowfishSimple(String keyStr) + { + Initialize(keyStr, null); + } + + public BlowfishSimple(String keyStr, Byte[] iv) + { + Initialize(keyStr, iv); + } + + /// Initializes the instance with a (new) key string. + /// The key material. + /// + public void Initialize(String keyStr, Byte[] iv) + { + IV = iv; + byte[] key = TransformKey(keyStr); + + this.bfc = new BlowfishCBC(key, 0, key.Length); + Array.Clear(key, 0, key.Length); + } + + + + /// Encrypts a string. + /// For efficiency the given string will be UTF-8 encoded and padded to + /// the next 8byte block border. The CBC IV plus the encrypted data will then be + /// BASE64 encoded and returned as the final encryption result. + /// The string to encrypt. + /// The encrypted string. + public String Encrypt(String plainText) + { + return Encrypt(plainText, this.IV); + } + + public String Encrypt(String plainText, byte[] iv) + { + if (string.IsNullOrEmpty(plainText)) return string.Empty; + int txtLen = plainText.Length; + if (txtLen % 8 != 0) + plainText = plainText.PadRight(txtLen - txtLen % 8 + 8, ' '); + byte[] ueData = Encoding.UTF8.GetBytes(plainText); + + int origLen = ueData.Length; + + byte[] inBuf = new byte[origLen]; + + Array.Copy(ueData, 0, inBuf, 0, origLen); + + byte[] outBuf = new byte[inBuf.Length]; + + this.bfc.IV = iv; + + this.bfc.Encrypt( + inBuf, + 0, + outBuf, + 0, + inBuf.Length); + + String sResult = Convert.ToBase64String(outBuf); + + Array.Clear(inBuf, 0, inBuf.Length); + + return sResult; + } + + /// Decrypts a string which was formely generated by the Encrypt() + /// method and a particular key. + /// The string has to be decrypted with the same key, otherwise the + /// result will be simply garbage. If you want to check if the key is the right + /// one use the VerifyKey() method. + /// The string to decrypt. + /// The decrypted string, or null on error (usually caused by a wrong + /// key passed in). + + public String Decrypt(String cipherText) + { + return Decrypt(cipherText, this.IV); + } + + public String Decrypt(String cipherText, byte[] iv) + { + byte[] cdata; + + try + { + cdata = Convert.FromBase64String(cipherText); + } + catch (FormatException) + { + return null; + } + + this.bfc.SetIV(iv, 0); + + byte[] outBuf = new byte[cdata.Length]; + + int dataAbs = outBuf.Length; + + this.bfc.Decrypt( + cdata, + 0, + outBuf, + 0, + dataAbs); + + return Encoding.UTF8.GetString(outBuf); + } + } +} diff --git a/code/DG.Tool/DG.Tool.csproj b/code/DG.Tool/DG.Tool.csproj new file mode 100644 index 0000000..367de39 --- /dev/null +++ b/code/DG.Tool/DG.Tool.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + True + 1.0.13 + + + + + + + + diff --git a/code/DG.Tool/DateTimeTool.cs b/code/DG.Tool/DateTimeTool.cs new file mode 100644 index 0000000..c110c84 --- /dev/null +++ b/code/DG.Tool/DateTimeTool.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class DateTimeTool + { + /// + /// 获取指定日期所在周的第一天,星期天为第一天 + /// + /// + /// + public static DateTime GetDateTimeWeekFirstDaySun(DateTime dateTime) + { + DateTime firstWeekDay = DateTime.Now; + try + { + //得到是星期几,然后从当前日期减去相应天数 + int weeknow = Convert.ToInt32(dateTime.DayOfWeek); + + int daydiff = (-1) * weeknow; + + firstWeekDay = dateTime.AddDays(daydiff); + } + catch { } + + return firstWeekDay; + } + + /// + /// 获取指定日期所在周的第一天,星期一为第一天 + /// + /// + /// + public static DateTime GetDateTimeWeekFirstDayMon(DateTime dateTime) + { + DateTime firstWeekDay = DateTime.Now; + + try + { + int weeknow = Convert.ToInt32(dateTime.DayOfWeek); + + //星期一为第一天,weeknow等于0时,要向前推6天。 + weeknow = (weeknow == 0 ? (7 - 1) : (weeknow - 1)); + + int daydiff = (-1) * weeknow; + + firstWeekDay = dateTime.AddDays(daydiff); + } + catch { } + + return firstWeekDay; + } + + /// + /// 获取指定日期所在周的最后一天,星期六为最后一天 + /// + /// + /// + public static DateTime GetDateTimeWeekLastDaySat(DateTime dateTime) + { + DateTime lastWeekDay = DateTime.Now; + + try + { + int weeknow = Convert.ToInt32(dateTime.DayOfWeek); + + int daydiff = (7 - weeknow) - 1; + + lastWeekDay = dateTime.AddDays(daydiff); + + } + catch { } + + return lastWeekDay; + } + + /// + /// 获取指定日期所在周的最后一天,星期天为最后一天 + /// + /// + /// + + public static DateTime GetDateTimeWeekLastDaySun(DateTime dateTime) + { + DateTime lastWeekDay = DateTime.Now; + + try + { + int weeknow = Convert.ToInt32(dateTime.DayOfWeek); + + weeknow = (weeknow == 0 ? 7 : weeknow); + + int daydiff = (7 - weeknow); + + lastWeekDay = dateTime.AddDays(daydiff); + } + catch { } + + return lastWeekDay; + } + + /// + /// 获取指定日期的月份第一天 + /// + /// + /// + public static DateTime GetDateTimeMonthFirstDay(DateTime dateTime) + { + if (dateTime == null) + { + dateTime = DateTime.Now; + } + return new DateTime(dateTime.Year, dateTime.Month, 1); + } + + /// + /// 获取指定月份最后一天 + /// + /// + /// + public static DateTime GetDateTimeMonthLastDay(DateTime dateTime) + { + int day = DateTime.DaysInMonth(dateTime.Year, dateTime.Month); + + return new DateTime(dateTime.Year, dateTime.Month, day); + } + /// + /// 时间戳转为C#格式时间 + /// + /// + /// + public static DateTime GetTimeFromLinuxTime(long timeStamp) + { + System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0); + dtDateTime = dtDateTime.AddMilliseconds(timeStamp).ToLocalTime(); + return dtDateTime; + } + /// + /// 时间戳转为C#格式时间 + /// + /// + /// + public static DateTime GetTimeFromLinuxShortTime(long timeStamp) + { + DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); + long lTime = long.Parse(timeStamp.ToString() + "0000000"); + TimeSpan toNow = new TimeSpan(lTime); return dtStart.Add(toNow); + } + /// + /// DateTime时间格式转换为Unix时间戳格式 + /// + /// + /// + public static int ConvertDateTimeInt(System.DateTime time) + { + System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); + return (int)(time - startTime).TotalSeconds; + } + } +} diff --git a/code/DG.Tool/EnumHelper.cs b/code/DG.Tool/EnumHelper.cs new file mode 100644 index 0000000..0cd1768 --- /dev/null +++ b/code/DG.Tool/EnumHelper.cs @@ -0,0 +1,55 @@ +using System; +using System.ComponentModel; +using System.Reflection; + +namespace DG.Tool +{ + /// + /// 获取实体类Attribute自定义属性 + /// + public static class EnumHelper + { + /// + /// 返回枚举项的描述信息。 + /// + /// 要获取描述信息的枚举项。 + /// 枚举想的描述信息。 + public static string GetDescription(this Enum value) + { + Type enumType = value.GetType(); + // 获取枚举常数名称。 + string name = Enum.GetName(enumType, value); + if (name != null) + { + // 获取枚举字段。 + FieldInfo fieldInfo = enumType.GetField(name); + if (fieldInfo != null) + { + // 获取描述的属性。 + DescriptionAttribute attr = Attribute.GetCustomAttribute(fieldInfo, + typeof(DescriptionAttribute), false) as DescriptionAttribute; + if (attr != null) + { + return attr.Description; + } + } + } + return null; + } + /// + /// 获取枚举名字 + /// + /// + /// + public static string GetName(this Enum value) + { + Type enumType = value.GetType(); + // 获取枚举常数名称。 + string name = Enum.GetName(enumType, value); + if (name == null) return null; + // 获取枚举字段。 + FieldInfo fieldInfo = enumType.GetField(name); + return fieldInfo?.Name; + } + } +} diff --git a/code/DG.Tool/ExcelHelper.cs b/code/DG.Tool/ExcelHelper.cs new file mode 100644 index 0000000..987fc5a --- /dev/null +++ b/code/DG.Tool/ExcelHelper.cs @@ -0,0 +1,897 @@ +using Microsoft.VisualBasic; +using NPOI.HSSF.UserModel; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using OfficeOpenXml; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class ExcelHelper + { + public static void Export(string fullName, List data) + { + ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial; + + var ttype = typeof(T); + var typeName = ttype.Name; + var typeDescriptionAttr = ttype.GetCustomAttribute(); + if (typeDescriptionAttr != null) + { + typeName = typeDescriptionAttr.Description; + } + + var tps = ttype.GetProperties().ToList(); + for (var i = tps.Count - 1; i >= 0; i--) + { + var attr = tps[i].GetCustomAttribute(); + if (attr != null) + { + tps.RemoveAt(i); + } + } + + using (var package = new ExcelPackage(new FileInfo(fullName))) + { + var worksheet = package.Workbook.Worksheets.Add(typeName); + for (var i = 0; i < tps.Count; i++) + { + var tp = tps[i]; + var tpName = tp.Name; + var tpDescriptionAttr = tp.GetCustomAttribute(); + if (tpDescriptionAttr != null) + { + tpName = tpDescriptionAttr.Description; + } + + worksheet.Cells[1, i + 1].Value = tpName; + } + for (var j = 0; j < data.Count; j++) + { + for (var k = 0; k < tps.Count; k++) + { + var val_display = ""; + var val = tps[k].GetValue(data[j], null); + if (val != null) + { + val_display = Convert.ToString(val); + } + worksheet.Cells[j + 2, k + 1].Value = val_display; + } + } + package.Save(); + } + } + + /// + /// excel导入 + /// + /// + /// + /// 数据从第几行开始 + /// 只导入第一个Sheet的数据 + /// + public static List Import(byte[] bytes, int dataRow = 2, bool onlyFirstSheet = true) where T : class, new() + { + var ttype = typeof(T); + var tps = ttype.GetProperties(); + + var items = new List(); + using (var ms = new MemoryStream(bytes)) + { + ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial; + using (var package = new ExcelPackage(ms)) + { + var sheets = package.Workbook.Worksheets.ToList(); + if (onlyFirstSheet) + { + sheets = new List { package.Workbook.Worksheets[0] }; + } + + foreach (var sheet in sheets) + { + for (var i = dataRow; i <= sheet.Dimension.Rows; i++) + { + var one = Activator.CreateInstance(); + for (var j = 0; j < tps.Length; j++) + { + var cell = sheet.Cells[i, j + 1]; + if (cell == null) + { + continue; + } + + var tp = tps[j]; + var val = GetValue2(tp, cell.GetValue()); + tp.SetValue(one, val, null); + } + items.Add(one); + } + } + } + } + + return items; + } + + public static async Task ExportAsync(string fullName, List data) + { + ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial; + + var ttype = typeof(T); + var typeName = ttype.Name; + var typeDescriptionAttr = ttype.GetCustomAttribute(); + if (typeDescriptionAttr != null) + { + typeName = typeDescriptionAttr.Description; + } + + var tps = ttype.GetProperties().ToList(); + for (var i = tps.Count - 1; i >= 0; i--) + { + var attr = tps[i].GetCustomAttribute(); + if (attr != null) + { + tps.RemoveAt(i); + } + } + + using (var package = new ExcelPackage(new FileInfo(fullName))) + { + var worksheet = package.Workbook.Worksheets.Add(typeName); + for (var i = 0; i < tps.Count; i++) + { + var tp = tps[i]; + var tpName = tp.Name; + var tpDescriptionAttr = tp.GetCustomAttribute(); + if (tpDescriptionAttr != null) + { + tpName = tpDescriptionAttr.Description; + } + + worksheet.Cells[1, i + 1].Value = tpName; + } + for (var j = 0; j < data.Count; j++) + { + for (var k = 0; k < tps.Count; k++) + { + var val_display = ""; + var val = tps[k].GetValue(data[j], null); + if (val != null) + { + val_display = Convert.ToString(val); + } + worksheet.Cells[j + 2, k + 1].Value = val_display; + } + } + await package.SaveAsync(); + } + } + + private static object GetValue2(PropertyInfo tp, string val) + { + if (tp.PropertyType == typeof(int) || tp.PropertyType == typeof(int?)) + { + if (int.TryParse(val, out int temp)) + { + return temp; + } + + return default(int?); + } + + if (tp.PropertyType == typeof(long) || tp.PropertyType == typeof(long?)) + { + if (long.TryParse(val, out long temp)) + { + return temp; + } + + return default(long?); + } + + if (tp.PropertyType == typeof(decimal) || tp.PropertyType == typeof(decimal?)) + { + if (decimal.TryParse(val, out decimal temp)) + { + return temp; + } + + return default(decimal?); + } + + if (tp.PropertyType == typeof(DateTime) || tp.PropertyType == typeof(DateTime?)) + { + if (DateTime.TryParse(val, out DateTime temp)) + { + return temp; + } + + return default(DateTime?); + } + + if (tp.PropertyType == typeof(bool) || tp.PropertyType == typeof(bool?)) + { + if (bool.TryParse(val, out bool temp)) + { + return temp; + } + + return default(bool?); + } + + return val; + } + + + /// + /// 生成Excel文件(多行头部) + /// + /// 数据源 + /// 文件名 + /// + public static void GridToExcelByNPOIMultiHeader(DataTable dt, string strExcelFileName, List> extHeaders) + { + HSSFWorkbook workbook = new HSSFWorkbook(); + try + { + int sheetIndex = 0; + int dataIndex = 0; + ICellStyle HeadercellStyle = workbook.CreateCellStyle(); + HeadercellStyle.BorderBottom = BorderStyle.Thin; + HeadercellStyle.BorderLeft = BorderStyle.Thin; + HeadercellStyle.BorderRight = BorderStyle.Thin; + HeadercellStyle.BorderTop = BorderStyle.Thin; + HeadercellStyle.Alignment = HorizontalAlignment.Center; + + ICellStyle cellStyle = workbook.CreateCellStyle(); + + //为避免日期格式被Excel自动替换,所以设定 format 为 『@』 表示一率当成text來看 + cellStyle.DataFormat = HSSFDataFormat.GetBuiltinFormat("@"); + cellStyle.BorderBottom = BorderStyle.Thin; + cellStyle.BorderLeft = BorderStyle.Thin; + cellStyle.BorderRight = BorderStyle.Thin; + cellStyle.BorderTop = BorderStyle.Thin; + + //字体 + IFont headerfont = workbook.CreateFont(); + headerfont.Boldweight = (short)FontBoldWeight.Bold; + HeadercellStyle.SetFont(headerfont); + + IFont cellfont = workbook.CreateFont(); + cellfont.Boldweight = (short)FontBoldWeight.Normal; + cellStyle.SetFont(cellfont); + var index = extHeaders.Count; + + while (dataIndex < dt.Rows.Count) + { + sheetIndex++; + ISheet sheet = workbook.CreateSheet($"Sheet{sheetIndex}"); + + if (index > 0) + { + for (var i = 0; i < index; i++) + { + IRow extHeaderRow = sheet.CreateRow(i); + + if (extHeaders[i].Count == 0) + { + ICell extCell = extHeaderRow.CreateCell(0); + extCell.SetCellValue(""); + extCell.CellStyle = HeadercellStyle; + } + else + { + for (var j = 0; j < extHeaders[i].Count; j++) + { + ICell cell = extHeaderRow.CreateCell(j); + cell.SetCellValue(extHeaders[i][j]); + cell.CellStyle = HeadercellStyle; + } + } + } + } + + //用column name 作为列名 + int icolIndex = 0; + IRow headerRow = sheet.CreateRow(index); + foreach (DataColumn item in dt.Columns) + { + ICell cell = headerRow.CreateCell(icolIndex); + cell.SetCellValue(item.ColumnName); + cell.CellStyle = HeadercellStyle; + icolIndex++; + } + + //建立内容行 + int iRowIndex = 1; + int iCellIndex = 0; + for (int count = 0; dataIndex < dt.Rows.Count; dataIndex++, count++) + { + if (count >= 65000) + break; + DataRow Rowitem = dt.Rows[dataIndex]; + IRow DataRow = sheet.CreateRow(index + iRowIndex); + foreach (DataColumn Colitem in dt.Columns) + { + ICell cell = DataRow.CreateCell(iCellIndex); + cell.SetCellValue(Rowitem[Colitem].ToString()); + cell.CellStyle = cellStyle; + iCellIndex++; + } + iCellIndex = 0; + iRowIndex++; + } + + //自适应列宽度 + for (int i = 0; i < icolIndex; i++) + { + sheet.AutoSizeColumn(i); + } + } + + if (System.IO.File.Exists(strExcelFileName)) + System.IO.File.Delete(strExcelFileName); + + //写Excel + FileStream file = new FileStream(strExcelFileName, FileMode.Create); + workbook.Write(file); + file.Flush(); + file.Close(); + } + catch (Exception ex) + { + throw; + } + finally { workbook = null; } + } + + /// + /// 生成Excel文件 + /// + /// 数据源 + /// 文件名 + /// 如果是web下载,strExcelFileName则仅仅是文件名而非路径名 + public static void GridToExcelByNPOI(DataTable dt, string strExcelFileName) + { + HSSFWorkbook workbook = new HSSFWorkbook(); + try + { + int sheetIndex = 0; + int dataIndex = 0; + ICellStyle HeadercellStyle = workbook.CreateCellStyle(); + HeadercellStyle.BorderBottom = BorderStyle.Thin; + HeadercellStyle.BorderLeft = BorderStyle.Thin; + HeadercellStyle.BorderRight = BorderStyle.Thin; + HeadercellStyle.BorderTop = BorderStyle.Thin; + HeadercellStyle.Alignment = HorizontalAlignment.Center; + + ICellStyle cellStyle = workbook.CreateCellStyle(); + + //为避免日期格式被Excel自动替换,所以设定 format 为 『@』 表示一率当成text來看 + cellStyle.DataFormat = HSSFDataFormat.GetBuiltinFormat("@"); + cellStyle.BorderBottom = BorderStyle.Thin; + cellStyle.BorderLeft = BorderStyle.Thin; + cellStyle.BorderRight = BorderStyle.Thin; + cellStyle.BorderTop = BorderStyle.Thin; + + //字体 + IFont headerfont = workbook.CreateFont(); + headerfont.Boldweight = (short)FontBoldWeight.Bold; + HeadercellStyle.SetFont(headerfont); + + IFont cellfont = workbook.CreateFont(); + cellfont.Boldweight = (short)FontBoldWeight.Normal; + cellStyle.SetFont(cellfont); + while (dataIndex < dt.Rows.Count) + { + sheetIndex++; + ISheet sheet = workbook.CreateSheet($"Sheet{sheetIndex}"); + + //用column name 作为列名 + int icolIndex = 0; + IRow headerRow = sheet.CreateRow(0); + foreach (DataColumn item in dt.Columns) + { + ICell cell = headerRow.CreateCell(icolIndex); + cell.SetCellValue(item.ColumnName); + cell.CellStyle = HeadercellStyle; + icolIndex++; + } + + //建立内容行 + int iRowIndex = 1; + int iCellIndex = 0; + for (int count = 0; dataIndex < dt.Rows.Count; dataIndex++, count++) + { + if (count >= 65000) + break; + DataRow Rowitem = dt.Rows[dataIndex]; + IRow DataRow = sheet.CreateRow(iRowIndex); + foreach (DataColumn Colitem in dt.Columns) + { + ICell cell = DataRow.CreateCell(iCellIndex); + cell.SetCellValue(Rowitem[Colitem].ToString()); + cell.CellStyle = cellStyle; + iCellIndex++; + } + iCellIndex = 0; + iRowIndex++; + } + + //自适应列宽度 + for (int i = 0; i < icolIndex; i++) + { + sheet.AutoSizeColumn(i); + } + } + + //如果没有传路径,就生成用于web下载的流 + //if (isWebDownload) + //{ + // using (MemoryStream ms = new MemoryStream()) + // { + // workbook.Write(ms); + // ms.Flush(); + // ms.Position = 0; + + // byte[] bytes = ms.GetBuffer(); + // HttpContext.GetUserIp(); + // string UserAgent = WebRequest.DefaultWebProxy. + + // .g + + // Request.UserAgent + + // string UserAgent = HttpContext.Current.Request.ServerVariables["http_user_agent"].ToLower(); + // string filename = strExcelFileName + ".xls"; + // if (UserAgent.IndexOf("firefox") <= 0)//火狐,文件名不需要编码 + // { + // filename = HttpUtility.UrlEncode(filename, Encoding.UTF8); + // } + // HttpContext.Current.Response.ContentType = "application/vnd.ms-excel"; + + // HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment;filename=" + filename); + // HttpContext.Current.Response.BinaryWrite(bytes); + // HttpContext.Current.Response.End(); + // } + //} + //else + { + if (System.IO.File.Exists(strExcelFileName)) + System.IO.File.Delete(strExcelFileName); + + //写Excel + FileStream file = new FileStream(strExcelFileName, FileMode.Create); + workbook.Write(file); + file.Flush(); + file.Close(); + } + } + catch (Exception ex) + { + throw; + } + finally { workbook = null; } + } + + /// + /// 将DataTable数据导入到excel中 + /// + /// 要导入的数据 + /// DataTable的列名是否要导入 + /// 导出文件名全路径 + /// 要导入的excel的sheet的名称 + /// 导入数据行数(包含列名那一行) + public static int DataTableToExcel(DataTable data, string fileName, bool isColumnWritten, string sheetName = "Sheet1") + { + int i = 0; + int j = 0; + int count = 0; + ISheet sheet = null; + IWorkbook workbook = null; + string myDir = Path.GetDirectoryName(fileName); + //判断文件夹是否存在 + if (!Directory.Exists(myDir)) + { + //文件夹不存在则创建该文件夹 + if (myDir != null) + Directory.CreateDirectory(myDir); + } + using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite)) + { + if (fileName.IndexOf(".xlsx", StringComparison.Ordinal) > 0) // 2007版本 + workbook = new XSSFWorkbook(); + else if (fileName.IndexOf(".xls", StringComparison.Ordinal) > 0) // 2003版本 + workbook = new HSSFWorkbook(); + + try + { + if (workbook != null) + { + sheet = workbook.CreateSheet(sheetName); + } + else + { + return -1; + } + + if (isColumnWritten == true) //写入DataTable的列名 + { + IRow row = sheet.CreateRow(0); + for (j = 0; j < data.Columns.Count; ++j) + { + row.CreateCell(j).SetCellValue(data.Columns[j].ColumnName); + } + count = 1; + } + else + { + count = 0; + } + + for (i = 0; i < data.Rows.Count; ++i) + { + IRow row = sheet.CreateRow(count); + for (j = 0; j < data.Columns.Count; ++j) + { + row.CreateCell(j).SetCellValue(data.Rows[i][j].ToString()); + } + ++count; + } + workbook.Write(fs); //写入到excel + workbook.Close(); + return count; + } + catch (Exception ex) + { + workbook?.Close(); + throw new Exception(ex.Message); + } + } + } + + public static DataTable ExcelToDataTable(string fileName, string sheetName, bool isFirstRowColumn, Dictionary columnTemplate = null, string[] requireColumns = null, int? maxRows = null) + { + ISheet sheet = null; + DataTable data = new DataTable(); + IWorkbook workbook = null; + int startRow = 0; + try + { + using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + { + try + { + workbook = new XSSFWorkbook(fs); + } + catch + { + workbook = new HSSFWorkbook(fs); + } + } + + if (sheetName != null) + { + if (workbook != null) + { + sheet = workbook.GetSheet(sheetName); + if (sheet == null) //如果没有找到指定的sheetName对应的sheet,则尝试获取第一个sheet + { + sheet = workbook.GetSheetAt(0); + } + } + } + else + { + if (workbook != null) sheet = workbook.GetSheetAt(0); + } + if (sheet != null) + { + IRow firstRow = sheet.GetRow(0); + int cellCount = firstRow.LastCellNum; //一行最后一个cell的编号 即总的列数 + + if (isFirstRowColumn) + { + for (int i = firstRow.FirstCellNum; i < cellCount; ++i) + { + ICell cell = firstRow.GetCell(i); + string cellValue = cell?.StringCellValue?.Trim(); + if (!string.IsNullOrWhiteSpace(cellValue))//列名正确性验证 + { + if (columnTemplate != null && !columnTemplate.First().Value.Contains(cellValue)) + throw new Exception($"{columnTemplate.First().Key}不存在列名:{cellValue}!正确列名为:{string.Join(",", columnTemplate.First().Value)}"); + DataColumn column = new DataColumn(cellValue); + data.Columns.Add(column); + } + } + startRow = sheet.FirstRowNum + 1; + } + else + { + startRow = sheet.FirstRowNum; + } + + //最后一列的标号 + int rowCount = sheet.LastRowNum; + if (maxRows != null) + { + if (rowCount > maxRows) + throw new Exception($"请拆分文件,一次最多支持{maxRows}条数据"); + } + for (int i = startRow; i <= rowCount; ++i) + { + IRow row = sheet.GetRow(i); + if (row == null || row.Cells.Count == 0 || row.FirstCellNum == -1 || row.Cells.All(d => d.CellType == CellType.Blank)) continue; //没有数据的行默认是null        + + DataRow dataRow = data.NewRow(); + for (int j = row.FirstCellNum; j < cellCount; ++j) + { + var cellvalue = row.GetCell(j); + if (cellvalue == null || (cellvalue.ToString().Trim() == "0")) + { + if (requireColumns != null && requireColumns.Contains(data.Columns[j].ColumnName)) + { + //throw new Exception($"第{i}行,第{j}列,【{data.Columns[j].ColumnName}】不能为空或0,必须填写!"); + } + } + if (cellvalue != null) dataRow[j] = cellvalue.ToString().Trim(); + else + { + dataRow[j] = ""; //string.Empty; + } + } + data.Rows.Add(dataRow); + } + } + workbook?.Close(); + return data; + } + catch (Exception ex) + { + workbook?.Close(); + throw new Exception(ex.Message); + } + } + + + /// + /// 将excel中的数据导入到DataTable中 + /// + /// excel工作薄sheet的名称 + /// 第一行是否是DataTable的列名 + /// 第一行是否是DataTable的列名 + /// 开始行数 + /// 开始收集数据行数 + /// 返回的DataTable + public static DataTable ExcelToDataTable(string sheetName, bool isFirstRowColumn, string fileName, int startRow = 0, int startData = 1) + { + ISheet sheet = null; + DataTable data = new DataTable(); + try + { + IWorkbook workbook = null; + FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); + if (fileName.IndexOf(".xlsx") > 0 || fileName.IndexOf(".xlsm") > 0) // 2007版本 + workbook = new XSSFWorkbook(fs); + else if (fileName.IndexOf(".xls") > 0) // 2003版本 + workbook = new HSSFWorkbook(fs); + + if (sheetName != null) + { + sheet = workbook.GetSheet(sheetName); + if (sheet == null) //如果没有找到指定的sheetName对应的sheet,则尝试获取第一个sheet + { + sheet = workbook.GetSheetAt(0); + } + } + else + { + sheet = workbook.GetSheetAt(0); + } + if (sheet != null) + { + IRow firstRow = sheet.GetRow(startRow); + int cellCount = firstRow.LastCellNum; //一行最后一个cell的编号 即总的列数 + + if (isFirstRowColumn) + { + for (int i = firstRow.FirstCellNum; i < cellCount; ++i) + { + ICell cell = firstRow.GetCell(i); + if (cell != null) + { + string cellValue = cell.StringCellValue.Replace(" ", "").Replace("?", ""); + if (cellValue != null) + { + if (data.Columns[cellValue] != null) + { + cellValue += i; + } + DataColumn column = new DataColumn(cellValue); + data.Columns.Add(column); + } + } + } + startRow += sheet.FirstRowNum + startData; + } + else + { + startRow = sheet.FirstRowNum; + } + + //最后一列的标号 + int rowCount = sheet.LastRowNum; + for (int i = startRow; i <= rowCount; ++i) + { + IRow row = sheet.GetRow(i); + if (row == null) continue; //没有数据的行默认是null        + + DataRow dataRow = data.NewRow(); + for (int j = row.FirstCellNum; j < cellCount; ++j) + { + if (row.GetCell(j) != null) //同理,没有数据的单元格都默认是null + dataRow[j] = row.GetCell(j).ToString(); + } + data.Rows.Add(dataRow); + } + } + + return data; + } + catch (Exception ex) + { + Console.WriteLine("Exception: " + ex.Message); + return null; + } + } + + /// + /// DataTable转成List + /// + /// + /// + /// + public static List ToDataList(this DataTable dt) + { + var list = new List(); + var plist = new List(typeof(T).GetProperties()); + foreach (DataRow item in dt.Rows) + { + T s = Activator.CreateInstance(); + for (int i = 0; i < dt.Columns.Count; i++) + { + PropertyInfo info = plist.Find(p => p.Name == dt.Columns[i].ColumnName); + if (info != null) + { + try + { + if (!Convert.IsDBNull(item[i])) + { + object v = null; + if (info.PropertyType.ToString().Contains("System.Nullable")) + { + v = Convert.ChangeType(item[i], Nullable.GetUnderlyingType(info.PropertyType)); + } + else + { + v = Convert.ChangeType(item[i], info.PropertyType); + } + info.SetValue(s, v, null); + } + } + catch (Exception ex) + { + throw new Exception("字段[" + info.Name + "]转换出错," + ex.Message); + } + } + } + list.Add(s); + } + return list; + } + + /// + /// DataTable转成Dto + /// + /// + /// + /// + public static T ToDataDto(this DataTable dt) + { + T s = Activator.CreateInstance(); + if (dt == null || dt.Rows.Count == 0) + { + return s; + } + var plist = new List(typeof(T).GetProperties()); + for (int i = 0; i < dt.Columns.Count; i++) + { + PropertyInfo info = plist.Find(p => p.Name == dt.Columns[i].ColumnName); + if (info != null) + { + try + { + if (!Convert.IsDBNull(dt.Rows[0][i])) + { + object v = null; + if (info.PropertyType.ToString().Contains("System.Nullable")) + { + v = Convert.ChangeType(dt.Rows[0][i], Nullable.GetUnderlyingType(info.PropertyType)); + } + else + { + v = Convert.ChangeType(dt.Rows[0][i], info.PropertyType); + } + info.SetValue(s, v, null); + } + } + catch (Exception ex) + { + throw new Exception("字段[" + info.Name + "]转换出错," + ex.Message); + } + } + } + return s; + } + + /// + /// list转化为table + /// + /// + /// + /// + public static DataTable ListToDataTable(List entitys) + { + + //检查实体集合不能为空 + if (entitys == null || entitys.Count < 1) + { + return new DataTable(); + } + + //取出第一个实体的所有Propertie + Type entityType = entitys[0].GetType(); + PropertyInfo[] entityProperties = entityType.GetProperties(); + + //生成DataTable的structure + //生产代码中,应将生成的DataTable结构Cache起来,此处略 + DataTable dt = new DataTable("dt"); + for (int i = 0; i < entityProperties.Length; i++) + { + //dt.Columns.Add(entityProperties[i].Name, entityProperties[i].PropertyType); + dt.Columns.Add(entityProperties[i].Name); + } + + //将所有entity添加到DataTable中 + foreach (object entity in entitys) + { + //检查所有的的实体都为同一类型 + if (entity.GetType() != entityType) + { + throw new Exception("要转换的集合元素类型不一致"); + } + DataRow dr = dt.NewRow(); + dt.Rows.Add(dr); + int rowCount = dt.Rows.Count; + object[] entityValues = new object[entityProperties.Length]; + for (int i = 0; i < entityProperties.Length; i++) + { + entityValues[i] = entityProperties[i].GetValue(entity, null); + //给当前行的每列加数据 + dt.Rows[rowCount][i] = entityValues[i]; + } + } + return dt; + } + + + } +} diff --git a/code/DG.Tool/HttpHelper.cs b/code/DG.Tool/HttpHelper.cs new file mode 100644 index 0000000..548daf3 --- /dev/null +++ b/code/DG.Tool/HttpHelper.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class HttpHelper + { + public static string PostAjaxData(string url, string param, Encoding encoding) + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.Method = "POST"; + request.ContentType = "application/json;charet=utf-8"; + request.Headers.Add("dataType", "json"); + request.Headers.Add("type", "post"); + byte[] data = encoding.GetBytes(param); + + using (BinaryWriter reqStream = new BinaryWriter(request.GetRequestStream())) + { + reqStream.Write(data, 0, data.Length); + } + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + StreamReader reader = new StreamReader(response.GetResponseStream(), encoding); + string result = reader.ReadToEnd(); + return result; + } + } + + public static T PostAjaxData(string url, string param, Encoding encoding) + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.Method = "POST"; + request.ContentType = "application/json;charet=utf-8"; + request.Headers.Add("dataType", "json"); + request.Headers.Add("type", "post"); + byte[] data = encoding.GetBytes(param); + + using (BinaryWriter reqStream = new BinaryWriter(request.GetRequestStream())) + { + reqStream.Write(data, 0, data.Length); + } + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + StreamReader reader = new StreamReader(response.GetResponseStream(), encoding); + string result = reader.ReadToEnd(); + return JsonHelper.FromJson(result); + } + } + + /// + /// 获取数据 + /// + /// + /// + /// + public static string GetData(string Url, string RequestPara, Encoding encoding) + { + RequestPara = RequestPara.IndexOf('?') > -1 ? (RequestPara) : ("?" + RequestPara); + + WebRequest hr = HttpWebRequest.Create(Url + RequestPara); + + byte[] buf = encoding.GetBytes(RequestPara); + hr.Method = "GET"; + + System.Net.WebResponse response = hr.GetResponse(); + StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); + string ReturnVal = reader.ReadToEnd(); + reader.Close(); + response.Close(); + + return ReturnVal; + } + + /// + /// 获取数据 + /// + /// + /// + /// + public static T GetData(string Url, string RequestPara, Encoding encoding) + { + RequestPara = RequestPara.IndexOf('?') > -1 ? (RequestPara) : ("?" + RequestPara); + + WebRequest hr = HttpWebRequest.Create(Url + RequestPara); + + byte[] buf = encoding.GetBytes(RequestPara); + hr.Method = "GET"; + + System.Net.WebResponse response = hr.GetResponse(); + StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); + string ReturnVal = reader.ReadToEnd(); + reader.Close(); + response.Close(); + return JsonHelper.FromJson(ReturnVal); + } + } +} \ No newline at end of file diff --git a/code/DG.Tool/JsonHelper.cs b/code/DG.Tool/JsonHelper.cs new file mode 100644 index 0000000..18e7a89 --- /dev/null +++ b/code/DG.Tool/JsonHelper.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Unicode; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class JsonHelper + { + public static JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + /// + /// 类对像转换成json格式 + /// + /// + public static string ToJson(this object t) + { + return JsonSerializer.Serialize(t, jsonSerializerOptions); + } + + /// + /// 类转化为json + /// + /// + /// + /// + public static string ToJson(this T t) + { + return JsonSerializer.Serialize(t, jsonSerializerOptions); + } + + + public static T FromJson(string t) + { + return JsonSerializer.Deserialize(t, jsonSerializerOptions); + } + } +} \ No newline at end of file diff --git a/code/DG.Tool/PhoneHelper.cs b/code/DG.Tool/PhoneHelper.cs new file mode 100644 index 0000000..5df3648 --- /dev/null +++ b/code/DG.Tool/PhoneHelper.cs @@ -0,0 +1,434 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public class PhoneHelper + { + /// + /// 格式化以手机号码作为用户名的username + /// + /// + /// + public static string FormatPhoneUserName(string username) + { + string newUsername = GetFormatPhoneUserName(username);//手机号码格式化 + //newUsername = FormatUserName(newUsername);//用户名格式化 + //return newUsername; + return newUsername; + } + /// + /// 格式化以手机号码作为用户名的username(存在多个用户名) + /// + /// + /// + public static string FormatPhoneMoreUserName(string username) + { + string newUsername = GetFormatPhoneUserName(username);//手机号码格式化 + if (string.IsNullOrEmpty(newUsername)) + return newUsername; + string userNames = string.Empty; + foreach (var item in newUsername.Split(',')) + { + userNames += FormatUserName(item) + ",";//用户名格式化 + } + userNames = userNames.Substring(0, userNames.Length - 1); + return userNames; + } + + + /// + /// 格式化以手机号码作为用户名的username + /// + /// 需要改动的用户名 + /// + /// + public static string FormatPhoneUserNameContent(string username) + { + string newUsername = GetFormatPhoneUserName(username);//手机号码格式化 + return newUsername; + } + /// + /// 格式化以手机号码作为用户名的username + /// + /// 需要改动的用户名 + /// 需要改动的用户名 + /// + /// + public static string FormatPhoneUserNameContent(string username, string subTypeCode) + { + string newUsername = GetFormatPhoneUserName(username);//手机号码格式化 + if (subTypeCode != "SMS_ResetPwd") + return newUsername; + //创建对象 + Regex regex = new Regex("账号(?[\\s\\S]*?)的", RegexOptions.Multiline); + //匹配 + MatchCollection match = regex.Matches(username); + //取得第一个值 + if (match.Count > 0) + { + string value = match[0].Groups["value"].ToString(); + string newValue = FormatUserName(value); + newUsername = newUsername.Replace(string.Format("账号{0}的", value), string.Format("账号{0}的", newValue)); + } + + + //创建对象 + Regex regex2 = new Regex("帐号(?[\\s\\S]*?),", RegexOptions.Multiline); + //匹配 + MatchCollection match2 = regex2.Matches(newUsername); + //取得第一个值 + if (match2.Count > 0) + { + string value1 = match2[0].Groups["value"].ToString(); + string newValue1 = FormatUserName(value1); + newUsername = newUsername.Replace(string.Format("帐号{0},", value1), string.Format("帐号{0},", newValue1)); + } + + return newUsername; + } + /// + /// 用户名 + /// + /// + /// + public static string FormatUserName(string userName) + { + /* + 用户名屏蔽规则: + 1、手机号形式的按原处理规则中间几位屏蔽; + 2、否则看用户名长度,小于等于5位的,第一位不屏蔽,后面的屏蔽,比如“test1”,处理后为“t****”; + 3、5位以上的,用户名长度除2向下取整,再减1做为起始值,从起始值开始后面4位用*号;比如“test123”,处理后为“te****3” + */ + if (string.IsNullOrEmpty(userName)) + return ""; + if (userName == "未设置") + return userName; + string newUserName = userName; + //判断 是否已经在手机号屏蔽的时候屏蔽过一次 + if (userName.IndexOf("*****") > -1) + { + return newUserName; + } + int nameLth = newUserName.Length; + if (nameLth <= 5) + { + newUserName = newUserName.Substring(0, 1) + GetAsterisk(nameLth - 1); + } + else + { + int startIndex = nameLth / 2; + startIndex = startIndex - 1; + newUserName = newUserName.Substring(0, startIndex) + "****" + newUserName.Substring(startIndex + 4, nameLth - startIndex - 4); + } + return newUserName; + } + /// + /// 格式化以手机号码作为用户名的username + /// + /// + /// + private static string GetFormatPhoneUserName(string username) + { + string newUsername = username; + if (string.IsNullOrWhiteSpace(newUsername)) + return newUsername; + while (ContainMobile(newUsername)) + { + string phone = GetMobile(newUsername); + if (string.IsNullOrWhiteSpace(phone)) + break; + newUsername = newUsername.Replace(phone, (phone.Substring(0, 3) + "*****" + phone.Substring(8, 3))); + } + return newUsername; + } + + #region 检查是否手机号 + /// + /// 00852+8位是香港的电话 + /// + /// + /// + public static bool ChekMobile(string number) + { + string rgs = @"^(13|14|15|16|17|18|19|0085)\d{9}$"; + Regex reg = new Regex(rgs); + return reg.IsMatch(number); + } + /// + /// 检查是否包含手机号码 + /// + /// + /// + public static bool ContainMobile(string number) + { + string rgs = @".*(13|14|15|16|17|18|19)\d{9}.*"; + Regex reg = new Regex(rgs); + return reg.IsMatch(number); + } + /// + /// 从文本中返回号码字符串 + /// + /// + /// + public static string GetMobile(string txt) + { + string rgs = @"(12|13|14|15|16|17|18|19)\d{9}"; + string result = Regex.Match(txt, rgs).Value; + return result; + } + public static bool CheckIsNum(string txt) + { + string rgs = "^\\d{6,16}$"; + Regex reg = new Regex(rgs); + return reg.IsMatch(txt); + } + + public static bool IsNum(string txt) + { + string rgs = "^\\d+$"; + Regex reg = new Regex(rgs); + return reg.IsMatch(txt); + } + public static bool IsChinese(string txt) + { + string rgs = "[\u4E00-\u9FA5]"; + Regex reg = new Regex(rgs); + return reg.IsMatch(txt); + } + public static bool IsNumOrStr(string txt) + { + string rgs = "^[A-Za-z0-9]+$"; + Regex reg = new Regex(rgs); + return reg.IsMatch(txt); + } + #endregion + + /// + /// 获取星号 + /// + /// 需要返回的星号数量 + /// + private static string GetAsterisk(int number) + { + string xingHao = string.Empty; + if (number == 0) + return xingHao; + for (int i = 0; i < number; i++) + { + xingHao += "*"; + } + return xingHao; + } + + #region 超过8个数字的数据替换成* + + /// + /// 超过8个数字的数据替换成* + /// + /// + /// + public static string Replace8Number(object txt) + { + if (txt == null) + { + return ""; + } + if (string.IsNullOrEmpty(txt.ToString())) + { + return ""; + } + var content = txt.ToString(); + //普通数字 + string numReg = @"\d+"; + var result = Regex.Matches(content, numReg).Cast().Select(t => t.Value).ToList(); + if (result != null && result.Count > 0) + { + foreach (var s in result) + { + if (s.Length > 7) + { + content = content.Replace(s, GetHideNumber(s)); + } + + } + } + //下标数字 + numReg = @"[₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{8,}"; + result = Regex.Matches(content, numReg).Cast().Select(t => t.Value).ToList(); + if (result != null && result.Count > 0) + { + foreach (var s in result) + { + if (s.Length > 7) + { + content = content.Replace(s, GetHideDownNumber(s)); + } + + } + } + //上标数字 + numReg = @"[¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰]{8,}"; + result = Regex.Matches(content, numReg).Cast().Select(t => t.Value).ToList(); + if (result != null && result.Count > 0) + { + foreach (var s in result) + { + if (s.Length > 7) + { + content = content.Replace(s, GetHideUpNumber(s)); + } + + } + } + //全角数字 + numReg = @"[1,2,3,4,5,6,7,8,9,0]{8,}"; + result = Regex.Matches(content, numReg).Cast().Select(t => t.Value).ToList(); + if (result != null && result.Count > 0) + { + foreach (var s in result) + { + if (s.Length > 7) + { + content = content.Replace(s, GetHideRoundNumber(s)); + } + + } + } + return content; + } + //全角数字屏蔽 + public static string GetHideRoundNumber(string phoneNo) + { + string rexstr = ""; + string xinghao = ""; + if (phoneNo.Length == 8) + { + rexstr = @"([1,2,3,4,5,6,7,8,9,0]{2})([1,2,3,4,5,6,7,8,9,0]{4})([1,2,3,4,5,6,7,8,9,0]{2})"; + xinghao = "****"; + } + else if (phoneNo.Length == 9) + { + rexstr = @"([1,2,3,4,5,6,7,8,9,0]{2})([1,2,3,4,5,6,7,8,9,0]{5})([1,2,3,4,5,6,7,8,9,0]{2})"; + xinghao = "*****"; + } + else if (phoneNo.Length == 10) + { + rexstr = @"([1,2,3,4,5,6,7,8,9,0]{2})([1,2,3,4,5,6,7,8,9,0]{5})([1,2,3,4,5,6,7,8,9,0]{3})"; + xinghao = "*****"; + } + else + { + rexstr = @"([1,2,3,4,5,6,7,8,9,0]{3})([1,2,3,4,5,6,7,8,9,0]{" + (phoneNo.Length - 6) + @"})([1,2,3,4,5,6,7,8,9,0]{3})"; + for (int i = 0; i < (phoneNo.Length - 6); i++) + { + xinghao += "*"; + } + } + Regex re = new Regex(rexstr, RegexOptions.None); + phoneNo = re.Replace(phoneNo, "$1" + xinghao + "$3"); + return phoneNo; + } + //上标数字屏蔽 + public static string GetHideUpNumber(string phoneNo) + { + string rexstr = ""; + string xinghao = ""; + if (phoneNo.Length == 8) + { + rexstr = @"([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{2})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{4})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{2})"; + xinghao = "****"; + } + else if (phoneNo.Length == 9) + { + rexstr = @"([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{2})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{5})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{2})"; + xinghao = "*****"; + } + else if (phoneNo.Length == 10) + { + rexstr = @"([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{2})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{5})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{3})"; + xinghao = "*****"; + } + else + { + rexstr = @"([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{3})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{" + (phoneNo.Length - 6) + @"})([¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,⁰,]{3})"; + for (int i = 0; i < (phoneNo.Length - 6); i++) + { + xinghao += "*"; + } + } + Regex re = new Regex(rexstr, RegexOptions.None); + phoneNo = re.Replace(phoneNo, "$1" + xinghao + "$3"); + return phoneNo; + } + //下标数字屏蔽 + public static string GetHideDownNumber(string phoneNo) + { + string rexstr = ""; + string xinghao = ""; + if (phoneNo.Length == 8) + { + rexstr = @"([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{2})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{4})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{2})"; + xinghao = "****"; + } + else if (phoneNo.Length == 9) + { + rexstr = @"([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{2})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{5})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{2})"; + xinghao = "*****"; + } + else if (phoneNo.Length == 10) + { + rexstr = @"([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{2})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{5})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{3})"; + xinghao = "*****"; + } + else + { + rexstr = @"([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{3})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{" + (phoneNo.Length - 6) + @"})([₁,₂,₃,₄,₅,₆,₇,₈,₉,₀]{3})"; + for (int i = 0; i < (phoneNo.Length - 6); i++) + { + xinghao += "*"; + } + } + Regex re = new Regex(rexstr, RegexOptions.None); + phoneNo = re.Replace(phoneNo, "$1" + xinghao + "$3"); + return phoneNo; + } + //数字屏蔽 + public static string GetHideNumber(string phoneNo) + { + string rexstr = ""; + string xinghao = ""; + if (phoneNo.Length == 8) + { + rexstr = @"(\d{2})(\d{4})(\d{2})"; + xinghao = "****"; + } + else if (phoneNo.Length == 9) + { + rexstr = @"(\d{2})(\d{5})(\d{2})"; + xinghao = "*****"; + } + else if (phoneNo.Length == 10) + { + rexstr = @"(\d{2})(\d{5})(\d{3})"; + xinghao = "*****"; + } + else + { + rexstr = @"(\d{3})(\d{" + (phoneNo.Length - 6) + @"})(\d{3})"; + for (int i = 0; i < (phoneNo.Length - 6); i++) + { + xinghao += "*"; + } + } + Regex re = new Regex(rexstr, RegexOptions.None); + phoneNo = re.Replace(phoneNo, "$1" + xinghao + "$3"); + return phoneNo; + } + #endregion + } +} diff --git a/code/DG.Tool/ResUtil.cs b/code/DG.Tool/ResUtil.cs new file mode 100644 index 0000000..1d96e55 --- /dev/null +++ b/code/DG.Tool/ResUtil.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class ResUtil + { + //生成resid + public static string CreateResId(string number) + { + number = number.Replace("+86", ""); + if (number.StartsWith("01") && number.Length == 12) + { + number = number.Substring(1); + } + + //1、正则表达式提取数字串值 + number = Regex.Replace(number, @"[^\d.\d]", "", RegexOptions.IgnoreCase); + + //*******开始resid算法******************// + bool ismobile = ChekMobile(number) || ChekMobile(number.TrimStart('0')); + if (ismobile) + number = number.TrimStart('0'); + string bkn_ms_52 = InReversibleStr(number, ismobile); + string kn_ms_6 = ReversibleStr(number); + string resId = MixResID(bkn_ms_52, kn_ms_6, number, ismobile); + return resId; + } + + /// + /// 00852+8位是香港的电话 + /// + /// + /// + public static bool ChekMobile(string number) + { + string rgs = @"^(13|14|15|16|17|18|19|0085)\d{9}$"; + Regex reg = new Regex(rgs); + return reg.IsMatch(number); + } + + /// + /// 检查是否包含手机号码 + /// + /// + /// + public static bool ContainMobile(string number) + { + string rgs = @".*(13|14|15|16|17|18|19)\d{9}.*"; + Regex reg = new Regex(rgs); + return reg.IsMatch(number); + } + + public static bool CheckIsNum(string txt) + { + string rgs = "^\\d{6,16}$"; + Regex reg = new Regex(rgs); + return reg.IsMatch(txt); + } + + public static string UserMd5(string str) + { + var cl = str; + var result = string.Empty; + var md5 = MD5.Create();//实例化一个md5对像 + // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择  + var s = md5.ComputeHash(Encoding.UTF8.GetBytes(cl)); + // 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得 + for (int i = 0; i < s.Length; i++) + { + // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符 + result = result + s[i].ToString("x2"); + } + return result; + } + + public static string NumberFormat(string number) + { + if (string.IsNullOrEmpty(number) || number.Length <= 6) + { + return number; + } + return number.Substring(0, 3) + new string('*', number.Length - 6) + number.Substring(number.Length - 3); + } + + /// + /// 不可逆密码串 + /// + /// + /// + private static string InReversibleStr(string number, bool ismobile) + { + //11位手机前补'x'、不足12位的号码前补'0,超过12位的取后12位 + string rnum = ismobile ? "x" + number : number; + rnum = rnum.PadLeft(12, '0'); + rnum = rnum.Substring(rnum.Length - 12); + + string strms = EncryptMD5(rnum + "@no_zuo_no_die@").Substring(10, 13); + Int64 si = Convert.ToInt64(strms, 16); + return Convert.ToString(si, 2).PadLeft(52, '0'); + } + + /// + /// md5加密 + /// + /// + /// + public static string EncryptMD5(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } + MD5 md5 = MD5.Create(); + byte[] data = md5.ComputeHash(Encoding.Default.GetBytes(input)); + // Create a new Stringbuilder to collect the bytes + // and create a string. + StringBuilder sBuilder = new(); + // Loop through each byte of the hashed data + // and format each one as a hexadecimal string. + for (int i = 0; i < data.Length; i++) + { + sBuilder.Append(data[i].ToString("x2")); + } + return sBuilder.ToString(); + } + + /// + /// 生成可逆密码串 + /// + /// + /// + private static string ReversibleStr(string number) + { + //2位可逆 + int H2 = Int32.Parse(number.Substring(number.Length - 2)) % 64; + string strms = Convert.ToString(Convert.ToInt64(H2), 2).PadLeft(6, '0'); + + return MixKN_MS(strms, true); + } + + private static string MixResID(string bkn_ms, string kn_ms, string number, bool ismobile) + { + StringBuilder sb = new StringBuilder(); + int i = 0; + for (; i < kn_ms.Length; i++) + { + sb.Append(bkn_ms.Substring(i * 2, 2)); + sb.Append(kn_ms.Substring(i, 1)); + } + sb.Append(bkn_ms.Substring(i * 2)); + sb.Append(ismobile ? "1" : "0"); + return Convert2TO10(sb.ToString()).ToString().PadLeft(18, '0'); + } + + private static string MixKN_MS(string kn_ms, bool bMix) + { + int iMove = 2; + StringBuilder sb = new StringBuilder(); + if (bMix) + { + kn_ms = kn_ms.Substring(kn_ms.Length - iMove) + kn_ms.Substring(0, kn_ms.Length - iMove); + } + for (int i = 0; i < kn_ms.Length / 2; i++) + { + sb.Append(kn_ms[i * 2 + 1]); + sb.Append(kn_ms[i * 2]); + } + kn_ms = sb.ToString(); + if (!bMix) + { + kn_ms = kn_ms.Substring(iMove) + kn_ms.Substring(0, iMove); + } + return kn_ms; + } + + /// + /// 返回后二位的手机号码串 + /// + /// + /// + public static string Reve_ms(string resId) + { + string code = Convert10TO2(Convert.ToDecimal(resId)).PadLeft(59, '0'); + code = code.Substring(code.Length - 59); + StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= 6; i++) + { + sb.Append(code[i * 3 - 1]); + } + code = MixKN_MS(sb.ToString(), false); + code = Convert.ToInt32(code, 2).ToString().PadLeft(2, '0'); + return "*********" + code; + } + + /// + /// 二进制转十进制 + /// + /// + /// + public static decimal Convert2TO10(string resid) + { + decimal residNum = 0; + string str = resid; + for (int i = 0, j = resid.Length - 1; i < resid.Length; i++, j--) + { + string bitebag = resid.Substring(j, 1); + decimal t = Convert.ToInt32(bitebag) * Pow(i); + residNum = residNum + t; + } + return residNum; + } + + /// + /// 十进制转二进制 + /// + /// + /// + public static string Convert10TO2(decimal resudnum) + { + string str = string.Empty; + while (resudnum > 0) + { + int y = Convert.ToInt32(resudnum % Convert.ToDecimal(2)); + resudnum = Math.Floor(resudnum / Convert.ToDecimal(2)); + str = y.ToString() + str; + } + return str; + } + + public static decimal Pow(int y) + { + decimal pow = 1; + if (y == 0) + { + pow = 1; + } + else if (y == 1) + { + pow = 2; + } + else + { + pow = 2; + while (y > 1) + { + pow = pow * 2; + y--; + } + } + return pow; + } + } +} \ No newline at end of file diff --git a/code/DG.Tool/SecurityHelper.cs b/code/DG.Tool/SecurityHelper.cs new file mode 100644 index 0000000..344f7a7 --- /dev/null +++ b/code/DG.Tool/SecurityHelper.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace DG.Tool +{ + public static class SecurityHelper + { + /// + /// 加密 + /// + /// + /// + /// + public static string EncyptData(string ciphertext, string accessKey) + { + SymmetricAlgorithm des = new DESCryptoServiceProvider(); + + Encoding utf = new UTF8Encoding(); + byte[] key = utf.GetBytes(accessKey); + byte[] iv = { 0x75, 0x70, 0x63, 0x68, 0x69, 0x6e, 0x61, 0x31 }; + ICryptoTransform encryptor = des.CreateEncryptor(key, iv); + byte[] data = utf.GetBytes(ciphertext); + byte[] encData = encryptor.TransformFinalBlock(data, 0, data.Length); + return Convert.ToBase64String(encData); + } + + /// + /// 解密 + /// + /// + /// + /// + public static string DecyptData(string cryptograph, string accessKey) + { + SymmetricAlgorithm des = new DESCryptoServiceProvider(); + + Encoding utf = new UTF8Encoding(); + byte[] key = utf.GetBytes(accessKey); + byte[] iv = { 0x75, 0x70, 0x63, 0x68, 0x69, 0x6e, 0x61, 0x31 }; + ICryptoTransform decryptor = des.CreateDecryptor(key, iv); + byte[] encData = Convert.FromBase64String(cryptograph); + byte[] data = decryptor.TransformFinalBlock(encData, 0, encData.Length); + return utf.GetString(data); + } + + public static string SignData(string ciphertext, string accessKey) + { + Encoding utf = new UTF8Encoding(); + HMACMD5 hmac = new HMACMD5(utf.GetBytes(accessKey)); + byte[] hashValue = hmac.ComputeHash(utf.GetBytes(ciphertext)); + return Convert.ToBase64String(hashValue); + } + + /// + /// 加密 + /// + /// + /// + /// + /// + public static string EncyptDataNew(string ciphertext, string accessKey, string iv) + { + SymmetricAlgorithm des = new DESCryptoServiceProvider(); + + Encoding utf = new UTF8Encoding(); + byte[] key = utf.GetBytes(accessKey); + byte[] ivbt = utf.GetBytes(iv); + ICryptoTransform encryptor = des.CreateEncryptor(key, ivbt); + byte[] data = utf.GetBytes(ciphertext); + byte[] encData = encryptor.TransformFinalBlock(data, 0, data.Length); + return Convert.ToBase64String(encData); + } + + /// + /// 解密 + /// + /// + /// + /// + /// + public static string DecyptDataNew(string cryptograph, string accessKey, string iv) + { + SymmetricAlgorithm des = new DESCryptoServiceProvider(); + + Encoding utf = new UTF8Encoding(); + byte[] key = utf.GetBytes(accessKey); + byte[] ivbt = utf.GetBytes(iv); + ICryptoTransform decryptor = des.CreateDecryptor(key, ivbt); + byte[] encData = Convert.FromBase64String(cryptograph); + byte[] data = decryptor.TransformFinalBlock(encData, 0, encData.Length); + return utf.GetString(data); + } + + public static string CreateSignEncodingStr(string json, string clientid, string accessKey) + { + if (string.IsNullOrWhiteSpace(clientid)) + { + clientid = "UPWEBSITE"; + } + string key = "content={0}&clientid=" + clientid + "&sign={1}"; + string jiami = EncyptData(json, accessKey); + string jiami1 = HttpUtility.UrlEncode(jiami, Encoding.UTF8); + string jiasuo = SignData(jiami, accessKey); + string jiasuo1 = HttpUtility.UrlEncode(jiasuo, Encoding.UTF8); + key = string.Format(key, jiami1, jiasuo1); + return key; + } + + public static object CreateSignEncodingJsonStr(string json, string clientid, string accessKey) + { + //if (string.IsNullOrWhiteSpace(clientid)) + //{ + // clientid = "UPWEBSITE"; + //} + //string key = "content={0}&clientid=" + clientid + "&sign={1}"; + //string jiami = EncyptData(json, accessKey); + //string jiami1 = HttpUtility.UrlEncode(jiami, Encoding.UTF8); + //string jiasuo = SignData(jiami, accessKey); + //string jiasuo1 = HttpUtility.UrlEncode(jiasuo, Encoding.UTF8); + //key = string.Format(key, jiami1, jiasuo1); + //return key; + + if (string.IsNullOrWhiteSpace(clientid)) + { + clientid = "UPWEBSITE"; + } + string content = EncyptData(json, accessKey); + string sign = SignData(content, accessKey); + var maxjson = new + { + content = content, + clientId = clientid, + sign = sign + }; + return maxjson; + } + } +} \ No newline at end of file diff --git a/code/DG.Tool/SignHelper.cs b/code/DG.Tool/SignHelper.cs new file mode 100644 index 0000000..3c1614f --- /dev/null +++ b/code/DG.Tool/SignHelper.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class SignHelper + { + /// + /// 计算签名 + /// + /// + /// + /// + public static string GetSign(string appId, string appSecret, Dictionary param, string timestamps) + { + + //一次排序 + var newP = param.OrderBy(m => m.Key).ToDictionary(m => m.Key, n => n.Value); + var pJosn = JsonHelper.ToJson(newP); + + //二次排序 + var enStrList = new string[] { appId, pJosn, appSecret, timestamps }; + Array.Sort(enStrList, string.CompareOrdinal); + + //拼接 + var enStr = string.Join("", enStrList); + //md5 加密 + return GetMd5Hash(enStr); + } + /// + /// 计算 md5 + /// + /// + /// + private static string GetMd5Hash(string enCode) + { + string res = ""; + byte[] data = Encoding.GetEncoding("utf-8").GetBytes(enCode); + MD5 md5 = MD5.Create(); + byte[] bytes = md5.ComputeHash(data); + for (int i = 0; i < bytes.Length; i++) + { + res += bytes[i].ToString("x2"); + } + return res; + } + /// + /// 获取时间戳 + /// + /// + public static string GetTimeStamp() + { + TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds).ToString(); + } + } +} diff --git a/code/DG.Tool/TimeHelper.cs b/code/DG.Tool/TimeHelper.cs new file mode 100644 index 0000000..acdada6 --- /dev/null +++ b/code/DG.Tool/TimeHelper.cs @@ -0,0 +1,365 @@ +using System; +using System.Diagnostics; +using System.Globalization; + +namespace DG.Tool +{ + /// + /// 时间类 + /// 1、SecondToMinute(int Second) 把秒转换成分钟 + /// + public class TimeHelper + { + #region Stopwatch计时器 + + /// + /// 计时器开始 + /// + /// + public static Stopwatch TimerStart() + { + Stopwatch watch = new Stopwatch(); + watch.Reset(); + watch.Start(); + return watch; + } + + /// + /// 计时器结束 + /// + /// + /// + public static string TimerEnd(Stopwatch watch) + { + watch.Stop(); + double costtime = watch.ElapsedMilliseconds; + return costtime.ToString(); + } + + #endregion Stopwatch计时器 + + //返回每月的第一天和最后一天 + public static void ReturnDateFormat(int month, out string firstDay, out string lastDay) + { + int year = DateTime.Now.Year + month / 12; + if (month != 12) + { + month = month % 12; + } + switch (month) + { + case 1: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-0" + month + "-31"); + break; + + case 2: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + if (DateTime.IsLeapYear(DateTime.Now.Year)) + lastDay = DateTime.Now.ToString(year + "-0" + month + "-29"); + else + lastDay = DateTime.Now.ToString(year + "-0" + month + "-28"); + break; + + case 3: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString("yyyy-0" + month + "-31"); + break; + + case 4: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-0" + month + "-30"); + break; + + case 5: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-0" + month + "-31"); + break; + + case 6: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-0" + month + "-30"); + break; + + case 7: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-0" + month + "-31"); + break; + + case 8: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-0" + month + "-31"); + break; + + case 9: + firstDay = DateTime.Now.ToString(year + "-0" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-0" + month + "-30"); + break; + + case 10: + firstDay = DateTime.Now.ToString(year + "-" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-" + month + "-31"); + break; + + case 11: + firstDay = DateTime.Now.ToString(year + "-" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-" + month + "-30"); + break; + + default: + firstDay = DateTime.Now.ToString(year + "-" + month + "-01"); + lastDay = DateTime.Now.ToString(year + "-" + month + "-31"); + break; + } + } + + /// + /// 将时间格式化成 年月日 的形式,如果时间为null,返回当前系统时间 + /// + /// 年月日分隔符 + /// + /// + public string GetFormatDate(DateTime dt, char Separator) + { + if (dt != null && !dt.Equals(DBNull.Value)) + { + string tem = string.Format("yyyy{0}MM{1}dd", Separator, Separator); + return dt.ToString(tem); + } + else + { + return GetFormatDate(DateTime.Now, Separator); + } + } + + /// + /// 将时间格式化成 时分秒 的形式,如果时间为null,返回当前系统时间 + /// + /// + /// + /// + public string GetFormatTime(DateTime dt, char Separator) + { + if (dt != null && !dt.Equals(DBNull.Value)) + { + string tem = string.Format("hh{0}mm{1}ss", Separator, Separator); + return dt.ToString(tem); + } + else + { + return GetFormatDate(DateTime.Now, Separator); + } + } + + /// + /// 把秒转换成分钟 + /// + /// + public static int SecondToMinute(int Second) + { + decimal mm = (decimal)((decimal)Second / (decimal)60); + return Convert.ToInt32(Math.Ceiling(mm)); + } + + #region 返回某年某月最后一天 + + /// + /// 返回某年某月最后一天 + /// + /// 年份 + /// 月份 + /// + public static int GetMonthLastDate(int year, int month) + { + DateTime lastDay = new DateTime(year, month, new GregorianCalendar().GetDaysInMonth(year, month)); + int Day = lastDay.Day; + return Day; + } + + #endregion 返回某年某月最后一天 + + #region 返回时间差 + + public static string DateDiff(DateTime DateTime1, DateTime DateTime2) + { + string dateDiff = null; + try + { + //TimeSpan ts1 = new TimeSpan(DateTime1.Ticks); + //TimeSpan ts2 = new TimeSpan(DateTime2.Ticks); + //TimeSpan ts = ts1.Subtract(ts2).Duration(); + TimeSpan ts = DateTime2 - DateTime1; + if (ts.Days >= 1) + { + dateDiff = DateTime1.Month.ToString() + "月" + DateTime1.Day.ToString() + "日"; + } + else + { + if (ts.Hours > 1) + { + dateDiff = ts.Hours.ToString() + "小时前"; + } + else + { + dateDiff = ts.Minutes.ToString() + "分钟前"; + } + } + } + catch + { } + return dateDiff; + } + + #endregion 返回时间差 + + #region 获得两个日期的间隔 + + /// + /// 获得两个日期的间隔 + /// + /// 日期一。 + /// 日期二。 + /// 日期间隔TimeSpan。 + public static TimeSpan DateDiff2(DateTime DateTime1, DateTime DateTime2) + { + TimeSpan ts1 = new TimeSpan(DateTime1.Ticks); + TimeSpan ts2 = new TimeSpan(DateTime2.Ticks); + TimeSpan ts = ts1.Subtract(ts2).Duration(); + return ts; + } + + #endregion 获得两个日期的间隔 + + #region 格式化日期时间 + + /// + /// 格式化日期时间 + /// + /// 日期时间 + /// 显示模式 + /// 0-9种模式的日期 + public static string FormatDate(DateTime dateTime1, string dateMode) + { + switch (dateMode) + { + case "0": + return dateTime1.ToString("yyyy-MM-dd"); + + case "1": + return dateTime1.ToString("yyyy-MM-dd HH:mm:ss"); + + case "2": + return dateTime1.ToString("yyyy/MM/dd"); + + case "3": + return dateTime1.ToString("yyyy年MM月dd日"); + + case "4": + return dateTime1.ToString("MM-dd"); + + case "5": + return dateTime1.ToString("MM/dd"); + + case "6": + return dateTime1.ToString("MM月dd日"); + + case "7": + return dateTime1.ToString("yyyy-MM"); + + case "8": + return dateTime1.ToString("yyyy/MM"); + + case "9": + return dateTime1.ToString("yyyy年MM月"); + + default: + return dateTime1.ToString(); + } + } + + #endregion 格式化日期时间 + + #region 得到随机日期 + + /// + /// 得到随机日期 + /// + /// 起始日期 + /// 结束日期 + /// 间隔日期之间的 随机日期 + public static DateTime GetRandomTime(DateTime time1, DateTime time2) + { + Random random = new Random(); + DateTime minTime = new DateTime(); + DateTime maxTime = new DateTime(); + + TimeSpan ts = new TimeSpan(time1.Ticks - time2.Ticks); + + // 获取两个时间相隔的秒数 + double dTotalSecontds = ts.TotalSeconds; + int iTotalSecontds = 0; + + if (dTotalSecontds > Int32.MaxValue) + { + iTotalSecontds = Int32.MaxValue; + } + else if (dTotalSecontds < Int32.MinValue) + { + iTotalSecontds = Int32.MinValue; + } + else + { + iTotalSecontds = (int)dTotalSecontds; + } + + if (iTotalSecontds > 0) + { + minTime = time2; + maxTime = time1; + } + else if (iTotalSecontds < 0) + { + minTime = time1; + maxTime = time2; + } + else + { + return time1; + } + + int maxValue = iTotalSecontds; + + if (iTotalSecontds <= Int32.MinValue) + maxValue = Int32.MinValue + 1; + + int i = random.Next(Math.Abs(maxValue)); + + return minTime.AddSeconds(i); + } + + #endregion 得到随机日期 + + /// + /// DateTime时间格式转换为Unix时间戳格式 + /// + /// + /// + public static int ConvertDateTimeInt(System.DateTime time) + { + System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); + return (int)(time - startTime).TotalSeconds; + } + + /// + /// 时间戳转为C#格式时间 + /// + /// + /// + public static DateTime GetTimeFromLinuxTime(long timeStamp) + { + System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0); + dtDateTime = dtDateTime.AddMilliseconds(timeStamp).ToLocalTime(); + return dtDateTime; + } + } +} \ No newline at end of file diff --git a/code/DG.Tool/Utility.cs b/code/DG.Tool/Utility.cs new file mode 100644 index 0000000..987bc2e --- /dev/null +++ b/code/DG.Tool/Utility.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace DG.Tool +{ + public static class Utility + { + /// + /// 获取随机数 + /// + /// + /// + public static string CreateRandomSatl(int codeCount) + { + string allChar = "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,w,x,y,z"; + string[] allCharArray = allChar.Split(','); + string randomCode = ""; + int temp = -1; + Random rand = new Random(); + for (int i = 0; i < codeCount; i++) + { + if (temp != -1) + { + rand = new Random(i * temp * ((int)DateTime.Now.Ticks)); + } + int t = rand.Next(50); + if (temp == t) + { + return CreateRandomSatl(codeCount); + } + temp = t; + randomCode += allCharArray[t]; + } + return randomCode; + } + public static string Sha512(string value) + { + SHA512 s512 = new SHA512Managed(); + byte[] bit_pwd = Encoding.Default.GetBytes(value); + byte[] bit_shapsw = s512.ComputeHash(bit_pwd); + string shapsw = Convert.ToBase64String(bit_shapsw); + return shapsw; + } + public static int PasswordStrength(string password) + { + if (string.IsNullOrWhiteSpace(password)) return -1; + //字符统计 + int iNum = 0, iLtt = 0, iSym = 0; + foreach (char c in password) + { + if (c >= '0' && c <= '9') iNum++; + else if (c >= 'a' && c <= 'z') iLtt++; + else if (c >= 'A' && c <= 'Z') iLtt++; + else iSym++; + } + if (password.Length < 6) return 3; //长度小于6的密码 + if (iLtt == 0 && iSym == 0) return 1; //纯数字密码 + if (iNum == 0 && iSym == 0) return 2; //纯字母密码 + return 0; //正确 + } + + + public static string GetSendMsgModel(string strJson, string modelParam) + { + string key = string.Empty; + string values = string.Empty; + strJson = strJson.Replace(",\"", "*\"").Replace("\":", "\"#").ToString(); + + Regex regex = new Regex(@"(?<={)[^}]+(?=})"); + MatchCollection mc = regex.Matches(strJson); + Dictionary list = new Dictionary(); + for (int i = 0; i < mc.Count; i++) + { + + string strRow = mc[i].Value; + string[] strRows = strRow.Split('*'); + foreach (string str in strRows) + { + string[] strCell = str.Split('#'); + + key = strCell[0].Replace("\"", ""); + values = strCell[1].Replace("\"", ""); + list.Add(key, values); + } + } + foreach (var item in list) + { + modelParam = modelParam.Replace("${" + item.Key + "}", item.Value); + } + + return modelParam; + } + + public static string DateEnToCn(string date) + { + string datename = ""; + date = date.ToLower(); + switch (date) + {//--Monday 周一 Tuesday 周二 Wednesday 周三 Thursday 周四 Friday 周五 Saturday 周六 Sunday 周日 + case "monday": datename = "星期一"; break; + case "tuesday": datename = "星期二"; break; + case "wednesday": datename = "星期三"; break; + case "thursday": datename = "星期四"; break; + case "friday": datename = "星期五"; break; + case "saturday": datename = "星期六"; break; + case "sunday": datename = "星期日"; break; + } + return datename; + } + + /// + /// 将c# DateTime时间格式转换为Unix时间戳格式 + /// + /// 时间 + /// long + public static long ConvertDateTimeToInt(System.DateTime time) + { + System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1, 0, 0, 0, 0)); + long t = (time.Ticks - startTime.Ticks) / 10000; //除10000调整为13位 + return t; + } + /// + /// 时间戳转为C#格式时间 + /// + /// + /// + public static DateTime ConvertStringToDateTime(string timeStamp) + { + DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); + long lTime = long.Parse(timeStamp + "0000"); + TimeSpan toNow = new TimeSpan(lTime); + return dtStart.Add(toNow); + } + + /// + /// 获取文件的MD5码 + /// + /// 传入的文件名(含路径及后缀名) + /// + public static string GetMD5HashFromFile(string fileName) + { + try + { + FileStream file = new FileStream(fileName, System.IO.FileMode.Open); + MD5 md5 = new MD5CryptoServiceProvider(); + byte[] retVal = md5.ComputeHash(file); + file.Close(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < retVal.Length; i++) + { + sb.Append(retVal[i].ToString("x2")); + } + return sb.ToString(); + } + catch (Exception ex) + { + throw new Exception("GetMD5HashFromFile() fail,error:" + ex.Message); + } + } + + #region 秒转换小时 SecondToHour + /// + /// 秒转换小时 + /// + /// + /// + public static string SecondToHour(double time) + { + string str = ""; + int hour = 0; + int minute = 0; + int second = 0; + second = Convert.ToInt32(time); + + if (second > 60) + { + minute = second / 60; + second = second % 60; + } + if (minute > 60) + { + hour = minute / 60; + minute = minute % 60; + } + return (hour + "小时" + minute + "分钟" + + second + "秒"); + } + #endregion + + /// + ///时间格式化 + /// + /// 返回字符串的类型(默认4) + ///例子: 2014/05/08 19:14:17 + ///type=1 2014/05/08 没有时分秒 + ///type=2 2014/05/08 19:14 没有秒 + ///type=3 14/05/08 19:14 没有年前两位和秒 + ///type=4 05/08 19:14 没有年和秒 + /// + /// + public static string ToUnityString(this DateTime dt, int type = 4) + { + if (dt == DateTime.MinValue) + return ""; + string formartStr = string.Empty; + switch (type) + { + case 1: formartStr = "yyyy/MM/dd"; break; + case 2: formartStr = "yyyy/MM/dd HH:mm"; break; + case 3: formartStr = "yy/MM/dd HH:mm"; break; + case 4: formartStr = "MM/dd HH:mm"; break; + case 5: formartStr = "yyyyMM"; break; + case 6: formartStr = "yyyy-MM-dd"; break; + case 7: formartStr = "yyyy-MM-dd HH:mm:ss"; break; + } + return dt.ToString(formartStr); + + } + + /// + ///时间格式化 + /// + /// 返回字符串的类型(默认4) + ///例子: 2014/05/08 19:14:17 + ///type=1 2014/05/08 没有时分秒 + ///type=2 2014/05/08 19:14 没有秒 + ///type=3 14/05/08 19:14 没有年前两位和秒 + ///type=4 05/08 19:14 没有年和秒 + /// + /// + public static string ToUnityString(this DateTime? dt, int type = 4) + { + if (dt == null || dt.Value == DateTime.MinValue) + return ""; + string formartStr = string.Empty; + switch (type) + { + case 1: formartStr = "yyyy/MM/dd"; break; + case 2: formartStr = "yyyy/MM/dd HH:mm"; break; + case 3: formartStr = "yy/MM/dd HH:mm"; break; + case 4: formartStr = "MM/dd HH:mm"; break; + case 5: formartStr = "yyyyMM"; break; + case 6: formartStr = "yyyy-MM-dd"; break; + } + return dt.Value.ToString(formartStr); + } + } +} diff --git a/code/EmployeeDepartmentDetailWorker/EmployeeDepartmentDetailServices.csproj b/code/EmployeeDepartmentDetailWorker/EmployeeDepartmentDetailServices.csproj index 913015f..4c36d54 100644 --- a/code/EmployeeDepartmentDetailWorker/EmployeeDepartmentDetailServices.csproj +++ b/code/EmployeeDepartmentDetailWorker/EmployeeDepartmentDetailServices.csproj @@ -30,9 +30,6 @@ - - - @@ -50,6 +47,9 @@ + + + diff --git a/code/ResourceFlowWorker/ResourceFlowWorker.csproj b/code/ResourceFlowWorker/ResourceFlowWorker.csproj index 8c4777e..65debbe 100644 --- a/code/ResourceFlowWorker/ResourceFlowWorker.csproj +++ b/code/ResourceFlowWorker/ResourceFlowWorker.csproj @@ -16,9 +16,6 @@ - - - @@ -34,6 +31,10 @@ + + + + diff --git a/code/ToDoWorker/ToDoWorker.csproj b/code/ToDoWorker/ToDoWorker.csproj index 2bdf552..fbac55c 100644 --- a/code/ToDoWorker/ToDoWorker.csproj +++ b/code/ToDoWorker/ToDoWorker.csproj @@ -13,11 +13,6 @@ - - - - - @@ -33,6 +28,11 @@ + + + + + diff --git a/code/WeworkUserWorker/WeworkUserWorker.csproj b/code/WeworkUserWorker/WeworkUserWorker.csproj index d6f4f67..11d9a29 100644 --- a/code/WeworkUserWorker/WeworkUserWorker.csproj +++ b/code/WeworkUserWorker/WeworkUserWorker.csproj @@ -10,10 +10,6 @@ - - - - @@ -29,6 +25,11 @@ + + + + + diff --git a/code/Zxd.Core.Domain/Dto/Dg/Sms_RecordsDto.cs b/code/Zxd.Core.Domain/Dto/Dg/Sms_RecordsDto.cs new file mode 100644 index 0000000..10c071d --- /dev/null +++ b/code/Zxd.Core.Domain/Dto/Dg/Sms_RecordsDto.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Zxd.Core.Domain.Dto.Dg +{ + + public class Sms_RecordsDto + { + + /// + /// 渠道名称 + /// + public string channel_name { get; set; } + public int id { get; set; } + /// + /// 资源ID + /// + public string umid { get; set; } + /// + /// 脱敏手机号 + /// + public string mobile { get; set; } + + /// + /// 内部单号 + /// + public string trade_no { get; set; } + /// + /// 外部单号 + /// + public string out_trade_no { get; set; } + + /// + /// 模板类型ID + /// + public int sms_temp_type_id { get; set; } + + /// + /// 渠道ID + /// + public int channel_id { get; set; } + /// + /// 渠道商模板ID + /// + public string template_id { get; set; } + + + /// + /// 短信内容 + /// + public string sms_content { get; set; } + /// + /// 发送状态 0: 失败 1: 成功 + /// + public bool send_state { get; set; } + /// + /// 发送时间 + /// + public DateTime send_time { get; set; } + /// + /// 回执返回值 + /// + + public string receipt_code { get; set; } + /// + /// 回执状态码 + /// + public string receipt_status { get; set; } + + /// + /// 回执接收时间 + /// + public DateTime receipt_time { get; set; } + /// + /// 回执成功 0:失败 1:成功 + /// + public bool receipt_success { get; set; } + } +} diff --git a/code/Zxd.Core.Domain/Impl/ISmsRecordDomain.cs b/code/Zxd.Core.Domain/Impl/ISmsRecordDomain.cs new file mode 100644 index 0000000..295bacf --- /dev/null +++ b/code/Zxd.Core.Domain/Impl/ISmsRecordDomain.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zxd.Core.Domain.Dto.Dg; + +namespace Zxd.Core.Domain.Impl +{ + public interface ISmsRecordDomain : IScopedDependency + { + Task> GetSmsRecordsList(string umid); + } +} diff --git a/code/Zxd.Core.Domain/SmsRecordDomain.cs b/code/Zxd.Core.Domain/SmsRecordDomain.cs new file mode 100644 index 0000000..92f1f22 --- /dev/null +++ b/code/Zxd.Core.Domain/SmsRecordDomain.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zxd.Core.Domain.Dto.Dg; +using Zxd.Entity.dg; + +namespace Zxd.Core.Domain +{ + + public class SmsRecordDomain : ISmsRecordDomain + { + + private readonly IBaseRepository _dgRepository; + public SmsRecordDomain( + IBaseRepository dgRepository) + { + _dgRepository = dgRepository; + } + public async Task> GetSmsRecordsList(string umid) + { + if (string.IsNullOrEmpty(umid)) + { + throw new ApiException("umid不能为空"); + } + DateTime dateTime = DateTime.Now.AddDays(-10);//超过10天的数据不做查找 + var query = _dgRepository.GetRepository().Query(); + var channel = _dgRepository.GetRepository().Query(); + var xx = (from a in query + join b in channel on a.channel_id equals b.id + where a.umid == umid && a.send_time >= dateTime + select new Sms_RecordsDto() + { + channel_id = a.channel_id, + id = a.id, + channel_name = b.channel_name, + mobile = a.phone, + out_trade_no = a.out_trade_no, + receipt_code = a.receipt_code, + receipt_status = a.receipt_status, + receipt_success = a.receipt_success, + receipt_time = a.receipt_time, + send_state = true, + send_time = a.send_time, + sms_content = a.sms_content, + sms_temp_type_id = 0, + template_id = a.template_id, + trade_no = a.trade_no, + umid = a.umid, + } + ).OrderByDescending(m => m.id); + + + return xx.ToList(); + } + } +} diff --git a/code/Zxd.Core.Domain/Zxd.Core.Domain.csproj b/code/Zxd.Core.Domain/Zxd.Core.Domain.csproj index 720881d..514b7bb 100644 --- a/code/Zxd.Core.Domain/Zxd.Core.Domain.csproj +++ b/code/Zxd.Core.Domain/Zxd.Core.Domain.csproj @@ -15,12 +15,7 @@ - - - - - @@ -31,10 +26,20 @@ + + + + + + + + ..\..\lib\Crm.Core.Shared.dll + + diff --git a/code/Zxd.Core.Shared/Zxd.Core.Shared.csproj b/code/Zxd.Core.Shared/Zxd.Core.Shared.csproj index d6283bc..e375b8e 100644 --- a/code/Zxd.Core.Shared/Zxd.Core.Shared.csproj +++ b/code/Zxd.Core.Shared/Zxd.Core.Shared.csproj @@ -9,7 +9,7 @@ - + diff --git a/code/Zxd.Core.WebApi/Controllers/ActivityController.cs b/code/Zxd.Core.WebApi/Controllers/ActivityController.cs index 6a5af63..6aaa1f8 100644 --- a/code/Zxd.Core.WebApi/Controllers/ActivityController.cs +++ b/code/Zxd.Core.WebApi/Controllers/ActivityController.cs @@ -1,4 +1,5 @@ -using Zxd.Core.Domain.Dto.Activity; +using Crm.Core.Shared; +using Zxd.Core.Domain.Dto.Activity; namespace Zxd.Core.WebApi.Controllers { diff --git a/code/Zxd.Core.WebApi/Controllers/CustomerController.cs b/code/Zxd.Core.WebApi/Controllers/CustomerController.cs index 1fe0ead..cb9d5b2 100644 --- a/code/Zxd.Core.WebApi/Controllers/CustomerController.cs +++ b/code/Zxd.Core.WebApi/Controllers/CustomerController.cs @@ -1,4 +1,5 @@ using Zxd.Core.Domain.Dto.Crm; +using Zxd.Core.Domain.Dto.Dg; using Zxd.Core.Domain.Dto.Wework; using Zxd.Core.Domain.Dto.Zxd.Order; using static Zxd.Domain.Impl.ICustomerDomain; @@ -10,11 +11,13 @@ namespace Zxd.Core.WebApi.Controllers { private readonly ICustomerDomain _customerDomain; private readonly IOrderDomain _orderDomain; + private readonly ISmsRecordDomain _smsRecordDomain; - public CustomerController(ICustomerDomain customerDomain, IOrderDomain orderDomain) + public CustomerController(ICustomerDomain customerDomain, IOrderDomain orderDomain, ISmsRecordDomain smsRecordDomain) { _customerDomain = customerDomain; _orderDomain = orderDomain; + _smsRecordDomain = smsRecordDomain; } @@ -92,11 +95,21 @@ namespace Zxd.Core.WebApi.Controllers public async Task> ExtUserBandGetAsync(string? ResId) { return await _customerDomain.ExtUserBandGetAsync(ResId); - }/// - - /// 根据外部联系人Userid获取resid - /// - /// + } + /// + /// 获取验证码列表 + /// + /// + /// + [HttpGet("GetSmsRecord")] + public async Task> GetSmsRecord(string umid) + { + return await _smsRecordDomain.GetSmsRecordsList(umid); + } + /// + /// 根据外部联系人Userid获取resid + /// + /// [HttpPost("GetResidByExtUser")] public async Task> GetResidByExtUser([FromBody] string extUser) { diff --git a/code/Zxd.Core.WebApi/Zxd.Core.WebApi.csproj b/code/Zxd.Core.WebApi/Zxd.Core.WebApi.csproj index 9c9b72c..0d26102 100644 --- a/code/Zxd.Core.WebApi/Zxd.Core.WebApi.csproj +++ b/code/Zxd.Core.WebApi/Zxd.Core.WebApi.csproj @@ -24,6 +24,11 @@ + + + ..\..\lib\Crm.Core.Shared.dll + + diff --git a/code/Zxd.Core.sln b/code/Zxd.Core.sln index 25c7f02..d7ed138 100644 --- a/code/Zxd.Core.sln +++ b/code/Zxd.Core.sln @@ -53,6 +53,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DG.Kafka", "DG.Kafka\DG.Kaf EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DG.Kafka.Worker", "DG.Kafka.Worker\DG.Kafka.Worker.csproj", "{1E97050D-CA19-46A7-92A2-7BFA09006971}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DG.Core", "DG.Core\DG.Core.csproj", "{A32F8817-49CB-4729-97A5-7E7615760A41}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DG.EntityFramework", "DG.EntityFramework\DG.EntityFramework.csproj", "{D15B60AC-C477-411D-828D-ABD65568ABF8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DG.Redis", "DG.Redis\DG.Redis.csproj", "{2ADFE051-3B5E-4828-BF5D-3E90A887E7F1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DG.Tool", "DG.Tool\DG.Tool.csproj", "{F04DB682-8C17-4781-9C91-ACC7E953371A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +139,22 @@ Global {1E97050D-CA19-46A7-92A2-7BFA09006971}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E97050D-CA19-46A7-92A2-7BFA09006971}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E97050D-CA19-46A7-92A2-7BFA09006971}.Release|Any CPU.Build.0 = Release|Any CPU + {A32F8817-49CB-4729-97A5-7E7615760A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A32F8817-49CB-4729-97A5-7E7615760A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A32F8817-49CB-4729-97A5-7E7615760A41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A32F8817-49CB-4729-97A5-7E7615760A41}.Release|Any CPU.Build.0 = Release|Any CPU + {D15B60AC-C477-411D-828D-ABD65568ABF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D15B60AC-C477-411D-828D-ABD65568ABF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D15B60AC-C477-411D-828D-ABD65568ABF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D15B60AC-C477-411D-828D-ABD65568ABF8}.Release|Any CPU.Build.0 = Release|Any CPU + {2ADFE051-3B5E-4828-BF5D-3E90A887E7F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2ADFE051-3B5E-4828-BF5D-3E90A887E7F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2ADFE051-3B5E-4828-BF5D-3E90A887E7F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2ADFE051-3B5E-4828-BF5D-3E90A887E7F1}.Release|Any CPU.Build.0 = Release|Any CPU + {F04DB682-8C17-4781-9C91-ACC7E953371A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F04DB682-8C17-4781-9C91-ACC7E953371A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F04DB682-8C17-4781-9C91-ACC7E953371A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F04DB682-8C17-4781-9C91-ACC7E953371A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -153,6 +177,10 @@ Global {BB3183D9-38F5-4860-ADE6-735EF9F8EF7F} = {19629268-FAF2-42CA-A7D4-D5CCE739FF4A} {FAA4E537-81BF-462E-BE19-3194B7DD7D47} = {5E8706D3-CCF5-4654-B3AC-00DF95CED9EE} {1E97050D-CA19-46A7-92A2-7BFA09006971} = {5E8706D3-CCF5-4654-B3AC-00DF95CED9EE} + {A32F8817-49CB-4729-97A5-7E7615760A41} = {5E8706D3-CCF5-4654-B3AC-00DF95CED9EE} + {D15B60AC-C477-411D-828D-ABD65568ABF8} = {5E8706D3-CCF5-4654-B3AC-00DF95CED9EE} + {2ADFE051-3B5E-4828-BF5D-3E90A887E7F1} = {5E8706D3-CCF5-4654-B3AC-00DF95CED9EE} + {F04DB682-8C17-4781-9C91-ACC7E953371A} = {5E8706D3-CCF5-4654-B3AC-00DF95CED9EE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6C94942A-1B07-4CF7-BB70-E63A968E909D} diff --git a/code/Zxd.Crm.Domain/Zxd.Crm.Domain.csproj b/code/Zxd.Crm.Domain/Zxd.Crm.Domain.csproj index b7eb951..3de4e8d 100644 --- a/code/Zxd.Crm.Domain/Zxd.Crm.Domain.csproj +++ b/code/Zxd.Crm.Domain/Zxd.Crm.Domain.csproj @@ -11,10 +11,7 @@ - - - @@ -25,6 +22,9 @@ + + + diff --git a/code/Zxd.Domain/Zxd.Domain.csproj b/code/Zxd.Domain/Zxd.Domain.csproj index e67b683..2274033 100644 --- a/code/Zxd.Domain/Zxd.Domain.csproj +++ b/code/Zxd.Domain/Zxd.Domain.csproj @@ -12,10 +12,6 @@ - - - - @@ -26,6 +22,9 @@ + + + diff --git a/code/Zxd.Entity/dg/Sms_Channel.cs b/code/Zxd.Entity/dg/Sms_Channel.cs new file mode 100644 index 0000000..63a05d9 --- /dev/null +++ b/code/Zxd.Entity/dg/Sms_Channel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Zxd.Entity.dg +{ + [Table("t_sms_channel")] + public class Sms_Channel + { + public int id { get; set; } + public string channel_name { get; set; } + } +} diff --git a/code/Zxd.Entity/dg/Sms_Records.cs b/code/Zxd.Entity/dg/Sms_Records.cs new file mode 100644 index 0000000..ddb55ef --- /dev/null +++ b/code/Zxd.Entity/dg/Sms_Records.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Zxd.Entity.dg +{ + [Table("t_sms_records")] + public class Sms_Records + { + [Key] + public int id { get; set; } + /// + /// 资源ID + /// + public string umid { get; set; } + /// + /// 脱敏手机号 + /// + public string phone { get; set; } + + /// + /// 内部单号 + /// + public string trade_no { get; set; } + /// + /// 外部单号 + /// + public string out_trade_no { get; set; } + + ///// + ///// 模板类型ID + ///// + //public int sms_temp_type_id { get; set; } + + /// + /// 渠道ID + /// + public int channel_id { get; set; } + /// + /// 渠道商模板ID + /// + public string template_id { get; set; } + + + /// + /// 短信内容 + /// + public string sms_content { get; set; } + ///// + ///// 发送状态 0: 失败 1: 成功 + ///// + //public bool send_state { get; set; } + /// + /// 发送时间 + /// + public DateTime send_time { get; set; } + /// + /// 回执返回值 + /// + + public string receipt_code { get; set; } + /// + /// 回执状态码 + /// + public string receipt_status { get; set; } + + /// + /// 回执接收时间 + /// + public DateTime receipt_time { get; set; } + /// + /// 回执成功 0:失败 1:成功 + /// + public bool receipt_success { get; set; } + + } +} diff --git a/code/Zxd.EntityFramework/DgDbContext.cs b/code/Zxd.EntityFramework/DgDbContext.cs new file mode 100644 index 0000000..8b335dc --- /dev/null +++ b/code/Zxd.EntityFramework/DgDbContext.cs @@ -0,0 +1,45 @@ +using DG.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zxd.Entity.dg; + +namespace Zxd.EntityFramework +{ + + public class DgDbContext : DbContext + { + public DgDbContext(DbContextOptions options) : base(options) + + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") + { + var loggerFactory = new LoggerFactory(); + loggerFactory.AddProvider(new EFLoggerProvider()); + optionsBuilder.UseLoggerFactory(loggerFactory); + } + optionsBuilder.ConfigureWarnings(b => b.Ignore(CoreEventId.ContextInitialized)); + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + + } + + public DbSet Sms_Records { get; set; } + public DbSet Sms_Channel { get; set; } + + + + } +} diff --git a/code/Zxd.EntityFramework/Zxd.EntityFramework.csproj b/code/Zxd.EntityFramework/Zxd.EntityFramework.csproj index b6e282c..dcd79fb 100644 --- a/code/Zxd.EntityFramework/Zxd.EntityFramework.csproj +++ b/code/Zxd.EntityFramework/Zxd.EntityFramework.csproj @@ -7,12 +7,12 @@ - + diff --git a/lib/Crm.Core.Shared.dll b/lib/Crm.Core.Shared.dll new file mode 100644 index 0000000000000000000000000000000000000000..51c0ad1bad4dbc5163b2436888a28e0954d3c2a7 GIT binary patch literal 7168 zcmeHLYiu0V6+Scj@cQke^FC8z~Su@etShr4v9_lCBqlM^8 zx4-bmpto6C8?V>8h?YT#`s$P2IL5K}Vkc@5Tle#3f%BJb8VH@Y5RKi#P5HlaRFoxb zpFrGwZ1fV{!i8A4jS)3KyJHj4$U@({@iFaQsf(6zeYcXYY<+)RuA1N z1_TV)7sP-N2?R0FVH0Z!ARtj+5Ca0Hk1vQ3 z1jmI=e`Keot|HHg!!>L%7d6(w>tq=3H*^Cr%c} zj+@i;SJ=piI&QpqAcbQX`Oe^KcI>q5ad6&n`LqLVGqtsR(i>Vg&rzO3$W!hEq(|!& zXKffeH^sncEp~2Oy%YT)%$yg1*F%p*qGW^_cZ42_G}8}p>7#Tk^guXDyTcvOe-UDQ zNbuW&%SF$MT`v*J;a{QsKO>A+gc#RG88u?O3tsMTB?C0{uC>rV^xs8#=GZXeFyqm- z(1(#x59~0M7J588>1T7?-iTWv#9W&74!5Kus0Ozph>DB zy`8Y#8@n2FtcL0p+lB=)My-k^F@hLfs@Md4wX{yLBkS>>155d94 zNDJ5r#V(+_&GbdZ{(|Z@)7KRHF?w&IQ;Pi>y|>Ue728b(+<{Lic7V!Y&nPy5{+81B z75fzWTS`CinPkRyv__xBJu@GB-a%j7^8F~abTXnLJD#^60@l%0z!uslxK%JII3ReF z;Ku|FV3ekSZIl=LxL^bEyl9ZStU>Ow2I9Lcfou0Lu$5+jtLRx^2mJ`Rf!+dkQ>&Js z?Gy+0(KW!2ik=2`qe|CneRL~r0oDNxu`>!;$xw#+wBw@Ot&sJ*q2H#>i1Mi5Ddcy( zb_y{c)E<|}XGMQb@Ep7kYv;r}E6N3Fb3yEv<=mIWOB&~+35GO|8PYgrT$FafUcscG zA$VMHM(|O=vx3hF&PtqFu`dXc&XGx%z69ff?Sj36Nx?BeLvUJfM)0iQtRRIrPF%28 za7=Joa7OT~Acdu+V6Whq;I!b3;90?0L5fIwL0+w+SZ`|Z7sI`WUqVql5@}5T4Di|T zy}+I*+edY_yXXY)9%AK~;5pGx3O**dO4_f8aQoLK!b_sxN)JH!G<5!@6_TDquxsR* z5d-R2ZEB#`0d-_k3%d!Z<9@D(&8v!zyPy&FQlO4G+6;RcP^UI{G(32hz+NHGnw3Bu z_e&e}OMp79f=8oEfjX^*N24`Bo#OCl)DF}!Z&t(Z0P3`kE`xnJP{(ts9rl$#9nYT* z*jE8pQFxly0Ds2;+x^(1V)?L%Ucn>N-x_c}w!LM`GR7@)Y$FYu-mvMGEf4l}gLbxT znLF@fJ7;TBV=GzUn>}dokb)-h&o922IyBau;!o zTOiM&Tkpq~H}V>ghjP$~)#r{$gh#wSt>l+|#f*;h65^h<(T`F7_rf z8D#zr&i)9pIs>B zEz`NtESiqtRlDXI-OSTsj#0`SFXwW6`&6{xhN>t@*EI{{)|8B{B99mjGTwz(bn}Sq z9I7~i_PdbHe$(Mgw%9pPoUomO;lXKG@7Su(E>7fgWejIt5Pzs54x5&7R4jLKCqoWa zfs9vi1cO6desQiif}A2O5kZzb>m2bY98cdPFQA!>qrZ`OL;b> zT^oyM$u7DkFI_D8la?)iX3o3))byOMB{H7v1Vu>Yja<=oy?n+EBvnkTl>Ssro6g}p zZh18kQ70_hpuuJ0nz}TY&p5VgPk1;lYbG&Yrrf+sbJxh9h^}PmuK|*rGWU(&hG7SV zSLF*huK`LDQywknpa1(yzv+54^>pv6=Q@wwSDu?q+=g( zwV1qTqFs+hD-(PBNBB9FES0WSv-A4H*Cx6UplR9My`0LsCCixN2LdhAsCmJJ!RH~3 zYH;V7hHLs{BR&gY$A=KSSqZ^=IX}$tYg;F}0>AAK@!S1~-k7v3`^X>)pD$S^gxZ1P zr0L|bU~#iO_*nN|%@EU%EJwEKtK;_HNE;kNUo?UJr<<_zcR#gpOMcV168p8?*xQst z9r(LM^){SNrDtD%A$#TLo)~<**m&P}e))$Xj?uSmGGBOXk+<7eBqzqVoK_B)=~a1Rm=wnx~G z2W-5ZI6JJ!u-im)5=D1HfJWS66x_{MuWUUyt02zu%zZ z@F$t7u%f1<)qXO_L7Y6kWtoUs#Qd?vzYf3W`D?9%7DaO5H^@Uf8_eHMher`qLrxxA z<)P;;5^0OH>B6}Ezs)qxHK1NpNyD3;jko8L^f84I8DO}nu=OEk0q=?2!hx1RKa=pY zorQn5oF8PKg6iVUkMk(a%k#bBbYUIQ=JMW;I1VCJ#<>wacVYLB-0w29=Sq3fwuqdp zs?6WLzXWo(@JIOVuOrSL#K=hm9%l*f*F1t8M#|r{pp~dHgYMWW?ohH?_KvUypw3is`~teRjbsDXp7W0iJH0?YXMi^!YJdI&+{u?Ifv{0 Y0o#x;e;0Nu0>}Fp3=%I|(5P!Nj(l#1K322Lwt|MV2Ox8d)+k zauO4qiiOZ-yD3cz?55CN(n3$$vvlEXdk*wt4=io>P|7(ArF**Ufx>3DJ?*wzc4^C@ z1+%~VX2z0ZL;DARZ9DedxnJ+Td+)pN&6^qZe&7@2AtEo%citg-3|Bt3EBM}^1abB9 zuT|4m0?#%+CU!sDICMB`MaRs-h?yFVrc?QRp%@)DqULx$n$1TOdj_MUg^UrestT@h zOz-U`+ATcPf4=$+x3^blMKmN@iJHODY;MyoT%$M-;v}k3xU~Id2K%p$2LQq6!$Sv8 zu`2(UuMWv9953u7+QY~}qNSXOi;oh~Jn(+LgJ|et*?ZAZBE3ScgXR$2BVII)7eTCz z0Yq_ftd8Hn)DeYOanmx>ppK}kL^UAl{DkJ z;}l;c`6#o7=%Wop;$p==kX|8U3y6Z$JnSHH(V0tOZuLUX{gW{2ZEkl3o>??}_973p ztr$7<2lcRw)v-B}o=G31GFF9>D1-n^h{p;GA&miRnZ)3ELyWfUhP)qMJNUvEjAHDi(s>B1Ac@pn4Y$Q*w{w3Z39CI z8_a_Z^amSE*fyGxkr*@(!ZLXRv3Q$TAV&&e-?`+GurQtULsnSs27vKV32n}w#ML{c zt>oCF5L=A^CDvw(x@%D(U?4|!7YYFbgJLrj0tV7$GaSU}h;u2a7XH9Fji5W;sOAM)KsS2D#Jyf>c;wr5!v81e8>!q-_R$Ex>Z>bM!VgILVb&_E{ zwiQ}U6!tH=q1GSvhqaGq*S{iTSAtvX3;SY7RZEks4YW`=fI*&4D=oo1c>sBZFJXCt z%LQdGTV%`CBu{V=ao1YmDi{hyM64ax%eKpftAmrQEB%x6`7%%1H6|i5hLLNbmj~nt zmW1T3e3`1l3=*==X$C{vVTfL?uae8BY)i^OAl9My3udxooy=_(T#y*yoJIARX+o=r zWJ#-+)?&brPQ<}eC#Wsjq+`C-$Gt87mXn*GM)}j`pE8suc#56C(0Z58tGBvf9l@E% z)>tgTUBSU_TzPV|fH1GFkT{KD$6G)@T5PA?!o;=Qd>1!InYP5PVKHfI*_TCee@k#P z-=sD_k79Klf#2SRQ*z;yn5VBj?sAoTk@wBL8|QZKT6XWH=dr$228Q)lgF97$EkA~v zu894N?M4Gt54GNi*Di=1svtP&GVGm1Td%D3Nqgz?Mtm{UQ478F3DpNnYrXPTzE+e4 zH-@?G>`mqHRK0KUAA8UEF&cKa1x!=B-fOjp=Ek=Om21bRT@;@-9p59$hKf&{J3h^h zk6t>3oYwkf*guIi&086;Ji(kL?_1O#n>*@WRg^DR_WVB1guCYMRLulua4#w)J)+yG z?vnM>sn#z_weF_+bBi)sU#=fCyNJ4iK_88v>YD8qhdouTHAja=VE zcjJtpKtGR@M~BxJ=A#-Y2xr4Mabs6}tvSxO7_G#2zw#Qi*g~DNtPf2}djQI^WuC%b zX5iZITnA<-+~ScZ2EmC90j#aI2~XiVP%UC%Gj$GL(rWx-s4w-_W|D4R;E@ zOXmPfSbA5U#6A<(R~R2Gn%Vq_#TwtkKITDOS6(+rV_1xl3|hJKx}F5CCD1xno0Xlp z!Z2t)kOr}D(Pu*e>^A7PVgq5h-&YHw5cn% z#hrxr6TI&sq)J@@HI-=G$}ji+r>@bL6s+?xUi3V#*HSf%YUK6a=F{jc@7tP2A5(Cb zf`)?MRq(4GmUei!?HXSg_`d`h?)NbK3Gg6&i88SBqVLXtM*k(5|2?Ju6ZECgBa+)b zq%*t{_fd^L>u0>d%hEd(ze;I-8nBv5+5zZ%g~D2d=KEg&{$S{BZ6V#N+I}B=jqayQ zg+|x=*|!bws+#^DtrpX3er~%$Y2KqW)5@Rq8n=2Pv`8$Y8)VWSq0a;S^J~N~)?D+&0&F_5`+I{J;eDQN+s>k8urMK)$yC z_tI^sir>i{dN)SK2YC&}LQH9V0|V9T+Y3qub{pEg3J<#RtZ)f=K=r{iQ%e-}HuSrp zw~RcEHMCO6>Znmsf1zXxX_e9(SJYx!Lrru=;ODZ5cKKeDf^P7=4tP5BhHRvE{kOoM z7Shv3mxnZupuZFI0e`9BUy2CudGZaZ(JBwa$NhC4jk*JufS;!Z;IsJ#H9FEw6cnpH z3_mCqBI@0Mo%ChE7s&%_ugIU$-YDFpJL|u#a}IBurq;oRIZbT@^=H!*%kG}0wt_+e zaNI`U^l|R%74Q1^#OL!zbWmCFWzsBRCXKYQqK~I2s zK~eYWdT5WwORw3Q%4;vZsi?E{&*(#t)vaJ(?$kdbU&qKdIMm&;22_(n9SXdTI*2>e z(LfETD;%m*wj(!JJ5;Z{0a@PVPzOQ>Jbv2iP*%u?RrE%iQoiVvQPjP3SWkI$y2GJN zJ?9D1BP*3>N;5>?P}Hm9nh-xr9bd(=SH)AZ5!5Y;vPWee-Qmbsa~{1GD{Ic9|5cP7 zdlgM7%5GOh{xz(5J8ht&o+_$Vl&iT|QLg4nM>9x=L>09-)V1_AF`qU#)CKwhX2X>g z?ZT9BWM7Aj*GoZHV<{B$5Ee;6otUrh4t)V$Pi;-c1$|$-ko{)7Q80W>@o&PqFX*OF zfDT~R9>99cH5&%>VWu+P2pFU`z#7`3V26S|3ic~_qk<^~vw#9s`)$3Bz6iL49#x#j zi8K8;@mTz;;`|TbrF65xPb>H)J*~A-kN95|Cc6Dq^0YYPub1b9mH~ei z)#|M!RDMUEr|$;a<$jeJ;GyTiY1DooR#Sx5c;c8VX8~*JUce>vH-IbY z9{^kEp8z+I&l{&JX#rptEeE_7&r)$3RQ#I&yC@9{UC7&6Pq(s}R-Clrr0EEK-fJoT z?TUZ9;@=Mbv)<3q3i=fwd-9}`K1uh{C%jK7{!@y70r&^K7ZgsyqAR>4PM|L#xL+Yy z-y_(LN3b1_;E1B&e9#+(eg=^Bqe{Qgf!u1P!aE$uoGyhQR50a0=BE{2Qt);KKd0b> zVln;J`=siZB>NLpaJzyXlKnZT@RY(!3V%Ss^NRC?!k<+5D++&AE~ax{;o-JX569A| z@a+okQ1}C$Pk~QfhEI4`K=Ty^$;bG11uvr{WwBqq3p9A!zby>A@OPh1Ff+p6v%G3@9-g0pPWOk`_Q$P#vJe zRJS9rIi@*cHlwU4!j%t>GPN`KG7|T;vdDoiLc0Kq_@Ot6R&6Pu>R^EQh(7PZrbMWq-#d}fyLhXiT+WQV~>Sx${5xcC5c^Bjhs#ySe z3E~W)#XRhOs?o)ra}0zD-r zs9AoPdgU1^$xqQ4;ODWsS)g+4!4=20_ASX|Te6k5b!0LF#z@vG8s@%&dBiZ=haE|x zzzilgvSK`)E*6w@3zN2iws$oEiE+IBa@d0IxP3_{lZmV~mP<`^^2}f z#`9@hdb;!DqlTFp&KXSYE{u#ACa~S9TPARs+u`+g>-DZ*8{S2)EeVMeXC|54#QL>}ah^CdUvoILbl+z8{FZ24wbGezES9nMV*Ws8+IW~HXw!ZkKnG{%Mw zn}zX_!)4)6%0&Km;2W563^^$a+*Z4?h&f;8cONerc?)G$X1OlyKrXH~%(;a<`9rG0 z(K3~LSFIU^T<*b6w5sj_)aG%8S!KEF!Cn({I9)6Y+|&osd*7Q`) z?Tb|kc_UxMYG>pqu`_N@g?J|C>Y(#fhUX{sSV&WLL@h^(0h5WsXeyhxyBjx6SaLXU zxnhiI&cJ63FZIkM2MoL0ncQ^c(sof3ELXhX@tWeIb9S3Bc!f3c>4~2FhnzO<+F~4X zR(0xeR<-g%PaWn6?n3z@mr?iev@xc-!=h6raOWJvFd^-51S3hj)Nmr)jPK6oZ>FJw z{f?|7pXthB5T`ps#U9OO43j#?vpIKNoo1?8&4bx%6b~0NN(Z{z+Kb8LFgE}(XT&pd z81Kv?dK1f4#a+VTadpk$J9iJY5YH)a?!}bMse5v{gc!Rvb?8XSDJdGVON)@AGB$IY zf%WiEYCKo$!Spo83aE12T-4h{`{rP(d8KZo2ms^kqNuiR(=3=2=luwvx;TTH$fic} z1*@1%Tka^hbBybsmuzROEySsAWtOutV<>rMSlkz{XO^mTc!|4BhKJT7cTM4GZ(%Gp zCX+?{mY`xFx?rIL%iM$oHfNZJ(7E$Y-{D-^@fj;K>L1<5Ss8xYewB;SVU(8Dphl)Y1`NJ_bRd`4aU0s3>y z-c)+k2!z_0& zyLh{DS*!$<%-9v{kk}=dMd~daHTn$vNP->dD;%Ta3Y)d3QiDI6n;gA(^MwVY!!%P9 z*t*!ad&hmu&Ix6jw=M~6#klMzs4$l7$zyMp;=2_#Dr0~+>V~j|weWP@MFqT}JcMVs z5u!y&D$d0ZHB+22c*Bo({GOZeN#X!S3D+=Rf7f?@fRfMLI5NEZqoTp(eEO!}7{4)o zbx&2_+okaSkN35bct_N_(*1jfkE|Q|!T@-l0~L1I#`jMqNB9}d|C0XZzvq6Jxa((o zpZR9TB{}j$N#H44=stjO9i!C>y^3L^U%>;4Gw$&VsYOMkbiUeOBk&eGGU*M9$Wnh@ zr1Y?e^!lPwEUm`_Wu({Pc>Tb=_yp6-7=L*WXZ@{0h?-^ilFOW^BU{DfD%PrJbGP9N zSuHuX>x1w{8LcHBluDqF-+QsL5b8drfz zAQLr$kW^1lEDhlEL8SCjb;D90TC1WrJL9Yv3bo)Uq0y38hldop%Ru#P3N)c5s?KTCv{E%mz>n9r6xUE zI14sJ2vLg|IM@1k_7G1GAII0=3#fu*3o718_JMys6sgB+7&WCN(t}sBZcZlyNTr=$ z)~)x(xPi_MVpNS2zuF<)%l+VGpn3;^vcsE|A-Qm$nHuXWMEE9HjQhYt?gAuH^uRTpP}jn(@z}Es{nqk)wx9!8u+LbnBJM<%cLNmdK|%P zCUnq-FOuuytu|i~L``_#Jl(?CfAA97GUG8H?}_-OP>Z0Zxs_) zcO2mtOhdA&BbO^2>%|=-i@i4(fu1}bF|q5;u-b0CX!(6=@Crr|C45unxf>jhej|I9 zMDJc^kK)|E6Q_E|&r(|zU-9WuoDW0xaD|NJ{Oz^!7#K_pKJ=MKesXHH~j!@{*sK*Z+9WTkzXG7VZKTb*Y(G_qTsG z7h6^{k<0b+lgOx*Mv)on0fwdTtcHz?L!W8vPt^Z|4A{RL#7_!JGlNl+iLgA(;Q6#sQuq4jj28+5K#*2iJ{CVXeV}k`_P|>eWC$~*TSle z>umyD3*S??@39%a&DFLQHwrQB-}TC(RS{1cdTYhGUR}AbYUp!Dib^*R-*Ocm&hBG+ zk4m^hq3v$88&R!zmpeui%F_`#42z_elZ5JHkHhB-avyGdOxf9ZufFU&?uCs4_Q>PN zN3p_7qSU0MF4+_5>MF0Q* literal 0 HcmV?d00001