This commit is contained in:
Kimdiego2098
2024-04-03 19:01:57 +08:00
commit e5dd7cc2fa
467 changed files with 56607 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

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

363
.gitignore vendored Normal file
View File

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

202
LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023-present Diego
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

67
README.md Normal file
View File

@@ -0,0 +1,67 @@
# ThingsGateway
## 介绍
**NetCore** 跨平台边缘采集网关(工业设备采集)
**ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
**ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 **ThingsGateway - Admin**
## 文档
[ThingsGateway](https://diego2098.gitee.io/thingsgateway-docs/) 文档。
### 插件列表
#### 采集插件
| 插件名称 | 备注 |
|-------|-------|
| ModbusMaster | Rtu/Tcp报文格式支持串口/Tcp/Udp链路 |
| S7 | 西门子PLC S7系列 |
| Dlt6452007 | Master支持串口/Tcp/Udp链路 |
| OpcDaClient | 64位编译 |
| OpcUaClient | 支持证书登录扩展对象Json读写 |
#### 业务插件
| 插件名称 | 备注 |
|-------|-------|
| ModbusSlave | Rtu/Tcp报文格式支持串口/Tcp/Udp链路支持Rpc反写 |
| OpcUaServer | OpcUa服务端支持Rpc反写 |
| Mqtt Client | Mqtt客户端支持Rpc反写脚本自定义上传内容 |
| Mqtt Server | Mqtt服务端支持WebSocket支持Rpc反写脚本自定义上传内容 |
| Kafka Client | 数据生产,脚本自定义上传内容 |
| RabbitMQ Client | 数据生产,脚本自定义上传内容 |
| SqlDB | 关系数据库存储,支持历史存储和实时数据更新 |
| SqlHisAlarm | 报警历史数据关系数据库存储 |
| TDengineDB | 时序数据库存储 |
| QuestDB | 时序数据库存储 |
## 协议
[ThingsGateway](https://gitee.com/diego2098/ThingsGateway) 采用 [Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE) 开源协议。
## 演示
[ThingsGateway演示地址](http://120.24.62.140:5000/)
账户 : **superAdmin**
密码 : **111111**
## 赞助
[ThingsGateway赞助途径](https://diego2098.gitee.io/thingsgateway-docs/docs/1000)
## 社区
QQ群605534569 [跳转](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569)
## Pro插件
[插件列表](https://diego2098.gitee.io/thingsgateway-docs/docs/1001)

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

13
src/Delete .vs .bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
chcp 65001
setlocal enabledelayedexpansion
set "folder=%~dp0"
attrib -s -h "%folder%\.vs" >nul 2>&1
if exist "%folder%\.vs" (
rd /s /q "%folder%\.vs"
echo 删除了.vs文件夹%folder%\.vs
)
echo 删除完成!
pause

View File

@@ -0,0 +1,19 @@
@echo off
chcp 65001
setlocal enabledelayedexpansion
set "folder=%~dp0"
for /r "%folder%" /d %%i in (*) do (
set "dirname=%%~nxi"
if /I "!dirname!"=="bin" (
rd /s /q "%%i"
echo 删除了名称为"bin"的文件夹:%%i
)
if /I "!dirname!"=="obj" (
rd /s /q "%%i"
echo 删除了名称为"obj"的文件夹:%%i
)
)
echo 删除完成!
pause

24
src/Directory.Build.props Normal file
View File

@@ -0,0 +1,24 @@
<Project>
<PropertyGroup>
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;</NoWarn>
<TargetFrameworks>net8.0;</TargetFrameworks>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>5.0.3.8</Version>
<Authors>Diego</Authors>
<Company>Diego</Company>
<Product>Diego</Product>
<Copyright>版权所有 © 2023-present Diego</Copyright>
<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl>
<RepositoryType>Gitee</RepositoryType>
<GenerateResxSourceIncludeDefaultValues>true</GenerateResxSourceIncludeDefaultValues>
</PropertyGroup>
<ItemGroup>
<None Include="$(SolutionDir)..\README.md" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)..\LICENSE" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)..\icon.png" Pack="true" PackagePath="\" />
<None Include="$(SolutionDir)Directory.Build.props" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,161 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Rougamo;
using Rougamo.Context;
using System.Collections.Concurrent;
using ThingsGateway.Core.Extension;
using ThingsGateway.Core.Json.Extension;
using UAParser;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// Aop拦截器
/// </summary>
public class OperDescAttribute : MoAttribute
{
/// <summary>
/// 日志消息队列(线程安全)
/// </summary>
private static readonly ConcurrentQueue<SysOperateLog> _logMessageQueue = new();
static OperDescAttribute()
{
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);
}
public OperDescAttribute(string description, bool isRecordPar = true)
{
Description = description;
IsRecordPar = isRecordPar;
}
public override AccessFlags Flags => AccessFlags.Public | AccessFlags.Method;
public override Feature Features => Feature.OnException | Feature.OnSuccess;
/// <summary>
/// 说明需配置本地化json文件
/// </summary>
public string Description { get; }
/// <summary>
/// 是否记录进出参数
/// </summary>
public bool IsRecordPar { get; }
public override void OnSuccess(MethodContext context)
{
//插入操作日志
SysOperateLog log = GetOperLog(context);
WriteToQueue(log);
}
public override void OnException(MethodContext context)
{
//插入异常日志
SysOperateLog log = GetOperLog(context);
log.Category = LogCateGoryEnum.Exception;//操作类型为异常
log.ExeStatus = false;//操作状态为失败
if (context.Exception is UserFriendlyException exception)
log.ExeMessage = exception?.Message;
else
log.ExeMessage = context.Exception?.ToString();
WriteToQueue(log);
}
/// <summary>
/// 将日志消息写入数据库中
/// </summary>
private static async Task ProcessQueue()
{
var db = DbContext.Db.CopyNew();
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
while (!(appLifetime.ApplicationStopping.IsCancellationRequested || appLifetime.ApplicationStopped.IsCancellationRequested))
{
try
{
if (_logMessageQueue.Count > 0)
{
await db.InsertableWithAttr(_logMessageQueue.ToListWithDequeue()).ExecuteCommandAsync();//入库
}
await Task.Delay(3000, appLifetime.ApplicationStopping);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
private SysOperateLog GetOperLog(MethodContext context)
{
var str = App.HttpContext?.Request?.Headers?.UserAgent;
var methodBase = context.Method;
ClientInfo? clientInfo = null;
if (str.HasValue)
{
clientInfo = Parser.GetDefault().Parse(str);
}
string? paramJson = null;
if (IsRecordPar)
{
var args = context.Arguments;
var parametersInfo = methodBase.GetParameters();
var parametersDict = new Dictionary<string, object>();
for (int i = 0; i < parametersInfo.Length; i++)
{
parametersDict[parametersInfo[i].Name!] = args[i];
}
paramJson = parametersDict.ToSystemTextJsonString();
}
var result = context.ReturnValue;
var resultJson = IsRecordPar ? result?.ToSystemTextJsonString() : null;
//操作日志表实体
var log = new SysOperateLog
{
Name = App.CreateLocalizerByType(typeof(OperDescAttribute))![Description],
Category = LogCateGoryEnum.Operate,
ExeStatus = true,
OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4()?.ToString(),
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = DateTime.Now,
OpAccount = UserManager.UserAccount,
ReqUrl = null,
ReqMethod = "browser",
ResultJson = resultJson,
ClassName = methodBase.ReflectedType!.Name,
MethodName = methodBase.Name,
ParamJson = paramJson,
VerificatId = UserManager.VerificatId,
};
return log;
}
/// <summary>
/// 将日志消息写入队列中等待后台任务出队写入数据库
/// </summary>
/// <param name="logMsg">结构化日志消息</param>
private void WriteToQueue(SysOperateLog logMsg)
{
_logMessageQueue.Enqueue(logMsg);
}
}

View File

@@ -0,0 +1,19 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 忽略Excel导入导出
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreExcelAttribute : Attribute
{
}

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 需要角色授权权限
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class RolePermissionAttribute : Attribute
{
}
/// <summary>
/// 忽略角色授权权限
/// </summary>
public class IgnoreRolePermissionAttribute : Attribute
{
}

View File

@@ -0,0 +1,35 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 种子数据忽略新增
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreSeedDataAddAttribute : Attribute
{
}
/// <summary>
/// 种子数据忽略修改
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreSeedDataUpdateAttribute : Attribute
{
}
/// <summary>
/// 忽略初始化表
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreInitTableAttribute : Attribute
{
}

View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 管理员才能访问
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SuperAdminAttribute : Attribute
{
}
/// <summary>
/// 忽略超级管理员才能访问特性
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class IgnoreSuperAdminAttribute : Attribute
{
}

View File

@@ -0,0 +1,46 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 最小值校验
/// </summary>
public class MinValueAttribute : ValidationAttribute
{
/// <summary>
/// 最小值
/// </summary>
/// <param name="value"></param>
public MinValueAttribute(int value)
{
MinValue = value;
}
private int MinValue { get; set; }
/// <summary>
/// 最小值校验
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public override bool IsValid(object? value)
{
if (value is null)
{
return false;
}
var input = Convert.ToInt32(value);
return input >= MinValue;
}
}

View File

@@ -0,0 +1,56 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application
{
public class CacheConst
{
public const string Cache_Prefix_Admin = "ThingsGatewayAdmin:";
/// <summary>
/// 系统字典表缓存Key
/// </summary>
public const string Cache_SysDict = $"{CacheConst.Cache_Prefix_Admin}SysDict:";
/// <summary>
/// 用户表缓存Key
/// </summary>
public const string Cache_SysUser = $"{CacheConst.Cache_Prefix_Admin}SysUser:";
/// <summary>
/// 用户账号关系缓存Key
/// </summary>
public const string Cache_SysUserAccount = $"{CacheConst.Cache_Prefix_Admin}SysUserAccount:";
/// <summary>
/// 资源表缓存Key
/// </summary>
public const string Cache_SysResource = $"{CacheConst.Cache_Prefix_Admin}SysResource:";
/// <summary>
/// 关系表缓存Key
/// </summary>
public const string Cache_SysRelation = $"{CacheConst.Cache_Prefix_Admin}SysRelation:";
/// <summary>
/// 角色表缓存Key
/// </summary>
public const string Cache_SysRole = $"{CacheConst.Cache_Prefix_Admin}SysRole:";
#region
/// <summary>
/// 登录错误次数缓存Key
/// </summary>
public const string Cache_LoginErrorCount = $"{CacheConst.Cache_Prefix_Admin}LoginErrorCount:";
#endregion
}
}

View File

@@ -0,0 +1,37 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 授权用户常量
/// </summary>
public class ClaimConst
{
/// <summary>
/// 账号
/// </summary>
public const string Account = "Account";
/// <summary>
/// SuperAdmin
/// </summary>
public const string SuperAdmin = "SuperAdmin";
/// <summary>
/// 用户Id
/// </summary>
public const string UserId = "UserId";
/// <summary>
/// 验证Id
/// </summary>
public const string VerificatId = "VerificatId";
}

View File

@@ -0,0 +1,37 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 资源表常量
/// </summary>
public class ResourceConst
{
/// <summary>
/// 系统内置编码
/// </summary>
public const string System = "System";
/// <summary>
/// 系统管理内置ID 1
/// </summary>
public const long SystemId = 2;
/// <summary>
/// SPA内置ID 2
/// </summary>
public const long SpaId = 1;
/// <summary>
/// SPA内置
/// </summary>
public const string SpaTitle = "SPA";
}

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 角色常量
/// </summary>
public class RoleConst
{
/// <summary>
/// 超级管理员
/// </summary>
public const string SuperAdmin = "SuperAdmin";
/// <summary>
/// 业务管理员
/// </summary>
public const string BizAdmin = "BizAdmin";
/// <summary>
/// api角色
/// </summary>
public const string ApiRole = "ApiRole";
}

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// SqlSugar系统常量
/// </summary>
public class SqlSugarConst
{
/// <summary>
/// DB_Admin
/// </summary>
public const string DB_Admin = "DB_Admin";
/// <summary>
/// DB_Log
/// </summary>
public const string DB_Log = "DB_Log";
/// <summary>
/// DB_Custom
/// </summary>
public const string DB_Custom = "DB_Custom";
}

View File

@@ -0,0 +1,47 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
namespace ThingsGateway.Admin.Application;
[Route("auth")]
[LoggingMonitor]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<LoginOutput> LoginAsync([FromBody] LoginInput input)
{
return await _authService.LoginAsync(input);
}
[HttpGet("logout")]
[Authorize]
[IgnoreRolePermission]
public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl)
{
await _authService.LoginOutAsync();
return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
{
["ReturnUrl"] = returnUrl
}));
}
}

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 文化 Controller
/// </summary>
[Route("[controller]/[action]")]
public class CultureController : Controller
{
/// <summary>
/// 设置文化方法
/// </summary>
/// <param name="culture"></param>
/// <param name="redirectUri"></param>
/// <returns></returns>
[HttpGet]
public IActionResult SetCulture(string culture, string redirectUri)
{
if (string.IsNullOrEmpty(culture))
{
HttpContext.Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);
}
else
{
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture, culture)), new CookieOptions()
{
Expires = DateTimeOffset.Now.AddYears(1)
});
}
return LocalRedirect(redirectUri);
}
/// <summary>
/// 重置文化方法
/// </summary>
/// <param name="redirectUri"></param>
/// <returns></returns>
[HttpGet]
public IActionResult ResetCulture(string redirectUri)
{
HttpContext.Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);
return LocalRedirect(redirectUri);
}
}

View File

@@ -0,0 +1,36 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ThingsGateway.Admin.Application;
[Route("export")]
public class ExportController : ControllerBase
{
private readonly ISysOperateLogService _operateLogService;
public ExportController(ISysOperateLogService operateLogService)
{
_operateLogService = operateLogService;
}
[HttpGet("operateLog")]
[Authorize]
[IgnoreRolePermission]
public async Task<IActionResult> DownloadOperateLogAsync([FromQuery] QueryPageOptions input)
{
var fileStreamResult = await _operateLogService.ExportFileAsync(input);
return fileStreamResult;
}
}

View File

@@ -0,0 +1,346 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json;
using ThingsGateway.Core.Json.Extension;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 规范化RESTful风格返回值
/// </summary>
public class ResultFilter : IAsyncActionFilter
{
public const string ValidationFailedKey = $"{nameof(ResultFilter)}Validate";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 排除 WebSocket 请求处理
if (context.HttpContext.IsWebSocketRequest())
{
await next();
return;
}
var httpContext = context.HttpContext;
var unifyResult = httpContext.RequestServices.GetRequiredService<IUnifyResultProvider>();
#region
// 解析验证消息
{
if (!context.ModelState.IsValid)
{
var allValidationResults = new List<ValidationResult>();
int errorCount = 0;
//重新获取错误信息
foreach (var item in context.ActionArguments)
{
if (errorCount == context.ModelState.ErrorCount) break;
foreach (var parameter in context.ModelState)
{
if (errorCount == context.ModelState.ErrorCount) break;
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(item.Value!);
ValidateProperty(validationContext, validationResults, parameter.Key);
allValidationResults.AddRange(validationResults);
errorCount += validationResults.Count;
}
}
//var validationMetadata = GetValidationMetadata(context.ModelState!);
if (allValidationResults.Count > 0)
{
string? errorMessage;
if (allValidationResults.Count == 1)
{
errorMessage = allValidationResults.FirstOrDefault()!.ErrorMessage;
}
else
{
errorMessage = allValidationResults!.ToDictionary(a => a.MemberNames.FirstOrDefault()!, a => a.ErrorMessage).ToSystemTextJsonString();
}
var result = unifyResult.OnValidateFailed(context, errorMessage);
if (result != null)
{
context.Result = result;
// 存储验证执行结果
context.HttpContext.Items[ValidationFailedKey] = errorMessage;
return;
}
}
}
}
#endregion
// 执行 Action 并获取结果
ActionExecutedContext? actionExecutedContext = await next();
#region
// 如果出现异常
if (actionExecutedContext.Exception != null)
{
// 判断是否支持 MVC 规范化处理
if (UnifyContext.CheckHttpContextNonUnify(httpContext)) return;
// 执行规范化异常处理
actionExecutedContext.Result = unifyResult.OnException(actionExecutedContext);
actionExecutedContext.ExceptionHandled = true;
return;
}
#endregion
#region
// 处理已经含有状态码结果的 Result
if (actionExecutedContext.Result is IStatusCodeActionResult statusCodeResult && statusCodeResult.StatusCode != null)
{
// 小于 200 或者 大于 299 都不是成功值,直接跳过
if (statusCodeResult.StatusCode.Value < 200 || statusCodeResult.StatusCode.Value > 299)
{
// 处理规范化结果
if (!UnifyContext.CheckStatusCodeNonUnify(httpContext))
{
var statusCode = statusCodeResult.StatusCode.Value;
// 解决刷新 Token 时间和 Token 时间相近问题
if (statusCodeResult.StatusCode.Value == StatusCodes.Status401Unauthorized
&& httpContext.Response.Headers.ContainsKey("access-token")
&& httpContext.Response.Headers.ContainsKey("x-access-token"))
{
httpContext.Response.StatusCode = statusCode = StatusCodes.Status403Forbidden;
}
// 如果 Response 已经完成输出,则禁止写入
if (httpContext.Response.HasStarted) return;
await unifyResult.OnResponseStatusCodes(httpContext, statusCode);
}
return;
}
}
#endregion
#region
// 获取控制器信息
var actionDescriptor = actionExecutedContext.ActionDescriptor as ControllerActionDescriptor;
// 判断是否支持 MVC 规范化处理或特定检查
if (UnifyContext.CheckHttpContextNonUnify(httpContext)) return;
// 判断是否跳过规范化处理
if (UnifyContext.CheckSucceededNonUnify(actionDescriptor!.MethodInfo)) return;
// 处理 BadRequestObjectResult 类型规范化处理
if (actionExecutedContext.Result is BadRequestObjectResult badRequestObjectResult)
{
// 解析验证消息
var validationMetadata = GetValidationMetadata(badRequestObjectResult.Value!);
var result = unifyResult.OnValidateFailed(context, validationMetadata);
if (result != null) actionExecutedContext.Result = result;
}
else
{
IActionResult? result = default;
// 检查是否是有效的结果(可进行规范化的结果)
if (UnifyContext.CheckVaildResult(actionExecutedContext.Result!, out var data))
{
result = unifyResult.OnSucceeded(actionExecutedContext, data);
}
// 如果是不能规范化的结果类型,则跳过
if (result == null) return;
actionExecutedContext.Result = result;
}
#endregion
}
/// <summary>
/// 获取验证错误信息
/// </summary>
/// <param name="errors"></param>
/// <returns></returns>
internal static string GetValidationMetadata(object errors)
{
object? validationResults = null;
string? message = default;
// 判断是否是集合类型
if (errors is IEnumerable && errors is not string)
{
// 如果是模型验证字典类型
if (errors is ModelStateDictionary modelState)
{
// 将验证错误信息转换成字典并序列化成 Json
validationResults = modelState.Where(u => modelState[u.Key]!.ValidationState == ModelValidationState.Invalid)
.ToDictionary(u => u.Key, u => modelState[u.Key]!.Errors.Select(c => c.ErrorMessage).ToArray());
}
// 如果是 ValidationProblemDetails 特殊类型
else if (errors is ValidationProblemDetails validation)
{
validationResults = validation.Errors
.ToDictionary(u => u.Key, u => u.Value.ToArray());
}
// 如果是字典类型
else if (errors is IDictionary<string, string[]> dicResults)
{
validationResults = dicResults;
}
message = JsonSerializer.Serialize(validationResults, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
});
}
return message;
}
/// <summary>
/// 获取异常元数据
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetExceptionMetadata(ActionContext context)
{
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
var exception = context is ExceptionContext exContext
? exContext.Exception
: (
context is ActionExecutedContext edContext
? edContext.Exception
: default
);
string? errors = exception?.InnerException?.Message ?? exception?.Message;
return errors;
}
/// <summary>
/// 验证整个模型时验证属性方法
/// </summary>
/// <param name="context"></param>
/// <param name="results"></param>
/// <param name="pName"></param>
private void ValidateProperty(ValidationContext context, List<ValidationResult> results, string pName)
{
// 获得所有可写属性
var pi = context.ObjectType.GetPropertyByName(pName);
if (pi != null)
{
// 设置其关联属性字段
var propertyValue = pi.GetValue(context.ObjectInstance);
var fieldIdentifier = new FieldIdentifier(context.ObjectInstance, pi.Name);
context.DisplayName = fieldIdentifier.GetDisplayName();
context.MemberName = fieldIdentifier.FieldName;
// 组件进行验证
ValidateDataAnnotations(propertyValue, context, results, pi);
}
}
/// <summary>
/// 通过属性设置的 DataAnnotation 进行数据验证
/// </summary>
/// <param name="value"></param>
/// <param name="context"></param>
/// <param name="results"></param>
/// <param name="propertyInfo"></param>
/// <param name="memberName"></param>
private void ValidateDataAnnotations(object? value, ValidationContext context, List<ValidationResult> results, PropertyInfo propertyInfo, string? memberName = null)
{
var rules = propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>().ToList();
var metadataType = context.ObjectType.GetCustomAttribute<MetadataTypeAttribute>(false);
if (metadataType != null)
{
var p = metadataType.MetadataClassType.GetPropertyByName(propertyInfo.Name);
if (p != null)
{
rules.AddRange(p.GetCustomAttributes(true).OfType<ValidationAttribute>());
}
}
var displayName = context.DisplayName;
memberName ??= propertyInfo.Name;
var attributeSpan = nameof(Attribute).AsSpan();
foreach (var rule in rules)
{
var result = rule.GetValidationResult(value, context);
if (result != null && result != ValidationResult.Success)
{
// 查找 resx 资源文件中的 ErrorMessage
var ruleNameSpan = rule.GetType().Name.AsSpan();
var index = ruleNameSpan.IndexOf(attributeSpan, StringComparison.OrdinalIgnoreCase);
var ruleName = ruleNameSpan[..index];
var find = false;
// 通过设置 ErrorMessage 检索
if (!context.ObjectType.Assembly.IsDynamic && !find
&& !string.IsNullOrEmpty(rule.ErrorMessage)
&& App.CreateLocalizerByType(context.ObjectType)!.TryGetLocalizerString(rule.ErrorMessage, out var msg))
{
rule.ErrorMessage = msg;
find = true;
}
// 通过 Attribute 检索
if (!rule.GetType().Assembly.IsDynamic && !find
&& App.CreateLocalizerByType(rule.GetType())!.TryGetLocalizerString(nameof(rule.ErrorMessage), out msg))
{
rule.ErrorMessage = msg;
find = true;
}
// 通过 字段.规则名称 检索
if (!context.ObjectType.Assembly.IsDynamic && !find
&& App.CreateLocalizerByType(context.ObjectType)!.TryGetLocalizerString($"{memberName}.{ruleName.ToString()}", out msg))
{
rule.ErrorMessage = msg;
find = true;
}
if (!find)
{
rule.ErrorMessage = result.ErrorMessage;
}
var errorMessage = !string.IsNullOrEmpty(rule.ErrorMessage) && rule.ErrorMessage.Contains("{0}")
? rule.FormatErrorMessage(displayName)
: rule.ErrorMessage;
results.Add(new ValidationResult(errorMessage, new string[] { memberName }));
}
}
}
}

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 规范化结果提供器
/// </summary>
public interface IUnifyResultProvider
{
/// <summary>
/// 异常返回值
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
IActionResult OnException(ActionExecutedContext context);
/// <summary>
/// 成功返回值
/// </summary>
/// <param name="context"></param>
/// <param name="data"></param>
/// <returns></returns>
IActionResult OnSucceeded(ActionExecutedContext context, object? data);
/// <summary>
/// 验证失败返回值
/// </summary>
/// <param name="context"></param>
/// <param name="errors"></param>
/// <returns></returns>
IActionResult OnValidateFailed(ActionExecutingContext context, string? errors);
/// <summary>
/// 拦截返回状态码
/// </summary>
/// <param name="context"></param>
/// <param name="statusCode"></param>
/// <returns></returns>
Task OnResponseStatusCodes(HttpContext context, int statusCode);
}

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace Microsoft.AspNetCore.Mvc;
/// <summary>
/// 禁止规范化处理
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class NonUnifyAttribute : Attribute
{
}

View File

@@ -0,0 +1,145 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Reflection;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 规范化结果上下文
/// </summary>
public static class UnifyContext
{
/// <summary>
/// 是否启用规范化结果
/// </summary>
internal static bool EnabledUnifyHandler = true;
/// <summary>
/// 检查请求成功是否进行规范化处理
/// </summary>
/// <param name="method"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
internal static bool CheckSucceededNonUnify(MethodInfo method)
{
// 判断是否跳过规范化处理
var isSkip = !EnabledUnifyHandler
|| method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|| method.ReflectedType!.IsDefined(typeof(NonUnifyAttribute), true)
|| method.DeclaringType!.Assembly.GetName().Name!.StartsWith("Microsoft.AspNetCore.OData");
return isSkip;
}
/// <summary>
/// 检查短路状态码(>=400是否进行规范化处理
/// </summary>
/// <param name="context"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
internal static bool CheckStatusCodeNonUnify(HttpContext context)
{
// 获取终点路由特性
var endpointFeature = context.Features.Get<IEndpointFeature>();
if (endpointFeature == null) return false;
// 判断是否跳过规范化处理
var isSkip = !EnabledUnifyHandler
|| context.Request.Headers["accept"].ToString().Contains("odata.metadata=", StringComparison.OrdinalIgnoreCase)
|| context.Request.Headers["accept"].ToString().Contains("odata.streaming=", StringComparison.OrdinalIgnoreCase)
|| ResponseContentTypesOfNonUnify.Any(u => context.Response.Headers["content-type"].ToString().Contains(u, StringComparison.OrdinalIgnoreCase)
|| context.GetMetadata<NonUnifyAttribute>() != null
|| endpointFeature?.Endpoint?.Metadata?.GetMetadata<NonUnifyAttribute>() != null
);
return isSkip;
}
/// <summary>
/// 跳过规范化处理的 Response Content-Type
/// </summary>
internal static string[] ResponseContentTypesOfNonUnify = new[]
{
"text/event-stream",
"application/pdf",
"application/octet-stream",
"image/"
};
/// <summary>
/// 检查 HttpContext 是否进行规范化处理
/// </summary>
/// <param name="httpContext"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
internal static bool CheckHttpContextNonUnify(HttpContext httpContext)
{
var contentType = httpContext.Response.Headers["content-type"].ToString();
if (ResponseContentTypesOfNonUnify.Any(u => contentType.Contains(u, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
return false;
}
/// <summary>
/// 检查是否是有效的结果(可进行规范化的结果)
/// </summary>
/// <param name="result"></param>
/// <param name="data"></param>
/// <returns></returns>
internal static bool CheckVaildResult(IActionResult result, out object? data)
{
data = default;
// 排除以下结果,跳过规范化处理
var isDataResult = result switch
{
ViewResult => false,
PartialViewResult => false,
FileResult => false,
ChallengeResult => false,
SignInResult => false,
SignOutResult => false,
RedirectToPageResult => false,
RedirectToRouteResult => false,
RedirectResult => false,
RedirectToActionResult => false,
LocalRedirectResult => false,
ForbidResult => false,
ViewComponentResult => false,
PageResult => false,
NotFoundResult => false,
NotFoundObjectResult => false,
_ => true,
};
// 目前支持返回值 ActionResult
if (isDataResult) data = result switch
{
// 处理内容结果
ContentResult content => content.Content,
// 处理对象结果
ObjectResult obj => obj.Value,
// 处理 JSON 对象
JsonResult json => json.Value,
_ => null,
};
return isDataResult;
}
}

View File

@@ -0,0 +1,38 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 全局返回结果
/// </summary>
/// <typeparam name="T"></typeparam>
public class UnifyResult<T>
{
/// <summary>
/// 状态码
/// </summary>
public int Code { get; set; }
/// <summary>
/// 数据
/// </summary>
public T? Data { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public object? Msg { get; set; }
/// <summary>
/// 时间
/// </summary>
public DateTime Time { get; set; }
}

View File

@@ -0,0 +1,99 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Localization;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 规范化RESTful风格返回值
/// </summary>
public class UnifyResultProvider : IUnifyResultProvider
{
private static IStringLocalizer Localizer = App.CreateLocalizerByType(typeof(UnifyResultProvider))!;
/// <summary>
/// 异常返回
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public IActionResult OnException(ActionExecutedContext context)
{
return new JsonResult(RESTfulResult(context.Result is IStatusCodeActionResult statusCodeResult ? statusCodeResult.StatusCode ?? 500 : 500, false, null, context.Exception?.GetTrue()?.Message));
}
/// <summary>
/// 状态码响应拦截
/// </summary>
/// <param name="context"></param>
/// <param name="statusCode"></param>
/// <returns></returns>
public async Task OnResponseStatusCodes(HttpContext context, int statusCode)
{
switch (statusCode)
{
// 处理 401 状态码
case StatusCodes.Status401Unauthorized:
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, false, Localizer["TokenOver"]));
break;
// 处理 403 状态码
case StatusCodes.Status403Forbidden:
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, false, default, "NoPermission"));
break;
default: break;
}
}
/// <summary>
/// 成功返回
/// </summary>
/// <param name="context"></param>
/// <param name="data"></param>
/// <returns></returns>
public IActionResult OnSucceeded(ActionExecutedContext context, object? data)
{
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
}
/// <summary>
/// 验证失败返回
/// </summary>
/// <param name="context"></param>
/// <param name="errors"></param>
/// <returns></returns>
public IActionResult OnValidateFailed(ActionExecutingContext context, string? errors)
{
return new JsonResult(RESTfulResult(StatusCodes.Status400BadRequest, false, null, errors));
}
/// <summary>
/// 返回 RESTful 风格结果集
/// </summary>
/// <param name="statusCode">状态码</param>
/// <param name="succeeded">是否成功</param>
/// <param name="data">数据</param>
/// <param name="errors">错误信息</param>
/// <returns></returns>
private static UnifyResult<object> RESTfulResult(int statusCode, bool succeeded = default, object? data = default, object? errors = default)
{
return new UnifyResult<object>
{
Code = statusCode,
Msg = statusCode == StatusCodes.Status200OK ? "请求成功" : errors,
Data = data,
Time = DateTime.Now,
};
}
}

View File

@@ -0,0 +1,82 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using ThingsGateway.Core;
using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// Gitee WebHook
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class GiteeController : ControllerBase
{
/// <summary>
/// Gitee Webhook
/// </summary>
/// <returns></returns>
[HttpPost]
public IActionResult Webhook([FromQuery] string? id, [FromServices] IConfiguration config, [FromServices] IDispatchService<GiteePostBody> dispatch, [FromBody] GiteePostBody payload)
{
bool ret = false;
if (Check())
{
// 全局推送
if (payload.HeadCommit != null || payload.Commits?.Count > 0)
{
dispatch.Dispatch(new DispatchEntry<GiteePostBody>()
{
Name = "Gitee",
Entry = payload
});
}
ret = true;
}
return ret ? Ok() : Unauthorized();
bool Check()
{
var configId = config.GetValue<string>("WebHooks:Gitee:Id");
var configToken = config.GetValue<string>("WebHooks:Gitee:Token");
var token = "";
if (Request.Headers.TryGetValue("X-Gitee-Token", out var val))
{
token = val.FirstOrDefault() ?? string.Empty;
}
return id == configId && token == configToken
&& payload.Id == configId && payload.Password == configToken;
}
}
/// <summary>
/// Webhook 测试接口
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Webhook()
{
return Ok(new { Message = "Ok" });
}
/// <summary>
/// 跨域握手协议
/// </summary>
/// <returns></returns>
[HttpOptions]
public string Options() => string.Empty;
}

View File

@@ -0,0 +1,47 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
namespace ThingsGateway.Admin.Application;
[Route("openapi/auth")]
[LoggingMonitor]
public class OpenApiController : ControllerBase
{
private readonly IAuthService _authService;
public OpenApiController(IAuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<LoginOutput> LoginAsync([FromBody] LoginInput input)
{
return await _authService.LoginAsync(input, false);
}
[HttpGet("logout")]
[Authorize]
[IgnoreRolePermission]
public async Task<IActionResult> LogoutAsync([FromQuery] string returnUrl)
{
await _authService.LoginOutAsync();
return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
{
["ReturnUrl"] = returnUrl
}));
}
}

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ThingsGateway.Admin.Application;
[Route("testController")]
[RolePermission]
[Authorize(AuthenticationSchemes = "Bearer")]
public class TestController : ControllerBase
{
[HttpGet("test")]
public async Task Test()
{
await Task.CompletedTask;
}
}

View File

@@ -0,0 +1,117 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Core;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 主键id基类
/// </summary>
public abstract class PrimaryIdEntity : IPrimaryIdEntity
{
/// <summary>
/// 主键Id
/// </summary>
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual long Id { get; set; }
}
/// <summary>
/// 主键实体基类
/// </summary>
public abstract class PrimaryKeyEntity : PrimaryIdEntity
{
/// <summary>
/// 拓展信息
/// </summary>
[SugarColumn(ColumnDescription = "扩展信息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual string? ExtJson { get; set; }
}
/// <summary>
/// 框架实体基类
/// </summary>
public abstract class BaseEntity : PrimaryKeyEntity
{
/// <summary>
/// 创建时间
/// </summary>
[SugarColumn(ColumnDescription = "创建时间", IsOnlyIgnoreUpdate = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual DateTime? CreateTime { get; set; }
/// <summary>
/// 创建人
/// </summary>
[SugarColumn(ColumnDescription = "创建人", IsOnlyIgnoreUpdate = true, IsNullable = true)]
[IgnoreExcel]
[NotNull]
[AutoGenerateColumn(Ignore = true)]
public virtual string? CreateUser { get; set; }
/// <summary>
/// 创建者Id
/// </summary>
[SugarColumn(ColumnDescription = "创建者Id", IsOnlyIgnoreUpdate = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual long CreateUserId { get; set; }
/// <summary>
/// 软删除
/// </summary>
[SugarColumn(ColumnDescription = "软删除", IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual bool IsDelete { get; set; } = false;
/// <summary>
/// 更新时间
/// </summary>
[SugarColumn(ColumnDescription = "更新时间", IsOnlyIgnoreInsert = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual DateTime? UpdateTime { get; set; }
/// <summary>
/// 更新人
/// </summary>
[SugarColumn(ColumnDescription = "更新人", IsOnlyIgnoreInsert = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual string? UpdateUser { get; set; }
/// <summary>
/// 修改者Id
/// </summary>
[SugarColumn(ColumnDescription = "修改者Id", IsOnlyIgnoreInsert = true, IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public virtual long? UpdateUserId { get; set; }
/// <summary>
/// 排序码
///</summary>
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = true, Sortable = true, DefaultSortOrder = SortOrder.Asc)]
public int? SortCode { get; set; }
}

View File

@@ -0,0 +1,59 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
[SugarTable("sys_dict", TableDescription = "字典表")]
[Tenant(SqlSugarConst.DB_Admin)]
public class SysDict : BaseEntity
{
/// <summary>
/// 类型
///</summary>
[SugarColumn(ColumnDescription = "类型", Length = 200)]
[AutoGenerateColumn(Ignore = true, Filterable = true, Sortable = true)]
public virtual DictTypeEnum DictType { get; set; }
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200)]
[Required]
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public string Category { get; set; }
/// <summary>
/// 名称
///</summary>
[SugarColumn(ColumnDescription = "名称", Length = 200)]
[Required]
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public virtual string Name { get; set; }
/// <summary>
/// 代码
///</summary>
[SugarColumn(ColumnDescription = "代码", ColumnDataType = StaticConfig.CodeFirst_BigString)]
[Required]
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public virtual string Code { get; set; }
/// <summary>
/// 描述
///</summary>
[SugarColumn(ColumnDescription = "描述", Length = 200, IsNullable = true)]
public string Remark { get; set; }
}

View File

@@ -0,0 +1,136 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 操作日志表
///</summary>
[SugarTable("sys_operatelog", TableDescription = "操作日志表")]
[Tenant(SqlSugarConst.DB_Log)]
public class SysOperateLog
{
/// <summary>
/// 日志分类
///</summary>
[SugarColumn(ColumnDescription = "日志分类", Length = 200)]
[AutoGenerateColumn(Order = 1, Filterable = true, Sortable = true)]
public LogCateGoryEnum Category { get; set; }
/// <summary>
/// 日志名称
///</summary>
[SugarColumn(ColumnDescription = "日志名称", Length = 200)]
[AutoGenerateColumn(Order = 2, Filterable = true, Sortable = true)]
public string Name { get; set; }
/// <summary>
/// 类名称
///</summary>
[SugarColumn(ColumnDescription = "类名称", Length = 200)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string ClassName { get; set; }
/// <summary>
/// 方法名称
///</summary>
[SugarColumn(ColumnDescription = "方法名称", Length = 200)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string MethodName { get; set; }
/// <summary>
/// 请求参数
///</summary>
[SugarColumn(ColumnDescription = "请求参数", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)]
public string? ParamJson { get; set; }
/// <summary>
/// 请求方式
///</summary>
[SugarColumn(ColumnDescription = "请求方式", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string? ReqMethod { get; set; }
/// <summary>
/// 请求地址
///</summary>
[SugarColumn(ColumnDescription = "请求地址", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string? ReqUrl { get; set; }
/// <summary>
/// 返回结果
///</summary>
[SugarColumn(ColumnDescription = "返回结果", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)]
public string? ResultJson { get; set; }
/// <summary>
/// 具体消息
///</summary>
[SugarColumn(ColumnDescription = "具体消息", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(ShowTips = true, Filterable = true, Sortable = true)]
public string? ExeMessage { get; set; }
/// <summary>
/// 执行状态
///</summary>
[SugarColumn(ColumnDescription = "执行状态", Length = 200)]
[AutoGenerateColumn(Filterable = true, Sortable = true)]
public bool ExeStatus { get; set; }
/// <summary>
/// 操作账号
///</summary>
[SugarColumn(ColumnDescription = "操作账号", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Filterable = true, Sortable = true)]
public string? OpAccount { get; set; }
/// <summary>
/// 操作浏览器
///</summary>
[SugarColumn(ColumnDescription = "操作浏览器", Length = 200)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string OpBrowser { get; set; }
/// <summary>
/// 操作ip
///</summary>
[SugarColumn(ColumnDescription = "操作ip", Length = 200)]
[AutoGenerateColumn(Filterable = true, Sortable = true)]
public string? OpIp { get; set; }
/// <summary>
/// 操作系统
///</summary>
[SugarColumn(ColumnDescription = "操作系统", Length = 200)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string OpOs { get; set; }
/// <summary>
/// 操作时间
///</summary>
[SugarColumn(ColumnDescription = "操作时间")]
[AutoGenerateColumn(Visible = true, DefaultSort = true, Sortable = true, DefaultSortOrder = SortOrder.Desc)]
public DateTime OpTime { get; set; }
/// <summary>
/// 验证Id
///</summary>
[SugarColumn(ColumnDescription = "验证Id")]
[IgnoreExcel]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public long VerificatId { get; set; }
}

View File

@@ -0,0 +1,39 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using SqlSugar;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 系统关系表
///</summary>
[SugarTable("sys_relation", TableDescription = "系统关系表")]
[Tenant(SqlSugarConst.DB_Admin)]
public class SysRelation : PrimaryKeyEntity
{
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200)]
public RelationCategoryEnum Category { get; set; }
/// <summary>
/// 对象ID
///</summary>
[SugarColumn(ColumnDescription = "对象ID")]
public long ObjectId { get; set; }
/// <summary>
/// 目标ID
///</summary>
[SugarColumn(ColumnDescription = "目标ID", IsNullable = true)]
public string? TargetId { get; set; }
}

View File

@@ -0,0 +1,101 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Routing;
using Newtonsoft.Json;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 系统资源表
///</summary>
[SugarTable("sys_resource", TableDescription = "系统资源表")]
[Tenant(SqlSugarConst.DB_Admin)]
public class SysResource : BaseEntity
{
/// <summary>
/// 父id
///</summary>
[SugarColumn(ColumnDescription = "父id")]
[AutoGenerateColumn(Ignore = true)]
public virtual long ParentId { get; set; } = 0;
/// <summary>
/// 模块
///</summary>
[SugarColumn(ColumnDescription = "模块")]
[AutoGenerateColumn(Visible = false, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false, Searchable = true)]
public virtual long Module { get; set; }
/// <summary>
/// 标题
///</summary>
[SugarColumn(ColumnDescription = "标题", Length = 200)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, Searchable = true)]
public virtual string Title { get; set; }
/// <summary>
/// 图标
///</summary>
[SugarColumn(ColumnDescription = "图标", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = false, Filterable = false)]
public virtual string? Icon { get; set; }
/// <summary>
/// 编码
///</summary>
[SugarColumn(ColumnDescription = "编码", Length = 200)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public virtual string Code { get; set; }
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类")]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public ResourceCategoryEnum Category { get; set; } = ResourceCategoryEnum.Menu;
/// <summary>
/// 目标类型
///</summary>
[SugarColumn(ColumnDescription = "目标类型", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual TargetEnum? Target { get; set; }
/// <summary>
/// 菜单匹配类型
/// </summary>
[SugarColumn(ColumnDescription = "菜单匹配类型", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual NavLinkMatch? NavLinkMatch { get; set; }
/// <summary>
/// 路径
///</summary>
[SugarColumn(ColumnDescription = "路径", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true, Searchable = true)]
public virtual string Href { get; set; }
/// <summary>
/// 子节点
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public List<SysResource>? Children { get; set; }
}

View File

@@ -0,0 +1,52 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 系统角色表
///</summary>
[SugarTable("sys_role", TableDescription = "系统角色表")]
[Tenant(SqlSugarConst.DB_Admin)]
public class SysRole : BaseEntity
{
/// <summary>
/// 编码
///</summary>
[SugarColumn(ColumnDescription = "编码", Length = 200)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public string Code { get; set; }
/// <summary>
/// 名称
///</summary>
[SugarColumn(ColumnDescription = "名称", Length = 200)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual string Name { get; set; }
/// <summary>
/// 分类
///</summary>
[SugarColumn(ColumnDescription = "分类", Length = 200, IsNullable = false)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual RoleCategoryEnum Category { get; set; }
public override int GetHashCode()
{
return Id.GetHashCode();
}
}

View File

@@ -0,0 +1,192 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Mapster;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 系统用户表
///</summary>
[SugarTable("sys_user", TableDescription = "系统用户表")]
[Tenant(SqlSugarConst.DB_Admin)]
public class SysUser : BaseEntity
{
/// <summary>
/// 头像
///</summary>
[SugarColumn(ColumnDescription = "头像", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = false, Filterable = false)]
[AdaptIgnore]
public virtual string? Avatar { get; set; }
/// <summary>
/// 账号
///</summary>
[SugarColumn(ColumnDescription = "账号", Length = 200)]
[Required]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public virtual string Account { get; set; }
/// <summary>
/// 密码
///</summary>
[SugarColumn(ColumnDescription = "密码", ColumnDataType = StaticConfig.CodeFirst_BigString)]
[AutoGenerateColumn(Ignore = true)]
public string Password { get; set; }
/// <summary>
/// 是否启用
///</summary>
[SugarColumn(ColumnDescription = "是否启用")]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public bool Status { get; set; }
/// <summary>
/// 手机
/// 这里使用了SM4自动加密解密
///</summary>
[SugarColumn(ColumnDescription = "手机", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public string? Phone { get; set; }
/// <summary>
/// 邮箱
///</summary>
[SugarColumn(ColumnDescription = "邮箱", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
public string? Email { get; set; }
/// <summary>
/// 上次登录ip
///</summary>
[SugarColumn(ColumnDescription = "上次登录ip", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LastLoginIp { get; set; }
/// <summary>
/// 上次登录设备
///</summary>
[SugarColumn(ColumnDescription = "上次登录设备", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LastLoginDevice { get; set; }
/// <summary>
/// 上次登录时间
///</summary>
[SugarColumn(ColumnDescription = "上次登录时间", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public DateTime? LastLoginTime { get; set; }
/// <summary>
/// 上次登录地点
///</summary>
[SugarColumn(ColumnDescription = "上次登录地点", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string LastLoginAddress { get; set; }
/// <summary>
/// 最新登录ip
///</summary>
[SugarColumn(ColumnDescription = "最新登录ip", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LatestLoginIp { get; set; }
/// <summary>
/// 最新登录时间
///</summary>
[SugarColumn(ColumnDescription = "最新登录时间", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public DateTime? LatestLoginTime { get; set; }
/// <summary>
/// 最新登录设备
///</summary>
[SugarColumn(ColumnDescription = "最新登录设备", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string? LatestLoginDevice { get; set; }
/// <summary>
/// 最新登录地点
///</summary>
[SugarColumn(ColumnDescription = "最新登录地点", Length = 200, IsNullable = true)]
[AutoGenerateColumn(Visible = false, Sortable = true, Filterable = true, IsVisibleWhenAdd = false, IsVisibleWhenEdit = false)]
public string LatestLoginAddress { get; set; }
/// <summary>
/// 默认模块
///</summary>
[SugarColumn(ColumnDescription = "默认模块")]
[AutoGenerateColumn(Ignore = true)]
public long DefaultModule { get; set; }
#region other
/// <summary>
/// 按钮码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public Dictionary<string, List<string>> ButtonCodeList { get; set; } = new();
/// <summary>
/// 权限码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<string> PermissionCodeList { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// 角色码集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<string> RoleCodeList { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// 角色ID集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<long> RoleIdList { get; set; } = Enumerable.Empty<long>();
/// <summary>
/// 数据范围集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<DataScope> DataScopeList { get; set; } = Enumerable.Empty<DataScope>();
/// <summary>
/// 模块集合
/// </summary>
[SugarColumn(IsIgnore = true)]
[AutoGenerateColumn(Ignore = true)]
public IEnumerable<SysResource> ModuleList { get; set; } = Enumerable.Empty<SysResource>();
#endregion other
}
/// <summary>
/// 数据范围类
/// </summary>
public class DataScope
{
/// <summary>
/// API接口
/// </summary>
public string ApiUrl { get; set; }
}

View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 登录设备类型枚举
/// </summary>
public enum AuthDeviceTypeEnum
{
/// <summary>
/// PC端
/// </summary>
PC,
/// <summary>
/// 移动端
/// </summary>
APP,
}

View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 字典表类型
/// </summary>
public enum DictTypeEnum
{
/// <summary>
/// 系统使用
/// </summary>
System,
/// <summary>
/// 用户自定义
/// </summary>
Define,
}

View File

@@ -0,0 +1,19 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public enum LogCateGoryEnum
{
Login,
Logout,
Operate,
Exception
}

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public enum RelationCategoryEnum
{
UserHasRole,
UserHasResource,
UserHasPermission,
UserHasOpenApiPermission,
UserHasModule,
UserDefaultRazor,
UserWorkbenchData,
RoleHasResource,
RoleHasPermission,
RoleHasOpenApiPermission,
RoleHasModule,
}

View File

@@ -0,0 +1,18 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public enum ResourceCategoryEnum
{
Module,
Menu,
Button,
}

View File

@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public enum RoleCategoryEnum
{
Global,
Api,
}

View File

@@ -0,0 +1,19 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public enum TargetEnum
{
_self,
_blank,
_parent,
_top
}

View File

@@ -0,0 +1,29 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <inheritdoc/>
public class UserFriendlyException : Exception
{
public UserFriendlyException()
{
}
public UserFriendlyException(string? message) : base(message)
{
}
public UserFriendlyException(string? message, Exception? innerException) : base(message, innerException)
{
}
public bool? IsValidationException { get; set; }
}

View File

@@ -0,0 +1,80 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
//
// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证。
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// ASP.NET Core 服务拓展类
/// </summary>
public static class AspNetCoreBuilderServiceCollectionExtensions
{
/// <summary>
/// 注册 Mvc 过滤器
/// </summary>
/// <typeparam name="TFilter"></typeparam>
/// <param name="mvcBuilder"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IMvcBuilder AddMvcFilter<TFilter>(this IMvcBuilder mvcBuilder, Action<MvcOptions> configure = default)
where TFilter : IFilterMetadata
{
mvcBuilder.Services.AddMvcFilter<TFilter>(configure);
return mvcBuilder;
}
/// <summary>
/// 注册 Mvc 过滤器
/// </summary>
/// <typeparam name="TFilter"></typeparam>
/// <param name="services"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IServiceCollection AddMvcFilter<TFilter>(this IServiceCollection services, Action<MvcOptions> configure = default)
where TFilter : IFilterMetadata
{
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<TFilter>();
// 其他额外配置
configure?.Invoke(options);
});
return services;
}
/// <summary>
/// 注册 Mvc 过滤器
/// </summary>
/// <param name="services"></param>
/// <param name="filter"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IServiceCollection AddMvcFilter(this IServiceCollection services, IFilterMetadata filter, Action<MvcOptions> configure = default)
{
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(filter);
// 其他额外配置
configure?.Invoke(options);
});
return services;
}
}

View File

@@ -0,0 +1,39 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Authorization;
/// <summary>
/// 授权处理上下文拓展类
/// </summary>
public static class AuthorizationHandlerContextExtensions
{
/// <summary>
/// 获取当前 HttpContext 上下文
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static DefaultHttpContext GetCurrentHttpContext(this AuthorizationHandlerContext context)
{
DefaultHttpContext? httpContext;
// 获取 httpContext 对象
if (context.Resource is AuthorizationFilterContext filterContext) httpContext = (DefaultHttpContext)filterContext.HttpContext;
else if (context.Resource is DefaultHttpContext defaultHttpContext) httpContext = defaultHttpContext;
else httpContext = null;
return httpContext;
}
}

View File

@@ -0,0 +1,187 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Text;
namespace Microsoft.AspNetCore.Http;
/// <summary>
/// Http 拓展类
/// </summary>
public static class HttpContextExtensions
{
/// <summary>
/// 获取 Action 特性
/// </summary>
/// <typeparam name="TAttribute"></typeparam>
/// <param name="httpContext"></param>
/// <returns></returns>
public static TAttribute GetMetadata<TAttribute>(this HttpContext httpContext)
where TAttribute : class
{
return httpContext.GetEndpoint()?.Metadata?.GetMetadata<TAttribute>();
}
/// <summary>
/// 获取 控制器/Action 描述器
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public static ControllerActionDescriptor GetControllerActionDescriptor(this HttpContext httpContext)
{
return httpContext.GetEndpoint()?.Metadata?.FirstOrDefault(u => u is ControllerActionDescriptor) as ControllerActionDescriptor;
}
/// <summary>
/// 设置规范化文档自动登录
/// </summary>
/// <param name="httpContext"></param>
/// <param name="accessToken"></param>
public static void SigninToSwagger(this HttpContext httpContext, string accessToken)
{
// 设置 Swagger 刷新自动授权
httpContext.Response.Headers["access-token"] = accessToken;
}
/// <summary>
/// 设置规范化文档退出登录
/// </summary>
/// <param name="httpContext"></param>
public static void SignoutToSwagger(this HttpContext httpContext)
{
httpContext.Response.Headers["access-token"] = "invalid_token";
}
/// <summary>
/// 设置响应头 Tokens
/// </summary>
/// <param name="httpContext"></param>
/// <param name="accessToken"></param>
/// <param name="refreshToken"></param>
public static void SetTokensOfResponseHeaders(this HttpContext httpContext, string accessToken, string? refreshToken = null)
{
httpContext.Response.Headers["access-token"] = accessToken;
if (!string.IsNullOrWhiteSpace(refreshToken))
{
httpContext.Response.Headers["x-access-token"] = refreshToken;
}
}
/// <summary>
/// 获取本机 IPv4地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetLocalIpAddressToIPv4(this HttpContext context)
{
return context.Connection.LocalIpAddress?.MapToIPv4()?.ToString();
}
/// <summary>
/// 获取本机 IPv6地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetLocalIpAddressToIPv6(this HttpContext context)
{
return context.Connection.LocalIpAddress?.MapToIPv6()?.ToString();
}
/// <summary>
/// 获取远程 IPv4地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetRemoteIpAddressToIPv4(this HttpContext context)
{
return context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString();
}
/// <summary>
/// 获取远程 IPv6地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetRemoteIpAddressToIPv6(this HttpContext context)
{
return context.Connection.RemoteIpAddress?.MapToIPv6()?.ToString();
}
/// <summary>
/// 获取完整请求地址
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static string GetRequestUrlAddress(this HttpRequest request)
{
return new StringBuilder()
.Append(request.Scheme)
.Append("://")
.Append(request.Host)
.Append(request.PathBase)
.Append(request.Path)
.Append(request.QueryString)
.ToString();
}
/// <summary>
/// 获取来源地址
/// </summary>
/// <param name="request"></param>
/// <param name="refererHeaderKey"></param>
/// <returns></returns>
public static string GetRefererUrlAddress(this HttpRequest request, string refererHeaderKey = "Referer")
{
return request.Headers[refererHeaderKey].ToString();
}
/// <summary>
/// 读取 Body 内容
/// </summary>
/// <param name="httpContext"></param>
/// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
/// <returns></returns>
public static async Task<string> ReadBodyContentAsync(this HttpContext httpContext)
{
if (httpContext == null) return default;
return await httpContext.Request.ReadBodyContentAsync();
}
/// <summary>
/// 读取 Body 内容
/// </summary>
/// <param name="request"></param>
/// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
/// <returns></returns>
public static async Task<string> ReadBodyContentAsync(this HttpRequest request)
{
request.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true);
var body = await reader.ReadToEndAsync();
request.Body.Seek(0, SeekOrigin.Begin);
return body;
}
/// <summary>
/// 判断是否是 WebSocket 请求
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool IsWebSocketRequest(this HttpContext context)
{
return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
}
}

View File

@@ -0,0 +1,216 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.Localization;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
namespace ThingsGateway.Admin.Application;
public static class LocalizerExtensions
{
public static PropertyInfo? GetPropertyByName(this Type type, string propertyName) => type.GetRuntimeProperties().FirstOrDefault(p => p.Name == propertyName);
public static MethodInfo? GetMethodByName(this Type type, string methodName) => type.GetRuntimeMethods().FirstOrDefault(p => p.Name == methodName);
public static FieldInfo? GetFieldByName(this Type type, string fieldName) => type.GetRuntimeFields().FirstOrDefault(p => p.Name == fieldName);
/// <summary>
/// 获取指定 Type 的资源文件
/// </summary>
/// <param name="localizer"></param>
/// <param name="key"></param>
/// <param name="text"></param>
/// <returns></returns>
public static bool TryGetLocalizerString(this IStringLocalizer localizer, string key, [MaybeNullWhen(false)] out string? text)
{
var ret = false;
text = null;
var l = localizer[key];
if (l != null)
{
ret = !l.ResourceNotFound;
if (ret)
{
text = l.Value;
}
}
return ret;
}
/// <summary>
/// 获得类型自身的描述信息
/// </summary>
/// <param name="modelType"></param>
/// <returns></returns>
public static string GetTypeDisplayName(this Type modelType)
{
string fieldName = modelType.Name;
var cacheKey = $"{nameof(GetTypeDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}";
var displayName = App.CacheService.GetOrCreate(cacheKey, entry =>
{
string? dn = null;
// 显示名称为空时通过资源文件查找 FieldName 项
var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType);
var stringLocalizer = localizer?[fieldName];
if (stringLocalizer is { ResourceNotFound: false })
{
dn = stringLocalizer.Value;
}
else if (modelType.IsEnum)
{
var info = modelType.GetFieldByName(fieldName);
if (info != null)
{
dn = FindDisplayAttribute(info);
}
}
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
{
dn = FindDisplayAttribute(propertyInfo);
}
return dn;
}, 300);
return displayName ?? fieldName;
string? FindDisplayAttribute(MemberInfo memberInfo)
{
// 回退查找 Display 标签
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
return dn;
}
}
/// <summary>
/// 获得类型属性的描述信息
/// </summary>
/// <param name="modelType"></param>
/// <param name="fieldName"></param>
/// <returns></returns>
public static string GetPropertyDisplayName(this Type modelType, string fieldName)
{
var cacheKey = $"{nameof(GetPropertyDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}-{fieldName}";
var displayName = App.CacheService.GetOrCreate(cacheKey, entry =>
{
string? dn = null;
// 显示名称为空时通过资源文件查找 FieldName 项
var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType);
var stringLocalizer = localizer?[fieldName];
if (stringLocalizer is { ResourceNotFound: false })
{
dn = stringLocalizer.Value;
}
else if (modelType.IsEnum)
{
var info = modelType.GetFieldByName(fieldName);
if (info != null)
{
dn = FindDisplayAttribute(info);
}
}
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
{
dn = FindDisplayAttribute(propertyInfo);
}
return dn;
}, 300);
return displayName ?? fieldName;
string? FindDisplayAttribute(MemberInfo memberInfo)
{
// 回退查找 Display 标签
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
return dn;
}
}
/// <summary>
/// 获得方法的描述信息
/// </summary>
/// <param name="modelType"></param>
/// <param name="methodName"></param>
/// <returns></returns>
public static string GetMethodDisplayName(this Type modelType, string methodName)
{
var cacheKey = $"{nameof(GetMethodDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.FullName}-{modelType.TypeHandle.Value}-{methodName}";
var displayName = App.CacheService.GetOrCreate(cacheKey, entry =>
{
string? dn = null;
// 显示名称为空时通过资源文件查找 methodName 项
var localizer = modelType.Assembly.IsDynamic ? null : App.CreateLocalizerByType(modelType);
var stringLocalizer = localizer?[methodName];
if (stringLocalizer is { ResourceNotFound: false })
{
dn = stringLocalizer.Value;
}
else
{
var info = modelType.GetMethodByName(methodName);
if (info != null)
{
dn = FindDisplayAttribute(info);
}
}
return dn;
}, 300);
return displayName ?? methodName;
string? FindDisplayAttribute(MemberInfo memberInfo)
{
// 回退查找 Display 标签
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
return dn;
}
}
private static bool TryGetProperty(Type modelType, string fieldName, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
{
var cacheKey = $"{nameof(TryGetProperty)}-{modelType.FullName}-{modelType.TypeHandle.Value}-{fieldName}";
propertyInfo = App.CacheService.GetOrCreate(cacheKey, entry =>
{
IEnumerable<PropertyInfo>? props;
// 支持 MetadataType
var metadataType = modelType.GetCustomAttribute<MetadataTypeAttribute>(false);
if (metadataType != null)
{
props = modelType.GetRuntimeProperties().AsEnumerable().Concat(metadataType.MetadataClassType.GetRuntimeProperties());
}
else
{
props = modelType.GetRuntimeProperties().AsEnumerable();
}
var pi = props.FirstOrDefault(p => p.Name == fieldName);
return pi;
}, 300);
return propertyInfo != null;
}
}

View File

@@ -0,0 +1,594 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Http;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 对象拓展类
/// </summary>
public static class ObjectExtensions
{
/// <summary>
/// 将 DateTimeOffset 转换成本地 DateTime
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTime ConvertToDateTime(this DateTimeOffset dateTime)
{
if (dateTime.Offset.Equals(TimeSpan.Zero))
return dateTime.UtcDateTime;
if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime)))
return dateTime.ToLocalTime().DateTime;
else
return dateTime.DateTime;
}
/// <summary>
/// 将 DateTimeOffset? 转换成本地 DateTime?
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime)
{
return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null;
}
/// <summary>
/// 将 DateTime 转换成 DateTimeOffset
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime)
{
return DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
}
/// <summary>
/// 将 DateTime? 转换成 DateTimeOffset?
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime)
{
return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null;
}
/// <summary>
/// 将时间戳转换为 DateTime
/// </summary>
/// <param name="timestamp"></param>
/// <returns></returns>
internal static DateTime ConvertToDateTime(this long timestamp)
{
var timeStampDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var digitCount = (int)Math.Floor(Math.Log10(timestamp) + 1);
if (digitCount != 13 && digitCount != 10)
{
throw new ArgumentException("Data is not a valid timestamp format.");
}
return (digitCount == 13
? timeStampDateTime.AddMilliseconds(timestamp) // 13 位时间戳
: timeStampDateTime.AddSeconds(timestamp)).ToLocalTime(); // 10 位时间戳
}
/// <summary>
/// 将 IFormFile 转换成 byte[]
/// </summary>
/// <param name="formFile"></param>
/// <returns></returns>
public static byte[] ToByteArray(this IFormFile formFile)
{
var fileLength = formFile.Length;
using var stream = formFile.OpenReadStream();
var bytes = new byte[fileLength];
stream.Read(bytes, 0, (int)fileLength);
return bytes;
}
/// <summary>
/// 将流保存到本地磁盘
/// </summary>
/// <param name="stream"></param>
/// <param name="path"></param>
/// <returns></returns>
public static void CopyToSave(this Stream stream, string path)
{
// 空检查
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path));
using var fileStream = File.Create(path);
stream.CopyTo(fileStream);
}
/// <summary>
/// 将字节数组保存到本地磁盘
/// </summary>
/// <param name="bytes"></param>
/// <param name="path"></param>
/// <returns></returns>
public static void CopyToSave(this byte[] bytes, string path)
{
using var stream = new MemoryStream(bytes);
stream.CopyToSave(path);
}
/// <summary>
/// 将流保存到本地磁盘
/// </summary>
/// <param name="stream"></param>
/// <param name="path">需包含文件名完整路径</param>
/// <returns></returns>
public static async Task CopyToSaveAsync(this Stream stream, string path)
{
// 空检查
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException(nameof(path));
}
// 文件名判断
if (string.IsNullOrWhiteSpace(Path.GetFileName(path)))
{
throw new ArgumentException("The parameter of <path> parameter must include the complete file name.");
}
using var fileStream = File.Create(path);
await stream.CopyToAsync(fileStream);
}
/// <summary>
/// 将字节数组保存到本地磁盘
/// </summary>
/// <param name="bytes"></param>
/// <param name="path"></param>
/// <returns></returns>
public static async Task CopyToSaveAsync(this byte[] bytes, string path)
{
using var stream = new MemoryStream(bytes);
await stream.CopyToSaveAsync(path);
}
/// <summary>
/// 判断是否是富基元类型
/// </summary>
/// <param name="type">类型</param>
/// <returns></returns>
internal static bool IsRichPrimitive(this Type type)
{
// 处理元组类型
if (type.IsValueTuple()) return false;
// 处理数组类型,基元数组类型也可以是基元类型
if (type.IsArray) return type.GetElementType().IsRichPrimitive();
// 基元类型或值类型或字符串类型
if (type.IsPrimitive || type.IsValueType || type == typeof(string)) return true;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) return type.GenericTypeArguments[0].IsRichPrimitive();
return false;
}
/// <summary>
/// 合并两个字典
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dic">字典</param>
/// <param name="newDic">新字典</param>
/// <returns></returns>
internal static Dictionary<string, T> AddOrUpdate<T>(this Dictionary<string, T> dic, IDictionary<string, T> newDic)
{
foreach (var key in newDic.Keys)
{
if (dic.TryGetValue(key, out var value))
{
dic[key] = value;
}
else
{
dic.Add(key, newDic[key]);
}
}
return dic;
}
/// <summary>
/// 合并两个字典
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dic">字典</param>
/// <param name="newDic">新字典</param>
internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic)
{
foreach (var (key, value) in newDic)
{
dic.AddOrUpdate(key, value, (key, old) => value);
}
}
/// <summary>
/// 判断是否是元组类型
/// </summary>
/// <param name="type">类型</param>
/// <returns></returns>
internal static bool IsValueTuple(this Type type)
{
return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
}
/// <summary>
/// 判断方法是否是异步
/// </summary>
/// <param name="method">方法</param>
/// <returns></returns>
internal static bool IsAsync(this MethodInfo method)
{
return method.GetCustomAttribute<AsyncMethodBuilderAttribute>() != null
|| method.ReturnType.ToString().StartsWith(typeof(Task).FullName);
}
/// <summary>
/// 判断是否是匿名类型
/// </summary>
/// <param name="obj">对象</param>
/// <returns></returns>
internal static bool IsAnonymous(this object obj)
{
var type = obj is Type t ? t : obj.GetType();
return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
&& type.IsGenericType && type.Name.Contains("AnonymousType")
&& (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
&& type.Attributes.HasFlag(TypeAttributes.NotPublic);
}
/// <summary>
/// 获取所有祖先类型
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
internal static IEnumerable<Type> GetAncestorTypes(this Type type)
{
var ancestorTypes = new List<Type>();
while (type != null && type != typeof(object))
{
if (IsNoObjectBaseType(type))
{
var baseType = type.BaseType;
ancestorTypes.Add(baseType);
type = baseType;
}
else break;
}
return ancestorTypes;
static bool IsNoObjectBaseType(Type type) => type.BaseType != typeof(object);
}
/// <summary>
/// 获取方法真实返回类型
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
internal static Type GetRealReturnType(this MethodInfo method)
{
// 判断是否是异步方法
var isAsyncMethod = method.IsAsync();
// 获取类型返回值并处理 Task 和 Task<T> 类型返回值
var returnType = method.ReturnType;
return isAsyncMethod ? (returnType.GenericTypeArguments.FirstOrDefault() ?? typeof(void)) : returnType;
}
/// <summary>
/// 将一个对象转换为指定类型
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
internal static T ChangeType<T>(this object obj)
{
return (T)ChangeType(obj, typeof(T));
}
/// <summary>
/// 将一个对象转换为指定类型
/// </summary>
/// <param name="obj">待转换的对象</param>
/// <param name="type">目标类型</param>
/// <returns>转换后的对象</returns>
internal static object ChangeType(this object obj, Type type)
{
if (type == null) return obj;
if (type == typeof(string)) return obj?.ToString();
if (type == typeof(Guid) && obj != null) return Guid.Parse(obj.ToString());
if (type == typeof(bool) && obj != null && obj is not bool)
{
var objStr = obj.ToString().ToLower();
if (objStr == "1" || objStr == "true" || objStr == "yes" || objStr == "on") return true;
return false;
}
if (obj == null) return type.IsValueType ? Activator.CreateInstance(type) : null;
var underlyingType = Nullable.GetUnderlyingType(type);
if (type.IsAssignableFrom(obj.GetType())) return obj;
else if ((underlyingType ?? type).IsEnum)
{
if (underlyingType != null && string.IsNullOrWhiteSpace(obj.ToString())) return null;
else return Enum.Parse(underlyingType ?? type, obj.ToString());
}
// 处理DateTime -> DateTimeOffset 类型
else if (obj.GetType().Equals(typeof(DateTime)) && (underlyingType ?? type).Equals(typeof(DateTimeOffset)))
{
return ((DateTime)obj).ConvertToDateTimeOffset();
}
// 处理 DateTimeOffset -> DateTime 类型
else if (obj.GetType().Equals(typeof(DateTimeOffset)) && (underlyingType ?? type).Equals(typeof(DateTime)))
{
return ((DateTimeOffset)obj).ConvertToDateTime();
}
else if (typeof(IConvertible).IsAssignableFrom(underlyingType ?? type))
{
try
{
return Convert.ChangeType(obj, underlyingType ?? type, null);
}
catch
{
return underlyingType == null ? Activator.CreateInstance(type) : null;
}
}
else
{
var converter = TypeDescriptor.GetConverter(type);
if (converter.CanConvertFrom(obj.GetType())) return converter.ConvertFrom(obj);
var constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor != null)
{
var o = constructor.Invoke(null);
var propertys = type.GetProperties();
var oldType = obj.GetType();
foreach (var property in propertys)
{
var p = oldType.GetProperty(property.Name);
if (property.CanWrite && p != null && p.CanRead)
{
property.SetValue(o, ChangeType(p.GetValue(obj, null), property.PropertyType), null);
}
}
return o;
}
}
return obj;
}
/// <summary>
/// 查找方法指定特性,如果没找到则继续查找声明类
/// </summary>
/// <typeparam name="TAttribute"></typeparam>
/// <param name="method"></param>
/// <param name="inherit"></param>
/// <returns></returns>
internal static TAttribute GetFoundAttribute<TAttribute>(this MethodInfo method, bool inherit)
where TAttribute : Attribute
{
// 获取方法所在类型
var declaringType = method.DeclaringType;
var attributeType = typeof(TAttribute);
// 判断方法是否定义指定特性,如果没有再查找声明类
var foundAttribute = method.IsDefined(attributeType, inherit)
? method.GetCustomAttribute<TAttribute>(inherit)
: (
declaringType.IsDefined(attributeType, inherit)
? declaringType.GetCustomAttribute<TAttribute>(inherit)
: default
);
return foundAttribute;
}
/// <summary>
/// 格式化字符串
/// </summary>
/// <param name="str"></param>
/// <param name="args"></param>
/// <returns></returns>
internal static string Format(this string str, params object[] args)
{
return args == null || args.Length == 0 ? str : string.Format(str, args);
}
/// <summary>
/// 切割骆驼命名式字符串
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
internal static string[] SplitCamelCase(this string str)
{
if (str == null) return Array.Empty<string>();
if (string.IsNullOrWhiteSpace(str)) return new string[] { str };
if (str.Length == 1) return new string[] { str };
return Regex.Split(str, @"(?=\p{Lu}\p{Ll})|(?<=\p{Ll})(?=\p{Lu})")
.Where(u => u.Length > 0)
.ToArray();
}
/// <summary>
/// JsonElement 转 Object
/// </summary>
/// <param name="jsonElement"></param>
/// <returns></returns>
internal static object ToObject(this JsonElement jsonElement)
{
switch (jsonElement.ValueKind)
{
case JsonValueKind.String:
return jsonElement.GetString();
case JsonValueKind.Undefined:
case JsonValueKind.Null:
return default;
case JsonValueKind.Number:
return jsonElement.GetDecimal();
case JsonValueKind.True:
case JsonValueKind.False:
return jsonElement.GetBoolean();
case JsonValueKind.Object:
var enumerateObject = jsonElement.EnumerateObject();
var dic = new Dictionary<string, object>();
foreach (var item in enumerateObject)
{
dic.Add(item.Name, item.Value.ToObject());
}
return dic;
case JsonValueKind.Array:
var enumerateArray = jsonElement.EnumerateArray();
var list = new List<object>();
foreach (var item in enumerateArray)
{
list.Add(item.ToObject());
}
return list;
default:
return default;
}
}
/// <summary>
/// 清除字符串前后缀
/// </summary>
/// <param name="str">字符串</param>
/// <param name="pos">0前后缀1后缀-1前缀</param>
/// <param name="affixes">前后缀集合</param>
/// <returns></returns>
internal static string ClearStringAffixes(this string str, int pos = 0, params string[] affixes)
{
// 空字符串直接返回
if (string.IsNullOrWhiteSpace(str)) return str;
// 空前后缀集合直接返回
if (affixes == null || affixes.Length == 0) return str;
var startCleared = false;
var endCleared = false;
string tempStr = null;
foreach (var affix in affixes)
{
if (string.IsNullOrWhiteSpace(affix)) continue;
if (pos != 1 && !startCleared && str.StartsWith(affix, StringComparison.OrdinalIgnoreCase))
{
tempStr = str[affix.Length..];
startCleared = true;
}
if (pos != -1 && !endCleared && str.EndsWith(affix, StringComparison.OrdinalIgnoreCase))
{
var _tempStr = !string.IsNullOrWhiteSpace(tempStr) ? tempStr : str;
tempStr = _tempStr[..^affix.Length];
endCleared = true;
if (string.IsNullOrWhiteSpace(tempStr))
{
tempStr = null;
endCleared = false;
}
}
if (startCleared && endCleared) break;
}
return !string.IsNullOrWhiteSpace(tempStr) ? tempStr : str;
}
/// <summary>
/// 首字母小写
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
internal static string ToLowerCamelCase(this string str)
{
if (string.IsNullOrWhiteSpace(str)) return str;
return string.Concat(str.First().ToString().ToLower(), str.AsSpan(1));
}
/// <summary>
/// 首字母大写
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
internal static string ToUpperCamelCase(this string str)
{
if (string.IsNullOrWhiteSpace(str)) return str;
return string.Concat(str.First().ToString().ToUpper(), str.AsSpan(1));
}
/// <summary>
/// 判断集合是否为空
/// </summary>
/// <typeparam name="T">元素类型</typeparam>
/// <param name="collection">集合对象</param>
/// <returns><see cref="bool"/> 实例true 表示空集合false 表示非空集合</returns>
internal static bool IsEmpty<T>(this IEnumerable<T> collection)
{
return collection == null || !collection.Any();
}
/// <summary>
/// 获取类型自定义特性
/// </summary>
/// <typeparam name="TAttribute">特性类型</typeparam>
/// <param name="type">类类型</param>
/// <param name="inherit">是否继承查找</param>
/// <returns>特性对象</returns>
internal static TAttribute GetTypeAttribute<TAttribute>(this Type type, bool inherit = false)
where TAttribute : Attribute
{
// 空检查
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
// 检查特性并获取特性对象
return type.IsDefined(typeof(TAttribute), inherit)
? type.GetCustomAttribute<TAttribute>(inherit)
: default;
}
}

View File

@@ -0,0 +1,109 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using NewLife;
using SqlSugar;
namespace ThingsGateway.Admin.Application;
public static class QueryPageOptionsExtensions
{
/// <summary>
/// 根据查询条件返回sqlsugar ISugarQueryable
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="option"></param>
/// <returns></returns>
public static ISugarQueryable<T> GetQuery<T>(this SqlSugarClient db, QueryPageOptions option)
{
ISugarQueryable<T>? query = db.Queryable<T>();
var where = option.ToFilter();
if (where.HasFilters())
{
query = query.Where(where.GetFilterLambda<T>());//name asc模式
}
foreach (var item in option.SortList)
{
query = query.OrderByIF(!string.IsNullOrEmpty(item), $"{item}");//name asc模式
}
foreach (var item in option.AdvancedSortList)
{
query = query.OrderByIF(!string.IsNullOrEmpty(item), $"{item}");//name asc模式
}
query = query.OrderByIF(option.SortOrder != SortOrder.Unset, $"{option.SortName} {option.SortOrder}");
return query;
}
/// <summary>
/// 根据查询条件返回IEnumerable
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="datas"></param>
/// <param name="option"></param>
/// <returns></returns>
public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option)
{
var where = option.ToFilter();
if (where.HasFilters())
{
datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortList.Any())
{
datas = datas.Sort(option.SortList);//name asc模式
}
if (option.AdvancedSortList.Any())
{
datas = datas.Sort(option.AdvancedSortList);//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
{
datas = datas.Sort(option.SortName, option.SortOrder);
}
if (option.IsPage)
{
datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
}
else if (option.IsVirtualScroll)
{
datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
}
return datas;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="datas"></param>
/// <param name="option"></param>
/// <returns></returns>
public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option)
{
var ret = new QueryData<T>()
{
IsSorted = option.SortOrder != SortOrder.Unset,
IsFiltered = option.Filters.Any(),
IsAdvanceSearch = option.AdvanceSearches.Any(),
IsSearch = option.Searches.Any() || option.CustomerSearches.Any()
};
var items = datas.GetData(option);
ret.TotalCount = datas.Count();
ret.Items = items;
return ret;
}
}

View File

@@ -0,0 +1,170 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using SqlSugar;
using System.Linq.Expressions;
using ThingsGateway.Core;
namespace ThingsGateway.Admin.Application;
public static class SqlSugarExtensions
{
public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new()
{
return await db.Updateable(updateObjs).ExecuteCommandAsync() > 0;
}
public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
{
return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
.ExecuteCommandAsync() > 0;
}
public static ISugarQueryable<TEntity> ExportIgnoreColumns<TEntity>(this ISugarQueryable<TEntity> queryable)
{
return queryable.IgnoreColumns(
new string[]
{
nameof(BaseEntity.Id),
nameof(BaseEntity.CreateTime),
nameof(BaseEntity.CreateUser),
nameof(BaseEntity.CreateUserId),
nameof(BaseEntity.ExtJson),
nameof(BaseEntity.IsDelete),
nameof(BaseEntity.UpdateTime),
nameof(BaseEntity.UpdateUser),
nameof(BaseEntity.UpdateUserId),
}
);
}
public static SqlSugarPagedList<TEntity> ToPagedList<TEntity>(this ISugarQueryable<TEntity> queryable, int current,
int size)
{
var total = 0;
var records = queryable.ToPageList(current, size, ref total);
var pages = (int)Math.Ceiling(total / (double)size);
return new SqlSugarPagedList<TEntity>
{
Current = current,
Size = size,
Records = records,
Total = total,
Pages = pages,
HasNextPages = current < pages,
HasPrevPages = current - 1 > 0
};
}
public static async Task<SqlSugarPagedList<TEntity>> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> queryable,
int current, int size)
{
RefAsync<int> totalCount = 0;
var records = await queryable.ToPageListAsync(current, size, totalCount);
var totalPages = (int)Math.Ceiling(totalCount / (double)size);
return new SqlSugarPagedList<TEntity>
{
Current = current,
Size = size,
Records = records,
Total = (int)totalCount,
Pages = totalPages,
HasNextPages = current < totalPages,
HasPrevPages = current - 1 > 0
};
}
/// <summary>
/// SqlSugar分页扩展,查询出结果后再转换实体类
/// </summary>
public static SqlSugarPagedList<TResult> ToPagedList<TEntity, TResult>(this ISugarQueryable<TEntity> queryable,
int current, int size)
{
var totalCount = 0;
var records = queryable.ToPageList(current, size, ref totalCount);
var totalPages = (int)Math.Ceiling(totalCount / (double)size);
return new SqlSugarPagedList<TResult>
{
Current = current,
Size = size,
Records = records.Cast<TResult>(),
Total = (int)totalCount,
Pages = totalPages,
HasNextPages = current < totalPages,
HasPrevPages = current - 1 > 0
};
}
/// <summary>
/// SqlSugar分页扩展,查询出结果后再转换实体类
/// </summary>
public static async Task<SqlSugarPagedList<TResult>> ToPagedListAsync<TEntity, TResult>(this ISugarQueryable<TEntity> queryable,
int current, int size)
{
RefAsync<int> totalCount = 0;
var records = await queryable.ToPageListAsync(current, size, totalCount);
var totalPages = (int)Math.Ceiling(totalCount / (double)size);
return new SqlSugarPagedList<TResult>
{
Current = current,
Size = size,
Records = records.Cast<TResult>(),
Total = (int)totalCount,
Pages = totalPages,
HasNextPages = current < totalPages,
HasPrevPages = current - 1 > 0
};
}
/// <summary>
/// SqlSugar分页扩展查询前扩展转换实体类
/// </summary>
public static SqlSugarPagedList<TResult> ToPagedList<TEntity, TResult>(this ISugarQueryable<TEntity> queryable, int pageIndex,
int pageSize, Expression<Func<TEntity, TResult>> expression)
{
var totalCount = 0;
var items = queryable.ToPageList(pageIndex, pageSize, ref totalCount, expression);
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
return new SqlSugarPagedList<TResult>
{
Current = pageIndex,
Size = pageSize,
Records = items,
Total = totalCount,
Pages = totalPages,
HasNextPages = pageIndex < totalPages,
HasPrevPages = pageIndex - 1 > 0
};
}
/// <summary>
/// SqlSugar分页扩展查询前扩展转换实体类
/// </summary>
public static async Task<SqlSugarPagedList<TResult>> ToPagedListAsync<TEntity, TResult>(
this ISugarQueryable<TEntity> queryable, int pageIndex, int pageSize, Expression<Func<TEntity, TResult>> expression)
{
RefAsync<int> totalCount = 0;
var items = await queryable.ToPageListAsync(pageIndex, pageSize, totalCount, expression);
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
return new SqlSugarPagedList<TResult>
{
Current = pageIndex,
Size = pageSize,
Records = items,
Total = (int)totalCount,
Pages = totalPages,
HasNextPages = pageIndex < totalPages,
HasPrevPages = pageIndex - 1 > 0
};
}
}

View File

@@ -0,0 +1,173 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Collections.Concurrent;
using System.Reflection;
using ThingsGateway.Core;
namespace ThingsGateway.Admin.Application;
public static class StartupExtensions
{
private static ConcurrentBag<AppStartup> AppStartups = new();
/// <summary>
/// 反射获取所有AppStartup的继承类执行名称为第一个参数是<see cref="IServiceCollection"/>的方法
/// </summary>
/// <param name="service"></param>
public static void ConfigureServices(this IHostApplicationBuilder service)
{
AddStartups(service);
}
/// <summary>
/// ConfigureServices获取的全部实例中执行名称为第一个参数是<see cref="IApplicationBuilder"/>的方法
/// </summary>
public static void UseServices(this IApplicationBuilder builder)
{
UseStartups(AppStartups, builder);
}
/// <summary>
/// 添加 Startup 自动扫描
/// </summary>
internal static void AddStartups(this IHostApplicationBuilder builder)
{
App.Configuration = builder.Configuration;
if (builder.Environment is IWebHostEnvironment webHostEnvironment)
App.WebRootPath = webHostEnvironment.WebRootPath;
App.ContentRootPath = builder.Environment.ContentRootPath;
App.IsDevelopment = builder.Environment.IsDevelopment();
// 扫描所有继承 AppStartup 的类
var startups = App.EffectiveTypes
.Where(u => typeof(AppStartup).IsAssignableFrom(u) && u.IsClass && !u.IsAbstract && !u.IsGenericType)
.OrderByDescending(u => GetStartupOrder(u));
// 注册自定义 startup
foreach (var type in startups)
{
var startup = Activator.CreateInstance(type) as AppStartup;
AppStartups.Add(startup!);
// 获取所有符合依赖注入格式的方法如返回值void且第一个参数是 IServiceCollection 类型
var serviceMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(u => u.ReturnType == typeof(void)
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IServiceCollection));
if (!serviceMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in serviceMethods)
{
method.Invoke(startup, new[] { builder.Services });
}
}
}
/// <summary>
/// 批量将自定义 AppStartup 添加到 Startup.cs 的 Configure 中
/// </summary>
/// <param name="startups"></param>
/// <param name="app"></param>
private static void UseStartups(IEnumerable<AppStartup> startups, IApplicationBuilder app)
{
App.RootServices = app.ApplicationServices;
App.CacheService = app.ApplicationServices.GetRequiredService<ICacheService>();
// 遍历所有
foreach (var startup in startups)
{
var type = startup.GetType();
// 获取所有符合依赖注入格式的方法,如返回值 void且第一个参数是 IApplicationBuilder 类型
var configureMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(u => u.ReturnType == typeof(void)
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IApplicationBuilder));
if (!configureMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in configureMethods)
{
method.Invoke(startup, ResolveMethodParameterInstances(app, method));
}
}
AppStartups.Clear();
}
/// <summary>
/// 获取 Startup 排序
/// </summary>
/// <param name="type">排序类型</param>
/// <returns>int</returns>
private static int GetStartupOrder(Type type)
{
return !type.IsDefined(typeof(AppStartupAttribute), true) ? 0 : type.GetCustomAttribute<AppStartupAttribute>(true)!.Order;
}
/// <summary>
/// 解析方法参数实例
/// </summary>
/// <param name="app"></param>
/// <param name="method"></param>
/// <returns></returns>
private static object[] ResolveMethodParameterInstances(IApplicationBuilder app, MethodInfo method)
{
// 获取方法所有参数
var parameters = method.GetParameters();
var parameterInstances = new object[parameters.Length];
parameterInstances[0] = app;
// 解析服务
for (var i = 1; i < parameters.Length; i++)
{
var parameter = parameters[i];
parameterInstances[i] = app.ApplicationServices.GetRequiredService(parameter.ParameterType);
}
return parameterInstances;
}
}
public abstract class AppStartup
{
}
/// <summary>
/// 注册服务启动配置
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class AppStartupAttribute : Attribute
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="order"></param>
public AppStartupAttribute(int order)
{
Order = order;
}
/// <summary>
/// 排序
/// </summary>
public int Order { get; set; }
}

View File

@@ -0,0 +1,94 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
/// <summary>
/// TextWriter扩展
/// </summary>
public static class TextWriterExtensions
{
private const string DefaultBackgroundColor = "\x1B[49m";
private const string DefaultForegroundColor = "\x1B[39m\x1B[22m";
/// <summary>
/// 写入流
/// </summary>
/// <param name="textWriter"></param>
/// <param name="message"></param>
/// <param name="background"></param>
/// <param name="foreground"></param>
public static void WriteWithColor(
this TextWriter textWriter,
string message,
ConsoleColor? background,
ConsoleColor? foreground)
{
var backgroundColor = background.HasValue ? GetBackgroundColorEscapeCode(background.Value) : null;
var foregroundColor = foreground.HasValue ? GetForegroundColorEscapeCode(foreground.Value) : null;
if (backgroundColor is not null)
{
textWriter.Write(backgroundColor);
}
if (foregroundColor is not null)
{
textWriter.Write(foregroundColor);
}
textWriter.WriteLine(message);
if (foregroundColor is not null)
{
textWriter.Write(DefaultForegroundColor);
}
if (backgroundColor is not null)
{
textWriter.Write(DefaultBackgroundColor);
}
}
private static string GetBackgroundColorEscapeCode(ConsoleColor color) =>
color switch
{
ConsoleColor.Black => "\x1B[40m",
ConsoleColor.DarkRed => "\x1B[41m",
ConsoleColor.DarkGreen => "\x1B[42m",
ConsoleColor.DarkYellow => "\x1B[43m",
ConsoleColor.DarkBlue => "\x1B[44m",
ConsoleColor.DarkMagenta => "\x1B[45m",
ConsoleColor.DarkCyan => "\x1B[46m",
ConsoleColor.Gray => "\x1B[47m",
_ => DefaultBackgroundColor
};
private static string GetForegroundColorEscapeCode(ConsoleColor color) =>
color switch
{
ConsoleColor.Black => "\x1B[30m",
ConsoleColor.DarkRed => "\x1B[31m",
ConsoleColor.DarkGreen => "\x1B[32m",
ConsoleColor.DarkYellow => "\x1B[33m",
ConsoleColor.DarkBlue => "\x1B[34m",
ConsoleColor.DarkMagenta => "\x1B[35m",
ConsoleColor.DarkCyan => "\x1B[36m",
ConsoleColor.Gray => "\x1B[37m",
ConsoleColor.Red => "\x1B[1m\x1B[31m",
ConsoleColor.Green => "\x1B[1m\x1B[32m",
ConsoleColor.Yellow => "\x1B[1m\x1B[33m",
ConsoleColor.Blue => "\x1B[1m\x1B[34m",
ConsoleColor.Magenta => "\x1B[1m\x1B[35m",
ConsoleColor.Cyan => "\x1B[1m\x1B[36m",
ConsoleColor.White => "\x1B[1m\x1B[37m",
_ => DefaultForegroundColor
};
}

View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo />
</Weavers>

View File

@@ -0,0 +1,129 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
#if !NET5_0
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateOnly 类型序列化
/// </summary>
public class NewtonsoftJsonDateOnlyJsonConverter : JsonConverter<DateOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override DateOnly ReadJson(JsonReader reader, Type objectType, DateOnly existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return DateOnly.Parse(value);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, DateOnly value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString(Format));
}
}
/// <summary>
/// DateOnly? 类型序列化
/// </summary>
public class NewtonsoftJsonNullableDateOnlyJsonConverter : JsonConverter<DateOnly?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonNullableDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override DateOnly? ReadJson(JsonReader reader, Type objectType, DateOnly? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return !string.IsNullOrWhiteSpace(value) ? DateOnly.Parse(value) : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, DateOnly? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else serializer.Serialize(writer, value.Value.ToString(Format));
}
}
#endif

View File

@@ -0,0 +1,126 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Newtonsoft.Json;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTime 类型序列化
/// </summary>
public class NewtonsoftJsonDateTimeJsonConverter : JsonConverter<DateTime>
{
/// <summary>
/// 默认构造函数
/// </summary>
public NewtonsoftJsonDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString(Format));
}
}
/// <summary>
/// DateTime 类型序列化
/// </summary>
public class NewtonsoftNullableJsonDateTimeJsonConverter : JsonConverter<DateTime?>
{
/// <summary>
/// 默认构造函数
/// </summary>
public NewtonsoftNullableJsonDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftNullableJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else serializer.Serialize(writer, value.Value.ToString(Format));
}
}

View File

@@ -0,0 +1,167 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Newtonsoft.Json;
using ThingsGateway.Core.Extension;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTimeOffset 类型序列化
/// </summary>
public class NewtonsoftJsonDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public NewtonsoftJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTimeOffset ReadJson(JsonReader reader, Type objectType, DateTimeOffset existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTimeOffset value, JsonSerializer serializer)
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
serializer.Serialize(writer, formatDateTime.ToString(Format));
}
}
/// <summary>
/// DateTimeOffset 类型序列化
/// </summary>
public class NewtonsoftJsonNullableDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public NewtonsoftJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTimeOffset? ReadJson(JsonReader reader, Type objectType, DateTimeOffset? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
/// <exception cref="NotImplementedException"></exception>
public override void WriteJson(JsonWriter writer, DateTimeOffset? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
serializer.Serialize(writer, formatDateTime!.Value.ToString(Format));
}
}
}

View File

@@ -0,0 +1,140 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// 解决 long 精度问题
/// </summary>
public class NewtonsoftJsonLongToStringJsonConverter : JsonConverter<long>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public NewtonsoftJsonLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override long ReadJson(JsonReader reader, Type objectType, long existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jt = JValue.ReadFrom(reader);
return jt.Type == JTokenType.Null // 处理 public long? Property { get; set;} = 0; 情况,也就是类型是 long? 但是也给了默认值
? existingValue
: jt.Value<long>();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, long value, JsonSerializer serializer)
{
if (OverMaxLengthOf17)
{
if (value.ToString().Length <= 17) writer.WriteValue(value);
else writer.WriteValue(value.ToString());
}
else writer.WriteValue(value.ToString());
}
}
/// <summary>
/// 解决 long? 精度问题
/// </summary>
public class NewtonsoftJsonNullableLongToStringJsonConverter : JsonConverter<long?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public NewtonsoftJsonNullableLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override long? ReadJson(JsonReader reader, Type objectType, long? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jt = JValue.ReadFrom(reader);
return jt.Value<long?>();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, long? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else
{
var newValue = value.Value;
if (OverMaxLengthOf17)
{
if (newValue.ToString().Length <= 17) writer.WriteValue(newValue);
else writer.WriteValue(newValue.ToString());
}
else writer.WriteValue(newValue.ToString());
}
}
}

View File

@@ -0,0 +1,129 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
#if !NET5_0
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// TimeOnly 类型序列化
/// </summary>
public class NewtonsoftJsonTimeOnlyJsonConverter : JsonConverter<TimeOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override TimeOnly ReadJson(JsonReader reader, Type objectType, TimeOnly existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return TimeOnly.Parse(value!);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, TimeOnly value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString(Format));
}
}
/// <summary>
/// TimeOnly? 类型序列化
/// </summary>
public class NewtonsoftJsonNullableTimeOnlyJsonConverter : JsonConverter<TimeOnly?>
{
/// <summary>
/// 构造函数
/// </summary>
public NewtonsoftJsonNullableTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public NewtonsoftJsonNullableTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="hasExistingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override TimeOnly? ReadJson(JsonReader reader, Type objectType, TimeOnly? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = JValue.ReadFrom(reader).Value<string>();
return !string.IsNullOrWhiteSpace(value) ? TimeOnly.Parse(value) : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, TimeOnly? value, JsonSerializer serializer)
{
if (value == null) writer.WriteNull();
else serializer.Serialize(writer, value.Value.ToString(Format));
}
}
#endif

View File

@@ -0,0 +1,119 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateOnly 类型序列化
/// </summary>
public class SystemTextJsonDateOnlyJsonConverter : JsonConverter<DateOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateOnly.Parse(reader.GetString()!);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
/// <summary>
/// DateOnly? 类型序列化
/// </summary>
public class SystemTextJsonNullableDateOnlyJsonConverter : JsonConverter<DateOnly?>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonNullableDateOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableDateOnlyJsonConverter(string format = "yyyy-MM-dd")
{
Format = format;
}
/// <summary>
/// 日期格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateOnly.TryParse(reader.GetString(), out var date) ? date : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateOnly? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString(Format));
}
}

View File

@@ -0,0 +1,119 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTime 类型序列化
/// </summary>
public class SystemTextJsonDateTimeJsonConverter : JsonConverter<DateTime>
{
/// <summary>
/// 默认构造函数
/// </summary>
public SystemTextJsonDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
/// <summary>
/// DateTime? 类型序列化
/// </summary>
public class SystemTextJsonNullableDateTimeJsonConverter : JsonConverter<DateTime?>
{
/// <summary>
/// 默认构造函数
/// </summary>
public SystemTextJsonNullableDateTimeJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableDateTimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Penetrates.ConvertToDateTime(ref reader);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString(Format));
}
}

View File

@@ -0,0 +1,160 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
using ThingsGateway.Core.Extension;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// DateTimeOffset 类型序列化
/// </summary>
public class SystemTextJsonDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public SystemTextJsonDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
writer.WriteStringValue(formatDateTime.ToString(Format));
}
}
/// <summary>
/// DateTimeOffset? 类型序列化
/// </summary>
public class SystemTextJsonNullableDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset?>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonNullableDateTimeOffsetJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
/// <param name="outputToLocalDateTime"></param>
public SystemTextJsonNullableDateTimeOffsetJsonConverter(string format = "yyyy-MM-dd HH:mm:ss", bool outputToLocalDateTime = false)
: this(format)
{
Localized = outputToLocalDateTime;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 是否输出为为当地时间
/// </summary>
public bool Localized { get; private set; } = false;
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.SpecifyKind(Penetrates.ConvertToDateTime(ref reader), Localized ? DateTimeKind.Local : DateTimeKind.Utc);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else
{
// 判断是否序列化成当地时间
var formatDateTime = Localized ? value.ConvertToDateTime() : value;
writer.WriteStringValue(formatDateTime!.Value.ToString(Format));
}
}
}

View File

@@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// 解决 long 精度问题
/// </summary>
public class SystemTextJsonLongToStringJsonConverter : JsonConverter<long>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public SystemTextJsonLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType == JsonTokenType.String
? long.Parse(reader.GetString()!)
: reader.GetInt64();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
{
if (OverMaxLengthOf17)
{
if (value.ToString().Length <= 17) writer.WriteNumberValue(value);
else writer.WriteStringValue(value.ToString());
}
else writer.WriteStringValue(value.ToString());
}
}
/// <summary>
/// 解决 long? 精度问题
/// </summary>
public class SystemTextJsonNullableLongToStringJsonConverter : JsonConverter<long?>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonNullableLongToStringJsonConverter()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="overMaxLengthOf17"></param>
public SystemTextJsonNullableLongToStringJsonConverter(bool overMaxLengthOf17 = false)
{
OverMaxLengthOf17 = overMaxLengthOf17;
}
/// <summary>
/// 是否超过最大长度 17 再处理
/// </summary>
public bool OverMaxLengthOf17 { get; set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType == JsonTokenType.String
? long.Parse(reader.GetString()!)
: reader.GetInt64();
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else
{
var newValue = value.Value;
if (OverMaxLengthOf17)
{
if (newValue.ToString().Length <= 17) writer.WriteNumberValue(newValue);
else writer.WriteStringValue(newValue.ToString());
}
else writer.WriteStringValue(newValue.ToString());
}
}
}

View File

@@ -0,0 +1,123 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
#if !NET5_0
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// TimeOnly 类型序列化
/// </summary>
public class SystemTextJsonTimeOnlyJsonConverter : JsonConverter<TimeOnly>
{
/// <summary>
/// 构造函数
/// </summary>
public SystemTextJsonTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeOnly.Parse(reader.GetString()!);
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
/// <summary>
/// TimeOnly? 类型序列化
/// </summary>
public class SystemTextJsonNullableTimeOnlyJsonConverter : JsonConverter<TimeOnly?>
{
/// <summary>
/// 默认构造函数
/// </summary>
public SystemTextJsonNullableTimeOnlyJsonConverter()
: this(default)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="format"></param>
public SystemTextJsonNullableTimeOnlyJsonConverter(string format = "HH:mm:ss")
{
Format = format;
}
/// <summary>
/// 时间格式化格式
/// </summary>
public string Format { get; private set; }
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override TimeOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeOnly.TryParse(reader.GetString(), out var time) ? time : null;
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, TimeOnly? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString(Format));
}
}
#endif

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Mvc;
using ThingsGateway.JsonSerialization;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Json 序列化服务拓展类
/// </summary>
public static class JsonSerializationServiceCollectionExtensions
{
/// <summary>
/// 配置 Json 序列化提供器
/// </summary>
/// <typeparam name="TJsonSerializerProvider"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddJsonSerialization<TJsonSerializerProvider>(this IServiceCollection services)
where TJsonSerializerProvider : class, IJsonSerializerProvider
{
services.AddSingleton<IJsonSerializerProvider, TJsonSerializerProvider>();
return services;
}
/// <summary>
/// 配置 JsonOptions 序列化选项
/// <para>主要给非 Web 环境使用</para>
/// </summary>
/// <param name="services"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IServiceCollection AddJsonOptions(this IServiceCollection services, Action<JsonOptions> configure)
{
// 手动添加配置
services.Configure<JsonOptions>(options =>
{
configure?.Invoke(options);
});
return services;
}
}

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using ThingsGateway.JsonSerialization;
namespace Newtonsoft.Json;
/// <summary>
/// Newtonsoft.Json 拓展
/// </summary>
public static class NewtonsoftJsonExtensions
{
/// <summary>
/// 添加 DateTime/DateTime?/DateTimeOffset/DateTimeOffset? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <param name="outputFormat"></param>
/// <param name="localized">自动转换 DateTimeOffset 为当地时间</param>
/// <returns></returns>
public static IList<JsonConverter> AddDateTimeTypeConverters(this IList<JsonConverter> converters, string outputFormat = "yyyy-MM-dd HH:mm:ss", bool localized = false)
{
converters.Add(new NewtonsoftJsonDateTimeJsonConverter(outputFormat));
converters.Add(new NewtonsoftNullableJsonDateTimeJsonConverter(outputFormat));
converters.Add(new NewtonsoftJsonDateTimeOffsetJsonConverter(outputFormat, localized));
converters.Add(new NewtonsoftJsonNullableDateTimeOffsetJsonConverter(outputFormat, localized));
return converters;
}
/// <summary>
/// 添加 long/long? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <param name="overMaxLengthOf17">是否超过最大长度 17 再处理</param>
/// <remarks></remarks>
public static IList<JsonConverter> AddLongTypeConverters(this IList<JsonConverter> converters, bool overMaxLengthOf17 = false)
{
converters.Add(new NewtonsoftJsonLongToStringJsonConverter(overMaxLengthOf17));
converters.Add(new NewtonsoftJsonNullableLongToStringJsonConverter(overMaxLengthOf17));
return converters;
}
/// <summary>
/// 添加 DateOnly/DateOnly? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <returns></returns>
public static IList<JsonConverter> AddDateOnlyConverters(this IList<JsonConverter> converters)
{
#if !NET5_0
converters.Add(new NewtonsoftJsonDateOnlyJsonConverter());
converters.Add(new NewtonsoftJsonNullableDateOnlyJsonConverter());
#endif
return converters;
}
/// <summary>
/// 添加 TimeOnly/TimeOnly? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <returns></returns>
public static IList<JsonConverter> AddTimeOnlyConverters(this IList<JsonConverter> converters)
{
#if !NET5_0
converters.Add(new NewtonsoftJsonTimeOnlyJsonConverter());
converters.Add(new NewtonsoftJsonNullableTimeOnlyJsonConverter());
#endif
return converters;
}
}

View File

@@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using ThingsGateway.JsonSerialization;
using System.Text.Json.Serialization;
namespace System.Text.Json;
/// <summary>
/// System.Text.Json 拓展
/// </summary>
public static class SystemTextJsonExtensions
{
/// <summary>
/// 添加 DateTime/DateTime?/DateTimeOffset/DateTimeOffset? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <param name="outputFormat"></param>
/// <param name="localized">自动转换 DateTimeOffset 为当地时间</param>
/// <returns></returns>
public static IList<JsonConverter> AddDateTimeTypeConverters(this IList<JsonConverter> converters, string outputFormat = "yyyy-MM-dd HH:mm:ss", bool localized = false)
{
converters.Add(new SystemTextJsonDateTimeJsonConverter(outputFormat));
converters.Add(new SystemTextJsonNullableDateTimeJsonConverter(outputFormat));
converters.Add(new SystemTextJsonDateTimeOffsetJsonConverter(outputFormat, localized));
converters.Add(new SystemTextJsonNullableDateTimeOffsetJsonConverter(outputFormat, localized));
return converters;
}
/// <summary>
/// 添加 long/long? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <param name="overMaxLengthOf17">是否超过最大长度 17 再处理</param>
/// <remarks></remarks>
public static IList<JsonConverter> AddLongTypeConverters(this IList<JsonConverter> converters, bool overMaxLengthOf17 = false)
{
converters.Add(new SystemTextJsonLongToStringJsonConverter(overMaxLengthOf17));
converters.Add(new SystemTextJsonNullableLongToStringJsonConverter(overMaxLengthOf17));
return converters;
}
/// <summary>
/// 添加 DateOnly/DateOnly? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <returns></returns>
public static IList<JsonConverter> AddDateOnlyConverters(this IList<JsonConverter> converters)
{
#if !NET5_0
converters.Add(new SystemTextJsonDateOnlyJsonConverter());
converters.Add(new SystemTextJsonNullableDateOnlyJsonConverter());
#endif
return converters;
}
/// <summary>
/// 添加 TimeOnly/TimeOnly? 类型序列化处理
/// </summary>
/// <param name="converters"></param>
/// <returns></returns>
public static IList<JsonConverter> AddTimeOnlyConverters(this IList<JsonConverter> converters)
{
#if !NET5_0
converters.Add(new SystemTextJsonTimeOnlyJsonConverter());
converters.Add(new SystemTextJsonNullableTimeOnlyJsonConverter());
#endif
return converters;
}
}

View File

@@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// 常量、公共方法配置类
/// </summary>
internal static class Penetrates
{
/// <summary>
/// 转换
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
internal static DateTime ConvertToDateTime(ref Utf8JsonReader reader)
{
// 处理时间戳自动转换
if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt64(out var longValue))
{
return longValue.ToDateTime();
};
var stringValue = reader.GetString();
// 处理时间戳自动转换
if (long.TryParse(stringValue, out var longValue2))
{
return longValue2.ToDateTime();
}
return Convert.ToDateTime(stringValue);
}
/// <summary>
/// 转换
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
internal static DateTime ConvertToDateTime(ref JsonReader reader)
{
if (reader.TokenType == JsonToken.Integer)
{
return JValue.ReadFrom(reader).Value<long>().ToDateTime();
}
var stringValue = JValue.ReadFrom(reader).Value<string>();
// 处理时间戳自动转换
if (long.TryParse(stringValue, out var longValue2))
{
return longValue2.ToDateTime();
}
return Convert.ToDateTime(stringValue);
}
}

View File

@@ -0,0 +1,88 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json;
using ThingsGateway.Admin.Application;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// JSON 静态帮助类
/// </summary>
public static class JSON
{
/// <summary>
/// 获取 JSON 序列化提供器
/// </summary>
/// <returns></returns>
public static IJsonSerializerProvider GetJsonSerializer()
{
return App.RootServices!.GetService<IJsonSerializerProvider>();
}
/// <summary>
/// 序列化对象
/// </summary>
/// <param name="value"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
public static string Serialize(object value, object jsonSerializerOptions = default)
{
return GetJsonSerializer().Serialize(value, jsonSerializerOptions);
}
/// <summary>
/// 反序列化字符串
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
public static T Deserialize<T>(string json, object jsonSerializerOptions = default)
{
return GetJsonSerializer().Deserialize<T>(json, jsonSerializerOptions);
}
/// <summary>
/// 获取 JSON 配置选项
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <returns></returns>
public static TOptions GetSerializerOptions<TOptions>()
where TOptions : class
{
return GetJsonSerializer().GetSerializerOptions() as TOptions;
}
/// <summary>
/// 检查 JSON 字符串是否有效
/// </summary>
/// <param name="jsonString"></param>
/// <returns></returns>
public static bool IsValid(string jsonString)
{
if (string.IsNullOrWhiteSpace(jsonString)) return false;
try
{
using var document = JsonDocument.Parse(jsonString);
return true;
}
catch (JsonException)
{
return false;
}
}
}

View File

@@ -0,0 +1,51 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// Json 序列化提供器
/// </summary>
public interface IJsonSerializerProvider
{
/// <summary>
/// 序列化对象
/// </summary>
/// <param name="value"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
string Serialize(object value, object jsonSerializerOptions = default);
/// <summary>
/// 反序列化字符串
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
T Deserialize<T>(string json, object jsonSerializerOptions = default);
/// <summary>
/// 反序列化字符串
/// </summary>
/// <param name="json"></param>
/// <param name="returnType"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
object Deserialize(string json, Type returnType, object jsonSerializerOptions = default);
/// <summary>
/// 返回读取全局配置的 JSON 选项
/// </summary>
/// <returns></returns>
object GetSerializerOptions();
}

View File

@@ -0,0 +1,82 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Text.Json;
namespace ThingsGateway.JsonSerialization;
/// <summary>
/// System.Text.Json 序列化提供器(默认实现)
/// </summary>
public class SystemTextJsonSerializerProvider : IJsonSerializerProvider
{
/// <summary>
/// 获取 JSON 配置选项
/// </summary>
private readonly JsonOptions _jsonOptions;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="options"></param>
public SystemTextJsonSerializerProvider(IOptions<JsonOptions> options)
{
_jsonOptions = options.Value;
}
/// <summary>
/// 序列化对象
/// </summary>
/// <param name="value"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
public string Serialize(object value, object jsonSerializerOptions = null)
{
return JsonSerializer.Serialize(value, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerOptions);
}
/// <summary>
/// 反序列化字符串
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
public T Deserialize<T>(string json, object jsonSerializerOptions = null)
{
return JsonSerializer.Deserialize<T>(json, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerOptions);
}
/// <summary>
/// 反序列化字符串
/// </summary>
/// <param name="json"></param>
/// <param name="returnType"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
public object Deserialize(string json, Type returnType, object jsonSerializerOptions = null)
{
return JsonSerializer.Deserialize(json, returnType, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerOptions);
}
/// <summary>
/// 返回读取全局配置的 JSON 选项
/// </summary>
/// <returns></returns>
public object GetSerializerOptions()
{
return _jsonOptions?.JsonSerializerOptions;
}
}

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using ThingsGateway.Logging;
namespace Microsoft.Extensions.Logging;
/// <summary>
/// <see cref="ILogger"/> 拓展
/// </summary>
public static class ILoggerExtensions
{
/// <summary>
/// 配置日志上下文
/// </summary>
/// <param name="logger"></param>
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
/// <returns></returns>
public static IDisposable ScopeContext(this ILogger logger, IDictionary<object, object> properties)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
return logger.BeginScope(new LogContext { Properties = properties });
}
/// <summary>
/// 配置日志上下文
/// </summary>
/// <param name="logger"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IDisposable ScopeContext(this ILogger logger, Action<LogContext> configure)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
var logContext = new LogContext();
configure?.Invoke(logContext);
return logger.BeginScope(logContext);
}
/// <summary>
/// 配置日志上下文
/// </summary>
/// <param name="logger"></param>
/// <param name="context"></param>
/// <returns></returns>
public static IDisposable ScopeContext(this ILogger logger, LogContext context)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
return logger.BeginScope(context);
}
}

View File

@@ -0,0 +1,150 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using ThingsGateway.Logging;
namespace Microsoft.Extensions.Logging;
/// <summary>
/// <see cref="ILoggerFactory"/> 拓展
/// </summary>
public static class ILoggerFactoryExtensions
{
/// <summary>
/// 添加文件日志记录器
/// </summary>
/// <param name="factory">日志工厂</param>
/// <param name="fileName">日志文件完整路径或文件名,推荐 .log 作为拓展名</param>
/// <param name="append">追加到已存在日志文件或覆盖它们</param>
/// <returns><see cref="ILoggerFactory"/></returns>
public static ILoggerFactory AddFile(this ILoggerFactory factory, string fileName, bool append = true)
{
// 添加文件日志记录器提供程序
factory.AddProvider(new FileLoggerProvider(fileName ?? "application.log", append));
return factory;
}
/// <summary>
/// 添加文件日志记录器
/// </summary>
/// <param name="factory">日志工厂</param>
/// <param name="fileName">日志文件完整路径或文件名,推荐 .log 作为拓展名</param>
/// <param name="configure"></param>
/// <returns><see cref="ILoggerFactory"/></returns>
public static ILoggerFactory AddFile(this ILoggerFactory factory, string fileName, Action<FileLoggerOptions> configure)
{
var options = new FileLoggerOptions();
configure?.Invoke(options);
// 添加文件日志记录器提供程序
factory.AddProvider(new FileLoggerProvider(fileName ?? "application.log", options));
return factory;
}
/// <summary>
/// 添加文件日志记录器
/// </summary>
/// <param name="factory">日志工厂</param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns><see cref="ILoggerFactory"/></returns>
public static ILoggerFactory AddFile(this ILoggerFactory factory, Action<FileLoggerOptions> configure = default)
{
return factory.AddFile(() => "Logging:File", configure);
}
/// <summary>
/// 添加文件日志记录器
/// </summary>
/// <param name="factory">日志工厂</param>
/// <param name="configuraionKey">获取配置文件对应的 Key</param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns><see cref="ILoggerFactory"/></returns>
public static ILoggerFactory AddFile(this ILoggerFactory factory, Func<string> configuraionKey, Action<FileLoggerOptions> configure = default)
{
// 添加文件日志记录器提供程序
factory.AddProvider(Penetrates.CreateFromConfiguration(configuraionKey, configure));
return factory;
}
/// <summary>
/// 添加数据库日志记录器
/// </summary>
/// <typeparam name="TDatabaseLoggingWriter">实现自 <see cref="IDatabaseLoggingWriter"/></typeparam>
/// <param name="factory">日志工厂</param>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns><see cref="ILoggerFactory"/></returns>
public static ILoggerFactory AddDatabase<TDatabaseLoggingWriter>(this ILoggerFactory factory, IServiceProvider serviceProvider, Action<DatabaseLoggerOptions> configure)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
var options = new DatabaseLoggerOptions();
configure?.Invoke(options);
var databaseLoggerProvider = new DatabaseLoggerProvider(options);
// 解决数据库写入器中循环引用数据库仓储问题
if (databaseLoggerProvider._serviceScope == null)
{
databaseLoggerProvider.SetServiceProvider(serviceProvider, typeof(TDatabaseLoggingWriter));
}
// 添加数据库日志记录器提供程序
factory.AddProvider(databaseLoggerProvider);
return factory;
}
/// <summary>
/// 添加数据库日志记录器
/// </summary>
/// <typeparam name="TDatabaseLoggingWriter">实现自 <see cref="IDatabaseLoggingWriter"/></typeparam>
/// <param name="factory">日志工厂</param>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="configuraionKey">配置文件对于的 Key</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns><see cref="ILoggerFactory"/></returns>
public static ILoggerFactory AddDatabase<TDatabaseLoggingWriter>(this ILoggerFactory factory, IServiceProvider serviceProvider, string configuraionKey = default, Action<DatabaseLoggerOptions> configure = default)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
return factory.AddDatabase<TDatabaseLoggingWriter>(() => configuraionKey ?? "Logging:Database", serviceProvider, configure);
}
/// <summary>
/// 添加数据库日志记录器
/// </summary>
/// <typeparam name="TDatabaseLoggingWriter">实现自 <see cref="IDatabaseLoggingWriter"/></typeparam>
/// <param name="factory">日志工厂</param>
/// <param name="configuraionKey">获取配置文件对应的 Key</param>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns><see cref="ILoggerFactory"/></returns>
public static ILoggerFactory AddDatabase<TDatabaseLoggingWriter>(this ILoggerFactory factory, Func<string> configuraionKey, IServiceProvider serviceProvider, Action<DatabaseLoggerOptions> configure = default)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
// 创建数据库日志记录器提供程序
var databaseLoggerProvider = Penetrates.CreateFromConfiguration(configuraionKey, configure);
// 解决数据库写入器中循环引用数据库仓储问题
if (databaseLoggerProvider._serviceScope == null)
{
databaseLoggerProvider.SetServiceProvider(serviceProvider, typeof(TDatabaseLoggingWriter));
}
// 添加数据库日志记录器提供程序
factory.AddProvider(databaseLoggerProvider);
return factory;
}
}

View File

@@ -0,0 +1,198 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Diagnostics;
using ThingsGateway.Logging;
namespace Microsoft.Extensions.Logging;
/// <summary>
/// 日志构建器拓展类
/// </summary>
public static class ILoggingBuilderExtensions
{
/// <summary>
/// 添加控制台默认格式化器
/// </summary>
/// <param name="builder"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static ILoggingBuilder AddConsoleFormatter(this ILoggingBuilder builder, Action<ConsoleFormatterExtendOptions> configure = default)
{
configure ??= (options) => { };
return builder.AddConsole(options => options.FormatterName = "console-format")
.AddConsoleFormatter<ConsoleFormatterExtend, ConsoleFormatterExtendOptions>(configure);
}
/// <summary>
/// 添加文件日志记录器
/// </summary>
/// <param name="builder">日志构建器</param>
/// <param name="fileName">日志文件完整路径或文件名,推荐 .log 作为拓展名</param>
/// <param name="append">追加到已存在日志文件或覆盖它们</param>
/// <returns><see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string fileName, bool append = true)
{
// 注册文件日志记录器提供器
builder.Services.Add(ServiceDescriptor.Singleton<ILoggerProvider, FileLoggerProvider>((serviceProvider) =>
{
return new FileLoggerProvider(fileName ?? "application.log", append);
}));
return builder;
}
/// <summary>
/// 添加文件日志记录器
/// </summary>
/// <param name="builder">日志构建器</param>
/// <param name="fileName">日志文件完整路径或文件名,推荐 .log 作为拓展名</param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns><see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string fileName, Action<FileLoggerOptions> configure)
{
// 注册文件日志记录器提供器
builder.Services.Add(ServiceDescriptor.Singleton<ILoggerProvider, FileLoggerProvider>((serviceProvider) =>
{
var options = new FileLoggerOptions();
configure?.Invoke(options);
return new FileLoggerProvider(fileName ?? "application.log", options);
}));
return builder;
}
/// <summary>
/// 添加文件日志记录器(从配置文件中)默认 Key 为:"Logging:File"
/// </summary>
/// <param name="builder">日志构建器</param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns><see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddFile(this ILoggingBuilder builder, Action<FileLoggerOptions> configure = default)
{
return builder.AddFile(() => "Logging:File", configure);
}
/// <summary>
/// 添加文件日志记录器(从配置文件中)
/// </summary>
/// <param name="builder">日志构建器</param>
/// <param name="configuraionKey">获取配置文件对应的 Key</param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns><see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddFile(this ILoggingBuilder builder, Func<string> configuraionKey, Action<FileLoggerOptions> configure = default)
{
// 注册文件日志记录器提供器
builder.Services.Add(ServiceDescriptor.Singleton<ILoggerProvider, FileLoggerProvider>((serviceProvider) =>
{
return Penetrates.CreateFromConfiguration(configuraionKey, configure);
}));
return builder;
}
/// <summary>
/// 添加数据库日志记录器
/// </summary>
/// <typeparam name="TDatabaseLoggingWriter">实现自 <see cref="IDatabaseLoggingWriter"/></typeparam>
/// <param name="builder">日志构建器</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns><see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddDatabase<TDatabaseLoggingWriter>(this ILoggingBuilder builder, Action<DatabaseLoggerOptions> configure)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
// 注册数据库日志写入器
builder.Services.TryAddTransient<TDatabaseLoggingWriter, TDatabaseLoggingWriter>();
// 注册数据库日志记录器提供器
builder.Services.Add(ServiceDescriptor.Singleton<ILoggerProvider>((serviceProvider) =>
{
// 解决在 IDatabaseLoggingWriter 实现类直接注册仓储导致死循环的问题
var stackTrace = new System.Diagnostics.StackTrace();
var frames = stackTrace.GetFrames();
if (frames.Any(u => u.HasMethod() && u.GetMethod()!.Name == "ResolveDbContext")
|| frames.Count(u => u.HasMethod() && u.GetMethod()!.Name.StartsWith("<AddDatabase>")) > 1)
{
return new EmptyLoggerProvider();
}
var options = new DatabaseLoggerOptions();
configure?.Invoke(options);
// 数据库日志记录器提供程序
var databaseLoggerProvider = new DatabaseLoggerProvider(options);
databaseLoggerProvider.SetServiceProvider(serviceProvider, typeof(TDatabaseLoggingWriter));
return databaseLoggerProvider;
}));
return builder;
}
/// <summary>
/// 添加数据库日志记录器
/// </summary>
/// <typeparam name="TDatabaseLoggingWriter">实现自 <see cref="IDatabaseLoggingWriter"/></typeparam>
/// <param name="builder">日志构建器</param>
/// <param name="configuraionKey">配置文件对于的 Key</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns><see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddDatabase<TDatabaseLoggingWriter>(this ILoggingBuilder builder, string configuraionKey = default, Action<DatabaseLoggerOptions> configure = default)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
return builder.AddDatabase<TDatabaseLoggingWriter>(() => configuraionKey ?? "Logging:Database", configure);
}
/// <summary>
/// 添加数据库日志记录器(从配置文件中)
/// </summary>
/// <typeparam name="TDatabaseLoggingWriter">实现自 <see cref="IDatabaseLoggingWriter"/></typeparam>
/// <param name="builder">日志构建器</param>
/// <param name="configuraionKey">获取配置文件对于的 Key</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns><see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddDatabase<TDatabaseLoggingWriter>(this ILoggingBuilder builder, Func<string> configuraionKey, Action<DatabaseLoggerOptions> configure = default)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
// 注册数据库日志写入器
builder.Services.TryAddTransient<TDatabaseLoggingWriter, TDatabaseLoggingWriter>();
// 注册数据库日志记录器提供器
builder.Services.Add(ServiceDescriptor.Singleton<ILoggerProvider>((serviceProvider) =>
{
// 解决在 IDatabaseLoggingWriter 实现类直接注册仓储导致死循环的问题
var stackTrace = new System.Diagnostics.StackTrace();
var frames = stackTrace.GetFrames();
if (frames.Any(u => u.HasMethod() && u.GetMethod()!.Name == "ResolveDbContext")
|| frames.Count(u => u.HasMethod() && u.GetMethod()!.Name.StartsWith("<AddDatabase>")) > 1)
{
return new EmptyLoggerProvider();
}
// 创建数据库日志记录器提供程序
var databaseLoggerProvider = Penetrates.CreateFromConfiguration(configuraionKey, configure);
databaseLoggerProvider.SetServiceProvider(serviceProvider, typeof(TDatabaseLoggingWriter));
return databaseLoggerProvider;
}));
return builder;
}
}

View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// LogContext 拓展
/// </summary>
public static class LogContextExtensions
{
/// <summary>
/// 设置上下文数据
/// </summary>
/// <param name="logContext"></param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns></returns>
public static LogContext Set(this LogContext logContext, object key, object value)
{
if (logContext == null || key == null) return logContext;
logContext.Properties ??= new Dictionary<object, object>();
if (logContext.Properties.ContainsKey(key)) logContext.Properties.Remove(key);
logContext.Properties.Add(key, value);
return logContext;
}
/// <summary>
/// 批量设置上下文数据
/// </summary>
/// <param name="logContext"></param>
/// <param name="properties"></param>
/// <returns></returns>
public static LogContext SetRange(this LogContext logContext, IDictionary<object, object> properties)
{
if (logContext == null
|| properties == null
|| properties.Count == 0) return logContext;
foreach (var (key, value) in properties)
{
logContext.Set(key, value);
}
return logContext;
}
/// <summary>
/// 获取上下文数据
/// </summary>
/// <param name="logContext"></param>
/// <param name="key">键</param>
/// <returns></returns>
public static object Get(this LogContext logContext, object key)
{
if (logContext == null
|| key == null
|| logContext.Properties == null
|| logContext.Properties.Count == 0) return default;
var isExists = logContext.Properties.TryGetValue(key, out var value);
return isExists ? value : null;
}
/// <summary>
/// 获取上下文数据
/// </summary>
/// <param name="logContext"></param>
/// <param name="key">键</param>
/// <returns></returns>
public static object Get<T>(this LogContext logContext, object key)
{
var value = logContext.Get(key);
return (T)value;
}
}

View File

@@ -0,0 +1,66 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
namespace ThingsGateway.Logging;
/// <summary>
/// <see cref="LogMessage"/> 拓展
/// </summary>
public static class LogMessageExtensions
{
/// <summary>
/// 高性能创建 JSON 对象字符串
/// </summary>
/// <param name="_"><see cref="LogMessage"/></param>
/// <param name="writeAction"></param>
/// <param name="writeIndented">是否对 JSON 格式化</param>
/// <returns><see cref="string"/></returns>
public static string Write(this LogMessage _, Action<Utf8JsonWriter> writeAction, bool writeIndented = false)
{
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
// 解决中文乱码问题
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = writeIndented
});
writeAction?.Invoke(writer);
writer.Flush();
return Encoding.UTF8.GetString(stream.ToArray());
}
/// <summary>
/// 高性能创建 JSON 数组字符串
/// </summary>
/// <param name="logMsg"><see cref="LogMessage"/></param>
/// <param name="writeAction"></param>
/// <param name="writeIndented">是否对 JSON 格式化</param>
/// <returns><see cref="string"/></returns>
public static string WriteArray(this LogMessage logMsg, Action<Utf8JsonWriter> writeAction, bool writeIndented = false)
{
return logMsg.Write(writer =>
{
writer.WriteStartArray();
writeAction?.Invoke(writer);
writer.WriteEndArray();
}, writeIndented);
}
}

View File

@@ -0,0 +1,153 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ThingsGateway.Admin.Application;
using ThingsGateway.Logging;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// 日志服务拓展类
/// </summary>
public static class LoggingServiceCollectionExtensions
{
/// <summary>
/// 添加控制台默认格式化器
/// </summary>
/// <param name="services"></param>
/// <param name="configure">添加更多配置</param>
/// <returns></returns>
public static IServiceCollection AddConsoleFormatter(this IServiceCollection services, Action<ConsoleFormatterExtendOptions> configure = default)
{
return services.AddLogging(builder => builder.AddConsoleFormatter(configure));
}
/// <summary>
/// 添加日志监视器服务
/// </summary>
/// <param name="services"></param>
/// <param name="configure">添加更多配置</param>
/// <param name="jsonKey">配置文件对于的 Key默认为 Logging:Monitor</param>
/// <returns></returns>
public static IServiceCollection AddMonitorLogging(this IServiceCollection services, Action<LoggingMonitorSettings> configure = default, string jsonKey = "Logging:Monitor")
{
// 读取配置
var settings = App.Configuration?.GetSection(jsonKey).Get<LoggingMonitorSettings>()
?? new LoggingMonitorSettings();
settings.IsMvcFilterRegister = false; // 解决过去 Mvc Filter 全局注册的问题
settings.FromGlobalFilter = true; // 解决局部和全局触发器同时配置触发两次问题
settings.IncludeOfMethods ??= Array.Empty<string>();
settings.ExcludeOfMethods ??= Array.Empty<string>();
settings.MethodsSettings ??= Array.Empty<LoggingMonitorMethod>();
// 添加外部配置
configure?.Invoke(settings);
// 如果配置 GlobalEnabled = false 且 IncludeOfMethods 和 ExcludeOfMethods 都为空,则不注册服务
if (settings.GlobalEnabled == false
&& settings.IncludeOfMethods.Length == 0
&& settings.ExcludeOfMethods.Length == 0) return services;
// 注册日志监视器过滤器
services.AddMvcFilter(new LoggingMonitorAttribute(settings));
return services;
}
/// <summary>
/// 添加文件日志服务
/// </summary>
/// <param name="services"></param>
/// <param name="fileName">日志文件完整路径或文件名,推荐 .log 作为拓展名</param>
/// <param name="append">追加到已存在日志文件或覆盖它们</param>
/// <returns></returns>
public static IServiceCollection AddFileLogging(this IServiceCollection services, string fileName, bool append = true)
{
return services.AddLogging(builder => builder.AddFile(fileName, append));
}
/// <summary>
/// 添加文件日志服务
/// </summary>
/// <param name="services"></param>
/// <param name="fileName">日志文件完整路径或文件名,推荐 .log 作为拓展名</param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns></returns>
public static IServiceCollection AddFileLogging(this IServiceCollection services, string fileName, Action<FileLoggerOptions> configure)
{
return services.AddLogging(builder => builder.AddFile(fileName, configure));
}
/// <summary>
/// 添加文件日志服务(从配置文件中读取配置)
/// </summary>
/// <param name="services"></param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns></returns>
public static IServiceCollection AddFileLogging(this IServiceCollection services, Action<FileLoggerOptions> configure = default)
{
return services.AddLogging(builder => builder.AddFile(configure));
}
/// <summary>
/// 添加文件日志服务(从配置文件中读取配置)
/// </summary>
/// <param name="services"></param>
/// <param name="configuraionKey">获取配置文件对应的 Key</param>
/// <param name="configure">文件日志记录器配置选项委托</param>
/// <returns></returns>
public static IServiceCollection AddFileLogging(this IServiceCollection services, Func<string> configuraionKey, Action<FileLoggerOptions> configure = default)
{
return services.AddLogging(builder => builder.AddFile(configuraionKey, configure));
}
/// <summary>
/// 添加数据库日志服务
/// </summary>
/// <param name="services"></param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns></returns>
public static IServiceCollection AddDatabaseLogging<TDatabaseLoggingWriter>(this IServiceCollection services, Action<DatabaseLoggerOptions> configure)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
return services.AddLogging(builder => builder.AddDatabase<TDatabaseLoggingWriter>(configure));
}
/// <summary>
/// 添加数据库日志服务
/// </summary>
/// <param name="services"></param>
/// <param name="configuraionKey">配置文件对于的 Key</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns></returns>
public static IServiceCollection AddDatabaseLogging<TDatabaseLoggingWriter>(this IServiceCollection services, string configuraionKey = default, Action<DatabaseLoggerOptions> configure = default)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
return services.AddLogging(builder => builder.AddDatabase<TDatabaseLoggingWriter>(configuraionKey, configure));
}
/// <summary>
/// 添加数据库日志服务
/// </summary>
/// <param name="services"></param>
/// <param name="configuraionKey">获取配置文件对于的 Key</param>
/// <param name="configure">数据库日志记录器配置选项委托</param>
/// <returns></returns>
public static IServiceCollection AddDatabaseLogging<TDatabaseLoggingWriter>(this IServiceCollection services, Func<string> configuraionKey, Action<DatabaseLoggerOptions> configure = default)
where TDatabaseLoggingWriter : class, IDatabaseLoggingWriter
{
return services.AddLogging(builder => builder.AddDatabase<TDatabaseLoggingWriter>(configuraionKey, configure));
}
}

View File

@@ -0,0 +1,40 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// 控制台颜色结构
/// </summary>
internal readonly struct ConsoleColors
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="foreground"></param>
/// <param name="background"></param>
public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
{
Foreground = foreground;
Background = background;
}
/// <summary>
/// 前景色
/// </summary>
public ConsoleColor? Foreground { get; }
/// <summary>
/// 背景色
/// </summary>
public ConsoleColor? Background { get; }
}

View File

@@ -0,0 +1,123 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using ThingsGateway.Admin.Application;
namespace ThingsGateway.Logging;
/// <summary>
/// 控制台默认格式化程序拓展
/// </summary>
public sealed class ConsoleFormatterExtend : ConsoleFormatter, IDisposable
{
/// <summary>
/// 日志格式化选项刷新 Token
/// </summary>
private readonly IDisposable _formatOptionsReloadToken;
/// <summary>
/// 日志格式化配置选项
/// </summary>
private ConsoleFormatterExtendOptions _formatterOptions;
/// <summary>
/// 是否启用控制台颜色
/// </summary>
private bool _disableColors;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="formatterOptions"></param>
public ConsoleFormatterExtend(IOptionsMonitor<ConsoleFormatterExtendOptions> formatterOptions)
: base("console-format")
{
(_formatOptionsReloadToken, _formatterOptions) = (formatterOptions.OnChange(ReloadFormatterOptions)!, formatterOptions.CurrentValue);
_disableColors = _formatterOptions.ColorBehavior == LoggerColorBehavior.Disabled || (_formatterOptions.ColorBehavior == LoggerColorBehavior.Default && Console.IsOutputRedirected);
}
/// <summary>
/// 写入日志
/// </summary>
/// <typeparam name="TState"></typeparam>
/// <param name="logEntry"></param>
/// <param name="scopeProvider"></param>
/// <param name="textWriter"></param>
public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter)
{
// 获取格式化后的消息
var message = logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception);
// 创建日志消息
var logDateTime = _formatterOptions.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
var logMsg = new LogMessage(logEntry.Category, logEntry.LogLevel, logEntry.EventId, message!, logEntry.Exception!, null, logEntry.State!, logDateTime, Environment.CurrentManagedThreadId, _formatterOptions.UseUtcTimestamp, App.GetTraceId());
string? standardMessage = null;
// 是否自定义了自定义日志格式化程序,如果是则使用
if (_formatterOptions.MessageFormat != null)
{
// 设置日志上下文
logMsg = Penetrates.SetLogContext(scopeProvider!, logMsg, _formatterOptions.IncludeScopes);
// 设置日志消息模板
standardMessage = _formatterOptions.MessageFormat(logMsg);
}
// 判断是否自定义了日志筛选器,如果是则检查是否符合条件
if (_formatterOptions.WriteFilter?.Invoke(logMsg) == false) return;
// 空检查
if (message is null) return;
// 获取标准化日志消息
standardMessage ??= Penetrates.OutputStandardMessage(logMsg
, _formatterOptions.DateFormat
, true
, _disableColors
, _formatterOptions.WithTraceId
);
// 判断是否自定义了日志格式化程序
if (_formatterOptions.WriteHandler != null)
{
_formatterOptions.WriteHandler?.Invoke(logMsg, scopeProvider!, textWriter, standardMessage, _formatterOptions);
}
else
{
// 写入控制台
textWriter.WriteLine(standardMessage);
}
}
/// <summary>
/// 释放非托管资源
/// </summary>
public void Dispose()
{
_formatOptionsReloadToken?.Dispose();
}
/// <summary>
/// 刷新日志格式化选项
/// </summary>
/// <param name="options"></param>
private void ReloadFormatterOptions(ConsoleFormatterExtendOptions options)
{
_formatterOptions = options;
_disableColors = options.ColorBehavior == LoggerColorBehavior.Disabled || (options.ColorBehavior == LoggerColorBehavior.Default && Console.IsOutputRedirected);
}
}

View File

@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
namespace ThingsGateway.Logging;
/// <summary>
/// 控制台默认格式化选项拓展
/// </summary>
public sealed class ConsoleFormatterExtendOptions : ConsoleFormatterOptions
{
/// <summary>
/// 构造函数
/// </summary>
public ConsoleFormatterExtendOptions()
: base()
{
// 默认启用控制台日志上下文功能
IncludeScopes = true;
}
/// <summary>
/// 控制是否启用颜色
/// </summary>
public LoggerColorBehavior ColorBehavior { get; set; }
/// <summary>
/// 自定义日志消息格式化程序
/// </summary>
public Func<LogMessage, string> MessageFormat { get; set; }
/// <summary>
/// 日期格式化
/// </summary>
public string DateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";
/// <summary>
/// 自定义日志筛选器
/// </summary>
public Func<LogMessage, bool> WriteFilter { get; set; }
/// <summary>
/// 自定义格式化日志处理程序
/// </summary>
public Action<LogMessage, IExternalScopeProvider, TextWriter, string, ConsoleFormatterExtendOptions> WriteHandler { get; set; }
/// <summary>
/// 显示跟踪/请求 Id
/// </summary>
public bool WithTraceId { get; set; } = false;
}

View File

@@ -0,0 +1,127 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using ThingsGateway.Admin.Application;
namespace ThingsGateway.Logging;
/// <summary>
/// 数据库日志记录器
/// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
public sealed class DatabaseLogger : ILogger
{
/// <summary>
/// 记录器类别名称
/// </summary>
private readonly string _logName;
/// <summary>
/// 数据库日志记录器提供器
/// </summary>
private readonly DatabaseLoggerProvider _databaseLoggerProvider;
/// <summary>
/// 日志配置选项
/// </summary>
private readonly DatabaseLoggerOptions _options;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logName">记录器类别名称</param>
/// <param name="databaseLoggerProvider">数据库日志记录器提供器</param>
public DatabaseLogger(string logName, DatabaseLoggerProvider databaseLoggerProvider)
{
_logName = logName;
_databaseLoggerProvider = databaseLoggerProvider;
_options = databaseLoggerProvider.LoggerOptions;
}
/// <summary>
/// 开始逻辑操作范围
/// </summary>
/// <typeparam name="TState">标识符类型参数</typeparam>
/// <param name="state">要写入的项/对象</param>
/// <returns><see cref="IDisposable"/></returns>
public IDisposable BeginScope<TState>(TState state) where TState : notnull
{
return _databaseLoggerProvider.ScopeProvider.Push(state);
}
/// <summary>
/// 检查是否已启用给定日志级别
/// </summary>
/// <param name="logLevel">日志级别</param>
/// <returns><see cref="bool"/></returns>
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= _options.MinimumLevel;
}
/// <summary>
/// 写入日志项
/// </summary>
/// <typeparam name="TState">标识符类型参数</typeparam>
/// <param name="logLevel">日志级别</param>
/// <param name="eventId">事件 Id</param>
/// <param name="state">要写入的项/对象</param>
/// <param name="exception">异常对象</param>
/// <param name="formatter">日志格式化器</param>
/// <exception cref="ArgumentNullException"></exception>
public void Log<TState>(LogLevel logLevel
, EventId eventId
, TState state
, Exception? exception
, Func<TState, Exception, string> formatter)
{
// 判断日志级别是否有效
if (!IsEnabled(logLevel)) return;
// 检查日志格式化器
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
// 获取格式化后的消息
var message = formatter(state, exception!);
var logDateTime = _options.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
var logMsg = new LogMessage(_logName, logLevel, eventId, message, exception!, null, state!, logDateTime, Environment.CurrentManagedThreadId, _options.UseUtcTimestamp, App.GetTraceId());
// 设置日志上下文
logMsg = Penetrates.SetLogContext(_databaseLoggerProvider.ScopeProvider, logMsg, _options.IncludeScopes);
// 判断是否自定义了日志筛选器,如果是则检查是否符合条件
if (_options.WriteFilter?.Invoke(logMsg) == false) return;
// 设置日志消息模板
logMsg.Message = _options.MessageFormat != null
? _options.MessageFormat(logMsg)
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId);
// 空检查
if (logMsg.Message is null) return;
// 判断是否忽略循环输出日志,解决数据库日志提供程序中也输出日志导致写入递归问题
if (_options.IgnoreReferenceLoop)
{
var stackTrace = new StackTrace();
if (stackTrace.GetFrames().Any(u => u.HasMethod() && typeof(IDatabaseLoggingWriter).IsAssignableFrom(u.GetMethod()!.DeclaringType))) return;
}
// 写入日志队列
_databaseLoggerProvider.WriteToQueue(logMsg);
}
}

View File

@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Logging;
/// <summary>
/// 数据库记录器配置选项
/// </summary>
public sealed class DatabaseLoggerOptions
{
/// <summary>
/// 自定义日志筛选器
/// </summary>
public Func<LogMessage, bool> WriteFilter { get; set; }
/// <summary>
/// 最低日志记录级别
/// </summary>
public LogLevel MinimumLevel { get; set; } = LogLevel.Trace;
/// <summary>
/// 自定义日志消息格式化程序
/// </summary>
public Func<LogMessage, string> MessageFormat { get; set; }
/// <summary>
/// 自定义数据库日志写入错误程序
/// </summary>
/// <remarks>主要解决日志在写入过程出现异常问题</remarks>
/// <example>
/// options.HandleWriteError = (err) => {
/// // do anything
/// };
/// </example>
public Action<DatabaseWriteError> HandleWriteError { get; set; }
/// <summary>
/// 是否使用 UTC 时间戳,默认 false
/// </summary>
public bool UseUtcTimestamp { get; set; }
/// <summary>
/// 日期格式化
/// </summary>
public string DateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";
/// <summary>
/// 是否启用日志上下文
/// </summary>
public bool IncludeScopes { get; set; } = true;
/// <summary>
/// 忽略日志循环输出
/// </summary>
/// <remarks>对性能有些许影响</remarks>
public bool IgnoreReferenceLoop { get; set; } = true;
/// <summary>
/// 显示跟踪/请求 Id
/// </summary>
public bool WithTraceId { get; set; } = false;
}

View File

@@ -0,0 +1,195 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace ThingsGateway.Logging;
/// <summary>
/// 数据库日志记录器提供程序
/// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
[ProviderAlias("Database")]
public sealed class DatabaseLoggerProvider : ILoggerProvider, ISupportExternalScope
{
/// <summary>
/// 存储多日志分类日志记录器
/// </summary>
private readonly ConcurrentDictionary<string, DatabaseLogger> _databaseLoggers = new();
/// <summary>
/// 日志消息队列(线程安全)
/// </summary>
private readonly BlockingCollection<LogMessage> _logMessageQueue = new(12000);
/// <summary>
/// 日志作用域提供器
/// </summary>
private IExternalScopeProvider _scopeProvider;
/// <summary>
/// 数据库日志写入器作用域范围
/// </summary>
internal IServiceScope _serviceScope;
/// <summary>
/// 数据库日志写入器
/// </summary>
private IDatabaseLoggingWriter _databaseLoggingWriter;
/// <summary>
/// 长时间运行的后台任务
/// </summary>
/// <remarks>实现不间断写入</remarks>
private Task _processQueueTask;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="databaseLoggerOptions">数据库日志记录器配置选项</param>
public DatabaseLoggerProvider(DatabaseLoggerOptions databaseLoggerOptions)
{
LoggerOptions = databaseLoggerOptions;
}
/// <summary>
/// 数据库日志记录器配置选项
/// </summary>
internal DatabaseLoggerOptions LoggerOptions { get; private set; }
/// <summary>
/// 日志作用域提供器
/// </summary>
internal IExternalScopeProvider ScopeProvider
{
get
{
_scopeProvider ??= new LoggerExternalScopeProvider();
return _scopeProvider;
}
}
/// <summary>
/// 创建数据库日志记录器
/// </summary>
/// <param name="categoryName">日志分类名</param>
/// <returns><see cref="ILogger"/></returns>
public ILogger CreateLogger(string categoryName)
{
return _databaseLoggers.GetOrAdd(categoryName, name => new DatabaseLogger(name, this));
}
/// <summary>
/// 设置作用域提供器
/// </summary>
/// <param name="scopeProvider"></param>
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
}
/// <summary>
/// 释放非托管资源
/// </summary>
/// <remarks>控制日志消息队列</remarks>
public void Dispose()
{
// 标记日志消息队列停止写入
_logMessageQueue.CompleteAdding();
try
{
// 设置 1.5秒的缓冲时间,避免还有日志消息没有完成写入数据库中
_processQueueTask?.Wait(1500);
}
catch (TaskCanceledException) { }
catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { }
catch { }
// 清空数据库日志记录器
_databaseLoggers.Clear();
// 释放数据库写入器作用域范围
_serviceScope?.Dispose();
}
/// <summary>
/// 将日志消息写入队列中等待后台任务出队写入数据库
/// </summary>
/// <param name="logMsg">结构化日志消息</param>
internal void WriteToQueue(LogMessage logMsg)
{
// 只有队列可持续入队才写入
if (!_logMessageQueue.IsAddingCompleted)
{
try
{
_logMessageQueue.Add(logMsg);
return;
}
catch (InvalidOperationException) { }
catch { }
}
}
/// <summary>
/// 设置服务提供器
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="databaseLoggingWriterType"></param>
internal void SetServiceProvider(IServiceProvider serviceProvider, Type databaseLoggingWriterType)
{
// 解析服务作用域工厂服务
var serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
// 创建服务作用域
_serviceScope = serviceScopeFactory.CreateScope();
// 基于当前作用域创建数据库日志写入器
_databaseLoggingWriter = (_serviceScope.ServiceProvider.GetRequiredService(databaseLoggingWriterType)! as IDatabaseLoggingWriter)!;
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
_processQueueTask = Task.Factory.StartNew(async state => await ((DatabaseLoggerProvider)state!).ProcessQueueAsync()
, this, TaskCreationOptions.LongRunning);
}
/// <summary>
/// 将日志消息写入数据库中
/// </summary>
/// <remarks></remarks>
private async Task ProcessQueueAsync()
{
foreach (var logMsg in _logMessageQueue.GetConsumingEnumerable())
{
try
{
// 调用数据库写入器写入数据库方法
await _databaseLoggingWriter.WriteAsync(logMsg, _logMessageQueue.Count == 0);
}
catch (Exception ex)
{
// 处理数据库写入错误
if (LoggerOptions.HandleWriteError != null)
{
var databaseWriteError = new DatabaseWriteError(ex);
LoggerOptions.HandleWriteError(databaseWriteError);
}
// 这里不抛出异常,避免中断日志写入
else { }
}
finally { }
}
}
}

View File

@@ -0,0 +1,52 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Logging;
/// <summary>
/// 数据库日志配置类
/// </summary>
public sealed class DatabaseLoggerSettings
{
/// <summary>
/// 最低日志记录级别
/// </summary>
public LogLevel MinimumLevel { get; set; } = LogLevel.Trace;
/// <summary>
/// 是否使用 UTC 时间戳,默认 false
/// </summary>
public bool UseUtcTimestamp { get; set; }
/// <summary>
/// 日期格式化
/// </summary>
public string DateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";
/// <summary>
/// 是否启用日志上下文
/// </summary>
public bool IncludeScopes { get; set; } = true;
/// <summary>
/// 忽略日志循环输出
/// </summary>
/// <remarks>对性能有些许影响</remarks>
public bool IgnoreReferenceLoop { get; set; } = true;
/// <summary>
/// 显示跟踪/请求 Id
/// </summary>
public bool WithTraceId { get; set; } = false;
}

View File

@@ -0,0 +1,33 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// 数据库写入错误信息上下文
/// </summary>
public sealed class DatabaseWriteError
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="exception">异常对象</param>
internal DatabaseWriteError(Exception exception)
{
Exception = exception;
}
/// <summary>
/// 引起数据库写入异常信息
/// </summary>
public Exception Exception { get; private set; }
}

View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// 数据库日志写入器
/// </summary>
public interface IDatabaseLoggingWriter
{
/// <summary>
/// 写入数据库
/// </summary>
/// <param name="logMsg">结构化日志消息</param>
/// <param name="flush">清除缓冲区</param>
/// <returns><see cref="Task"/></returns>
Task WriteAsync(LogMessage logMsg, bool flush);
}

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Logging;
/// <summary>
/// 空日志记录器
/// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
public sealed class EmptyLogger : ILogger
{
/// <summary>
/// 开始逻辑操作范围
/// </summary>
/// <typeparam name="TState">标识符类型参数</typeparam>
/// <param name="state">要写入的项/对象</param>
/// <returns><see cref="IDisposable"/></returns>
public IDisposable BeginScope<TState>(TState state) where TState : notnull
{
return default;
}
/// <summary>
/// 检查是否已启用给定日志级别
/// </summary>
/// <param name="logLevel">日志级别</param>
/// <returns><see cref="bool"/></returns>
public bool IsEnabled(LogLevel logLevel)
{
return false;
}
/// <summary>
/// 写入日志项
/// </summary>
/// <typeparam name="TState">标识符类型参数</typeparam>
/// <param name="logLevel">日志级别</param>
/// <param name="eventId">事件 Id</param>
/// <param name="state">要写入的项/对象</param>
/// <param name="exception">异常对象</param>
/// <param name="formatter">日志格式化器</param>
/// <exception cref="ArgumentNullException"></exception>
public void Log<TState>(LogLevel logLevel
, EventId eventId
, TState state
, Exception? exception
, Func<TState, Exception, string> formatter)
{
// 判断日志级别是否有效
if (!IsEnabled(logLevel)) return;
}
}

View File

@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace ThingsGateway.Logging;
/// <summary>
/// 空日志记录器提供程序
/// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
[ProviderAlias("Empty")]
public sealed class EmptyLoggerProvider : ILoggerProvider
{
/// <summary>
/// 存储多日志分类日志记录器
/// </summary>
private readonly ConcurrentDictionary<string, EmptyLogger> _emptyLoggers = new();
/// <summary>
/// 创建空日志记录器
/// </summary>
/// <param name="categoryName">日志分类名</param>
/// <returns><see cref="ILogger"/></returns>
public ILogger CreateLogger(string categoryName)
{
return _emptyLoggers.GetOrAdd(categoryName, name => new EmptyLogger());
}
/// <summary>
/// 释放非托管资源
/// </summary>
/// <remarks>控制日志消息队列</remarks>
public void Dispose()
{
// 清空空日志记录器
_emptyLoggers.Clear();
}
}

View File

@@ -0,0 +1,118 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
using ThingsGateway.Admin.Application;
namespace ThingsGateway.Logging;
/// <summary>
/// 文件日志记录器
/// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
public sealed class FileLogger : ILogger
{
/// <summary>
/// 记录器类别名称
/// </summary>
private readonly string _logName;
/// <summary>
/// 文件日志记录器提供器
/// </summary>
private readonly FileLoggerProvider _fileLoggerProvider;
/// <summary>
/// 日志配置选项
/// </summary>
private readonly FileLoggerOptions _options;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logName">记录器类别名称</param>
/// <param name="fileLoggerProvider">文件日志记录器提供器</param>
public FileLogger(string logName, FileLoggerProvider fileLoggerProvider)
{
_logName = logName;
_fileLoggerProvider = fileLoggerProvider;
_options = fileLoggerProvider.LoggerOptions;
}
/// <summary>
/// 开始逻辑操作范围
/// </summary>
/// <typeparam name="TState">标识符类型参数</typeparam>
/// <param name="state">要写入的项/对象</param>
/// <returns><see cref="IDisposable"/></returns>
public IDisposable BeginScope<TState>(TState state) where TState : notnull
{
return _fileLoggerProvider.ScopeProvider.Push(state);
}
/// <summary>
/// 检查是否已启用给定日志级别
/// </summary>
/// <param name="logLevel">日志级别</param>
/// <returns><see cref="bool"/></returns>
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= _options.MinimumLevel;
}
/// <summary>
/// 写入日志项
/// </summary>
/// <typeparam name="TState">标识符类型参数</typeparam>
/// <param name="logLevel">日志级别</param>
/// <param name="eventId">事件 Id</param>
/// <param name="state">要写入的项/对象</param>
/// <param name="exception">异常对象</param>
/// <param name="formatter">日志格式化器</param>
/// <exception cref="ArgumentNullException"></exception>
public void Log<TState>(LogLevel logLevel
, EventId eventId
, TState state
, Exception? exception
, Func<TState, Exception, string> formatter)
{
// 判断日志级别是否有效
if (!IsEnabled(logLevel)) return;
// 检查日志格式化器
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
// 获取格式化后的消息
var message = formatter(state, exception!);
var logDateTime = _options.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
var logMsg = new LogMessage(_logName, logLevel, eventId, message, exception!, null, state!, logDateTime, Environment.CurrentManagedThreadId, _options.UseUtcTimestamp, App.GetTraceId());
// 设置日志上下文
logMsg = Penetrates.SetLogContext(_fileLoggerProvider.ScopeProvider, logMsg, _options.IncludeScopes);
// 判断是否自定义了日志筛选器,如果是则检查是否符合条件
if (_options.WriteFilter?.Invoke(logMsg) == false) return;
// 设置日志消息模板
logMsg.Message = _options.MessageFormat != null
? _options.MessageFormat(logMsg)
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId);
// 空检查
if (logMsg.Message is null) return;
// 写入日志队列
_fileLoggerProvider.WriteToQueue(logMsg);
}
}

View File

@@ -0,0 +1,97 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Logging;
/// <summary>
/// 文件日志记录器配置选项
/// </summary>
public sealed class FileLoggerOptions
{
/// <summary>
/// 追加到已存在日志文件或覆盖它们
/// </summary>
public bool Append { get; set; } = true;
/// <summary>
/// 控制每一个日志文件最大存储大小,默认无限制,单位是 B也就是 1024 才等于 1KB
/// </summary>
/// <remarks>如果指定了该值,那么日志文件大小超出了该配置就会创建的日志文件,新创建的日志文件命名规则:文件名+[递增序号].log</remarks>
public long FileSizeLimitBytes { get; set; } = 0;
/// <summary>
/// 控制最大创建的日志文件数量,默认无限制,配合 <see cref="FileSizeLimitBytes"/> 使用
/// </summary>
/// <remarks>如果指定了该值,那么超出该值将从最初日志文件中从头写入覆盖</remarks>
public int MaxRollingFiles { get; set; } = 0;
/// <summary>
/// 最低日志记录级别
/// </summary>
public LogLevel MinimumLevel { get; set; } = LogLevel.Trace;
/// <summary>
/// 是否使用 UTC 时间戳,默认 false
/// </summary>
public bool UseUtcTimestamp { get; set; }
/// <summary>
/// 自定义日志消息格式化程序
/// </summary>
public Func<LogMessage, string> MessageFormat { get; set; }
/// <summary>
/// 自定义日志筛选器
/// </summary>
public Func<LogMessage, bool> WriteFilter { get; set; }
/// <summary>
/// 自定义日志文件名格式化程序(规则)
/// </summary>
/// <example>
/// options.FileNameRule = (fileName) => {
/// return String.Format(Path.GetFileNameWithoutExtension(fileName) + "_{0:yyyy}-{0:MM}-{0:dd}" + Path.GetExtension(fileName), DateTime.UtcNow);
///
/// // 或者每天创建一个文件
/// // return String.Format(fileName, DateTime.UtcNow);
/// }
/// </example>
public Func<string, string> FileNameRule { get; set; }
/// <summary>
/// 自定义日志文件写入错误程序
/// </summary>
/// <remarks>主要解决日志在写入过程中文件被打开或其他应用程序占用的情况,一旦出现上述情况可创建备用日志文件继续写入</remarks>
/// <example>
/// options.HandleWriteError = (err) => {
/// err.UseRollbackFileName(Path.GetFileNameWithoutExtension(err.CurrentFileName)+ "_alt" + Path.GetExtension(err.CurrentFileName));
/// };
/// </example>
public Action<FileWriteError> HandleWriteError { get; set; }
/// <summary>
/// 日期格式化
/// </summary>
public string DateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";
/// <summary>
/// 是否启用日志上下文
/// </summary>
public bool IncludeScopes { get; set; } = true;
/// <summary>
/// 显示跟踪/请求 Id
/// </summary>
public bool WithTraceId { get; set; } = false;
}

View File

@@ -0,0 +1,195 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace ThingsGateway.Logging;
/// <summary>
/// 文件日志记录器提供程序
/// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
[ProviderAlias("File")]
public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
{
/// <summary>
/// 存储多日志分类日志记录器
/// </summary>
private readonly ConcurrentDictionary<string, FileLogger> _fileLoggers = new();
/// <summary>
/// 日志消息队列(线程安全)
/// </summary>
private readonly BlockingCollection<LogMessage> _logMessageQueue = new(12000);
/// <summary>
/// 日志作用域提供器
/// </summary>
private IExternalScopeProvider _scopeProvider;
/// <summary>
/// 记录日志所有滚动文件名
/// </summary>
/// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks>
internal readonly ConcurrentDictionary<string, FileInfo> _rollingFileNames = new();
/// <summary>
/// 文件日志写入器
/// </summary>
private readonly FileLoggingWriter _fileLoggingWriter;
/// <summary>
/// 长时间运行的后台任务
/// </summary>
/// <remarks>实现不间断写入</remarks>
private readonly Task _processQueueTask;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="fileName">日志文件名</param>
public FileLoggerProvider(string fileName)
: this(fileName, true)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="fileName">日志文件名</param>
/// <param name="append">追加到已存在日志文件或覆盖它们</param>
public FileLoggerProvider(string fileName, bool append)
: this(fileName, new FileLoggerOptions() { Append = append })
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="fileName">日志文件名</param>
/// <param name="fileLoggerOptions">文件日志记录器配置选项</param>
public FileLoggerProvider(string fileName, FileLoggerOptions fileLoggerOptions)
{
// 支持文件名嵌入系统环境变量,格式为:%SystemDrive%%SystemRoot%,处理 Windows 和 Linux 路径分隔符不一致问题
FileName = Environment.ExpandEnvironmentVariables(fileName).Replace('\\', '/');
LoggerOptions = fileLoggerOptions;
// 创建文件日志写入器
_fileLoggingWriter = new FileLoggingWriter(this);
// 创建长时间运行的后台任务,并将日志消息队列中数据写入文件中
_processQueueTask = Task.Factory.StartNew(async state => await ((FileLoggerProvider)state!).ProcessQueueAsync()
, this, TaskCreationOptions.LongRunning);
}
/// <summary>
/// 文件名
/// </summary>
internal string FileName;
/// <summary>
/// 文件日志记录器配置选项
/// </summary>
internal FileLoggerOptions LoggerOptions { get; private set; }
/// <summary>
/// 日志作用域提供器
/// </summary>
internal IExternalScopeProvider ScopeProvider
{
get
{
_scopeProvider ??= new LoggerExternalScopeProvider();
return _scopeProvider;
}
}
/// <summary>
/// 创建文件日志记录器
/// </summary>
/// <param name="categoryName">日志分类名</param>
/// <returns><see cref="ILogger"/></returns>
public ILogger CreateLogger(string categoryName)
{
return _fileLoggers.GetOrAdd(categoryName, name => new FileLogger(name, this));
}
/// <summary>
/// 设置作用域提供器
/// </summary>
/// <param name="scopeProvider"></param>
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
}
/// <summary>
/// 释放非托管资源
/// </summary>
/// <remarks>控制日志消息队列</remarks>
public void Dispose()
{
// 标记日志消息队列停止写入
_logMessageQueue.CompleteAdding();
try
{
// 设置 1.5秒的缓冲时间,避免还有日志消息没有完成写入文件中
_processQueueTask?.Wait(1500);
}
catch (TaskCanceledException) { }
catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { }
catch { }
// 清空文件日志记录器
_fileLoggers.Clear();
// 清空滚动文件名记录器
_rollingFileNames.Clear();
// 释放内部文件写入器
_fileLoggingWriter.Close();
}
/// <summary>
/// 将日志消息写入队列中等待后台任务出队写入文件
/// </summary>
/// <param name="logMsg">日志消息</param>
internal void WriteToQueue(LogMessage logMsg)
{
// 只有队列可持续入队才写入
if (!_logMessageQueue.IsAddingCompleted)
{
try
{
_logMessageQueue.Add(logMsg);
return;
}
catch (InvalidOperationException) { }
catch { }
}
}
/// <summary>
/// 将日志消息写入文件中
/// </summary>
/// <returns></returns>
private async Task ProcessQueueAsync()
{
foreach (var logMsg in _logMessageQueue.GetConsumingEnumerable())
{
await _fileLoggingWriter.WriteAsync(logMsg, _logMessageQueue.Count == 0);
}
}
}

View File

@@ -0,0 +1,68 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Logging;
/// <summary>
/// 文件日志配置类
/// </summary>
public sealed class FileLoggerSettings
{
/// <summary>
/// 日志文件完整路径或文件名,推荐 .log 作为拓展名
/// </summary>
public string FileName { get; set; } = "application.log";
/// <summary>
/// 追加到已存在日志文件或覆盖它们
/// </summary>
public bool Append { get; set; } = true;
/// <summary>
/// 控制每一个日志文件最大存储大小,默认无限制,单位是 B也就是 1024 才等于 1KB
/// </summary>
/// <remarks>如果指定了该值,那么日志文件大小超出了该配置就会创建的日志文件,新创建的日志文件命名规则:文件名+[递增序号].log</remarks>
public long FileSizeLimitBytes { get; set; } = 0;
/// <summary>
/// 控制最大创建的日志文件数量,默认无限制,配合 <see cref="FileSizeLimitBytes"/> 使用
/// </summary>
/// <remarks>如果指定了该值,那么超出该值将从最初日志文件中从头写入覆盖</remarks>
public int MaxRollingFiles { get; set; } = 0;
/// <summary>
/// 最低日志记录级别
/// </summary>
public LogLevel MinimumLevel { get; set; } = LogLevel.Trace;
/// <summary>
/// 是否使用 UTC 时间戳,默认 false
/// </summary>
public bool UseUtcTimestamp { get; set; }
/// <summary>
/// 日期格式化
/// </summary>
public string DateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";
/// <summary>
/// 是否启用日志上下文
/// </summary>
public bool IncludeScopes { get; set; } = true;
/// <summary>
/// 显示跟踪/请求 Id
/// </summary>
public bool WithTraceId { get; set; } = false;
}

View File

@@ -0,0 +1,343 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// 文件日志写入器
/// </summary>
internal class FileLoggingWriter
{
/// <summary>
/// 文件日志记录器提供程序
/// </summary>
private readonly FileLoggerProvider _fileLoggerProvider;
/// <summary>
/// 日志配置选项
/// </summary>
private readonly FileLoggerOptions _options;
/// <summary>
/// 日志文件名
/// </summary>
private string _fileName;
/// <summary>
/// 文件流
/// </summary>
private Stream _fileStream;
/// <summary>
/// 文本写入器
/// </summary>
private TextWriter _textWriter;
/// <summary>
/// 缓存上次返回的基本日志文件名,避免重复解析
/// </summary>
private string __LastBaseFileName = null;
/// <summary>
/// 判断是否启动滚动日志功能
/// </summary>
private readonly bool _isEnabledRollingFiles;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="fileLoggerProvider">文件日志记录器提供程序</param>
internal FileLoggingWriter(FileLoggerProvider fileLoggerProvider)
{
_fileLoggerProvider = fileLoggerProvider;
_options = fileLoggerProvider.LoggerOptions;
_isEnabledRollingFiles = _options.MaxRollingFiles > 0 && _options.FileSizeLimitBytes > 0;
// 解析当前写入日志的文件名
GetCurrentFileName();
// 打开文件并持续写入
OpenFile(_options.Append);
}
/// <summary>
/// 获取日志基础文件名
/// </summary>
/// <returns>日志文件名</returns>
private string GetBaseFileName()
{
var fileName = _fileLoggerProvider.FileName;
// 如果配置了日志文件名格式化程序,则先处理再返回
if (_options.FileNameRule != null)
fileName = _options.FileNameRule(fileName);
return fileName;
}
/// <summary>
/// 解析当前写入日志的文件名
/// </summary>
private void GetCurrentFileName()
{
// 获取日志基础文件名并将其缓存
var baseFileName = GetBaseFileName();
__LastBaseFileName = baseFileName;
// 是否配置了日志文件最大存储大小
if (_options.FileSizeLimitBytes > 0)
{
// 定义文件查找通配符
var logFileMask = Path.GetFileNameWithoutExtension(baseFileName) + "*" + Path.GetExtension(baseFileName);
// 获取文件路径
var logDirName = Path.GetDirectoryName(baseFileName);
// 如果没有配置文件路径则默认放置根目录
if (string.IsNullOrEmpty(logDirName)) logDirName = Directory.GetCurrentDirectory();
// 在当前目录下根据文件通配符查找所有匹配的文件
var logFiles = Directory.Exists(logDirName)
? Directory.GetFiles(logDirName, logFileMask, SearchOption.TopDirectoryOnly)
: Array.Empty<string>();
// 处理已有日志文件存在情况
if (logFiles.Length > 0)
{
// 根据文件名和最后更新时间获取最近操作的文件
var lastFileInfo = logFiles
.Select(fName => new FileInfo(fName))
.OrderByDescending(fInfo => fInfo.Name)
.OrderByDescending(fInfo => fInfo.LastWriteTime).First();
_fileName = lastFileInfo.FullName;
}
// 没有任何匹配的日志文件直接使用当前基础文件名
else _fileName = baseFileName;
}
else _fileName = baseFileName;
}
/// <summary>
/// 获取下一个匹配的日志文件名
/// </summary>
/// <remarks>只有配置了 <see cref="FileLoggerOptions.FileSizeLimitBytes"/> 或 <see cref="FileLoggerOptions.FileNameRule"/> 或 <see cref="FileLoggerOptions.MaxRollingFiles"/> 有效</remarks>
/// <returns>新的文件名</returns>
private string GetNextFileName()
{
// 获取日志基础文件名
var baseFileName = GetBaseFileName();
// 如果文件不存在或没有达到 FileSizeLimitBytes 限制大小,则返回基础文件名
if (!System.IO.File.Exists(baseFileName)
|| _options.FileSizeLimitBytes <= 0
|| new FileInfo(baseFileName).Length < _options.FileSizeLimitBytes) return baseFileName;
// 获取日志基础文件名和当前日志文件名
var currentFileIndex = 0;
var baseFileNameOnly = Path.GetFileNameWithoutExtension(baseFileName);
var currentFileNameOnly = Path.GetFileNameWithoutExtension(_fileName);
// 解析日志文件名【递增】部分
var suffix = currentFileNameOnly[baseFileNameOnly.Length..];
if (suffix.Length > 0 && int.TryParse(suffix, out var parsedIndex))
{
currentFileIndex = parsedIndex;
}
// 【递增】部分 +1
var nextFileIndex = currentFileIndex + 1;
// 如果配置了最大【递增】数,则超出自动从头开始(覆盖写入)
if (_options.MaxRollingFiles > 0)
{
nextFileIndex %= _options.MaxRollingFiles;
}
// 返回下一个匹配的日志文件名(完整路径)
var nextFileName = baseFileNameOnly + (nextFileIndex > 0 ? nextFileIndex.ToString() : "") + Path.GetExtension(baseFileName);
return Path.Combine(Path.GetDirectoryName(baseFileName)!, nextFileName);
}
/// <summary>
/// 打开文件
/// </summary>
/// <param name="append"></param>
private void OpenFile(bool append)
{
try
{
CreateFileStream();
}
catch (Exception ex)
{
// 处理文件写入错误
if (_options.HandleWriteError != null)
{
var fileWriteError = new FileWriteError(_fileName, ex);
_options.HandleWriteError(fileWriteError);
// 如果配置了备用文件名,则重新写入
if (fileWriteError.RollbackFileName != null)
{
_fileLoggerProvider.FileName = fileWriteError.RollbackFileName;
// 递归操作,直到应用程序停止
GetCurrentFileName();
CreateFileStream();
}
}
// 其他直接抛出异常
else throw;
}
// 初始化文本写入器
_textWriter = new StreamWriter(_fileStream);
// 创建文件流
void CreateFileStream()
{
var fileInfo = new FileInfo(_fileName);
// 判断文件目录是否存在,不存在则自动创建
fileInfo.Directory!.Create();
// 创建文件流,采用共享锁方式
_fileStream = new FileStream(_fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, FileOptions.WriteThrough);
// 删除超出滚动日志限制的文件
DropFilesIfOverLimit(fileInfo);
// 判断是否追加还是覆盖
if (append) _fileStream.Seek(0, SeekOrigin.End);
else _fileStream.SetLength(0);
}
}
/// <summary>
/// 判断是否需要创建新文件写入
/// </summary>
private void CheckForNewLogFile()
{
var openNewFile = false;
if (isMaxFileSizeThresholdReached() || isBaseFileNameChanged())
openNewFile = true;
// 重新创建新文件并写入
if (openNewFile)
{
Close();
// 计算新文件名
_fileName = GetNextFileName();
// 打开新文件并写入
OpenFile(false);
}
// 是否超出限制的最大大小
bool isMaxFileSizeThresholdReached() => _options.FileSizeLimitBytes > 0
&& _fileStream.Length > _options.FileSizeLimitBytes;
// 是否重新自定义了文件名
bool isBaseFileNameChanged()
{
if (_options.FileNameRule != null)
{
var baseFileName = GetBaseFileName();
if (baseFileName != __LastBaseFileName)
{
__LastBaseFileName = baseFileName;
return true;
}
return false;
}
return false;
}
}
/// <summary>
/// 删除超出滚动日志限制的文件
/// </summary>
/// <param name="fileInfo"></param>
private void DropFilesIfOverLimit(FileInfo fileInfo)
{
// 判断是否启用滚动文件功能
if (!_isEnabledRollingFiles) return;
// 处理 Windows 和 Linux 路径分隔符不一致问题
var fName = fileInfo.FullName.Replace('\\', '/');
// 将当前文件名存储到集合中
var succeed = _fileLoggerProvider._rollingFileNames.TryAdd(fName, fileInfo);
// 判断超出限制的文件自动删除
if (succeed && _fileLoggerProvider._rollingFileNames.Count > _options.MaxRollingFiles)
{
// 根据最后写入时间删除过时日志
var dropFiles = _fileLoggerProvider._rollingFileNames
.OrderBy(u => u.Value.LastWriteTimeUtc)
.Take(_fileLoggerProvider._rollingFileNames.Count - _options.MaxRollingFiles);
// 遍历所有需要删除的文件
foreach (var rollingFile in dropFiles)
{
var removeSucceed = _fileLoggerProvider._rollingFileNames.TryRemove(rollingFile.Key, out _);
if (!removeSucceed) continue;
// 执行删除
Task.Run(() =>
{
if (File.Exists(rollingFile.Key)) File.Delete(rollingFile.Key);
});
}
}
}
/// <summary>
/// 写入文件
/// </summary>
/// <param name="logMsg">日志消息</param>
/// <param name="flush"></param>
/// <returns></returns>
internal async Task WriteAsync(LogMessage logMsg, bool flush)
{
if (_textWriter == null) return;
CheckForNewLogFile();
await _textWriter.WriteLineAsync(logMsg.Message);
if (flush)
{
await _textWriter.FlushAsync();
}
}
/// <summary>
/// 关闭文本写入器并释放
/// </summary>
internal void Close()
{
if (_textWriter == null) return;
var textloWriter = _textWriter;
_textWriter = null;
textloWriter.Dispose();
_fileStream.Dispose();
_fileStream = null;
}
}

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// 文件写入错误信息上下文
/// </summary>
public sealed class FileWriteError
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="currentFileName">当前日志文件名</param>
/// <param name="exception">异常对象</param>
internal FileWriteError(string currentFileName, Exception exception)
{
CurrentFileName = currentFileName;
Exception = exception;
}
/// <summary>
/// 当前日志文件名
/// </summary>
public string CurrentFileName { get; private set; }
/// <summary>
/// 引起文件写入异常信息
/// </summary>
public Exception Exception { get; private set; }
/// <summary>
/// 备用日志文件名
/// </summary>
internal string RollbackFileName { get; private set; }
/// <summary>
/// 配置日志文件写入错误后新的备用日志文件名
/// </summary>
/// <param name="rollbackFileName">备用日志文件名</param>
public void UseRollbackFileName(string rollbackFileName)
{
RollbackFileName = rollbackFileName;
}
}

View File

@@ -0,0 +1,31 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// 日志上下文
/// </summary>
public sealed class LogContext
{
/// <summary>
/// 构造函数
/// </summary>
public LogContext()
{
}
/// <summary>
/// 日志上下文数据
/// </summary>
public IDictionary<object, object> Properties { get; set; }
}

View File

@@ -0,0 +1,125 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Logging;
/// <summary>
/// 日志结构化消息
/// </summary>
public struct LogMessage
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logName">记录器类别名称</param>
/// <param name="logLevel">日志级别</param>
/// <param name="eventId">事件 Id</param>
/// <param name="message">日志消息</param>
/// <param name="exception">异常对象</param>
/// <param name="context">日志上下文</param>
/// <param name="state">当前状态值</param>
/// <param name="logDateTime">日志记录时间</param>
/// <param name="threadId">线程 Id</param>
/// <param name="useUtcTimestamp">是否使用 UTC 时间戳</param>
/// <param name="traceId">请求/跟踪 Id</param>
internal LogMessage(string logName
, LogLevel logLevel
, EventId eventId
, string message
, Exception exception
, LogContext context
, object state
, DateTime logDateTime
, int threadId
, bool useUtcTimestamp
, string traceId)
{
LogName = logName;
Message = message;
LogLevel = logLevel;
EventId = eventId;
Exception = exception;
Context = context;
State = state;
LogDateTime = logDateTime;
ThreadId = threadId;
UseUtcTimestamp = useUtcTimestamp;
TraceId = traceId;
}
/// <summary>
/// 记录器类别名称
/// </summary>
public string LogName { get; }
/// <summary>
/// 日志级别
/// </summary>
public LogLevel LogLevel { get; }
/// <summary>
/// 事件 Id
/// </summary>
public EventId EventId { get; }
/// <summary>
/// 日志消息
/// </summary>
public string Message { get; internal set; }
/// <summary>
/// 异常对象
/// </summary>
public Exception Exception { get; }
/// <summary>
/// 当前状态值
/// </summary>
/// <remarks>可以是任意类型</remarks>
public object State { get; }
/// <summary>
/// 日志记录时间
/// </summary>
public DateTime LogDateTime { get; }
/// <summary>
/// 线程 Id
/// </summary>
public int ThreadId { get; }
/// <summary>
/// 是否使用 UTC 时间戳
/// </summary>
public bool UseUtcTimestamp { get; }
/// <summary>
/// 请求/跟踪 Id
/// </summary>
public string TraceId { get; }
/// <summary>
/// 日志上下文
/// </summary>
public LogContext Context { get; set; }
/// <summary>
/// 重写默认输出
/// </summary>
/// <returns><see cref="string"/></returns>
public override readonly string ToString()
{
return Penetrates.OutputStandardMessage(this);
}
}

View File

@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
// 版权归百小僧及百签科技(广东)有限公司所有。
namespace ThingsGateway.Logging;
/// <summary>
/// LoggingMonitor 序列化属性命名规则选项
/// </summary>
public enum ContractResolverTypes
{
/// <summary>
/// CamelCase 小驼峰
/// </summary>
/// <remarks>默认值</remarks>
CamelCase = 0,
/// <summary>
/// 保持原样
/// </summary>
Default = 1
}

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