mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-22 19:43:07 +08:00
Compare commits
118 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0174f7c6f2 | ||
![]() |
df9e7d6ff1 | ||
![]() |
b40ca920d3 | ||
![]() |
5a4b0a0e93 | ||
![]() |
a879edd68b | ||
![]() |
62e0a6ee9d | ||
![]() |
765e5564d4 | ||
![]() |
10eecac19b | ||
![]() |
59241b8faa | ||
![]() |
52b3097f04 | ||
![]() |
d922296b70 | ||
![]() |
aec91da28b | ||
![]() |
013ff394be | ||
![]() |
081e07473d | ||
![]() |
d33d900592 | ||
![]() |
29365c4ef9 | ||
![]() |
17a6189089 | ||
![]() |
003b8a3763 | ||
![]() |
1c7f8b5cab | ||
![]() |
b7ff9ffca2 | ||
![]() |
9bba9bda76 | ||
![]() |
ec2fcc75d3 | ||
![]() |
57a4038577 | ||
![]() |
b78a76e60f | ||
![]() |
28bd751d44 | ||
![]() |
dcba7b9810 | ||
![]() |
7f1a741ce6 | ||
![]() |
ca2b17d433 | ||
![]() |
af589eac10 | ||
![]() |
573670f1f5 | ||
![]() |
f3ec85a03d | ||
![]() |
c94308454f | ||
![]() |
c9c2b2b69d | ||
![]() |
40574b776f | ||
![]() |
f426c1533d | ||
![]() |
4a3b5163f1 | ||
![]() |
3a0946d357 | ||
![]() |
9dd21c644c | ||
![]() |
6713190a23 | ||
![]() |
9f24533425 | ||
![]() |
ed1d9374eb | ||
![]() |
f14d27129e | ||
![]() |
5b04f02fbe | ||
![]() |
227080e332 | ||
![]() |
1e87482a49 | ||
![]() |
054d31c3ea | ||
![]() |
3a068a7b03 | ||
![]() |
b12e923c99 | ||
![]() |
ab33eed8d3 | ||
![]() |
d930a9a8eb | ||
![]() |
af381fce12 | ||
![]() |
b64ac0539e | ||
![]() |
541c60b363 | ||
![]() |
824e95f7cb | ||
![]() |
38f7850196 | ||
![]() |
bef9de88e2 | ||
![]() |
48cd5e7c7f | ||
![]() |
3b44fda51c | ||
![]() |
dbfc9a5bb4 | ||
![]() |
1b758aa41a | ||
![]() |
43bdc70899 | ||
![]() |
fadda000a6 | ||
![]() |
45a8c91a5a | ||
![]() |
8e938f18be | ||
![]() |
ab1b364c54 | ||
![]() |
5ec65b2fb0 | ||
![]() |
926eced724 | ||
![]() |
f7f8802272 | ||
![]() |
c6910dff02 | ||
![]() |
ad299d0dbb | ||
![]() |
8b124d1050 | ||
![]() |
ff41080dbd | ||
![]() |
0e28606e3d | ||
![]() |
6a025ceee5 | ||
![]() |
6b2e53d6dc | ||
![]() |
b989aa5561 | ||
![]() |
f5b0b7ebd2 | ||
![]() |
16881ae076 | ||
![]() |
af04112656 | ||
![]() |
a2863112dc | ||
![]() |
f531e4dfc5 | ||
![]() |
8db9b32ba7 | ||
![]() |
dd5691cbef | ||
![]() |
de48b32af3 | ||
![]() |
600b5042a1 | ||
![]() |
aac77029da | ||
![]() |
e50205f557 | ||
![]() |
e227411d1f | ||
![]() |
2de0ed793f | ||
![]() |
cb0276f273 | ||
![]() |
562b3f17c9 | ||
![]() |
0f78f81c1c | ||
![]() |
6594937d0a | ||
![]() |
64bc6084be | ||
![]() |
20434d5bd2 | ||
![]() |
9ecc9380e6 | ||
![]() |
a57c55080b | ||
![]() |
b8ca06c6be | ||
![]() |
5aff6461a1 | ||
![]() |
6cf53fefec | ||
![]() |
45132f3503 | ||
![]() |
f1e78a0e8a | ||
![]() |
0bf28ec275 | ||
![]() |
41f8412c97 | ||
![]() |
c535974362 | ||
![]() |
1860c5f215 | ||
![]() |
6d778b2d39 | ||
![]() |
f48b99c259 | ||
![]() |
3c73b93051 | ||
![]() |
98f3f2d519 | ||
![]() |
b76b4e8d68 | ||
![]() |
07285a7c61 | ||
![]() |
03c0dfef37 | ||
![]() |
6ef6929c35 | ||
![]() |
e3c0c173f0 | ||
![]() |
7d64e058d4 | ||
![]() |
e97ee9b64b | ||
![]() |
6a03e39eeb |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -365,4 +365,5 @@ FodyWeavers.xsd
|
|||||||
/src/*Pro*/
|
/src/*Pro*/
|
||||||
/src/*Pro*
|
/src/*Pro*
|
||||||
/src/*pro*
|
/src/*pro*
|
||||||
/src/*pro*/
|
/src/*pro*/
|
||||||
|
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json
|
||||||
|
@@ -126,35 +126,8 @@ dotnet_style_qualification_for_property = false:silent
|
|||||||
dotnet_style_qualification_for_method = false:silent
|
dotnet_style_qualification_for_method = false:silent
|
||||||
dotnet_style_qualification_for_event = false:silent
|
dotnet_style_qualification_for_event = false:silent
|
||||||
|
|
||||||
|
dotnet_diagnostic.RCS1146.severity = warning
|
||||||
dotnet_diagnostic.CA2208.severity = none
|
dotnet_diagnostic.RCS1059.severity = none
|
||||||
dotnet_diagnostic.CA2008.severity = none
|
dotnet_diagnostic.RCS1138.severity = suggestion
|
||||||
dotnet_diagnostic.CA1812.severity = none
|
|
||||||
dotnet_diagnostic.CA1508.severity = none
|
|
||||||
dotnet_diagnostic.CA1512.severity = none
|
|
||||||
dotnet_diagnostic.CA1513.severity = none
|
|
||||||
dotnet_diagnostic.CA1810.severity = none
|
|
||||||
dotnet_diagnostic.CA1814.severity = none
|
|
||||||
dotnet_diagnostic.CA1815.severity = none
|
|
||||||
dotnet_diagnostic.CA1835.severity = none
|
|
||||||
dotnet_diagnostic.CA1819.severity = none
|
|
||||||
dotnet_diagnostic.CA1823.severity = none
|
|
||||||
dotnet_diagnostic.CA2002.severity = none
|
|
||||||
dotnet_diagnostic.CA5350.severity = none
|
|
||||||
dotnet_diagnostic.CA5351.severity = none
|
|
||||||
dotnet_diagnostic.CA5358.severity = none
|
|
||||||
dotnet_diagnostic.CA5384.severity = none
|
|
||||||
dotnet_diagnostic.CA5392.severity = none
|
|
||||||
dotnet_diagnostic.CA1805.severity = none
|
|
||||||
dotnet_diagnostic.CA1851.severity = none
|
|
||||||
dotnet_diagnostic.CA1510.severity = none
|
|
||||||
dotnet_diagnostic.CA5401.severity = none
|
|
||||||
dotnet_diagnostic.CA2022.severity = none
|
|
||||||
dotnet_diagnostic.CA1848.severity = none
|
|
||||||
dotnet_diagnostic.CA2000.severity = none
|
|
||||||
dotnet_diagnostic.CA5394.severity = none
|
|
||||||
dotnet_diagnostic.CA3003.severity = none
|
|
||||||
dotnet_diagnostic.CA1515.severity = none
|
|
||||||
dotnet_diagnostic.CA1849.severity = none
|
|
||||||
|
|
||||||
dotnet_code_quality.CA1822.api_surface = private, internal
|
dotnet_code_quality.CA1822.api_surface = private, internal
|
@@ -0,0 +1,6 @@
|
|||||||
|
## Release 1.0
|
||||||
|
|
||||||
|
### New Rules
|
||||||
|
Rule ID | Category | Severity | Notes
|
||||||
|
--------|----------|----------|--------------------
|
||||||
|
TG0001 | Conflict | Error | SetParametersAsyncGenerator
|
@@ -0,0 +1,32 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="..\..\PackNuget.props" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
|
||||||
|
<Version>$(SourceGeneratorVersion)</Version>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<SignAssembly>false</SignAssembly>
|
||||||
|
|
||||||
|
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||||
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
<!-- 避免 DLL 被打包到 lib/ -->
|
||||||
|
<EnableSourceGenerator>true</EnableSourceGenerator>
|
||||||
|
<!-- 可选 -->
|
||||||
|
|
||||||
|
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="$(OutputPath)\$(TargetFileName)" Pack="true"
|
||||||
|
PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@@ -0,0 +1,9 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||||
|
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||||
|
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||||
|
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||||
|
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||||
|
// 使用文档:https://thingsgateway.cn/
|
||||||
|
// QQ群:605534569
|
||||||
|
//------------------------------------------------------------------------------
|
@@ -0,0 +1,534 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
public partial class SetParametersAsyncGenerator : ISourceGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
private string m_DoNotGenerateSetParametersAsyncAttribute = """
|
||||||
|
|
||||||
|
using System;
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||||
|
internal sealed class DoNotGenerateSetParametersAsyncAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
;
|
||||||
|
private string m_GenerateSetParametersAsyncAttribute = """
|
||||||
|
|
||||||
|
using System;
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||||
|
internal sealed class GenerateSetParametersAsyncAttribute : Attribute
|
||||||
|
{
|
||||||
|
public bool RequireExactMatch { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
;
|
||||||
|
private string m_GlobalGenerateSetParametersAsyncAttribute = """
|
||||||
|
|
||||||
|
using System;
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
|
||||||
|
internal sealed class GlobalGenerateSetParametersAsyncAttribute : Attribute
|
||||||
|
{
|
||||||
|
public bool Enable { get; }
|
||||||
|
|
||||||
|
public GlobalGenerateSetParametersAsyncAttribute(bool enable = true)
|
||||||
|
{
|
||||||
|
Enable = enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
;
|
||||||
|
|
||||||
|
private static readonly DiagnosticDescriptor ParameterNameConflict = new DiagnosticDescriptor(
|
||||||
|
id: "TG0001",
|
||||||
|
title: "Parameter name conflict",
|
||||||
|
messageFormat: "Parameter names are case insensitive. {0} conflicts with {1}.",
|
||||||
|
category: "Conflict",
|
||||||
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: "Parameter names must be case insensitive to be usable in routes. Rename the parameter to not be in conflict with other parameters.");
|
||||||
|
|
||||||
|
public void Initialize(GeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
context.RegisterForPostInitialization(a =>
|
||||||
|
{
|
||||||
|
a.AddSource(nameof(m_DoNotGenerateSetParametersAsyncAttribute), m_DoNotGenerateSetParametersAsyncAttribute);
|
||||||
|
a.AddSource(nameof(m_GenerateSetParametersAsyncAttribute), m_GenerateSetParametersAsyncAttribute);
|
||||||
|
a.AddSource(nameof(m_GlobalGenerateSetParametersAsyncAttribute), m_GlobalGenerateSetParametersAsyncAttribute);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register a syntax receiver that will be created for each generation pass
|
||||||
|
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
// https://github.com/dotnet/AspNetCore.Docs/blob/1e199f340780f407a685695e6c4d953f173fa891/aspnetcore/blazor/webassembly-performance-best-practices.md#implement-setparametersasync-manually
|
||||||
|
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidate_classes = GetCandidateClasses(receiver, context);
|
||||||
|
|
||||||
|
foreach (var class_symbol in candidate_classes.Distinct(SymbolEqualityComparer.Default).Cast<INamedTypeSymbol>())
|
||||||
|
{
|
||||||
|
GenerateSetParametersAsyncMethod(context, class_symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GenerateSetParametersAsyncMethod(GeneratorExecutionContext context, INamedTypeSymbol class_symbol)
|
||||||
|
{
|
||||||
|
var force_exact_match = class_symbol.GetAttributes().Any(a => a.NamedArguments.Any(na => na.Key == "RequireExactMatch" && na.Value.Value is bool v && v));
|
||||||
|
var namespaceName = class_symbol.ContainingNamespace.ToDisplayString();
|
||||||
|
var type_kind = class_symbol.TypeKind switch { TypeKind.Class => "class", TypeKind.Interface => "interface", _ => "struct" };
|
||||||
|
var type_parameters = string.Join(", ", class_symbol.TypeArguments.Select(t => t.Name));
|
||||||
|
type_parameters = string.IsNullOrEmpty(type_parameters) ? type_parameters : "<" + type_parameters + ">";
|
||||||
|
context.AddCode(class_symbol.ToDisplayString() + "_override.cs", $@"
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#pragma warning disable CA2007
|
||||||
|
#pragma warning disable CS0162
|
||||||
|
#pragma warning disable CS8632
|
||||||
|
namespace {namespaceName}
|
||||||
|
{{
|
||||||
|
public partial class {class_symbol.Name}{type_parameters}
|
||||||
|
{{
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// </summary>
|
||||||
|
public override Task SetParametersAsync(ParameterView parameters)
|
||||||
|
{{
|
||||||
|
Dictionary<string,object?> parameterValues = new();
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
{{
|
||||||
|
if(BlazorImplementation__WriteSingleParameter(parameter.Name, parameter.Value)==false)
|
||||||
|
{{
|
||||||
|
// 如果没有处理参数,则添加到参数列表中
|
||||||
|
parameterValues.Add(parameter.Name, parameter.Value);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
if(parameterValues.Count > 0)
|
||||||
|
{{
|
||||||
|
parameters.SetParameterProperties(this);
|
||||||
|
}}
|
||||||
|
if (!_initialized)
|
||||||
|
{{
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
return RunInitAndSetParametersAsync();
|
||||||
|
}}
|
||||||
|
else
|
||||||
|
{{
|
||||||
|
return CallOnParametersSetAsync();
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
// We do not want the debugger to consider NavigationExceptions caught by this method as user-unhandled.
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
[System.Diagnostics.DebuggerDisableUserUnhandledExceptions]
|
||||||
|
#endif
|
||||||
|
private async Task RunInitAndSetParametersAsync()
|
||||||
|
{{
|
||||||
|
Task task;
|
||||||
|
|
||||||
|
try
|
||||||
|
{{
|
||||||
|
OnInitialized();
|
||||||
|
task = OnInitializedAsync();
|
||||||
|
}}
|
||||||
|
catch (Exception ex) when (ex is not NavigationException)
|
||||||
|
{{
|
||||||
|
throw;
|
||||||
|
}}
|
||||||
|
|
||||||
|
if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
|
||||||
|
{{
|
||||||
|
// Call state has changed here so that we render after the sync part of OnInitAsync has run
|
||||||
|
// and wait for it to finish before we continue. If no async work has been done yet, we want
|
||||||
|
// to defer calling StateHasChanged up until the first bit of async code happens or until
|
||||||
|
// the end. Additionally, we want to avoid calling StateHasChanged if no
|
||||||
|
// async work is to be performed.
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{{
|
||||||
|
await task;
|
||||||
|
}}
|
||||||
|
catch // avoiding exception filters for AOT runtime support
|
||||||
|
{{
|
||||||
|
// Ignore exceptions from task cancellations.
|
||||||
|
// Awaiting a canceled task may produce either an OperationCanceledException (if produced as a consequence of
|
||||||
|
// CancellationToken.ThrowIfCancellationRequested()) or a TaskCanceledException (produced as a consequence of awaiting Task.FromCanceled).
|
||||||
|
// It's much easier to check the state of the Task (i.e. Task.IsCanceled) rather than catch two distinct exceptions.
|
||||||
|
if (!task.IsCanceled)
|
||||||
|
{{
|
||||||
|
throw;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
|
||||||
|
}}
|
||||||
|
|
||||||
|
await CallOnParametersSetAsync();
|
||||||
|
}}
|
||||||
|
|
||||||
|
// We do not want the debugger to consider NavigationExceptions caught by this method as user-unhandled.
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
[System.Diagnostics.DebuggerDisableUserUnhandledExceptions]
|
||||||
|
#endif
|
||||||
|
private Task CallOnParametersSetAsync()
|
||||||
|
{{
|
||||||
|
Task task;
|
||||||
|
|
||||||
|
try
|
||||||
|
{{
|
||||||
|
OnParametersSet();
|
||||||
|
task = OnParametersSetAsync();
|
||||||
|
}}
|
||||||
|
catch (Exception ex) when (ex is not NavigationException)
|
||||||
|
{{
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
System.Diagnostics.Debugger.BreakForUserUnhandledException(ex);
|
||||||
|
#endif
|
||||||
|
throw;
|
||||||
|
}}
|
||||||
|
|
||||||
|
// If no async work is to be performed, i.e. the task has already ran to completion
|
||||||
|
// or was canceled by the time we got to inspect it, avoid going async and re-invoking
|
||||||
|
// StateHasChanged at the culmination of the async work.
|
||||||
|
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
|
||||||
|
task.Status != TaskStatus.Canceled;
|
||||||
|
|
||||||
|
// We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
|
||||||
|
// the synchronous part of OnParametersSetAsync has run.
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
return shouldAwaitTask ?
|
||||||
|
CallStateHasChangedOnAsyncCompletion(task) :
|
||||||
|
Task.CompletedTask;
|
||||||
|
}}
|
||||||
|
|
||||||
|
// We do not want the debugger to stop more than once per user-unhandled exception.
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
[System.Diagnostics.DebuggerDisableUserUnhandledExceptions]
|
||||||
|
#endif
|
||||||
|
private async Task CallStateHasChangedOnAsyncCompletion(Task task)
|
||||||
|
{{
|
||||||
|
try
|
||||||
|
{{
|
||||||
|
await task;
|
||||||
|
}}
|
||||||
|
catch // avoiding exception filters for AOT runtime support
|
||||||
|
{{
|
||||||
|
// Ignore exceptions from task cancellations, but don't bother issuing a state change.
|
||||||
|
if (task.IsCanceled)
|
||||||
|
{{
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}}
|
||||||
|
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
#pragma warning restore CS8632
|
||||||
|
#pragma warning restore CS0162
|
||||||
|
#pragma warning restore CA2007
|
||||||
|
");
|
||||||
|
var bases = class_symbol.GetTypeHierarchy().Where(t => !SymbolEqualityComparer.Default.Equals(t, class_symbol));
|
||||||
|
var members = class_symbol.GetMembers() // members of the type itself
|
||||||
|
.Concat(bases.SelectMany(t => t.GetMembers().Where(m => m.DeclaredAccessibility != Accessibility.Private))) // plus accessible members of any base
|
||||||
|
.Distinct(SymbolEqualityComparer.Default);
|
||||||
|
var property_symbols = members.OfType<IPropertySymbol>();
|
||||||
|
var writable_property_symbols = property_symbols.Where(ps =>
|
||||||
|
!ps.IsReadOnly || ps.GetAttributes().Any(a =>
|
||||||
|
a.AttributeClass?.Name is "CascadingParameter" or "CascadingParameterAttribute")
|
||||||
|
);
|
||||||
|
|
||||||
|
var parameter_symbols = writable_property_symbols
|
||||||
|
.Where(ps => ps.GetAttributes().Any(a => false
|
||||||
|
|| a.AttributeClass.Name == "Parameter"
|
||||||
|
|| a.AttributeClass.Name == "ParameterAttribute"
|
||||||
|
|| a.AttributeClass.Name == "CascadingParameter"
|
||||||
|
|| a.AttributeClass.Name == "CascadingParameterAttribute"
|
||||||
|
|
||||||
|
));
|
||||||
|
var name_conflicts = parameter_symbols.GroupBy(ps => ps.Name.ToLowerInvariant()).Where(g => g.Count() > 1);
|
||||||
|
foreach (var conflict in name_conflicts)
|
||||||
|
{
|
||||||
|
var key = conflict.Key;
|
||||||
|
var conflicting_parameters = conflict.ToList();
|
||||||
|
foreach (var parameter in conflicting_parameters)
|
||||||
|
{
|
||||||
|
var this_name = parameter.Name;
|
||||||
|
var conflicting_name = conflicting_parameters.Select(p => p.Name).FirstOrDefault(n => n != this_name);
|
||||||
|
foreach (var location in parameter.Locations)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(ParameterNameConflict, location, this_name, conflicting_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var all = parameter_symbols.ToList();
|
||||||
|
var catch_all_parameter = parameter_symbols.FirstOrDefault(p =>
|
||||||
|
{
|
||||||
|
var parameter_attr = p.GetAttributes().FirstOrDefault(a => a.AttributeClass!.Name.StartsWith("Parameter"));
|
||||||
|
return parameter_attr?.NamedArguments.Any(n => n.Key == "CaptureUnmatchedValues" && n.Value.Value is bool v && v) == true;
|
||||||
|
});
|
||||||
|
var lower_case_match_cases = parameter_symbols.Except(new[] { catch_all_parameter }).Select(p => $"case \"{p.Name.ToLowerInvariant()}\": this.{p.Name} = ({p.Type.ToDisplayString()}) value; break;");
|
||||||
|
var lower_case_match_default = catch_all_parameter == null ? @"default: {return false;}" : $@"
|
||||||
|
default:
|
||||||
|
{{
|
||||||
|
this.{catch_all_parameter.Name} ??= new System.Collections.Generic.Dictionary<string, object>();
|
||||||
|
var writable_dict = this.{catch_all_parameter.Name};
|
||||||
|
if (!writable_dict.TryAdd(name, value))
|
||||||
|
{{
|
||||||
|
writable_dict[name] = value;
|
||||||
|
}}
|
||||||
|
break;
|
||||||
|
}}";
|
||||||
|
|
||||||
|
var exact_match_cases = parameter_symbols.Except(new[] { catch_all_parameter }).Select(p => $"case \"{p!.Name}\": this.{p.Name} = ({p.Type.ToDisplayString()}) value; break;");
|
||||||
|
string exact_match_default;
|
||||||
|
if (force_exact_match)
|
||||||
|
{
|
||||||
|
if (catch_all_parameter == null) // exact matches are forced, and we do not have a catch-all parameter, therefore we need to throw on unmatched parameter
|
||||||
|
{
|
||||||
|
exact_match_default = @"default: { return false;";
|
||||||
|
}
|
||||||
|
else // exact matches are forced, and we DO have a catch-all parameter, therefore we simply add that unmatched parameter to the dictionary
|
||||||
|
{
|
||||||
|
exact_match_default = $@"
|
||||||
|
default:
|
||||||
|
{{
|
||||||
|
this.{catch_all_parameter.Name} ??= new System.Collections.Generic.Dictionary<string, object>();
|
||||||
|
var writable_dict = this.{catch_all_parameter.Name};
|
||||||
|
if (!writable_dict.TryAdd(name, value))
|
||||||
|
{{
|
||||||
|
writable_dict[name] = value;
|
||||||
|
}}
|
||||||
|
break;
|
||||||
|
}}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// exact matches are not forced, so if there is no exact match, we fall back to compare it in lower case
|
||||||
|
exact_match_default = $@"
|
||||||
|
default:
|
||||||
|
{{
|
||||||
|
switch (name.ToLowerInvariant())
|
||||||
|
{{
|
||||||
|
{string.Join("\n", lower_case_match_cases)}
|
||||||
|
{lower_case_match_default}
|
||||||
|
}}
|
||||||
|
break;
|
||||||
|
}}
|
||||||
|
";
|
||||||
|
}
|
||||||
|
context.AddCode(class_symbol.ToDisplayString() + "_implementation.cs", $@"
|
||||||
|
using System;
|
||||||
|
|
||||||
|
#pragma warning disable CS0162
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
#pragma warning disable CS8632
|
||||||
|
namespace {namespaceName}
|
||||||
|
{{
|
||||||
|
public partial class {class_symbol.Name}{type_parameters}
|
||||||
|
{{
|
||||||
|
|
||||||
|
private bool BlazorImplementation__WriteSingleParameter(string name, object value)
|
||||||
|
{{
|
||||||
|
if(name != ""Body"")
|
||||||
|
{{
|
||||||
|
|
||||||
|
switch (name)
|
||||||
|
{{
|
||||||
|
{string.Join("\n", exact_match_cases)}
|
||||||
|
{exact_match_default}
|
||||||
|
}}
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
#pragma warning restore CS8632
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
#pragma warning restore CS0162");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasUserDefinedSetParametersAsync(INamedTypeSymbol classSymbol)
|
||||||
|
{
|
||||||
|
return classSymbol
|
||||||
|
.GetMembers("SetParametersAsync")
|
||||||
|
.OfType<IMethodSymbol>()
|
||||||
|
.Any(m =>
|
||||||
|
m.Parameters.Length == 1 &&
|
||||||
|
m.Parameters[0].Type.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterView" &&
|
||||||
|
m.DeclaredAccessibility == Accessibility.Public &&
|
||||||
|
!m.IsStatic);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool IsPartial(INamedTypeSymbol symbol)
|
||||||
|
{
|
||||||
|
return symbol.DeclaringSyntaxReferences
|
||||||
|
.Select(r => r.GetSyntax())
|
||||||
|
.OfType<ClassDeclarationSyntax>()
|
||||||
|
.Any(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)));
|
||||||
|
}
|
||||||
|
private static bool IsComponent(ClassDeclarationSyntax classDeclarationSyntax, INamedTypeSymbol symbol, Compilation compilation)
|
||||||
|
{
|
||||||
|
if (HasUserDefinedSetParametersAsync(symbol))
|
||||||
|
{
|
||||||
|
// 用户自己写了方法,不生成
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsPartial(symbol))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classDeclarationSyntax.SyntaxTree.FilePath.EndsWith(".razor") || classDeclarationSyntax.SyntaxTree.FilePath.EndsWith(".razor.cs"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var iComponent = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.IComponent");
|
||||||
|
var componentBase = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.ComponentBase");
|
||||||
|
if (iComponent == null || componentBase == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(symbol, iComponent))
|
||||||
|
return true;
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(symbol, componentBase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerate methods with at least one Group attribute
|
||||||
|
/// </summary>
|
||||||
|
private static IEnumerable<INamedTypeSymbol> GetCandidateClasses(SyntaxReceiver receiver, GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
var compilation = context.Compilation;
|
||||||
|
var positiveAttributeSymbol = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.GenerateSetParametersAsyncAttribute");
|
||||||
|
var negativeAttributeSymbol = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.DoNotGenerateSetParametersAsyncAttribute");
|
||||||
|
|
||||||
|
// loop over the candidate methods, and keep the ones that are actually annotated
|
||||||
|
|
||||||
|
// 找特性
|
||||||
|
var assemblyAttributes = compilation.Assembly.GetAttributes();
|
||||||
|
|
||||||
|
var enableAttr = assemblyAttributes.FirstOrDefault(attr =>
|
||||||
|
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.GlobalGenerateSetParametersAsyncAttribute");
|
||||||
|
|
||||||
|
var globalEnable = false;
|
||||||
|
if (enableAttr != null)
|
||||||
|
{
|
||||||
|
var arg = enableAttr.ConstructorArguments.FirstOrDefault();
|
||||||
|
if (arg.Value is bool b)
|
||||||
|
globalEnable = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ClassDeclarationSyntax class_declaration in receiver.CandidateClasses)
|
||||||
|
{
|
||||||
|
var model = compilation.GetSemanticModel(class_declaration.SyntaxTree);
|
||||||
|
var class_symbol = model.GetDeclaredSymbol(class_declaration);
|
||||||
|
if (class_symbol is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (class_symbol.Name == "_Imports")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否拒绝生成
|
||||||
|
var hasNegative = class_symbol.GetAttributes().Any(ad =>
|
||||||
|
ad.AttributeClass?.Equals(negativeAttributeSymbol, SymbolEqualityComparer.Default) == true);
|
||||||
|
|
||||||
|
if (hasNegative)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
|
||||||
|
if (IsComponent(class_declaration, class_symbol, compilation))
|
||||||
|
{
|
||||||
|
if (globalEnable)
|
||||||
|
{
|
||||||
|
yield return class_symbol;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 必须显式标注 Positive Attribute
|
||||||
|
var hasPositive = class_symbol.GetAttributes().Any(ad =>
|
||||||
|
ad.AttributeClass?.Equals(positiveAttributeSymbol, SymbolEqualityComparer.Default) == true);
|
||||||
|
|
||||||
|
if (hasPositive)
|
||||||
|
yield return class_symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Created on demand before each generation pass
|
||||||
|
/// </summary>
|
||||||
|
internal class SyntaxReceiver : ISyntaxReceiver
|
||||||
|
{
|
||||||
|
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
|
||||||
|
/// </summary>
|
||||||
|
public void OnVisitSyntaxNode(SyntaxNode syntax_node)
|
||||||
|
{
|
||||||
|
// any class with at least one attribute is a candidate for property generation
|
||||||
|
if (syntax_node is ClassDeclarationSyntax classDeclarationSyntax)
|
||||||
|
{
|
||||||
|
CandidateClasses.Add(classDeclarationSyntax);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
internal static class SourceGeneratorContextExtension
|
||||||
|
{
|
||||||
|
public static void AddCode(this GeneratorExecutionContext context, string hint_name, string code)
|
||||||
|
{
|
||||||
|
context.AddSource(hint_name.Replace("<", "_").Replace(">", "_"), SourceText.From(code, Encoding.UTF8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
internal static class StringExtension
|
||||||
|
{
|
||||||
|
public static string NormalizeWhitespace(this string code)
|
||||||
|
{
|
||||||
|
return CSharpSyntaxTree.ParseText(code).GetRoot().NormalizeWhitespace().ToFullString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public static class TypeSymbolExtension
|
||||||
|
{
|
||||||
|
public static IEnumerable<INamedTypeSymbol> GetTypeHierarchy(this INamedTypeSymbol symbol)
|
||||||
|
{
|
||||||
|
yield return symbol;
|
||||||
|
if (symbol.BaseType != null)
|
||||||
|
{
|
||||||
|
foreach (var type in GetTypeHierarchy(symbol.BaseType))
|
||||||
|
{
|
||||||
|
yield return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
param($installPath, $toolsPath, $package, $project)
|
||||||
|
|
||||||
|
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
|
||||||
|
|
||||||
|
foreach($analyzersPath in $analyzersPaths)
|
||||||
|
{
|
||||||
|
# Install the language agnostic analyzers.
|
||||||
|
if (Test-Path $analyzersPath)
|
||||||
|
{
|
||||||
|
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
|
||||||
|
{
|
||||||
|
if($project.Object.AnalyzerReferences)
|
||||||
|
{
|
||||||
|
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# $project.Type gives the language name like (C# or VB.NET)
|
||||||
|
$languageFolder = ""
|
||||||
|
if($project.Type -eq "C#")
|
||||||
|
{
|
||||||
|
$languageFolder = "cs"
|
||||||
|
}
|
||||||
|
if($project.Type -eq "VB.NET")
|
||||||
|
{
|
||||||
|
$languageFolder = "vb"
|
||||||
|
}
|
||||||
|
if($languageFolder -eq "")
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($analyzersPath in $analyzersPaths)
|
||||||
|
{
|
||||||
|
# Install language specific analyzers.
|
||||||
|
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
||||||
|
if (Test-Path $languageAnalyzersPath)
|
||||||
|
{
|
||||||
|
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
|
||||||
|
{
|
||||||
|
if($project.Object.AnalyzerReferences)
|
||||||
|
{
|
||||||
|
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
param($installPath, $toolsPath, $package, $project)
|
||||||
|
|
||||||
|
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
|
||||||
|
|
||||||
|
foreach($analyzersPath in $analyzersPaths)
|
||||||
|
{
|
||||||
|
# Uninstall the language agnostic analyzers.
|
||||||
|
if (Test-Path $analyzersPath)
|
||||||
|
{
|
||||||
|
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
|
||||||
|
{
|
||||||
|
if($project.Object.AnalyzerReferences)
|
||||||
|
{
|
||||||
|
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# $project.Type gives the language name like (C# or VB.NET)
|
||||||
|
$languageFolder = ""
|
||||||
|
if($project.Type -eq "C#")
|
||||||
|
{
|
||||||
|
$languageFolder = "cs"
|
||||||
|
}
|
||||||
|
if($project.Type -eq "VB.NET")
|
||||||
|
{
|
||||||
|
$languageFolder = "vb"
|
||||||
|
}
|
||||||
|
if($languageFolder -eq "")
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($analyzersPath in $analyzersPaths)
|
||||||
|
{
|
||||||
|
# Uninstall language specific analyzers.
|
||||||
|
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
||||||
|
if (Test-Path $languageAnalyzersPath)
|
||||||
|
{
|
||||||
|
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
|
||||||
|
{
|
||||||
|
if($project.Object.AnalyzerReferences)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,6 +20,7 @@ using System.Collections.Concurrent;
|
|||||||
using ThingsGateway.Extension;
|
using ThingsGateway.Extension;
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
using ThingsGateway.NewLife.Json.Extension;
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -64,32 +65,38 @@ public sealed class OperDescAttribute : MoAttribute
|
|||||||
|
|
||||||
public override void OnException(MethodContext context)
|
public override void OnException(MethodContext context)
|
||||||
{
|
{
|
||||||
//插入异常日志
|
if (App.HttpContext?.Request.Path.StartsWithSegments("/_blazor") == true)
|
||||||
SysOperateLog log = GetOperLog(LocalizerType, context);
|
{
|
||||||
|
//插入异常日志
|
||||||
|
SysOperateLog log = GetOperLog(LocalizerType, context);
|
||||||
|
|
||||||
log.Category = LogCateGoryEnum.Exception;//操作类型为异常
|
log.Category = LogCateGoryEnum.Exception;//操作类型为异常
|
||||||
log.ExeStatus = false;//操作状态为失败
|
log.ExeStatus = false;//操作状态为失败
|
||||||
if (context.Exception is AppFriendlyException exception)
|
if (context.Exception is AppFriendlyException exception)
|
||||||
log.ExeMessage = exception?.Message;
|
log.ExeMessage = exception?.Message;
|
||||||
else
|
else
|
||||||
log.ExeMessage = context.Exception?.ToString();
|
log.ExeMessage = context.Exception?.ToString();
|
||||||
|
|
||||||
OperDescAttribute.WriteToQueue(log);
|
OperDescAttribute.WriteToQueue(log);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnSuccess(MethodContext context)
|
public override void OnSuccess(MethodContext context)
|
||||||
{
|
{
|
||||||
//插入操作日志
|
if (App.HttpContext?.Request.Path.StartsWithSegments("/_blazor") == true)
|
||||||
SysOperateLog log = GetOperLog(LocalizerType, context);
|
{
|
||||||
OperDescAttribute.WriteToQueue(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//插入操作日志
|
||||||
|
SysOperateLog log = GetOperLog(LocalizerType, context);
|
||||||
|
OperDescAttribute.WriteToQueue(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static SqlSugarClient _db = DbContext.GetDB<SysOperateLog>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将日志消息写入数据库中
|
/// 将日志消息写入数据库中
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task ProcessQueue()
|
private static async Task ProcessQueue()
|
||||||
{
|
{
|
||||||
var db = DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
|
|
||||||
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
|
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
|
||||||
while (!appLifetime.ApplicationStopping.IsCancellationRequested)
|
while (!appLifetime.ApplicationStopping.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -98,7 +105,7 @@ public sealed class OperDescAttribute : MoAttribute
|
|||||||
var data = _logMessageQueue.ToListWithDequeue(); // 从日志队列中获取数据
|
var data = _logMessageQueue.ToListWithDequeue(); // 从日志队列中获取数据
|
||||||
if (data.Count > 0)
|
if (data.Count > 0)
|
||||||
{
|
{
|
||||||
await db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);//入库
|
await _db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);//入库
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -115,7 +122,7 @@ public sealed class OperDescAttribute : MoAttribute
|
|||||||
private SysOperateLog GetOperLog(Type? localizerType, MethodContext context)
|
private SysOperateLog GetOperLog(Type? localizerType, MethodContext context)
|
||||||
{
|
{
|
||||||
var methodBase = context.Method;
|
var methodBase = context.Method;
|
||||||
var clientInfo = AppService.ClientInfo;
|
var userAgent = AppService.UserAgent;
|
||||||
string? paramJson = null;
|
string? paramJson = null;
|
||||||
if (IsRecordPar)
|
if (IsRecordPar)
|
||||||
{
|
{
|
||||||
@@ -127,10 +134,10 @@ public sealed class OperDescAttribute : MoAttribute
|
|||||||
{
|
{
|
||||||
parametersDict[parametersInfo[i].Name!] = args[i];
|
parametersDict[parametersInfo[i].Name!] = args[i];
|
||||||
}
|
}
|
||||||
paramJson = parametersDict.ToJsonNetString();
|
paramJson = parametersDict.ToSystemTextJsonString();
|
||||||
}
|
}
|
||||||
var result = context.ReturnValue;
|
var result = context.ReturnValue;
|
||||||
var resultJson = IsRecordPar ? result?.ToJsonNetString() : null;
|
var resultJson = IsRecordPar ? result?.ToSystemTextJsonString() : null;
|
||||||
//操作日志表实体
|
//操作日志表实体
|
||||||
var log = new SysOperateLog
|
var log = new SysOperateLog
|
||||||
{
|
{
|
||||||
@@ -138,8 +145,8 @@ public sealed class OperDescAttribute : MoAttribute
|
|||||||
Category = LogCateGoryEnum.Operate,
|
Category = LogCateGoryEnum.Operate,
|
||||||
ExeStatus = true,
|
ExeStatus = true,
|
||||||
OpIp = AppService?.RemoteIpAddress ?? string.Empty,
|
OpIp = AppService?.RemoteIpAddress ?? string.Empty,
|
||||||
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
|
OpBrowser = userAgent?.Browser,
|
||||||
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
|
OpOs = userAgent?.Platform,
|
||||||
OpTime = DateTime.Now,
|
OpTime = DateTime.Now,
|
||||||
OpAccount = UserManager.UserAccount,
|
OpAccount = UserManager.UserAccount,
|
||||||
ReqUrl = null,
|
ReqUrl = null,
|
||||||
|
@@ -13,14 +13,16 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 需要角色授权权限
|
/// 需要角色授权权限
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||||
public sealed class RolePermissionAttribute : Attribute
|
public sealed class RolePermissionAttribute : Attribute
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 忽略角色授权权限
|
/// 忽略角色授权权限
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
|
||||||
public sealed class IgnoreRolePermissionAttribute : Attribute
|
public sealed class IgnoreRolePermissionAttribute : Attribute
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,21 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||||
|
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||||
|
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||||
|
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||||
|
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||||
|
// 使用文档:https://thingsgateway.cn/
|
||||||
|
// QQ群:605534569
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class LoginLogAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class LogoutLogAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
@@ -11,7 +11,7 @@
|
|||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class CacheConst
|
public static class CacheConst
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Token表缓存Key
|
/// Token表缓存Key
|
||||||
|
@@ -13,7 +13,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通讯器常量
|
/// 通讯器常量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class HubConst
|
public static class HubConst
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 系统HubUrl
|
/// 系统HubUrl
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
/// 资源表常量
|
/// 资源表常量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class ResourceConst
|
public static class ResourceConst
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 系统内置编码
|
/// 系统内置编码
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
/// 角色常量
|
/// 角色常量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class RoleConst
|
public static class RoleConst
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// api角色
|
/// api角色
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
/// SqlSugar系统常量
|
/// SqlSugar系统常量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class SqlSugarConst
|
public static class SqlSugarConst
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DB_Admin
|
/// DB_Admin
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
|
|
||||||
[ApiDescriptionSettings(false)]
|
[ApiDescriptionSettings(false)]
|
||||||
[Route("api/auth")]
|
[Route("api/auth")]
|
||||||
[LoggingMonitor]
|
[RequestAudit]
|
||||||
public class AuthController : ControllerBase
|
public class AuthController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
@@ -27,14 +28,31 @@ public class AuthController : ControllerBase
|
|||||||
|
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
|
[LoginLog]
|
||||||
public Task<LoginOutput> LoginAsync([FromBody] LoginInput input)
|
public Task<LoginOutput> LoginAsync([FromBody] LoginInput input)
|
||||||
{
|
{
|
||||||
|
|
||||||
return _authService.LoginAsync(input);
|
return _authService.LoginAsync(input);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("oauth-login")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[SuppressRequestAudit]
|
||||||
|
public IActionResult OAuthLogin(string scheme = "Gitee", string returnUrl = "/")
|
||||||
|
{
|
||||||
|
var props = new AuthenticationProperties
|
||||||
|
{
|
||||||
|
RedirectUri = returnUrl
|
||||||
|
};
|
||||||
|
return Challenge(props, scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost("logout")]
|
[HttpPost("logout")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[IgnoreRolePermission]
|
[IgnoreRolePermission]
|
||||||
|
[LogoutLog]
|
||||||
public Task LogoutAsync()
|
public Task LogoutAsync()
|
||||||
{
|
{
|
||||||
return _authService.LoginOutAsync();
|
return _authService.LoginOutAsync();
|
||||||
|
@@ -25,7 +25,8 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
[Description("登录")]
|
[Description("登录")]
|
||||||
[Route("openapi/auth")]
|
[Route("openapi/auth")]
|
||||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||||
[LoggingMonitor]
|
[RequestAudit]
|
||||||
|
[ApiController]
|
||||||
public class OpenApiController : ControllerBase
|
public class OpenApiController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
|
@@ -15,16 +15,13 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
|
|
||||||
[Route("api/[controller]/[action]")]
|
[Route("api/[controller]/[action]")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
|
[ApiController]
|
||||||
public class TestController : ControllerBase
|
public class TestController : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpPost]
|
[HttpGet]
|
||||||
public Task Test(string data)
|
public void Test()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 3; i++)
|
GC.Collect();
|
||||||
{
|
GC.WaitForPendingFinalizers();
|
||||||
GC.Collect();
|
|
||||||
GC.WaitForPendingFinalizers();
|
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
[SugarTable("sys_dict", TableDescription = "字典表")]
|
[SugarTable("sys_dict", TableDescription = "字典表")]
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -14,10 +14,10 @@ using Microsoft.AspNetCore.Components.Routing;
|
|||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -12,11 +12,11 @@ using BootstrapBlazor.Components;
|
|||||||
|
|
||||||
using Mapster;
|
using Mapster;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -10,9 +10,8 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.List;
|
using ThingsGateway.List;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||||
|
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||||
|
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||||
|
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||||
|
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||||
|
// 使用文档:https://thingsgateway.cn/
|
||||||
|
// QQ群:605534569
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public class RequestAudit
|
||||||
|
{
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
using ThingsGateway.DependencyInjection;
|
||||||
|
|
||||||
|
namespace System;
|
||||||
|
|
||||||
|
[SuppressSniffer, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||||
|
public sealed class RequestAuditAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,101 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||||
|
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||||
|
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||||
|
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||||
|
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||||
|
// 使用文档:https://thingsgateway.cn/
|
||||||
|
// QQ群:605534569
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public class RequestAuditData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 分类
|
||||||
|
/// </summary>
|
||||||
|
public string CateGory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端信息
|
||||||
|
/// </summary>
|
||||||
|
public UserAgent Client { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求方法:POST/GET
|
||||||
|
/// </summary>
|
||||||
|
public string Method { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作名称
|
||||||
|
/// </summary>
|
||||||
|
public string Operation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 方法名称
|
||||||
|
/// </summary>
|
||||||
|
public string ActionName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 认证信息
|
||||||
|
/// </summary>
|
||||||
|
public List<AuthorizationClaims> AuthorizationClaims { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 控制器名
|
||||||
|
/// </summary>
|
||||||
|
public string ControllerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异常信息
|
||||||
|
/// </summary>
|
||||||
|
public LogException Exception { get; set; }
|
||||||
|
|
||||||
|
public long TimeOperationElapsedMilliseconds { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 服务端
|
||||||
|
/// </summary>
|
||||||
|
public string LocalIPv4 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset LogDateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 参数列表
|
||||||
|
/// </summary>
|
||||||
|
public List<Parameters> Parameters { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端IPV4地址
|
||||||
|
/// </summary>
|
||||||
|
public string RemoteIPv4 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
public string RequestUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回信息
|
||||||
|
/// </summary>
|
||||||
|
public object ReturnInformation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证错误信息
|
||||||
|
/// </summary>
|
||||||
|
public Validation Validation { get; set; }
|
||||||
|
public MethodInfo MethodInfo { get; set; }
|
||||||
|
}
|
||||||
|
|
@@ -0,0 +1,301 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||||
|
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||||
|
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||||
|
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||||
|
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||||
|
// 使用文档:https://thingsgateway.cn/
|
||||||
|
// QQ群:605534569
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Logging;
|
||||||
|
|
||||||
|
using ThingsGateway.FriendlyException;
|
||||||
|
using ThingsGateway.Logging;
|
||||||
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
|
using ThingsGateway.UnifyResult;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter
|
||||||
|
{
|
||||||
|
private const int FilterOrder = -3000;
|
||||||
|
public int Order => FilterOrder;
|
||||||
|
|
||||||
|
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||||
|
{
|
||||||
|
var timeOperation = Stopwatch.StartNew();
|
||||||
|
var resultContext = await next().ConfigureAwait(false);
|
||||||
|
// 计算接口执行时间
|
||||||
|
timeOperation.Stop();
|
||||||
|
|
||||||
|
var controllerActionDescriptor = (context.ActionDescriptor as ControllerActionDescriptor);
|
||||||
|
// 获取动作方法描述器
|
||||||
|
var actionMethod = controllerActionDescriptor?.MethodInfo;
|
||||||
|
|
||||||
|
|
||||||
|
// 处理 Blazor Server
|
||||||
|
if (actionMethod == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排除 WebSocket 请求处理
|
||||||
|
if (context.HttpContext.IsWebSocketRequest())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果贴了 [SuppressMonitor] 特性则跳过
|
||||||
|
if (actionMethod.IsDefined(typeof(SuppressRequestAuditAttribute), true)
|
||||||
|
|| actionMethod.DeclaringType.IsDefined(typeof(SuppressRequestAuditAttribute), true))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 只有方法贴有特性才进行审计
|
||||||
|
if (
|
||||||
|
!actionMethod.DeclaringType.IsDefined(typeof(RequestAuditAttribute), true)
|
||||||
|
&&
|
||||||
|
!actionMethod.IsDefined(typeof(RequestAuditAttribute), true))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var logData = new RequestAuditData();
|
||||||
|
|
||||||
|
|
||||||
|
logData.TimeOperationElapsedMilliseconds = timeOperation.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
var resultHttpContext = (resultContext as FilterContext).HttpContext;
|
||||||
|
|
||||||
|
// 获取 HttpContext 和 HttpRequest 对象
|
||||||
|
var httpContext = context.HttpContext;
|
||||||
|
var httpRequest = httpContext.Request;
|
||||||
|
|
||||||
|
// 获取客户端 Ipv4 地址
|
||||||
|
var remoteIPv4 = httpContext.GetRemoteIpAddressToIPv4();
|
||||||
|
logData.RemoteIPv4 = remoteIPv4;
|
||||||
|
var requestUrl = Uri.UnescapeDataString(httpRequest.GetRequestUrlAddress());
|
||||||
|
logData.RequestUrl = requestUrl;
|
||||||
|
|
||||||
|
object returnValue = null;
|
||||||
|
Type finalReturnType;
|
||||||
|
var result = resultContext.Result as IActionResult;
|
||||||
|
// 解析返回值
|
||||||
|
if (UnifyContext.CheckVaildResult(result, out var data))
|
||||||
|
{
|
||||||
|
returnValue = data;
|
||||||
|
finalReturnType = data?.GetType();
|
||||||
|
}
|
||||||
|
// 处理文件类型
|
||||||
|
else if (result is FileResult fresult)
|
||||||
|
{
|
||||||
|
returnValue = new
|
||||||
|
{
|
||||||
|
FileName = fresult.FileDownloadName,
|
||||||
|
fresult.ContentType,
|
||||||
|
Length = fresult is FileContentResult cresult ? (object)cresult.FileContents.Length : null
|
||||||
|
};
|
||||||
|
finalReturnType = fresult?.GetType();
|
||||||
|
}
|
||||||
|
else finalReturnType = result?.GetType();
|
||||||
|
|
||||||
|
logData.ReturnInformation = returnValue;
|
||||||
|
|
||||||
|
//获取客户端信息
|
||||||
|
var client = App.GetService<IAppService>().UserAgent;
|
||||||
|
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
|
||||||
|
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
|
||||||
|
|
||||||
|
var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[actionMethod.Name];
|
||||||
|
//获取特性
|
||||||
|
|
||||||
|
logData.CateGory = desc.Value;//传操作名称
|
||||||
|
logData.Operation = desc.Value;//传操作名称
|
||||||
|
logData.Client = client;
|
||||||
|
logData.Path = httpContext.Request.Path.Value;//请求地址
|
||||||
|
logData.Method = httpContext.Request.Method;//请求方法
|
||||||
|
logData.MethodInfo = actionMethod;//请求方法
|
||||||
|
|
||||||
|
logData.ControllerName = controllerActionDescriptor.ControllerName;
|
||||||
|
logData.ActionName = controllerActionDescriptor.ActionName;
|
||||||
|
|
||||||
|
logData.AuthorizationClaims = new();
|
||||||
|
// 获取授权用户
|
||||||
|
var user = httpContext.User;
|
||||||
|
foreach (var claim in user.Claims)
|
||||||
|
{
|
||||||
|
logData.AuthorizationClaims.Add(new AuthorizationClaims
|
||||||
|
{
|
||||||
|
Type = claim.Type,
|
||||||
|
Value = claim.Value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
logData.LocalIPv4 = httpContext.GetLocalIpAddressToIPv4();
|
||||||
|
logData.LogDateTime = DateTimeOffset.Now;
|
||||||
|
var parameterValues = context.ActionArguments;
|
||||||
|
|
||||||
|
logData.Parameters = new();
|
||||||
|
var parameters = actionMethod.GetParameters();
|
||||||
|
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
{
|
||||||
|
// 判断是否禁用记录特定参数
|
||||||
|
if (parameter.IsDefined(typeof(SuppressRequestAuditAttribute), false)) continue;
|
||||||
|
|
||||||
|
// 排除标记 [FromServices] 的解析
|
||||||
|
if (parameter.IsDefined(typeof(FromServicesAttribute), false)) continue;
|
||||||
|
|
||||||
|
var name = parameter.Name;
|
||||||
|
var parameterType = parameter.ParameterType;
|
||||||
|
|
||||||
|
_ = parameterValues.TryGetValue(name, out var value);
|
||||||
|
|
||||||
|
|
||||||
|
var par = new Parameters()
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
};
|
||||||
|
logData.Parameters.Add(par);
|
||||||
|
|
||||||
|
object rawValue = default;
|
||||||
|
|
||||||
|
// 文件类型参数
|
||||||
|
if (value is IFormFile || value is List<IFormFile>)
|
||||||
|
{
|
||||||
|
// 单文件
|
||||||
|
if (value is IFormFile formFile)
|
||||||
|
{
|
||||||
|
var fileSize = Math.Round(formFile.Length / 1024D);
|
||||||
|
rawValue = new
|
||||||
|
{
|
||||||
|
name = formFile.Name,
|
||||||
|
fileName = formFile.FileName,
|
||||||
|
length = formFile.Length,
|
||||||
|
contentType = formFile.ContentType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 多文件
|
||||||
|
else if (value is List<IFormFile> formFiles)
|
||||||
|
{
|
||||||
|
var rawValues1 = new List<object>();
|
||||||
|
for (var i = 0; i < formFiles.Count; i++)
|
||||||
|
{
|
||||||
|
var file = formFiles[i];
|
||||||
|
var size = Math.Round(file.Length / 1024D);
|
||||||
|
var rawValue1 = new
|
||||||
|
{
|
||||||
|
name = file.Name,
|
||||||
|
fileName = file.FileName,
|
||||||
|
length = file.Length,
|
||||||
|
contentType = file.ContentType
|
||||||
|
};
|
||||||
|
rawValues1.Add(rawValue1);
|
||||||
|
}
|
||||||
|
rawValue = rawValues1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理 byte[] 参数类型
|
||||||
|
else if (value is byte[] byteArray)
|
||||||
|
{
|
||||||
|
rawValue = new
|
||||||
|
{
|
||||||
|
length = byteArray.Length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 处理基元类型,字符串类型和空值
|
||||||
|
else if (parameterType.IsPrimitive || value is string || value == null)
|
||||||
|
{
|
||||||
|
rawValue = value;
|
||||||
|
}
|
||||||
|
// 其他类型统一进行序列化
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rawValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
par.Value = rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 获取异常对象情况
|
||||||
|
Exception exception = resultContext.Exception;
|
||||||
|
if (exception is AppFriendlyException friendlyException)
|
||||||
|
{
|
||||||
|
logData.Validation = new();
|
||||||
|
logData.Validation.Message = friendlyException.Message;
|
||||||
|
}
|
||||||
|
else if (exception != null)
|
||||||
|
{
|
||||||
|
logData.Exception = new();
|
||||||
|
logData.Exception.Message = exception.Message;
|
||||||
|
logData.Exception.StackTrace = exception.StackTrace;
|
||||||
|
logData.Exception.Type = HandleGenericType(exception.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 创建日志记录器
|
||||||
|
var logger = httpContext.RequestServices.GetRequiredService<ILogger<RequestAudit>>();
|
||||||
|
|
||||||
|
var logContext = new LogContext();
|
||||||
|
|
||||||
|
logContext.Set(nameof(RequestAuditData), logData);
|
||||||
|
|
||||||
|
// 设置日志上下文
|
||||||
|
using var scope = logger.ScopeContext(logContext);
|
||||||
|
|
||||||
|
if (exception == null)
|
||||||
|
{
|
||||||
|
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理泛型类型转字符串打印问题
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string HandleGenericType(Type type)
|
||||||
|
{
|
||||||
|
if (type == null) return string.Empty;
|
||||||
|
|
||||||
|
var typeName = type.FullName ?? (!string.IsNullOrEmpty(type.Namespace) ? type.Namespace + "." : string.Empty) + type.Name;
|
||||||
|
|
||||||
|
// 处理泛型类型问题
|
||||||
|
if (type.IsConstructedGenericType)
|
||||||
|
{
|
||||||
|
var prefix = type.GetGenericArguments()
|
||||||
|
.Select(genericArg => HandleGenericType(genericArg))
|
||||||
|
.Aggregate((previous, current) => previous + ", " + current);
|
||||||
|
|
||||||
|
typeName = typeName.Split('`').First() + "<" + prefix + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
using ThingsGateway.DependencyInjection;
|
||||||
|
|
||||||
|
namespace System;
|
||||||
|
|
||||||
|
[SuppressSniffer, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||||
|
public sealed class SuppressRequestAuditAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@@ -8,5 +8,4 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
global using ThingsGateway;
|
|
||||||
global using ThingsGateway.NewLife.Extension;
|
global using ThingsGateway.NewLife.Extension;
|
@@ -51,7 +51,7 @@ public class HardwareInfo
|
|||||||
/// 进程占用内存
|
/// 进程占用内存
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AutoGenerateColumn(Ignore = true)]
|
[AutoGenerateColumn(Ignore = true)]
|
||||||
public string WorkingSet { get; set; }
|
public int WorkingSet { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新时间
|
/// 更新时间
|
||||||
|
@@ -17,8 +17,10 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
using ThingsGateway.Extension;
|
using ThingsGateway.Extension;
|
||||||
using ThingsGateway.NewLife;
|
using ThingsGateway.NewLife;
|
||||||
|
using ThingsGateway.NewLife.Caching;
|
||||||
using ThingsGateway.NewLife.Threading;
|
using ThingsGateway.NewLife.Threading;
|
||||||
using ThingsGateway.Schedule;
|
using ThingsGateway.Schedule;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -51,15 +53,25 @@ public class HardwareJob : IJob, IHardwareJob
|
|||||||
|
|
||||||
#endregion 属性
|
#endregion 属性
|
||||||
|
|
||||||
|
private MemoryCache MemoryCache = new() { };
|
||||||
|
private const string CacheKey = "HistoryHardwareInfo";
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<List<HistoryHardwareInfo>> GetHistoryHardwareInfos()
|
public async Task<List<HistoryHardwareInfo>> GetHistoryHardwareInfos()
|
||||||
{
|
{
|
||||||
using var db = DbContext.Db.GetConnectionScopeWithAttr<HistoryHardwareInfo>().CopyNew();
|
var historyHardwareInfos = MemoryCache.Get<List<HistoryHardwareInfo>>(CacheKey);
|
||||||
return await db.Queryable<HistoryHardwareInfo>().ToListAsync().ConfigureAwait(false);
|
if (historyHardwareInfos == null)
|
||||||
|
{
|
||||||
|
using var db = DbContext.GetDB<HistoryHardwareInfo>(); ;
|
||||||
|
historyHardwareInfos = await db.Queryable<HistoryHardwareInfo>().Where(a => a.Date > DateTime.Now.AddDays(-3)).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
MemoryCache.Set(CacheKey, historyHardwareInfos);
|
||||||
|
}
|
||||||
|
return historyHardwareInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool error = false;
|
private bool error = false;
|
||||||
private DateTime hisInsertTime = default;
|
private DateTime hisInsertTime = default;
|
||||||
|
private SqlSugarClient _db = DbContext.GetDB<HistoryHardwareInfo>();
|
||||||
|
|
||||||
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
|
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
@@ -69,7 +81,7 @@ public class HardwareJob : IJob, IHardwareJob
|
|||||||
{
|
{
|
||||||
if (HardwareInfo.MachineInfo == null)
|
if (HardwareInfo.MachineInfo == null)
|
||||||
{
|
{
|
||||||
await MachineInfo.RegisterAsync().ConfigureAwait(false);
|
MachineInfo.Register();
|
||||||
HardwareInfo.MachineInfo = MachineInfo.Current;
|
HardwareInfo.MachineInfo = MachineInfo.Current;
|
||||||
|
|
||||||
string currentPath = Directory.GetCurrentDirectory();
|
string currentPath = Directory.GetCurrentDirectory();
|
||||||
@@ -94,13 +106,13 @@ public class HardwareJob : IJob, IHardwareJob
|
|||||||
{
|
{
|
||||||
HardwareInfo.MachineInfo.Refresh();
|
HardwareInfo.MachineInfo.Refresh();
|
||||||
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
|
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
|
||||||
HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToString("F2");
|
HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt();
|
||||||
error = false;
|
error = false;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (!error)
|
if (!error)
|
||||||
_logger.LogWarning(ex, _localizer["GetHardwareInfoFail"]);
|
_logger.LogWarning(ex, "Get Hardwareinfo Fail");
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,22 +123,26 @@ public class HardwareJob : IJob, IHardwareJob
|
|||||||
if (DateTime.Now > hisInsertTime.Add(TimeSpan.FromMilliseconds(HardwareInfoOptions.HistoryInterval)))
|
if (DateTime.Now > hisInsertTime.Add(TimeSpan.FromMilliseconds(HardwareInfoOptions.HistoryInterval)))
|
||||||
{
|
{
|
||||||
hisInsertTime = DateTime.Now;
|
hisInsertTime = DateTime.Now;
|
||||||
using var db = DbContext.Db.GetConnectionScopeWithAttr<HistoryHardwareInfo>().CopyNew();
|
|
||||||
{
|
{
|
||||||
var his = new HistoryHardwareInfo()
|
var his = new HistoryHardwareInfo()
|
||||||
{
|
{
|
||||||
Date = TimerX.Now,
|
Date = TimerX.Now,
|
||||||
DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToString("F2"),
|
DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(),
|
||||||
Battery = (HardwareInfo.MachineInfo.Battery * 100).ToString("F2"),
|
Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(),
|
||||||
MemoryUsage = (HardwareInfo.WorkingSet),
|
MemoryUsage = (HardwareInfo.WorkingSet),
|
||||||
CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToString("F2"),
|
CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(),
|
||||||
Temperature = (HardwareInfo.MachineInfo.Temperature).ToString("F2"),
|
Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(),
|
||||||
};
|
};
|
||||||
await db.Insertable(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
|
await _db.Insertable(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
|
||||||
|
MemoryCache.Remove(CacheKey);
|
||||||
}
|
}
|
||||||
var sevenDaysAgo = TimerX.Now.AddDays(-HardwareInfoOptions.DaysAgo);
|
var sevenDaysAgo = TimerX.Now.AddDays(-HardwareInfoOptions.DaysAgo);
|
||||||
//删除特定信息
|
//删除特定信息
|
||||||
await db.Deleteable<HistoryHardwareInfo>(a => a.Date <= sevenDaysAgo).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
|
var result = await _db.Deleteable<HistoryHardwareInfo>(a => a.Date <= sevenDaysAgo).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
|
||||||
|
if (result > 0)
|
||||||
|
{
|
||||||
|
MemoryCache.Remove(CacheKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
error = false;
|
error = false;
|
||||||
@@ -137,7 +153,7 @@ public class HardwareJob : IJob, IHardwareJob
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (!error)
|
if (!error)
|
||||||
_logger.LogWarning(ex, _localizer["GetHardwareInfoFail"]);
|
_logger.LogWarning(ex, "Get Hardwareinfo Fail");
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -19,23 +19,23 @@ public class HistoryHardwareInfo
|
|||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SugarColumn(ColumnDescription = "磁盘使用率")]
|
[SugarColumn(ColumnDescription = "磁盘使用率")]
|
||||||
public string DriveUsage { get; set; }
|
public int DriveUsage { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SugarColumn(ColumnDescription = "内存")]
|
[SugarColumn(ColumnDescription = "内存")]
|
||||||
public string MemoryUsage { get; set; }
|
public int MemoryUsage { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SugarColumn(ColumnDescription = "CPU使用率")]
|
[SugarColumn(ColumnDescription = "CPU使用率")]
|
||||||
public string CpuUsage { get; set; }
|
public int CpuUsage { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SugarColumn(ColumnDescription = "温度")]
|
[SugarColumn(ColumnDescription = "温度")]
|
||||||
public string Temperature { get; set; }
|
public int Temperature { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SugarColumn(ColumnDescription = "电池")]
|
[SugarColumn(ColumnDescription = "电池")]
|
||||||
public string Battery { get; set; }
|
public int Battery { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SugarColumn(ColumnDescription = "时间")]
|
[SugarColumn(ColumnDescription = "时间")]
|
||||||
|
@@ -27,7 +27,7 @@ public class LogJob : IJob
|
|||||||
|
|
||||||
private static async Task DeleteSysOperateLog(int daysAgo, CancellationToken stoppingToken)
|
private static async Task DeleteSysOperateLog(int daysAgo, CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
using var db = DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
|
using var db = DbContext.GetDB<SysOperateLog>();
|
||||||
var time = DateTime.Now.AddDays(-daysAgo);
|
var time = DateTime.Now.AddDays(-daysAgo);
|
||||||
await db.DeleteableWithAttr<SysOperateLog>().Where(u => u.OpTime < time).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false); // 删除操作日志
|
await db.DeleteableWithAttr<SysOperateLog>().Where(u => u.OpTime < time).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false); // 删除操作日志
|
||||||
}
|
}
|
||||||
|
@@ -1,466 +1,402 @@
|
|||||||
{
|
{
|
||||||
|
"ThingsGateway.Admin.Application.AppConfig": {
|
||||||
|
"LoginPolicy": "LoginPolicy",
|
||||||
|
"PagePolicy": "PagePolicy",
|
||||||
|
"PasswordPolicy": "PasswordPolicy",
|
||||||
|
"WebsitePolicy": "WebsitePolicy"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.AuthController": {
|
||||||
|
"AuthController": "Login API",
|
||||||
|
"LoginAsync": "Login",
|
||||||
|
"LogoutAsync": "Logout"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.AuthService": {
|
||||||
|
"AuthErrorMax": "Account password error, will be locked for {1} minutes after exceeding {0} times, error count {2}",
|
||||||
|
"MustDesc": "Password needs to be encrypted with DESC before passing",
|
||||||
|
"OrgDisable": "The affiliated company/department has been deactivated, please contact the administrator",
|
||||||
|
"PasswordError": "Too many password errors, please try again in {0} minutes",
|
||||||
|
"SingleLoginWarn": "Your account is logged in elsewhere",
|
||||||
|
"TenantNull": "The tenant does not exist",
|
||||||
|
"UserDisable": "Account {0} has been disabled",
|
||||||
|
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
|
||||||
|
"UserNull": "User {0} does not exist"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.BaseDataEntity": {
|
||||||
|
"CreateOrgId": "CreateOrgId"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.BaseEntity": {
|
||||||
|
"CreateTime": "CreateTime",
|
||||||
|
"CreateUser": "CreateUser",
|
||||||
|
"SortCode": "SortCode",
|
||||||
|
"UpdateTime": "UpdateTime",
|
||||||
|
"UpdateUser": "UpdateUser"
|
||||||
|
},
|
||||||
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
|
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
|
||||||
"UserExpire": "User expired, please login again"
|
"UserExpire": "User expired, please login again"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.SysUser": {
|
|
||||||
"Disable": "Disable",
|
|
||||||
"Enable": "Enable",
|
|
||||||
"GrantRole": "GrantRole",
|
|
||||||
"ExitVerificat": "You have been forcibly logged out",
|
|
||||||
"PasswordEdited": "Password changed, logged out",
|
|
||||||
|
|
||||||
"Avatar": "Avatar",
|
|
||||||
"Account": "Account",
|
|
||||||
"Account.Required": "Account.Required",
|
|
||||||
"Password": "Password",
|
|
||||||
"Status": "Status",
|
|
||||||
"Phone": "Phone",
|
|
||||||
"Email": "Email",
|
|
||||||
"LastLoginIp": "LastLoginIp",
|
|
||||||
"LastLoginDevice": "LastLoginDevice",
|
|
||||||
"LastLoginTime": "LastLoginTime",
|
|
||||||
"LastLoginAddress": "LastLoginAddress",
|
|
||||||
"LatestLoginIp": "LatestLoginIp",
|
|
||||||
"LatestLoginTime": "LatestLoginTime",
|
|
||||||
"LatestLoginDevice": "LatestLoginDevice",
|
|
||||||
"LatestLoginAddress": "LatestLoginAddress",
|
|
||||||
"SortCode": "SortCode",
|
|
||||||
"CreateTime": "CreateTime",
|
|
||||||
"UpdateTime": "UpdateTime",
|
|
||||||
"OrgNames": "OrgNames",
|
|
||||||
"PositionName": "PositionName",
|
|
||||||
"OrgId": "Org",
|
|
||||||
"PositionId": "Position",
|
|
||||||
"DirectorId": "Director",
|
|
||||||
|
|
||||||
"CheckSelf": "Prohibit {0} yourself",
|
|
||||||
"CanotDeleteAdminUser": "Cannot delete built-in super admin user",
|
|
||||||
"CanotEditAdminUser": "Cannot edit super admin user",
|
|
||||||
"CanotGrantAdmin": "Cannot assign admins roles",
|
|
||||||
"EmailDup": "Duplicate email {0} exists",
|
|
||||||
"AccountDup": "Duplicate account {0} exists",
|
|
||||||
"CanotDeleteSelf": "Cannot delete yourself",
|
|
||||||
"EmailError": "Email format error {0}",
|
|
||||||
"PhoneError": "Phone number format error {0}",
|
|
||||||
"NoOrg": "The organization does not exist",
|
|
||||||
"DirectorSelf": "Cannot set oneself as the supervisor",
|
|
||||||
|
|
||||||
"DemoCanotUpdatePassword": "DEMO environment does not allow password modification",
|
|
||||||
"OldPasswordError": "Incorrect old password",
|
|
||||||
"ConfirmPasswordDiff": "Passwords entered twice are inconsistent",
|
|
||||||
"PasswordLengthLess": "Password length cannot be less than {0}",
|
|
||||||
"PasswordMustNum ": "Password must contain numbers",
|
|
||||||
"PasswordMustLow": "Password must contain lowercase letters",
|
|
||||||
"PasswordMustUpp": "Password must contain uppercase letters",
|
|
||||||
"PasswordMustSpecial": "Password must contain special characters"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.SysRole": {
|
|
||||||
"Code": "Code",
|
|
||||||
"Name": "Name",
|
|
||||||
"Name.Required": "{0} is required",
|
|
||||||
"Category": "Category",
|
|
||||||
"SortCode": "Sort",
|
|
||||||
"CreateTime": "CreateTime",
|
|
||||||
"UpdateTime": "UpdateTime",
|
|
||||||
"OrgId": "Org",
|
|
||||||
"Global": "Global",
|
|
||||||
"Status": "Status",
|
|
||||||
"CanotDeleteAdmin": "Cannot delete built-in super admin role",
|
|
||||||
"CanotEditAdmin": "Cannot edit super admin role",
|
|
||||||
"CanotGrantAdmin": "Cannot assign admins roles",
|
|
||||||
"NameDup": "Duplicate role name {0}",
|
|
||||||
|
|
||||||
"OrgNotNull": "Organization cannot be null",
|
|
||||||
"SameOrgNameDup": "Duplicate role name exists: {0}",
|
|
||||||
"CannotRoleScopeAll": "Organization role cannot select global data scope",
|
|
||||||
"CodeDup": "Duplicate code exists: {0}"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
|
|
||||||
"Global": "Global",
|
|
||||||
"Org": "Org"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.DataScopeEnum": {
|
"ThingsGateway.Admin.Application.DataScopeEnum": {
|
||||||
"SCOPE_SELF": "Self",
|
|
||||||
"SCOPE_ALL": "All",
|
"SCOPE_ALL": "All",
|
||||||
"SCOPE_ORG": "OnlyOrg",
|
"SCOPE_ORG": "OnlyOrg",
|
||||||
"SCOPE_ORG_CHILD": "OrgChild",
|
"SCOPE_ORG_CHILD": "OrgChild",
|
||||||
"SCOPE_ORG_DEFINE": "Define"
|
"SCOPE_ORG_DEFINE": "Define",
|
||||||
|
"SCOPE_SELF": "Self"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.DefaultDataScope": {
|
"ThingsGateway.Admin.Application.DefaultDataScope": {
|
||||||
"ScopeCategory": "DataScope",
|
"ScopeCategory": "DataScope",
|
||||||
"ScopeDefineOrgIdList": "DefineOrgList"
|
"ScopeDefineOrgIdList": "DefineOrgList"
|
||||||
},
|
},
|
||||||
|
"ThingsGateway.Admin.Application.DictTypeEnum": {
|
||||||
|
"Define": "Business",
|
||||||
"ThingsGateway.Admin.Application.SysResource": {
|
"System": "System"
|
||||||
"Title": "Title",
|
|
||||||
"Module": "Module",
|
|
||||||
"Title.Required": "{0} is required",
|
|
||||||
"Href.Required": "{0} is required",
|
|
||||||
"Icon": "Icon",
|
|
||||||
"Href": "Path",
|
|
||||||
"Code": "Code",
|
|
||||||
"Category": "Category",
|
|
||||||
"Target": "Target",
|
|
||||||
"NavLinkMatch": "NavLinkMatch",
|
|
||||||
"SortCode": "Sort",
|
|
||||||
"CreateTime": "CreateTime",
|
|
||||||
"UpdateTime": "UpdateTime",
|
|
||||||
"ParentId": "Parent",
|
|
||||||
"ResourceDup": "Duplicate name {0} exists",
|
|
||||||
"ResourceParentChoiceSelf": "Parent cannot choose itself",
|
|
||||||
"ResourceParentNull": "Parent does not exist {0}",
|
|
||||||
"NotFoundResource": "System exception, menu not found",
|
|
||||||
"ModuleIdDiff": "Module is inconsistent with parent menu",
|
|
||||||
"CanotDeleteSystemResource": "Cannot delete system resource {0}",
|
|
||||||
"ResourceMenuHrefNotNull": "Menu href cannot null"
|
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
|
|
||||||
"TargetId": "Target",
|
|
||||||
"ContainsChild": "ContainsChild",
|
|
||||||
"ContainsPosition": "ContainsPosition"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.SysPosition": {
|
|
||||||
"Category.Required": "{0} is a required field",
|
|
||||||
"Name.Required": "{0} is a required field",
|
|
||||||
"Code.Required": "{0} is a required field",
|
|
||||||
"OrgId.MinValue": "{0} is a required field",
|
|
||||||
"Category": "Category",
|
|
||||||
"Name": "Name",
|
|
||||||
"Code": "Code",
|
|
||||||
"Status": "Status",
|
|
||||||
"OrgId": "Organization",
|
|
||||||
"Remark": "Remarks",
|
|
||||||
"SortCode": "SortCode",
|
|
||||||
"CreateTime": "CreateTime",
|
|
||||||
"UpdateTime": "UpdateTime",
|
|
||||||
"Dup": "Duplicate position exists with Category {0} and Name {1}",
|
|
||||||
"CodeDup": "Duplicate code {0} exists",
|
|
||||||
"NameDup": "Duplicate name {0} exists",
|
|
||||||
"CanotContainsSelf": "Cannot contain itself",
|
|
||||||
"TargetNameDup": "Target node has duplicate name {0}",
|
|
||||||
"ParentChoiceSelf": "Parent cannot be itself",
|
|
||||||
"ParentNull": "Parent does not exist {0}",
|
|
||||||
"DeleteUserFirst": "Please remove the users under the position first"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.SysOrg": {
|
|
||||||
"Category.Required": "{0} is a required field",
|
|
||||||
"Name.Required": "{0} is a required field",
|
|
||||||
"Code.Required": "{0} is a required field",
|
|
||||||
"Category": "Category",
|
|
||||||
"Name": "Name",
|
|
||||||
"Code": "Code",
|
|
||||||
"Status": "Status",
|
|
||||||
"ParentId": "ParentOrg",
|
|
||||||
"Names": "Names",
|
|
||||||
"Remark": "Remarks",
|
|
||||||
"DirectorId": "Director",
|
|
||||||
"SortCode": "SortCode",
|
|
||||||
"CreateTime": "CreateTime",
|
|
||||||
"UpdateTime": "UpdateTime",
|
|
||||||
"Dup": "Duplicate organization exists with Category {0} and Name {1}",
|
|
||||||
"CodeDup": "Duplicate code {0} exists",
|
|
||||||
"NameDup": "Duplicate name {0} exists",
|
|
||||||
"CanotContainsSelf": "Cannot contain itself",
|
|
||||||
"TargetNameDup": "Target node has duplicate name {0}",
|
|
||||||
"ParentChoiceSelf": "Parent cannot be itself",
|
|
||||||
"ParentNull": "Parent does not exist {0}",
|
|
||||||
"DeleteUserFirst": "Please remove the users under the organization first",
|
|
||||||
"DeleteRoleFirst": "Please remove the roles under the organization first",
|
|
||||||
"DeletePositionFirst": "Please remove the positions under the organization first",
|
|
||||||
"RootOrg": "Unable to create top-level organization"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.OrgEnum": {
|
|
||||||
"COMPANY": "Company",
|
|
||||||
"DEPT": "Dept"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.PositionCategoryEnum": {
|
|
||||||
"HIGH": "High",
|
|
||||||
"MIDDLE": "Middle",
|
|
||||||
"LOW": "Low"
|
|
||||||
},
|
|
||||||
//controller
|
|
||||||
"ThingsGateway.Admin.Application.AuthController": {
|
|
||||||
//auth
|
|
||||||
"AuthController": "Login API",
|
|
||||||
"LoginAsync": "Login",
|
|
||||||
"LogoutAsync": "Logout"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.TestController": {
|
|
||||||
//auth
|
|
||||||
"TestController": "Test API",
|
|
||||||
"Test": "Test"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.OpenApiAuthController": {
|
|
||||||
//auth
|
|
||||||
"OpenApiAuthController": "Login API",
|
|
||||||
"LoginAsync": "Login",
|
|
||||||
"LogoutAsync": "Logout"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.FileService": {
|
"ThingsGateway.Admin.Application.FileService": {
|
||||||
"FileNullError": "File cannot be empty",
|
|
||||||
"FileLengthError": "File size cannot exceed {0} M",
|
"FileLengthError": "File size cannot exceed {0} M",
|
||||||
|
"FileNullError": "File cannot be empty",
|
||||||
"FileTypeError": "Not supported format {0}"
|
"FileTypeError": "Not supported format {0}"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.UnifyResultProvider": {
|
|
||||||
"TokenOver": "Login has expired, please login again",
|
|
||||||
"NoPermission": "Access denied, no permission"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.AuthService": {
|
|
||||||
|
|
||||||
"TenantNull": "The tenant does not exist",
|
|
||||||
"OrgDisable": "The affiliated company/department has been deactivated, please contact the administrator",
|
|
||||||
|
|
||||||
"SingleLoginWarn": "Your account is logged in elsewhere",
|
|
||||||
"UserNull": "User {0} does not exist",
|
|
||||||
"MustDesc": "Password needs to be encrypted with DESC before passing",
|
|
||||||
"PasswordError": "Too many password errors, please try again in {0} minutes",
|
|
||||||
"UserDisable": "Account {0} has been disabled",
|
|
||||||
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
|
|
||||||
"AuthErrorMax": "Account password error, will be locked for {1} minutes after exceeding {0} times, error count {2}"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.HardwareInfo": {
|
"ThingsGateway.Admin.Application.HardwareInfo": {
|
||||||
"Environment": "HostEnvironment",
|
"Environment": "HostEnvironment",
|
||||||
"FrameworkDescription": ".NETFramework",
|
"FrameworkDescription": ".NETFramework",
|
||||||
"OsArchitecture": "System Architecture",
|
"OsArchitecture": "System Architecture",
|
||||||
"UUID": "UUID",
|
"UpdateTime": "UpdateTime",
|
||||||
"UpdateTime": "UpdateTime"
|
"UUID": "UUID"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
|
|
||||||
"DriveUsage": "Disk Usage",
|
|
||||||
"MemoryUsage": "Memory",
|
|
||||||
"CpuUsage": "CPU Usage",
|
|
||||||
"Temperature": "Temperature",
|
|
||||||
"Battery": "Battery"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
//oper
|
|
||||||
"ThingsGateway.Admin.Application.OperDescAttribute": {
|
|
||||||
//dict
|
|
||||||
"SaveDict": "Modify dictionary",
|
|
||||||
"DeleteDict": "Delete dictionary",
|
|
||||||
"EditLoginPolicy": "Modify login policy",
|
|
||||||
"EditPasswordPolicy": "Modify password policy",
|
|
||||||
"EditPagePolicy": "Modify page policy",
|
|
||||||
"EditWebsitePolicy": "Modify website settings",
|
|
||||||
//operlog
|
|
||||||
"DeleteOperLog": "Delete operation log",
|
|
||||||
"ExportOperLog": "Export operation log",
|
|
||||||
|
|
||||||
//resource
|
|
||||||
"SaveResource": "Modify resource",
|
|
||||||
"DeleteResource": "Delete resource",
|
|
||||||
|
|
||||||
//role
|
|
||||||
"SaveRole": "Modify role",
|
|
||||||
"DeleteRole": "Delete role",
|
|
||||||
"RoleGrantResource": "Role grant resource",
|
|
||||||
"RoleGrantUser": "Role grant user",
|
|
||||||
"RoleGrantApiPermission": "Role grant OpenApi",
|
|
||||||
"GrantApi": "GrantApi",
|
|
||||||
"GrantUser": "GrantUser",
|
|
||||||
"GrantRole": "GrantRole",
|
|
||||||
"GrantResource": "GrantResource",
|
|
||||||
//user
|
|
||||||
"SaveUser": "Modify user",
|
|
||||||
"DeleteuSER": "Delete user",
|
|
||||||
"ResetPassword": "Reset pw",
|
|
||||||
"UserGrantRole": "User grant role",
|
|
||||||
"UserGrantResource": "User grant resource",
|
|
||||||
"UserGrantApiPermission": "User grant OpenApi",
|
|
||||||
|
|
||||||
//usercenter
|
|
||||||
"UpdateUserInfo": "Update personal information",
|
|
||||||
"WorkbenchInfo": "Update personal workbench",
|
|
||||||
"UpdatePassword": "Update personal password",
|
|
||||||
|
|
||||||
//session
|
|
||||||
"ExitVerificat": "Force token off",
|
|
||||||
"ExitSession": "Force session off",
|
|
||||||
|
|
||||||
|
|
||||||
"CopyOrg": "Copy Organization",
|
|
||||||
"DeleteOrg": "Delete Organization",
|
|
||||||
"SaveOrg": "Save Organization",
|
|
||||||
|
|
||||||
"DeletePosition": "Delete Position",
|
|
||||||
"SavePosition": "Save Position",
|
|
||||||
|
|
||||||
"NoPermission": "No Permission",
|
|
||||||
|
|
||||||
|
|
||||||
"CopyResource": "CopyResource",
|
|
||||||
"ChangeParentResource": "ChangeParentResource"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
//service
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.HardwareJob": {
|
"ThingsGateway.Admin.Application.HardwareJob": {
|
||||||
"GetHardwareInfoFail": "Get Hardwareinfo Fail"
|
"GetHardwareInfoFail": "Get Hardwareinfo Fail"
|
||||||
},
|
},
|
||||||
|
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
|
||||||
//dto
|
"Battery": "Battery",
|
||||||
"ThingsGateway.Admin.Application.UserSelectorOutput": {
|
"CpuUsage": "CPU Usage",
|
||||||
|
"DriveUsage": "Disk Usage",
|
||||||
|
"MemoryUsage": "Memory",
|
||||||
|
"Temperature": "Temperature"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
|
||||||
|
"Exception": "Exception",
|
||||||
|
"Login": "Login",
|
||||||
|
"Logout": "Logout",
|
||||||
|
"Operate": "Operation"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.LogEnum": {
|
||||||
|
"FAIL": "Fail",
|
||||||
|
"SUCCESS": "Success"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.LoginInput": {
|
||||||
"Account": "Account",
|
"Account": "Account",
|
||||||
"OrgId": "Org"
|
"Account.Required": "{0} is required",
|
||||||
|
"Password": "Password",
|
||||||
|
"Password.Required": "{0} is required"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.LoginPolicy": {
|
||||||
|
"ErrorCount": "Login error count lock threshold",
|
||||||
|
"ErrorCount.MinValue": "{0} value is too small",
|
||||||
|
"ErrorLockTime": "Login error lock duration (min)",
|
||||||
|
"ErrorLockTime.MinValue": "{0} value is too small",
|
||||||
|
"ErrorResetTime": "Login error count expiration duration (min)",
|
||||||
|
"ErrorResetTime.MinValue": "{0} value is too small",
|
||||||
|
"SingleOpen": "Single user login switch",
|
||||||
|
"VerificatExpireTime": "Login expiration time (min)",
|
||||||
|
"VerificatExpireTime.MinValue": "{0} value is too small"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.LogoutInput": {
|
||||||
|
"VerificatId.Required": "{0} is required"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.OpenApiAuthController": {
|
||||||
|
"LoginAsync": "Login",
|
||||||
|
"LogoutAsync": "Logout",
|
||||||
|
"OpenApiAuthController": "Login API"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.OperateLogPageInput": {
|
||||||
|
"Account": "Account",
|
||||||
|
"Category": "Category",
|
||||||
|
"SearchDate": "SearchDate"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.OperDescAttribute": {
|
||||||
|
"ChangeParentResource": "ChangeParentResource",
|
||||||
|
"CopyOrg": "Copy Organization",
|
||||||
|
"CopyResource": "CopyResource",
|
||||||
|
"DeleteDict": "Delete dictionary",
|
||||||
|
"DeleteOperLog": "Delete operation log",
|
||||||
|
"DeleteOrg": "Delete Organization",
|
||||||
|
"DeletePosition": "Delete Position",
|
||||||
|
"DeleteResource": "Delete resource",
|
||||||
|
"DeleteRole": "Delete role",
|
||||||
|
"DeleteuSER": "Delete user",
|
||||||
|
"EditLoginPolicy": "Modify login policy",
|
||||||
|
"EditPagePolicy": "Modify page policy",
|
||||||
|
"EditPasswordPolicy": "Modify password policy",
|
||||||
|
"EditWebsitePolicy": "Modify website settings",
|
||||||
|
"ExitSession": "Force session off",
|
||||||
|
"ExitVerificat": "Force token off",
|
||||||
|
"ExportOperLog": "Export operation log",
|
||||||
|
"GrantApi": "GrantApi",
|
||||||
|
"GrantResource": "GrantResource",
|
||||||
|
"GrantRole": "GrantRole",
|
||||||
|
"GrantUser": "GrantUser",
|
||||||
|
"NoPermission": "No Permission",
|
||||||
|
"ResetPassword": "Reset pw",
|
||||||
|
"RoleGrantApiPermission": "Role grant OpenApi",
|
||||||
|
"RoleGrantResource": "Role grant resource",
|
||||||
|
"RoleGrantUser": "Role grant user",
|
||||||
|
"SaveDict": "Modify dictionary",
|
||||||
|
"SaveOrg": "Save Organization",
|
||||||
|
"SavePosition": "Save Position",
|
||||||
|
"SaveResource": "Modify resource",
|
||||||
|
"SaveRole": "Modify role",
|
||||||
|
"SaveUser": "Modify user",
|
||||||
|
"UpdatePassword": "Update personal password",
|
||||||
|
"UpdateUserInfo": "Update personal information",
|
||||||
|
"UserGrantApiPermission": "User grant OpenApi",
|
||||||
|
"UserGrantResource": "User grant resource",
|
||||||
|
"UserGrantRole": "User grant role",
|
||||||
|
"WorkbenchInfo": "Update personal workbench"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.OrgEnum": {
|
||||||
|
"COMPANY": "Company",
|
||||||
|
"DEPT": "Dept"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.PagePolicy": {
|
||||||
|
"Razor": "Default homepage",
|
||||||
|
"Shortcuts": "Default shortcuts"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.PasswordPolicy": {
|
||||||
|
"DefaultPassword": "Default user password",
|
||||||
|
"DefaultPassword.Required": "{0} is required",
|
||||||
|
"PasswordContainChar": "Contain special characters",
|
||||||
|
"PasswordContainLower": "Contain lowercase letters",
|
||||||
|
"PasswordContainNum": "Contain numbers",
|
||||||
|
"PasswordContainUpper": "Contain uppercase letters",
|
||||||
|
"PasswordMinLen": "Minimum password length",
|
||||||
|
"PasswordMinLen.MinValue": "{0} value is too small"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.PositionCategoryEnum": {
|
||||||
|
"HIGH": "High",
|
||||||
|
"LOW": "Low",
|
||||||
|
"MIDDLE": "Middle"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
|
||||||
|
"Button": "Button",
|
||||||
|
"Menu": "Menu",
|
||||||
|
"Module": "Module"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.ResourceTableSearchModel": {
|
"ThingsGateway.Admin.Application.ResourceTableSearchModel": {
|
||||||
"Module": "Module",
|
|
||||||
"Href": "Path",
|
"Href": "Path",
|
||||||
|
"Module": "Module",
|
||||||
"Title": "Title"
|
"Title": "Title"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.WorkbenchInfo": {
|
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
|
||||||
"Razor": "Homepage",
|
"Global": "Global",
|
||||||
"Shortcuts": "Shortcuts"
|
"Org": "Org"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
|
|
||||||
"Password": "Password",
|
|
||||||
"NewPassword": "New password",
|
|
||||||
"ConfirmPassword": "Confirm password",
|
|
||||||
"Password.Required": "{0} is required",
|
|
||||||
"NewPassword.Required": "{0} is required",
|
|
||||||
"ConfirmPassword.Required": "{0} is required"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.VerificatInfo": {
|
|
||||||
"Expire": "Expire(min)",
|
|
||||||
"Online": "Online",
|
|
||||||
"VerificatTimeout": "VerificatTimeout",
|
|
||||||
"Device": "Device",
|
|
||||||
"LoginIp": "LoginIp",
|
|
||||||
"LoginTime": "LoginTime"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.SessionOutput": {
|
"ThingsGateway.Admin.Application.SessionOutput": {
|
||||||
"Account": "Account",
|
"Account": "Account",
|
||||||
"Online": "Online status",
|
|
||||||
"LatestLoginIp": "Latest login IP",
|
"LatestLoginIp": "Latest login IP",
|
||||||
"LatestLoginTime": "Latest login time",
|
"LatestLoginTime": "Latest login time",
|
||||||
|
"Online": "Online status",
|
||||||
"VerificatCount": "Token count"
|
"VerificatCount": "Token count"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.SysDict": {
|
"ThingsGateway.Admin.Application.SysDict": {
|
||||||
"Category.Required": "{0} is required",
|
|
||||||
"Name.Required": "{0} is required",
|
|
||||||
"Code.Required": "{0} is required",
|
|
||||||
"Category": "Category",
|
"Category": "Category",
|
||||||
"Name": "Name",
|
"Category.Required": "{0} is required",
|
||||||
"Code": "Code",
|
"Code": "Code",
|
||||||
"Remark": "Remark",
|
"Code.Required": "{0} is required",
|
||||||
"SortCode": "Sort",
|
|
||||||
"CreateTime": "CreateTime",
|
|
||||||
"UpdateTime": "UpdateTime",
|
|
||||||
"DemoCanotUpdateWebsitePolicy": "DEMO environment does not allow modifying website settings",
|
"DemoCanotUpdateWebsitePolicy": "DEMO environment does not allow modifying website settings",
|
||||||
"DictDup": "Duplicate configuration exists, category {0}, name {1}"
|
"DictDup": "Duplicate configuration exists, category {0}, name {1}",
|
||||||
|
"Name": "Name",
|
||||||
|
"Name.Required": "{0} is required",
|
||||||
|
"Remark": "Remark"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.SysOperateLog": {
|
"ThingsGateway.Admin.Application.SysOperateLog": {
|
||||||
|
"Category": "Category",
|
||||||
"ClassName": "ClassName",
|
"ClassName": "ClassName",
|
||||||
"ExeMessage": "ExeMessage",
|
"ExeMessage": "ExeMessage",
|
||||||
"MethodName": "MethodName",
|
|
||||||
"ParamJson": "ParamJson",
|
|
||||||
"ReqMethod": "RequestMethod",
|
|
||||||
"ReqUrl": "RequestUrl",
|
|
||||||
"ResultJson": "ResultJson",
|
|
||||||
"Category": "Category",
|
|
||||||
"ExeStatus": "ExeStatus",
|
"ExeStatus": "ExeStatus",
|
||||||
|
"MethodName": "MethodName",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"OpAccount": "OpAccount",
|
"OpAccount": "OpAccount",
|
||||||
"OpBrowser": "OpBrowser",
|
"OpBrowser": "OpBrowser",
|
||||||
"OpIp": "OpIp",
|
"OpIp": "OpIp",
|
||||||
"OpOs": "OpOs",
|
"OpOs": "OpOs",
|
||||||
"OpTime": "OpTime",
|
"OpTime": "OpTime",
|
||||||
|
"ParamJson": "ParamJson",
|
||||||
|
"ReqMethod": "RequestMethod",
|
||||||
|
"ReqUrl": "RequestUrl",
|
||||||
|
"ResultJson": "ResultJson",
|
||||||
"VerificatId": "VerificatId"
|
"VerificatId": "VerificatId"
|
||||||
|
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.OperateLogPageInput": {
|
"ThingsGateway.Admin.Application.SysOrg": {
|
||||||
"SearchDate": "SearchDate",
|
"CanotContainsSelf": "Cannot contain itself",
|
||||||
"Account": "Account",
|
"Category": "Category",
|
||||||
"Category": "Category"
|
"Category.Required": "{0} is a required field",
|
||||||
|
"Code": "Code",
|
||||||
|
"Code.Required": "{0} is a required field",
|
||||||
|
"CodeDup": "Duplicate code {0} exists",
|
||||||
|
"DeletePositionFirst": "Please remove the positions under the organization first",
|
||||||
|
"DeleteRoleFirst": "Please remove the roles under the organization first",
|
||||||
|
"DeleteUserFirst": "Please remove the users under the organization first",
|
||||||
|
"DirectorId": "Director",
|
||||||
|
"Dup": "Duplicate organization exists with Category {0} and Name {1}",
|
||||||
|
"Name": "Name",
|
||||||
|
"Name.Required": "{0} is a required field",
|
||||||
|
"NameDup": "Duplicate name {0} exists",
|
||||||
|
"Names": "Names",
|
||||||
|
"ParentChoiceSelf": "Parent cannot be itself",
|
||||||
|
"ParentId": "ParentOrg",
|
||||||
|
"ParentNull": "Parent does not exist {0}",
|
||||||
|
"Remark": "Remarks",
|
||||||
|
"RootOrg": "Unable to create top-level organization",
|
||||||
|
"Status": "Status",
|
||||||
|
"TargetNameDup": "Target node has duplicate name {0}"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.LoginInput": {
|
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
|
||||||
"Account": "Account",
|
"ContainsChild": "ContainsChild",
|
||||||
"Password": "Password",
|
"ContainsPosition": "ContainsPosition",
|
||||||
"Account.Required": "{0} is required",
|
"TargetId": "Target"
|
||||||
"Password.Required": "{0} is required"
|
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.LogoutInput": {
|
"ThingsGateway.Admin.Application.SysPosition": {
|
||||||
"VerificatId.Required": "{0} is required"
|
"CanotContainsSelf": "Cannot contain itself",
|
||||||
|
"Category": "Category",
|
||||||
|
"Category.Required": "{0} is a required field",
|
||||||
|
"Code": "Code",
|
||||||
|
"Code.Required": "{0} is a required field",
|
||||||
|
"CodeDup": "Duplicate code {0} exists",
|
||||||
|
"DeleteUserFirst": "Please remove the users under the position first",
|
||||||
|
"Dup": "Duplicate position exists with Category {0} and Name {1}",
|
||||||
|
"Name": "Name",
|
||||||
|
"Name.Required": "{0} is a required field",
|
||||||
|
"NameDup": "Duplicate name {0} exists",
|
||||||
|
"OrgId": "Organization",
|
||||||
|
"OrgId.MinValue": "{0} is a required field",
|
||||||
|
"ParentChoiceSelf": "Parent cannot be itself",
|
||||||
|
"ParentNull": "Parent does not exist {0}",
|
||||||
|
"Remark": "Remarks",
|
||||||
|
"Status": "Status",
|
||||||
|
"TargetNameDup": "Target node has duplicate name {0}"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.AppConfig": {
|
"ThingsGateway.Admin.Application.SysResource": {
|
||||||
"LoginPolicy": "LoginPolicy",
|
"CanotDeleteSystemResource": "Cannot delete system resource {0}",
|
||||||
"PasswordPolicy": "PasswordPolicy",
|
"Category": "Category",
|
||||||
"PagePolicy": "PagePolicy",
|
"Code": "Code",
|
||||||
"WebsitePolicy": "WebsitePolicy"
|
"Href": "Path",
|
||||||
},
|
"Href.Required": "{0} is required",
|
||||||
"ThingsGateway.Admin.Application.LoginPolicy": {
|
"Icon": "Icon",
|
||||||
"SingleOpen": "Single user login switch",
|
|
||||||
"ErrorLockTime": "Login error lock duration (min)",
|
|
||||||
"ErrorResetTime": "Login error count expiration duration (min)",
|
|
||||||
"ErrorCount": "Login error count lock threshold",
|
|
||||||
"VerificatExpireTime": "Login expiration time (min)",
|
|
||||||
"ErrorLockTime.MinValue": "{0} value is too small",
|
|
||||||
"ErrorResetTime.MinValue": "{0} value is too small",
|
|
||||||
"ErrorCount.MinValue": "{0} value is too small",
|
|
||||||
"VerificatExpireTime.MinValue": "{0} value is too small"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.PagePolicy": {
|
|
||||||
"Shortcuts": "Default shortcuts",
|
|
||||||
"Razor": "Default homepage"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.PasswordPolicy": {
|
|
||||||
"DefaultPassword": "Default user password",
|
|
||||||
"DefaultPassword.Required": "{0} is required",
|
|
||||||
"PasswordMinLen": "Minimum password length",
|
|
||||||
"PasswordMinLen.MinValue": "{0} value is too small",
|
|
||||||
"PasswordContainNum": "Contain numbers",
|
|
||||||
"PasswordContainLower": "Contain lowercase letters",
|
|
||||||
"PasswordContainUpper": "Contain uppercase letters",
|
|
||||||
"PasswordContainChar": "Contain special characters"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.WebsitePolicy": {
|
|
||||||
"WebStatus": "WebStatus",
|
|
||||||
"CloseTip": "CloseTip",
|
|
||||||
"CloseTip.Required": "{0} is required"
|
|
||||||
},
|
|
||||||
//enum
|
|
||||||
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
|
|
||||||
"Module": "Module",
|
"Module": "Module",
|
||||||
"Menu": "Menu",
|
"ModuleIdDiff": "Module is inconsistent with parent menu",
|
||||||
"Button": "Button"
|
"NavLinkMatch": "NavLinkMatch",
|
||||||
|
"NotFoundResource": "System exception, menu not found",
|
||||||
|
"ParentId": "Parent",
|
||||||
|
"ResourceDup": "Duplicate name {0} exists",
|
||||||
|
"ResourceMenuHrefNotNull": "Menu href cannot null",
|
||||||
|
"ResourceParentChoiceSelf": "Parent cannot choose itself",
|
||||||
|
"ResourceParentNull": "Parent does not exist {0}",
|
||||||
|
"Target": "Target",
|
||||||
|
"Title": "Title",
|
||||||
|
"Title.Required": "{0} is required"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.SysRole": {
|
||||||
|
"CannotRoleScopeAll": "Organization role cannot select global data scope",
|
||||||
|
"CanotDeleteAdmin": "Cannot delete built-in super admin role",
|
||||||
|
"CanotEditAdmin": "Cannot edit super admin role",
|
||||||
|
"CanotGrantAdmin": "Cannot assign admins roles",
|
||||||
|
"Category": "Category",
|
||||||
|
"Code": "Code",
|
||||||
|
"CodeDup": "Duplicate code exists: {0}",
|
||||||
|
"Global": "Global",
|
||||||
|
"Name": "Name",
|
||||||
|
"Name.Required": "{0} is required",
|
||||||
|
"NameDup": "Duplicate role name {0}",
|
||||||
|
"OrgId": "Org",
|
||||||
|
"OrgNotNull": "Organization cannot be null",
|
||||||
|
"SameOrgNameDup": "Duplicate role name exists: {0}",
|
||||||
|
"Status": "Status"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.SysUser": {
|
||||||
|
"Account": "Account",
|
||||||
|
"Account.Required": "Account.Required",
|
||||||
|
"AccountDup": "Duplicate account {0} exists",
|
||||||
|
"Avatar": "Avatar",
|
||||||
|
"CanotDeleteAdminUser": "Cannot delete built-in super admin user",
|
||||||
|
"CanotDeleteSelf": "Cannot delete yourself",
|
||||||
|
"CanotEditAdminUser": "Cannot edit super admin user",
|
||||||
|
"CanotGrantAdmin": "Cannot assign admins roles",
|
||||||
|
"CheckSelf": "Prohibit {0} yourself",
|
||||||
|
"ConfirmPasswordDiff": "Passwords entered twice are inconsistent",
|
||||||
|
"DemoCanotUpdatePassword": "DEMO environment does not allow password modification",
|
||||||
|
"DirectorId": "Director",
|
||||||
|
"DirectorSelf": "Cannot set oneself as the supervisor",
|
||||||
|
"Disable": "Disable",
|
||||||
|
"Email": "Email",
|
||||||
|
"EmailDup": "Duplicate email {0} exists",
|
||||||
|
"EmailError": "Email format error {0}",
|
||||||
|
"Enable": "Enable",
|
||||||
|
"ExitVerificat": "You have been forcibly logged out",
|
||||||
|
"GrantRole": "GrantRole",
|
||||||
|
"LastLoginAddress": "LastLoginAddress",
|
||||||
|
"LastLoginDevice": "LastLoginDevice",
|
||||||
|
"LastLoginIp": "LastLoginIp",
|
||||||
|
"LastLoginTime": "LastLoginTime",
|
||||||
|
"LatestLoginAddress": "LatestLoginAddress",
|
||||||
|
"LatestLoginDevice": "LatestLoginDevice",
|
||||||
|
"LatestLoginIp": "LatestLoginIp",
|
||||||
|
"LatestLoginTime": "LatestLoginTime",
|
||||||
|
"NoOrg": "The organization does not exist",
|
||||||
|
"OldPasswordError": "Incorrect old password",
|
||||||
|
"OrgId": "Org",
|
||||||
|
"OrgNames": "OrgNames",
|
||||||
|
"Password": "Password",
|
||||||
|
"PasswordEdited": "Password changed, logged out",
|
||||||
|
"PasswordLengthLess": "Password length cannot be less than {0}",
|
||||||
|
"PasswordMustLow": "Password must contain lowercase letters",
|
||||||
|
"PasswordMustNum": "Password must contain numbers",
|
||||||
|
"PasswordMustSpecial": "Password must contain special characters",
|
||||||
|
"PasswordMustUpp": "Password must contain uppercase letters",
|
||||||
|
"Phone": "Phone",
|
||||||
|
"PhoneError": "Phone number format error {0}",
|
||||||
|
"PositionId": "Position",
|
||||||
|
"PositionName": "PositionName",
|
||||||
|
"Status": "Status"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.TargetEnum": {
|
"ThingsGateway.Admin.Application.TargetEnum": {
|
||||||
"_self": "Current window",
|
|
||||||
"_blank": "New window",
|
"_blank": "New window",
|
||||||
"_parent": "Parent window",
|
"_parent": "Parent window",
|
||||||
|
"_self": "Current window",
|
||||||
"_top": "Top window"
|
"_top": "Top window"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Application.DictTypeEnum": {
|
"ThingsGateway.Admin.Application.TestController": {
|
||||||
"System": "System",
|
"Test": "Test",
|
||||||
"Define": "Business"
|
"TestController": "Test API"
|
||||||
},
|
},
|
||||||
|
"ThingsGateway.Admin.Application.UnifyResultProvider": {
|
||||||
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
|
"NoPermission": "Access denied, no permission",
|
||||||
"Login": "Login",
|
"TokenOver": "Login has expired, please login again"
|
||||||
"Logout": "Logout",
|
|
||||||
"Operate": "Operation",
|
|
||||||
"Exception": "Exception"
|
|
||||||
},
|
},
|
||||||
|
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
|
||||||
"ThingsGateway.Admin.Application.LogEnum": {
|
"ConfirmPassword": "Confirm password",
|
||||||
"SUCCESS": "Success",
|
"ConfirmPassword.Required": "{0} is required",
|
||||||
"FAIL": "Fail"
|
"NewPassword": "New password",
|
||||||
|
"NewPassword.Required": "{0} is required",
|
||||||
|
"Password": "Password",
|
||||||
|
"Password.Required": "{0} is required"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.UserSelectorOutput": {
|
||||||
|
"Account": "Account",
|
||||||
|
"OrgId": "Org"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.VerificatInfo": {
|
||||||
|
"Device": "Device",
|
||||||
|
"Expire": "Expire(min)",
|
||||||
|
"LoginIp": "LoginIp",
|
||||||
|
"LoginTime": "LoginTime",
|
||||||
|
"Online": "Online",
|
||||||
|
"VerificatTimeout": "VerificatTimeout"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.WebsitePolicy": {
|
||||||
|
"CloseTip": "CloseTip",
|
||||||
|
"CloseTip.Required": "{0} is required",
|
||||||
|
"WebStatus": "WebStatus"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.WorkbenchInfo": {
|
||||||
|
"Razor": "Homepage",
|
||||||
|
"Shortcuts": "Shortcuts"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,469 +1,402 @@
|
|||||||
{
|
{
|
||||||
|
"ThingsGateway.Admin.Application.AppConfig": {
|
||||||
|
"LoginPolicy": "登录策略",
|
||||||
|
"PagePolicy": "页面设置",
|
||||||
|
"PasswordPolicy": "密码策略",
|
||||||
|
"WebsitePolicy": "网站设置"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.AuthController": {
|
||||||
|
"AuthController": "登录API",
|
||||||
|
"LoginAsync": "登录",
|
||||||
|
"LogoutAsync": "注销"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.AuthService": {
|
||||||
|
"AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ",
|
||||||
|
"MustDesc": "密码需要DESC加密后传入",
|
||||||
|
"OrgDisable": "所属公司/部门已停用,请联系管理员",
|
||||||
|
"PasswordError": "密码错误次数过多,请 {0} 分钟后再试",
|
||||||
|
"SingleLoginWarn": "您的账号已在别处登录",
|
||||||
|
"TenantNull": "租户不存在",
|
||||||
|
"UserDisable": "账号 {0} 已停用",
|
||||||
|
"UserNoModule": "该账号未分配模块,请联系管理员",
|
||||||
|
"UserNull": "用户 {0} 不存在"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.BaseDataEntity": {
|
||||||
|
"CreateOrgId": "创建机构Id"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Application.BaseEntity": {
|
||||||
|
"CreateTime": "创建时间",
|
||||||
|
"CreateUser": "创建人",
|
||||||
|
"SortCode": "排序",
|
||||||
|
"UpdateTime": "更新时间",
|
||||||
|
"UpdateUser": "更新人"
|
||||||
|
},
|
||||||
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
|
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
|
||||||
"UserExpire": "用户登录已过期,请重新登录"
|
"UserExpire": "用户登录已过期,请重新登录"
|
||||||
},
|
},
|
||||||
|
"ThingsGateway.Admin.Application.DataScopeEnum": {
|
||||||
"ThingsGateway.Admin.Application.SysUser": {
|
"SCOPE_ALL": "全部",
|
||||||
"Disable": "禁用",
|
"SCOPE_ORG": "仅所属组织",
|
||||||
"Enable": "启用",
|
"SCOPE_ORG_CHILD": "所属组织及以下",
|
||||||
"GrantRole": "分配角色",
|
"SCOPE_ORG_DEFINE": "自定义",
|
||||||
"ExitVerificat": "您已被强制下线",
|
"SCOPE_SELF": "仅自己"
|
||||||
"PasswordEdited": "密码被修改,已退出登录",
|
},
|
||||||
"Avatar": "头像",
|
"ThingsGateway.Admin.Application.DefaultDataScope": {
|
||||||
"Account": "账号",
|
"ScopeCategory": "数据范围",
|
||||||
"Account.Required": " {0} 是必填项",
|
"ScopeDefineOrgIdList": "自定义列表"
|
||||||
"Password": "密码",
|
},
|
||||||
"Status": "状态",
|
"ThingsGateway.Admin.Application.DictTypeEnum": {
|
||||||
"Phone": "手机",
|
"Define": "业务配置",
|
||||||
"Email": "邮箱",
|
"System": "系统配置"
|
||||||
"LastLoginIp": "上次登录ip",
|
},
|
||||||
"LastLoginDevice": "上次登录设备",
|
"ThingsGateway.Admin.Application.FileService": {
|
||||||
"LastLoginTime": "上次登录时间",
|
"FileLengthError": "文件大小不允许超过 {0} M",
|
||||||
"LastLoginAddress": "上次登录地点",
|
"FileNullError": "文件不能为空",
|
||||||
"LatestLoginIp": "最新登录ip",
|
"FileTypeError": "不支持 {0} 格式"
|
||||||
"LatestLoginTime": "最新登录时间",
|
},
|
||||||
"LatestLoginDevice": "最新登录设备",
|
"ThingsGateway.Admin.Application.HardwareInfo": {
|
||||||
"LatestLoginAddress": "最新登录地点",
|
"Environment": "主机环境",
|
||||||
"SortCode": "排序",
|
"FrameworkDescription": "NET框架",
|
||||||
"CreateTime": "创建时间",
|
"OsArchitecture": "系统架构",
|
||||||
"UpdateTime": "更新时间",
|
"UpdateTime": "更新时间",
|
||||||
"OrgNames": "机构名称",
|
"UUID": "唯一编码"
|
||||||
"PositionName": "职位名称",
|
},
|
||||||
"OrgId": "机构",
|
"ThingsGateway.Admin.Application.HardwareJob": {
|
||||||
"PositionId": "职位",
|
"GetHardwareInfoFail": "获取硬件信息出错"
|
||||||
"DirectorId": "主管",
|
},
|
||||||
"CheckSelf": "禁止 {0} 自己",
|
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
|
||||||
"CanotDeleteAdminUser": "不可删除系统内置超管用户",
|
"Battery": "电池",
|
||||||
"CanotEditAdminUser": "不可编辑超管用户",
|
"CpuUsage": "CPU使用率",
|
||||||
"CanotGrantAdmin": "不能分配超管角色",
|
"DriveUsage": "磁盘使用率",
|
||||||
"EmailDup": "存在重复的邮箱 {0}",
|
"MemoryUsage": "内存",
|
||||||
"AccountDup": "存在重复的账号 {0}",
|
"Temperature": "温度"
|
||||||
"CanotDeleteSelf": "不可删除自己",
|
},
|
||||||
"EmailError": "邮箱 {0} 格式错误",
|
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
|
||||||
"PhoneError": "手机号码 {0} 格式错误",
|
"Exception": "异常",
|
||||||
"NoOrg": "组织机构不存在",
|
"Login": "登录",
|
||||||
"DirectorSelf": "不能设置自己为主管",
|
"Logout": "注销",
|
||||||
|
"Operate": "操作"
|
||||||
|
},
|
||||||
"DemoCanotUpdatePassword": "DEMO环境不允许修改密码",
|
"ThingsGateway.Admin.Application.LogEnum": {
|
||||||
"OldPasswordError": "原密码错误",
|
"FAIL": "失败",
|
||||||
"ConfirmPasswordDiff": "两次输入的密码不一致",
|
"SUCCESS": "成功"
|
||||||
"PasswordLengthLess": "密码长度不能小于 {0} ",
|
},
|
||||||
"PasswordMustNum ": "密码必须包含数字",
|
"ThingsGateway.Admin.Application.LoginInput": {
|
||||||
"PasswordMustLow": "密码必须包含小写字母",
|
"Account": "登录账号",
|
||||||
"PasswordMustUpp": "密码必须包含大写字母",
|
"Account.Required": "{0} 是必填项",
|
||||||
"PasswordMustSpecial": "密码必须包含特殊字符"
|
"Password": "登录密码",
|
||||||
},
|
"Password.Required": "{0} 是必填项"
|
||||||
|
},
|
||||||
"ThingsGateway.Admin.Application.SysRole": {
|
"ThingsGateway.Admin.Application.LoginPolicy": {
|
||||||
"Code": "编码",
|
"ErrorCount": "登录错误次数锁定阈值",
|
||||||
"Name": "名称",
|
"ErrorCount.MinValue": " {0} 值太小",
|
||||||
"Name.Required": " {0} 是必填项",
|
"ErrorLockTime": "登录错误锁定时长(分)",
|
||||||
"Category": "分类",
|
"ErrorLockTime.MinValue": " {0} 值太小",
|
||||||
"SortCode": "排序",
|
"ErrorResetTime": "登录错误次数过期时长(分)",
|
||||||
"Global": "全局",
|
"ErrorResetTime.MinValue": " {0} 值太小",
|
||||||
"Status": "状态",
|
"SingleOpen": "单用户登录开关",
|
||||||
"OrgId": "机构",
|
"VerificatExpireTime": "登录过期时间(分)",
|
||||||
"CreateTime": "创建时间",
|
"VerificatExpireTime.MinValue": " {0} 值太小"
|
||||||
"UpdateTime": "更新时间",
|
},
|
||||||
|
"ThingsGateway.Admin.Application.LogoutInput": {
|
||||||
"CanotDeleteAdmin": "不可删除系统内置超管角色",
|
"VerificatId.Required": "{0} 是必填项"
|
||||||
"CanotEditAdmin": "不可编辑超管角色",
|
},
|
||||||
"CanotGrantAdmin": "不能分配超管角色",
|
"ThingsGateway.Admin.Application.OpenApiAuthController": {
|
||||||
|
"LoginAsync": "登录",
|
||||||
"NameDup": "存在重复的角色名称 {0}",
|
"LogoutAsync": "注销",
|
||||||
"OrgNotNull": "机构不能为空",
|
"OpenApiAuthController": "登录API"
|
||||||
"SameOrgNameDup": "存在重复的角色名称 {0}",
|
},
|
||||||
"CannotRoleScopeAll": "机构角色不能选择全局数据范围",
|
"ThingsGateway.Admin.Application.OperateLogPageInput": {
|
||||||
"CodeDup": "存在重复的编码 {0}"
|
"Account": "操作账号",
|
||||||
},
|
"Category": "分类",
|
||||||
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
|
"SearchDate": "时间范围"
|
||||||
"Global": "全局",
|
},
|
||||||
"Org": "机构"
|
"ThingsGateway.Admin.Application.OperDescAttribute": {
|
||||||
},
|
"ChangeParentResource": "更改父节点",
|
||||||
"ThingsGateway.Admin.Application.DataScopeEnum": {
|
"CopyOrg": "复制机构",
|
||||||
"SCOPE_SELF": "仅自己",
|
"CopyResource": "复制资源",
|
||||||
"SCOPE_ALL": "全部",
|
"DeleteDict": "删除字典",
|
||||||
"SCOPE_ORG": "仅所属组织",
|
"DeleteOperLog": "删除操作日志",
|
||||||
"SCOPE_ORG_CHILD": "所属组织及以下",
|
"DeleteOrg": "删除机构",
|
||||||
"SCOPE_ORG_DEFINE": "自定义"
|
"DeletePosition": "删除岗位",
|
||||||
},
|
"DeleteResource": "删除资源",
|
||||||
"ThingsGateway.Admin.Application.DefaultDataScope": {
|
"DeleteRole": "删除角色",
|
||||||
"ScopeCategory": "数据范围",
|
"DeleteuSER": "删除用户",
|
||||||
"ScopeDefineOrgIdList": "自定义列表"
|
"EditLoginPolicy": "修改登录策略",
|
||||||
},
|
"EditPagePolicy": "修改页面策略",
|
||||||
"ThingsGateway.Admin.Application.SysResource": {
|
"EditPasswordPolicy": "修改密码策略",
|
||||||
"Title": "标题",
|
"EditWebsitePolicy": "修改网站设置",
|
||||||
"Module": "模块",
|
"ExitSession": "强退会话",
|
||||||
"Title.Required": "{0} 是必填项",
|
"ExitVerificat": "强退令牌",
|
||||||
"Href.Required": "{0} 是必填项",
|
"ExportOperLog": "导出操作日志",
|
||||||
"Icon": "图标",
|
"GrantApi": "API",
|
||||||
"Href": "路径",
|
"GrantResource": "资源",
|
||||||
"Code": "编码",
|
"GrantRole": "角色",
|
||||||
"Category": "分类",
|
"GrantUser": "用户",
|
||||||
"Target": "跳转类型",
|
"NoPermission": "无权限操作",
|
||||||
"NavLinkMatch": "匹配类型",
|
"ResetPassword": "重置密码",
|
||||||
"SortCode": "排序",
|
"RoleGrantApiPermission": "角色授权OpenApi",
|
||||||
"ParentId": "上级菜单",
|
"RoleGrantResource": "角色授权资源",
|
||||||
"CreateTime": "创建时间",
|
"RoleGrantUser": "角色授权用户",
|
||||||
"UpdateTime": "更新时间",
|
"SaveDict": "修改字典",
|
||||||
"ResourceDup": "存在重复的名称 {0}",
|
"SaveOrg": "保存机构",
|
||||||
"ResourceParentChoiceSelf": "父级不能选择自己",
|
"SavePosition": "保存岗位",
|
||||||
"ResourceParentNull": "父级不存在 {0}",
|
"SaveResource": "修改资源",
|
||||||
"NotFoundResource": "系统异常,没找到该菜单",
|
"SaveRole": "修改角色",
|
||||||
"ModuleIdDiff": "模块与上级菜单不一致",
|
"SaveUser": "修改用户",
|
||||||
"CanotDeleteSystemResource": "不可删除系统资源 {0}",
|
"UpdatePassword": "更新个人密码",
|
||||||
"ResourceMenuHrefNotNull": "菜单的路径不能为空"
|
"UpdateUserInfo": "更新个人信息",
|
||||||
},
|
"UserGrantApiPermission": "用户授权OpenApi",
|
||||||
|
"UserGrantResource": "用户授权资源",
|
||||||
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
|
"UserGrantRole": "用户授权角色",
|
||||||
"TargetId": "目标机构",
|
"WorkbenchInfo": "更新个人工作台"
|
||||||
"ContainsChild": "包含下级",
|
},
|
||||||
"ContainsPosition": "包含职位"
|
"ThingsGateway.Admin.Application.OrgEnum": {
|
||||||
},
|
"COMPANY": "公司",
|
||||||
"ThingsGateway.Admin.Application.SysPosition": {
|
"DEPT": "部门"
|
||||||
"Category.Required": "{0} 是必填项",
|
},
|
||||||
"Name.Required": "{0} 是必填项",
|
"ThingsGateway.Admin.Application.PagePolicy": {
|
||||||
"Code.Required": "{0} 是必填项",
|
"Razor": "默认主页",
|
||||||
"OrgId.MinValue": "{0} 是必填项",
|
"Shortcuts": "默认快捷方式"
|
||||||
"Category": "分类",
|
},
|
||||||
"Name": "名称",
|
"ThingsGateway.Admin.Application.PasswordPolicy": {
|
||||||
"Code": "代码",
|
"DefaultPassword": "默认用户密码",
|
||||||
"Status": "状态",
|
"DefaultPassword.Required": " {0} 是必填项",
|
||||||
"OrgId": "机构",
|
"PasswordContainChar": "包含特殊字符",
|
||||||
"Remark": "备注",
|
"PasswordContainLower": "包含小写字母",
|
||||||
"SortCode": "排序",
|
"PasswordContainNum": "包含数字",
|
||||||
"CreateTime": "创建时间",
|
"PasswordContainUpper": "包含大写字母",
|
||||||
"UpdateTime": "更新时间",
|
"PasswordMinLen": "密码最小长度",
|
||||||
"Dup": "存在重复的岗位 分类 {0} 名称 {1}",
|
"PasswordMinLen.MinValue": " {0} 值太小"
|
||||||
"CodeDup": "存在重复的编码 {0}",
|
},
|
||||||
"NameDup": "存在重复的名称 {0}",
|
"ThingsGateway.Admin.Application.PositionCategoryEnum": {
|
||||||
"CanotContainsSelf": "不可包含自己",
|
"HIGH": "高层",
|
||||||
"TargetNameDup": "目标节点存在重复的名称 {0}",
|
"LOW": "低层",
|
||||||
"ParentChoiceSelf": "父级不能选择自己",
|
"MIDDLE": "中层"
|
||||||
"ParentNull": "父级不存在 {0}",
|
},
|
||||||
"DeleteUserFirst": "请先删除职位下的用户"
|
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
|
||||||
|
"Button": "按钮",
|
||||||
},
|
"Menu": "菜单",
|
||||||
|
"Module": "模块"
|
||||||
"ThingsGateway.Admin.Application.SysOrg": {
|
},
|
||||||
"Category.Required": "{0} 是必填项",
|
"ThingsGateway.Admin.Application.ResourceTableSearchModel": {
|
||||||
"Name.Required": "{0} 是必填项",
|
"Href": "路径",
|
||||||
"Code.Required": "{0} 是必填项",
|
"Module": "模块",
|
||||||
"Category": "分类",
|
"Title": "标题"
|
||||||
"Name": "名称",
|
},
|
||||||
"Code": "代码",
|
"ThingsGateway.Admin.Application.RoleCategoryEnum": {
|
||||||
"Status": "状态",
|
"Global": "全局",
|
||||||
"ParentId": "上级机构",
|
"Org": "机构"
|
||||||
"Names": "机构全称",
|
},
|
||||||
"Remark": "备注",
|
"ThingsGateway.Admin.Application.SessionOutput": {
|
||||||
"DirectorId": "主管",
|
"Account": "账号",
|
||||||
"SortCode": "排序",
|
"LatestLoginIp": "最新登录ip",
|
||||||
"CreateTime": "创建时间",
|
"LatestLoginTime": "最新登录时间",
|
||||||
"UpdateTime": "更新时间",
|
"Online": "在线状态",
|
||||||
"Dup": "存在重复的机构 分类 {0} 名称 {1}",
|
"VerificatCount": "令牌数量"
|
||||||
"CodeDup": "存在重复的编码 {0}",
|
},
|
||||||
"NameDup": "存在重复的名称 {0}",
|
"ThingsGateway.Admin.Application.SysDict": {
|
||||||
"CanotContainsSelf": "不可包含自己",
|
"Category": "分类",
|
||||||
"TargetNameDup": "目标节点存在重复的名称 {0}",
|
"Category.Required": "{0} 是必填项",
|
||||||
"ParentChoiceSelf": "父级不能选择自己",
|
"Code": "代码",
|
||||||
"ParentNull": "父级不存在 {0}",
|
"Code.Required": "{0} 是必填项",
|
||||||
"DeleteUserFirst": "请先删除机构下的用户",
|
"DemoCanotUpdateWebsitePolicy": "DEMO环境不允许修改网站设置",
|
||||||
"DeleteRoleFirst": "请先删除机构下的角色",
|
"DictDup": "存在重复的配置 分类 {0} 名称 {1}",
|
||||||
"DeletePositionFirst": "请先删除机构下的职位",
|
"Name": "名称",
|
||||||
"RootOrg": "无法创建顶层机构"
|
"Name.Required": "{0} 是必填项",
|
||||||
},
|
"Remark": "备注"
|
||||||
"ThingsGateway.Admin.Application.OrgEnum": {
|
},
|
||||||
"COMPANY": "公司",
|
"ThingsGateway.Admin.Application.SysOperateLog": {
|
||||||
"DEPT": "部门"
|
"Category": "日志分类",
|
||||||
},
|
"ClassName": "类名",
|
||||||
"ThingsGateway.Admin.Application.PositionCategoryEnum": {
|
"ExeMessage": "具体消息",
|
||||||
"HIGH": "高层",
|
"ExeStatus": "执行状态",
|
||||||
"MIDDLE": "中层",
|
"MethodName": "方法名称",
|
||||||
"LOW": "低层"
|
"Name": "日志名称",
|
||||||
},
|
"OpAccount": "账号",
|
||||||
|
"OpBrowser": "浏览器",
|
||||||
//controller
|
"OpIp": "ip",
|
||||||
"ThingsGateway.Admin.Application.AuthController": {
|
"OpOs": "系统",
|
||||||
//auth
|
"OpTime": "操作时间",
|
||||||
"AuthController": "登录API",
|
"ParamJson": "请求参数",
|
||||||
"LoginAsync": "登录",
|
"ReqMethod": "请求方式",
|
||||||
"LogoutAsync": "注销"
|
"ReqUrl": "请求地址",
|
||||||
},
|
"ResultJson": "返回结果",
|
||||||
"ThingsGateway.Admin.Application.TestController": {
|
"VerificatId": "验证Id"
|
||||||
//auth
|
},
|
||||||
"TestController": "测试API",
|
"ThingsGateway.Admin.Application.SysOrg": {
|
||||||
"Test": "测试"
|
"CanotContainsSelf": "不可包含自己",
|
||||||
},
|
"Category": "分类",
|
||||||
"ThingsGateway.Admin.Application.OpenApiAuthController": {
|
"Category.Required": "{0} 是必填项",
|
||||||
//auth
|
"Code": "代码",
|
||||||
"OpenApiAuthController": "登录API",
|
"Code.Required": "{0} 是必填项",
|
||||||
"LoginAsync": "登录",
|
"CodeDup": "存在重复的编码 {0}",
|
||||||
"LogoutAsync": "注销"
|
"DeletePositionFirst": "请先删除机构下的职位",
|
||||||
},
|
"DeleteRoleFirst": "请先删除机构下的角色",
|
||||||
|
"DeleteUserFirst": "请先删除机构下的用户",
|
||||||
|
"DirectorId": "主管",
|
||||||
|
"Dup": "存在重复的机构 分类 {0} 名称 {1}",
|
||||||
|
"Name": "名称",
|
||||||
"ThingsGateway.Admin.Application.FileService": {
|
"Name.Required": "{0} 是必填项",
|
||||||
"FileNullError": "文件不能为空",
|
"NameDup": "存在重复的名称 {0}",
|
||||||
"FileLengthError": "文件大小不允许超过 {0} M",
|
"Names": "机构全称",
|
||||||
"FileTypeError": "不支持 {0} 格式"
|
"ParentChoiceSelf": "父级不能选择自己",
|
||||||
},
|
"ParentId": "上级机构",
|
||||||
|
"ParentNull": "父级不存在 {0}",
|
||||||
"ThingsGateway.Admin.Application.UnifyResultProvider": {
|
"Remark": "备注",
|
||||||
"TokenOver": "登录已过期,请重新登录",
|
"RootOrg": "无法创建顶层机构",
|
||||||
"NoPermission": "禁止访问,没有权限"
|
"Status": "状态",
|
||||||
},
|
"TargetNameDup": "目标节点存在重复的名称 {0}"
|
||||||
|
},
|
||||||
"ThingsGateway.Admin.Application.AuthService": {
|
"ThingsGateway.Admin.Application.SysOrgCopyInput": {
|
||||||
"TenantNull": "租户不存在",
|
"ContainsChild": "包含下级",
|
||||||
"OrgDisable": "所属公司/部门已停用,请联系管理员",
|
"ContainsPosition": "包含职位",
|
||||||
"SingleLoginWarn": "您的账号已在别处登录",
|
"TargetId": "目标机构"
|
||||||
"UserNull": "用户 {0} 不存在",
|
},
|
||||||
"PasswordError": "密码错误次数过多,请 {0} 分钟后再试",
|
"ThingsGateway.Admin.Application.SysPosition": {
|
||||||
"AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ",
|
"CanotContainsSelf": "不可包含自己",
|
||||||
"UserDisable": "账号 {0} 已停用",
|
"Category": "分类",
|
||||||
"MustDesc": "密码需要DESC加密后传入",
|
"Category.Required": "{0} 是必填项",
|
||||||
"UserNoModule": "该账号未分配模块,请联系管理员"
|
"Code": "代码",
|
||||||
},
|
"Code.Required": "{0} 是必填项",
|
||||||
|
"CodeDup": "存在重复的编码 {0}",
|
||||||
|
"DeleteUserFirst": "请先删除职位下的用户",
|
||||||
"ThingsGateway.Admin.Application.HardwareInfo": {
|
"Dup": "存在重复的岗位 分类 {0} 名称 {1}",
|
||||||
"Environment": "主机环境",
|
"Name": "名称",
|
||||||
"FrameworkDescription": "NET框架",
|
"Name.Required": "{0} 是必填项",
|
||||||
"OsArchitecture": "系统架构",
|
"NameDup": "存在重复的名称 {0}",
|
||||||
"UUID": "唯一编码",
|
"OrgId": "机构",
|
||||||
"UpdateTime": "更新时间"
|
"OrgId.MinValue": "{0} 是必填项",
|
||||||
},
|
"ParentChoiceSelf": "父级不能选择自己",
|
||||||
"ThingsGateway.Admin.Application.HistoryHardwareInfo": {
|
"ParentNull": "父级不存在 {0}",
|
||||||
"DriveUsage": "磁盘使用率",
|
"Remark": "备注",
|
||||||
"MemoryUsage": "内存",
|
"Status": "状态",
|
||||||
"CpuUsage": "CPU使用率",
|
"TargetNameDup": "目标节点存在重复的名称 {0}"
|
||||||
"Temperature": "温度",
|
},
|
||||||
"Battery": "电池"
|
"ThingsGateway.Admin.Application.SysResource": {
|
||||||
},
|
"CanotDeleteSystemResource": "不可删除系统资源 {0}",
|
||||||
|
"Category": "分类",
|
||||||
//oper
|
"Code": "编码",
|
||||||
"ThingsGateway.Admin.Application.OperDescAttribute": {
|
"Href": "路径",
|
||||||
//dict
|
"Href.Required": "{0} 是必填项",
|
||||||
"SaveDict": "修改字典",
|
"Icon": "图标",
|
||||||
"DeleteDict": "删除字典",
|
"Module": "模块",
|
||||||
"EditLoginPolicy": "修改登录策略",
|
"ModuleIdDiff": "模块与上级菜单不一致",
|
||||||
"EditPasswordPolicy": "修改密码策略",
|
"NavLinkMatch": "匹配类型",
|
||||||
"EditPagePolicy": "修改页面策略",
|
"NotFoundResource": "系统异常,没找到该菜单",
|
||||||
"EditWebsitePolicy": "修改网站设置",
|
"ParentId": "上级菜单",
|
||||||
//operlog
|
"ResourceDup": "存在重复的名称 {0}",
|
||||||
"DeleteOperLog": "删除操作日志",
|
"ResourceMenuHrefNotNull": "菜单的路径不能为空",
|
||||||
"ExportOperLog": "导出操作日志",
|
"ResourceParentChoiceSelf": "父级不能选择自己",
|
||||||
|
"ResourceParentNull": "父级不存在 {0}",
|
||||||
//resource
|
"Target": "跳转类型",
|
||||||
"SaveResource": "修改资源",
|
"Title": "标题",
|
||||||
"DeleteResource": "删除资源",
|
"Title.Required": "{0} 是必填项"
|
||||||
|
},
|
||||||
//role
|
"ThingsGateway.Admin.Application.SysRole": {
|
||||||
"SaveRole": "修改角色",
|
"CannotRoleScopeAll": "机构角色不能选择全局数据范围",
|
||||||
"DeleteRole": "删除角色",
|
"CanotDeleteAdmin": "不可删除系统内置超管角色",
|
||||||
"RoleGrantResource": "角色授权资源",
|
"CanotEditAdmin": "不可编辑超管角色",
|
||||||
"RoleGrantUser": "角色授权用户",
|
"CanotGrantAdmin": "不能分配超管角色",
|
||||||
"RoleGrantApiPermission": "角色授权OpenApi",
|
"Category": "分类",
|
||||||
"GrantApi": "API",
|
"Code": "编码",
|
||||||
"GrantUser": "用户",
|
"CodeDup": "存在重复的编码 {0}",
|
||||||
"GrantRole": "角色",
|
"Global": "全局",
|
||||||
"GrantResource": "资源",
|
"Name": "名称",
|
||||||
//user
|
"Name.Required": " {0} 是必填项",
|
||||||
"SaveUser": "修改用户",
|
"NameDup": "存在重复的角色名称 {0}",
|
||||||
"DeleteuSER": "删除用户",
|
"OrgId": "机构",
|
||||||
"ResetPassword": "重置密码",
|
"OrgNotNull": "机构不能为空",
|
||||||
"UserGrantRole": "用户授权角色",
|
"SameOrgNameDup": "存在重复的角色名称 {0}",
|
||||||
"UserGrantResource": "用户授权资源",
|
"Status": "状态"
|
||||||
"UserGrantApiPermission": "用户授权OpenApi",
|
},
|
||||||
|
"ThingsGateway.Admin.Application.SysUser": {
|
||||||
//usercenter
|
"Account": "账号",
|
||||||
"UpdateUserInfo": "更新个人信息",
|
"Account.Required": " {0} 是必填项",
|
||||||
"WorkbenchInfo": "更新个人工作台",
|
"AccountDup": "存在重复的账号 {0}",
|
||||||
"UpdatePassword": "更新个人密码",
|
"Avatar": "头像",
|
||||||
|
"CanotDeleteAdminUser": "不可删除系统内置超管用户",
|
||||||
//session
|
"CanotDeleteSelf": "不可删除自己",
|
||||||
"ExitVerificat": "强退令牌",
|
"CanotEditAdminUser": "不可编辑超管用户",
|
||||||
"ExitSession": "强退会话",
|
"CanotGrantAdmin": "不能分配超管角色",
|
||||||
|
"CheckSelf": "禁止 {0} 自己",
|
||||||
|
"ConfirmPasswordDiff": "两次输入的密码不一致",
|
||||||
"CopyOrg": "复制机构",
|
"DemoCanotUpdatePassword": "DEMO环境不允许修改密码",
|
||||||
"DeleteOrg": "删除机构",
|
"DirectorId": "主管",
|
||||||
"SaveOrg": "保存机构",
|
"DirectorSelf": "不能设置自己为主管",
|
||||||
|
"Disable": "禁用",
|
||||||
"DeletePosition": "删除岗位",
|
"Email": "邮箱",
|
||||||
"SavePosition": "保存岗位",
|
"EmailDup": "存在重复的邮箱 {0}",
|
||||||
|
"EmailError": "邮箱 {0} 格式错误",
|
||||||
"NoPermission": "无权限操作",
|
"Enable": "启用",
|
||||||
|
"ExitVerificat": "您已被强制下线",
|
||||||
"CopyResource": "复制资源",
|
"GrantRole": "分配角色",
|
||||||
"ChangeParentResource": "更改父节点"
|
"LastLoginAddress": "上次登录地点",
|
||||||
},
|
"LastLoginDevice": "上次登录设备",
|
||||||
|
"LastLoginIp": "上次登录ip",
|
||||||
//service
|
"LastLoginTime": "上次登录时间",
|
||||||
|
"LatestLoginAddress": "最新登录地点",
|
||||||
"ThingsGateway.Admin.Application.HardwareJob": {
|
"LatestLoginDevice": "最新登录设备",
|
||||||
"GetHardwareInfoFail": "获取硬件信息出错"
|
"LatestLoginIp": "最新登录ip",
|
||||||
},
|
"LatestLoginTime": "最新登录时间",
|
||||||
|
"NoOrg": "组织机构不存在",
|
||||||
//dto
|
"OldPasswordError": "原密码错误",
|
||||||
"ThingsGateway.Admin.Application.UserSelectorOutput": {
|
"OrgId": "机构",
|
||||||
"Account": "账号",
|
"OrgNames": "机构名称",
|
||||||
"OrgId": "机构"
|
"Password": "密码",
|
||||||
},
|
"PasswordEdited": "密码被修改,已退出登录",
|
||||||
"ThingsGateway.Admin.Application.ResourceTableSearchModel": {
|
"PasswordLengthLess": "密码长度不能小于 {0} ",
|
||||||
"Module": "模块",
|
"PasswordMustLow": "密码必须包含小写字母",
|
||||||
"Href": "路径",
|
"PasswordMustNum": "密码必须包含数字",
|
||||||
"Title": "标题"
|
"PasswordMustSpecial": "密码必须包含特殊字符",
|
||||||
},
|
"PasswordMustUpp": "密码必须包含大写字母",
|
||||||
"ThingsGateway.Admin.Application.WorkbenchInfo": {
|
"Phone": "手机",
|
||||||
"Razor": "主页",
|
"PhoneError": "手机号码 {0} 格式错误",
|
||||||
"Shortcuts": "快捷方式"
|
"PositionId": "职位",
|
||||||
},
|
"PositionName": "职位名称",
|
||||||
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
|
"Status": "状态"
|
||||||
"Password": "密码",
|
},
|
||||||
"NewPassword": "新密码",
|
"ThingsGateway.Admin.Application.TargetEnum": {
|
||||||
"ConfirmPassword": "确认密码",
|
"_blank": "新窗口",
|
||||||
"Password.Required": " {0} 是必填项",
|
"_parent": "父级窗口",
|
||||||
"NewPassword.Required": " {0} 是必填项",
|
"_self": "本窗口",
|
||||||
"ConfirmPassword.Required": " {0} 是必填项"
|
"_top": "顶级窗口"
|
||||||
},
|
},
|
||||||
|
"ThingsGateway.Admin.Application.TestController": {
|
||||||
|
"Test": "测试",
|
||||||
"ThingsGateway.Admin.Application.VerificatInfo": {
|
"TestController": "测试API"
|
||||||
"Expire": "过期时间(分)",
|
},
|
||||||
"Online": "在线状态",
|
"ThingsGateway.Admin.Application.UnifyResultProvider": {
|
||||||
"VerificatTimeout": "超时时间",
|
"NoPermission": "禁止访问,没有权限",
|
||||||
"Device": "登录设备",
|
"TokenOver": "登录已过期,请重新登录"
|
||||||
"LoginIp": "登录IP",
|
},
|
||||||
"LoginTime": "登录时间"
|
"ThingsGateway.Admin.Application.UpdatePasswordInput": {
|
||||||
},
|
"ConfirmPassword": "确认密码",
|
||||||
|
"ConfirmPassword.Required": " {0} 是必填项",
|
||||||
"ThingsGateway.Admin.Application.SessionOutput": {
|
"NewPassword": "新密码",
|
||||||
"Account": "账号",
|
"NewPassword.Required": " {0} 是必填项",
|
||||||
"Online": "在线状态",
|
"Password": "密码",
|
||||||
"LatestLoginIp": "最新登录ip",
|
"Password.Required": " {0} 是必填项"
|
||||||
"LatestLoginTime": "最新登录时间",
|
},
|
||||||
"VerificatCount": "令牌数量"
|
"ThingsGateway.Admin.Application.UserSelectorOutput": {
|
||||||
},
|
"Account": "账号",
|
||||||
|
"OrgId": "机构"
|
||||||
"ThingsGateway.Admin.Application.SysDict": {
|
},
|
||||||
"Category.Required": "{0} 是必填项",
|
"ThingsGateway.Admin.Application.VerificatInfo": {
|
||||||
"Name.Required": "{0} 是必填项",
|
"Device": "登录设备",
|
||||||
"Code.Required": "{0} 是必填项",
|
"Expire": "过期时间(分)",
|
||||||
"Category": "分类",
|
"LoginIp": "登录IP",
|
||||||
"Name": "名称",
|
"LoginTime": "登录时间",
|
||||||
"Code": "代码",
|
"Online": "在线状态",
|
||||||
"Remark": "备注",
|
"VerificatTimeout": "超时时间"
|
||||||
"SortCode": "排序",
|
},
|
||||||
"CreateTime": "创建时间",
|
"ThingsGateway.Admin.Application.WebsitePolicy": {
|
||||||
"UpdateTime": "更新时间",
|
"CloseTip": "关闭提示",
|
||||||
"DictDup": "存在重复的配置 分类 {0} 名称 {1}",
|
"CloseTip.Required": " {0} 是必填项",
|
||||||
"DemoCanotUpdateWebsitePolicy": "DEMO环境不允许修改网站设置"
|
"WebStatus": "是否开放"
|
||||||
},
|
},
|
||||||
|
"ThingsGateway.Admin.Application.WorkbenchInfo": {
|
||||||
|
"Razor": "主页",
|
||||||
"ThingsGateway.Admin.Application.SysOperateLog": {
|
"Shortcuts": "快捷方式"
|
||||||
"ClassName": "类名",
|
|
||||||
"ExeMessage": "具体消息",
|
|
||||||
"MethodName": "方法名称",
|
|
||||||
"ParamJson": "请求参数",
|
|
||||||
"ReqMethod": "请求方式",
|
|
||||||
"ReqUrl": "请求地址",
|
|
||||||
"ResultJson": "返回结果",
|
|
||||||
"Category": "日志分类",
|
|
||||||
"ExeStatus": "执行状态",
|
|
||||||
"Name": "日志名称",
|
|
||||||
"OpAccount": "账号",
|
|
||||||
"OpBrowser": "浏览器",
|
|
||||||
"OpIp": "ip",
|
|
||||||
"OpOs": "系统",
|
|
||||||
"OpTime": "操作时间",
|
|
||||||
"VerificatId": "验证Id"
|
|
||||||
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.OperateLogPageInput": {
|
|
||||||
"SearchDate": "时间范围",
|
|
||||||
"Account": "操作账号",
|
|
||||||
"Category": "分类"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.LoginInput": {
|
|
||||||
"Account": "登录账号",
|
|
||||||
"Password": "登录密码",
|
|
||||||
"Account.Required": "{0} 是必填项",
|
|
||||||
"Password.Required": "{0} 是必填项"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.LogoutInput": {
|
|
||||||
"VerificatId.Required": "{0} 是必填项"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.AppConfig": {
|
|
||||||
"LoginPolicy": "登录策略",
|
|
||||||
"PasswordPolicy": "密码策略",
|
|
||||||
"PagePolicy": "页面设置",
|
|
||||||
"WebsitePolicy": "网站设置"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.LoginPolicy": {
|
|
||||||
"SingleOpen": "单用户登录开关",
|
|
||||||
"ErrorLockTime": "登录错误锁定时长(分)",
|
|
||||||
"ErrorResetTime": "登录错误次数过期时长(分)",
|
|
||||||
"ErrorCount": "登录错误次数锁定阈值",
|
|
||||||
"VerificatExpireTime": "登录过期时间(分)",
|
|
||||||
"ErrorLockTime.MinValue": " {0} 值太小",
|
|
||||||
"ErrorResetTime.MinValue": " {0} 值太小",
|
|
||||||
"ErrorCount.MinValue": " {0} 值太小",
|
|
||||||
"VerificatExpireTime.MinValue": " {0} 值太小"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.PagePolicy": {
|
|
||||||
"Shortcuts": "默认快捷方式",
|
|
||||||
"Razor": "默认主页"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.PasswordPolicy": {
|
|
||||||
"DefaultPassword": "默认用户密码",
|
|
||||||
"DefaultPassword.Required": " {0} 是必填项",
|
|
||||||
"PasswordMinLen": "密码最小长度",
|
|
||||||
"PasswordMinLen.MinValue": " {0} 值太小",
|
|
||||||
"PasswordContainNum": "包含数字",
|
|
||||||
"PasswordContainLower": "包含小写字母",
|
|
||||||
"PasswordContainUpper": "包含大写字母",
|
|
||||||
"PasswordContainChar": "包含特殊字符"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.WebsitePolicy": {
|
|
||||||
"WebStatus": "是否开放",
|
|
||||||
"CloseTip": "关闭提示",
|
|
||||||
"CloseTip.Required": " {0} 是必填项"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//enum
|
|
||||||
"ThingsGateway.Admin.Application.ResourceCategoryEnum": {
|
|
||||||
"Module": "模块",
|
|
||||||
"Menu": "菜单",
|
|
||||||
"Button": "按钮"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.TargetEnum": {
|
|
||||||
"_self": "本窗口",
|
|
||||||
"_blank": "新窗口",
|
|
||||||
"_parent": "父级窗口",
|
|
||||||
"_top": "顶级窗口"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Application.DictTypeEnum": {
|
|
||||||
"System": "系统配置",
|
|
||||||
"Define": "业务配置"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.LogCateGoryEnum": {
|
|
||||||
"Login": "登录",
|
|
||||||
"Logout": "注销",
|
|
||||||
"Operate": "操作",
|
|
||||||
"Exception": "异常"
|
|
||||||
},
|
|
||||||
|
|
||||||
"ThingsGateway.Admin.Application.LogEnum": {
|
|
||||||
"SUCCESS": "成功",
|
|
||||||
"FAIL": "失败"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
@@ -8,17 +8,15 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
using ThingsGateway.Extension;
|
using ThingsGateway.Extension;
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
using ThingsGateway.Logging;
|
using ThingsGateway.Logging;
|
||||||
using ThingsGateway.NewLife.Json.Extension;
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
using ThingsGateway.Razor;
|
using ThingsGateway.Razor;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
using UAParser;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -41,33 +39,36 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
|
|||||||
/// <param name="flush"></param>
|
/// <param name="flush"></param>
|
||||||
public async Task WriteAsync(LogMessage logMsg, bool flush)
|
public async Task WriteAsync(LogMessage logMsg, bool flush)
|
||||||
{
|
{
|
||||||
//获取请求json字符串
|
|
||||||
var jsonString = logMsg.Context.Get("loggingMonitor").ToString();
|
|
||||||
//转成实体
|
//转成实体
|
||||||
var loggingMonitor = jsonString.FromJsonNetString<LoggingMonitorJson>();
|
var requestAuditData = logMsg.Context.Get(nameof(RequestAuditData)) as RequestAuditData;
|
||||||
//日志时间赋值
|
//日志时间赋值
|
||||||
loggingMonitor.LogDateTime = logMsg.LogDateTime;
|
requestAuditData.LogDateTime = logMsg.LogDateTime;
|
||||||
// loggingMonitor.ReturnInformation.Value
|
// requestAuditData.ReturnInformation.Value
|
||||||
//验证失败不记录日志
|
//验证失败不记录日志
|
||||||
bool save = false;
|
bool save = false;
|
||||||
if (loggingMonitor.Validation == null)
|
if (requestAuditData.Validation == null)
|
||||||
{
|
{
|
||||||
var operation = logMsg.Context.Get(LoggingConst.Operation).ToString();//获取操作名称
|
var operation = requestAuditData.Operation;//获取操作名称
|
||||||
var client = (ClientInfo)logMsg.Context.Get(LoggingConst.Client);//获取客户端信息
|
var client = requestAuditData.Client;//获取客户端信息
|
||||||
var path = logMsg.Context.Get(LoggingConst.Path).ToString();//获取操作名称
|
var path = requestAuditData.Path;//获取操作名称
|
||||||
var method = logMsg.Context.Get(LoggingConst.Method).ToString();//获取方法
|
var method = requestAuditData.Method;//获取方法
|
||||||
|
var methodInfo = requestAuditData.MethodInfo;
|
||||||
|
var login = methodInfo.GetCustomAttribute(typeof(LoginLogAttribute));
|
||||||
|
var logout = methodInfo.GetCustomAttribute(typeof(LogoutLogAttribute));
|
||||||
|
|
||||||
//表示访问日志
|
//表示访问日志
|
||||||
if (path == "/api/auth/login" || path == "/api/auth/logout")
|
if (login != null || logout != null)
|
||||||
{
|
{
|
||||||
//如果没有异常信息
|
//如果没有异常信息
|
||||||
if (loggingMonitor.Exception == null)
|
if (requestAuditData.Exception == null)
|
||||||
{
|
{
|
||||||
save = await CreateVisitLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false);//添加到访问日志
|
LogCateGoryEnum logCateGoryEnum = login != null ? LogCateGoryEnum.Login : LogCateGoryEnum.Logout;
|
||||||
|
save = await CreateVisitLog(operation, path, requestAuditData, client, logCateGoryEnum, flush).ConfigureAwait(false);//添加到访问日志
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//添加到异常日志
|
//添加到异常日志
|
||||||
save = await CreateOperationLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false);
|
save = await CreateOperationLog(operation, path, requestAuditData, client, flush).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -76,7 +77,7 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
|
|||||||
if (!operation.IsNullOrWhiteSpace() && method == "POST")
|
if (!operation.IsNullOrWhiteSpace() && method == "POST")
|
||||||
{
|
{
|
||||||
//添加到操作日志
|
//添加到操作日志
|
||||||
save = await CreateOperationLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false);
|
save = await CreateOperationLog(operation, path, requestAuditData, client, flush).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,27 +92,21 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="operation">操作名称</param>
|
/// <param name="operation">操作名称</param>
|
||||||
/// <param name="path">请求地址</param>
|
/// <param name="path">请求地址</param>
|
||||||
/// <param name="loggingMonitor">loggingMonitor</param>
|
/// <param name="requestAuditData">requestAuditData</param>
|
||||||
/// <param name="clientInfo">客户端信息</param>
|
/// <param name="userAgent">客户端信息</param>
|
||||||
/// <param name="flush"></param>
|
/// <param name="flush"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task<bool> CreateOperationLog(string operation, string path, LoggingMonitorJson loggingMonitor, ClientInfo clientInfo, bool flush)
|
private async Task<bool> CreateOperationLog(string operation, string path, RequestAuditData requestAuditData, UserAgent userAgent, bool flush)
|
||||||
{
|
{
|
||||||
//账号
|
//账号
|
||||||
var opAccount = loggingMonitor.AuthorizationClaims?.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
|
var opAccount = requestAuditData.AuthorizationClaims?.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
|
||||||
|
|
||||||
//获取参数json字符串,
|
//获取参数json字符串,
|
||||||
var paramJson = loggingMonitor.Parameters == null || loggingMonitor.Parameters.Count == 0 ? null : loggingMonitor.Parameters[0].Value.ToJsonNetString();
|
var paramJson = requestAuditData.Parameters == null || requestAuditData.Parameters.Count == 0 ? null : requestAuditData.Parameters.ToSystemTextJsonString();
|
||||||
|
|
||||||
//获取结果json字符串
|
//获取结果json字符串
|
||||||
var resultJson = string.Empty;
|
var resultJson = requestAuditData.ReturnInformation?.ToSystemTextJsonString();
|
||||||
if (loggingMonitor.ReturnInformation != null)//如果有返回值
|
|
||||||
{
|
|
||||||
if (loggingMonitor.ReturnInformation.Value != null)//如果返回值不为空
|
|
||||||
{
|
|
||||||
resultJson = loggingMonitor.ReturnInformation.Value.ToJsonNetString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//操作日志表实体
|
//操作日志表实体
|
||||||
var sysLogOperate = new SysOperateLog
|
var sysLogOperate = new SysOperateLog
|
||||||
@@ -119,36 +114,36 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
|
|||||||
Name = operation,
|
Name = operation,
|
||||||
Category = LogCateGoryEnum.Operate,
|
Category = LogCateGoryEnum.Operate,
|
||||||
ExeStatus = true,
|
ExeStatus = true,
|
||||||
OpIp = loggingMonitor.RemoteIPv4,
|
OpIp = requestAuditData.RemoteIPv4,
|
||||||
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
|
OpBrowser = userAgent?.Browser,
|
||||||
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
|
OpOs = userAgent?.Platform,
|
||||||
OpTime = loggingMonitor.LogDateTime.LocalDateTime,
|
OpTime = requestAuditData.LogDateTime.LocalDateTime,
|
||||||
OpAccount = opAccount,
|
OpAccount = opAccount,
|
||||||
ReqMethod = loggingMonitor.HttpMethod,
|
ReqMethod = requestAuditData.Method,
|
||||||
ReqUrl = path,
|
ReqUrl = path,
|
||||||
ResultJson = resultJson,
|
ResultJson = resultJson,
|
||||||
ClassName = loggingMonitor.DisplayName,
|
ClassName = requestAuditData.ControllerName,
|
||||||
MethodName = loggingMonitor.ActionName,
|
MethodName = requestAuditData.ActionName,
|
||||||
ParamJson = paramJson,
|
ParamJson = paramJson,
|
||||||
VerificatId = UserManager.VerificatId,
|
VerificatId = UserManager.VerificatId,
|
||||||
};
|
};
|
||||||
//如果异常不为空
|
//如果异常不为空
|
||||||
if (loggingMonitor.Exception != null)
|
if (requestAuditData.Exception != null)
|
||||||
{
|
{
|
||||||
sysLogOperate.Category = LogCateGoryEnum.Exception;//操作类型为异常
|
sysLogOperate.Category = LogCateGoryEnum.Exception;//操作类型为异常
|
||||||
sysLogOperate.ExeStatus = false;//操作状态为失败
|
sysLogOperate.ExeStatus = false;//操作状态为失败
|
||||||
|
|
||||||
if (loggingMonitor.Exception.Type == typeof(AppFriendlyException).ToString())
|
if (requestAuditData.Exception.Type == typeof(AppFriendlyException).ToString())
|
||||||
sysLogOperate.ExeMessage = loggingMonitor?.Exception.Message;
|
sysLogOperate.ExeMessage = requestAuditData?.Exception.Message;
|
||||||
else
|
else
|
||||||
sysLogOperate.ExeMessage = $"{loggingMonitor.Exception.Type}:{loggingMonitor.Exception.Message}{Environment.NewLine}{loggingMonitor.Exception.StackTrace}";
|
sysLogOperate.ExeMessage = $"{requestAuditData.Exception.Type}:{requestAuditData.Exception.Message}{Environment.NewLine}{requestAuditData.Exception.StackTrace}";
|
||||||
}
|
}
|
||||||
|
|
||||||
_operateLogMessageQueue.Enqueue(sysLogOperate);
|
_operateLogMessageQueue.Enqueue(sysLogOperate);
|
||||||
|
|
||||||
if (flush)
|
if (flush)
|
||||||
{
|
{
|
||||||
SqlSugarClient ??= DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
|
SqlSugarClient ??= DbContext.GetDB<SysOperateLog>();
|
||||||
await SqlSugarClient.InsertableWithAttr(_operateLogMessageQueue.ToListWithDequeue()).ExecuteCommandAsync().ConfigureAwait(false);//入库
|
await SqlSugarClient.InsertableWithAttr(_operateLogMessageQueue.ToListWithDequeue()).ExecuteCommandAsync().ConfigureAwait(false);//入库
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -160,52 +155,54 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="operation">访问类型</param>
|
/// <param name="operation">访问类型</param>
|
||||||
/// <param name="path"></param>
|
/// <param name="path"></param>
|
||||||
/// <param name="loggingMonitor">loggingMonitor</param>
|
/// <param name="requestAuditData">requestAuditData</param>
|
||||||
/// <param name="clientInfo">客户端信息</param>
|
/// <param name="userAgent">客户端信息</param>
|
||||||
|
/// <param name="logCateGoryEnum">logCateGory</param>
|
||||||
/// <param name="flush"></param>
|
/// <param name="flush"></param>
|
||||||
private async Task<bool> CreateVisitLog(string operation, string path, LoggingMonitorJson loggingMonitor, ClientInfo clientInfo, bool flush)
|
private async Task<bool> CreateVisitLog(string operation, string path, RequestAuditData requestAuditData, UserAgent userAgent, LogCateGoryEnum logCateGoryEnum, bool flush)
|
||||||
{
|
{
|
||||||
long verificatId = 0;//验证Id
|
long verificatId = 0;//验证Id
|
||||||
var opAccount = "";//用户账号
|
var opAccount = "";//用户账号
|
||||||
if (path == "/api/auth/login")
|
if (logCateGoryEnum == LogCateGoryEnum.Login)
|
||||||
{
|
{
|
||||||
//如果是登录,用户信息就从返回值里拿
|
//如果是登录,用户信息就从返回值里拿
|
||||||
var result = loggingMonitor.ReturnInformation?.Value?.ToJsonNetString();//返回值转json
|
if (requestAuditData.ReturnInformation is UnifyResult<LoginOutput> userInfo)
|
||||||
var userInfo = result.FromJsonNetString<UnifyResult<LoginOutput>>();//格式化成user表
|
{
|
||||||
opAccount = userInfo.Data.Account;//赋值账号
|
opAccount = userInfo.Data.Account;//赋值账号
|
||||||
verificatId = userInfo.Data.VerificatId;
|
verificatId = userInfo.Data.VerificatId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//如果是登录出,用户信息就从AuthorizationClaims里拿
|
//如果是登录出,用户信息就从AuthorizationClaims里拿
|
||||||
opAccount = loggingMonitor.AuthorizationClaims.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
|
opAccount = requestAuditData.AuthorizationClaims.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
|
||||||
verificatId = loggingMonitor.AuthorizationClaims.Where(it => it.Type == ClaimConst.VerificatId).Select(it => it.Value).FirstOrDefault().ToLong();
|
verificatId = requestAuditData.AuthorizationClaims.Where(it => it.Type == ClaimConst.VerificatId).Select(it => it.Value).FirstOrDefault().ToLong();
|
||||||
}
|
}
|
||||||
//日志表实体
|
//日志表实体
|
||||||
var sysLogVisit = new SysOperateLog
|
var sysLogVisit = new SysOperateLog
|
||||||
{
|
{
|
||||||
Name = operation,
|
Name = operation,
|
||||||
Category = path == "/api/auth/login" ? LogCateGoryEnum.Login : LogCateGoryEnum.Logout,
|
Category = logCateGoryEnum,
|
||||||
ExeStatus = true,
|
ExeStatus = true,
|
||||||
OpIp = loggingMonitor.RemoteIPv4,
|
OpIp = requestAuditData.RemoteIPv4,
|
||||||
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
|
OpBrowser = userAgent?.Browser,
|
||||||
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
|
OpOs = userAgent?.Platform,
|
||||||
OpTime = loggingMonitor.LogDateTime.LocalDateTime,
|
OpTime = requestAuditData.LogDateTime.LocalDateTime,
|
||||||
VerificatId = verificatId,
|
VerificatId = verificatId,
|
||||||
OpAccount = opAccount,
|
OpAccount = opAccount,
|
||||||
|
|
||||||
ReqMethod = loggingMonitor.HttpMethod,
|
ReqMethod = requestAuditData.Method,
|
||||||
ReqUrl = path,
|
ReqUrl = path,
|
||||||
ResultJson = loggingMonitor.ReturnInformation?.Value?.ToJsonNetString(),
|
ResultJson = requestAuditData.ReturnInformation?.ToSystemTextJsonString(),
|
||||||
ClassName = loggingMonitor.DisplayName,
|
ClassName = requestAuditData.ControllerName,
|
||||||
MethodName = loggingMonitor.ActionName,
|
MethodName = requestAuditData.ActionName,
|
||||||
ParamJson = loggingMonitor.Parameters?.ToJsonNetString(),
|
ParamJson = requestAuditData.Parameters?.ToSystemTextJsonString(),
|
||||||
};
|
};
|
||||||
_operateLogMessageQueue.Enqueue(sysLogVisit);
|
_operateLogMessageQueue.Enqueue(sysLogVisit);
|
||||||
|
|
||||||
if (flush)
|
if (flush)
|
||||||
{
|
{
|
||||||
SqlSugarClient ??= DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
|
SqlSugarClient ??= DbContext.GetDB<SysOperateLog>();
|
||||||
await SqlSugarClient.InsertableWithAttr(_operateLogMessageQueue.ToListWithDequeue()).ExecuteCommandAsync().ConfigureAwait(false);//入库
|
await SqlSugarClient.InsertableWithAttr(_operateLogMessageQueue.ToListWithDequeue()).ExecuteCommandAsync().ConfigureAwait(false);//入库
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 日志常量
|
/// 日志常量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LoggingConst
|
public static class LoggingConst
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分类
|
/// 分类
|
||||||
|
@@ -0,0 +1,239 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
|
||||||
|
using ThingsGateway.Extension;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 只适合 Demo 登录,会直接授权超管的权限
|
||||||
|
/// </summary>
|
||||||
|
public class AdminOAuthHandler<TOptions>(
|
||||||
|
IVerificatInfoService verificatInfoService,
|
||||||
|
IAppService appService,
|
||||||
|
ISysUserService sysUserService,
|
||||||
|
ISysDictService configService,
|
||||||
|
IOptionsMonitor<TOptions> options,
|
||||||
|
ILoggerFactory logger,
|
||||||
|
IUserAgentService userAgentService,
|
||||||
|
UrlEncoder encoder
|
||||||
|
) : OAuthHandler<TOptions>(options, logger, encoder)
|
||||||
|
where TOptions : AdminOAuthOptions, new()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
static AdminOAuthHandler()
|
||||||
|
{
|
||||||
|
Task.Factory.StartNew(Insertable, TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志消息队列(线程安全)
|
||||||
|
/// </summary>
|
||||||
|
protected static readonly ConcurrentQueue<SysOperateLog> _operateLogMessageQueue = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建访问日志
|
||||||
|
/// </summary>
|
||||||
|
private static async Task Insertable()
|
||||||
|
{
|
||||||
|
var db = DbContext.GetDB<SysOperateLog>();
|
||||||
|
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
|
||||||
|
while (!appLifetime.ApplicationStopping.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = _operateLogMessageQueue.ToListWithDequeue(); // 从日志队列中获取数据
|
||||||
|
if (data.Count > 0)
|
||||||
|
{
|
||||||
|
await db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);//入库
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
NewLife.Log.XTrace.WriteException(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await Task.Delay(3000, appLifetime.ApplicationStopping).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<AuthenticationTicket> CreateTicketAsync(
|
||||||
|
ClaimsIdentity identity,
|
||||||
|
AuthenticationProperties properties,
|
||||||
|
OAuthTokenResponse tokens)
|
||||||
|
{
|
||||||
|
Backchannel.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokens.AccessToken);
|
||||||
|
properties.RedirectUri = Options.HomePath;
|
||||||
|
properties.IsPersistent = true;
|
||||||
|
var appConfig = await configService.GetAppConfigAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
int expire = appConfig.LoginPolicy.VerificatExpireTime;
|
||||||
|
if (!string.IsNullOrEmpty(tokens.ExpiresIn) && int.TryParse(tokens.ExpiresIn, out var result))
|
||||||
|
{
|
||||||
|
properties.ExpiresUtc = TimeProvider.System.GetUtcNow().AddSeconds(result);
|
||||||
|
expire = (int)(result / 60.0);
|
||||||
|
}
|
||||||
|
var user = await Options.HandleUserInfoAsync(Context, tokens).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var loginEvent = await GetLogin(expire).ConfigureAwait(false);
|
||||||
|
await UpdateUser(loginEvent).ConfigureAwait(false);
|
||||||
|
identity.AddClaim(new Claim(ClaimConst.VerificatId, loginEvent.VerificatId.ToString()));
|
||||||
|
identity.AddClaim(new Claim(ClaimConst.UserId, RoleConst.SuperAdminId.ToString()));
|
||||||
|
|
||||||
|
identity.AddClaim(new Claim(ClaimConst.SuperAdmin, "true"));
|
||||||
|
identity.AddClaim(new Claim(ClaimConst.OrgId, RoleConst.DefaultTenantId.ToString()));
|
||||||
|
identity.AddClaim(new Claim(ClaimConst.TenantId, RoleConst.DefaultTenantId.ToString()));
|
||||||
|
|
||||||
|
|
||||||
|
var context = new OAuthCreatingTicketContext(
|
||||||
|
new ClaimsPrincipal(identity),
|
||||||
|
properties,
|
||||||
|
Context,
|
||||||
|
Scheme,
|
||||||
|
Options,
|
||||||
|
Backchannel,
|
||||||
|
tokens,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
|
context.RunClaimActions();
|
||||||
|
await Events.CreatingTicket(context).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var httpContext = context.HttpContext;
|
||||||
|
UserAgent? userAgent = null;
|
||||||
|
var str = httpContext?.Request?.Headers?.UserAgent;
|
||||||
|
if (!string.IsNullOrEmpty(str))
|
||||||
|
{
|
||||||
|
userAgent = userAgentService.Parse(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sysOperateLog = new SysOperateLog()
|
||||||
|
{
|
||||||
|
Name = this.Scheme.Name,
|
||||||
|
Category = LogCateGoryEnum.Login,
|
||||||
|
ExeStatus = true,
|
||||||
|
OpIp = httpContext.GetRemoteIpAddressToIPv4(),
|
||||||
|
OpBrowser = userAgent?.Browser,
|
||||||
|
OpOs = userAgent?.Platform,
|
||||||
|
OpTime = DateTime.Now,
|
||||||
|
VerificatId = loginEvent.VerificatId,
|
||||||
|
OpAccount = Options.GetName(user),
|
||||||
|
|
||||||
|
ReqMethod = "OAuth",
|
||||||
|
ReqUrl = string.Empty,
|
||||||
|
ResultJson = string.Empty,
|
||||||
|
ClassName = nameof(AdminOAuthHandler<TOptions>),
|
||||||
|
MethodName = string.Empty,
|
||||||
|
ParamJson = string.Empty,
|
||||||
|
};
|
||||||
|
_operateLogMessageQueue.Enqueue(sysOperateLog);
|
||||||
|
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<LoginEvent> GetLogin(int expire)
|
||||||
|
{
|
||||||
|
var sysUser = await sysUserService.GetUserByIdAsync(RoleConst.SuperAdminId).ConfigureAwait(false);//获取用户信息
|
||||||
|
|
||||||
|
var loginEvent = new LoginEvent
|
||||||
|
{
|
||||||
|
Ip = appService.RemoteIpAddress,
|
||||||
|
Device = appService.UserAgent?.Platform,
|
||||||
|
Expire = expire,
|
||||||
|
SysUser = sysUser,
|
||||||
|
VerificatId = CommonUtils.GetSingleId()
|
||||||
|
};
|
||||||
|
|
||||||
|
//获取verificat列表
|
||||||
|
var tokenTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
|
||||||
|
//生成verificat信息
|
||||||
|
var verificatInfo = new VerificatInfo
|
||||||
|
{
|
||||||
|
Device = loginEvent.Device,
|
||||||
|
Expire = loginEvent.Expire,
|
||||||
|
VerificatTimeout = tokenTimeout,
|
||||||
|
Id = loginEvent.VerificatId,
|
||||||
|
UserId = loginEvent.SysUser.Id,
|
||||||
|
LoginIp = loginEvent.Ip,
|
||||||
|
LoginTime = loginEvent.DateTime
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//添加到verificat列表
|
||||||
|
verificatInfoService.Add(verificatInfo);
|
||||||
|
return loginEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 登录事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loginEvent"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task UpdateUser(LoginEvent loginEvent)
|
||||||
|
{
|
||||||
|
var sysUser = loginEvent.SysUser;
|
||||||
|
|
||||||
|
#region 登录/密码策略
|
||||||
|
|
||||||
|
var key = CacheConst.Cache_LoginErrorCount + sysUser.Account;//获取登录错误次数Key值
|
||||||
|
App.CacheService.Remove(key);//移除登录错误次数
|
||||||
|
|
||||||
|
//获取用户verificat列表
|
||||||
|
var userToken = verificatInfoService.GetOne(loginEvent.VerificatId);
|
||||||
|
|
||||||
|
#endregion 登录/密码策略
|
||||||
|
|
||||||
|
#region 重新赋值属性,设置本次登录信息为最新的信息
|
||||||
|
|
||||||
|
sysUser.LastLoginIp = sysUser.LatestLoginIp;
|
||||||
|
sysUser.LastLoginTime = sysUser.LatestLoginTime;
|
||||||
|
sysUser.LatestLoginIp = loginEvent.Ip;
|
||||||
|
sysUser.LatestLoginTime = loginEvent.DateTime;
|
||||||
|
|
||||||
|
#endregion 重新赋值属性,设置本次登录信息为最新的信息
|
||||||
|
|
||||||
|
using var db = DbContext.GetDB<SysUser>();
|
||||||
|
//更新用户登录信息
|
||||||
|
if (await db.Updateable(sysUser).UpdateColumns(it => new
|
||||||
|
{
|
||||||
|
it.LastLoginIp,
|
||||||
|
it.LastLoginTime,
|
||||||
|
it.LatestLoginIp,
|
||||||
|
it.LatestLoginTime,
|
||||||
|
}).ExecuteCommandAsync().ConfigureAwait(false) > 0)
|
||||||
|
App.CacheService.HashAdd(CacheConst.Cache_SysUser, sysUser.Id.ToString(), sysUser);//更新Cache信息
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>自定义 Token 异常</summary>
|
||||||
|
public class OAuthTokenException : Exception
|
||||||
|
{
|
||||||
|
public OAuthTokenException() : base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public OAuthTokenException(string? message, Exception? innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public OAuthTokenException(string? message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,87 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
/// <summary>OAuthOptions 配置类</summary>
|
||||||
|
public abstract class AdminOAuthOptions : OAuthOptions
|
||||||
|
{
|
||||||
|
/// <summary>默认构造函数</summary>
|
||||||
|
protected AdminOAuthOptions()
|
||||||
|
{
|
||||||
|
ConfigureClaims();
|
||||||
|
this.Events.OnRemoteFailure = context =>
|
||||||
|
{
|
||||||
|
var redirectUri = string.IsNullOrEmpty(HomePath) ? "/" : HomePath;
|
||||||
|
context.Response.Redirect(redirectUri);
|
||||||
|
context.HandleResponse();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
Backchannel = new HttpClient(new HttpClientHandler
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
||||||
|
});
|
||||||
|
Backchannel.DefaultRequestHeaders.UserAgent.Add(
|
||||||
|
new ProductInfoHeaderValue("ThingsGateway", "1.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>配置 Claims 映射</summary>
|
||||||
|
protected virtual void ConfigureClaims()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetName(JsonElement element)
|
||||||
|
{
|
||||||
|
JsonElement.ObjectEnumerator target = element.EnumerateObject();
|
||||||
|
return target.TryGetValue("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>获得/设置 登陆后首页</summary>
|
||||||
|
public string HomePath { get; set; } = "/";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>处理用户信息方法</summary>
|
||||||
|
public virtual async Task<JsonElement> HandleUserInfoAsync(HttpContext context, OAuthTokenResponse tokens)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, BuildUserInfoUrl(tokens));
|
||||||
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
|
||||||
|
var response = await Backchannel.SendAsync(request, context.RequestAborted).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return JsonDocument.Parse(content).RootElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new OAuthTokenException($"OAuth user info endpoint failure: {await Display(response).ConfigureAwait(false)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>生成用户信息请求地址方法</summary>
|
||||||
|
protected virtual string BuildUserInfoUrl(OAuthTokenResponse tokens)
|
||||||
|
{
|
||||||
|
return QueryHelpers.AddQueryString(UserInformationEndpoint, new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "access_token", tokens.AccessToken }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/// <summary>生成错误信息方法</summary>
|
||||||
|
protected async Task<string> Display(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
var output = new StringBuilder();
|
||||||
|
output.Append($"Status: {response.StatusCode}; ");
|
||||||
|
output.Append($"Headers: {response.Headers}; ");
|
||||||
|
output.Append($"Body: {await response.Content.ReadAsStringAsync().ConfigureAwait(false)};");
|
||||||
|
|
||||||
|
return output.ToString();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,122 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife.Log;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public class GiteeOAuthOptions : AdminOAuthOptions
|
||||||
|
{
|
||||||
|
INoticeService _noticeService;
|
||||||
|
IVerificatInfoService _verificatInfoService;
|
||||||
|
public GiteeOAuthOptions() : base()
|
||||||
|
{
|
||||||
|
_noticeService = App.GetService<INoticeService>();
|
||||||
|
_verificatInfoService = App.GetService<IVerificatInfoService>();
|
||||||
|
this.SignInScheme = ClaimConst.Scheme;
|
||||||
|
this.AuthorizationEndpoint = "https://gitee.com/oauth/authorize";
|
||||||
|
this.TokenEndpoint = "https://gitee.com/oauth/token";
|
||||||
|
this.UserInformationEndpoint = "https://gitee.com/api/v5/user";
|
||||||
|
this.HomePath = "/";
|
||||||
|
this.CallbackPath = "/signin-gitee";
|
||||||
|
Scope.Add("user_info");
|
||||||
|
Scope.Add("projects");
|
||||||
|
|
||||||
|
Events.OnCreatingTicket = async context =>
|
||||||
|
{
|
||||||
|
await HandlerGiteeStarredUrl(context).ConfigureAwait(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
Events.OnRedirectToAuthorizationEndpoint = context =>
|
||||||
|
{
|
||||||
|
context.Response.Redirect(context.RedirectUri);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
Events.OnRemoteFailure = context =>
|
||||||
|
{
|
||||||
|
XTrace.WriteException(context.Failure);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>刷新 Token 方法</summary>
|
||||||
|
protected virtual async Task<OAuthTokenResponse> RefreshTokenAsync(TicketReceivedContext ticketReceivedContext, string refreshToken)
|
||||||
|
{
|
||||||
|
var query = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "refresh_token", refreshToken },
|
||||||
|
{ "grant_type", "refresh_token" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, QueryHelpers.AddQueryString(TokenEndpoint, query));
|
||||||
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
|
||||||
|
var response = await Backchannel.SendAsync(request, ticketReceivedContext.HttpContext.RequestAborted).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return OAuthTokenResponse.Success(JsonDocument.Parse(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return OAuthTokenResponse.Failed(new OAuthTokenException($"OAuth token endpoint failure: {await Display(response).ConfigureAwait(false)}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public override string GetName(JsonElement element)
|
||||||
|
{
|
||||||
|
JsonElement.ObjectEnumerator target = element.EnumerateObject();
|
||||||
|
return target.TryGetValue("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandlerGiteeStarredUrl(OAuthCreatingTicketContext context, string repoFullName = "ThingsGateway/ThingsGateway")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(context.AccessToken))
|
||||||
|
throw new InvalidOperationException("Access token is missing.");
|
||||||
|
|
||||||
|
var uri = $"https://gitee.com/api/v5/user/starred/{repoFullName}";
|
||||||
|
|
||||||
|
var queryString = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "access_token", context.AccessToken }
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, QueryHelpers.AddQueryString(uri, queryString))
|
||||||
|
{
|
||||||
|
Headers = { Accept = { new MediaTypeWithQualityHeaderValue("application/json") } }
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var id = context.Identity.Claims.FirstOrDefault(a => a.Type == ClaimConst.VerificatId).Value;
|
||||||
|
|
||||||
|
var verificatInfoIds = _verificatInfoService.GetOne(id.ToLong());
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(5000).ConfigureAwait(false);
|
||||||
|
await _noticeService.NavigationMesage(verificatInfoIds.ClientIds, "https://gitee.com/ThingsGateway/ThingsGateway", "创作不易,如有帮助请star仓库").ConfigureAwait(false);
|
||||||
|
});
|
||||||
|
//throw new Exception($"Failed to star repository: {response.StatusCode}, {content}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
protected override void ConfigureClaims()
|
||||||
|
{
|
||||||
|
ClaimActions.MapJsonKey(ClaimConst.AvatarUrl, "avatar_url");
|
||||||
|
ClaimActions.MapJsonKey(ClaimConst.Account, "name");
|
||||||
|
|
||||||
|
base.ConfigureClaims();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public class GiteeOAuthSettings
|
||||||
|
{
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
public string ClientSecret { get; set; }
|
||||||
|
}
|
@@ -0,0 +1,122 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife.Log;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public class GitHubOAuthOptions : AdminOAuthOptions
|
||||||
|
{
|
||||||
|
INoticeService _noticeService;
|
||||||
|
IVerificatInfoService _verificatInfoService;
|
||||||
|
public GitHubOAuthOptions() : base()
|
||||||
|
{
|
||||||
|
_noticeService = App.GetService<INoticeService>();
|
||||||
|
_verificatInfoService = App.GetService<IVerificatInfoService>();
|
||||||
|
SignInScheme = ClaimConst.Scheme;
|
||||||
|
AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
|
||||||
|
TokenEndpoint = "https://github.com/login/oauth/access_token";
|
||||||
|
UserInformationEndpoint = "https://api.github.com/user";
|
||||||
|
HomePath = "/";
|
||||||
|
CallbackPath = "/signin-github";
|
||||||
|
|
||||||
|
Scope.Add("read:user");
|
||||||
|
Scope.Add("public_repo"); // 需要用于 Star 仓库
|
||||||
|
|
||||||
|
Events.OnCreatingTicket = async context =>
|
||||||
|
{
|
||||||
|
await HandleGitHubStarAsync(context).ConfigureAwait(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
Events.OnRedirectToAuthorizationEndpoint = context =>
|
||||||
|
{
|
||||||
|
context.Response.Redirect(context.RedirectUri);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
Events.OnRemoteFailure = context =>
|
||||||
|
{
|
||||||
|
XTrace.WriteException(context.Failure);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ConfigureClaims()
|
||||||
|
{
|
||||||
|
ClaimActions.MapJsonKey(ClaimConst.AvatarUrl, "avatar_url");
|
||||||
|
ClaimActions.MapJsonKey(ClaimConst.Account, "login");
|
||||||
|
|
||||||
|
base.ConfigureClaims();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetName(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.TryGetProperty("login", out var loginProp))
|
||||||
|
{
|
||||||
|
return loginProp.GetString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleGitHubStarAsync(OAuthCreatingTicketContext context, string repoFullName = "ThingsGateway/ThingsGateway")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(context.AccessToken))
|
||||||
|
throw new InvalidOperationException("Access token is missing.");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Put, $"https://api.github.com/user/starred/{repoFullName}")
|
||||||
|
{
|
||||||
|
Headers =
|
||||||
|
{
|
||||||
|
Accept = { new MediaTypeWithQualityHeaderValue("application/vnd.github+json") },
|
||||||
|
Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken),
|
||||||
|
},
|
||||||
|
Content = new StringContent(string.Empty) // GitHub Star 接口需要空内容
|
||||||
|
};
|
||||||
|
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("ThingsGateway", "1.0")); // GitHub API 要求 User-Agent
|
||||||
|
|
||||||
|
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var id = context.Identity.Claims.FirstOrDefault(a => a.Type == ClaimConst.VerificatId).Value;
|
||||||
|
|
||||||
|
var verificatInfoIds = _verificatInfoService.GetOne(id.ToLong());
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(5000).ConfigureAwait(false);
|
||||||
|
await _noticeService.NavigationMesage(verificatInfoIds.ClientIds, "https://github.com/ThingsGateway/ThingsGateway", "创作不易,如有帮助请star仓库").ConfigureAwait(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>处理用户信息方法</summary>
|
||||||
|
public override async Task<JsonElement> HandleUserInfoAsync(HttpContext context, OAuthTokenResponse tokens)
|
||||||
|
{
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, UserInformationEndpoint);
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
|
||||||
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github+json"));
|
||||||
|
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("ThingsGateway", "1.0")); // GitHub API 要求 User-Agent
|
||||||
|
var response = await Backchannel.SendAsync(request, context.RequestAborted).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return JsonDocument.Parse(content).RootElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new OAuthTokenException($"OAuth user info endpoint failure: {await Display(response).ConfigureAwait(false)}");
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public class GithubOAuthSettings : GiteeOAuthSettings
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public static class OAuthUserExtensions
|
||||||
|
{
|
||||||
|
public static string TryGetValue(this JsonElement.ObjectEnumerator target, string propertyName)
|
||||||
|
{
|
||||||
|
return target.FirstOrDefault<JsonProperty>((Func<JsonProperty, bool>)(t => t.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase))).Value.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
@@ -87,7 +87,7 @@ public class BlazorAuthenticationHandler : AppAuthorizeHandler
|
|||||||
var roles = await _sysRoleService.GetRoleListByUserIdAsync(userId).ConfigureAwait(false);
|
var roles = await _sysRoleService.GetRoleListByUserIdAsync(userId).ConfigureAwait(false);
|
||||||
|
|
||||||
//这里鉴别用户使能状态
|
//这里鉴别用户使能状态
|
||||||
if (user == null || !user.Status)
|
if (user?.Status != true)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ public class BlazorAuthenticationHandler : AppAuthorizeHandler
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
//这里鉴别用户使能状态
|
//这里鉴别用户使能状态
|
||||||
if (user == null || !user.Status)
|
if (user?.Status != true)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ public class SysRelationSeedData : ISqlSugarEntitySeedData<SysRelation>
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<SysRelation> SeedData()
|
public IEnumerable<SysRelation> SeedData()
|
||||||
{
|
{
|
||||||
var db = DbContext.Db.GetConnectionScopeWithAttr<SysRelation>().CopyNew();
|
using var db = DbContext.GetDB<SysRelation>();
|
||||||
if (db.Queryable<SysRelation>().Any(a => a.ObjectId == RoleConst.SuperAdminId))
|
if (db.Queryable<SysRelation>().Any(a => a.ObjectId == RoleConst.SuperAdminId))
|
||||||
return Enumerable.Empty<SysRelation>();
|
return Enumerable.Empty<SysRelation>();
|
||||||
var data = SeedDataUtil.GetSeedData<SysRelation>(PathExtensions.CombinePathWithOs("SeedData", "Admin", "seed_sys_relation.json"));
|
var data = SeedDataUtil.GetSeedData<SysRelation>(PathExtensions.CombinePathWithOs("SeedData", "Admin", "seed_sys_relation.json"));
|
||||||
|
@@ -22,26 +22,19 @@ using System.Globalization;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
using ThingsGateway.Extension;
|
using ThingsGateway.Extension;
|
||||||
using ThingsGateway.SpecificationDocument;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
internal sealed class ApiPermissionService : IApiPermissionService
|
internal sealed class ApiPermissionService : IApiPermissionService
|
||||||
{
|
{
|
||||||
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionGroupCollectionProvider;
|
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionGroupCollectionProvider;
|
||||||
private readonly SwaggerGeneratorOptions _generatorOptions;
|
|
||||||
|
|
||||||
public ApiPermissionService(
|
public ApiPermissionService(
|
||||||
IOptions<SwaggerGeneratorOptions> generatorOptions,
|
IOptions<SwaggerGeneratorOptions> generatorOptions,
|
||||||
IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider)
|
IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider)
|
||||||
{
|
{
|
||||||
_generatorOptions = generatorOptions.Value;
|
|
||||||
_apiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider;
|
_apiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider;
|
||||||
}
|
}
|
||||||
private IEnumerable<string> GetDocumentNames()
|
|
||||||
{
|
|
||||||
return _generatorOptions.SwaggerDocs.Keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public List<OpenApiPermissionTreeSelector> ApiPermissionTreeSelector()
|
public List<OpenApiPermissionTreeSelector> ApiPermissionTreeSelector()
|
||||||
@@ -53,37 +46,37 @@ internal sealed class ApiPermissionService : IApiPermissionService
|
|||||||
permissions = new();
|
permissions = new();
|
||||||
|
|
||||||
Dictionary<string, OpenApiPermissionTreeSelector> groupOpenApis = new();
|
Dictionary<string, OpenApiPermissionTreeSelector> groupOpenApis = new();
|
||||||
foreach (var item in GetDocumentNames())
|
|
||||||
{
|
|
||||||
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = new() { ApiName = item ?? "Default" };
|
|
||||||
groupOpenApis.TryAdd(openApiPermissionTreeSelector.ApiName, openApiPermissionTreeSelector);
|
|
||||||
}
|
|
||||||
var apiDescriptions = _apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items;
|
var apiDescriptions = _apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items;
|
||||||
|
|
||||||
|
foreach (var item1 in apiDescriptions)
|
||||||
|
{
|
||||||
|
foreach (var item in item1.Items)
|
||||||
|
{
|
||||||
|
if (item.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
|
||||||
|
{
|
||||||
|
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = new() { ApiName = controllerActionDescriptor.ControllerName ?? "Default" };
|
||||||
|
groupOpenApis.TryAdd(openApiPermissionTreeSelector.ApiName, openApiPermissionTreeSelector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取所有需要数据权限的控制器
|
// 获取所有需要数据权限的控制器
|
||||||
var controllerTypes =
|
var controllerTypes =
|
||||||
App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(RolePermissionAttribute), false));
|
App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(RolePermissionAttribute), false));
|
||||||
|
|
||||||
foreach (var groupOpenApi in groupOpenApis)
|
//foreach (var groupOpenApi in groupOpenApis)
|
||||||
{
|
{
|
||||||
|
|
||||||
foreach (var apiDescriptionGroup in apiDescriptions)
|
foreach (var apiDescriptionGroup in apiDescriptions)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
var routes = apiDescriptionGroup.Items.Where(api => api.ActionDescriptor is ControllerActionDescriptor);
|
var routes = apiDescriptionGroup.Items.Where(api => api.ActionDescriptor is ControllerActionDescriptor);
|
||||||
|
|
||||||
OpenApiPermissionTreeSelector openApiPermissionTreeSelector = groupOpenApi.Value;
|
|
||||||
|
|
||||||
Dictionary<string, OpenApiPermissionTreeSelector> openApiPermissionTreeSelectorDict = new();
|
Dictionary<string, OpenApiPermissionTreeSelector> openApiPermissionTreeSelectorDict = new();
|
||||||
|
|
||||||
foreach (var route in routes)
|
foreach (var route in routes)
|
||||||
{
|
{
|
||||||
if (!SpecificationDocumentBuilder.CheckApiDescriptionInCurrentGroup(groupOpenApi.Key, route))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var actionDesc = (ControllerActionDescriptor)route.ActionDescriptor;
|
var actionDesc = (ControllerActionDescriptor)route.ActionDescriptor;
|
||||||
if (!actionDesc.ControllerTypeInfo.CustomAttributes.Any(a => a.AttributeType == typeof(RolePermissionAttribute)))
|
if (!actionDesc.ControllerTypeInfo.CustomAttributes.Any(a => a.AttributeType == typeof(RolePermissionAttribute)))
|
||||||
continue;
|
continue;
|
||||||
@@ -116,10 +109,8 @@ internal sealed class ApiPermissionService : IApiPermissionService
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
openApiPermissionTreeSelector.Children.AddRange(openApiPermissionTreeSelectorDict.Values);
|
if (openApiPermissionTreeSelectorDict.Values.Any(a => a.Children.Count > 0))
|
||||||
|
permissions.AddRange(openApiPermissionTreeSelectorDict.Values);
|
||||||
if (openApiPermissionTreeSelector.Children.Any(a => a.Children.Count > 0))
|
|
||||||
permissions.Add(openApiPermissionTreeSelector);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,12 +15,17 @@ using Microsoft.AspNetCore.WebUtilities;
|
|||||||
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
using UAParser;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
public class AppService : IAppService
|
public class AppService : IAppService
|
||||||
{
|
{
|
||||||
|
private readonly IUserAgentService UserAgentService;
|
||||||
|
private readonly IClaimsPrincipalService ClaimsPrincipalService;
|
||||||
|
public AppService(IUserAgentService userAgentService, IClaimsPrincipalService claimsPrincipalService)
|
||||||
|
{
|
||||||
|
UserAgentService = userAgentService;
|
||||||
|
ClaimsPrincipalService = claimsPrincipalService;
|
||||||
|
}
|
||||||
public string GetReturnUrl(string returnUrl)
|
public string GetReturnUrl(string returnUrl)
|
||||||
{
|
{
|
||||||
var url = QueryHelpers.AddQueryString(CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
|
var url = QueryHelpers.AddQueryString(CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
|
||||||
@@ -41,18 +46,16 @@ public class AppService : IAppService
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public Parser Parser = Parser.GetDefault();
|
public UserAgent? UserAgent
|
||||||
public ClientInfo? ClientInfo
|
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var str = App.HttpContext?.Request?.Headers?.UserAgent;
|
var str = App.HttpContext?.Request?.Headers?.UserAgent;
|
||||||
ClientInfo? clientInfo = null;
|
|
||||||
if (!string.IsNullOrEmpty(str))
|
if (!string.IsNullOrEmpty(str))
|
||||||
{
|
{
|
||||||
clientInfo = Parser.Parse(str);
|
return UserAgentService.Parse(str);
|
||||||
}
|
}
|
||||||
return clientInfo;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +72,7 @@ public class AppService : IAppService
|
|||||||
ExpiresUtc = diffTime,
|
ExpiresUtc = diffTime,
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
public ClaimsPrincipal? User => App.User;
|
public ClaimsPrincipal? User => ClaimsPrincipalService.User;
|
||||||
|
|
||||||
public string? RemoteIpAddress => App.HttpContext?.GetRemoteIpAddressToIPv4();
|
public string? RemoteIpAddress => App.HttpContext?.GetRemoteIpAddressToIPv4();
|
||||||
|
|
||||||
|
@@ -13,19 +13,17 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
using UAParser;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
public class HybridAppService : IAppService
|
public class HybridAppService : IAppService
|
||||||
{
|
{
|
||||||
public HybridAppService()
|
public HybridAppService(IUserAgentService userAgentService)
|
||||||
{
|
{
|
||||||
var str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0";
|
var str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0";
|
||||||
ClientInfo = Parser.GetDefault().Parse(str);
|
UserAgent = userAgentService.Parse(str);
|
||||||
RemoteIpAddress = "127.0.0.1";
|
RemoteIpAddress = "127.0.0.1";
|
||||||
}
|
}
|
||||||
public ClientInfo? ClientInfo { get; }
|
public UserAgent? UserAgent { get; }
|
||||||
|
|
||||||
private static BlazorHybridAuthenticationStateProvider _authenticationStateProvider;
|
private static BlazorHybridAuthenticationStateProvider _authenticationStateProvider;
|
||||||
private static BlazorHybridAuthenticationStateProvider AuthenticationStateProvider
|
private static BlazorHybridAuthenticationStateProvider AuthenticationStateProvider
|
||||||
|
@@ -8,14 +8,17 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace ThingsGateway.Razor;
|
using System.Security.Claims;
|
||||||
|
|
||||||
public abstract class ThingsGatewayRulesEngineComponentBase : BootstrapModuleComponentBase
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
public class HybridClaimsPrincipalService : IClaimsPrincipalService
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
HybridAppService _hybridAppService;
|
||||||
protected override void OnLoadJSModule()
|
public HybridClaimsPrincipalService(HybridAppService hybridAppService)
|
||||||
{
|
{
|
||||||
base.OnLoadJSModule();
|
_hybridAppService = hybridAppService;
|
||||||
ModulePath = $"./_content/ThingsGateway.RulesEngine/{ModulePath}";
|
|
||||||
}
|
}
|
||||||
|
public ClaimsPrincipal? User => _hybridAppService.User;
|
||||||
|
|
||||||
}
|
}
|
@@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
using UAParser;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
public interface IAppService
|
public interface IAppService
|
||||||
@@ -20,7 +18,7 @@ public interface IAppService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// ClientInfo
|
/// ClientInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClientInfo? ClientInfo { get; }
|
public UserAgent? UserAgent { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ClaimsPrincipal
|
/// ClaimsPrincipal
|
||||||
|
@@ -12,8 +12,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
using ThingsGateway.DataEncryption;
|
using ThingsGateway.DataEncryption;
|
||||||
@@ -64,6 +62,10 @@ public class AuthService : IAuthService
|
|||||||
{
|
{
|
||||||
throw Oops.Bah(appConfig.WebsitePolicy.CloseTip);
|
throw Oops.Bah(appConfig.WebsitePolicy.CloseTip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
string? password = input.Password;
|
string? password = input.Password;
|
||||||
if (isCookie) //openApi登录不再需要解密
|
if (isCookie) //openApi登录不再需要解密
|
||||||
{
|
{
|
||||||
@@ -96,9 +98,9 @@ public class AuthService : IAuthService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task LoginOutAsync()
|
public async Task LoginOutAsync()
|
||||||
{
|
{
|
||||||
if (UserManager.UserId == 0)
|
if (UserManager.VerificatId == 0)
|
||||||
return;
|
return;
|
||||||
var verificatId = UserManager.UserId;
|
var verificatId = UserManager.VerificatId;
|
||||||
//获取用户信息
|
//获取用户信息
|
||||||
var userinfo = await _sysUserService.GetUserByAccountAsync(UserManager.UserAccount, UserManager.TenantId).ConfigureAwait(false);
|
var userinfo = await _sysUserService.GetUserByAccountAsync(UserManager.UserAccount, UserManager.TenantId).ConfigureAwait(false);
|
||||||
if (userinfo != null)
|
if (userinfo != null)
|
||||||
@@ -237,25 +239,20 @@ public class AuthService : IAuthService
|
|||||||
var logingEvent = new LoginEvent
|
var logingEvent = new LoginEvent
|
||||||
{
|
{
|
||||||
Ip = _appService.RemoteIpAddress,
|
Ip = _appService.RemoteIpAddress,
|
||||||
Device = App.GetService<IAppService>().ClientInfo?.OS?.ToString(),
|
Device = _appService.UserAgent?.Platform,
|
||||||
Expire = expire,
|
Expire = expire,
|
||||||
SysUser = sysUser,
|
SysUser = sysUser,
|
||||||
VerificatId = verificatId
|
VerificatId = verificatId
|
||||||
};
|
};
|
||||||
await WriteTokenToCache(loginPolicy, logingEvent).ConfigureAwait(false);//写入verificat到cache
|
await WriteTokenToCache(loginPolicy, logingEvent).ConfigureAwait(false);//写入verificat到cache
|
||||||
await UpdateUser(logingEvent).ConfigureAwait(false);
|
await UpdateUser(logingEvent).ConfigureAwait(false);
|
||||||
if (sysUser.Account == RoleConst.SuperAdmin)
|
|
||||||
{
|
|
||||||
var modules = (await _sysResourceService.GetAllAsync().ConfigureAwait(false)).Where(a => a.Category == ResourceCategoryEnum.Module).OrderBy(a => a.SortCode);//获取模块列表
|
|
||||||
sysUser.ModuleList = modules.ToList();//模块列表赋值给用户
|
|
||||||
}
|
|
||||||
//返回结果
|
//返回结果
|
||||||
return new LoginOutput
|
return new LoginOutput
|
||||||
{
|
{
|
||||||
VerificatId = verificatId,
|
VerificatId = verificatId,
|
||||||
Account = sysUser.Account,
|
Account = sysUser.Account,
|
||||||
Id = sysUser.Id,
|
Id = sysUser.Id,
|
||||||
ModuleList = sysUser.ModuleList,
|
|
||||||
AccessToken = accessToken,
|
AccessToken = accessToken,
|
||||||
RefreshToken = refreshToken
|
RefreshToken = refreshToken
|
||||||
};
|
};
|
||||||
@@ -327,7 +324,7 @@ public class AuthService : IAuthService
|
|||||||
|
|
||||||
#endregion 重新赋值属性,设置本次登录信息为最新的信息
|
#endregion 重新赋值属性,设置本次登录信息为最新的信息
|
||||||
|
|
||||||
using var db = DbContext.Db.GetConnectionScopeWithAttr<SysUser>().CopyNew();
|
using var db = DbContext.GetDB<SysUser>();
|
||||||
//更新用户登录信息
|
//更新用户登录信息
|
||||||
if (await db.Updateable(sysUser).UpdateColumns(it => new
|
if (await db.Updateable(sysUser).UpdateColumns(it => new
|
||||||
{
|
{
|
||||||
|
@@ -77,7 +77,7 @@ internal sealed class SysDictService : BaseService<SysDict>, ISysDictService
|
|||||||
//更新数据
|
//更新数据
|
||||||
List<SysDict> dicts = new List<SysDict>()
|
List<SysDict> dicts = new List<SysDict>()
|
||||||
{
|
{
|
||||||
new SysDict() { DictType = DictTypeEnum.System, Category = nameof(PagePolicy), Name = nameof(PagePolicy.Shortcuts), Code = input.Shortcuts.ToJsonNetString() },
|
new SysDict() { DictType = DictTypeEnum.System, Category = nameof(PagePolicy), Name = nameof(PagePolicy.Shortcuts), Code = input.Shortcuts.ToSystemTextJsonString() },
|
||||||
};
|
};
|
||||||
var storageable = await db.Storageable(dicts).WhereColumns(it => new { it.DictType, it.Category, it.Name }).ToStorageAsync().ConfigureAwait(false);
|
var storageable = await db.Storageable(dicts).WhereColumns(it => new { it.DictType, it.Category, it.Name }).ToStorageAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
@@ -16,9 +16,9 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
/// 内存推送事件服务
|
/// 内存推送事件服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TEntry"></typeparam>
|
/// <typeparam name="TEntry"></typeparam>
|
||||||
public class EventService<TEntry> : IEventService<TEntry>
|
public class EventService<TEntry> : IEventService<TEntry>, IDisposable
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<string, Func<TEntry, Task>> Cache { get; } = new();
|
private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new();
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
@@ -11,8 +11,6 @@
|
|||||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
using Yitter.IdGenerator;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -28,7 +26,7 @@ public class UserIdProvider : IUserIdProvider
|
|||||||
|
|
||||||
if (UserId > 0)
|
if (UserId > 0)
|
||||||
{
|
{
|
||||||
return $"{UserId}{SysHub.Separate}{YitIdHelper.NextId()}";//返回用户ID
|
return $"{UserId}{SysHub.Separate}{CommonUtils.GetSingleId()}";//返回用户ID
|
||||||
}
|
}
|
||||||
|
|
||||||
return connection.ConnectionId;
|
return connection.ConnectionId;
|
||||||
|
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
internal sealed class SysOperateLogService : BaseService<SysOperateLog>, ISysOperateLogService
|
internal sealed class SysOperateLogService : BaseService<SysOperateLog>, ISysOperateLogService
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -10,10 +10,9 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.Extension.Generic;
|
using ThingsGateway.Extension.Generic;
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -10,9 +10,8 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -12,11 +12,10 @@ using BootstrapBlazor.Components;
|
|||||||
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -334,7 +333,7 @@ internal sealed class SysResourceService : BaseService<SysResource>, ISysResourc
|
|||||||
flatList.Add(node);
|
flatList.Add(node);
|
||||||
|
|
||||||
// 如果当前节点有子节点,则递归处理每个子节点
|
// 如果当前节点有子节点,则递归处理每个子节点
|
||||||
if (node.Children != null && node.Children.Count > 0)
|
if (node.Children?.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var child in node.Children)
|
foreach (var child in node.Children)
|
||||||
{
|
{
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -10,10 +10,9 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
using ThingsGateway.NewLife.Json.Extension;
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -277,7 +276,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
|
|||||||
if (isSuperAdmin)
|
if (isSuperAdmin)
|
||||||
throw Oops.Bah(Localizer["CanotGrantAdmin"]);
|
throw Oops.Bah(Localizer["CanotGrantAdmin"]);
|
||||||
var menuIds = input.GrantInfoList.Select(it => it.MenuId).ToList();//菜单ID
|
var menuIds = input.GrantInfoList.Select(it => it.MenuId).ToList();//菜单ID
|
||||||
var extJsons = input.GrantInfoList.Select(it => it.ToJsonNetString()).ToList();//拓展信息
|
var extJsons = input.GrantInfoList.Select(it => it.ToSystemTextJsonString()).ToList();//拓展信息
|
||||||
var relationRoles = new List<SysRelation>();//要添加的角色资源和授权关系表
|
var relationRoles = new List<SysRelation>();//要添加的角色资源和授权关系表
|
||||||
var sysRole = (await GetAllAsync().ConfigureAwait(false)).FirstOrDefault(it => it.Id == input.Id);//获取角色
|
var sysRole = (await GetAllAsync().ConfigureAwait(false)).FirstOrDefault(it => it.Id == input.Id);//获取角色
|
||||||
|
|
||||||
@@ -338,7 +337,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
|
|||||||
ExtJson = new RelationPermission
|
ExtJson = new RelationPermission
|
||||||
{
|
{
|
||||||
ApiUrl = it.ApiRoute,
|
ApiUrl = it.ApiRoute,
|
||||||
}.ToJsonNetString()
|
}.ToSystemTextJsonString()
|
||||||
});
|
});
|
||||||
relationRoles.AddRange(relationRolePer);//合并列表
|
relationRoles.AddRange(relationRolePer);//合并列表
|
||||||
}
|
}
|
||||||
@@ -410,7 +409,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService
|
|||||||
if (sysRole != null)
|
if (sysRole != null)
|
||||||
{
|
{
|
||||||
await _relationService.SaveRelationBatchAsync(RelationCategoryEnum.RoleHasOpenApiPermission, input.Id,
|
await _relationService.SaveRelationBatchAsync(RelationCategoryEnum.RoleHasOpenApiPermission, input.Id,
|
||||||
input.GrantInfoList.Select(a => (a.ApiUrl, a.ToJsonNetString()))
|
input.GrantInfoList.Select(a => (a.ApiUrl, a.ToSystemTextJsonString()))
|
||||||
, true).ConfigureAwait(false);//添加到数据库
|
, true).ConfigureAwait(false);//添加到数据库
|
||||||
await ClearTokenUtil.DeleteUserCacheByRoleIds(new List<long> { input.Id }).ConfigureAwait(false);//清除角色下用户缓存
|
await ClearTokenUtil.DeleteUserCacheByRoleIds(new List<long> { input.Id }).ConfigureAwait(false);//清除角色下用户缓存
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ using BootstrapBlazor.Components;
|
|||||||
|
|
||||||
using Mapster;
|
using Mapster;
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
|
@@ -12,13 +12,12 @@ using BootstrapBlazor.Components;
|
|||||||
|
|
||||||
using Mapster;
|
using Mapster;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.DataEncryption;
|
using ThingsGateway.DataEncryption;
|
||||||
using ThingsGateway.Extension;
|
using ThingsGateway.Extension;
|
||||||
using ThingsGateway.Extension.Generic;
|
using ThingsGateway.Extension.Generic;
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
using ThingsGateway.NewLife.Json.Extension;
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -435,7 +434,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
|||||||
if (sysUser != null)
|
if (sysUser != null)
|
||||||
{
|
{
|
||||||
await _relationService.SaveRelationBatchAsync(RelationCategoryEnum.UserHasOpenApiPermission, input.Id,
|
await _relationService.SaveRelationBatchAsync(RelationCategoryEnum.UserHasOpenApiPermission, input.Id,
|
||||||
input.GrantInfoList.Select(a => (a.ApiUrl, a.ToJsonNetString())),
|
input.GrantInfoList.Select(a => (a.ApiUrl, a.ToSystemTextJsonString())),
|
||||||
true).ConfigureAwait(false);//添加到数据库
|
true).ConfigureAwait(false);//添加到数据库
|
||||||
DeleteUserFromCache(input.Id);
|
DeleteUserFromCache(input.Id);
|
||||||
}
|
}
|
||||||
@@ -466,7 +465,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
|||||||
var exist = await GetUserByIdAsync(input.Id).ConfigureAwait(false);//获取用户信息
|
var exist = await GetUserByIdAsync(input.Id).ConfigureAwait(false);//获取用户信息
|
||||||
if (exist != null)
|
if (exist != null)
|
||||||
{
|
{
|
||||||
var isSuperAdmin = exist.Account == RoleConst.SuperAdmin;//判断是否有超管
|
var isSuperAdmin = exist.Id == RoleConst.SuperAdminId;//判断是否有超管
|
||||||
if (isSuperAdmin && !UserManager.SuperAdmin)
|
if (isSuperAdmin && !UserManager.SuperAdmin)
|
||||||
throw Oops.Bah(Localizer["CanotEditAdminUser"]);
|
throw Oops.Bah(Localizer["CanotEditAdminUser"]);
|
||||||
|
|
||||||
@@ -540,7 +539,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
|||||||
await CheckApiDataScopeAsync(sysUser.OrgId, sysUser.CreateUserId).ConfigureAwait(false);
|
await CheckApiDataScopeAsync(sysUser.OrgId, sysUser.CreateUserId).ConfigureAwait(false);
|
||||||
if (sysUser != null)
|
if (sysUser != null)
|
||||||
{
|
{
|
||||||
var isSuperAdmin = (sysUser.Account == RoleConst.SuperAdmin || input.GrantInfoList.Any(a => a == RoleConst.SuperAdminRoleId)) && !UserManager.SuperAdmin;//判断是否有超管
|
var isSuperAdmin = (sysUser.Id == RoleConst.SuperAdminId || input.GrantInfoList.Any(a => a == RoleConst.SuperAdminRoleId)) && !UserManager.SuperAdmin;//判断是否有超管
|
||||||
if (isSuperAdmin)
|
if (isSuperAdmin)
|
||||||
throw Oops.Bah(Localizer["CanotGrantAdmin"]);
|
throw Oops.Bah(Localizer["CanotGrantAdmin"]);
|
||||||
|
|
||||||
@@ -557,7 +556,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
|||||||
public async Task GrantResourceAsync(GrantResourceData input)
|
public async Task GrantResourceAsync(GrantResourceData input)
|
||||||
{
|
{
|
||||||
var menuIds = input.GrantInfoList.Select(it => it.MenuId).ToList();//菜单ID
|
var menuIds = input.GrantInfoList.Select(it => it.MenuId).ToList();//菜单ID
|
||||||
var extJsons = input.GrantInfoList.Select(it => it.ToJsonNetString()).ToList();//拓展信息
|
var extJsons = input.GrantInfoList.Select(it => it.ToSystemTextJsonString()).ToList();//拓展信息
|
||||||
var relationUsers = new List<SysRelation>();//要添加的用户资源和授权关系表
|
var relationUsers = new List<SysRelation>();//要添加的用户资源和授权关系表
|
||||||
var sysUser = await GetUserByIdAsync(input.Id).ConfigureAwait(false);//获取用户
|
var sysUser = await GetUserByIdAsync(input.Id).ConfigureAwait(false);//获取用户
|
||||||
await CheckApiDataScopeAsync(sysUser.OrgId, sysUser.CreateUserId).ConfigureAwait(false);
|
await CheckApiDataScopeAsync(sysUser.OrgId, sysUser.CreateUserId).ConfigureAwait(false);
|
||||||
@@ -613,7 +612,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
|||||||
TargetId = it.ApiRoute,
|
TargetId = it.ApiRoute,
|
||||||
Category = RelationCategoryEnum.UserHasPermission,
|
Category = RelationCategoryEnum.UserHasPermission,
|
||||||
ExtJson = new RelationPermission { ApiUrl = it.ApiRoute }
|
ExtJson = new RelationPermission { ApiUrl = it.ApiRoute }
|
||||||
.ToJsonNetString()
|
.ToSystemTextJsonString()
|
||||||
});
|
});
|
||||||
relationUsers.AddRange(relationUserPer);//合并列表
|
relationUsers.AddRange(relationUserPer);//合并列表
|
||||||
}
|
}
|
||||||
@@ -660,7 +659,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
|||||||
public async Task<bool> DeleteUserAsync(IEnumerable<long> ids)
|
public async Task<bool> DeleteUserAsync(IEnumerable<long> ids)
|
||||||
{
|
{
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
var containsSuperAdmin = await db.Queryable<SysUser>().Where(it => it.Account == RoleConst.SuperAdmin && ids.Contains(it.Id)).AnyAsync().ConfigureAwait(false);//判断是否有超管
|
var containsSuperAdmin = await db.Queryable<SysUser>().Where(it => it.Id == RoleConst.SuperAdminId && ids.Contains(it.Id)).AnyAsync().ConfigureAwait(false);//判断是否有超管
|
||||||
if (containsSuperAdmin)
|
if (containsSuperAdmin)
|
||||||
throw Oops.Bah(Localizer["CanotDeleteAdminUser"]);
|
throw Oops.Bah(Localizer["CanotDeleteAdminUser"]);
|
||||||
if (ids.Contains(UserManager.UserId))
|
if (ids.Contains(UserManager.UserId))
|
||||||
@@ -899,7 +898,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
|||||||
var tenantId = await _sysOrgService.GetTenantIdByOrgIdAsync(sysUser.OrgId, sysOrgList).ConfigureAwait(false);
|
var tenantId = await _sysOrgService.GetTenantIdByOrgIdAsync(sysUser.OrgId, sysOrgList).ConfigureAwait(false);
|
||||||
sysUser.TenantId = tenantId;
|
sysUser.TenantId = tenantId;
|
||||||
|
|
||||||
if (sysUser.Account == RoleConst.SuperAdmin)
|
if (sysUser.Id == RoleConst.SuperAdminId)
|
||||||
{
|
{
|
||||||
var modules = (await _sysResourceService.GetAllAsync().ConfigureAwait(false)).Where(a => a.Category == ResourceCategoryEnum.Module).OrderBy(a => a.SortCode);
|
var modules = (await _sysResourceService.GetAllAsync().ConfigureAwait(false)).Where(a => a.Category == ResourceCategoryEnum.Module).OrderBy(a => a.SortCode);
|
||||||
sysUser.ModuleList = modules.ToList();//模块列表赋值给用户
|
sysUser.ModuleList = modules.ToList();//模块列表赋值给用户
|
||||||
|
@@ -203,7 +203,7 @@ internal sealed class UserCenterService : BaseService<SysUser>, IUserCenterServi
|
|||||||
public async Task UpdateWorkbenchInfoAsync(WorkbenchInfo input)
|
public async Task UpdateWorkbenchInfoAsync(WorkbenchInfo input)
|
||||||
{
|
{
|
||||||
//关系表保存个人工作台
|
//关系表保存个人工作台
|
||||||
await _relationService.SaveRelationAsync(RelationCategoryEnum.UserWorkbenchData, input.Id, null, input.Shortcuts.ToJsonNetString(),
|
await _relationService.SaveRelationAsync(RelationCategoryEnum.UserWorkbenchData, input.Id, null, input.Shortcuts.ToSystemTextJsonString(),
|
||||||
true).ConfigureAwait(false);
|
true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,10 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
using ThingsGateway.List;
|
|
||||||
using ThingsGateway.NewLife.Json.Extension;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
@@ -40,6 +37,8 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
|
|||||||
|
|
||||||
private VerificatInfo? GetFromDb(long id)
|
private VerificatInfo? GetFromDb(long id)
|
||||||
{
|
{
|
||||||
|
if (id == 0)
|
||||||
|
return null;
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
var verificatInfo = db.Queryable<VerificatInfo>().First(u => u.Id == id);
|
var verificatInfo = db.Queryable<VerificatInfo>().First(u => u.Id == id);
|
||||||
if (verificatInfo != null)
|
if (verificatInfo != null)
|
||||||
@@ -169,7 +168,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
|
|||||||
public void RemoveAllClientId()
|
public void RemoveAllClientId()
|
||||||
{
|
{
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
db.Updateable<VerificatInfo>().SetColumns("ClientIds", new ConcurrentList<long>().ToJsonNetString()).Where(a => a.Id >= 0).ExecuteCommand();
|
db.Updateable<VerificatInfo>().SetColumns(a => a.ClientIds == null).Where(a => a.Id > 0).ExecuteCommand();
|
||||||
VerificatInfoService.RemoveCache();
|
VerificatInfoService.RemoveCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,11 +10,8 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
using ThingsGateway.UnifyResult;
|
using ThingsGateway.UnifyResult;
|
||||||
@@ -28,18 +25,12 @@ public class Startup : AppStartup
|
|||||||
{
|
{
|
||||||
Directory.CreateDirectory("DB");
|
Directory.CreateDirectory("DB");
|
||||||
|
|
||||||
services.AddConfigurableOptions<SqlSugarOptions>();
|
|
||||||
services.AddConfigurableOptions<AdminLogOptions>();
|
services.AddConfigurableOptions<AdminLogOptions>();
|
||||||
services.AddConfigurableOptions<TenantOptions>();
|
services.AddConfigurableOptions<TenantOptions>();
|
||||||
|
|
||||||
services.AddSingleton(typeof(IDataService<>), typeof(BaseService<>));
|
services.AddSingleton<IUserAgentService, UserAgentService>();
|
||||||
services.AddSingleton<ISugarAopService, SugarAopService>();
|
|
||||||
services.AddSingleton<ISugarConfigAopService, SugarConfigAopService>();
|
|
||||||
|
|
||||||
services.AddSingleton<IAppService, AppService>();
|
services.AddSingleton<IAppService, AppService>();
|
||||||
|
|
||||||
StaticConfig.EnableAllWhereIF = true;
|
|
||||||
|
|
||||||
services.AddConfigurableOptions<EmailOptions>();
|
services.AddConfigurableOptions<EmailOptions>();
|
||||||
services.AddConfigurableOptions<HardwareInfoOptions>();
|
services.AddConfigurableOptions<HardwareInfoOptions>();
|
||||||
|
|
||||||
@@ -56,7 +47,6 @@ public class Startup : AppStartup
|
|||||||
|
|
||||||
services.AddSingleton<IVerificatInfoService, VerificatInfoService>();
|
services.AddSingleton<IVerificatInfoService, VerificatInfoService>();
|
||||||
services.AddSingleton<IUserCenterService, UserCenterService>();
|
services.AddSingleton<IUserCenterService, UserCenterService>();
|
||||||
services.AddSingleton<ISugarAopService, SugarAopService>();
|
|
||||||
services.AddSingleton<ISysDictService, SysDictService>();
|
services.AddSingleton<ISysDictService, SysDictService>();
|
||||||
services.AddSingleton<ISysOperateLogService, SysOperateLogService>();
|
services.AddSingleton<ISysOperateLogService, SysOperateLogService>();
|
||||||
services.AddSingleton<IRelationService, RelationService>();
|
services.AddSingleton<IRelationService, RelationService>();
|
||||||
@@ -76,7 +66,7 @@ public class Startup : AppStartup
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Use(IApplicationBuilder applicationBuilder)
|
public void Use(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
//检查ConfigId
|
//检查ConfigId
|
||||||
var configIdGroup = DbContext.DbConfigs.GroupBy(it => it.ConfigId);
|
var configIdGroup = DbContext.DbConfigs.GroupBy(it => it.ConfigId);
|
||||||
@@ -88,8 +78,8 @@ public class Startup : AppStartup
|
|||||||
//遍历配置
|
//遍历配置
|
||||||
DbContext.DbConfigs?.ForEach(it =>
|
DbContext.DbConfigs?.ForEach(it =>
|
||||||
{
|
{
|
||||||
var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象
|
var connection = DbContext.GetDB().GetConnection(it.ConfigId);//获取数据库连接对象
|
||||||
if (it.InitTable == true)
|
if (it.InitDatabase == true)
|
||||||
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,6 +87,21 @@ public class Startup : AppStartup
|
|||||||
CodeFirstUtils.CodeFirst(fullName!);//CodeFirst
|
CodeFirstUtils.CodeFirst(fullName!);//CodeFirst
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var db = DbContext.GetDB<SysOperateLog>();
|
||||||
|
if (db.CurrentConnectionConfig.DbType == SqlSugar.DbType.Sqlite)
|
||||||
|
{
|
||||||
|
if (!db.DbMaintenance.IsAnyIndex("idx_operatelog_optime_date"))
|
||||||
|
{
|
||||||
|
var indexsql = "CREATE INDEX idx_operatelog_optime_date ON sys_operatelog(strftime('%Y-%m-%d', OpTime));";
|
||||||
|
db.Ado.ExecuteCommand(indexsql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
|
||||||
//删除在线用户统计
|
//删除在线用户统计
|
||||||
var verificatInfoService = App.RootServices.GetService<IVerificatInfoService>();
|
var verificatInfoService = App.RootServices.GetService<IVerificatInfoService>();
|
||||||
verificatInfoService.RemoveAllClientId();
|
verificatInfoService.RemoveAllClientId();
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<Import Project="$(SolutionDir)Version.props" />
|
<Import Project="..\..\Version.props" />
|
||||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
<Import Project="..\..\PackNuget.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
@@ -18,10 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.4" />
|
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
|
||||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
|
||||||
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
|
|
||||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.191" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||||
@@ -30,9 +27,9 @@
|
|||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET9Version)" />
|
||||||
<PackageReference Include="System.Formats.Asn1" Version="9.0.4" />
|
<PackageReference Include="System.Formats.Asn1" Version="$(NET9Version)" />
|
||||||
<PackageReference Include="System.Threading.RateLimiting" Version="9.0.4" />
|
<PackageReference Include="System.Threading.RateLimiting" Version="$(NET9Version)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Remove="SeedData\Admin\*.json" />
|
<Content Remove="SeedData\Admin\*.json" />
|
||||||
@@ -44,12 +41,20 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\README.md" Pack="true" PackagePath="\" />
|
<None Include="..\README.md" Pack="true" PackagePath="\" />
|
||||||
<None Include="..\README.zh-CN.md" Pack="true" PackagePath="\" />
|
<None Include="..\README.zh-CN.md" Pack="true" PackagePath="\" />
|
||||||
<None Remove="$(SolutionDir)..\README.md" Pack="false" PackagePath="\" />
|
<None Remove="..\..\..\README.md" Pack="false" PackagePath="\" />
|
||||||
<None Remove="$(SolutionDir)..\README.zh-CN.md" Pack="false" PackagePath="\" />
|
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />
|
<ProjectReference Include="..\ThingsGateway.DB\ThingsGateway.DB.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<!--<Target Name="Mapster" AfterTargets="AfterBuild">
|
||||||
|
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool restore" />
|
||||||
|
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster extension -o MapsterGenerator -a "$(TargetDir)$(ProjectName).dll"" />
|
||||||
|
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster mapper -o MapsterGenerator -a "$(TargetDir)$(ProjectName).dll"" />
|
||||||
|
</Target>-->
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
namespace ThingsGateway.Admin.Application
|
||||||
|
{
|
||||||
|
/// <summary>Default interface for UserAgentService</summary>
|
||||||
|
public interface IUserAgentService
|
||||||
|
{
|
||||||
|
/// <summary>Gets or sets the settings.</summary>
|
||||||
|
public UserAgentSettings Settings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Parses the specified user agent string.</summary>
|
||||||
|
/// <param name="userAgentString">The user agent string.</param>
|
||||||
|
/// <returns>An UserAgent object</returns>
|
||||||
|
UserAgent? Parse(string userAgentString);
|
||||||
|
}
|
||||||
|
}
|
145
src/Admin/ThingsGateway.Admin.Application/UserAgent/UserAgent.cs
Normal file
145
src/Admin/ThingsGateway.Admin.Application/UserAgent/UserAgent.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parsed UserAgent object
|
||||||
|
/// </summary>
|
||||||
|
public class UserAgent
|
||||||
|
{
|
||||||
|
private readonly UserAgentSettings settings;
|
||||||
|
|
||||||
|
internal string Agent = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this UserAgent is a browser.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if this UserAgent is a browser; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public bool IsBrowser { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this UserAgent is a robot.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if this UserAgent is a robot; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public bool IsRobot { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this UserAgent is a mobile device.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if this UserAgent is a mobile device; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public bool IsMobile { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the platform.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The platform or operating system.
|
||||||
|
/// </value>
|
||||||
|
public string Platform { get; set; } = "";
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the browser.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The browser.
|
||||||
|
/// </value>
|
||||||
|
public string Browser { get; set; } = "";
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the browser version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The browser version.
|
||||||
|
/// </value>
|
||||||
|
public string BrowserVersion { get; set; } = "";
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the mobile device.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The mobile device.
|
||||||
|
/// </value>
|
||||||
|
public string Mobile { get; set; } = "";
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the robot.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The robot.
|
||||||
|
/// </value>
|
||||||
|
public string Robot { get; set; } = "";
|
||||||
|
|
||||||
|
internal UserAgent(UserAgentSettings settings, string? userAgentString = null)
|
||||||
|
{
|
||||||
|
this.settings = settings;
|
||||||
|
|
||||||
|
if (userAgentString != null)
|
||||||
|
{
|
||||||
|
Agent = userAgentString.Trim();
|
||||||
|
SetPlatform();
|
||||||
|
if (SetRobot()) return;
|
||||||
|
if (SetBrowser()) return;
|
||||||
|
if (SetMobile()) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool SetPlatform()
|
||||||
|
{
|
||||||
|
foreach (var item in settings.Platforms)
|
||||||
|
{
|
||||||
|
if (Regex.IsMatch(Agent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase))
|
||||||
|
{
|
||||||
|
Platform = item.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Platform = "Unknown Platform";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool SetBrowser()
|
||||||
|
{
|
||||||
|
foreach (var item in settings.Browsers)
|
||||||
|
{
|
||||||
|
var match = Regex.Match(Agent, $@"{item.Key}.*?([0-9\.]+)", RegexOptions.IgnoreCase);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
IsBrowser = true;
|
||||||
|
BrowserVersion = match.Groups[1].Value;
|
||||||
|
Browser = item.Value;
|
||||||
|
SetMobile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool SetRobot()
|
||||||
|
{
|
||||||
|
foreach (var item in settings.Robots)
|
||||||
|
{
|
||||||
|
if (Regex.IsMatch(Agent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase))
|
||||||
|
{
|
||||||
|
IsRobot = true;
|
||||||
|
Robot = item.Value;
|
||||||
|
SetMobile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool SetMobile()
|
||||||
|
{
|
||||||
|
foreach (var item in settings.Mobiles)
|
||||||
|
{
|
||||||
|
if (Agent?.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
IsMobile = true;
|
||||||
|
Mobile = item.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
using ThingsGateway.NewLife.Caching;
|
||||||
|
|
||||||
|
namespace ThingsGateway.Admin.Application
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The UserAgent service
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ThingsGateway.Admin.Application.IUserAgentService" />
|
||||||
|
public class UserAgentService : IUserAgentService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the settings.
|
||||||
|
/// </summary>
|
||||||
|
public UserAgentSettings Settings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UserAgentService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public UserAgentService()
|
||||||
|
{
|
||||||
|
Settings = new UserAgentSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryCache MemoryCache { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the specified user agent string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userAgentString">The user agent string.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An UserAgent object
|
||||||
|
/// </returns>
|
||||||
|
public UserAgent? Parse(string? userAgentString)
|
||||||
|
{
|
||||||
|
userAgentString = ((userAgentString?.Length > Settings.UaStringSizeLimit) ? userAgentString?.Trim().Substring(0, Settings.UaStringSizeLimit) : userAgentString?.Trim()) ?? "";
|
||||||
|
return MemoryCache.GetOrAdd(userAgentString, entry =>
|
||||||
|
{
|
||||||
|
return new UserAgent(Settings, userAgentString);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,214 @@
|
|||||||
|
namespace ThingsGateway.Admin.Application
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UserAgent settings container.
|
||||||
|
/// </summary>
|
||||||
|
public class UserAgentSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum size of the useragent string. Limiting the length of the useragent string protects from hackers sending in extremely long user agent strings.
|
||||||
|
/// </summary>
|
||||||
|
public int UaStringSizeLimit { get; set; } = 512;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a dictionary containing mappings for platforms.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Platforms { get; } = new()
|
||||||
|
{
|
||||||
|
{"windows nt 10.0", "Windows 10"},
|
||||||
|
{"windows nt 6.3", "Windows 8.1"},
|
||||||
|
{"windows nt 6.2", "Windows 8"},
|
||||||
|
{"windows nt 6.1", "Windows 7"},
|
||||||
|
{"windows nt 6.0", "Windows Vista"},
|
||||||
|
{"windows nt 5.2", "Windows 2003"},
|
||||||
|
{"windows nt 5.1", "Windows XP"},
|
||||||
|
{"windows nt 5.0", "Windows 2000"},
|
||||||
|
{"windows nt 4.0", "Windows NT 4.0"},
|
||||||
|
{"winnt4.0", "Windows NT 4.0"},
|
||||||
|
{"winnt 4.0", "Windows NT"},
|
||||||
|
{"winnt", "Windows NT"},
|
||||||
|
{"windows 98", "Windows 98"},
|
||||||
|
{"win98", "Windows 98"},
|
||||||
|
{"windows 95", "Windows 95"},
|
||||||
|
{"win95", "Windows 95"},
|
||||||
|
{"windows phone", "Windows Phone"},
|
||||||
|
{"windows", "Unknown Windows OS"},
|
||||||
|
{"android", "Android"},
|
||||||
|
{"blackberry", "BlackBerry"},
|
||||||
|
{"iphone", "iOS"},
|
||||||
|
{"ipad", "iOS"},
|
||||||
|
{"ipod", "iOS"},
|
||||||
|
{"os x", "Mac OS X"},
|
||||||
|
{"ppc mac", "Power PC Mac"},
|
||||||
|
{"freebsd", "FreeBSD"},
|
||||||
|
{"ppc", "Macintosh"},
|
||||||
|
{"linux", "Linux"},
|
||||||
|
{"debian", "Debian"},
|
||||||
|
{"sunos", "Sun Solaris"},
|
||||||
|
{"beos", "BeOS"},
|
||||||
|
{"apachebench", "ApacheBench"},
|
||||||
|
{"aix", "AIX"},
|
||||||
|
{"irix", "Irix"},
|
||||||
|
{"osf", "DEC OSF"},
|
||||||
|
{"hp-ux", "HP-UX"},
|
||||||
|
{"netbsd", "NetBSD"},
|
||||||
|
{"bsdi", "BSDi"},
|
||||||
|
{"openbsd", "OpenBSD"},
|
||||||
|
{"gnu", "GNU/Linux"},
|
||||||
|
{"unix", "Unknown Unix OS"},
|
||||||
|
{"symbian", "Symbian OS"},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a dictionary containing mappings for browsers.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Browsers { get; } = new()
|
||||||
|
{
|
||||||
|
{"Microsoft Outlook", "Microsoft Outlook"},
|
||||||
|
{"OPR", "Opera"},
|
||||||
|
{"Flock", "Flock"},
|
||||||
|
{"Edge", "Edge"},
|
||||||
|
{"Edg", "Edge"},
|
||||||
|
{"Chrome", "Chrome"},
|
||||||
|
{"Opera.*?Version", "Opera"},
|
||||||
|
{"Opera", "Opera"},
|
||||||
|
{"MSIE", "Internet Explorer"},
|
||||||
|
{"Internet Explorer", "Internet Explorer"},
|
||||||
|
{"Trident.* rv" , "Internet Explorer"},
|
||||||
|
{"Shiira", "Shiira"},
|
||||||
|
{"Firefox", "Firefox"},
|
||||||
|
{"Chimera", "Chimera"},
|
||||||
|
{"Phoenix", "Phoenix"},
|
||||||
|
{"Firebird", "Firebird"},
|
||||||
|
{"Camino", "Camino"},
|
||||||
|
{"Netscape", "Netscape"},
|
||||||
|
{"OmniWeb", "OmniWeb"},
|
||||||
|
{"Safari", "Safari"},
|
||||||
|
{"Mozilla", "Mozilla"},
|
||||||
|
{"Konqueror", "Konqueror"},
|
||||||
|
{"icab", "iCab"},
|
||||||
|
{"Lynx", "Lynx"},
|
||||||
|
{"Links", "Links"},
|
||||||
|
{"hotjava", "HotJava"},
|
||||||
|
{"amaya", "Amaya"},
|
||||||
|
{"IBrowse", "IBrowse"},
|
||||||
|
{"Maxthon", "Maxthon"},
|
||||||
|
{"Ubuntu", "Ubuntu Web Browser"},
|
||||||
|
{"Vivaldi", "Vivaldi"},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a dictionary containing mappings for mobiles.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Mobiles { get; } = new()
|
||||||
|
{
|
||||||
|
// Legacy
|
||||||
|
{"mobileexplorer", "Mobile Explorer"},
|
||||||
|
{"palmsource", "Palm"},
|
||||||
|
{"palmscape", "Palmscape"},
|
||||||
|
// Phones and Manufacturers
|
||||||
|
{"motorola", "Motorola"},
|
||||||
|
{"nokia", "Nokia"},
|
||||||
|
{"palm", "Palm"},
|
||||||
|
{"iphone", "Apple iPhone"},
|
||||||
|
{"ipad", "iPad"},
|
||||||
|
{"ipod", "Apple iPod Touch"},
|
||||||
|
{"sony", "Sony Ericsson"},
|
||||||
|
{"ericsson", "Sony Ericsson"},
|
||||||
|
{"blackberry", "BlackBerry"},
|
||||||
|
{"cocoon", "O2 Cocoon"},
|
||||||
|
{"blazer", "Treo"},
|
||||||
|
{"lg", "LG"},
|
||||||
|
{"amoi", "Amoi"},
|
||||||
|
{"xda", "XDA"},
|
||||||
|
{"mda", "MDA"},
|
||||||
|
{"vario", "Vario"},
|
||||||
|
{"htc", "HTC"},
|
||||||
|
{"samsung", "Samsung"},
|
||||||
|
{"sharp", "Sharp"},
|
||||||
|
{"sie-", "Siemens"},
|
||||||
|
{"alcatel", "Alcatel"},
|
||||||
|
{"benq", "BenQ"},
|
||||||
|
{"ipaq", "HP iPaq"},
|
||||||
|
{"mot-", "Motorola"},
|
||||||
|
{"playstation portable", "PlayStation Portable"},
|
||||||
|
{"playstation 3", "PlayStation 3"},
|
||||||
|
{"playstation vita", "PlayStation Vita"},
|
||||||
|
{"hiptop", "Danger Hiptop"},
|
||||||
|
{"nec-", "NEC"},
|
||||||
|
{"panasonic", "Panasonic"},
|
||||||
|
{"philips", "Philips"},
|
||||||
|
{"sagem", "Sagem"},
|
||||||
|
{"sanyo", "Sanyo"},
|
||||||
|
{"spv", "SPV"},
|
||||||
|
{"zte", "ZTE"},
|
||||||
|
{"sendo", "Sendo"},
|
||||||
|
{"nintendo dsi", "Nintendo DSi"},
|
||||||
|
{"nintendo ds", "Nintendo DS"},
|
||||||
|
{"nintendo 3ds", "Nintendo 3DS"},
|
||||||
|
{"wii", "Nintendo Wii"},
|
||||||
|
{"open web", "Open Web"},
|
||||||
|
{"openweb", "OpenWeb"},
|
||||||
|
// Operating Systems
|
||||||
|
{"android", "Android"},
|
||||||
|
{"symbian", "Symbian"},
|
||||||
|
{"SymbianOS", "SymbianOS"},
|
||||||
|
{"elaine", "Palm"},
|
||||||
|
{"series60", "Symbian S60"},
|
||||||
|
{"windows ce", "Windows CE"},
|
||||||
|
// Browsers
|
||||||
|
{"obigo", "Obigo"},
|
||||||
|
{"netfront", "Netfront Browser"},
|
||||||
|
{"openwave", "Openwave Browser"},
|
||||||
|
{"mobilexplorer", "Mobile Explorer"},
|
||||||
|
{"operamini", "Opera Mini"},
|
||||||
|
{"opera mini", "Opera Mini"},
|
||||||
|
{"opera mobi", "Opera Mobile"},
|
||||||
|
{"fennec", "Firefox Mobile"},
|
||||||
|
// Other
|
||||||
|
{"digital paths", "Digital Paths"},
|
||||||
|
{"avantgo", "AvantGo"},
|
||||||
|
{"xiino", "Xiino"},
|
||||||
|
{"novarra", "Novarra Transcoder"},
|
||||||
|
{"vodafone", "Vodafone"},
|
||||||
|
{"docomo", "NTT DoCoMo"},
|
||||||
|
{"o2", "O2"},
|
||||||
|
// Fallback
|
||||||
|
{"mobile", "Generic Mobile"},
|
||||||
|
{"wireless", "Generic Mobile"},
|
||||||
|
{"j2me", "Generic Mobile"},
|
||||||
|
{"midp", "Generic Mobile"},
|
||||||
|
{"cldc", "Generic Mobile"},
|
||||||
|
{"up.link", "Generic Mobile"},
|
||||||
|
{"up.browser", "Generic Mobile"},
|
||||||
|
{"smartphone", "Generic Mobile"},
|
||||||
|
{"cellphone", "Generic Mobile"},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a dictionary containing mappings for robots.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Robots { get; } = new()
|
||||||
|
{
|
||||||
|
{"googlebot", "Googlebot"},
|
||||||
|
{"msnbot", "MSNBot"},
|
||||||
|
{"baiduspider", "Baiduspider"},
|
||||||
|
{"bingbot", "Bing"},
|
||||||
|
{"slurp", "Inktomi Slurp"},
|
||||||
|
{"yahoo", "Yahoo"},
|
||||||
|
{"ask jeeves", "Ask Jeeves"},
|
||||||
|
{"fastcrawler", "FastCrawler"},
|
||||||
|
{"infoseek", "InfoSeek Robot 1.0"},
|
||||||
|
{"lycos", "Lycos"},
|
||||||
|
{"yandex", "YandexBot"},
|
||||||
|
{"mediapartners-google", "MediaPartners Google"},
|
||||||
|
{"CRAZYWEBCRAWLER", "Crazy Webcrawler"},
|
||||||
|
{"adsbot-google", "AdsBot Google"},
|
||||||
|
{"feedfetcher-google", "Feedfetcher Google"},
|
||||||
|
{"curious george", "Curious George"},
|
||||||
|
{"ia_archiver", "Alexa Crawler"},
|
||||||
|
{"MJ12bot", "Majestic-12"},
|
||||||
|
{"Uptimebot", "Uptimebot"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -13,7 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class ClearTokenUtil
|
public static class ClearTokenUtil
|
||||||
{
|
{
|
||||||
private static IRelationService RelationService;
|
private static IRelationService RelationService;
|
||||||
private static ISysUserService SysUserService;
|
private static ISysUserService SysUserService;
|
||||||
@@ -40,7 +40,7 @@ public class ClearTokenUtil
|
|||||||
public static async Task DeleteUserTokenByOrgIds(HashSet<long> orgIds)
|
public static async Task DeleteUserTokenByOrgIds(HashSet<long> orgIds)
|
||||||
{
|
{
|
||||||
// 获取用户ID列表
|
// 获取用户ID列表
|
||||||
var userIds = await DbContext.Db.CopyNew().QueryableWithAttr<SysUser>().Where(it => orgIds.Contains(it.OrgId)).Select(it => it.Id).ToListAsync().ConfigureAwait(false);
|
var userIds = await DbContext.GetDB<SysUser>().Queryable<SysUser>().Where(it => orgIds.Contains(it.OrgId)).Select(it => it.Id).ToListAsync().ConfigureAwait(false);
|
||||||
//从redis中删除所属机构的用户token
|
//从redis中删除所属机构的用户token
|
||||||
App.CacheService.HashDel<VerificatInfo>(CacheConst.Cache_Token, userIds.Select(it => it.ToString()).ToArray());
|
App.CacheService.HashDel<VerificatInfo>(CacheConst.Cache_Token, userIds.Select(it => it.ToString()).ToArray());
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class NoticeUtil
|
public static class NoticeUtil
|
||||||
{
|
{
|
||||||
private static INoticeService NoticeService;
|
private static INoticeService NoticeService;
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class OpenApiUtil
|
public static class OpenApiUtil
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建树节点,传入的列表已经是树结构
|
/// 构建树节点,传入的列表已经是树结构
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class OrgUtil
|
public static class OrgUtil
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造选择项,ID/TITLE
|
/// 构造选择项,ID/TITLE
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class PositionUtil
|
public static class PositionUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class RoleUtil
|
public static class RoleUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Application;
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class UserUtil
|
public static class UserUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -8,13 +8,12 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.Extension.Generic;
|
using ThingsGateway.Extension.Generic;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Application;
|
namespace ThingsGateway.Admin.Application;
|
||||||
|
|
||||||
public class VerificatInfoUtil
|
public static class VerificatInfoUtil
|
||||||
{
|
{
|
||||||
private static IVerificatInfoService VerificatInfoService { get; set; }
|
private static IVerificatInfoService VerificatInfoService { get; set; }
|
||||||
static VerificatInfoUtil()
|
static VerificatInfoUtil()
|
||||||
|
@@ -5,6 +5,9 @@
|
|||||||
@<div>
|
@<div>
|
||||||
<span class="mx-3">@item.ConfirmMessage</span>
|
<span class="mx-3">@item.ConfirmMessage</span>
|
||||||
|
|
||||||
<Button Text=@Localizers["Jump"] Color="Color.Link" OnClick="()=>NavigationManager.NavigateTo(item.Uri)"></Button>
|
<a href=@item.Uri target="_blank">
|
||||||
|
@item.Uri
|
||||||
|
</a>
|
||||||
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
namespace ThingsGateway.Admin.Razor;
|
namespace ThingsGateway.Admin.Razor;
|
||||||
|
|
||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public class AdminOperConst
|
public static class AdminOperConst
|
||||||
{
|
{
|
||||||
public const string Add = "新增";
|
public const string Add = "新增";
|
||||||
public const string Delete = "删除";
|
public const string Delete = "删除";
|
||||||
|
@@ -40,6 +40,8 @@ public class BlazorAppContext
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public SysUser CurrentUser { get; private set; }
|
public SysUser CurrentUser { get; private set; }
|
||||||
|
|
||||||
|
public string? Avatar => UserManager.AvatarUrl.IsNullOrEmpty() ? CurrentUser.Avatar : UserManager.AvatarUrl;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户个人菜单
|
/// 用户个人菜单
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -97,7 +99,7 @@ public class BlazorAppContext
|
|||||||
AllResource = sysResources;
|
AllResource = sysResources;
|
||||||
var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet();
|
var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet();
|
||||||
CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList();
|
CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList();
|
||||||
AllMenus = sysResources.Where(a => a.Category == ResourceCategoryEnum.Menu);
|
AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu);
|
||||||
|
|
||||||
if (moduleId == null)
|
if (moduleId == null)
|
||||||
{
|
{
|
||||||
@@ -154,7 +156,7 @@ public class BlazorAppContext
|
|||||||
CurrentUser = (await SysUserService.GetUserByIdAsync(UserManager.UserId))!;
|
CurrentUser = (await SysUserService.GetUserByIdAsync(UserManager.UserId))!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TimeTick timeTick = new("50000");
|
TimeTick timeTick = new("60000");
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否拥有按钮授权
|
/// 是否拥有按钮授权
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -19,3 +19,4 @@ global using System.Diagnostics.CodeAnalysis;
|
|||||||
global using ThingsGateway.Razor;
|
global using ThingsGateway.Razor;
|
||||||
|
|
||||||
[assembly: SuppressMessage("Reliability", "CA2007", Justification = "<挂起>", Scope = "module")]
|
[assembly: SuppressMessage("Reliability", "CA2007", Justification = "<挂起>", Scope = "module")]
|
||||||
|
[assembly: GlobalGenerateSetParametersAsync(true)]
|
@@ -1,74 +1,73 @@
|
|||||||
{
|
{
|
||||||
"ThingsGateway.Admin.Razor.LoginConnectionHub": {
|
|
||||||
"Jump": "Jump"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Razor.UserLogin": {
|
|
||||||
"Account": "Username",
|
|
||||||
"Password": "Password",
|
|
||||||
"Login": "Login"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Razor._Imports": {
|
"ThingsGateway.Admin.Razor._Imports": {
|
||||||
"Org": "Org",
|
|
||||||
"Position": "Position",
|
|
||||||
"Role": "Role",
|
|
||||||
"RoleList": "Role List",
|
|
||||||
"PositionList": "Position List",
|
|
||||||
"OrgList": "Org List",
|
|
||||||
"MaxCount": "Exceeding the quantity limit",
|
|
||||||
"All": "All",
|
"All": "All",
|
||||||
"Copy": "Copy",
|
|
||||||
"Choice": "Choice",
|
"Choice": "Choice",
|
||||||
"Picture": "Picture",
|
"Copy": "Copy",
|
||||||
"Root": "Root",
|
|
||||||
"Save": "Save",
|
|
||||||
"CurrentVerificat": "CurrentVerificat",
|
"CurrentVerificat": "CurrentVerificat",
|
||||||
"EmptyText": "Empty",
|
"EmptyText": "Empty",
|
||||||
"SelectPlaceHolder": "Please select ...",
|
"ExportButtonText": "Export/Import",
|
||||||
"ExportButtonText": "Export/Import"
|
"MaxCount": "Exceeding the quantity limit",
|
||||||
|
"Org": "Org",
|
||||||
|
"OrgList": "Org List",
|
||||||
|
"Picture": "Picture",
|
||||||
|
"Position": "Position",
|
||||||
|
"PositionList": "Position List",
|
||||||
|
"Role": "Role",
|
||||||
|
"RoleList": "Role List",
|
||||||
|
"Root": "Root",
|
||||||
|
"Save": "Save",
|
||||||
|
"SelectPlaceHolder": "Please select ..."
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.ChoiceModuleComponent": {
|
"ThingsGateway.Admin.Razor.ChoiceModuleComponent": {
|
||||||
"SetDefaultModule": "Set as default module"
|
"SetDefaultModule": "Set as default module"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
||||||
"SystemInfo": "System Information",
|
"AvailableDisk": "Available Disk",
|
||||||
|
"AvailableMemory": "Available Memory",
|
||||||
|
"CpuUsage": "CPU Usage",
|
||||||
|
"Data": "Data",
|
||||||
|
"DateTime": "Date Time",
|
||||||
|
"DiskUsage": "Disk Usage",
|
||||||
"HardwareInfo": "Hardware Resources",
|
"HardwareInfo": "Hardware Resources",
|
||||||
"HardwareInfoChart": "Hardware Resources History Chart",
|
"HardwareInfoChart": "Hardware Resources History Chart",
|
||||||
"CpuUsage": "CPU Usage",
|
|
||||||
"AvailableMemory": "Available Memory",
|
|
||||||
"TotalMemory": "Total Memory",
|
|
||||||
"WorkingSet": "WorkingSet",
|
|
||||||
"MemoryUsage": "Memory",
|
|
||||||
"AvailableDisk": "Available Disk",
|
|
||||||
"TotalDisk": "Total Disk",
|
|
||||||
"DiskUsage": "Disk Usage",
|
|
||||||
"HistoryHardwareInfo": "Historical Trends",
|
"HistoryHardwareInfo": "Historical Trends",
|
||||||
"DateTime": "Date Time",
|
"MemoryUsage": "Memory",
|
||||||
"Data": "Data",
|
|
||||||
"OSName": "OS Name",
|
"OSName": "OS Name",
|
||||||
"OSVersion": "OS Version"
|
"OSVersion": "OS Version",
|
||||||
|
"SystemInfo": "System Information",
|
||||||
|
"TotalDisk": "Total Disk",
|
||||||
|
"TotalMemory": "Total Memory",
|
||||||
|
"WorkingSet": "WorkingSet"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Razor.LoginConnectionHub": {
|
||||||
|
"Jump": "Jump"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.OperLogPage": {
|
"ThingsGateway.Admin.Razor.OperLogPage": {
|
||||||
"Date": "Date",
|
|
||||||
"Count": "Count",
|
"Count": "Count",
|
||||||
"Operate": "Succeed",
|
"Date": "Date",
|
||||||
"Exception": "Failed",
|
"Exception": "Failed",
|
||||||
"Login": "Login",
|
"Login": "Login",
|
||||||
"Logout": "Logout",
|
"Logout": "Logout",
|
||||||
|
"Operate": "Succeed",
|
||||||
"SysOperateLog": "Operation Log"
|
"SysOperateLog": "Operation Log"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.SessionPage": {
|
"ThingsGateway.Admin.Razor.SessionPage": {
|
||||||
"VerificatInfo": "Token List",
|
"ExitSession": "Force Logout",
|
||||||
"ExitSession": "Force Logout"
|
"VerificatInfo": "Token List"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Razor.UserCenterPage": {
|
||||||
|
"UpdatePasswordInfo": "Change Password",
|
||||||
|
"UpdateUserInfo": "Update Personal Information",
|
||||||
|
"UpdateWorkbenchInfo": "Update Personal Workbench",
|
||||||
|
"UserInfo": "Personal Information",
|
||||||
|
"WorkbenchInfo": "Personal Workbench"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Razor.UserLogin": {
|
||||||
|
"Account": "Username",
|
||||||
|
"Login": "Login",
|
||||||
|
"Password": "Password"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.VerificatListDialog": {
|
"ThingsGateway.Admin.Razor.VerificatListDialog": {
|
||||||
"ExitVerificat": "Force Logout Token"
|
"ExitVerificat": "Force Logout Token"
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Razor.UserCenterPage": {
|
|
||||||
"UpdateUserInfo": "Update Personal Information",
|
|
||||||
"UserInfo": "Personal Information",
|
|
||||||
"UpdatePasswordInfo": "Change Password",
|
|
||||||
"UpdateWorkbenchInfo": "Update Personal Workbench",
|
|
||||||
"WorkbenchInfo": "Personal Workbench"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,74 +1,73 @@
|
|||||||
{
|
{
|
||||||
"ThingsGateway.Admin.Razor.LoginConnectionHub": {
|
|
||||||
"Jump": "跳转"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Razor.UserLogin": {
|
|
||||||
"Account": "登录账号",
|
|
||||||
"Password": "登录密码",
|
|
||||||
"Login": "登录"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Razor._Imports": {
|
"ThingsGateway.Admin.Razor._Imports": {
|
||||||
|
"All": "全部",
|
||||||
|
"Choice": "选择",
|
||||||
|
"Copy": "复制",
|
||||||
|
"CurrentVerificat": "当前令牌",
|
||||||
|
"EmptyText": "空",
|
||||||
|
"ExportButtonText": "导出/导入",
|
||||||
|
"MaxCount": "超过数量限制",
|
||||||
"Org": "机构",
|
"Org": "机构",
|
||||||
|
"OrgList": "机构列表",
|
||||||
|
"Picture": "头像",
|
||||||
"Position": "岗位",
|
"Position": "岗位",
|
||||||
|
"PositionList": "岗位列表",
|
||||||
"Role": "角色",
|
"Role": "角色",
|
||||||
"RoleList": "角色列表",
|
"RoleList": "角色列表",
|
||||||
"PositionList": "岗位列表",
|
|
||||||
"OrgList": "机构列表",
|
|
||||||
"MaxCount": "超过数量限制",
|
|
||||||
"All": "全部",
|
|
||||||
"Copy": "复制",
|
|
||||||
"Choice": "选择",
|
|
||||||
"Picture": "头像",
|
|
||||||
"Root": "根目录",
|
"Root": "根目录",
|
||||||
"Save": "保存",
|
"Save": "保存",
|
||||||
"CurrentVerificat": "当前令牌",
|
"SelectPlaceHolder": "请选择 ..."
|
||||||
"SelectPlaceHolder": "请选择 ...",
|
|
||||||
"EmptyText": "空",
|
|
||||||
"ExportButtonText": "导出/导入"
|
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.ChoiceModuleComponent": {
|
"ThingsGateway.Admin.Razor.ChoiceModuleComponent": {
|
||||||
"SetDefaultModule": "设置为默认模块"
|
"SetDefaultModule": "设置为默认模块"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
||||||
"SystemInfo": "系统信息",
|
"AvailableDisk": "可用磁盘",
|
||||||
|
"AvailableMemory": "可用内存",
|
||||||
|
"CpuUsage": "CPU使用率",
|
||||||
|
"Data": "数据",
|
||||||
|
"DateTime": "时间",
|
||||||
|
"DiskUsage": "磁盘使用率",
|
||||||
"HardwareInfo": "硬件资源",
|
"HardwareInfo": "硬件资源",
|
||||||
"HardwareInfoChart": "硬件资源历史曲线",
|
"HardwareInfoChart": "硬件资源历史曲线",
|
||||||
"CpuUsage": "CPU使用率",
|
|
||||||
"AvailableMemory": "可用内存",
|
|
||||||
"TotalMemory": "总内存",
|
|
||||||
"WorkingSet": "进程内存",
|
|
||||||
"MemoryUsage": "内存",
|
|
||||||
"AvailableDisk": "可用磁盘",
|
|
||||||
"TotalDisk": "总磁盘",
|
|
||||||
"DiskUsage": "磁盘使用率",
|
|
||||||
"HistoryHardwareInfo": "历史曲线",
|
"HistoryHardwareInfo": "历史曲线",
|
||||||
"DateTime": "时间",
|
"MemoryUsage": "内存",
|
||||||
"Data": "数据",
|
|
||||||
"OSName": "系统名称",
|
"OSName": "系统名称",
|
||||||
"OSVersion": "系统版本"
|
"OSVersion": "系统版本",
|
||||||
|
"SystemInfo": "系统信息",
|
||||||
|
"TotalDisk": "总磁盘",
|
||||||
|
"TotalMemory": "总内存",
|
||||||
|
"WorkingSet": "进程内存"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Razor.LoginConnectionHub": {
|
||||||
|
"Jump": "跳转"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.OperLogPage": {
|
"ThingsGateway.Admin.Razor.OperLogPage": {
|
||||||
"Date": "日期",
|
|
||||||
"Count": "数量",
|
"Count": "数量",
|
||||||
"Operate": "成功",
|
"Date": "日期",
|
||||||
"Exception": "失败",
|
"Exception": "失败",
|
||||||
"Login": "登录",
|
"Login": "登录",
|
||||||
"Logout": "注销",
|
"Logout": "注销",
|
||||||
|
"Operate": "成功",
|
||||||
"SysOperateLog": "操作日志"
|
"SysOperateLog": "操作日志"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.SessionPage": {
|
"ThingsGateway.Admin.Razor.SessionPage": {
|
||||||
"VerificatInfo": "令牌列表",
|
"ExitSession": "强退会话",
|
||||||
"ExitSession": "强退会话"
|
"VerificatInfo": "令牌列表"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Razor.UserCenterPage": {
|
||||||
|
"UpdatePasswordInfo": "修改密码",
|
||||||
|
"UpdateUserInfo": "更新个人信息",
|
||||||
|
"UpdateWorkbenchInfo": "更新个人工作台",
|
||||||
|
"UserInfo": "个人信息",
|
||||||
|
"WorkbenchInfo": "个人工作台"
|
||||||
|
},
|
||||||
|
"ThingsGateway.Admin.Razor.UserLogin": {
|
||||||
|
"Account": "登录账号",
|
||||||
|
"Login": "登录",
|
||||||
|
"Password": "登录密码"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Admin.Razor.VerificatListDialog": {
|
"ThingsGateway.Admin.Razor.VerificatListDialog": {
|
||||||
"ExitVerificat": "强退令牌"
|
"ExitVerificat": "强退令牌"
|
||||||
},
|
|
||||||
"ThingsGateway.Admin.Razor.UserCenterPage": {
|
|
||||||
"UpdateUserInfo": "更新个人信息",
|
|
||||||
"UserInfo": "个人信息",
|
|
||||||
"UpdatePasswordInfo": "修改密码",
|
|
||||||
"UpdateWorkbenchInfo": "更新个人工作台",
|
|
||||||
"WorkbenchInfo": "个人工作台"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -8,10 +8,9 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.Admin.Application;
|
using ThingsGateway.Admin.Application;
|
||||||
using ThingsGateway.NewLife.Extension;
|
using ThingsGateway.NewLife.Extension;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Razor;
|
namespace ThingsGateway.Admin.Razor;
|
||||||
|
|
||||||
|
@@ -8,9 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.Admin.Application;
|
using ThingsGateway.Admin.Application;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Razor;
|
namespace ThingsGateway.Admin.Razor;
|
||||||
|
|
||||||
|
@@ -8,10 +8,9 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.Admin.Application;
|
using ThingsGateway.Admin.Application;
|
||||||
using ThingsGateway.NewLife.Extension;
|
using ThingsGateway.NewLife.Extension;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Razor;
|
namespace ThingsGateway.Admin.Razor;
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
</PopConfirmButton>
|
</PopConfirmButton>
|
||||||
<PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count!=1||!AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
|
<PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count!=1||!AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
|
||||||
<BodyTemplate>
|
<BodyTemplate>
|
||||||
<div class="min-height-500 overflow-y-auto">
|
<div class="overflow-y-auto" style="height:500px">
|
||||||
<TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" />
|
<TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" />
|
||||||
</div>
|
</div>
|
||||||
</BodyTemplate>
|
</BodyTemplate>
|
||||||
|
@@ -8,10 +8,9 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
using ThingsGateway.Admin.Application;
|
using ThingsGateway.Admin.Application;
|
||||||
using ThingsGateway.NewLife.Extension;
|
using ThingsGateway.NewLife.Extension;
|
||||||
|
using ThingsGateway.SqlSugar;
|
||||||
|
|
||||||
namespace ThingsGateway.Admin.Razor;
|
namespace ThingsGateway.Admin.Razor;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user