mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-22 11:33:07 +08:00
强推
This commit is contained in:
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal 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
363
.gitignore
vendored
Normal 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
202
LICENSE
Normal 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
67
README.md
Normal 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)
|
||||
|
||||
|
||||
|
13
src/Delete .vs .bat
Normal file
13
src/Delete .vs .bat
Normal 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
|
19
src/Delete Bin And Obj.bat
Normal file
19
src/Delete Bin And Obj.bat
Normal 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
24
src/Directory.Build.props
Normal 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>
|
161
src/ThingsGateway.Admin.Application/Aop/OperDescAttribute.cs
Normal file
161
src/ThingsGateway.Admin.Application/Aop/OperDescAttribute.cs
Normal 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);
|
||||
}
|
||||
}
|
@@ -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
|
||||
{
|
||||
}
|
@@ -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
|
||||
{
|
||||
}
|
@@ -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
|
||||
{
|
||||
}
|
@@ -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
|
||||
{
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
56
src/ThingsGateway.Admin.Application/Const/CacheConst.cs
Normal file
56
src/ThingsGateway.Admin.Application/Const/CacheConst.cs
Normal 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 登录错误次数
|
||||
}
|
||||
}
|
37
src/ThingsGateway.Admin.Application/Const/ClaimConst.cs
Normal file
37
src/ThingsGateway.Admin.Application/Const/ClaimConst.cs
Normal 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";
|
||||
}
|
37
src/ThingsGateway.Admin.Application/Const/ResourceConst.cs
Normal file
37
src/ThingsGateway.Admin.Application/Const/ResourceConst.cs
Normal 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";
|
||||
}
|
32
src/ThingsGateway.Admin.Application/Const/RoleConst.cs
Normal file
32
src/ThingsGateway.Admin.Application/Const/RoleConst.cs
Normal 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";
|
||||
}
|
32
src/ThingsGateway.Admin.Application/Const/SqlSugarConst.cs
Normal file
32
src/ThingsGateway.Admin.Application/Const/SqlSugarConst.cs
Normal 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";
|
||||
}
|
@@ -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
|
||||
}));
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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
|
||||
{
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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
|
||||
}));
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
117
src/ThingsGateway.Admin.Application/Entity/BaseEntity.cs
Normal file
117
src/ThingsGateway.Admin.Application/Entity/BaseEntity.cs
Normal 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; }
|
||||
}
|
59
src/ThingsGateway.Admin.Application/Entity/SysDict.cs
Normal file
59
src/ThingsGateway.Admin.Application/Entity/SysDict.cs
Normal 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; }
|
||||
}
|
136
src/ThingsGateway.Admin.Application/Entity/SysOperateLog.cs
Normal file
136
src/ThingsGateway.Admin.Application/Entity/SysOperateLog.cs
Normal 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; }
|
||||
}
|
39
src/ThingsGateway.Admin.Application/Entity/SysRelation.cs
Normal file
39
src/ThingsGateway.Admin.Application/Entity/SysRelation.cs
Normal 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; }
|
||||
}
|
101
src/ThingsGateway.Admin.Application/Entity/SysResource.cs
Normal file
101
src/ThingsGateway.Admin.Application/Entity/SysResource.cs
Normal 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; }
|
||||
}
|
52
src/ThingsGateway.Admin.Application/Entity/SysRole.cs
Normal file
52
src/ThingsGateway.Admin.Application/Entity/SysRole.cs
Normal 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();
|
||||
}
|
||||
}
|
192
src/ThingsGateway.Admin.Application/Entity/SysUser.cs
Normal file
192
src/ThingsGateway.Admin.Application/Entity/SysUser.cs
Normal 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; }
|
||||
}
|
@@ -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,
|
||||
}
|
27
src/ThingsGateway.Admin.Application/Enum/DictTypeEnum.cs
Normal file
27
src/ThingsGateway.Admin.Application/Enum/DictTypeEnum.cs
Normal 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,
|
||||
}
|
19
src/ThingsGateway.Admin.Application/Enum/LogCateGoryEnum.cs
Normal file
19
src/ThingsGateway.Admin.Application/Enum/LogCateGoryEnum.cs
Normal 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
|
||||
}
|
@@ -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,
|
||||
}
|
@@ -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,
|
||||
}
|
17
src/ThingsGateway.Admin.Application/Enum/RoleCategoryEnum.cs
Normal file
17
src/ThingsGateway.Admin.Application/Enum/RoleCategoryEnum.cs
Normal 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,
|
||||
}
|
19
src/ThingsGateway.Admin.Application/Enum/TargetEnum.cs
Normal file
19
src/ThingsGateway.Admin.Application/Enum/TargetEnum.cs
Normal 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
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
3
src/ThingsGateway.Admin.Application/FodyWeavers.xml
Normal file
3
src/ThingsGateway.Admin.Application/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Rougamo />
|
||||
</Weavers>
|
@@ -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
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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 { }
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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
Reference in New Issue
Block a user