feat: 基础模块

注册登录
用户管理
角色管理
部门管理
消息管理
接口管理
菜单管理
字典管理
缓存管理
请求日志
系统设置
版本信息
代码生成
This commit is contained in:
tk 2023-11-17 18:54:31 +08:00
parent 18b4d7547a
commit d26e4c77cc
755 changed files with 30416 additions and 42743 deletions

72
.commitlintrc.js Normal file
View File

@ -0,0 +1,72 @@
// .commitlintrc.js
/** @type {import('cz-git').UserConfig} */
module.exports = {
rules: {
// @see: https://commitlint.js.org/#/reference-rules
},
prompt: {
alias: { fd: 'docs: fix typos' },
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixesSelect: '选择关联issue前缀可选:',
customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?'
},
types: [
{ value: 'wip', name: 'wip: 开发之中 | In development ', emoji:'🧠' },
{ value: 'feat', name: 'feat: 新增功能 | A new feature', emoji:'✨' },
{ value: 'fix', name: 'fix: 修复缺陷 | A bug fix', emoji:'🐛' },
{ value: 'docs', name: 'docs: 文档更新 | Documentation only changes', emoji:'📝' },
{ value: 'style', name: 'style: 代码格式 | Changes that do not affect the meaning of the code', emoji:'💄' },
{ value: 'refactor', name: 'refactor: 代码重构 | A code change that neither fixes a bug nor adds a feature', emoji:'♻️' },
{ value: 'perf', name: 'perf: 性能提升 | A code change that improves performance', emoji:'⚡' },
{ value: 'test', name: 'test: 测试相关 | Adding missing tests or correcting existing tests', emoji:'✅' },
{ value: 'build', name: 'build: 构建相关 | Changes that affect the build system or external dependencies', emoji:'📦' },
{ value: 'ci', name: 'ci: 持续集成 | Changes to our CI configuration files and scripts', emoji:'🎡' },
{ value: 'revert', name: 'revert: 回退代码 | Revert to a commit', emoji:'⏪' },
{ value: 'chore', name: 'chore: 其他修改 | Other changes that do not modify src or test files', emoji:'🔨' },
],
useEmoji: true,
emojiAlign: 'center',
useAI: false,
aiNumber: 1,
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixes: [
// 如果使用 gitee 作为开发管理
{ value: 'link', name: 'link: 链接 ISSUES 进行中' },
{ value: 'closed', name: 'closed: 标记 ISSUES 已完成' }
],
customIssuePrefixAlign: 'top',
emptyIssuePrefixAlias: 'skip',
customIssuePrefixAlias: 'custom',
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
}

View File

@ -15,25 +15,24 @@ indent_size = 2
[*.cs] [*.cs]
dotnet_analyzer_diagnostic.severity = warning dotnet_analyzer_diagnostic.severity = warning
dotnet_diagnostic.CA1200.severity = none
dotnet_diagnostic.CA1707.severity = none dotnet_diagnostic.CA1707.severity = none
dotnet_diagnostic.CA1716.severity = none
dotnet_diagnostic.IDE0005.severity = none dotnet_diagnostic.IDE0005.severity = none
dotnet_diagnostic.IDE0008.severity = none dotnet_diagnostic.IDE0008.severity = none
dotnet_diagnostic.IDE0010.severity = none dotnet_diagnostic.IDE0010.severity = none
dotnet_diagnostic.IDE0028.severity = none
dotnet_diagnostic.IDE0055.severity = none dotnet_diagnostic.IDE0055.severity = none
dotnet_diagnostic.IDE0160.severity = none dotnet_diagnostic.IDE0160.severity = none
dotnet_diagnostic.IDE0270.severity = none dotnet_diagnostic.IDE0300.severity = none
dotnet_diagnostic.IDE0305.severity = none
dotnet_diagnostic.RCS1141.severity = none dotnet_diagnostic.RCS1141.severity = none
dotnet_diagnostic.RCS1142.severity = none dotnet_diagnostic.RCS1142.severity = none
dotnet_diagnostic.RCS1181.severity = none dotnet_diagnostic.RCS1181.severity = none
dotnet_diagnostic.RCS1186.severity = none
dotnet_diagnostic.S101.severity = none dotnet_diagnostic.S101.severity = none
dotnet_diagnostic.S1121.severity = none dotnet_diagnostic.S1121.severity = none
dotnet_diagnostic.S1199.severity = none dotnet_diagnostic.S1135.severity = none
dotnet_diagnostic.S125.severity = none dotnet_diagnostic.S125.severity = none
dotnet_diagnostic.S2094.severity = none dotnet_diagnostic.S2094.severity = none
dotnet_diagnostic.S3925.severity = none dotnet_diagnostic.S3604.severity = none
dotnet_diagnostic.S4663.severity = none dotnet_diagnostic.S4663.severity = none
dotnet_diagnostic.SYSLIB1045.severity = none dotnet_diagnostic.SYSLIB1045.severity = none

52
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,52 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
name: CI
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 20.x ]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
# https://docs.github.com/zh/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('./src/frontend/admin/package.json') }}
restore-keys: ${{ runner.os }}-npm
- name: Publish frontend
working-directory: ./src/frontend/admin
run:
npm install && npm run build
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: ${{ runner.os }}-nuget
- name: Publish backend
working-directory: ./src/backend/NetAdmin.BizServer.Host
run: dotnet publish NetAdmin.BizServer.Host.csproj -c Release
- name: Build docker images
run: docker build -t nsnail/netadmin .
- name: Docker login
uses: docker/login-action@v3
with:
username: "nsnail"
password: "${{secrets.DOCKER_PASSWORD}}"
- name: Push docker images
run: docker push nsnail/netadmin

4
.tgitconfig Normal file
View File

@ -0,0 +1,4 @@
[hook "startcommit"]
cmdline = dot.clean.cmd
wait = true
show = true

10
1.git.pr.ps1 Normal file
View File

@ -0,0 +1,10 @@
$branch = $( git branch --show-current )
./dot.clean.cmd
git add .
./node_modules/.bin/git-cz.ps1
git pull
git push --set-upstream origin $branch
Start-Process -FilePath "https://github.com/nsnail/NetAdmin/compare/main...$branch"
Write-Host "按『Enter』重建分支『Ctrl+C』退出"
Pause
./git.rc.ps1

View File

@ -1,198 +0,0 @@
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var outputDirectory = Argument("output-directory", "./dist/Server/publish");
////////////////////////////////////////////////////////////////
// Tasks
Task("Clean")
.Does(context =>
{
context.CleanDirectory("./dist");
});
Task("Build")
.IsDependentOn("Clean")
.Does(context =>
{
DotNetBuild("./NetAdmin.sln", new DotNetBuildSettings {
Configuration = configuration
});
});
Task("Publish-BizServer")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.BizServer.Host/NetAdmin.BizServer.Host.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
Task("Publish-SdkServer")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.SdkServer.Host/NetAdmin.SdkServer.Host.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
Task("Publish-ManServer")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.ManServer.Host/NetAdmin.ManServer.Host.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
Task("Publish-SdkService")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.SdkService/NetAdmin.SdkService.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
Task("Publish-ManService")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.ManService/NetAdmin.ManService.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
Task("Publish-CallbackService")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.CallbackService/NetAdmin.CallbackService.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
Task("Publish-ScheduledService")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.ScheduledService/NetAdmin.ScheduledService.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
Task("Publish-PushService")
.Does(context =>
{
DotNetPublish("./src/Server/NetAdmin.PushService/NetAdmin.PushService.csproj", new DotNetPublishSettings {
NoBuild = true,
Configuration = configuration,
OutputDirectory = new DirectoryPath(outputDirectory)
});
});
// Task("Test")
// .IsDependentOn("Build")
// .Does(context =>
// {
// DotNetTest("./test/Spectre.Console.Tests/Spectre.Console.Tests.csproj", new DotNetTestSettings {
// Configuration = configuration,
// NoRestore = true,
// NoBuild = true,
// });
//
// DotNetTest("./test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj", new DotNetTestSettings {
// Configuration = configuration,
// NoRestore = true,
// NoBuild = true,
// });
//
// DotNetTest("./test/Spectre.Console.Analyzer.Tests/Spectre.Console.Analyzer.Tests.csproj", new DotNetTestSettings {
// Configuration = configuration,
// NoRestore = true,
// NoBuild = true,
// });
// });
// Task("Publish-GitHub")
// .WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
// //.IsDependentOn("Package")
// .Does(context =>
// {
// var apiKey = Argument<string>("github-key", null);
// if(string.IsNullOrWhiteSpace(apiKey)) {
// throw new CakeException("No GitHub API key was provided.");
// }
//
// // Publish to GitHub Packages
// var exitCode = 0;
// foreach(var file in context.GetFiles("./.artifacts/*.nupkg"))
// {
// context.Information("Publishing {0}...", file.GetFilename().FullPath);
// exitCode += StartProcess("dotnet",
// new ProcessSettings {
// Arguments = new ProcessArgumentBuilder()
// .Append("gpr")
// .Append("push")
// .AppendQuoted(file.FullPath)
// .AppendSwitchSecret("-k", " ", apiKey)
// }
// );
// }
//
// if(exitCode != 0)
// {
// throw new CakeException("Could not push GitHub packages.");
// }
// });
// Task("Publish-NuGet")
// //.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
// //.IsDependentOn("Package")
// .Does(context =>
// {
// var apiKey = Argument<string>("nuget-key", null);
// if(string.IsNullOrWhiteSpace(apiKey)) {
// throw new CakeException("No NuGet API key was provided.");
// }
//
// // Publish to GitHub Packages
// foreach(var file in context.GetFiles("./.artifacts/*.nupkg"))
// {
// context.Information("Publishing {0}...", file.GetFilename().FullPath);
// DotNetNuGetPush(file.FullPath, new DotNetNuGetPushSettings
// {
// Source = "https://api.nuget.org/v3/index.json",
// ApiKey = apiKey,
// SkipDuplicate = true
// });
// }
// });
////////////////////////////////////////////////////////////////
// Targets
// Task("Publish")
// .IsDependentOn("Publish-GitHub")
// .IsDependentOn("Publish-NuGet");
Task("Default")
.IsDependentOn("Build");
////////////////////////////////////////////////////////////////
// Execution
RunTarget(target)

10
CHANGELOG.md Normal file
View File

@ -0,0 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## 1.0.0 (2023-11-17)
### Features
* ✨ 基础模块 ([cd2a241](https://github.com/nsnail/NetAdmin/commit/cd2a2412b91bd07346b1d95b19102d48883479a5))

View File

@ -1,26 +1,34 @@
<!-- 注意此文件名大小写不可变更 -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<!-- $(XXX) 定义有顺序 排序请注意-->
<SolutionDir>$(MSBuildThisFileDirectory)</SolutionDir> <SolutionDir>$(MSBuildThisFileDirectory)</SolutionDir>
<BaseIntermediateOutputPath>$(SolutionDir)/dist/Server/$(MSBuildProjectName)/obj</BaseIntermediateOutputPath> </PropertyGroup>
<BaseOutputPath>$(SolutionDir)/dist/Server/$(MSBuildProjectName)/bin</BaseOutputPath> <Import Project="$(SolutionDir)/minver.targets"/>
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Authors>nsnail</Authors>
<BaseIntermediateOutputPath>$(SolutionDir)/dist/backend/$(MSBuildProjectName)/obj</BaseIntermediateOutputPath>
<BaseOutputPath>$(SolutionDir)/dist/backend/$(MSBuildProjectName)/bin</BaseOutputPath>
<Copyright>© 2006-2023 nsnail</Copyright>
<Description>一个基于.Net8/Vue3极致优雅的RBAC通用权限管理模板</Description>
<EnableBaseIntermediateOutputPathMismatchWarning>false</EnableBaseIntermediateOutputPathMismatchWarning> <EnableBaseIntermediateOutputPathMismatchWarning>false</EnableBaseIntermediateOutputPathMismatchWarning>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<MinVerDefaultPreReleaseIdentifiers>beta</MinVerDefaultPreReleaseIdentifiers> <MinVerDefaultPreReleaseIdentifiers>beta</MinVerDefaultPreReleaseIdentifiers>
<Product>NetAdmin</Product> <Product>NetAdmin</Product>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>http://git.shequnpay.com/lingyun/NetAdmin.git</RepositoryUrl> <RepositoryUrl>http://git.shequnpay.com/lingyun/NetAdmin.git</RepositoryUrl>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Title>$(AssemblyName)</Title> <Title>$(AssemblyName)</Title>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MinVer" Version="5.0.0-alpha.1"> <PackageReference Include="MinVer" Version="5.0.0-beta.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="../GlobalUsings.cs" Link="GlobalUsings.cs" /> <Compile Include="../GlobalUsings.cs" Link="GlobalUsings.cs"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
RUN apt update
RUN apt install -y redis
COPY ./dist/backend/NetAdmin.BizServer.Host/bin/Release/net8.0/publish .
ENTRYPOINT redis-server --daemonize yes && dotnet NetAdmin.BizServer.Host.dll

View File

@ -9,23 +9,46 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "backend", "backend", "{4DAF
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{5198A03D-0CAC-4828-A807-34A693F73859}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{5198A03D-0CAC-4828-A807-34A693F73859}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.commitlintrc.js = .commitlintrc.js
.editorconfig = .editorconfig .editorconfig = .editorconfig
.gitattributes = .gitattributes .gitattributes = .gitattributes
.gitignore = .gitignore .gitignore = .gitignore
Build.cake = Build.cake .tgitconfig = .tgitconfig
CodeQuality.props = CodeQuality.props 1.git.pr.ps1 = 1.git.pr.ps1
CopyPackageXmlCommentFiles.targets = CopyPackageXmlCommentFiles.targets build.cake = build.cake
clone.project.refs.ps1 = clone.project.refs.ps1
code.clean.csx = code.clean.csx
code.clean.ps1 = code.clean.ps1
code.cleanup.full.ps1 = code.cleanup.full.ps1
code.quality.props = code.quality.props
copy.pkg.xml.comment.files.targets = copy.pkg.xml.comment.files.targets
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
Dockerfile = Dockerfile
dot.clean.cmd = dot.clean.cmd
dotnet-tools.json = dotnet-tools.json dotnet-tools.json = dotnet-tools.json
gen.cs.tt = gen.cs.tt
gen.id.linq = gen.id.linq
gen.ln.cmd = gen.ln.cmd
gen.resx.tt = gen.resx.tt
git.rc.ps1 = git.rc.ps1
global.json = global.json global.json = global.json
image.optimize.csx = image.optimize.csx
install.as.tpl.ps1 = install.as.tpl.ps1
LICENSE = LICENSE LICENSE = LICENSE
minver.targets = minver.targets
NetAdmin.sln.DotSettings = NetAdmin.sln.DotSettings NetAdmin.sln.DotSettings = NetAdmin.sln.DotSettings
NuGet.Config = NuGet.Config nuget.config = nuget.config
package.json = package.json package.json = package.json
PreBuild.targets = PreBuild.targets prebuild.targets = prebuild.targets
README.md = README.md README.md = README.md
rename.csx = rename.csx
stylecop.analyzers.ruleset = stylecop.analyzers.ruleset
StyleCop.json = StyleCop.json StyleCop.json = StyleCop.json
StyleCopAnalyzers.ruleset = StyleCopAnalyzers.ruleset switcher.freesql.json = switcher.freesql.json
switcher.furion.json = switcher.furion.json
switcher.nsext.json = switcher.nsext.json
switcher.ps1 = switcher.ps1
sync.sln.files.csx = sync.sln.files.csx
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetAdmin.Infrastructure", "src\backend\NetAdmin.Infrastructure\NetAdmin.Infrastructure.csproj", "{1E62C322-EE42-4699-A6F1-791C53EFA62D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetAdmin.Infrastructure", "src\backend\NetAdmin.Infrastructure\NetAdmin.Infrastructure.csproj", "{1E62C322-EE42-4699-A6F1-791C53EFA62D}"
@ -54,8 +77,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetAdmin.SysComponent.Appli
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02.Components", "02.Components", "{3F23258D-8299-4992-9F51-2EE9B52CF9D2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02.Components", "02.Components", "{3F23258D-8299-4992-9F51-2EE9B52CF9D2}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "21.System", "21.System", "{E146D707-4D44-47B3-A8F9-D51EC5E1D047}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "04.BackgroundServices", "04.BackgroundServices", "{CBFBF29B-27E8-4DB1-ADD6-4B750897ACD3}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "04.BackgroundServices", "04.BackgroundServices", "{CBFBF29B-27E8-4DB1-ADD6-4B750897ACD3}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01.Frameworks", "01.Frameworks", "{D9C3EF66-2757-473D-A26B-54FD08DA203F}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01.Frameworks", "01.Frameworks", "{D9C3EF66-2757-473D-A26B-54FD08DA203F}"
@ -66,28 +87,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "06.Tests", "06.Tests", "{89
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetAdmin.BizServer.Tests", "src\backend\NetAdmin.BizServer.Tests\NetAdmin.BizServer.Tests.csproj", "{C7F27698-DA05-4ACD-B0D7-4791B3972002}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetAdmin.BizServer.Tests", "src\backend\NetAdmin.BizServer.Tests\NetAdmin.BizServer.Tests.csproj", "{C7F27698-DA05-4ACD-B0D7-4791B3972002}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{2D81D62C-1B2E-4758-84C6-728343CB734F}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1129FE25-466B-4F4F-85FC-3752664245E1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{3C6F049E-3EE8-4D66-9AFF-E8A369032487}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
CloneProjectRefs.ps1 = tools/CloneProjectRefs.ps1 ci.yml = .github/workflows/ci.yml
CodeCleanup.csx = tools/CodeCleanup.csx
CodeCleanup.ps1 = tools/CodeCleanup.ps1
CodeCleanupFull.ps1 = tools/CodeCleanupFull.ps1
DotClean.cmd = tools/DotClean.cmd
GenerateLn.cmd = tools/GenerateLn.cmd
GenerateLnCs.tt = tools/GenerateLnCs.tt
GenerateLnResx.tt = tools/GenerateLnResx.tt
GitPR.ps1 = tools/GitPR.ps1
GitRecreate.ps1 = tools/GitRecreate.ps1
IdGenerator.linq = tools/IdGenerator.linq
ImageOptimize.csx = tools/ImageOptimize.csx
InstallAsTpl.ps1 = tools/InstallAsTpl.ps1
Switcher.FreeSql.json = tools/Switcher.FreeSql.json
Switcher.Furion.json = tools/Switcher.Furion.json
Switcher.NSExt.json = tools/Switcher.NSExt.json
Switcher.ps1 = tools/Switcher.ps1
SyncMetaFiles.csx = tools/SyncMetaFiles.csx
EndProjectSection EndProjectSection
EndProjectSection EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetAdmin.Tests", "src\backend\NetAdmin.Tests\NetAdmin.Tests.csproj", "{00604162-C444-478B-B773-3AB23C856CA7}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -150,15 +157,15 @@ Global
{C7F27698-DA05-4ACD-B0D7-4791B3972002}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7F27698-DA05-4ACD-B0D7-4791B3972002}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7F27698-DA05-4ACD-B0D7-4791B3972002}.Release|Any CPU.ActiveCfg = Release|Any CPU {C7F27698-DA05-4ACD-B0D7-4791B3972002}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7F27698-DA05-4ACD-B0D7-4791B3972002}.Release|Any CPU.Build.0 = Release|Any CPU {C7F27698-DA05-4ACD-B0D7-4791B3972002}.Release|Any CPU.Build.0 = Release|Any CPU
{00604162-C444-478B-B773-3AB23C856CA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00604162-C444-478B-B773-3AB23C856CA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00604162-C444-478B-B773-3AB23C856CA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00604162-C444-478B-B773-3AB23C856CA7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{4DAF9366-855F-46BB-AE4C-660C92FA0697} = {C84EB5A0-37AD-4B17-A51E-E36888C4441E} {4DAF9366-855F-46BB-AE4C-660C92FA0697} = {C84EB5A0-37AD-4B17-A51E-E36888C4441E}
{12AE5B4B-CB1A-498E-83B8-04E201E31D86} = {4DAF9366-855F-46BB-AE4C-660C92FA0697} {12AE5B4B-CB1A-498E-83B8-04E201E31D86} = {4DAF9366-855F-46BB-AE4C-660C92FA0697}
{3F23258D-8299-4992-9F51-2EE9B52CF9D2} = {4DAF9366-855F-46BB-AE4C-660C92FA0697} {3F23258D-8299-4992-9F51-2EE9B52CF9D2} = {4DAF9366-855F-46BB-AE4C-660C92FA0697}
{E146D707-4D44-47B3-A8F9-D51EC5E1D047} = {3F23258D-8299-4992-9F51-2EE9B52CF9D2}
{34650E82-D257-46DA-BD6B-DE307113347B} = {E146D707-4D44-47B3-A8F9-D51EC5E1D047}
{19872A4C-3C9A-4C62-A33B-74F5B8D6F77C} = {E146D707-4D44-47B3-A8F9-D51EC5E1D047}
{C2CC1596-3BEE-43EA-A9BE-4EDE5716296C} = {E146D707-4D44-47B3-A8F9-D51EC5E1D047}
{CBFBF29B-27E8-4DB1-ADD6-4B750897ACD3} = {4DAF9366-855F-46BB-AE4C-660C92FA0697} {CBFBF29B-27E8-4DB1-ADD6-4B750897ACD3} = {4DAF9366-855F-46BB-AE4C-660C92FA0697}
{D9C3EF66-2757-473D-A26B-54FD08DA203F} = {4DAF9366-855F-46BB-AE4C-660C92FA0697} {D9C3EF66-2757-473D-A26B-54FD08DA203F} = {4DAF9366-855F-46BB-AE4C-660C92FA0697}
{70C54E1B-2083-4196-AB68-34CAF0075D82} = {D9C3EF66-2757-473D-A26B-54FD08DA203F} {70C54E1B-2083-4196-AB68-34CAF0075D82} = {D9C3EF66-2757-473D-A26B-54FD08DA203F}
@ -172,5 +179,10 @@ Global
{CE895E44-EEC3-4ECE-A56A-8A82E7D863E3} = {12AE5B4B-CB1A-498E-83B8-04E201E31D86} {CE895E44-EEC3-4ECE-A56A-8A82E7D863E3} = {12AE5B4B-CB1A-498E-83B8-04E201E31D86}
{89260294-80FC-49F1-8D73-AECD39AFF2B7} = {4DAF9366-855F-46BB-AE4C-660C92FA0697} {89260294-80FC-49F1-8D73-AECD39AFF2B7} = {4DAF9366-855F-46BB-AE4C-660C92FA0697}
{C7F27698-DA05-4ACD-B0D7-4791B3972002} = {89260294-80FC-49F1-8D73-AECD39AFF2B7} {C7F27698-DA05-4ACD-B0D7-4791B3972002} = {89260294-80FC-49F1-8D73-AECD39AFF2B7}
{3C6F049E-3EE8-4D66-9AFF-E8A369032487} = {1129FE25-466B-4F4F-85FC-3752664245E1}
{00604162-C444-478B-B773-3AB23C856CA7} = {D9C3EF66-2757-473D-A26B-54FD08DA203F}
{34650E82-D257-46DA-BD6B-DE307113347B} = {3F23258D-8299-4992-9F51-2EE9B52CF9D2}
{19872A4C-3C9A-4C62-A33B-74F5B8D6F77C} = {3F23258D-8299-4992-9F51-2EE9B52CF9D2}
{C2CC1596-3BEE-43EA-A9BE-4EDE5716296C} = {3F23258D-8299-4992-9F51-2EE9B52CF9D2}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,7 +1,7 @@
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve"> <wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve">
<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/DocumentationGenerationKind/@EntryValue">Inherit</s:String> <s:String x:Key="/Default/CodeEditing/GenerateMemberBody/DocumentationGenerationKind/@EntryValue">Inherit</s:String>
<s:Boolean x:Key="/Default/CodeEditing/TypingAssist/SkipClosingBracesOnTabInStringLiterals/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeEditing/TypingAssist/SkipClosingBracesOnTabInStringLiterals/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignmentInConditionalExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignmentInConditionalExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.cdn.azure.cn" value="https://nuget.cdn.azure.cn/v3/index.json" />
</packageSources>
</configuration>

View File

@ -1,24 +0,0 @@
<Project>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="dotnet tool restore" StdOutEncoding="utf-8"/>
<Exec Condition="!Exists('$(SolutionDir)/assets/resx/Ln.resx')" WorkingDirectory="$(SolutionDir)/tools"
Command="dotnet t4 $(SolutionDir)/tools/GenerateLnResx.tt -o $(SolutionDir)/assets/resx/Ln.resx"
StdOutEncoding="utf-8"/>
<Exec Condition="!Exists('$(SolutionDir)/dist/Server/$(ProjectName)/Ln.cs')"
WorkingDirectory="$(SolutionDir)/tools"
Command="dotnet t4 $(SolutionDir)/tools/GenerateLnCs.tt -o $(SolutionDir)/dist/Server/$(ProjectName)/Ln.cs"
StdOutEncoding="utf-8"/>
</Target>
<ItemGroup>
<None Include="$(SolutionDir)/assets/resx/Ln.txt">
<Link>Languages/Ln.txt</Link>
</None>
<EmbeddedResource Include="$(SolutionDir)/assets/resx/Ln.resx">
<Link>Languages/Ln.resx</Link>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<Compile Include="$(SolutionDir)/dist/Server/$(ProjectName)/Ln.Designer.cs">
<Link>Languages/Ln.Designer.cs</Link>
</Compile>
</ItemGroup>
</Project>

159
README.md
View File

@ -1,73 +1,87 @@
# NetAdmin # NetAdmin
## Git Commits 语义 通用后台权限管理系统、快速开发框架基于C#12/.NET8、Vue3/Vite、Element Plus等现代技术构建具有十分整洁、优雅的编码规范
- `FEA` 新增特性
- `REF` 项目重构
- `FIX` 缺陷修复
- `PER` 性能优化
- `RVT` 还原变更
- `FMT` 格式整理
- `DOC` 文档变更
- `TST` 单元测试
- `BLD` 工程构建
## 构建指南 [![.NET](https://github.com/nsnail/NetAdmin/actions/workflows/ci.yml/badge.svg)](https://github.com/nsnail/NetAdmin/actions/workflows/ci.yml)
1. 后端 [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nsnail/NetAdmin/blob/main/LICENSE)
1. 检查dotnet-sdk版本>=7.0.0 [![Furion](https://img.shields.io/badge/Furion-4.x-blueviolet.svg)](https://github.com/nsnail/NetAdmin/blob/main/LICENSE)
``` [![FreeSql](https://img.shields.io/badge/FreeSql-3.x-orange.svg)](https://github.com/nsnail/NetAdmin/blob/main/LICENSE)
## 在线预览
http://na.yaopy.com
## 一键运行
```shell
docker run -p 8080:8080 nsnail/netadmin
```
## 构建步骤
- 后端
1. 检查dotnet-sdk版本>=8.0.0
``` shell
dotnet --list-sdks dotnet --list-sdks
# 下载 dotnet https://dotnet.microsoft.com/zh-cn/download/dotnet
``` ```
2. 克隆代码仓库 2. 克隆代码仓库
``` ``` shell
git clone https://github.com/nsnail/NetAdmin.git git clone https://github.com/nsnail/NetAdmin.git
cd ./NetAdmin cd ./NetAdmin
```
3. 确保本机redis处于运行状态
```
redis-cli
```
4. 运行后端WebApi
```
dotnet run --project ./src/backend/NetAdmin.BizServer.Host/NetAdmin.BizServer.Host.csproj --urls http://[::]:65010
```
5. 体验WebApi程序
```
浏览器打开 http://localhost:65010 将看到SwaggerKnife4jUI界面
```
2. 前端
1. 检查nodejs版本>=20
```
node -v
```
2. 安装npm依赖包
```
cd ./src/frontend/admin
npm install
```
3. 运行前端项目
```
npm run dev
```
4. 体验前端程序
```
浏览器打开 http://localhost:65020 将看到管理界面默认用户名root密码1234qwer
```
## 项目文件目录树描述 # 下载 git https://git-scm.com/downloads
```
3. 确保本机redis处于运行状态
``` shell
redis-cli
# 下载 redis for windows https://github.com/redis-windows/redis-windows/releases
# 下载 redis for linux/mac https://redis.io/download
```
4. 运行后端WebApi
``` shell
dotnet run --project ./src/backend/NetAdmin.BizServer.Host/NetAdmin.BizServer.Host.csproj --urls http://[::]:5010
```
5. 体验WebApi程序
- 浏览器打开 http://localhost:5010 将看到SwaggerKnife4jUI界面
---
- 前端
1. 检查nodejs版本>=20
``` shell
node -v
# 下载 nodejs https://nodejs.org/en/download
```
2. 安装npm依赖包
``` shell
cd ./src/frontend/admin
npm install
```
3. 运行前端项目
``` shell
npm run dev
```
4. 体验前端程序
- 浏览器打开 http://localhost:5020 将看到管理界面默认用户名root密码1234qwer
## 文件目录树
``` ```
+---.template.config dotnet 项目模板配置目录 +---.template.config # dotnet 项目模板配置目录
+---assets 程序运行需要的资源文件目录 +---assets # 程序运行需要的资源文件目录
+---dist 项目编译与分发的二进制文件目录 +---dist # 程序编译与分发的二进制文件目录
+---refs 引用的第三方项目源文件目录 +---docs # 项目文档目录
+---src 项目源文件目录 +---refs # 引用的第三方项目仓库目录
| +---backend 后端程序源文件目录 +---src # 项目源文件目录
| \---frontend 前端程序源文件目录
\---tools 构建相关的工具目录
``` ```
## 后端项目架构 ## 后端项目架构
```mermaid ```mermaid
flowchart TD flowchart TD
H["NetAdmin.Host\n公共主机层\n.Net自托管主机程序\n输入输出格式化\n数据校验、鉴权\n...所有HTTP管道过滤器中间件"] --> C["NetAdmin.Cache\n公共缓存层\n基于Redis或MemoryCache的缓存策略实现"] H["NetAdmin.Host\n公共主机层\n.Net自托管主机程序\n输入输出格式化\n数据校验、鉴权\n...所有HTTP管道过滤器中间件"] --> C["NetAdmin.Cache\n公共缓存层\n基于Redis或MemoryCache的缓存策略实现"]
@ -84,3 +98,34 @@ XA["NetAdmin.XXX.Application\n业务逻辑层实例"]-->A
XH-->XC XH-->XC
XC-->XA XC-->XA
``` ```
## 引用的开源代码 / 特别鸣谢
| 语言 | 集成领域 | 开源库 |
|------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C# | Web基础框架 | [ASP.NET Core](https://github.com/dotnet/aspnetcore) |
| C# | 快速开发脚手架 | [Furion](https://gitee.com/dotnetchina/Furion) |
| C# | 数据库关系映射 | [FreeSql](https://github.com/dotnetcore/FreeSql) |
| C# | 代码质量检查 | [Roslynator.Analyzers](https://github.com/josefpihrt/roslynator) \| [SonarAnalyzer.CSharp](https://github.com/SonarSource/sonar-dotnet) \| [StyleCop.Analyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) |
| C# | 单元测试框架 | [xunit](https://github.com/xunit/xunit) \| [coverlet.collector](https://github.com/coverlet-coverage/coverlet) |
| C# | 分布式锁 | [RedLock.net](https://github.com/samcook/RedLock.net) |
| C# | 控制台终端界面库 | [Spectre.Console](https://github.com/spectreconsole/spectre.console) |
| C# | 扩展函数库 | [NSExt](https://github.com/nsnail/ns-ext.git) |
| C# | 图形处理库 | [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) |
| C# | 文件对象存储 | [Minio](https://github.com/minio/minio-dotnet) |
| C# | 性能监控采集 | [prometheus-net.AspNetCore](https://github.com/prometheus-net/prometheus-net) |
| C# | 雪花ID生成器 | [Yitter.IdGenerator](https://github.com/yitter/idgenerator) |
| C# | 自动化版本管理 | [MinVer](https://github.com/adamralph/minver) |
| C# | JavaScript引擎 | [MsieJavaScriptEngine](https://github.com/Taritsyn/MsieJavaScriptEngine) |
| C# | WebApi图形界面 | [IGeekFan.AspNetCore.Knife4jUI](https://github.com/luoyunchong/IGeekFan.AspNetCore.Knife4jUI) |
| TypeScript | SPA基础框架 | [Vue](https://github.com/vuejs/core) |
| TypeScript | 前端构建工具 | [Vite](https://github.com/vitejs/vite) |
| TypeScript | UI控件库 | [Element Plus](https://github.com/element-plus/element-plus) |
| TypeScript | CSS解析器 | [Sass](https://github.com/sass/sass) |
| TypeScript | 图表和数据可视化 | [ECharts](https://github.com/apache/echarts) |
| JavaScript | 后台管理界面 | [SCUI](https://gitee.com/lolicode/scui) |
| JavaScript | HTTP请求库 | [Axios](https://github.com/axios/axios) |
| JavaScript | JavaScript解析器 | [Terser](https://github.com/terser/terser) |
| JavaScript | 代码质量检查 | [ESLint](https://github.com/eslint/eslint) |
| JavaScript | 代码格式化工具 | [Prettier](https://github.com/prettier/prettier) |
| JavaScript | 标准加密库 | [crypto-js](https://github.com/brix/crypto-js) |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,15 +0,0 @@
version: "3"
services:
net-admin-minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "9001:9001"
restart: always
container_name: net-admin-minio
environment:
TZ: Asia/Shanghai
volumes:
- /etc/localtime:/etc/localtime
- /etc/timezone:/etc/timezone

View File

@ -22,52 +22,63 @@
PublicKeyToken=b77a5c561934e089 PublicKeyToken=b77a5c561934e089
</value> </value>
</resheader> </resheader>
// ReSharper disable DuplicateResource
<data name="_1分钟内只能发送1次" xml:space="preserve"><value>1分钟内只能发送1次</value></data> <data name="_1分钟内只能发送1次" xml:space="preserve"><value>1分钟内只能发送1次</value></data>
<data name="_6位数字" xml:space="preserve"><value>6位数字</value></data> <data name="_6位数字" xml:space="preserve"><value>6位数字</value></data>
<data name="_8位以上数字字母组合" xml:space="preserve"><value>8位以上数字字母组合</value></data> <data name="_8位以上数字字母组合" xml:space="preserve"><value>8位以上数字字母组合</value></data>
<data name="Xml注释文件不存在" xml:space="preserve"><value>Xml注释文件不存在</value></data> <data name="XML注释文件不存在" xml:space="preserve"><value>XML注释文件不存在</value></data>
<data name="不正确" xml:space="preserve"><value>不正确</value></data> <data name="不为其中之一" xml:space="preserve"><value>不为其中之一</value></data>
<data name="不能为空" xml:space="preserve"><value>不能为空</value></data> <data name="不以什么开始" xml:space="preserve"><value>不以什么开始</value></data>
<data name="不以什么结束" xml:space="preserve"><value>不以什么结束</value></data>
<data name="不包含" xml:space="preserve"><value>不包含</value></data>
<data name="不等于" xml:space="preserve"><value>不等于</value></data>
<data name="业务模块" xml:space="preserve"><value>业务模块</value></data> <data name="业务模块" xml:space="preserve"><value>业务模块</value></data>
<data name="东乡族" xml:space="preserve"><value>东乡族</value></data> <data name="东乡族" xml:space="preserve"><value>东乡族</value></data>
<data name="丧偶" xml:space="preserve"><value>丧偶</value></data> <data name="丧偶" xml:space="preserve"><value>丧偶</value></data>
<data name="中专" xml:space="preserve"><value>中专</value></data> <data name="中专" xml:space="preserve"><value>中专</value></data>
<data name="中共党员" xml:space="preserve"><value>中共党员</value></data> <data name="中共党员" xml:space="preserve"><value>中共党员</value></data>
<data name="中文姓名" xml:space="preserve"><value>中文姓名</value></data> <data name="中文姓名" xml:space="preserve"><value>中文姓名</value></data>
<data name="为其中之一" xml:space="preserve"><value>为其中之一</value></data>
<data name="乌孜别克族" xml:space="preserve"><value>乌孜别克族</value></data> <data name="乌孜别克族" xml:space="preserve"><value>乌孜别克族</value></data>
<data name="事务已回滚" xml:space="preserve"><value>事务已回滚</value></data> <data name="事务已回滚" xml:space="preserve"><value>事务已回滚</value></data>
<data name="事务已提交" xml:space="preserve"><value>事务已提交</value></data> <data name="事务已提交" xml:space="preserve"><value>事务已提交</value></data>
<data name="京族" xml:space="preserve"><value>京族</value></data> <data name="京族" xml:space="preserve"><value>京族</value></data>
<data name="人机校验请求" xml:space="preserve"><value>人机校验请求</value></data> <data name="人机校验请求不能为空" xml:space="preserve"><value>人机校验请求不能为空</value></data>
<data name="人机验证未通过" xml:space="preserve"><value>人机验证未通过</value></data> <data name="人机验证未通过" xml:space="preserve"><value>人机验证未通过</value></data>
<data name="仡佬族" xml:space="preserve"><value>仡佬族</value></data> <data name="仡佬族" xml:space="preserve"><value>仡佬族</value></data>
<data name="以什么开始" xml:space="preserve"><value>以什么开始</value></data>
<data name="以什么结束" xml:space="preserve"><value>以什么结束</value></data>
<data name="仫佬族" xml:space="preserve"><value>仫佬族</value></data> <data name="仫佬族" xml:space="preserve"><value>仫佬族</value></data>
<data name="佤族" xml:space="preserve"><value>佤族</value></data> <data name="佤族" xml:space="preserve"><value>佤族</value></data>
<data name="侗族" xml:space="preserve"><value>侗族</value></data> <data name="侗族" xml:space="preserve"><value>侗族</value></data>
<data name="俄罗斯族" xml:space="preserve"><value>俄罗斯族</value></data> <data name="俄罗斯族" xml:space="preserve"><value>俄罗斯族</value></data>
<data name="保安族" xml:space="preserve"><value>保安族</value></data> <data name="保安族" xml:space="preserve"><value>保安族</value></data>
<data name="保密" xml:space="preserve"><value>保密</value></data> <data name="保密" xml:space="preserve"><value>保密</value></data>
<data name="信息" xml:space="preserve"><value>信息</value></data>
<data name="倒序排序" xml:space="preserve"><value>倒序排序</value></data> <data name="倒序排序" xml:space="preserve"><value>倒序排序</value></data>
<data name="傈僳族" xml:space="preserve"><value>傈僳族</value></data> <data name="傈僳族" xml:space="preserve"><value>傈僳族</value></data>
<data name="傣族" xml:space="preserve"><value>傣族</value></data> <data name="傣族" xml:space="preserve"><value>傣族</value></data>
<data name="允许的文件大小" xml:space="preserve"><value>允许的文件大小</value></data> <data name="允许的文件大小" xml:space="preserve"><value>允许的文件大小</value></data>
<data name="允许的文件格式" xml:space="preserve"><value>允许的文件格式</value></data> <data name="允许的文件格式" xml:space="preserve"><value>允许的文件格式</value></data>
<data name="全部数据" xml:space="preserve"><value>全部数据</value></data> <data name="全部数据" xml:space="preserve"><value>全部数据</value></data>
<data name="公告" xml:space="preserve"><value>公告</value></data>
<data name="共青团员" xml:space="preserve"><value>共青团员</value></data> <data name="共青团员" xml:space="preserve"><value>共青团员</value></data>
<data name="出生证" xml:space="preserve"><value>出生证</value></data> <data name="出生证" xml:space="preserve"><value>出生证</value></data>
<data name="初中" xml:space="preserve"><value>初中</value></data> <data name="初中" xml:space="preserve"><value>初中</value></data>
<data name="初始化完毕" xml:space="preserve"><value>初始化完毕</value></data> <data name="删除" xml:space="preserve"><value>删除</value></data>
<data name="包含" xml:space="preserve"><value>包含</value></data>
<data name="区号电话号码分机号" xml:space="preserve"><value>区号电话号码分机号</value></data> <data name="区号电话号码分机号" xml:space="preserve"><value>区号电话号码分机号</value></data>
<data name="博士" xml:space="preserve"><value>博士</value></data> <data name="博士" xml:space="preserve"><value>博士</value></data>
<data name="博士后" xml:space="preserve"><value>博士后</value></data> <data name="博士后" xml:space="preserve"><value>博士后</value></data>
<data name="参数格式不正确" xml:space="preserve"><value>参数格式不正确</value></data> <data name="参数格式不正确" xml:space="preserve"><value>参数格式不正确</value></data>
<data name="发送失败" xml:space="preserve"><value>发送失败</value></data> <data name="发送失败" xml:space="preserve"><value>发送失败</value></data>
<data name="同步数据库结构" xml:space="preserve"><value>同步数据库结构</value></data>
<data name="哈尼族" xml:space="preserve"><value>哈尼族</value></data> <data name="哈尼族" xml:space="preserve"><value>哈尼族</value></data>
<data name="哈萨克族" xml:space="preserve"><value>哈萨克族</value></data> <data name="哈萨克族" xml:space="preserve"><value>哈萨克族</value></data>
<data name="唯一编码" xml:space="preserve"><value>唯一编码</value></data> <data name="唯一编码不能为空" xml:space="preserve"><value>唯一编码不能为空</value></data>
<data name="回族" xml:space="preserve"><value>回族</value></data> <data name="回族" xml:space="preserve"><value>回族</value></data>
<data name="图标代码" xml:space="preserve"><value>图标代码</value></data> <data name="图标代码不能为空" xml:space="preserve"><value>图标代码不能为空</value></data>
<data name="图标名称" xml:space="preserve"><value>图标名称</value></data> <data name="图标名称不能为空" xml:space="preserve"><value>图标名称不能为空</value></data>
<data name="土家族" xml:space="preserve"><value>土家族</value></data> <data name="土家族" xml:space="preserve"><value>土家族</value></data>
<data name="土族" xml:space="preserve"><value>土族</value></data> <data name="土族" xml:space="preserve"><value>土族</value></data>
<data name="基诺族" xml:space="preserve"><value>基诺族</value></data> <data name="基诺族" xml:space="preserve"><value>基诺族</value></data>
@ -76,65 +87,84 @@
<data name="壮族" xml:space="preserve"><value>壮族</value></data> <data name="壮族" xml:space="preserve"><value>壮族</value></data>
<data name="外国人居留证" xml:space="preserve"><value>外国人居留证</value></data> <data name="外国人居留证" xml:space="preserve"><value>外国人居留证</value></data>
<data name="大专" xml:space="preserve"><value>大专</value></data> <data name="大专" xml:space="preserve"><value>大专</value></data>
<data name="大于" xml:space="preserve"><value>大于</value></data>
<data name="大于等于" xml:space="preserve"><value>大于等于</value></data>
<data name="女" xml:space="preserve"><value>女</value></data> <data name="女" xml:space="preserve"><value>女</value></data>
<data name="字典名称" xml:space="preserve"><value>字典名称</value></data> <data name="字典名称不能为空" xml:space="preserve"><value>字典名称不能为空</value></data>
<data name="字典目录不存在" xml:space="preserve"><value>字典目录不存在</value></data> <data name="字典目录不存在" xml:space="preserve"><value>字典目录不存在</value></data>
<data name="字典目录编号" xml:space="preserve"><value>字典目录编号</value></data> <data name="字典目录编号不能为空" xml:space="preserve"><value>字典目录编号不能为空</value></data>
<data name="字典编码" xml:space="preserve"><value>字典编码</value></data> <data name="字典编码不能为空" xml:space="preserve"><value>字典编码不能为空</value></data>
<data name="密码" xml:space="preserve"><value>密码</value></data> <data name="宕机" xml:space="preserve"><value>宕机</value></data>
<data name="密码不能为空" xml:space="preserve"><value>密码不能为空</value></data>
<data name="小于" xml:space="preserve"><value>小于</value></data>
<data name="小于等于" xml:space="preserve"><value>小于等于</value></data>
<data name="小学" xml:space="preserve"><value>小学</value></data> <data name="小学" xml:space="preserve"><value>小学</value></data>
<data name="已发送" xml:space="preserve"><value>已发送</value></data> <data name="已发送" xml:space="preserve"><value>已发送</value></data>
<data name="已婚" xml:space="preserve"><value>已婚</value></data> <data name="已婚" xml:space="preserve"><value>已婚</value></data>
<data name="已完成" xml:space="preserve"><value>已完成</value></data> <data name="已完成" xml:space="preserve"><value>已完成</value></data>
<data name="已校验" xml:space="preserve"><value>已校验</value></data> <data name="已校验" xml:space="preserve"><value>已校验</value></data>
<data name="已读" xml:space="preserve"><value>已读</value></data>
<data name="布依族" xml:space="preserve"><value>布依族</value></data> <data name="布依族" xml:space="preserve"><value>布依族</value></data>
<data name="布朗族" xml:space="preserve"><value>布朗族</value></data> <data name="布朗族" xml:space="preserve"><value>布朗族</value></data>
<data name="并且" xml:space="preserve"><value>并且</value></data>
<data name="开始事务" xml:space="preserve"><value>开始事务</value></data> <data name="开始事务" xml:space="preserve"><value>开始事务</value></data>
<data name="彝族" xml:space="preserve"><value>彝族</value></data> <data name="彝族" xml:space="preserve"><value>彝族</value></data>
<data name="德昂族" xml:space="preserve"><value>德昂族</value></data> <data name="德昂族" xml:space="preserve"><value>德昂族</value></data>
<data name="必须介于" xml:space="preserve"><value>必须介于</value></data>
<data name="怒族" xml:space="preserve"><value>怒族</value></data> <data name="怒族" xml:space="preserve"><value>怒族</value></data>
<data name="意外错误" xml:space="preserve"><value>意外错误</value></data> <data name="意外错误" xml:space="preserve"><value>意外错误</value></data>
<data name="成功" xml:space="preserve"><value>成功</value></data> <data name="成功" xml:space="preserve"><value>成功</value></data>
<data name="或者" xml:space="preserve"><value>或者</value></data>
<data name="手机" xml:space="preserve"><value>手机</value></data> <data name="手机" xml:space="preserve"><value>手机</value></data>
<data name="手机号码" xml:space="preserve"><value>手机号码</value></data> <data name="手机号码" xml:space="preserve"><value>手机号码</value></data>
<data name="手机号码或座机号码" xml:space="preserve"><value>手机号码或座机号码</value></data> <data name="手机号码不正确" xml:space="preserve"><value>手机号码不正确</value></data>
<data name="护照" xml:space="preserve"><value>护照</value></data> <data name="护照" xml:space="preserve"><value>护照</value></data>
<data name="拉祜族族" xml:space="preserve"><value>拉祜族族</value></data> <data name="拉祜族族" xml:space="preserve"><value>拉祜族族</value></data>
<data name="指定部门数据" xml:space="preserve"><value>指定部门数据</value></data> <data name="指定部门数据" xml:space="preserve"><value>指定部门数据</value></data>
<data name="按钮" xml:space="preserve"><value>按钮</value></data> <data name="按钮" xml:space="preserve"><value>按钮</value></data>
<data name="插入种子数据" xml:space="preserve"><value>插入种子数据</value></data>
<data name="撒拉族" xml:space="preserve"><value>撒拉族</value></data> <data name="撒拉族" xml:space="preserve"><value>撒拉族</value></data>
<data name="支付宝账号" xml:space="preserve"><value>支付宝账号</value></data> <data name="支付宝账号" xml:space="preserve"><value>支付宝账号</value></data>
<data name="数据库同步开始" xml:space="preserve"><value>数据库同步开始</value></data> <data name="数据库同步开始" xml:space="preserve"><value>数据库同步开始</value></data>
<data name="数据库服务器时钟偏移" xml:space="preserve"><value>数据库服务器时钟偏移</value></data> <data name="数据库服务器时钟偏移" xml:space="preserve"><value>数据库服务器时钟偏移</value></data>
<data name="数据库结构同步完成" xml:space="preserve"><value>数据库结构同步完成</value></data> <data name="数据库结构同步完成" xml:space="preserve"><value>数据库结构同步完成</value></data>
<data name="数据版本" xml:space="preserve"><value>数据版本</value></data> <data name="数据版本不能为空" xml:space="preserve"><value>数据版本不能为空</value></data>
<data name="文件不能为空" xml:space="preserve"><value>文件不能为空</value></data> <data name="文件不能为空" xml:space="preserve"><value>文件不能为空</value></data>
<data name="新密码" xml:space="preserve"><value>新密码</value></data> <data name="新密码不能为空" xml:space="preserve"><value>新密码不能为空</value></data>
<data name="新手机号码" xml:space="preserve"><value>新手机号码</value></data> <data name="新手机号码" xml:space="preserve"><value>新手机号码</value></data>
<data name="新手机号码验证码不正确" xml:space="preserve"><value>新手机号码验证码不正确</value></data>
<data name="无效操作" xml:space="preserve"><value>无效操作</value></data> <data name="无效操作" xml:space="preserve"><value>无效操作</value></data>
<data name="无效网络地址" xml:space="preserve"><value>无效网络地址</value></data> <data name="无效端口号" xml:space="preserve"><value>无效端口号</value></data>
<data name="无效证件号码" xml:space="preserve"><value>无效证件号码</value></data> <data name="无效证件号码" xml:space="preserve"><value>无效证件号码</value></data>
<data name="无效输入" xml:space="preserve"><value>无效输入</value></data> <data name="无效输入" xml:space="preserve"><value>无效输入</value></data>
<data name="日志长度超过限制" xml:space="preserve"><value>日志长度超过限制</value></data> <data name="日志长度超过限制" xml:space="preserve"><value>日志长度超过限制</value></data>
<data name="旧密码" xml:space="preserve"><value>旧密码</value></data> <data name="日期范围" xml:space="preserve"><value>日期范围</value></data>
<data name="旧密码不正确" xml:space="preserve"><value>旧密码不正确</value></data>
<data name="旧密码不能为空" xml:space="preserve"><value>旧密码不能为空</value></data>
<data name="旧手机号码" xml:space="preserve"><value>旧手机号码</value></data> <data name="旧手机号码" xml:space="preserve"><value>旧手机号码</value></data>
<data name="旧手机号码不正确" xml:space="preserve"><value>旧手机号码不正确</value></data>
<data name="旧手机号码验证码不正确" xml:space="preserve"><value>旧手机号码验证码不正确</value></data>
<data name="时间戳缺失或误差过大" xml:space="preserve"><value>时间戳缺失或误差过大</value></data> <data name="时间戳缺失或误差过大" xml:space="preserve"><value>时间戳缺失或误差过大</value></data>
<data name="普米族" xml:space="preserve"><value>普米族</value></data> <data name="普米族" xml:space="preserve"><value>普米族</value></data>
<data name="景颇族" xml:space="preserve"><value>景颇族</value></data> <data name="景颇族" xml:space="preserve"><value>景颇族</value></data>
<data name="朝鲜族" xml:space="preserve"><value>朝鲜族</value></data> <data name="朝鲜族" xml:space="preserve"><value>朝鲜族</value></data>
<data name="未婚" xml:space="preserve"><value>未婚</value></data> <data name="未婚" xml:space="preserve"><value>未婚</value></data>
<data name="未指定部门" xml:space="preserve"><value>未指定部门</value></data> <data name="未指定部门" xml:space="preserve"><value>未指定部门</value></data>
<data name="未读" xml:space="preserve"><value>未读</value></data>
<data name="本人数据" xml:space="preserve"><value>本人数据</value></data> <data name="本人数据" xml:space="preserve"><value>本人数据</value></data>
<data name="本科" xml:space="preserve"><value>本科</value></data> <data name="本科" xml:space="preserve"><value>本科</value></data>
<data name="本部门和下级部门数据" xml:space="preserve"><value>本部门和下级部门数据</value></data> <data name="本部门和下级部门数据" xml:space="preserve"><value>本部门和下级部门数据</value></data>
<data name="本部门数据" xml:space="preserve"><value>本部门数据</value></data> <data name="本部门数据" xml:space="preserve"><value>本部门数据</value></data>
<data name="柯尔克孜族" xml:space="preserve"><value>柯尔克孜族</value></data> <data name="柯尔克孜族" xml:space="preserve"><value>柯尔克孜族</value></data>
<data name="框架" xml:space="preserve"><value>框架</value></data> <data name="框架" xml:space="preserve"><value>框架</value></data>
<data name="模块名称不能为空" xml:space="preserve"><value>模块名称不能为空</value></data>
<data name="模块说明不能为空" xml:space="preserve"><value>模块说明不能为空</value></data>
<data name="比较数据库结构" xml:space="preserve"><value>比较数据库结构</value></data>
<data name="毛南族" xml:space="preserve"><value>毛南族</value></data> <data name="毛南族" xml:space="preserve"><value>毛南族</value></data>
<data name="水族" xml:space="preserve"><value>水族</value></data> <data name="水族" xml:space="preserve"><value>水族</value></data>
<data name="汉族" xml:space="preserve"><value>汉族</value></data> <data name="汉族" xml:space="preserve"><value>汉族</value></data>
<data name="注册" xml:space="preserve"><value>注册</value></data> <data name="注册" xml:space="preserve"><value>注册</value></data>
<data name="消息主题不能为空" xml:space="preserve"><value>消息主题不能为空</value></data>
<data name="消息内容不能为空" xml:space="preserve"><value>消息内容不能为空</value></data>
<data name="港澳台通行证" xml:space="preserve"><value>港澳台通行证</value></data> <data name="港澳台通行证" xml:space="preserve"><value>港澳台通行证</value></data>
<data name="满族" xml:space="preserve"><value>满族</value></data> <data name="满族" xml:space="preserve"><value>满族</value></data>
<data name="父节点不存在" xml:space="preserve"><value>父节点不存在</value></data> <data name="父节点不存在" xml:space="preserve"><value>父节点不存在</value></data>
@ -142,22 +172,24 @@
<data name="珞巴族" xml:space="preserve"><value>珞巴族</value></data> <data name="珞巴族" xml:space="preserve"><value>珞巴族</value></data>
<data name="瑶族" xml:space="preserve"><value>瑶族</value></data> <data name="瑶族" xml:space="preserve"><value>瑶族</value></data>
<data name="用户不存在" xml:space="preserve"><value>用户不存在</value></data> <data name="用户不存在" xml:space="preserve"><value>用户不存在</value></data>
<data name="用户名" xml:space="preserve"><value>用户名</value></data> <data name="用户名不能为空" xml:space="preserve"><value>用户名不能为空</value></data>
<data name="用户名不能是手机号" xml:space="preserve"><value>用户名不能是手机号</value></data> <data name="用户名不能是手机号" xml:space="preserve"><value>用户名不能是手机号</value></data>
<data name="用户名或密码错误" xml:space="preserve"><value>用户名或密码错误</value></data> <data name="用户名或密码错误" xml:space="preserve"><value>用户名或密码错误</value></data>
<data name="用户名长度4位以上" xml:space="preserve"><value>用户名长度4位以上</value></data> <data name="用户名长度4位以上" xml:space="preserve"><value>用户名长度4位以上</value></data>
<data name="用户头像" xml:space="preserve"><value>用户头像</value></data> <data name="用户头像不能为空" xml:space="preserve"><value>用户头像不能为空</value></data>
<data name="用户档案" xml:space="preserve"><value>用户档案</value></data> <data name="用户档案不能为空" xml:space="preserve"><value>用户档案不能为空</value></data>
<data name="电子邮箱" xml:space="preserve"><value>电子邮箱</value></data> <data name="电子邮箱" xml:space="preserve"><value>电子邮箱</value></data>
<data name="男" xml:space="preserve"><value>男</value></data> <data name="男" xml:space="preserve"><value>男</value></data>
<data name="畲族" xml:space="preserve"><value>畲族</value></data> <data name="畲族" xml:space="preserve"><value>畲族</value></data>
<data name="登录" xml:space="preserve"><value>登录</value></data> <data name="登录" xml:space="preserve"><value>登录</value></data>
<data name="白族" xml:space="preserve"><value>白族</value></data> <data name="白族" xml:space="preserve"><value>白族</value></data>
<data name="目标手机" xml:space="preserve"><value>目标手机</value></data> <data name="目标设备不能为空" xml:space="preserve"><value>目标设备不能为空</value></data>
<data name="目标设备" xml:space="preserve"><value>目标设备</value></data> <data name="短信验证请求不能为空" xml:space="preserve"><value>短信验证请求不能为空</value></data>
<data name="短信验证请求" xml:space="preserve"><value>短信验证请求</value></data>
<data name="硕士" xml:space="preserve"><value>硕士</value></data> <data name="硕士" xml:space="preserve"><value>硕士</value></data>
<data name="离异" xml:space="preserve"><value>离异</value></data> <data name="离异" xml:space="preserve"><value>离异</value></data>
<data name="私信" xml:space="preserve"><value>私信</value></data>
<data name="站内信不存在" xml:space="preserve"><value>站内信不存在</value></data>
<data name="等于" xml:space="preserve"><value>等于</value></data>
<data name="等待发送" xml:space="preserve"><value>等待发送</value></data> <data name="等待发送" xml:space="preserve"><value>等待发送</value></data>
<data name="签名缺失" xml:space="preserve"><value>签名缺失</value></data> <data name="签名缺失" xml:space="preserve"><value>签名缺失</value></data>
<data name="系统模块" xml:space="preserve"><value>系统模块</value></data> <data name="系统模块" xml:space="preserve"><value>系统模块</value></data>
@ -167,49 +199,56 @@
<data name="维吾尔族" xml:space="preserve"><value>维吾尔族</value></data> <data name="维吾尔族" xml:space="preserve"><value>维吾尔族</value></data>
<data name="羌族" xml:space="preserve"><value>羌族</value></data> <data name="羌族" xml:space="preserve"><value>羌族</value></data>
<data name="群众" xml:space="preserve"><value>群众</value></data> <data name="群众" xml:space="preserve"><value>群众</value></data>
<data name="自定义" xml:space="preserve"><value>自定义</value></data>
<data name="苗族" xml:space="preserve"><value>苗族</value></data> <data name="苗族" xml:space="preserve"><value>苗族</value></data>
<data name="范围" xml:space="preserve"><value>范围</value></data>
<data name="菜单" xml:space="preserve"><value>菜单</value></data> <data name="菜单" xml:space="preserve"><value>菜单</value></data>
<data name="菜单名称" xml:space="preserve"><value>菜单名称</value></data> <data name="菜单名称不能为空" xml:space="preserve"><value>菜单名称不能为空</value></data>
<data name="菜单标题" xml:space="preserve"><value>菜单标题</value></data> <data name="菜单标题不能为空" xml:space="preserve"><value>菜单标题不能为空</value></data>
<data name="菜单编号" xml:space="preserve"><value>菜单编号</value></data> <data name="菜单编号不能为空" xml:space="preserve"><value>菜单编号不能为空</value></data>
<data name="蒙古族" xml:space="preserve"><value>蒙古族</value></data> <data name="蒙古族" xml:space="preserve"><value>蒙古族</value></data>
<data name="藏族" xml:space="preserve"><value>藏族</value></data> <data name="藏族" xml:space="preserve"><value>藏族</value></data>
<data name="裕固族" xml:space="preserve"><value>裕固族</value></data> <data name="裕固族" xml:space="preserve"><value>裕固族</value></data>
<data name="角色不存在" xml:space="preserve"><value>角色不存在</value></data> <data name="角色不存在" xml:space="preserve"><value>角色不存在</value></data>
<data name="角色名称" xml:space="preserve"><value>角色名称</value></data> <data name="角色名称不能为空" xml:space="preserve"><value>角色名称不能为空</value></data>
<data name="角色编号" xml:space="preserve"><value>角色编号</value></data> <data name="角色编号不能为空" xml:space="preserve"><value>角色编号不能为空</value></data>
<data name="角色编号列表" xml:space="preserve"><value>角色编号列表</value></data> <data name="角色编号列表不能为空" xml:space="preserve"><value>角色编号列表不能为空</value></data>
<data name="解绑手机号" xml:space="preserve"><value>解绑手机号</value></data> <data name="解绑手机号" xml:space="preserve"><value>解绑手机号</value></data>
<data name="设备类型" xml:space="preserve"><value>设备类型</value></data> <data name="警告" xml:space="preserve"><value>警告</value></data>
<data name="设备类型不能为空" xml:space="preserve"><value>设备类型不能为空</value></data>
<data name="该角色下存在用户" xml:space="preserve"><value>该角色下存在用户</value></data> <data name="该角色下存在用户" xml:space="preserve"><value>该角色下存在用户</value></data>
<data name="该部门下存在子部门" xml:space="preserve"><value>该部门下存在子部门</value></data> <data name="该部门下存在子部门" xml:space="preserve"><value>该部门下存在子部门</value></data>
<data name="该部门下存在用户" xml:space="preserve"><value>该部门下存在用户</value></data> <data name="该部门下存在用户" xml:space="preserve"><value>该部门下存在用户</value></data>
<data name="请求" xml:space="preserve"><value>请求</value></data> <data name="请求" xml:space="preserve"><value>请求</value></data>
<data name="请求对象" xml:space="preserve"><value>请求对象</value></data> <data name="请求对象不能为空" xml:space="preserve"><value>请求对象不能为空</value></data>
<data name="请联系管理员激活账号" xml:space="preserve"><value>请联系管理员激活账号</value></data> <data name="请联系管理员激活账号" xml:space="preserve"><value>请联系管理员激活账号</value></data>
<data name="读取用户令牌出错" xml:space="preserve"><value>读取用户令牌出错</value></data> <data name="读取用户令牌出错" xml:space="preserve"><value>读取用户令牌出错</value></data>
<data name="账号" xml:space="preserve"><value>账号</value></data> <data name="调试" xml:space="preserve"><value>调试</value></data>
<data name="账号不能为空" xml:space="preserve"><value>账号不能为空</value></data>
<data name="赫哲族" xml:space="preserve"><value>赫哲族</value></data> <data name="赫哲族" xml:space="preserve"><value>赫哲族</value></data>
<data name="跟踪" xml:space="preserve"><value>跟踪</value></data>
<data name="身份证" xml:space="preserve"><value>身份证</value></data> <data name="身份证" xml:space="preserve"><value>身份证</value></data>
<data name="达斡尔族" xml:space="preserve"><value>达斡尔族</value></data> <data name="达斡尔族" xml:space="preserve"><value>达斡尔族</value></data>
<data name="邀请码不正确" xml:space="preserve"><value>邀请码不正确</value></data> <data name="邀请码不正确" xml:space="preserve"><value>邀请码不正确</value></data>
<data name="邮箱验证码不正确" xml:space="preserve"><value>邮箱验证码不正确</value></data> <data name="邮箱验证码不正确" xml:space="preserve"><value>邮箱验证码不正确</value></data>
<data name="部门不存在" xml:space="preserve"><value>部门不存在</value></data> <data name="部门不存在" xml:space="preserve"><value>部门不存在</value></data>
<data name="部门名称" xml:space="preserve"><value>部门名称</value></data> <data name="部门名称不能为空" xml:space="preserve"><value>部门名称不能为空</value></data>
<data name="鄂伦春族" xml:space="preserve"><value>鄂伦春族</value></data> <data name="鄂伦春族" xml:space="preserve"><value>鄂伦春族</value></data>
<data name="鄂温克族" xml:space="preserve"><value>鄂温克族</value></data> <data name="鄂温克族" xml:space="preserve"><value>鄂温克族</value></data>
<data name="配置文件初始化完毕" xml:space="preserve"><value>配置文件初始化完毕</value></data>
<data name="重设密码" xml:space="preserve"><value>重设密码</value></data> <data name="重设密码" xml:space="preserve"><value>重设密码</value></data>
<data name="链接" xml:space="preserve"><value>链接</value></data> <data name="链接" xml:space="preserve"><value>链接</value></data>
<data name="错误" xml:space="preserve"><value>错误</value></data>
<data name="锡伯族" xml:space="preserve"><value>锡伯族</value></data> <data name="锡伯族" xml:space="preserve"><value>锡伯族</value></data>
<data name="键值" xml:space="preserve"><value>键值</value></data> <data name="键值不能为空" xml:space="preserve"><value>键值不能为空</value></data>
<data name="键名称" xml:space="preserve"><value>键名称</value></data> <data name="键名称不能为空" xml:space="preserve"><value>键名称不能为空</value></data>
<data name="门巴族" xml:space="preserve"><value>门巴族</value></data> <data name="门巴族" xml:space="preserve"><value>门巴族</value></data>
<data name="阿昌族" xml:space="preserve"><value>阿昌族</value></data> <data name="阿昌族" xml:space="preserve"><value>阿昌族</value></data>
<data name="顺序排序" xml:space="preserve"><value>顺序排序</value></data> <data name="顺序排序" xml:space="preserve"><value>顺序排序</value></data>
<data name="验证数据" xml:space="preserve"><value>验证数据</value></data> <data name="验证数据不能为空" xml:space="preserve"><value>验证数据不能为空</value></data>
<data name="验证码" xml:space="preserve"><value>验证码</value></data>
<data name="验证码不正确" xml:space="preserve"><value>验证码不正确</value></data> <data name="验证码不正确" xml:space="preserve"><value>验证码不正确</value></data>
<data name="验证码类型" xml:space="preserve"><value>验证码类型</value></data> <data name="验证码不能为空" xml:space="preserve"><value>验证码不能为空</value></data>
<data name="验证码类型不能为空" xml:space="preserve"><value>验证码类型不能为空</value></data>
<data name="高中" xml:space="preserve"><value>高中</value></data> <data name="高中" xml:space="preserve"><value>高中</value></data>
<data name="高山族" xml:space="preserve"><value>高山族</value></data> <data name="高山族" xml:space="preserve"><value>高山族</value></data>
<data name="黎族" xml:space="preserve"><value>黎族</value></data> <data name="黎族" xml:space="preserve"><value>黎族</value></data>

View File

@ -1,49 +1,59 @@
1分钟内只能发送1次 1分钟内只能发送1次
6位数字 6位数字
8位以上数字字母组合 8位以上数字字母组合
Xml注释文件不存在 XML注释文件不存在
不正确 不为其中之一
不能为空 不以什么开始
不以什么结束
不包含
不等于
业务模块 业务模块
东乡族 东乡族
丧偶 丧偶
中专 中专
中共党员 中共党员
中文姓名 中文姓名
为其中之一
乌孜别克族 乌孜别克族
事务已回滚 事务已回滚
事务已提交 事务已提交
京族 京族
人机校验请求 人机校验请求不能为空
人机验证未通过 人机验证未通过
仡佬族 仡佬族
以什么开始
以什么结束
仫佬族 仫佬族
佤族 佤族
侗族 侗族
俄罗斯族 俄罗斯族
保安族 保安族
保密 保密
信息
倒序排序 倒序排序
傈僳族 傈僳族
傣族 傣族
允许的文件大小 允许的文件大小
允许的文件格式 允许的文件格式
全部数据 全部数据
公告
共青团员 共青团员
出生证 出生证
初中 初中
初始化完毕 删除
包含
区号电话号码分机号 区号电话号码分机号
博士 博士
博士后 博士后
参数格式不正确 参数格式不正确
发送失败 发送失败
同步数据库结构
哈尼族 哈尼族
哈萨克族 哈萨克族
唯一编码 唯一编码不能为空
回族 回族
图标代码 图标代码不能为空
图标名称 图标名称不能为空
土家族 土家族
土族 土族
基诺族 基诺族
@ -52,65 +62,84 @@ Xml注释文件不存在
壮族 壮族
外国人居留证 外国人居留证
大专 大专
大于
大于等于
字典名称 字典名称不能为空
字典目录不存在 字典目录不存在
字典目录编号 字典目录编号不能为空
字典编码 字典编码不能为空
密码 宕机
密码不能为空
小于
小于等于
小学 小学
已发送 已发送
已婚 已婚
已完成 已完成
已校验 已校验
已读
布依族 布依族
布朗族 布朗族
并且
开始事务 开始事务
彝族 彝族
德昂族 德昂族
必须介于
怒族 怒族
意外错误 意外错误
成功 成功
或者
手机 手机
手机号码 手机号码
手机号码或座机号码 手机号码不正确
护照 护照
拉祜族族 拉祜族族
指定部门数据 指定部门数据
按钮 按钮
插入种子数据
撒拉族 撒拉族
支付宝账号 支付宝账号
数据库同步开始 数据库同步开始
数据库服务器时钟偏移 数据库服务器时钟偏移
数据库结构同步完成 数据库结构同步完成
数据版本 数据版本不能为空
文件不能为空 文件不能为空
新密码 新密码不能为空
新手机号码 新手机号码
新手机号码验证码不正确
无效操作 无效操作
无效网络地址 无效端口号
无效证件号码 无效证件号码
无效输入 无效输入
日志长度超过限制 日志长度超过限制
旧密码 日期范围
旧密码不正确
旧密码不能为空
旧手机号码 旧手机号码
旧手机号码不正确
旧手机号码验证码不正确
时间戳缺失或误差过大 时间戳缺失或误差过大
普米族 普米族
景颇族 景颇族
朝鲜族 朝鲜族
未婚 未婚
未指定部门 未指定部门
未读
本人数据 本人数据
本科 本科
本部门和下级部门数据 本部门和下级部门数据
本部门数据 本部门数据
柯尔克孜族 柯尔克孜族
框架 框架
模块名称不能为空
模块说明不能为空
比较数据库结构
毛南族 毛南族
水族 水族
汉族 汉族
注册 注册
消息主题不能为空
消息内容不能为空
港澳台通行证 港澳台通行证
满族 满族
父节点不存在 父节点不存在
@ -118,22 +147,24 @@ Xml注释文件不存在
珞巴族 珞巴族
瑶族 瑶族
用户不存在 用户不存在
用户名 用户名不能为空
用户名不能是手机号 用户名不能是手机号
用户名或密码错误 用户名或密码错误
用户名长度4位以上 用户名长度4位以上
用户头像 用户头像不能为空
用户档案 用户档案不能为空
电子邮箱 电子邮箱
畲族 畲族
登录 登录
白族 白族
目标手机 目标设备不能为空
目标设备 短信验证请求不能为空
短信验证请求
硕士 硕士
离异 离异
私信
站内信不存在
等于
等待发送 等待发送
签名缺失 签名缺失
系统模块 系统模块
@ -143,49 +174,56 @@ Xml注释文件不存在
维吾尔族 维吾尔族
羌族 羌族
群众 群众
自定义
苗族 苗族
范围
菜单 菜单
菜单名称 菜单名称不能为空
菜单标题 菜单标题不能为空
菜单编号 菜单编号不能为空
蒙古族 蒙古族
藏族 藏族
裕固族 裕固族
角色不存在 角色不存在
角色名称 角色名称不能为空
角色编号 角色编号不能为空
角色编号列表 角色编号列表不能为空
解绑手机号 解绑手机号
设备类型 警告
设备类型不能为空
该角色下存在用户 该角色下存在用户
该部门下存在子部门 该部门下存在子部门
该部门下存在用户 该部门下存在用户
请求 请求
请求对象 请求对象不能为空
请联系管理员激活账号 请联系管理员激活账号
读取用户令牌出错 读取用户令牌出错
账号 调试
账号不能为空
赫哲族 赫哲族
跟踪
身份证 身份证
达斡尔族 达斡尔族
邀请码不正确 邀请码不正确
邮箱验证码不正确 邮箱验证码不正确
部门不存在 部门不存在
部门名称 部门名称不能为空
鄂伦春族 鄂伦春族
鄂温克族 鄂温克族
配置文件初始化完毕
重设密码 重设密码
链接 链接
错误
锡伯族 锡伯族
键值 键值不能为空
键名称 键名称不能为空
门巴族 门巴族
阿昌族 阿昌族
顺序排序 顺序排序
验证数据 验证数据不能为空
验证码
验证码不正确 验证码不正确
验证码类型 验证码不能为空
验证码类型不能为空
高中 高中
高山族 高山族
黎族 黎族

View File

@ -9,16 +9,6 @@
"Title": "控制面板", "Title": "控制面板",
"Type": 1 "Type": 1
}, },
{
"Component": "profile",
"Id": 374967228141573,
"Name": "profile",
"Path": "/profile",
"Sort": 100,
"Title": "账号信息",
"Hidden": true,
"Type": 1
},
{ {
"Icon": "el-icon-setting", "Icon": "el-icon-setting",
"Id": 373837917724677, "Id": 373837917724677,
@ -116,6 +106,17 @@
"Title": "系统设置", "Title": "系统设置",
"Type": 1 "Type": 1
}, },
{
"Component": "home/widgets/components/ver",
"Icon": "el-icon-pointer",
"Id": 480998862188554,
"Name": "sys-about",
"ParentId": 373837917724677,
"Path": "/sys/about",
"Sort": 91,
"Title": "版本信息",
"Type": 1,
},
{ {
"Component": "sys/api", "Component": "sys/api",
"Icon": "sc-icon-api", "Icon": "sc-icon-api",
@ -128,46 +129,15 @@
"Type": 1 "Type": 1
}, },
{ {
"Icon": "sc-icon-business", "Component": "sys/msg",
"Id": 420881941635077, "Icon": "el-icon-message",
"Name": "biz", "Id": 482779610341392,
"Path": "/biz", "Name": "sys-msg",
"Sort": 99, "ParentId": 373837917724677,
"Title": "业务管理", "Path": "/sys/msg",
"Type": 1 "Sort": 98,
}, "Title": "消息管理",
{ "Type": 1,
"Component": "biz/product-category",
"Icon": "sc-icon-product-category",
"Id": 420881941635087,
"Name": "biz-product-category",
"ParentId": 420881941635077,
"Path": "/biz/product-category",
"Sort": 100,
"Title": "商品分类",
"Type": 1
},
{
"Component": "biz/member",
"Icon": "el-icon-user",
"Id": 422658027704337,
"Name": "biz-member",
"ParentId": 420881941635077,
"Path": "/biz/member",
"Sort": 100,
"Title": "会员管理",
"Type": 1
},
{
"Component": "biz/product",
"Icon": "sc-icon-product",
"Id": 421506245349391,
"Name": "biz-product",
"ParentId": 420881941635077,
"Path": "/biz/product",
"Sort": 100,
"Title": "商品管理",
"Type": 1
}, },
{ {
"Icon": "sc-icon-code", "Icon": "sc-icon-code",
@ -189,4 +159,14 @@
"Title": "代码生成", "Title": "代码生成",
"Type": 1 "Type": 1
}, },
{
"Id": 482777529417739,
"ParentId": 373838105399301,
"Icon": "el-icon-eleme-filled",
"Name": "dev-element",
"Path": "https://element-plus.gitee.io/zh-CN/component/button.html",
"Sort": 100,
"Title": "Element",
"Type": 3,
}
] ]

View File

@ -0,0 +1,10 @@
[
{
"Content": "<p>尊敬的用户:</p>\n<p style=\"padding-left: 40px;\">欢迎您使用 NetAdmin 后台管理系统NetAdmin 是一款通用后台权限管理系统和快速开发框架,它基于 C#12/.NET8、Vue3/Vite、Element Plus 等现代技术构建,具有十分整洁、优雅的编码规范。</p>\n<p style=\"padding-left: 40px;\">NetAdmin 致力于为企业提供高效、安全、易用的解决方案,帮助您快速构建出符合业务需求的应用程序。系统提供了丰富的功能模块,包括用户管理、权限管理、日志管理、文件上传等,可以满足您日常管理的需求。</p>\n<p style=\"padding-left: 40px;\">在使用 NetAdmin 的过程中,我们真诚地希望您能够遵守以下规定:</p>\n<p style=\"padding-left: 80px;\">1. 不得利用 NetAdmin 进行非法活动或者侵犯他人权益;</p>\n<p style=\"padding-left: 80px;\">2. 不得对 NetAdmin 系统进行恶意攻击或者破坏;</p>\n<p style=\"padding-left: 80px;\">3. 不得将 NetAdmin 系统的任何部分用于商业目的或者未经授权的访问。</p>\n<p style=\"padding-left: 80px;\">4. 为了更好地为您提供服务NetAdmin 将不断进行优化和升级,同时也欢迎您提出宝贵的意见和建议。如果您在使用过程中遇到任何问题,可以通过官方网站或者技术支持团队进行咨询和解决。</p>\n<p style=\"padding-left: 40px;\">再次感谢您对 NetAdmin 的信任和支持我们相信在您的使用过程中NetAdmin 一定会成为您的得力助手,为您的事业发展提供强有力的支持!</p>\n<p style=\"text-align: right;\">NetAdmin 开发团队</p>",
"MsgType": 2,
"Summary": "尊敬的用户:\n欢迎您使用 NetAdmin 后台管理系统NetAdmin 是一款通用后台权限管理系统和快速开发框架,它基于 C#12/.NET8、Vue3/Vite、Element Plus 等现代",
"Title": "欢迎使用 NetAdmin 后台管理系统",
"CreatedUserId": 370942943322181,
"CreatedUserName": "root",
}
]

View File

@ -2,5 +2,5 @@ $refs = ('https://github.com/nsnail/ns-ext.git', 'https://github.com/nsnail/Furi
foreach ($item in $refs) foreach ($item in $refs)
{ {
git clone --depth 1 --config "http.proxy=http://127.0.0.1:1081" $item "../refs/$( [regex]::Match($item, '/([^/]+)\.git$').Groups[1] )" git clone --depth 1 --config "http.proxy=http://127.0.0.1:1081" $item "./refs/$( [regex]::Match($item, '/([^/]+)\.git$').Groups[1] )"
} }

View File

@ -18,7 +18,7 @@ using System.Net.Http.Json;
{ {
CreateNoWindow = true, CreateNoWindow = true,
FileName = "dotnet", FileName = "dotnet",
Arguments = $"jb cleanupcode --include=\"{files}\" --no-build ../NetAdmin.sln", Arguments = $"jb cleanupcode --include=\"{files}\" --no-build ./NetAdmin.sln",
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = true RedirectStandardOutput = true
} }

View File

@ -3,5 +3,5 @@ $files = $( foreach ($line in $( git diff head origin/dev --stat-width 200 ) | f
$line.split('\|')[0].trim() $line.split('\|')[0].trim()
} ) -join ';' } ) -join ';'
echo $files echo $files
dotnet jb cleanupcode --no-build --include = "$files" ../NetAdmin.sln dotnet jb cleanupcode --no-build --include = "$files" ./NetAdmin.sln
dotnet script ../PushSign.csx dotnet script ./PushSign.csx

1
code.cleanup.full.ps1 Normal file
View File

@ -0,0 +1 @@
dotnet jb cleanupcode --no-build ./NetAdmin.sln

View File

@ -1,6 +1,8 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>$(SolutionDir)/StyleCopAnalyzers.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>$(SolutionDir)/stylecop.analyzers.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors> <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<MSBuildTreatWarningsAsErrors>true</MSBuildTreatWarningsAsErrors> <MSBuildTreatWarningsAsErrors>true</MSBuildTreatWarningsAsErrors>
@ -13,11 +15,11 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.4.0"> <PackageReference Include="Roslynator.Analyzers" Version="4.6.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.7.0.75501"> <PackageReference Include="SonarAnalyzer.CSharp" Version="9.12.0.78982">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@ -3,8 +3,8 @@
<ItemGroup> <ItemGroup>
<PackageReferenceFiles <PackageReferenceFiles
Condition="%(PackageReference.CopyToOutputDirectory) != ''" Condition="%(PackageReference.CopyToOutputDirectory) != ''"
Include="$(NugetPackageRoot)\%(PackageReference.Identity)\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)" /> Include="$(NugetPackageRoot)\%(PackageReference.Identity)\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)"/>
</ItemGroup> </ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(OutDir)" /> <Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(OutDir)"/>
</Target> </Target>
</Project> </Project>

182
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,182 @@
# 约定式提交 1.0.0
## [](#概述)概述
约定式提交规范是一种基于提交信息的轻量级约定。 它提供了一组简单规则来创建清晰的提交历史; 这更有利于编写自动化工具。 通过在提交信息中描述功能、修复和破坏性变更, 使这种惯例与 [SemVer](http://semver.org/lang/zh-CN) 相互对应。
提交说明的结构如下所示:
* * *
原文:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
译文:
<类型>[可选 范围]: <描述>
[可选 正文]
[可选 脚注]
* * *
提交说明包含了下面的结构化元素,以向类库使用者表明其意图:
1. **fix:** _类型_`fix` 的提交表示在代码库中修复了一个 bug这和语义化版本中的 [`PATCH`](https://semver.org/lang/zh-CN/#%E6%91%98%E8%A6%81) 相对应)。
2. **feat:** _类型_`feat` 的提交表示在代码库中新增了一个功能(这和语义化版本中的 [`MINOR`](https://semver.org/lang/zh-CN/#%E6%91%98%E8%A6%81) 相对应)。
3. **BREAKING CHANGE:** 在脚注中包含 `BREAKING CHANGE:`<类型>(范围) 后面有一个 `!` 的提交,表示引入了破坏性 API 变更(这和语义化版本中的 [`MAJOR`](https://semver.org/lang/zh-CN/#%E6%91%98%E8%A6%81) 相对应)。 破坏性变更可以是任意 _类型_ 提交的一部分。
4. 除 `fix:``feat:` 之外,也可以使用其它提交 _类型_ ,例如 [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional)(基于 [Angular 约定](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines))中推荐的 `build:``chore:``ci:``docs:``style:``refactor:``perf:``test:`,等等。
* build: 用于修改项目构建系统,例如修改依赖库、外部接口或者升级 Node 版本等;
* chore: 用于对非业务性代码进行修改,例如修改构建流程或者工具配置等;
* ci: 用于修改持续集成流程,例如修改 Travis、Jenkins 等工作流配置;
* docs: 用于修改文档,例如修改 README 文件、API 文档等;
* style: 用于修改代码的样式,例如调整缩进、空格、空行等;
* refactor: 用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑;
* perf: 用于优化性能,例如提升代码的性能、减少内存占用等;
* test: 用于修改测试用例,例如添加、删除、修改代码的测试用例等。
5. 脚注中除了 `BREAKING CHANGE: <description>` ,其它条目应该采用类似 [git trailer format](https://git-scm.com/docs/git-interpret-trailers) 这样的惯例。
其它提交类型在约定式提交规范中并没有强制限制,并且在语义化版本中没有隐式影响(除非它们包含 BREAKING CHANGE。 可以为提交类型添加一个围在圆括号内的范围,以为其提供额外的上下文信息。例如 `feat(parser): adds ability to parse arrays.`
## [](#示例)示例
### [](#包含了描述并且脚注中有破坏性变更的提交说明)包含了描述并且脚注中有破坏性变更的提交说明
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files
### [](#包含了--字符以提醒注意破坏性变更的提交说明)包含了 `!` 字符以提醒注意破坏性变更的提交说明
feat!: send an email to the customer when a product is shipped
### [](#包含了范围和破坏性变更--的提交說明)包含了范围和破坏性变更 `!` 的提交說明
feat(api)!: send an email to the customer when a product is shipped
### [](#包含了--和-breaking-change-脚注的提交说明)包含了 `!` 和 BREAKING CHANGE 脚注的提交说明
chore!: drop support for Node 6
BREAKING CHANGE: use JavaScript features not available in Node 6.
### [](#不包含正文的提交说明)不包含正文的提交说明
docs: correct spelling of CHANGELOG
### [](#包含范围的提交说明)包含范围的提交说明
feat(lang): add polish language
### [](#包含多行正文和多行脚注的提交说明)包含多行正文和多行脚注的提交说明
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
Reviewed-by: Z
Refs: #123
## [](#约定式提交规范)约定式提交规范
本文中的关键词 “必须MUST”、“禁止MUST NOT”、“必要REQUIRED”、“应当SHALL”、“不应当SHALL NOT”、“应该SHOULD”、“不应该SHOULD NOT”、“推荐RECOMMENDED”、“可以MAY” 和 “可选OPTIONAL” ,其相关解释参考 [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) 。
1. 每个提交都**必须**使用类型字段前缀,它由一个名词构成,诸如 `feat``fix` 其后接**可选的**范围字段,**可选的** `!`,以及**必要的**冒号(英文半角)和空格。
2. 当一个提交为应用或类库实现了新功能时,**必须**使用 `feat` 类型。
3. 当一个提交为应用修复了 bug 时,**必须**使用 `fix` 类型。
4. 范围字段**可以**跟随在类型字段后面。范围**必须**是一个描述某部分代码的名词,并用圆括号包围,例如: `fix(parser):`
5. 描述字段**必须**直接跟在 <类型>(范围) 前缀的冒号和空格之后。 描述指的是对代码变更的简短总结,例如: _fix: array parsing issue when multiple spaces were contained in string_
6. 在简短描述之后,**可以**编写较长的提交正文,为代码变更提供额外的上下文信息。正文**必须**起始于描述字段结束的一个空行后。
7. 提交的正文内容自由编写,并**可以**使用空行分隔不同段落。
8. 在正文结束的一个空行之后,**可以**编写一行或多行脚注。每行脚注都**必须**包含 一个令牌token后面紧跟 `:<space>``<space>#` 作为分隔符,后面再紧跟令牌的值(受 [git trailer convention](https://git-scm.com/docs/git-interpret-trailers) 启发)。
9. 脚注的令牌**必须**使用 `-` 作为连字符,比如 `Acked-by` (这样有助于 区分脚注和多行正文)。有一种例外情况就是 `BREAKING CHANGE`,它**可以**被认为是一个令牌。
10. 脚注的值**可以**包含空格和换行,值的解析过程**必须**直到下一个脚注的令牌/分隔符出现为止。
11. 破坏性变更**必须**在提交信息中标记出来,要么在 <类型>(范围) 前缀中标记,要么作为脚注的一项。
12. 包含在脚注中时,破坏性变更**必须**包含大写的文本 `BREAKING CHANGE`,后面紧跟着冒号、空格,然后是描述,例如: _BREAKING CHANGE: environment variables now take precedence over config files_
13. 包含在 <类型>(范围) 前缀时,破坏性变更**必须**通过把 `!` 直接放在 `:` 前面标记出来。 如果使用了 `!`,那么脚注中**可以**不写 `BREAKING CHANGE:` 同时提交信息的描述中**应该**用来描述破坏性变更。
14. 在提交说明中,**可以**使用 `feat``fix` 之外的类型比如_docs: updated ref docs._ 。
15. 工具的实现必须**不区分**大小写地解析构成约定式提交的信息单元,只有 `BREAKING CHANGE` **必须**是大写的。
16. BREAKING-CHANGE 作为脚注的令牌时**必须**是 BREAKING CHANGE 的同义词。
## [](#为什么使用约定式提交)为什么使用约定式提交
* 自动化生成 CHANGELOG。
* 基于提交的类型,自动决定语义化的版本变更。
* 向同事、公众与其他利益关系者传达变化的性质。
* 触发构建和部署流程。
* 让人们探索一个更加结构化的提交历史,以便降低对你的项目做出贡献的难度。
## [](#faq)FAQ
### [](#在初始开发阶段我该如何处理提交说明)在初始开发阶段我该如何处理提交说明?
我们建议你按照假设你已发布了产品那样来处理。因为通常总 _有人_ 使用你的软件,即便那是你软件开发的同事们。他们会希望知道诸如修复了什么、哪里不兼容等信息。
### [](#提交标题中的类型是大写还是小写)提交标题中的类型是大写还是小写?
大小写都可以,但最好是一致的。
### [](#如果提交符合多种类型我该如何操作)如果提交符合多种类型我该如何操作?
回退并尽可能创建多次提交。约定式提交的好处之一是能够促使我们做出更有组织的提交和 PR。
### [](#这不会阻碍快速开发和迭代吗)这不会阻碍快速开发和迭代吗?
它阻碍的是以杂乱无章的方式快速前进。它助你能在横跨多个项目以及和多个贡献者协作时长期地快速演进。
### [](#约定式提交会让开发者受限于提交的类型吗因为他们会想着已提供的类型)约定式提交会让开发者受限于提交的类型吗(因为他们会想着已提供的类型)?
约定式提交鼓励我们更多地使用某些类型的提交,比如 `fixes`。除此之外,约定式提交的灵活性也允许你的团队使用自己的类型,并随着时间的推移更改这些类型。
### [](#这和-semver-有什么关联呢)这和 SemVer 有什么关联呢?
`fix` 类型提交应当对应到 `PATCH` 版本。`feat` 类型提交应该对应到 `MINOR` 版本。带有 `BREAKING CHANGE` 的提交不管类型如何,都应该对应到 `MAJOR` 版本。
### [](#我对约定式提交做了形如-jameswomackconventional-commit-spec-的扩展该如何版本化管理这些扩展呢)我对约定式提交做了形如 `@jameswomack/conventional-commit-spec` 的扩展,该如何版本化管理这些扩展呢?
我们推荐使用 SemVer 来发布你对于这个规范的扩展(并鼓励你创建这些扩展!)
### [](#如果我不小心使用了错误的提交类型该怎么办呢)如果我不小心使用了错误的提交类型,该怎么办呢?
#### [](#当你使用了在规范中但错误的类型时例如将-feat-写成了-fix)当你使用了在规范中但错误的类型时,例如将 `feat` 写成了 `fix`
在合并或发布这个错误之前,我们建议使用 `git rebase -i` 来编辑提交历史。而在发布之后,根据你使用的工具和流程不同,会有不同的清理方案。
#### [](#当使用了-不在-规范中的类型时例如将-feat-写成了-feet)当使用了 _不在_ 规范中的类型时,例如将 `feat` 写成了 `feet`
在最坏的场景下,即便提交没有满足约定式提交的规范,也不会是世界末日。这只意味着这个提交会被基于规范的工具错过而已。
### [](#所有的贡献者都需要使用约定式提交规范吗)所有的贡献者都需要使用约定式提交规范吗?
并不!如果你使用基于 squash 的 Git 工作流,主管维护者可以在合并时清理提交信息——这不会对普通提交者产生额外的负担。 有种常见的工作流是让 git 系统自动从 pull request 中 squash 出提交,并向主管维护者提供一份表单,用以在合并时输入合适的 git 提交信息。
### [](#约定式提交规范中如何处理还原revert提交)约定式提交规范中如何处理还原revert提交?
还原提交Reverting会比较复杂你还原的是多个提交吗如果你还原了一个功能模块下次发布的应该是补丁吗
约定式提交不能明确的定义还原行为。所以我们把这个问题留给工具开发者, 基于 _类型__脚注_ 的灵活性来开发他们自己的还原处理逻辑。
一种建议是使用 `revert` 类型,和一个指向被还原提交摘要的脚注:
revert: let us never again speak of the noodle incident
Refs: 676104e, a215868

226
docs/SEMVER.md Normal file
View File

@ -0,0 +1,226 @@
语义化版本 2.0.0
===
摘要
---
版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
1. 主版本号:当你做了不兼容的 API 修改,
2. 次版本号:当你做了向下兼容的功能性新增,
3. 修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
简介
---
在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。
在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。
作为这个问题的解决方案之一,我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。为了让这套理论运作,你必须先有定义好的公共 API。这可能包括文档或代码的强制要求。无论如何这套 API 的清楚明了是十分重要的。一旦你定义了公共 API你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式X.Y.Z主版本号.次版本号.修订号)修复问题但不影响 API 时递增修订号API 保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。
我称这套系统为“语义化的版本控制”,在这套约定下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。
语义化版本控制规范SemVer
---
以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。
1. 使用语义化版本控制的软件必须MUST定义公共 API。该 API 可以在代码中被定义或出现于严谨的文档内。无论何种形式都应该力求精确且完整。
2. 标准的版本号必须MUST采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数且禁止MUST NOT在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须MUST以数值来递增。例如1.9.1 -> 1.10.0 -> 1.11.0。
3. 标记版本号的软件发行后禁止MUST NOT改变该版本软件的内容。任何修改都必须MUST以新版本发行。
4. 主版本号为零0.y.z的软件处于开发初始阶段一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。
5. 1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。
6. 修订号 Zx.y.Z `|` x > 0必须MUST在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。
7. 次版本号 Yx.Y.z `|` x > 0必须MUST在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须MUST递增。也可以MAY在内部程序有大量新功能或改进被加入时递增其中可以MAY包括修订级别的改变。每当次版本号递增时修订号必须MUST归零。
8. 主版本号 XX.y.z `|` X > 0必须MUST在有任何不兼容的修改被加入公共 API 时递增。其中可以MAY包括次版本号及修订级别的改变。每当主版本号递增时次版本号和修订号必须MUST归零。
9. 先行版本号可以MAY被标注在修订版之后先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须MUST由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成且禁止MUST NOT留白。数字型的标识符禁止MUST NOT在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
10. 版本编译信息可以MAY被标注在修订版或先行版本号之后先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须MUST由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成且禁止MUST NOT留白。当判断版本的优先层级时版本编译信息可SHOULD被忽略。因此当两个版本只有在版本编译信息有差别时属于相同的优先层级。范例1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
11. 版本的优先层级指的是不同版本在排序时如何比较。
1. 判断优先层级时必须MUST把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较版本编译信息不在这份比较的列表中
2. 由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较。
例如1.0.0 < 2.0.0 < 2.1.0 < 2.1.1
3. 当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。
例如1.0.0-alpha < 1.0.0
4. 有相同主版本号、次版本号及修订号的两个先行版本号其优先层级必须MUST透过由左到右的每个被句点分隔的标识符来比较直到找到一个差异值后决定
1. 只有数字的标识符以数值高低比较。
2. 有字母或连接号时则逐字以 ASCII 的排序来比较。
3. 数字的标识符比非数字的标识符优先层级低。
4. 若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。
例如1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
合法语义化版本的巴科斯范式语法
--------------------------------------------------
```
<valid semver> ::= <version core>
| <version core> "-" <pre-release>
| <version core> "+" <build>
| <version core> "-" <pre-release> "+" <build>
<version core> ::= <major> "." <minor> "." <patch>
<major> ::= <numeric identifier>
<minor> ::= <numeric identifier>
<patch> ::= <numeric identifier>
<pre-release> ::= <dot-separated pre-release identifiers>
<dot-separated pre-release identifiers> ::= <pre-release identifier>
| <pre-release identifier> "." <dot-separated pre-release identifiers>
<build> ::= <dot-separated build identifiers>
<dot-separated build identifiers> ::= <build identifier>
| <build identifier> "." <dot-separated build identifiers>
<pre-release identifier> ::= <alphanumeric identifier>
| <numeric identifier>
<build identifier> ::= <alphanumeric identifier>
| <digits>
<alphanumeric identifier> ::= <non-digit>
| <non-digit> <identifier characters>
| <identifier characters> <non-digit>
| <identifier characters> <non-digit> <identifier characters>
<numeric identifier> ::= "0"
| <positive digit>
| <positive digit> <digits>
<identifier characters> ::= <identifier character>
| <identifier character> <identifier characters>
<identifier character> ::= <digit>
| <non-digit>
<non-digit> ::= <letter>
| "-"
<digits> ::= <digit>
| <digit> <digits>
<digit> ::= "0"
| <positive digit>
<positive digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<letter> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
| "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
| "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d"
| "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n"
| "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x"
| "y" | "z"
```
为什么要使用语义化的版本控制?
---
这并不是一个新的或者革命性的想法。实际上,你可能已经在做一些近似的事情了。问题在于只是“近似”还不够。如果没有某个正式的规范可循,版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义,让你对软件使用者传达意向变得容易。一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。
举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去。假设有个名为“救火车”的函数库,它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当救火车创建时,梯子的版本号为 3.1.0。因为救火车使用了一些版本 3.1.0 所新增的功能,你可以放心地指定依赖于梯子的版本号大于等于 3.1.0 但小于 4.0.0。这样,当梯子版本 3.1.1 和 3.2.0 发布时,你可以将直接它们纳入你的包管理系统,因为它们能与原有依赖的软件兼容。
作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。
如果你对此认同,希望立即开始使用语义化版本控制,你只需声明你的函数库正在使用它并遵循这些规则就可以了。请在你的 README 文件中保留此页链接,让别人也知道这些规则并从中受益。
FAQ
---
### 在 0.y.z 初始开发阶段,我该如何进行版本控制?
最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。
### 如何判断发布 1.0.0 版本的时机?
当你的软件被用于正式环境,它应该已经达到了 1.0.0 版。如果你已经有个稳定的 API 被使用者依赖,也会是 1.0.0 版。如果你很担心向下兼容的问题,也应该算是 1.0.0 版了。
### 这不会阻碍快速开发和迭代吗?
主版本号为零的时候就是为了做快速开发。如果你每天都在改变 API那么你应该仍在主版本号为零的阶段0.y.z或是正在下个主版本的独立开发分支中。
### 对于公共 API若即使是最小但不向下兼容的改变都需要产生新的主版本号岂不是很快就达到 42.0.0 版?
这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版,意味着你必须为这些改变所带来的影响深思熟虑,并且评估所涉及的成本及效益比。
### 为整个公共 API 写文档太费事了!
为供他人使用的软件编写适当的文档,是你作为一名专业开发者应尽的职责。保持项目高效的一个非常重要的部分是掌控软件的复杂度,如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的,要掌控复杂度会是困难的。长远来看,使用语义化版本控制以及对于公共 API 有良好规范的坚持,可以让每个人及每件事都运行顺畅。
### 万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?
一旦发现自己破坏了语义化版本控制的规范,就要修正这个问题,并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况,也不能去修改已发行的版本。可以的话,将有问题的版本号记录到文档中,告诉使用者问题所在,让他们能够意识到这是有问题的版本。
### 如果我更新了自己的依赖但没有改变公共 API 该怎么办?
由于没有影响到公共 API这可以被认定是兼容的。若某个软件和你的包有共同依赖则它会有自己的依赖规范作者也会告知可能的冲突。要判断改版是属于修订等级或是次版等级是依据你更新的依赖关系是为了修复问题或是加入新功能。对于后者我经常会预期伴随着更多的代码这显然会是一个次版本号级别的递增。
### 如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)
自行做最佳的判断。如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响,那么最好做一次主版本的发布,即使严格来说这个修复仅是修订等级的发布。记住, 语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的,那就透过版本号来向他们说明。
### 我该如何处理即将弃用的功能?
弃用现存的功能是软件开发中的家常便饭,也通常是向前发展所必须的。当你弃用部分公共 API 时你应该做两件事1更新你的文档让使用者知道这个改变2在适当的时机将弃用的功能透过新的次版本号发布。在新的主版本完全移除弃用功能前至少要有一个次版本包含这个弃用信息这样使用者才能平顺地转移到新版 API。
### 语义化版本对于版本的字符串长度是否有限制呢?
没有,请自行做适当的判断。举例来说,长到 255 个字符的版本已过度夸张。再者,特定的系统对于字符串长度可能会有他们自己的限制。
### “v1.2.3” 是一个语义化版本号吗?
“v1.2.3” 并不是一个语义化的版本号。但是,在语义化版本号之前增加前缀 “v” 是用来表示版本号的常用做法。在版本控制系统中,将 “version” 缩写为 “v” 是很常见的。比如:`git tag v1.2.3 -m "Release version 1.2.3"`“v1.2.3” 表示标签名称,而 “1.2.3” 是语义化版本号。
### 是否有推荐的正则表达式用以检查语义化版本号的正确性?
有两个推荐的正则表达式。第一个用于支持按组名称提取的语言PCRE[Perl 兼容正则表达式,比如 Perl、PHP 和 R]、Python 和 Go
参见:<https://regex101.com/r/Ly7O1x/3/>
```
^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
```
第二个用于支持按编号提取的语言与第一个对应的提取项按顺序分别为major、minor、patch、prerelease、buildmetadata。主要包括 ECMA ScriptJavaScript、PCREPerl 兼容正则表达式,比如 Perl、PHP 和 R、Python 和 Go。
参见:<https://regex101.com/r/vkijKf/1/>
```
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
```
关于
---
语义化版本控制的规范是由 Gravatars 创办者兼 GitHub 共同创办者 [Tom Preston-Werner](http://tom.preston-werner.com) 所建立。
如果您有任何建议,请到 [GitHub 上提出您的问题](https://github.com/semver/semver/issues)。
许可证
---
[知识共享 署名 3.0 (CC BY 3.0)](http://creativecommons.org/licenses/by/3.0/)

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

4
dot.clean.cmd Normal file
View File

@ -0,0 +1,4 @@
call npm --prefix ./src/frontend/admin run prettier
dot rbom -w -e refs -e .git -e node_modules
dot trim -w -e refs -e .git -e node_modules
dot tolf -w -e refs -e .git -e node_modules

View File

@ -9,7 +9,7 @@
] ]
}, },
"cake.tool": { "cake.tool": {
"version": "3.1.0", "version": "3.2.0",
"commands": [ "commands": [
"dotnet-cake" "dotnet-cake"
] ]
@ -21,7 +21,7 @@
] ]
}, },
"jetbrains.resharper.globaltools": { "jetbrains.resharper.globaltools": {
"version": "2023.2.0", "version": "2023.2.3",
"commands": [ "commands": [
"jb" "jb"
] ]

View File

@ -63,7 +63,7 @@ public sealed class Ln
public static CultureInfo Culture { get; set; } public static CultureInfo Culture { get; set; }
<# <#
var xml = new XmlDocument(); var xml = new XmlDocument();
xml.Load("../assets/resx/Ln.resx"); xml.Load("./assets/res/Ln.resx");
foreach (XmlNode data in xml.SelectNodes("//root/data")!) foreach (XmlNode data in xml.SelectNodes("//root/data")!)
{ {
#> #>

2
gen.ln.cmd Normal file
View File

@ -0,0 +1,2 @@
dotnet t4 ./gen.resx.tt -o ./assets/res/Ln.resx
dotnet t4 ./gen.cs.tt -o ./dist/backend/NetAdmin.Infrastructure/Ln.cs

View File

@ -1,5 +1,7 @@
<#@ template language="C#" #> <#@ template language="C#" #>
<#@ output encoding="utf-8" extension="resx" #> <#@ output encoding="utf-8" extension="resx" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
@ -24,9 +26,10 @@
PublicKeyToken=b77a5c561934e089 PublicKeyToken=b77a5c561934e089
</value> </value>
</resheader> </resheader>
// ReSharper disable DuplicateResource
<# <#
var regex = new System.Text.RegularExpressions.Regex(@"^\d", System.Text.RegularExpressions.RegexOptions.Compiled); var regex = new Regex(@"^\d", RegexOptions.Compiled);
foreach (var line in System.IO.File.ReadLines("../assets/resx/ln.txt")) foreach (var line in File.ReadLines("./assets/res/ln.txt"))
{ {
#> #>
<data name="<#= regex.IsMatch(line) ? "_" : "" #><#= line #>" xml:space="preserve"><value><#= line #></value></data> <data name="<#= regex.IsMatch(line) ? "_" : "" #><#= line #>" xml:space="preserve"><value><#= line #></value></data>

View File

@ -1,5 +1,5 @@
$branch = $(git branch --show-current) $branch = $( git branch --show-current )
git checkout dev git checkout main
git pull git pull
git branch -D $branch git branch -D $branch
git branch $branch git branch $branch

View File

@ -1,10 +1,10 @@
{ {
"sdk": { "sdk": {
"version": "7.0.0", "version": "8.0.0",
"rollForward": "latestMajor", "rollForward": "latestMajor",
"allowPrerelease": true "allowPrerelease": true
}, },
"tools": { "tools": {
"dotnet": "7.0.0" "dotnet": "8.0.0"
} }
} }

View File

@ -7,7 +7,7 @@
var files = Directory var files = Directory
.EnumerateFiles( .EnumerateFiles(
".", "./",
"*.png", "*.png",
new EnumerationOptions new EnumerationOptions
{ {
@ -37,7 +37,7 @@ files = new[] { "*.jpg", "*.jpeg" }
.SelectMany( .SelectMany(
x => x =>
Directory.EnumerateFiles( Directory.EnumerateFiles(
".", "./",
x, x,
new EnumerationOptions new EnumerationOptions
{ {

2
install.as.tpl.ps1 Normal file
View File

@ -0,0 +1,2 @@
dotnet new uninstall .
dotnet new --install .

11
minver.targets Normal file
View File

@ -0,0 +1,11 @@
<Project>
<Target Name="MyTarget" AfterTargets="MinVer">
<PropertyGroup>
<AssemblyVersion>$(MinVerMajor).$(MinVerMinor).$(MinVerPatch)</AssemblyVersion>
<FileVersion>$(MinVerMajor).$(MinVerMinor).$(MinVerPatch)</FileVersion>
<PackageVersion>$(MinVerVersion)</PackageVersion>
<ProductVersion>$(MinVerVersion)</ProductVersion>
<Version>$(MinVerVersion)</Version>
</PropertyGroup>
</Target>
</Project>

View File

@ -1,12 +1,14 @@
{ {
"version": "1.0.0",
"devDependencies": { "devDependencies": {
"cz-conventional-changelog": "^3.3.0", "cz-git": "^1.7.1",
"prettier": "3.0.2", "commitizen": "^4.3.0",
"prettier-plugin-csharp": "0.6.0-development" "prettier": "^3.1.0",
"standard-version": "9.5.0"
}, },
"config": { "config": {
"commitizen": { "commitizen": {
"path": "./node_modules/cz-conventional-changelog" "path": "node_modules/cz-git"
} }
} }
} }

27
rename.csx Normal file
View File

@ -0,0 +1,27 @@
#r "nuget: NSExt, 1.1.0"
using NSExt.Extensions;
Console.WriteLine("请输入原始名称NetAdmin");
var oldName = Console.ReadLine().NullOrEmpty("NetAdmin");
Console.WriteLine("请输入替换名称:");
var newName = Console.ReadLine();
foreach (var path in Directory.EnumerateDirectories("./", $"*{oldName}*",
SearchOption.AllDirectories))
{
Console.Write($"{path} --> ");
var newPath = path.Replace(oldName, newName);
Directory.Move(path, newPath);
Console.WriteLine(newPath);
}
Console.WriteLine();
foreach (var path in Directory.EnumerateFiles("./", $"*.*", SearchOption.AllDirectories))
{
File.WriteAllText(path, File.ReadAllText(path).Replace(oldName, newName));
var newPath = path.Replace(oldName, newName);
if (newPath == path) continue;
Console.Write($"{path} --> ");
Directory.Move(path, newPath);
Console.WriteLine(newPath);
}

View File

@ -59,11 +59,12 @@ global using NetAdmin.Infrastructure.Configuration.Options.SubNodes.Upload;
global using NetAdmin.Infrastructure.Constant; global using NetAdmin.Infrastructure.Constant;
global using NetAdmin.Infrastructure.Enums; global using NetAdmin.Infrastructure.Enums;
global using NetAdmin.Infrastructure.Exceptions; global using NetAdmin.Infrastructure.Exceptions;
global using NetAdmin.Infrastructure.Exceptions.InvalidInput;
global using NetAdmin.Infrastructure.Exceptions.InvalidOperation;
global using NetAdmin.Infrastructure.Exceptions.Unexpected;
global using NetAdmin.Infrastructure.Extensions; global using NetAdmin.Infrastructure.Extensions;
global using NetAdmin.Infrastructure.Languages; global using NetAdmin.Infrastructure.Languages;
global using NetAdmin.Infrastructure.Utils; global using NetAdmin.Infrastructure.Utils;
global using NSExt.Attributes; global using NSExt.Attributes;
global using NSExt.Extensions; global using NSExt.Extensions;
#if !INFRAS
global using DynamicFilterInfo = NetAdmin.Domain.Dto.Dependency.DynamicFilterInfo;
global using DynamicFilterOperators = NetAdmin.Domain.Enums.DynamicFilterOperators;
#endif

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)/CodeQuality.props"/> <Import Project="$(SolutionDir)/code.quality.props"/>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../NetAdmin.Domain/NetAdmin.Domain.csproj"/> <ProjectReference Include="../NetAdmin.Domain/NetAdmin.Domain.csproj"/>
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,19 @@
using NetAdmin.Domain.Contexts;
using NetAdmin.Domain.DbMaps.Dependency;
namespace NetAdmin.Application.Repositories;
/// <summary>
/// 默认仓储
/// </summary>
public sealed class DefaultRepository<TEntity>(IFreeSql fSql //
, UnitOfWorkManager uowManger //
, ContextUserToken userToken) //
: DefaultRepository<TEntity, long>(fSql, uowManger)
where TEntity : EntityBase
{
/// <summary>
/// 当前上下文关联的用户
/// </summary>
public ContextUserToken UserToken => userToken;
}

View File

@ -1,39 +0,0 @@
using NetAdmin.Domain.Contexts;
using NetAdmin.Domain.DbMaps.Dependency;
namespace NetAdmin.Application.Repositories;
/// <summary>
/// 基础仓储接口
/// </summary>
public interface IRepository<TEntity> : IBaseRepository<TEntity>
where TEntity : EntityBase
{
/// <summary>
/// 当前上下文关联的用户
/// </summary>
ContextUserToken UserToken { get; }
/// <summary>
/// 递归删除
/// </summary>
/// <param name="exp">exp</param>
/// <param name="disableGlobalFilterNames">禁用全局过滤器名</param>
Task<bool> DeleteRecursiveAsync(Expression<Func<TEntity, bool>> exp, params string[] disableGlobalFilterNames);
/// <summary>
/// 获得Dto
/// </summary>
/// <param name="id">主键</param>
Task<TDto> GetAsync<TDto>(long id);
/// <summary>
/// 根据条件获取Dto
/// </summary>
Task<TDto> GetAsync<TDto>(Expression<Func<TEntity, bool>> exp);
/// <summary>
/// 根据条件获取实体
/// </summary>
Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> exp);
}

View File

@ -1,82 +0,0 @@
using NetAdmin.Domain.Contexts;
using NetAdmin.Domain.DbMaps.Dependency;
namespace NetAdmin.Application.Repositories;
/// <inheritdoc cref="IRepository{TEntity}" />
public sealed class Repository<TEntity> : DefaultRepository<TEntity, long>, IRepository<TEntity>
where TEntity : EntityBase
{
/// <summary>
/// Initializes a new instance of the <see cref="Repository{TEntity}" /> class.
/// </summary>
public Repository(IFreeSql fSql, UnitOfWorkManager uowManger, ContextUserToken userToken) //
: base(fSql, uowManger)
{
UserToken = userToken;
}
/// <summary>
/// 当前上下文关联的用户
/// </summary>
public ContextUserToken UserToken { get; }
/// <summary>
/// 递归删除
/// </summary>
/// <param name="exp">exp</param>
/// <param name="disableGlobalFilterNames">禁用全局过滤器名</param>
public async Task<bool> DeleteRecursiveAsync( //
Expression<Func<TEntity, bool>> exp, params string[] disableGlobalFilterNames)
{
_ = await Select.Where(exp)
.DisableGlobalFilter(disableGlobalFilterNames)
.AsTreeCte()
.ToDelete()
.ExecuteAffrowsAsync();
return true;
}
/// <summary>
/// 获得Dto
/// </summary>
/// <param name="id">主键</param>
public Task<TDto> GetAsync<TDto>(long id)
{
return Select.WhereDynamic(id).ToOneAsync<TDto>();
}
/// <summary>
/// 根据条件获取Dto
/// </summary>
public Task<TDto> GetAsync<TDto>(Expression<Func<TEntity, bool>> exp)
{
return Select.Where(exp).ToOneAsync<TDto>();
}
/// <summary>
/// 根据条件获取实体
/// </summary>
public Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> exp)
{
return Select.Where(exp).ToOneAsync();
}
/// <summary>
/// 获取分页列表
/// </summary>
/// <param name="dynamicFilterInfo">动态过滤器</param>
/// <param name="page">页码</param>
/// <param name="pageSize">页容量</param>
/// <returns>分页列表和总条数</returns>
public async Task<(IEnumerable<TEntity> List, long Total)> GetPagedListAsync(
DynamicFilterInfo dynamicFilterInfo, int page, int pageSize)
{
var list = await Select.WhereDynamicFilter(dynamicFilterInfo)
.Count(out var total)
.Page(page, pageSize)
.ToListAsync();
return (list, total);
}
}

View File

@ -14,7 +14,7 @@ public abstract class RepositoryService<TEntity, TLogger> : ServiceBase<TLogger>
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RepositoryService{TEntity, TLogger}" /> class. /// Initializes a new instance of the <see cref="RepositoryService{TEntity, TLogger}" /> class.
/// </summary> /// </summary>
protected RepositoryService(Repository<TEntity> rpo) // protected RepositoryService(DefaultRepository<TEntity> rpo) //
{ {
Rpo = rpo; Rpo = rpo;
} }
@ -22,7 +22,7 @@ public abstract class RepositoryService<TEntity, TLogger> : ServiceBase<TLogger>
/// <summary> /// <summary>
/// 默认仓储 /// 默认仓储
/// </summary> /// </summary>
protected Repository<TEntity> Rpo { get; } protected DefaultRepository<TEntity> Rpo { get; }
/// <summary> /// <summary>
/// 启用级联保存 /// 启用级联保存
@ -31,4 +31,12 @@ public abstract class RepositoryService<TEntity, TLogger> : ServiceBase<TLogger>
get => Rpo.DbContextOptions.EnableCascadeSave; get => Rpo.DbContextOptions.EnableCascadeSave;
set => Rpo.DbContextOptions.EnableCascadeSave = value; set => Rpo.DbContextOptions.EnableCascadeSave = value;
} }
/// <summary>
/// 针对 Sqlite 数据的更新操作
/// </summary>
/// <returns>
/// 非 Sqlite 数据库请删除
/// </returns>
protected abstract Task<TEntity> UpdateForSqliteAsync(TEntity req);
} }

View File

@ -33,13 +33,9 @@ public abstract class ServiceBase : IScoped, IService
ServiceId = Guid.NewGuid(); ServiceId = Guid.NewGuid();
} }
/// <summary> /// <inheritdoc />
/// 服务编号
/// </summary>
public Guid ServiceId { get; set; } public Guid ServiceId { get; set; }
/// <summary> /// <inheritdoc />
/// 上下文用户
/// </summary>
public ContextUserToken UserToken { get; set; } public ContextUserToken UserToken { get; set; }
} }

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)/CodeQuality.props"/> <Import Project="$(SolutionDir)/code.quality.props"/>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../NetAdmin.SysComponent.Application/NetAdmin.SysComponent.Application.csproj"/> <ProjectReference Include="../NetAdmin.SysComponent.Application/NetAdmin.SysComponent.Application.csproj"/>
</ItemGroup> </ItemGroup>

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)/CodeQuality.props"/> <Import Project="$(SolutionDir)/code.quality.props"/>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../NetAdmin.SysComponent.Cache/NetAdmin.SysComponent.Cache.csproj"/> <ProjectReference Include="../NetAdmin.SysComponent.Cache/NetAdmin.SysComponent.Cache.csproj"/>
<ProjectReference Include="../NetAdmin.BizServer.Application/NetAdmin.BizServer.Application.csproj"/> <ProjectReference Include="../NetAdmin.BizServer.Application/NetAdmin.BizServer.Application.csproj"/>

View File

@ -0,0 +1,66 @@
namespace NetAdmin.BizServer.Host.Extensions;
/// <summary>
/// ApplicationBuilder对象 扩展方法
/// </summary>
[SuppressSniffer]
// ReSharper disable once InconsistentNaming
public static class IApplicationBuilderExtensions
{
private const string _INDEX_HTML_PATH = ".index.html";
private const string _RES_PFX = $"{nameof(NetAdmin)}.{nameof(BizServer)}.{nameof(Host)}.UI";
private static readonly Regex _regex = new(@"\.(\w+)$", RegexOptions.Compiled);
private static IEnumerable<string> _allResNames;
/// <summary>
/// 托管管理后台
/// </summary>
public static IApplicationBuilder UseVueAdmin(this IApplicationBuilder me)
{
if (Assembly.GetExecutingAssembly().GetManifestResourceInfo(_RES_PFX + _INDEX_HTML_PATH) == null) {
return me;
}
_allResNames = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.Where(x => x.StartsWith(_RES_PFX, StringComparison.OrdinalIgnoreCase))
.Select(x => x[_RES_PFX.Length..]);
return me.Use(UseVueAdminAsync);
}
private static Stream GetManifestResourceStream(string path)
{
return Assembly.GetExecutingAssembly().GetManifestResourceStream(_RES_PFX + path);
}
private static async Task UseVueAdminAsync(HttpContext context, Func<Task> next)
{
if (!context.Request.Path.StartsWithSegments(new PathString("/api"))) {
var path = context.Request.Path.Value;
if (path == "/" || path?.Length == 0) {
path = _INDEX_HTML_PATH;
}
path = path!.Replace('/', '.');
var output = GetManifestResourceStream(path);
if (output == null) {
var resName = _allResNames.FirstOrDefault(x => path.EndsWith(x, StringComparison.OrdinalIgnoreCase));
output = resName == null
? GetManifestResourceStream(_INDEX_HTML_PATH)
: GetManifestResourceStream(resName);
}
if (output != null) {
context.Response.ContentLength = output.Length;
context.Response.ContentType = MimeTypeHelper.GetMimeTypeByExtName(_regex.Match(path!).Groups[1].Value);
await output.CopyToAsync(context.Response.Body);
return;
}
}
await next.Invoke();
}
}

View File

@ -13,11 +13,11 @@ namespace NetAdmin.BizServer.Host.Extensions;
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
/// <summary> /// <summary>
/// 注册FreeSql /// 添加 FreeSql
/// </summary> /// </summary>
public static IServiceCollection AddFreeSql(this IServiceCollection me) public static IServiceCollection AddFreeSql(this IServiceCollection me)
{ {
_ = me.AddFreeSql( // return me.AddFreeSql( //
FreeSqlInitOptions.SyncStructure | FreeSqlInitOptions.InsertSeedData, freeSql => { FreeSqlInitOptions.SyncStructure | FreeSqlInitOptions.InsertSeedData, freeSql => {
// 数据权限过滤器 // 数据权限过滤器
_ = freeSql.GlobalFilter.ApplyOnlyIf<IFieldOwner>( // _ = freeSql.GlobalFilter.ApplyOnlyIf<IFieldOwner>( //
@ -25,11 +25,10 @@ public static class ServiceCollectionExtensions
, () => ContextUserInfo.Create()?.Roles.All(x => x.DataScope == DataScopes.Self) ?? false , () => ContextUserInfo.Create()?.Roles.All(x => x.DataScope == DataScopes.Self) ?? false
, a => a.OwnerId == ContextUserInfo.Create().Id); , a => a.OwnerId == ContextUserInfo.Create().Id);
}); });
return me;
} }
/// <summary> /// <summary>
/// jwt授权处理器 /// 添加 jwt 授权处理器
/// </summary> /// </summary>
public static IServiceCollection AddJwt(this IServiceCollection me) public static IServiceCollection AddJwt(this IServiceCollection me)
{ {

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="$(SolutionDir)/CodeQuality.props"/> <Import Project="$(SolutionDir)/code.quality.props"/>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="$(SolutionDir)/assets/captcha/**" LinkBase="Assets/Captcha"/> <EmbeddedResource Include="$(SolutionDir)/assets/captcha/**" LinkBase="Assets/Captcha"/>
</ItemGroup> </ItemGroup>
@ -12,4 +12,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="../../../dist/frontend/admin/**/*" LinkBase="UI"/>
</ItemGroup>
</Project> </Project>

View File

@ -5,8 +5,8 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "http://localhost:65010", "launchUrl": "http://localhost:5010",
"applicationUrl": "http://[::]:65010", "applicationUrl": "http://[::]:5010",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

@ -1,7 +1,9 @@
using NetAdmin.BizServer.Host.Extensions; using NetAdmin.BizServer.Host.Extensions;
using NetAdmin.Host.Extensions; using NetAdmin.Host.Extensions;
using NetAdmin.Host.Middlewares; using NetAdmin.Host.Middlewares;
#if !DEBUG
using Prometheus; using Prometheus;
#endif
namespace NetAdmin.BizServer.Host; namespace NetAdmin.BizServer.Host;
@ -25,21 +27,23 @@ public sealed class Startup : NetAdmin.Host.Startup
public void Configure(IApplicationBuilder app) public void Configure(IApplicationBuilder app)
{ {
_ = app // _ = app //
.UseRealIp() // 获取真实IP .UseRealIp() // 使用RealIp中间件用于获取真实客户端IP地址
.EnableBuffering() // 启用 Body 重读 .EnableBuffering() // 启用请求体缓冲,允许多次读取请求体
.UseMiddleware<RequestAuditMiddleware>() // 请求审计 .UseMiddleware<RequestAuditMiddleware>() // 使用RequestAuditMiddleware中间件执行请求审计
#if DEBUG #if DEBUG
.UseOpenApiSkin() // Swagger皮肤 .UseOpenApiSkin() // 使用OpenApiSkin中间件仅在调试模式下提供Swagger UI皮肤
#else
.UseVueAdmin() // 托管管理后台,仅在非调试模式下
.UseHttpMetrics() // 使用HttpMetrics中间件启用HTTP性能监控
#endif #endif
.UseInject(string.Empty) // / Furion脚手架 .UseInject(string.Empty) // 使用Inject中间件Furion脚手架的依赖注入支持
.UseUnifyResultStatusCodes() // 状态码拦截 .UseUnifyResultStatusCodes() // 使用UnifyResultStatusCodes中间件用于统一处理结果状态码
.UseCorsAccessor() // 跨域访问 .UseCorsAccessor() // 使用CorsAccessor中间件启用跨域资源共享CORS支持
.UseRouting() // 路由映射 .UseRouting() // 使用Routing中间件配置路由映射
.UseHttpMetrics() // 性能监控 .UseAuthentication() // 使用Authentication中间件启用身份验证
.UseAuthentication() // / 认证 .UseAuthorization() // 使用Authorization中间件启用授权
.UseAuthorization() // 授权 .UseMiddleware<RemoveNullNodeMiddleware>() // 使用RemoveNullNodeMiddleware中间件删除JSON中的空节点
.UseMiddleware<RemoveNullNodeMiddleware>() // 删除json空节点 .UseEndpoints(); // 配置端点以处理请求
.UseEndpoints(); // 执行匹配的端点
} }
/// <summary> /// <summary>
@ -47,21 +51,21 @@ public sealed class Startup : NetAdmin.Host.Startup
/// </summary> /// </summary>
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
_ = services.AddConsoleFormatter() // 控制台日志模板 _ = services.AddConsoleFormatter() // 添加控制台日志模板
.AddAllOptions() // 注册配置项 .AddAllOptions() // 添加配置项
.AddJwt() // Jwt 授权处理器 .AddJwt() // 添加 Jwt 授权处理器
.AddSnowflake() // 雪花编号生成器 .AddSnowflake() // 添加雪花编号生成器
.AddEventBus() // 事件总线 .AddEventBus() // 添加事件总线
.AddFreeSql() // freeSql .AddFreeSql() // 添加 freeSql
.AddRemoteRequest() // 注册远程请求 .AddRemoteRequest() // 添加远程请求
.AddCorsAccessor() // 支持跨域访问 .AddCorsAccessor() // 添加支持跨域访问
.AddContextUser() // 上下文用户 .AddContextUser() // 添加上下文用户
.AddRedisCache() // Redis缓存 .AddRedisCache() // 添加 Redis 缓存
// IMvcBuilder // IMvcBuilder
.AddControllers() // 注册控制器 .AddControllers() // 添加控制器
.AddJsonSerializer(true) // json序列化配置 .AddJsonSerializer(true) // 添加JSON序列化器并设置显示枚举名而非数字枚举值
.AddDefaultApiResultHandler() // Api结果处理器 .AddDefaultApiResultHandler() // 添加默认的API结果处理程序
; ;
} }
} }

View File

@ -0,0 +1,172 @@
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using NetAdmin.BizServer.Host;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys.Api;
using NetAdmin.Domain.Dto.Sys.Cache;
using NetAdmin.Domain.Dto.Sys.Config;
using NetAdmin.Domain.Dto.Sys.Tool;
using NetAdmin.SysComponent.Application.Modules.Sys;
using NetAdmin.Tests;
using Xunit;
using Xunit.Abstractions;
namespace NetAdmin.BizServer.Tests;
/// <summary>
/// 所有测试
/// </summary>
[SuppressMessage("Usage", "xUnit1028:Test method must have valid return type")]
public class AllTests(WebApplicationFactory<Startup> factory, ITestOutputHelper testOutputHelper) :
WebApiTestBase<Startup>(factory, testOutputHelper), IToolsModule, ICacheModule, IApiModule, IConfigModule
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
[Fact]
public async Task<CacheStatisticsRsp> CacheStatisticsAsync()
{
var rsp = await PostAsync("/api/sys/cache/cache.statistics", null);
Assert.Equal(HttpStatusCode.OK, rsp.StatusCode);
return default;
}
/// <inheritdoc />
public Task<QueryConfigRsp> CreateAsync(CreateConfigReq req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<QueryApiRsp> CreateAsync(CreateApiReq req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryConfigReq> req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryApiReq> req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
[Theory]
[InlineData(default)]
public async Task<PagedQueryRsp<GetAllEntriesRsp>> GetAllEntriesAsync(PagedQueryReq<GetAllEntriesReq> req)
{
var rsp = await PostAsync("/api/sys/cache/get.all.entries"
, JsonContent.Create(new PagedQueryReq<GetAllEntriesReq>()));
Assert.Equal(HttpStatusCode.OK, rsp.StatusCode);
return default;
}
/// <inheritdoc />
public Task<QueryConfigRsp> GetAsync(QueryConfigReq req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<QueryApiRsp> GetAsync(QueryApiReq req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<QueryConfigRsp> GetLatestConfigAsync()
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<IEnumerable<GetModulesRsp>> GetModulesAsync()
{
throw new NotImplementedException();
}
/// <inheritdoc />
[Fact]
public async Task<DateTime> GetServerUtcTimeAsync()
{
var response = await PostAsync("/api/sys/tools/get.server.utc.time", null);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
return default;
}
/// <inheritdoc />
[Fact]
public async Task<string> GetVersionAsync()
{
var response = await PostAsync("/api/sys/tools/version", null);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
return default;
}
/// <inheritdoc />
public Task<IEnumerable<(string Name, string Version)>> ModulesAsync()
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryConfigRsp>> PagedQueryAsync(PagedQueryReq<QueryConfigReq> req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryApiRsp>> PagedQueryAsync(PagedQueryReq<QueryApiReq> req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<IEnumerable<QueryConfigRsp>> QueryAsync(QueryReq<QueryConfigReq> req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<IEnumerable<QueryApiRsp>> QueryAsync(QueryReq<QueryApiReq> req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
[Fact]
public async Task SyncAsync()
{
var response = await PostAsync("/api/sys/api/sync", null);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
/// <inheritdoc />
public Task<QueryConfigRsp> UpdateAsync(UpdateConfigReq req)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<NopReq> UpdateAsync(NopReq req)
{
throw new NotImplementedException();
}
}

View File

@ -1,21 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../NetAdmin.Host/NetAdmin.Host.csproj"/> <ProjectReference Include="../NetAdmin.Tests/NetAdmin.Tests.csproj"/>
<ProjectReference Include="..\NetAdmin.BizServer.Host\NetAdmin.BizServer.Host.csproj"/> <ProjectReference Include="../NetAdmin.BizServer.Host/NetAdmin.BizServer.Host.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0-preview-23371-04"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0-preview-23531-01"/>
<PackageReference Include="xunit" Version="2.5.1-pre.20"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1-pre.10">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,18 +0,0 @@
using Xunit;
namespace NetAdmin.BizServer.Tests;
/// <summary>
/// UsedNumberTests
/// </summary>
public class UsedNumberTests
{
/// <summary>
/// Test1
/// </summary>
[Fact]
public void Test1()
{
// Assert.True("1".Int32() == 2);
}
}

View File

@ -17,13 +17,9 @@ public abstract class CacheBase<TCacheContainer, TService> : ICache<TCacheContai
Service = service; Service = service;
} }
/// <summary> /// <inheritdoc />
/// 缓存对象
/// </summary>
public TCacheContainer Cache { get; } public TCacheContainer Cache { get; }
/// <summary> /// <inheritdoc />
/// 关联的服务
/// </summary>
public TService Service { get; } public TService Service { get; }
} }

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)/CodeQuality.props"/> <Import Project="$(SolutionDir)/code.quality.props"/>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../NetAdmin.Application/NetAdmin.Application.csproj"/> <ProjectReference Include="../NetAdmin.Application/NetAdmin.Application.csproj"/>
</ItemGroup> </ItemGroup>

View File

@ -1,7 +1,7 @@
namespace NetAdmin.Domain.Attributes.DataValidation; namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary> /// <summary>
/// 证件号码 /// 证件号码验证器
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class CertificateAttribute : RegexAttribute public sealed class CertificateAttribute : RegexAttribute

View File

@ -1,7 +1,7 @@
namespace NetAdmin.Domain.Attributes.DataValidation; namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary> /// <summary>
/// 中文姓名 /// 中文姓名验证器
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class ChineseNameAttribute : RegexAttribute public sealed class ChineseNameAttribute : RegexAttribute

View File

@ -1,23 +0,0 @@
namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary>
/// 电话验证器(手机或固话)
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class CulturePhoneAttribute : ValidationAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="CulturePhoneAttribute" /> class.
/// </summary>
public CulturePhoneAttribute()
{
ErrorMessageResourceName = nameof(Ln.);
ErrorMessageResourceType = typeof(Ln);
}
/// <inheritdoc />
public override bool IsValid(object value)
{
return new MobileAttribute().IsValid(value) || new TelephoneAttribute().IsValid(value);
}
}

View File

@ -1,26 +0,0 @@
namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary>
/// 区间验证器
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class CultureRangeAttribute : RangeAttribute
{
/// <inheritdoc cref="RangeAttribute" />
public CultureRangeAttribute(double minimum, double maximum) //
: base(minimum, maximum) { }
/// <inheritdoc cref="RangeAttribute" />
public CultureRangeAttribute(int minimum, int maximum) //
: base(minimum, maximum) { }
/// <inheritdoc cref="RangeAttribute" />
public CultureRangeAttribute(Type type, string minimum, string maximum) //
: base(type, minimum, maximum) { }
/// <inheritdoc />
public override string FormatErrorMessage(string name)
{
return $"{ErrorMessageString} {Ln.必须介于} {Minimum} - {Maximum}";
}
}

View File

@ -1,17 +0,0 @@
namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary>
/// 非空验证器
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class CultureRequiredAttribute : RequiredAttribute
{
/// <summary>Applies formatting to an error message, based on the data field where the error occurred.</summary>
/// <param name="name">The name to include in the formatted message.</param>
/// <exception cref="T:System.InvalidOperationException">The current attribute is malformed.</exception>
/// <returns>An instance of the formatted error message.</returns>
public override string FormatErrorMessage(string name)
{
return $"{ErrorMessageString.NullOrEmpty(name)} {Ln.不能为空}";
}
}

View File

@ -1,21 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary>
/// Url验证器
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class CultureUrlAttribute : RegexAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="CultureUrlAttribute" /> class.
/// </summary>
public CultureUrlAttribute() //
: base(Chars.RGX_URL)
{
ErrorMessageResourceName = nameof(Ln.);
ErrorMessageResourceType = typeof(Ln);
}
}

View File

@ -0,0 +1,18 @@
namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary>
/// 端口号验证器
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class PortAttribute : RangeAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="PortAttribute" /> class.
/// </summary>
public PortAttribute() //
: base(1, 65535)
{
ErrorMessageResourceName = nameof(Ln.);
ErrorMessageResourceType = typeof(Ln);
}
}

View File

@ -6,7 +6,4 @@ namespace NetAdmin.Domain.Attributes;
/// 标记一个字段启用雪花编号生成 /// 标记一个字段启用雪花编号生成
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class SnowflakeAttribute : Attribute public sealed class SnowflakeAttribute : Attribute { }
{
//
}

View File

@ -10,16 +10,19 @@ public sealed record ContextUserToken : DataAbstraction
/// <summary> /// <summary>
/// 用户编号 /// 用户编号
/// </summary> /// </summary>
public long Id { get; set; } /// ReSharper disable once MemberCanBePrivate.Global
public long Id { get; init; }
/// <summary> /// <summary>
/// 做授权验证的Token全局唯一可以随时重置强制下线 /// 做授权验证的Token全局唯一可以随时重置强制下线
/// </summary> /// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public Guid Token { get; init; } public Guid Token { get; init; }
/// <summary> /// <summary>
/// 用户名 /// 用户名
/// </summary> /// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public string UserName { get; init; } public string UserName { get; init; }
/// <summary> /// <summary>

View File

@ -3,14 +3,10 @@ using NetAdmin.Domain.DbMaps.Dependency.Fields;
namespace NetAdmin.Domain.DbMaps.Dependency; namespace NetAdmin.Domain.DbMaps.Dependency;
/// <summary> /// <inheritdoc />
/// 不可变实体
/// </summary>
public abstract record ImmutableEntity : ImmutableEntity<long> public abstract record ImmutableEntity : ImmutableEntity<long>
{ {
/// <summary> /// <inheritdoc cref="IFieldPrimary{T}.Id" />
/// 唯一编码
/// </summary>
[Snowflake] [Snowflake]
[Column(IsIdentity = false, IsPrimary = true, Position = 1)] [Column(IsIdentity = false, IsPrimary = true, Position = 1)]
public override long Id { get; init; } public override long Id { get; init; }
@ -19,26 +15,20 @@ public abstract record ImmutableEntity : ImmutableEntity<long>
/// <summary> /// <summary>
/// 不可变实体 /// 不可变实体
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T">主键类型</typeparam>
public abstract record ImmutableEntity<T> : LiteImmutableEntity<T>, IFieldCreatedUser public abstract record ImmutableEntity<T> : LiteImmutableEntity<T>, IFieldCreatedUser
{ {
/// <summary> /// <inheritdoc cref="IFieldCreatedUser.CreatedUserId" />
/// 创建者编号
/// </summary>
[JsonIgnore] [JsonIgnore]
[Column(CanUpdate = false, Position = -1)] [Column(CanUpdate = false, Position = -1)]
public long? CreatedUserId { get; init; } public long? CreatedUserId { get; init; }
/// <summary> /// <inheritdoc cref="IFieldCreatedUser.CreatedUserName" />
/// 创建者用户名
/// </summary>
[JsonIgnore] [JsonIgnore]
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31, CanUpdate = false, Position = -1)] [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31, CanUpdate = false, Position = -1)]
public virtual string CreatedUserName { get; set; } public virtual string CreatedUserName { get; set; }
/// <summary> /// <inheritdoc cref="IFieldPrimary{T}.Id" />
/// 唯一编码
/// </summary>
[Column(IsIdentity = false, IsPrimary = true, Position = 1)] [Column(IsIdentity = false, IsPrimary = true, Position = 1)]
public override T Id { get; init; } public override T Id { get; init; }
} }

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