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();
+
+ ///