init
This commit is contained in:
commit
a9c888e9e7
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 正常请求
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
// 执行中的过滤器管道
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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; }//是否绑定客服
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using DG.EventBus;
|
||||
|
||||
namespace DG.DotNet.Sample
|
||||
{
|
||||
public class TestEvent : IEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace DG.EventBus
|
||||
{
|
||||
public interface IEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DG.EventBus
|
||||
{
|
||||
public interface IEventSubscriber
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件订阅
|
||||
/// </summary>
|
||||
void Subscribe();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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();
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
Loading…
Reference in New Issue