This commit is contained in:
朱小炯 2025-06-28 11:01:51 +08:00
commit a9c888e9e7
177 changed files with 13560 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

363
.gitignore vendored Normal file
View File

@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# DG.DotNet

25
dg.dotnet/.dockerignore Normal file
View File

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@ -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,
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.1.6</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@ -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
{
}
}

View File

@ -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
{
}
}

View File

@ -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
{
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<DateTime>
{
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;
}
}
}

View File

@ -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
{
/// <summary>
/// 使用自定linq扩展执行排序查询分页功能 item1: 未分页结果item2分页后的结果
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="coditionEntity"></param>
/// <returns></returns>
public static IQueryable<T> UseCoditionFind<T>(this IQueryable<T> source, bool condition, Action<IQueryable<T>> action)
{
if (condition)
{
action(source);
}
return source;
}
}
}

View File

@ -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<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expLeft, Expression<Func<T, bool>> 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<Func<T, bool>>(body, candidateExpr);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expLeft, Expression<Func<T, bool>> 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<Func<T, bool>>(body, candidateExpr);
}
/// <summary>
/// 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))
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expLeft"></param>
/// <param name="expRight"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> AndListOr<T>(this Expression<Func<T, bool>> expLeft, Expression<Func<T, bool>>[] predicates)
{
var candidateExpr = Expression.Parameter(typeof(T), "candidate");
var parameterReplacer = new ParameterReplacer(candidateExpr);
var left = parameterReplacer.Replace(expLeft.Body);
Expression<Func<T, bool>> 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<Func<T, bool>>(body, candidateExpr);
}
/// <summary>
/// 传入条件之间为OR查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="predicates"></param>
/// <returns></returns>
public static IQueryable<T> WhereOR<T>(this IQueryable<T> source, params Expression<Func<T, bool>>[] 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<Func<T, bool>>(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;
}
}
}

View File

@ -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
{
/// <summary>
/// If extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="condition"></param>
/// <param name="action"></param>
/// <returns></returns>
public static T If<T>(this T t, bool condition, Action<T> action) where T : class
{
if (condition)
{
action(t);
}
return t;
}
/// <summary>
/// If extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="predicate"></param>
/// <param name="action"></param>
/// <returns></returns>
public static T If<T>(this T t, Predicate<T> predicate, Action<T> action) where T : class
{
if (t == null)
{
throw new ArgumentNullException();
}
if (predicate(t))
{
action(t);
}
return t;
}
/// <summary>
/// If extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="condition"></param>
/// <param name="func"></param>
/// <returns></returns>
public static T If<T>(this T t, bool condition, Func<T, T> func) where T : class => condition ? func(t) : t;
/// <summary>
/// If extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="predicate"></param>
/// <param name="func"></param>
/// <returns></returns>
public static T If<T>(this T t, Predicate<T> predicate, Func<T, T> func) where T : class => predicate(t) ? func(t) : t;
/// <summary>
/// If and else extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="condition"></param>
/// <param name="ifAction"></param>
/// <param name="elseAction"></param>
/// <returns></returns>
public static T IfAndElse<T>(this T t, bool condition, Action<T> ifAction, Action<T> elseAction) where T : class
{
if (condition)
{
ifAction(t);
}
else
{
elseAction(t);
}
return t;
}
/// <summary>
/// If and else extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="predicate"></param>
/// <param name="ifAction"></param>
/// <param name="elseAction"></param>
/// <returns></returns>
public static T IfAndElse<T>(this T t, Predicate<T> predicate, Action<T> ifAction, Action<T> elseAction) where T : class
{
if (t == null)
{
throw new ArgumentNullException();
}
if (predicate(t))
{
ifAction(t);
}
else
{
elseAction(t);
}
return t;
}
/// <summary>
/// If and else extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="condition"></param>
/// <param name="ifFunc"></param>
/// <param name="elseFunc"></param>
/// <returns></returns>
public static T IfAndElse<T>(this T t, bool condition, Func<T, T> ifFunc, Func<T, T> elseFunc) where T : class => condition ? ifFunc(t) : elseFunc(t);
/// <summary>
/// If and else extensions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="predicate"></param>
/// <param name="ifFunc"></param>
/// <param name="elseFunc"></param>
/// <returns></returns>
public static T IfAndElse<T>(this T t, Predicate<T> predicate, Func<T, T> ifFunc, Func<T, T> elseFunc) where T : class => predicate(t) ? ifFunc(t) : elseFunc(t);
}
}

View File

@ -0,0 +1,708 @@
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<HttpClient> _logger;
private static LogLevel _logLevel = LogLevel.Debug;
public HttpClient(IHttpClientFactory httpClientFactory,
ILogger<HttpClient> 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);
}
/// <summary>
/// Post Security
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="data"></param>
/// <param name="clientid"></param>
/// <param name="accessKey"></param>
/// <param name="iv"></param>
/// <returns></returns>
public async Task<T> PostSecurityAsync<T>(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<T>(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);
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="param"></param>
/// <param name="data"></param>
/// <param name="clientid"></param>
/// <param name="accessKey"></param>
/// <returns></returns>
public async Task<T> PostSecurityAsync<T>(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<T>(stream, options);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "POST 方法请求错误!");
throw;
}
}
/// <summary>
/// Post Security
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="data"></param>
/// <param name="clientid"></param>
/// <param name="accessKey"></param>
/// <param name="iv"></param>
/// <returns></returns>
public async Task<string> 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;
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="param"></param>
/// <param name="data"></param>
/// <param name="clientid"></param>
/// <param name="accessKey"></param>
/// <returns></returns>
public async Task<string> 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<T> UploadFileAsync<T>(string url, string fileName, string fullName, Dictionary<string, string>? 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<T>(stream, options);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "UploadFile 方法请求错误!");
throw;
}
}
public async Task<string> UploadFileAsync(string url, string fileName, string fullName, Dictionary<string, string>? 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
/// <summary>
/// Post
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="data"></param>
/// <param name="appId"></param>
/// <param name="appSecret"></param>
/// <param name="mediaType"></param>
/// <returns></returns>
public async Task<T> PostAsync<T>(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 stream = await httpResponse.Content.ReadAsStringAsync();
Log($"请求结果:{stream}");
var response = JsonSerializer.Deserialize<T>(stream, options);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "POST 方法请求错误!");
throw;
}
}
public async Task<string> 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;
}
}
/// <summary>
/// Get
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="appId"></param>
/// <param name="appSecret"></param>
/// <returns></returns>
public async Task<T> GetAsync<T>(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<string, object>();
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<T>(stream, options);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "GET 方法请求错误!");
throw;
}
}
/// <summary>
/// Get
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="param"></param>
/// <param name="appId"></param>
/// <param name="appSecret"></param>
/// <returns></returns>
public async Task<T> GetAsync<T>(string url, Dictionary<string, object> 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<T>(stream, options);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "GET 方法请求错误!");
throw;
}
}
/// <summary>
/// Get
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="appId"></param>
/// <param name="appSecret"></param>
/// <returns></returns>
public async Task<string> 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<string, object>();
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;
}
}
/// <summary>
/// Get
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="param"></param>
/// <param name="appId"></param>
/// <param name="appSecret"></param>
/// <returns></returns>
public async Task<string> GetAsync(string url, Dictionary<string, object> 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;
}
}
/// <summary>
/// 生成签名
/// </summary>
/// <param name="appId"></param>
/// <param name="bodyJson"></param>
/// <param name="secret"></param>
/// <param name="timestamps"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 计算 md5
/// </summary>
/// <param name="enCode"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 获取时间戳
/// </summary>
/// <returns></returns>
public static string GetTimeStamp()
{
TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
/// <summary>
/// 加密
/// </summary>
/// <param name="ciphertext"></param>
/// <param name="accessKey"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 解密
/// </summary>
/// <param name="cryptograph"></param>
/// <param name="accessKey"></param>
/// <returns></returns>
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);
}
/// <summary>
/// AES加密算法
/// </summary>
/// <param name="input">明文字符串</param>
/// <returns>字符串</returns>
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
}
}

View File

@ -0,0 +1,38 @@
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<T> PostSecurityAsync<T>(string url, object data, string clientid, string accessKey, string iv);
Task<T> PostSecurityAsync<T>(string url, object param, object data, string clientid, string accessKey);
Task<string> PostSecurityAsync(string url, object data, string clientid, string accessKey, string iv);
Task<string> PostSecurityAsync(string url, object param, object data, string clientid, string accessKey);
Task<T> PostAsync<T>(string url, object? data = null, string? appId = "", string? appSecret = "", string? mediaType = "application/json");
Task<string> PostAsync(string url, object? data = null, string? appId = "", string? appSecret = "", string? mediaType = "application/json");
Task<T> GetAsync<T>(string url, string appId = "", string appSecret = "", int timeout = 10000);
Task<T> GetAsync<T>(string url, Dictionary<string, object> param, string appId = "", string appSecret = "");
Task<string> GetAsync(string url, string appId = "", string appSecret = "");
Task<string> GetAsync(string url, Dictionary<string, object> param, string appId = "", string appSecret = "");
Task<T> UploadFileAsync<T>(string url, string fileName, string fullName, Dictionary<string, string>? headers = null);
Task<string> UploadFileAsync(string url, string fileName, string fullName, Dictionary<string, string>? headers = null);
}
}

View File

@ -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
{
/// <summary>
/// 启用API标准返回值模式
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
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));
});
}
/// <summary>
/// 启用API签名模式
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
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));
});
}
}
}

View File

@ -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, TTarget>(TSource source);
List<TTarget> Map<TSource, TTarget>(List<TSource> source);
}
}

View File

@ -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, TTarget>(TSource source) => CacheModel<TSource, TTarget>.Invoke(source);
public List<TTarget> Map<TSource, TTarget>(List<TSource> sources) => sources.AsParallel().Select(CacheModel<TSource, TTarget>.Invoke).ToList();
internal class CacheModel<TSource, TTarget>
{
private static readonly Func<TSource, TTarget> 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<Func<TSource, TTarget>>(Expression.MemberInit(Expression.New(typeof(TTarget)), memberBindings), parameterExpression).Compile();
}
public static TTarget Invoke(TSource source) => Func(source);
}
}
}

View File

@ -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
{
/// <summary>
/// 映射到
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static TTarget Map<TSource, TTarget>(this TSource source) => Mapper.Instance.Map<TSource, TTarget>(source);
/// <summary>
/// 映射到
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
/// <param name="sources"></param>
/// <returns></returns>
public static List<TTarget> Map<TSource, TTarget>(this List<TSource> sources) => Mapper.Instance.Map<TSource, TTarget>(sources);
/// <summary>
/// 复制到
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
/// <remarks>因为重名了,所以对方法取别名,同 MapTo</remarks>
public static TTarget Replicate<TSource, TTarget>(this TSource source) => source.Map<TSource, TTarget>();
/// <summary>
/// 复制到
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
/// <param name="sources"></param>
/// <returns></returns>
/// <remarks>因为重名了,所以对方法取别名,同 MapTo</remarks>
public static List<TTarget> Replicate<TSource, TTarget>(this List<TSource> sources) => sources.Map<TSource, TTarget>();
}
}

View File

@ -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, TTarget>(TSource source) => source.Map<TSource, TTarget>();
public List<TTarget> Map<TSource, TTarget>(List<TSource> source) => source.Map<TSource, TTarget>();
}
}

View File

@ -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()
{
}
}
}

View File

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System.Text.Json;
namespace DG.Core
{
/// <summary>
/// 表示处理API异常的筛选器。
/// </summary>
public class ApiExceptionFilterAttribute : Attribute, IExceptionFilter
{
private readonly ILogger<ApiExceptionFilterAttribute> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ApiExceptionFilterAttribute" /> class.
/// </summary>
/// <param name="logger">The logger</param>
public ApiExceptionFilterAttribute(ILogger<ApiExceptionFilterAttribute> logger)
{
_logger = logger;
}
/// <summary>
/// Called when [exception].
/// </summary>
/// <param name="context">The context.</param>
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;
}
}
}

View File

@ -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)
{
// 执行中的过滤器管道
}
}
}

View File

@ -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
{
/// <summary>
/// Represents an empty <see cref="IApiResult"/>.
/// </summary>
public static readonly IApiResult Empty = new ApiResult
{
Code = 0
};
/// <summary>
/// Gets or sets the status code.
/// </summary>
/// <value>The status code.</value>
[JsonPropertyName("code")]
public int Code { get; set; }
/// <summary>
/// Gets or sets the message.
/// </summary>
/// <value>The message.</value>
[JsonPropertyName("message")]
public string? Message { get; set; }
/// <summary>
/// Creates a new instance of <see cref="IApiResult{TData}"/> by the specified result.
/// </summary>
/// <typeparam name="TData">The type of the result.</typeparam>
/// <param name="data">The result.</param>
/// <returns>An instance inherited from <see cref="IApiResult{TResult}"/> interface.</returns>
public static IApiResult<TData> Succeed<TData>(TData data) => new ApiResult<TData>
{
Code = 0,
Data = data
};
/// <summary>
/// Creates a new instance of <see cref="IApiResult"/> by the specified error message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="code">The status code</param>
/// <returns>An instance inherited from <see cref="IApiResult"/> interface.</returns>
public static IApiResult Failed(string message, int? code = null) => new ApiResult
{
Code = code ?? -1,
Message = message
};
/// <summary>
/// Creates a new instance of <see cref="IApiResult{TResult}"/> by the specified error message.
/// </summary>
/// <typeparam name="TData">The type of the result.</typeparam>
/// <param name="data">The error result.</param>
/// <param name="message">The message.</param>
/// <param name="code">The status code.</param>
/// <returns>An instance inherited from <see cref="IApiResult"/> interface.</returns>
public static IApiResult<TData> Failed<TData>(TData data, string message, int? code = null) => new ApiResult<TData>
{
Code = code ?? -1,
Message = message,
Data = data
};
/// <summary>
/// Creates a new instance of <see cref="IApiResult"/> by the specified status code and message.
/// </summary>
/// <param name="code">The status code.</param>
/// <param name="message">The message.</param>
/// <returns>An instance inherited from <see cref="IApiResult"/> interface.</returns>
public static IApiResult From(int code, string message = null) => new ApiResult
{
Code = code,
Message = message
};
/// <summary>
/// Creates a new instance of <see cref="IApiResult{TResult}"/> by the specified result.
/// </summary>
/// <typeparam name="TData">The type of the result.</typeparam>
/// <param name="data">The result.</param>
/// <param name="code">The status code.</param>
/// <param name="message">The message.</param>
/// <returns>An instance inherited from <see cref="IApiResult{TResult}"/> interface.</returns>
public static IApiResult<TData> From<TData>(TData data, int code, string message) => new ApiResult<TData>
{
Code = code,
Message = message,
Data = data
};
}
}

View File

@ -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;
}
}
}
}

View File

@ -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)
{ }
}
}

View File

@ -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<TData> : ApiResult, IApiResult<TData>
{
/// <summary>
/// Initializes a new instance of the <see cref="ApiResult{TResult}"/> class.
/// </summary>
public ApiResult() { }
/// <summary>
/// Initializes a new instance of the <see cref="ApiResult{TResult}" /> class.
/// </summary>
/// <param name="data">The result.</param>
/// <param name="code">The status code.</param>
public ApiResult(TData data, int? code)
{
Code = code ?? 0;
Data = data;
}
/// <summary>
/// Gets or sets the result.
/// </summary>
/// <value>The result.</value>
[JsonPropertyName("data")]
public TData Data { get; set; }
}
}

View File

@ -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
{
/// <summary>
/// Gets or sets the status code.
/// </summary>
/// <value>The status code.</value>
int Code { get; set; }
/// <summary>
/// Gets or sets the message.
/// </summary>
/// <value>The message.</value>
string Message { get; set; }
}
}

View File

@ -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<TData> : IApiResult
{
/// <summary>
/// Gets or sets the result.
/// </summary>
/// <value>The result.</value>
TData Data { get; set; }
}
}

View File

@ -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<TData> where TData : class
{
public PageResult(int pageIndex, int pageSize, int total, IList<TData>? data)
{
PageIndex = pageIndex;
PageSize = pageSize;
Total = total;
Data = data;
TotalCount = total == 0 ? 0 : (Total / PageSize) + (Total % PageSize) > 0 ? 1 : 0;
}
/// <summary>
/// 页数
/// </summary>
public int PageIndex { get; set; }
/// <summary>
/// 分页大小
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 总数量
/// </summary>
public int Total { get; set; }
/// <summary>
/// 分页总数量
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 数据
/// </summary>
public IList<TData>? Data { get; set; }
}
}

View File

@ -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
{
/// <summary>
/// 页数
/// </summary>
public int PageIndex { get; set; }
/// <summary>
/// 分页大小
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 排序字段,支持逗号隔开
/// </summary>
public string? Sort { get; set; }
/// <summary>
/// 升降序Asc/Desc
/// </summary>
public string? Order { get; set; }
/// <summary>
/// 是否导出
/// </summary>
public bool? Export { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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<ApiSecurityAsyncFilter> _logger;
public ApiSecurityAsyncFilter(IConfiguration configuration,
ILogger<ApiSecurityAsyncFilter> 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<List<ClientKey>>();
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);
}
/// <summary>
/// AES解密
/// </summary>
/// <param name="input">密文字节数组</param>
/// <returns>返回解密后的字符串</returns>
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();
}
}
}

View File

@ -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
{
/// <summary>
/// 安全API
/// </summary>
public class ApiSecurityAttribute : Attribute, IFilterMetadata
{
public ApiSecurityAttribute()
{
}
}
}

View File

@ -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; }
}
}

View File

@ -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<IMapper, MapperManager>();
/// <summary>
/// Add auto ioc services
/// </summary>
/// <param name="services"></param>
/// <param name="baseType"></param>
/// <param name="lifeCycle"></param>
/// <returns></returns>
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<IHttpClient, HttpClient>();
}
}
}

View File

@ -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<string, object>();
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));
}
}
}
/// <summary>
/// 生成签名
/// </summary>
/// <param name="appId"></param>
/// <param name="bodyJson"></param>
/// <param name="secret"></param>
/// <param name="timestamps"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 计算 md5
/// </summary>
/// <param name="enCode"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 获取时间戳
/// </summary>
/// <returns></returns>
public static string GetTimeStamp()
{
TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
}
}

View File

@ -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
{
/// <summary>
/// API屏蔽签名
/// </summary>
public class ApiSignatureFilterForbidAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
}
}
}

View File

@ -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<ApiTimeSecurityAsyncFilter> _logger;
public ApiTimeSecurityAsyncFilter(IConfiguration configuration,
ILogger<ApiTimeSecurityAsyncFilter> 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<List<ClientKey>>();
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<string, object>();
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<ContentDto>(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);
}
/// <summary>
/// AES解密
/// </summary>
/// <param name="input">密文字节数组</param>
/// <returns>返回解密后的字符串</returns>
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();
}
/// <summary>
/// 获取时间戳
/// </summary>
/// <returns></returns>
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; }
}
}

View File

@ -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
{
/// <summary>
/// 安全API
/// </summary>
public class ApiTimeSecurityAttribute : Attribute, IFilterMetadata
{
public ApiTimeSecurityAttribute()
{
}
}
}

View File

@ -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;
}
/// <summary>
/// IsValid 为 false 时,提示得 error 信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public override string FormatErrorMessage(string name)
{
var lengthMessage = LengthError ? $",且长度不能超过{Length}" : "";
return $"{name}必须输入数字{lengthMessage},请重新输入!";
}
/// <summary>
/// 验证当前字段得结果
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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;
}
}
}
}

View File

@ -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;
}
/// <summary>
/// IsValid 为 false 时,提示得 error 信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public override string FormatErrorMessage(string name)
{
var lengthMessage = LengthError ? $",且长度不能超过{Length}" : "";
return $"{name}必须输入数字{lengthMessage},请重新输入!";
}
/// <summary>
/// 验证当前字段得结果
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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;
}
}
}
}

View File

@ -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;
}
/// <summary>
/// IsValid 为 false 时,提示得 error 信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public override string FormatErrorMessage(string name)
{
var lengthMessage = LengthError ? $",且长度不能超过{Length}" : "";
return $"{name}必须输入数字{lengthMessage},请重新输入!";
}
/// <summary>
/// 验证当前字段得结果
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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;
}
}
}
}

View File

@ -0,0 +1,18 @@
using DG.DotNet.Sample.Models;
using Microsoft.EntityFrameworkCore;
namespace DG.DotNet.Sample
{
public class AppDbContext : OracleDbContext
{
public readonly IConfiguration _configuration;
public AppDbContext(DbContextOptions<AppDbContext> options,
IAppManager appManager, IConfiguration configuration)
: base(options, appManager, configuration) => _configuration = configuration;
public DbSet<BAS_COMPANY> BAS_COMPANY { get; set; }
public DbSet<BAS_BUSINESSDEPARTMENT> BAS_BUSINESSDEPARTMENT { get; set; }
}
}

View File

@ -0,0 +1,127 @@
using DG.DotNet.Sample.Models;
using DG.EventBus;
using DG.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Encodings.Web;
using DG.Redis;
namespace DG.DotNet.Sample.Controllers
{
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
private readonly ILogger<TestController> _logger;
private readonly IScopeDbContextManager _manager;
private readonly IEventBus<TestEvent> _eventBus;
private readonly IHttpClient _httpClient;
private readonly IRedisManager _redisManager;
public TestController(IScopeDbContextManager manager,
ILogger<TestController> logger,
IEventBus<TestEvent> eventBus,
IHttpClient httpClient,
IRedisManager redisManager)
{
_logger = logger;
_manager = manager;
_eventBus = eventBus;
_httpClient = httpClient;
_redisManager = redisManager;
}
[HttpPost("[action]")]
[ApiSignatureFilterForbid]
public async Task<List<BAS_COMPANY>> Post([FromBody] BAS_COMPANY dto)
{
//TestEvent testEvent = new TestEvent();
//await _eventBus.publicAsync(testEvent);
using var repository = _manager.CreateRepository<AppDbContext>();
return await repository.GetRepository<BAS_COMPANY>().QueryListAsync();
}
[HttpGet("[action]")]
[ApiSignatureFilterForbid]
public async Task<List<BAS_COMPANY>> Get([FromHeader] string? appid = "crm_tg_dng8")
{
var d = await _redisManager.ExistsAsync("dat");
var list = new List<BAS_COMPANY>();
using (var repository = _manager.CreateRepository<AppDbContext>())
{
using var transaction = await repository.BeginTransactionAsync();
list = await repository.GetRepository<BAS_COMPANY>().QueryListAsync();
list.ForEach(x =>
{
if (x.COMPANYID == 600000173)
{
x.CREATEUSER = 600000207;
}
});
await repository.GetRepository<BAS_COMPANY>().BatchUpdateAsync(list, x => new { x.CREATEUSER });
var data = await repository.GetRepository<BAS_BUSINESSDEPARTMENT>().QueryListAsync();
data.ForEach(x =>
{
x.UTIME = DateTime.Now;
});
await repository.GetRepository<BAS_BUSINESSDEPARTMENT>().BatchUpdateAsync(data, x => new { x.UTIME });
await transaction.CommitAsync();
}
using (var repository = _manager.CreateRepository<AppDbContext>("crm_d1_dnzz"))
{
var list2 = await repository.GetRepository<BAS_COMPANY>().QueryListAsync();
list.AddRange(list2);
}
return list;
}
[HttpGet("[action]")]
[ApiSignatureFilterForbid]
public async Task<List<BAS_COMPANY>> GetInnerusers([FromHeader] string appid)
{
var data = await _httpClient.GetAsync<ApiResult<List<BAS_COMPANY>>>("https://localhost:7150/Test/GetInnerusers?eid=5004,4028,123", "qt_compliance", "CqlNBxRof0yl7eJM1f4IbOBhgribfooZJ1zwuIj5NzQ=");
return data.Data;
}
[HttpGet("[action]")]
[ApiSignatureFilterForbid]
public async Task<string> Test([FromQuery] BAS_COMPANY dto)
{
//int agentid = 1000006;
//string appid = "wwd4cd11d60db47118";
//string at = "221017-141316-73";
//string clientId = "UPWEBSITE";
//var options = new JsonSerializerOptions
//{
// DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
// PropertyNameCaseInsensitive = true,
// Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
//};
//var model = new
//{
// appid = appid,
// agentid = agentid,
// data = JsonSerializer.Serialize(new
// {
// msgtype = "text",
// text = new
// {
// content = "²âÊÔ2222222222"
// },
// agentid = agentid,
// toparty = "",
// totag = "",
// touser = at,
// safe = 0
// }, options)
//};
//var result = await _httpClient.WeworkSendeMsg<ApiResult<string>>("http://post.hc.dn8188.com/Wework/sendmsg.html", model, clientId, "1622a92d");
//return result.Data;
var result = await _httpClient.PostAsync<ApiResult>("https://certcontract.wantest.tcfortune.com:11188qt_compliance?content=8fYIIKMSFVxFY5i2AWz7Awu5dDpKxrp0HGf%2bm1Ij3fa6DcfLHvZbag%3d%3d&sign=9jgR248r5vBlH3yZqrz9%2bg%3d%3d&clientId=UPWEBSITE");
return "";
}
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>78e44202-6303-41c8-99b3-6abe5c750ef5</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Oracle.EntityFrameworkCore" Version="6.21.61" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DG.Core\DG.Core.csproj" />
<ProjectReference Include="..\DG.EventBus\DG.EventBus.csproj" />
<ProjectReference Include="..\DG.Oracle.EntityFrameworkCore\DG.Oracle.EntityFramework.csproj" />
<ProjectReference Include="..\DG.Redis\DG.Redis.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="DG.EntityFramework" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,37 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
#设置时间为中国上海
ENV TZ=Asia/Shanghai
ENV DEBIAN_FRONTEND noninteractive
# 设置包源为阿里
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list \
&& apt-get clean
# 安装 tzdata 软件包
RUN apt-get update \
&& apt-get install -y tzdata \
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
&& rm -rf /var/lib/apt/lists/ \
&& dpkg-reconfigure -f noninteractive tzdata
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["DG.DotNet.Sample/DG.DotNet.Sample.csproj", "DG.DotNet.Sample/"]
RUN dotnet restore "DG.DotNet.Sample/DG.DotNet.Sample.csproj"
COPY . .
WORKDIR "/src/DG.DotNet.Sample"
RUN dotnet build "DG.DotNet.Sample.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DG.DotNet.Sample.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DG.DotNet.Sample.dll"]

View File

@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace DG.DotNet.Sample.Models
{
[Table("BAS_BUSINESSDEPARTMENT")]
public class BAS_BUSINESSDEPARTMENT
{
public decimal ID { get; set; }
public decimal BUSINESSID { get; set; }
/// <summary>
/// 部门ID
/// </summary>
public decimal DEPTID { get; set; }
/// <summary>
/// 1、主部门 2、子部门
/// </summary>
public int DEPTTYPE { get; set; }
public DateTime CTIME { get; set; }
public DateTime UTIME { get; set; }
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Threading.Tasks;
namespace DG.DotNet.Sample.Models
{
[Table("BAS_COMPANY")]
public class BAS_COMPANY
{
[Key]
public decimal? COMPANYID { get; set; }
[StringLength(50)]
public string? COMPANYNAME { get; set; }
public short? ISOUTERAGENT { get; set; }
public decimal? PARENTID { get; set; }
[StringLength(6)]
public string? COMPANYCODE { get; set; }
public decimal? BUSINESSVALUE { get; set; }
public DateTime? CTIME { get; set; }
public decimal? CREATEUSER { get; set; }
public DateTime? UTIME { get; set; }
public decimal? UPDATEUSER { get; set; }
[StringLength(100)]
public string? COMMENTS { get; set; }
[StringLength(1)]
public string? SYSTEMCODE { get; set; }
[StringLength(20)]
public string? ORGANNAME { get; set; }
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Zxd.Entity.Zxd
{
[Table("t_meeting")]
public class Meeting
{
public Meeting()
{
MeetingAccessories = new List<MeetingAccessory>();
MeetingParticipants = new List<MeetingParticipant>();
}
[Key]
public int Id { get; set; }
[Column("meeting_name")]
public string? MeetingName { get; set; }
[Column("meeting_type")]
public int MeetingType { get; set; }
[Column("begin_time")]
public DateTime BeginTime { get; set; }
[Column("continue_hour")]
public int ContinueHour { get; set; }
[Column("continue_minute")]
public int ContinueMinute { get; set; }
public string? Site { get; set; }
public string? Compere { get; set; }
public string? Remark { get; set; }
[Column("create_user")]
public string? CreateUser { get; set; }
[Column("create_time")]
public DateTime CreateTime { get; set; }
[Column("is_delete")]
public bool IsDelete { get; set; }
[Column("update_user")]
public string? UpdateUser { get; set; }
[Column("update_time")]
public DateTime? UpdateTime { get; set; }
public virtual List<MeetingAccessory> MeetingAccessories { get; set; }
public virtual List<MeetingParticipant> MeetingParticipants { get; set; }
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Zxd.Entity.Zxd
{
[Table("meeting_accessory")]
public class MeetingAccessory
{
public MeetingAccessory()
{
}
[Key]
public int Id { get; set; }
[Column("meeting_id")]
public int MeetingId { get; set; }
[Column("file_name")]
public string? FileName { get; set; }
[Column("file_url")]
public string? FileUrl { get; set; }
[Column("file_size")]
public string? FileSize { get; set; }
[Column("uploader")]
public string? Uploader { get; set; }
[Column("upload_eid")]
public int UploadEid { get; set; }
[Column("upload_time")]
public DateTime UploadTime { get; set; }
public virtual Meeting? Meeting { get; set; }
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Zxd.Entity.Zxd
{
public class MeetingParticipant
{
public MeetingParticipant()
{
}
[Key]
public int Id { get; set; }
[Column("meeting_id")]
public int MeetingId { get; set; }
public int Eid { get; set; }
/// <summary>
/// 参与人
/// </summary>
public string? Paricipant { get; set; }
public virtual Meeting? Meeting { get; set; }
}
}

View File

@ -0,0 +1,93 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DG.DotNet.Sample.Models
{
[Table("RES_CUSTOMERDETAIL")]
public partial class RES_CUSTOMERDETAIL
{
[Key]
[StringLength(18)]
public string? RESID { get; set; }
public decimal? CACCOUNT { get; set; }
[StringLength(100)]
public string? EMAIL { get; set; }
[StringLength(30)]
public string? CNAME { get; set; }
[StringLength(1)]
public string? GENDER { get; set; }
public DateTime? BIRTHDAY { get; set; }
public decimal? PROVINCEID { get; set; }
public decimal? CITYID { get; set; }
[StringLength(200)]
public string? ADDRESS { get; set; }
[StringLength(50)]
public string? CUSTOMERTYPEID { get; set; }
[StringLength(50)]
public string? AMOUNTTYPEID { get; set; }
[StringLength(50)]
public string? JOBTYPEID { get; set; }
[StringLength(50)]
public string? OPERATIONTYPE { get; set; }
[StringLength(100)]
public string? MSN { get; set; }
[StringLength(20)]
public string? QQ { get; set; }
[StringLength(15)]
public string? FAX { get; set; }
[StringLength(100)]
public string? CUSTOMERFROMBIG { get; set; }
[StringLength(200)]
public string? PRIMARYNUMBERADDRESS { get; set; }
[StringLength(100)]
public string? SPECIALMEMO { get; set; }
public decimal? CREATEUSER { get; set; }
public DateTime? UTIME { get; set; }
public decimal? UPDATEUSER { get; set; }
public short ISPRIMARYNUM { get; set; }
public string? ZX_USERID { get; set; }
public string? REMARK { get; set; }
public string? CUSTOMERCATEGORY { get; set; }
public string? BIRTHDAYAREA { get; set; }
public string? DEALER { get; set; }
public string? OPERATIONTIME { get; set; }
public string? FREQUENCY { get; set; }
public string? STOCKPOSITION { get; set; }
public decimal? VISITSTATUS { get; set; }
public DateTime? VISITTIME { get; set; }
public decimal? VISITUSER { get; set; }
public string? VISITREMARK { get; set; }
public string? GUPIAO { get; set; }
public string? PROFITANDLOSS { get; set; }
public string? RISKTOLERANCE { get; set; }
public string? BELONGTOPROVINCE { get; set; }
public string? BELONGTOCITY { get; set; }
public int? RELATION { get; set; }
public int? ISBOUND { get; set; }//是否绑定客服
}
}

View File

@ -0,0 +1,37 @@
using DG.DotNet.Sample;
using DG.EventBus;
using DG.Core;
using Microsoft.EntityFrameworkCore;
using DG.DotNet.Sample.Workers;
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDGEntityFrameworkOracle<AppDbContext>("appid");
builder.Services.AddControllers()
.AddApiResult()
.AddApiSignature();
builder.Services.AddRedis(builder.Configuration);
builder.Services.AddEventBus();
builder.Services.AddDGHttpClient();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHostedService<TestWorker>();
builder.Services.AddSingleton<IEventHandler<TestEvent>, TestHandler>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "PreProduction")
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseDGEntityFrameworkOracle("crm_tg_dng8");
app.MapControllers();
app.Run();

View File

@ -0,0 +1,40 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:33425",
"sslPort": 44349
}
},
"profiles": {
"DG.DotNet.Sample": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:7150;http://localhost:5150",
"dotnetRunMessages": true
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"publishAllPorts": true,
"useSSL": true,
"httpPort": 5353,
"sslPort": 5354
}
}
}

View File

@ -0,0 +1,8 @@
using DG.EventBus;
namespace DG.DotNet.Sample
{
public class TestEvent : IEvent
{
}
}

View File

@ -0,0 +1,19 @@
using DG.EventBus;
namespace DG.DotNet.Sample
{
public class TestHandler : IEventHandler<TestEvent>
{
public bool CanHandle(TestEvent @event)
{
return true;
}
public async Task<bool> HandleAsync(TestEvent @event, CancellationToken cancellationToken = default)
{
await Task.Delay(10000);
Console.WriteLine("test");
return await Task.FromResult(true);
}
}
}

View File

@ -0,0 +1,70 @@
using DG.DotNet.Sample.Models;
using DG.EventBus;
using DG.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using System.Text.Json;
using Oracle.ManagedDataAccess.Client;
using System.Data;
namespace DG.DotNet.Sample.Workers
{
public class TestWorker : BackgroundService
{
private readonly ILogger<TestWorker> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IEventBus<TestEvent> _eventBus;
private readonly IHttpClient _httpClient;
public TestWorker(IServiceProvider serviceProvider,
ILogger<TestWorker> logger,
IEventBus<TestEvent> eventBus,
IHttpClient httpClient)
{
_logger = logger;
_serviceProvider = serviceProvider;
_eventBus = eventBus;
_httpClient = httpClient;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (IServiceScope scope = _serviceProvider.CreateScope())
{
var repository = scope.ServiceProvider.GetRequiredService<IOracleRepository<AppDbContext>>();
var sql = @"select
a.*
from
(
select
*
from
bas_businessdepartment
where
deptid in (
select
saledeptid
from
bas_salesdepartment start with saledeptid =(
select
a.deptid
from
bas_innerusergroup a
join bas_inneruser b on a.inneruserid = b.pkid
where
b.eid = :eid
) connect by prior department_parentid = department_id
)
) a
join bas_businesslines b on a.businessid = b.businessid";
var param = new OracleParameter[] {
new OracleParameter() { ParameterName = ":eid", OracleDbType = OracleDbType.Int64, Value = 4028 }
};
var list = await repository.ExecuteSqlToEntityAsync<BAS_BUSINESSDEPARTMENT>(sql, param);
_logger.LogInformation(JsonSerializer.Serialize(list));
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,48 @@
{
"ConnectionStrings": {
"crm_tg_dng8": "DATA SOURCE=192.168.11.41:1521/Orcl_TG;PERSIST SECURITY INFO=True;USER ID=UPDEV;PASSWORD=sa123456.",
"crm_d1_dnzz": "DATA SOURCE=192.168.11.82:1521/orcl;PERSIST SECURITY INFO=True;USER ID=UPDEV;PASSWORD=sa123456.",
"crm_d3_dnyy": "DATA SOURCE=192.168.11.41:1521/Orcl_CrmFB;PERSIST SECURITY INFO=True;USER ID=UPDEV;PASSWORD=sa123456."
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"SignConfig": {
"AppId": "qt_compliance",
"Secret": "CqlNBxRof0yl7eJM1f4IbOBhgribfooZJ1zwuIj5NzQ="
},
"ClientKey": [
{
"Id": "UPWEBSITE",
"Name": "NewWebSite",
"AccessKey": "1622a92d",
"Vi": "Nx7GqcMxc=F&cpUa",
"NewAccessKey": "YafhQn$3gLUl@XDI"
},
{
"Id": "TDORDERSITE",
"Name": "订单接口",
"AccessKey": "622a92d1"
}
],
"Redis": [
{
"Name": "Hg",
"HostName": "192.168.11.81",
"Port": "6379",
"Password": "Abc@123456",
"Defaultdatabase": "1"
},
{
"Name": "UserCenter",
"HostName": "192.168.11.103",
"Port": "6379",
"Password": "123",
"Defaultdatabase": "0"
}
]
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.0.32</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.6" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace DG.EntityFramework
{
public class DbContextProvider<TDbContext> : IDbContextProvider<TDbContext>
where TDbContext : DbContext
{
private readonly TDbContext _dbContext;
public DbContextProvider(TDbContext dbContext)
{
_dbContext = dbContext;
}
public TDbContext GetDbContext()
{
return _dbContext;
}
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Text;
namespace DG.EntityFramework
{
/// <summary>
/// Entity Framework Core Options
/// </summary>
public class EFCoreOptions<TDbContext>
where TDbContext : DbContext
{
/// <summary>
/// Gets or sets the database's connection string that will be used to store database entities.
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
///
/// </summary>
public DbConnection ExistingConnection { get; internal set; }
/// <summary>
///
/// </summary>
public DbContextOptionsBuilder<TDbContext> DbContextOptions { get; }
internal string Version { get; set; }
public EFCoreOptions()
{
DbContextOptions = new DbContextOptionsBuilder<TDbContext>();
Version = "1.0.0";
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace DG.EntityFramework
{
public class EFCoreOptionsExtension<TDbContext>
where TDbContext : DbContext
{
private readonly Action<EFCoreOptions<TDbContext>> _configure;
public EFCoreOptionsExtension(Action<EFCoreOptions<TDbContext>> configure)
{
_configure = configure;
}
public void AddServices(IServiceCollection services)
{
var options = new EFCoreOptions<TDbContext>();
_configure(options);
services.AddDbContext<TDbContext>();
services.Configure(_configure);
services.AddScoped<IUnitOfWorkManager, UnitOfWorkManager<TDbContext>>();
services.AddScoped<IUnitOfWorkCompleteHandle, UnitOfWorkCompleteHandle<TDbContext>>();
services.AddScoped<IDbContextProvider<TDbContext>, DbContextProvider<TDbContext>>();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> 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>(TState state) => null;
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
namespace DG.EntityFramework
{
/// <summary>
///
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public interface IDbContextProvider<out TDbContext>
where TDbContext : DbContext
{
/// <summary>
///
/// </summary>
/// <returns></returns>
TDbContext GetDbContext();
}
}

View File

@ -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<TDbContext> : IBaseRepository<TDbContext>
where TDbContext : DbContext
{
private readonly IServiceProvider _serviceProvider;
private readonly TDbContext _context;
public BaseRepository(
IServiceProvider serviceProvider,
TDbContext dbContext
)
{
_serviceProvider = serviceProvider;
_context = dbContext;
}
public IRepositoryBase<TDbContext, TEntity> GetRepository<TEntity>()
where TEntity : class
=> _serviceProvider.GetRequiredService<IRepositoryBase<TDbContext, TEntity>>();
public async Task<IDbContextTransaction> BeginTransactionAsync()
=> await _context.Database.BeginTransactionAsync();
/// <summary>
/// 创建SqlCommand
/// </summary>
/// <param name="sql"></param>
/// <param name="transaction"></param>
/// <param name="parameters"></param>
/// <returns></returns>
private async Task<DbCommand> 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;
}
/// <summary>
/// 执行存储过程的扩展方法ExecuteSqlCommand
/// </summary>
/// <param name="commandType"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public async Task<int> 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<List<T>> ExecuteSqlCommandAsync<T>(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<T> 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<List<T>> ExecuteSqlToListAsync<T>(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<T> 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<T>();
}
public async Task<T> ExecuteSqlToEntityAsync<T>(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<int> 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<long> 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<IDbContextTransaction> BeginTransactionAsync(IsolationLevel isolationLevel)
=> await _context.Database.BeginTransactionAsync(isolationLevel);
}
}

View File

@ -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<TDbContext>
where TDbContext : DbContext
{
IRepositoryBase<TDbContext, TEntity> GetRepository<TEntity>()
where TEntity : class;
/// <summary>
/// 执行存储过程的扩展方法ExecuteSqlCommand
/// </summary>
/// <param name="commandType"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
Task<int> ExecuteSqlCommandNonQueryAsync(CommandType commandType, string sql, [NotNull] params object[] parameters);
/// <summary>
/// 执行存储过程的扩展方法ExecuteSqlCommand
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="commandType"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
Task<List<T>> ExecuteSqlCommandAsync<T>(CommandType commandType, string sql, [NotNull] params object[] parameters)
where T : class, new();
/// <summary>
/// 开启事务
/// </summary>
/// <returns></returns>
Task<IDbContextTransaction> BeginTransactionAsync();
/// <summary>
///
/// </summary>
/// <param name="isolationLevel"></param>
/// <returns></returns>
Task<IDbContextTransaction> BeginTransactionAsync(IsolationLevel isolationLevel);
Task<List<T>> ExecuteSqlToListAsync<T>(string sql, [MaybeNull] params object[] parameters)
where T : class, new();
Task<T> ExecuteSqlToEntityAsync<T>(string sql, [MaybeNull] params object[] parameters)
where T : class, new();
Task<int> ExecuteSqlToCountAsync(string sql, [MaybeNull] params object[] parameters);
Task<long> ExecuteSqlToCountLongAsync(string sql, [MaybeNull] params object[] parameters);
}
}

View File

@ -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<TDbContext, TEntity>
where TDbContext : DbContext
where TEntity : class
{
#region Qurey
/// <summary>
/// Used to get a IQueryable that is used to retrieve entities from entire table.
/// </summary>
/// <returns>IQueryable to be used to select entities from database</returns>
IQueryable<TEntity> Query();
/// <summary>
/// Used to get a IQueryable that is used to retrieve entities from entire table.
/// </summary>
/// <param name="propertySelectors"></param>
/// <returns>IQueryable to be used to select entities from database</returns>
IQueryable<TEntity> QueryIncluding(params Expression<Func<TEntity, object>>[] propertySelectors);
/// <summary>
/// Used to query a array of entities from datatable
/// </summary>
/// <returns>Array of entities</returns>
Task<TEntity[]> QueryArrayAsync();
/// <summary>
/// Used to query a array of entities from datatable by predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns>Array of entities</returns>
//Task<TEntity[]> QueryArrayAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// Used to query a list of entities from datatable
/// </summary>
/// <returns>List of entities</returns>
Task<List<TEntity>> QueryListAsync();
/// <summary>
/// Used to query a list of entities from datatable by predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns>List of entities</returns>
//Task<List<TEntity>> QueryListAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// Used to query a single entity from datatable by predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns>Entity</returns>
Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// Gets an entity with given given predicate or null if not found.
/// </summary>
/// <param name="predicate">Predicate to filter entities</param>
Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate = null);
#endregion Qurey
#region Insert
/// <summary>
/// Insert a new entity
/// </summary>
/// <param name="entity"></param>
/// <param name="submit"></param>
/// <returns>Inserted entity</returns>
Task<TEntity> InsertAsync(TEntity entity, bool submit = true);
Task<List<TEntity>> BatchInsertAsync(List<TEntity> entities, bool submit = true);
#endregion Insert
#region Update
/// <summary>
/// Updates an existing entity
/// </summary>
/// <param name="entity"></param>
/// <param name="submit"></param>
/// <returns></returns>
Task<TEntity> UpdateAsync(TEntity entity, bool submit = true);
Task<TEntity> UpdateAsync(TEntity entity, Expression<Func<TEntity, object>> expression, bool submit = true);
Task BatchUpdateAsync(List<TEntity> entities, bool submit = true);
Task BatchUpdateAsync(List<TEntity> entities, Expression<Func<TEntity, object>> expression, bool submit = true);
#endregion Update
#region Delete
/// <summary>
/// Deletes an entity
/// </summary>
/// <param name="entity">Entity</param>
/// <param name="submit"></param>
/// <returns>Entity to be deleted</returns>
Task DeleteAsync(TEntity entity, bool submit = true);
Task BatchDeleteAsync(List<TEntity> entities, bool submit = true);
#endregion Delete
#region Expression
TDbContext Context { get; }
DbSet<TEntity> Table { get; }
void Attach(TEntity entity);
#endregion Expression
}
}

View File

@ -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<TDbContext, TEntity> : IRepositoryBase<TDbContext, TEntity>
where TDbContext : DbContext
where TEntity : class
{
/// <summary>
///
/// </summary>
public TDbContext Context { get; }
/// <summary>
///
/// </summary>
public DbSet<TEntity> Table => Context.Set<TEntity>();
/// <summary>
///
/// </summary>
/// <param name="dbContextProvider"></param>
public RepositoryBase(TDbContext context)
{
Context = context;
}
#region Qurey
/// <summary>
/// Used to get a IQueryable that is used to retrieve entities from entire table.
/// </summary>
/// <returns>IQueryable to be used to select entities from database</returns>
public IQueryable<TEntity> Query()
{
if (!(Context.Set<TEntity>() is IQueryable<TEntity> query))
throw new Exception($"{typeof(TEntity)} TEntity cannot be empty");
return query;
}
/// <summary>
/// Used to get a IQueryable that is used to retrieve entities from entire table.
/// </summary>
/// <param name="propertySelectors"></param>
/// <returns>IQueryable to be used to select entities from database</returns>
public IQueryable<TEntity> QueryIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
{
if (propertySelectors == null || !propertySelectors.Any())
{
return Query();
}
var query = Query();
return propertySelectors.Aggregate(query, (current, propertySelector) => current.Include(propertySelector));
}
/// <summary>
/// Used to query a array of entities from data table
/// </summary>
/// <returns>Array of entities</returns>
public async Task<TEntity[]> QueryArrayAsync()
{
return await Query().ToArrayAsync();
}
/// <summary>
/// Used to query a array of entities from data table by predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns>Array of entities</returns>
public async Task<TEntity[]> QueryArrayAsync(Expression<Func<TEntity, bool>> predicate)
{
return await Query().Where(predicate).ToArrayAsync();
}
/// <summary>
/// Used to query a list of entities from data table
/// </summary>
/// <returns>List of entities</returns>
public async Task<List<TEntity>> QueryListAsync()
{
return await Query().ToListAsync();
}
/// <summary>
/// Used to query a list of entities from data table by predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns>List of entities</returns>
public async Task<List<TEntity>> QueryListAsync(Expression<Func<TEntity, bool>> predicate = null)
{
return await Query().Where(predicate).ToListAsync();
}
/// <summary>
/// Used to query a single entity from datatable by predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns>Entity</returns>
public async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate = null)
{
if (predicate == null)
{
return await Query().SingleAsync();
}
return await Query().SingleAsync(predicate);
}
/// <summary>
/// Gets an entity with given given predicate or null if not found.
/// </summary>
/// <param name="predicate">Predicate to filter entities</param>
public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate = null)
{
if (predicate == null)
{
return await Query().FirstOrDefaultAsync();
}
return await Query().FirstOrDefaultAsync(predicate);
}
#endregion Qurey
#region Insert
/// <summary>
/// Insert a new entity
/// </summary>
/// <param name="entity"></param>
/// <param name="submit"></param>
/// <returns>Inserted entity</returns>
public virtual async Task<TEntity> InsertAsync(TEntity entity, bool submit = true)
{
AttachIfNot(entity);
Context.Entry(entity).State = EntityState.Added;
if (submit)
{
await Context.SaveChangesAsync();
}
return entity;
}
public virtual async Task<List<TEntity>> BatchInsertAsync(List<TEntity> entities, bool submit = true)
{
foreach (var entity in entities)
{
AttachIfNot(entity);
Context.Entry(entity).State = EntityState.Added;
}
if (submit)
{
await Context.SaveChangesAsync();
}
return entities;
}
#endregion Insert
#region Update
/// <summary>
/// Updates an existing entity
/// </summary>
/// <param name="entity"></param>
/// <param name="submit"></param>
/// <returns></returns>
public virtual async Task<TEntity> UpdateAsync(TEntity entity, bool submit = true)
{
Attach(entity);
Context.Entry(entity).State = EntityState.Modified;
if (submit)
{
await Context.SaveChangesAsync();
}
return await Task.FromResult(entity);
}
public virtual async Task<TEntity> UpdateAsync(TEntity entity, Expression<Func<TEntity, object>> expression, bool submit = true)
{
Attach(entity);
var entry = Context.Entry(entity);
//entry.State = EntityState.Unchanged;
foreach (var proInfo in expression.GetPropertyAccessList())
{
if (!string.IsNullOrEmpty(proInfo.Name))
entry.Property(proInfo.Name).IsModified = true;
}
if (submit)
{
await Context.SaveChangesAsync();
}
return await Task.FromResult(entity);
}
public virtual async Task BatchUpdateAsync(List<TEntity> entities, bool submit = true)
{
foreach(var entity in entities)
{
Attach(entity);
Context.Entry(entity).State = EntityState.Modified;
}
if (submit)
{
await Context.SaveChangesAsync();
}
}
public virtual async Task BatchUpdateAsync(List<TEntity> entities, Expression<Func<TEntity, object>> expression, bool submit = true)
{
foreach (var entity in entities)
{
Attach(entity);
var entry = Context.Entry(entity);
//entry.State = EntityState.Unchanged;
foreach (var proInfo in expression.GetPropertyAccessList())
{
if (!string.IsNullOrEmpty(proInfo.Name))
//4.4将每个 被修改的属性的状态 设置为已修改状态;后面生成update语句时就只为已修改的属性 更新
entry.Property(proInfo.Name).IsModified = true;
}
}
if (submit)
{
await Context.SaveChangesAsync();
}
}
#endregion Update
#region Delete
/// <summary>
/// Deletes an entity
/// </summary>
/// <param name="entity">Entity</param>
/// <param name="submit"></param>
/// <returns>Entity to be deleted</returns>
public virtual async Task DeleteAsync(TEntity entity, bool submit = true)
{
AttachIfNot(entity);
await Task.FromResult(Table.Remove(entity));
if (submit)
{
await Context.SaveChangesAsync();
}
}
public virtual async Task BatchDeleteAsync(List<TEntity> entities, bool submit = true)
{
foreach (var entity in entities)
{
AttachIfNot(entity);
Context.Entry(entity).State = EntityState.Deleted;
}
Table.RemoveRange(entities);
if (submit)
{
await Context.SaveChangesAsync();
}
}
#endregion Delete
#region Expression
/// <summary>
///
/// </summary>
/// <param name="entity"></param>
public void AttachIfNot(TEntity entity)
{
if (!Table.Local.Contains(entity))
{
Table.Attach(entity);
}
}
public void Attach(TEntity entity)
{
Table.Attach(entity);
}
#endregion Expression
}
}

View File

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace DG.EntityFramework
{
/// <summary>
/// Extensions method
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Add EntityFramework
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
/// <param name="services"></param>
/// <param name="options"></param>
/// <param name="isUseLogger"></param>
/// <returns></returns>
public static IServiceCollection AddDGEntityFramework<TDbContext>(this IServiceCollection services,
Action<DbContextOptionsBuilder> options)
where TDbContext : DbContext
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
services.AddDbContext<TDbContext>(options, ServiceLifetime.Scoped, ServiceLifetime.Scoped);
services.AddScoped<IUnitOfWorkManager, UnitOfWorkManager<TDbContext>>();
services.AddScoped<IDbContextProvider<TDbContext>, DbContextProvider<TDbContext>>();
services.AddScoped(typeof(IRepositoryBase<,>), typeof(RepositoryBase<,>));
services.AddScoped(typeof(IBaseRepository<>), typeof(BaseRepository<>));
return services;
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DG.EntityFramework
{
/// <summary>
/// Used to complete a unit of work. This interface can not be injected or directly used.
/// </summary>
public interface IUnitOfWorkCompleteHandle : IDisposable
{
/// <summary>
/// Completes this unit of work. It saves all changes and commit transaction if exists.
/// </summary>
void Complete();
/// <summary>
/// Completes this unit of work. It saves all changes and commit transaction if exists.
/// </summary>
/// <returns></returns>
Task CompleteAsync();
/// <summary>
///
/// </summary>
void Rollback();
/// <summary>
///
/// </summary>
/// <returns></returns>
Task RollbackAsync();
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace DG.EntityFramework
{
/// <summary>
/// Unit of work manager. Used to begin and control a unit of work.
/// </summary>
public interface IUnitOfWorkManager
{
/// <summary>
/// Begins a new unit of work.
/// </summary>
/// <returns>A handle to be able to complete the unit of work</returns>
IUnitOfWorkCompleteHandle Begin();
/// <summary>
/// Begins a new unit of work.
/// </summary>
/// <returns>A handle to be able to complete the unit of work</returns>
IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope);
/// <summary>
/// Begins a new unit of work.
/// </summary>
/// <returns>A handle to be able to complete the unit of work</returns>
IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options);
}
}

View File

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace DG.EntityFramework
{
public class UnitOfWorkCompleteHandle<TDbContext> : IUnitOfWorkCompleteHandle
where TDbContext : DbContext
{
private readonly IDbContextTransaction _dbContextTransaction;
public UnitOfWorkCompleteHandle(IDbContextTransaction dbContextTransaction)
{
_dbContextTransaction = dbContextTransaction ?? throw new ArgumentNullException(nameof(dbContextTransaction));
}
public void Complete()
{
_dbContextTransaction.Commit();
}
public async Task CompleteAsync()
{
await _dbContextTransaction.CommitAsync();
}
public void Rollback()
{
_dbContextTransaction.Rollback();
}
public async Task RollbackAsync()
{
await _dbContextTransaction.RollbackAsync();
}
public void Dispose()
{
_dbContextTransaction.Dispose();
}
}
}

View File

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace DG.EntityFramework
{
public class UnitOfWorkCompleteScopeHandle<TDbContext> : IUnitOfWorkCompleteHandle
where TDbContext : DbContext
{
private readonly TransactionScope _transactionScope;
public UnitOfWorkCompleteScopeHandle(TransactionScope transactionScope)
{
_transactionScope = transactionScope ?? throw new ArgumentNullException(nameof(transactionScope));
}
public void Complete()
{
_transactionScope.Complete();
}
public async Task CompleteAsync()
{
await Task.Run(() => _transactionScope.Complete());
}
public void Rollback()
{
_transactionScope.Dispose();
}
public async Task RollbackAsync()
{
await Task.Run(() => _transactionScope.Dispose());
}
public void Dispose()
{
_transactionScope.Dispose();
}
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Threading.Tasks;
using System.Transactions;
namespace DG.EntityFramework
{
public class UnitOfWorkManager<TDbContext> : IUnitOfWorkManager
where TDbContext : DbContext
{
private readonly TDbContext _dbContext;
public UnitOfWorkManager(TDbContext dbContext)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
public IUnitOfWorkCompleteHandle Begin()
{
var tran = _dbContext.Database.BeginTransaction();
var handle = new UnitOfWorkCompleteHandle<TDbContext>(tran);
return handle;
}
public IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope)
{
return Begin(new UnitOfWorkOptions { Scope = scope });
}
public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
{
var scope = new TransactionScope(
options.Scope.GetValueOrDefault(),
new TransactionOptions {
Timeout = options.Timeout.GetValueOrDefault(),
IsolationLevel = options.IsolationLevel.GetValueOrDefault()
});
var handle = new UnitOfWorkCompleteScopeHandle<TDbContext>(scope);
return handle;
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Transactions;
namespace DG.EntityFramework
{
/// <summary>
/// Unit of work options.
/// </summary>
public class UnitOfWorkOptions
{
/// <summary>
/// Creates a new UnitOfWorkOptions object.
/// </summary>
public UnitOfWorkOptions()
{
IsTransactional = true;
Scope = TransactionScopeOption.Required;
Timeout = TimeSpan.FromMinutes(2);
IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
}
/// <summary>
/// Scope option.
/// </summary>
public TransactionScopeOption? Scope { get; set; }
/// <summary>
/// Is this Unit of work transactional? Uses default value if not supplied.
/// </summary>
public bool? IsTransactional { get; set; }
/// <summary>
/// Timeout of Unit of work As milliseconds. Uses default value if not supplied.
/// </summary>
public TimeSpan? Timeout { get; set; }
/// <summary>
/// If this Unit of work is transactional, this option indicated the isolation level of the transaction. Uses default value if not supplied.
/// </summary>
public IsolationLevel? IsolationLevel { get; set; }
/// <summary>
/// This option should be set to System.Transactions.TransactionScopeAsyncFlowOption.Enabled if unit of work is used in an async scope.
/// </summary>
public TransactionScopeAsyncFlowOption? AsyncFlowOption { get; set; }
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DG.EventBus
{
public class EventBus<TEvent> : IEventBus<TEvent> where TEvent : IEvent
{
private readonly EventQueue eventQueue = new EventQueue();
private readonly IEnumerable<IEventHandler<TEvent>> eventHandlers;
public EventBus(IEnumerable<IEventHandler<TEvent>> eventHandlers)
{
this.eventHandlers = eventHandlers;
Subscribe();
}
/// <summary>
/// 发布事件到队列时触发处理事件
/// </summary>
/// <param name="sendere"></param>
/// <param name="e"></param>
private void EventQueue_EventPushed(object sendere, EventProcessedEventArgs e)
{
(from eh in this.eventHandlers
where
eh.CanHandle((TEvent)e.Event)
select eh).ToList().ForEach(async eh => await eh.HandleAsync((TEvent)e.Event));
}
public Task publicAsync(TEvent @event, CancellationToken cancellationToken = default)
=> Task.Factory.StartNew(() => eventQueue.Push(@event));
/// <summary>
/// 事件订阅(订阅队列上的事件)
/// </summary>
public void Subscribe()
{
eventQueue.EventPushed += EventQueue_EventPushed;
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DG.EventBus
{
/// <summary>
/// 消息事件参数
/// </summary>
public class EventProcessedEventArgs : EventArgs
{
public IEvent Event { get; }
public EventProcessedEventArgs(IEvent @event)
{
Event = @event;
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DG.EventBus
{
public class EventQueue
{
public event EventHandler<EventProcessedEventArgs> EventPushed;
public EventQueue()
{
}
public void Push(IEvent @event)
{
OnMessagePushed(new EventProcessedEventArgs(@event));
}
private void OnMessagePushed(EventProcessedEventArgs e)
{
this.EventPushed?.Invoke(this, e);
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace DG.EventBus
{
public interface IEvent
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DG.EventBus
{
public interface IEventBus<TEvent> : IEventSubscriber, IEventpublicer<TEvent> where TEvent : IEvent
{
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DG.EventBus
{
public interface IEventHandler<TEvent> where TEvent : IEvent
{
/// <summary>
/// 处理事件
/// </summary>
/// <param name="event"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<bool> HandleAsync(TEvent @event, CancellationToken cancellationToken = default);
/// <summary>
/// 可否处理
/// </summary>
/// <param name="event"></param>
/// <returns></returns>
bool CanHandle(TEvent @event);
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DG.EventBus
{
public interface IEventpublicer<TEvent> where TEvent : IEvent
{
/// <summary>
/// 发布事件
/// </summary>
/// <typeparam name="TEvent"></typeparam>
/// <param name="event"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task publicAsync(TEvent @event, CancellationToken cancellationToken = default);
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DG.EventBus
{
public interface IEventSubscriber
{
/// <summary>
/// 事件订阅
/// </summary>
void Subscribe();
}
}

View File

@ -0,0 +1,28 @@
using DG.EventBus;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extensions method
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// EventBus service registered
/// </summary>
/// <param name="services"></param>
/// <param name="options">Redis config Options</param>
/// <returns></returns>
public static IServiceCollection AddEventBus(this IServiceCollection services)
{
services.AddSingleton(typeof(IEventBus<>), typeof(EventBus<>));
return services;
}
}
}

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>18ffe2b4-7ab9-40b9-8f3e-00211cd240e0</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
<PackageReference Include="Hangfire.HttpJob" Version="3.7.9" />
<PackageReference Include="Hangfire.HttpJob.Agent" Version="1.4.7" />
<PackageReference Include="Hangfire.HttpJob.Client" Version="1.2.9" />
<PackageReference Include="Hangfire.MySqlStorage" Version="2.0.3" />
<PackageReference Include="HangFire.Redis" Version="2.0.1" />
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,22 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["DG.HttpJob/DG.HttpJob.csproj", "DG.HttpJob/"]
RUN dotnet restore "DG.HttpJob/DG.HttpJob.csproj"
COPY . .
WORKDIR "/src/DG.HttpJob"
RUN dotnet build "DG.HttpJob.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DG.HttpJob.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DG.HttpJob.dll"]

View File

@ -0,0 +1,118 @@
using Hangfire.Dashboard.BasicAuthorization;
using Hangfire;
using System.Configuration;
using Hangfire.HttpJob;
using Hangfire.MySql;
using System.Transactions;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHangfire(Configuration);
builder.Services.AddHangfireServer();
var app = builder.Build();
void Configuration(IGlobalConfiguration globalConfiguration)
{
// ConfigManagerConf.SetConfiguration(_appConfiguration);
var mailServer = builder.Configuration.GetValue<string>("JobMail:Server");
var mailPort = builder.Configuration.GetValue<int>("JobMail:Port");
var mailUseSsl = builder.Configuration.GetValue<bool>("JobMail:UseSsl");
var mailUser = builder.Configuration.GetValue<string>("JobMail:User");
var mailPassword = builder.Configuration.GetValue<string>("JobMail:Password");
var jobOptions = new HangfireHttpJobOptions
{
MailOption = new MailOption
{
Server = mailServer,
Port = mailPort,
UseSsl = mailUseSsl,
User = mailUser,
Password = mailPassword,
},
DefaultRecurringQueueName = "recurring" //这个是在下面设置的queue列表中的其中一个
};
var tablePrefix = builder.Configuration.GetValue<string>("JobConfig:TablePrefix");
var mySqlStorage = new MySqlStorageOptions
{
TransactionIsolationLevel = IsolationLevel.ReadCommitted,
QueuePollInterval = TimeSpan.FromSeconds(15),
JobExpirationCheckInterval = TimeSpan.FromHours(1),
CountersAggregateInterval = TimeSpan.FromMinutes(5),
PrepareSchemaIfNecessary = true,
DashboardJobListLimit = 50000,
TransactionTimeout = TimeSpan.FromMinutes(1)
};
var redisString = builder.Configuration.GetConnectionString("Jobs");
globalConfiguration.UseStorage(new MySqlStorage(redisString, mySqlStorage))
.UseHangfireHttpJob();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//强制显示中文
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-CN");
var serverName = "HttpJobs";
var queues = new List<string> { "default", "apis", "recurring" };
var jobOptions = new BackgroundJobServerOptions
{
Queues = queues.ToArray(),//队列名称,只能为小写
SchedulePollingInterval = TimeSpan.FromSeconds(15),//秒级任务需要配置短点一般任务可以配置默认时间默认15秒
ShutdownTimeout = TimeSpan.FromMinutes(30),//超时时间
WorkerCount = Environment.ProcessorCount * 5, //并发任务数
ServerName = serverName,//服务器名称
ServerTimeout = TimeSpan.FromMinutes(5)
};
var userName = builder.Configuration.GetValue<string>("JobConfig:UserName");
var password = builder.Configuration.GetValue<string>("JobConfig:Password");
var hangfireStartUpPath = "/jobs";
var hangfireFilterOptions = new DashboardOptions
{
Authorization = new[] { new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions
{
RequireSsl = false,
SslRedirect = false,
LoginCaseSensitive = true,
Users = new []
{
new BasicAuthAuthorizationUser
{
Login = userName,
PasswordClear = password,
}
}
}) },
DisplayStorageConnectionString = false,
AppPath = "#"
};
//app.UseHangfireServer();
app.UseHangfireDashboard(hangfireStartUpPath, hangfireFilterOptions);
var hangfireReadOnlyPath = "/jobs-read";
//只读面板,只能读取不能操作
app.UseHangfireDashboard(hangfireReadOnlyPath, new DashboardOptions
{
IgnoreAntiforgeryToken = true,
AppPath = hangfireStartUpPath,//返回时跳转的地址
DisplayStorageConnectionString = false,//是否显示数据库连接信息
IsReadOnlyFunc = Context => true
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,38 @@
{
"profiles": {
"DG.HttpJob": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "jobs",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7209;http://localhost:5080"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "jobs",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/jobs",
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53403",
"sslPort": 44333
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,24 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Jobs": "Server=192.168.11.41;Database=job;UserId=root;Password=sa123456.;port=3306;Convert Zero Datetime=True;Allow User Variables=True;pooling=true"
},
"JobConfig": {
"TablePrefix": "Dev",
"UserName": "admin",
"Password": "admin"
},
"JobMail": {
"Server": "smtp.exmail.qq.com",
"Port": 465,
"UseSsl": true,
"User": "",
"Password": ""
}
}

View File

@ -0,0 +1,67 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DG.Kafka.Worker
{
public abstract class BatchKafkaWorkerBase<T>
{
private readonly ILogger<BatchKafkaWorkerBase<T>> _logger;
private TaskConfig _taskConfig;
private int _milliSecondsDelay;
public BatchKafkaWorkerBase(ILogger<BatchKafkaWorkerBase<T>> logger)
{
_logger = logger;
_taskConfig = KafkaClient.Default.ConfigurationManager.GetSection("TaskConfig").Get<TaskConfig>();
if (_taskConfig == null)
{
throw new ArgumentNullException(nameof(_taskConfig));
}
_milliSecondsDelay = _taskConfig.MilliSecondsDelay;
}
public async Task Start(List<T> t)
{
Stopwatch watch = new Stopwatch();
watch.Reset();
watch.Start();
try
{
if (_milliSecondsDelay <= 0)
{
_logger.LogWarning($"[{DateTimeOffset.Now}] [{_taskConfig.TaskName}] 任务定时时间不能少于0");
return;
}
if (!_taskConfig.Enable)
{
await Task.Delay(_milliSecondsDelay);
_logger.LogWarning($"[{DateTimeOffset.Now}] [{_taskConfig.TaskName}] 任务停止启动!");
return;
}
await DoWorkAsync(t);
}
catch (Exception ex)
{
_logger.LogError(ex, $"[{DateTimeOffset.Now}] [{_taskConfig.TaskName}]");
}
watch.Stop();
double costtime = watch.ElapsedMilliseconds;
_logger.LogDebug($"[{DateTimeOffset.Now}] [{_taskConfig.TaskName}] 任务执行结束,用时:{costtime}ms");
}
protected virtual async Task DoWorkAsync(List<T> t)
{
}
public virtual async Task ShopAsync()
{
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.0.21</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DG.Kafka\DG.Kafka.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DG.Kafka.Worker
{
public interface IKafkaWorkerManager
{
Task RegisterWorker<TWorker, T>(string? topic) where TWorker : KafkaWorkerBase<T>;
Task RegisterBatchWorker<TWorker, T>(string? topic, int batchsize = 1000) where TWorker : BatchKafkaWorkerBase<T>;
}
}

Some files were not shown because too many files have changed in this diff Show More