From 7fbb7cd2b59e02d489bb7439a9f24d943d9c9714 Mon Sep 17 00:00:00 2001
From: YDL <1368269699@QQ.COM>
Date: Fri, 9 Sep 2022 10:20:09 +0800
Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=88=9D=E5=A7=8B=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 270 ++++++++
AX.WebDrillServer/AX.WebDrillServer.csproj | 79 +++
AX.WebDrillServer/Constants/Permissions.cs | 74 +++
.../Controllers/AccountsController.cs | 211 +++++++
.../Controllers/UsersController.cs | 164 +++++
.../Data/ApplicationDbContext.cs | 30 +
.../Data/ApplicationDbContextExtensions.cs | 32 +
.../Data/ApplicationDbContextSeed.cs | 170 +++++
AX.WebDrillServer/Dtos/DtoBase.cs | 41 ++
.../Dtos/Organizations/OrganizationDto.cs | 11 +
AX.WebDrillServer/Dtos/Pagination.cs | 34 +
AX.WebDrillServer/Dtos/QueryOptions.cs | 30 +
AX.WebDrillServer/Dtos/Users/UserDto.cs | 33 +
.../EntityBaseConfiguration.cs | 19 +
.../OrganizationConfiguration.cs | 13 +
.../PremissionConfiguration.cs | 17 +
.../EntityConfigurations/RoleConfiguration.cs | 17 +
.../Role_PremissionConfiguration.cs | 17 +
.../EntityConfigurations/UserConfiguration.cs | 17 +
.../User_RoleConfiguration.cs | 17 +
.../EntityMappers/EntityMapperConfig.cs | 41 ++
AX.WebDrillServer/EntityMappers/Mappers.cs | 55 ++
AX.WebDrillServer/Enums/OrganizationLevel.cs | 33 +
AX.WebDrillServer/Enums/SortType.cs | 11 +
AX.WebDrillServer/Errors/GlobalErrorCodes.cs | 27 +
.../Extensions/CollectionExtensions.cs | 33 +
.../Extensions/ControllerExtensions.cs | 92 +++
.../Extensions/DateTimeExtensions.cs | 51 ++
.../Extensions/EntityExtensions.cs | 14 +
AX.WebDrillServer/Extensions/HubExtensions.cs | 10 +
.../Extensions/JsonExtensions.cs | 10 +
.../Extensions/StringExtensions.cs | 17 +
AX.WebDrillServer/Helpers/DateTimeHelper.cs | 30 +
AX.WebDrillServer/Helpers/ExcelHelper.cs | 92 +++
.../Helpers/OrganizationHelper.cs | 27 +
AX.WebDrillServer/Hubs/INotificationClient.cs | 29 +
AX.WebDrillServer/Hubs/ITaskChatClient.cs | 27 +
AX.WebDrillServer/Hubs/NotificationHub.cs | 67 ++
AX.WebDrillServer/Hubs/TaskChatHub.cs | 123 ++++
AX.WebDrillServer/License.dat | Bin 0 -> 976 bytes
AX.WebDrillServer/Middlewares/AESHelper.cs | 53 ++
.../Middlewares/FileUploadOperationFilter.cs | 37 ++
.../JsonFormatters/RawJsonInputFormatter.cs | 27 +
.../JsonFormatters/RawJsonOutputFormatter.cs | 54 ++
.../Middlewares/Jwts/IJwtService.cs | 25 +
.../Middlewares/Jwts/JwtClaimTypes.cs | 173 ++++++
.../Middlewares/Jwts/JwtOptions.cs | 33 +
.../Middlewares/Jwts/JwtService.cs | 85 +++
AX.WebDrillServer/Middlewares/LicenseFile.cs | 18 +
AX.WebDrillServer/Middlewares/LicenseInfo.cs | 57 ++
.../Middlewares/LicenseMiddleware.cs | 48 ++
.../Middlewares/NameUserIdProvider.cs | 13 +
AX.WebDrillServer/Middlewares/ObjectId.cs | 588 ++++++++++++++++++
.../Middlewares/ObjectIdGenerator.cs | 16 +
AX.WebDrillServer/Middlewares/RSAHelper.cs | 125 ++++
.../Middlewares/ServerLicense.cs | 107 ++++
.../20220909012500_InitialCreate.Designer.cs | 385 ++++++++++++
.../20220909012500_InitialCreate.cs | 159 +++++
.../ApplicationDbContextModelSnapshot.cs | 383 ++++++++++++
AX.WebDrillServer/Models/EntityBase.cs | 44 ++
AX.WebDrillServer/Models/IEntityBase.cs | 43 ++
AX.WebDrillServer/Models/Organization.cs | 21 +
AX.WebDrillServer/Models/Premission.cs | 17 +
AX.WebDrillServer/Models/Role.cs | 17 +
AX.WebDrillServer/Models/Role_Premission.cs | 15 +
AX.WebDrillServer/Models/User.cs | 18 +
AX.WebDrillServer/Models/UserGroup.cs | 17 +
AX.WebDrillServer/Models/UserGroup_Role.cs | 15 +
AX.WebDrillServer/Models/UserGroup_User.cs | 16 +
AX.WebDrillServer/Models/User_Role.cs | 15 +
AX.WebDrillServer/Program.cs | 275 ++++++++
.../Properties/launchSettings.json | 30 +
.../Services/Emails/EmailService.cs | 71 +++
.../Services/Emails/IEmailService.cs | 19 +
.../Notifications/INotificationService.cs | 32 +
.../Notifications/NotificationService.cs | 88 +++
AX.WebDrillServer/appsettings.Staging.json | 44 ++
AX.WebDrillServer/appsettings.json | 31 +
AX.WebDrillServer/调试发布程序.bat | 3 +
79 files changed, 5302 insertions(+)
create mode 100644 .gitignore
create mode 100644 AX.WebDrillServer/AX.WebDrillServer.csproj
create mode 100644 AX.WebDrillServer/Constants/Permissions.cs
create mode 100644 AX.WebDrillServer/Controllers/AccountsController.cs
create mode 100644 AX.WebDrillServer/Controllers/UsersController.cs
create mode 100644 AX.WebDrillServer/Data/ApplicationDbContext.cs
create mode 100644 AX.WebDrillServer/Data/ApplicationDbContextExtensions.cs
create mode 100644 AX.WebDrillServer/Data/ApplicationDbContextSeed.cs
create mode 100644 AX.WebDrillServer/Dtos/DtoBase.cs
create mode 100644 AX.WebDrillServer/Dtos/Organizations/OrganizationDto.cs
create mode 100644 AX.WebDrillServer/Dtos/Pagination.cs
create mode 100644 AX.WebDrillServer/Dtos/QueryOptions.cs
create mode 100644 AX.WebDrillServer/Dtos/Users/UserDto.cs
create mode 100644 AX.WebDrillServer/EntityConfigurations/EntityBaseConfiguration.cs
create mode 100644 AX.WebDrillServer/EntityConfigurations/OrganizationConfiguration.cs
create mode 100644 AX.WebDrillServer/EntityConfigurations/PremissionConfiguration.cs
create mode 100644 AX.WebDrillServer/EntityConfigurations/RoleConfiguration.cs
create mode 100644 AX.WebDrillServer/EntityConfigurations/Role_PremissionConfiguration.cs
create mode 100644 AX.WebDrillServer/EntityConfigurations/UserConfiguration.cs
create mode 100644 AX.WebDrillServer/EntityConfigurations/User_RoleConfiguration.cs
create mode 100644 AX.WebDrillServer/EntityMappers/EntityMapperConfig.cs
create mode 100644 AX.WebDrillServer/EntityMappers/Mappers.cs
create mode 100644 AX.WebDrillServer/Enums/OrganizationLevel.cs
create mode 100644 AX.WebDrillServer/Enums/SortType.cs
create mode 100644 AX.WebDrillServer/Errors/GlobalErrorCodes.cs
create mode 100644 AX.WebDrillServer/Extensions/CollectionExtensions.cs
create mode 100644 AX.WebDrillServer/Extensions/ControllerExtensions.cs
create mode 100644 AX.WebDrillServer/Extensions/DateTimeExtensions.cs
create mode 100644 AX.WebDrillServer/Extensions/EntityExtensions.cs
create mode 100644 AX.WebDrillServer/Extensions/HubExtensions.cs
create mode 100644 AX.WebDrillServer/Extensions/JsonExtensions.cs
create mode 100644 AX.WebDrillServer/Extensions/StringExtensions.cs
create mode 100644 AX.WebDrillServer/Helpers/DateTimeHelper.cs
create mode 100644 AX.WebDrillServer/Helpers/ExcelHelper.cs
create mode 100644 AX.WebDrillServer/Helpers/OrganizationHelper.cs
create mode 100644 AX.WebDrillServer/Hubs/INotificationClient.cs
create mode 100644 AX.WebDrillServer/Hubs/ITaskChatClient.cs
create mode 100644 AX.WebDrillServer/Hubs/NotificationHub.cs
create mode 100644 AX.WebDrillServer/Hubs/TaskChatHub.cs
create mode 100644 AX.WebDrillServer/License.dat
create mode 100644 AX.WebDrillServer/Middlewares/AESHelper.cs
create mode 100644 AX.WebDrillServer/Middlewares/FileUploadOperationFilter.cs
create mode 100644 AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonInputFormatter.cs
create mode 100644 AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonOutputFormatter.cs
create mode 100644 AX.WebDrillServer/Middlewares/Jwts/IJwtService.cs
create mode 100644 AX.WebDrillServer/Middlewares/Jwts/JwtClaimTypes.cs
create mode 100644 AX.WebDrillServer/Middlewares/Jwts/JwtOptions.cs
create mode 100644 AX.WebDrillServer/Middlewares/Jwts/JwtService.cs
create mode 100644 AX.WebDrillServer/Middlewares/LicenseFile.cs
create mode 100644 AX.WebDrillServer/Middlewares/LicenseInfo.cs
create mode 100644 AX.WebDrillServer/Middlewares/LicenseMiddleware.cs
create mode 100644 AX.WebDrillServer/Middlewares/NameUserIdProvider.cs
create mode 100644 AX.WebDrillServer/Middlewares/ObjectId.cs
create mode 100644 AX.WebDrillServer/Middlewares/ObjectIdGenerator.cs
create mode 100644 AX.WebDrillServer/Middlewares/RSAHelper.cs
create mode 100644 AX.WebDrillServer/Middlewares/ServerLicense.cs
create mode 100644 AX.WebDrillServer/Migrations/20220909012500_InitialCreate.Designer.cs
create mode 100644 AX.WebDrillServer/Migrations/20220909012500_InitialCreate.cs
create mode 100644 AX.WebDrillServer/Migrations/ApplicationDbContextModelSnapshot.cs
create mode 100644 AX.WebDrillServer/Models/EntityBase.cs
create mode 100644 AX.WebDrillServer/Models/IEntityBase.cs
create mode 100644 AX.WebDrillServer/Models/Organization.cs
create mode 100644 AX.WebDrillServer/Models/Premission.cs
create mode 100644 AX.WebDrillServer/Models/Role.cs
create mode 100644 AX.WebDrillServer/Models/Role_Premission.cs
create mode 100644 AX.WebDrillServer/Models/User.cs
create mode 100644 AX.WebDrillServer/Models/UserGroup.cs
create mode 100644 AX.WebDrillServer/Models/UserGroup_Role.cs
create mode 100644 AX.WebDrillServer/Models/UserGroup_User.cs
create mode 100644 AX.WebDrillServer/Models/User_Role.cs
create mode 100644 AX.WebDrillServer/Program.cs
create mode 100644 AX.WebDrillServer/Properties/launchSettings.json
create mode 100644 AX.WebDrillServer/Services/Emails/EmailService.cs
create mode 100644 AX.WebDrillServer/Services/Emails/IEmailService.cs
create mode 100644 AX.WebDrillServer/Services/Notifications/INotificationService.cs
create mode 100644 AX.WebDrillServer/Services/Notifications/NotificationService.cs
create mode 100644 AX.WebDrillServer/appsettings.Staging.json
create mode 100644 AX.WebDrillServer/appsettings.json
create mode 100644 AX.WebDrillServer/调试发布程序.bat
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..67c0a5b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,270 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio cache/options directory
+.vs/
+.vscode/
+
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+/AX.EvaluationSystem/*.db
+
+# macOS Desktop Services Store
+.DS_Store
+
+# Use customlized development envirnment settings.
+appsettings.Development.json
\ No newline at end of file
diff --git a/AX.WebDrillServer/AX.WebDrillServer.csproj b/AX.WebDrillServer/AX.WebDrillServer.csproj
new file mode 100644
index 0000000..d4023a4
--- /dev/null
+++ b/AX.WebDrillServer/AX.WebDrillServer.csproj
@@ -0,0 +1,79 @@
+
+
+
+ net6.0
+ enable
+ enable
+ Debug;Release;Test
+
+
+
+ win-x64
+ bin\Debug\
+
+
+
+ linux-x64
+ true
+ true
+ true
+ bin\Test\
+ false
+ false
+
+
+
+ linux-x64
+ true
+ true
+ true
+ bin\Release\
+ false
+ false
+
+
+
+
+
+ false
+
+
+
+
+ false
+
+
+
+
+ true
+ $(NoWarn);1591
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AX.WebDrillServer/Constants/Permissions.cs b/AX.WebDrillServer/Constants/Permissions.cs
new file mode 100644
index 0000000..3d4db30
--- /dev/null
+++ b/AX.WebDrillServer/Constants/Permissions.cs
@@ -0,0 +1,74 @@
+namespace AX.WebDrillServer.Constants
+{
+ ///
+ /// 系统权限。
+ ///
+ public static class Permissions
+ {
+ #region 组织机构管理
+
+ public const string Organization = "Organization";
+
+ public const string OrganizationCreate = "Organization.Create";
+ public const string OrganizationRead = "Organization.Read";
+ public const string OrganizationUpdate = "Organization.Update";
+ public const string OrganizationDelete = "Organization.Delete";
+
+ #endregion 组织机构管理
+
+ #region 角色管理
+
+ public const string Role = "Role";
+
+ public const string RoleCreate = "Role.Create";
+ public const string RoleRead = "Role.Read";
+ public const string RoleUpdate = "Role.Update";
+ public const string RoleDelete = "Role.Delete";
+
+ #endregion 角色管理
+
+ #region 数据权限管理
+
+ public const string Permission = "Permission";
+
+ public const string PermissionCreate = "Permission.Create";
+ public const string PermissionRead = "Permission.Read";
+ public const string PermissionUpdate = "Permission.Update";
+ public const string PermissionDelete = "Permission.Delete";
+
+ #endregion 数据权限管理
+
+ #region 页面权限管理
+
+ public const string Page = "Page";
+
+ public const string PageCreate = "Page.Create";
+ public const string PageRead = "Page.Read";
+ public const string PageUpdate = "Page.Update";
+ public const string PageDelete = "Page.Delete";
+
+ #endregion 页面权限管理
+
+ #region 用户管理
+
+ public const string User = "User";
+
+ public const string UserCreate = "User.Create";
+ public const string UserRead = "User.Read";
+ public const string UserUpdate = "User.Update";
+ public const string UserDelete = "User.Delete";
+
+ #endregion 用户管理
+
+ #region 单位管理
+
+ public const string Company = "Company";
+
+ public const string CompanyCreate = "Company.Create";
+ public const string CompanyRead = "Company.Read";
+ public const string CompanyUpdate = "Company.Update";
+ public const string CompanyDelete = "Company.Delete";
+
+ #endregion 单位管理
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Controllers/AccountsController.cs b/AX.WebDrillServer/Controllers/AccountsController.cs
new file mode 100644
index 0000000..21d477b
--- /dev/null
+++ b/AX.WebDrillServer/Controllers/AccountsController.cs
@@ -0,0 +1,211 @@
+using AX.WebDrillServer.Data;
+using AX.WebDrillServer.Middlewares.Jwts;
+using AX.WebDrillServer.Models;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+using System.Security.Authentication;
+using System.Security.Claims;
+using static BCrypt.Net.BCrypt;
+
+namespace AX.WebDrillServer.Controllers
+{
+ ///
+ /// 账号控制器
+ ///
+ [ApiController]
+ [Authorize]
+ [Produces("application/json")]
+ [Route("api/[controller]")]
+ public class AccountsController : ControllerBase
+ {
+ private readonly ApplicationDbContext _context;
+ private readonly DbSet _users;
+
+ private readonly IMemoryCache _memoryCache;
+ private readonly IOptionsMonitor _jwtOptions;
+ private readonly IJwtService _jwtService;
+
+ public AccountsController(
+ ApplicationDbContext context,
+ IMemoryCache memoryCache,
+ IOptionsMonitor jwtOptions,
+ IJwtService jwtService)
+ {
+ _context = context;
+ _users = context.Users;
+
+ _memoryCache = memoryCache;
+ _jwtOptions = jwtOptions;
+ _jwtService = jwtService;
+ }
+
+// #region APIs
+
+// ///
+// /// 登录
+// ///
+// ///
+// ///
+// [AllowAnonymous]
+// [ProducesResponseType(StatusCodes.Status200OK)]
+// [ProducesResponseType(StatusCodes.Status400BadRequest)]
+// [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+// [HttpPost("[action]")]
+// public async Task> SignIn([FromBody] SignInDto dto)
+// {
+// var user = await _users.Include(u => u.Organization)
+// .SingleOrDefaultAsync(u => u.Username == dto.Username);
+// if (user == null)
+// return Problem("用户名或密码错误!");
+// if (!Verify(dto.Password, user.Password))
+// return Problem("用户名或密码错误!");
+
+// await _context.SaveChangesAsync();
+// var claims = new List
+// {
+// new Claim(JwtClaimTypes.Subject, user.Id),
+// new Claim(JwtClaimTypes.Name, user.Name ?? string.Empty),
+// new Claim(JwtClaimTypes.OrganizationId, user.OrganizationId ?? string.Empty),
+// new Claim(JwtClaimTypes.OrganizationCode, user.Organization?.Code ?? string.Empty),
+// };
+// if (!string.IsNullOrWhiteSpace(user.OrganizationId))
+// claims.Add(new Claim(JwtClaimTypes.OrganizationId, user.OrganizationId));
+// if (user.Organization != null && !string.IsNullOrWhiteSpace(user.Organization.Code))
+// claims.Add(new Claim(JwtClaimTypes.OrganizationCode, user.Organization.Code));
+
+// var identity = new ClaimsIdentity(claims);
+// var token = _jwtService.Create(identity);
+// var refreshToken = Guid.NewGuid().ToString("N");
+// var jwtOptions = _jwtOptions.CurrentValue;
+
+// _memoryCache.Set(refreshToken, dto.Username, DateTimeOffset.Now.AddMinutes(jwtOptions.RefreshExpires));
+
+// var result = user.ToDto(token, refreshToken, jwtOptions.Expires);
+
+// return Ok(result);
+// }
+
+// ///
+// /// 刷新令牌。
+// ///
+// [AllowAnonymous]
+// [ProducesResponseType(StatusCodes.Status200OK)]
+// [ProducesResponseType(StatusCodes.Status400BadRequest)]
+// [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+// [HttpPost("[action]")]
+// public ActionResult RefreshToken([FromBody] RefreshTokenDto dto)
+// {
+// if (string.IsNullOrEmpty(dto.RefreshToken) ||
+// string.IsNullOrEmpty(dto.Token))
+// return BadRequest();
+
+//#if RELEASE
+// //校验缓存中是否有该刷新令牌
+// if (!_memoryCache.TryGetValue(dto.RefreshToken, out _))
+// return Unauthorized();
+//#endif
+
+// //校验令牌是否有效
+// if (!_jwtService.Validate(dto.Token, out var principal))
+// return Unauthorized();
+
+// var jwtOptions = _jwtOptions.CurrentValue;
+// if (principal.Identity is not ClaimsIdentity identity)
+// return Unauthorized();
+
+// var newToken = _jwtService.Create(identity);
+
+// var result = new IdentityDto()
+// {
+// Token = newToken,
+// RefreshToken = dto.RefreshToken,
+// Expires = jwtOptions.Expires
+// };
+
+// return Ok(result);
+
+// }
+
+// ///
+// /// 修改密码
+// ///
+// ///
+// ///
+// [Authorize]
+// [ProducesResponseType(StatusCodes.Status204NoContent)]
+// [ProducesResponseType(StatusCodes.Status400BadRequest)]
+// [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+// [HttpPatch("[action]")]
+// public async Task ChangePassword([FromBody, BindRequired] ChangePasswordDto dto)
+// {
+// try
+// {
+// await ChangePasswordAsync(dto);
+// return NoContent();
+// }
+// catch (AuthenticationException e)
+// {
+// return Unauthorized(e.Message);
+// }
+// catch (Exception e)
+// {
+// return Problem(e.Message);
+// }
+// }
+
+// ///
+// /// 获取当前用户信息
+// ///
+// ///
+// [Authorize]
+// [ProducesResponseType(StatusCodes.Status204NoContent)]
+// [ProducesResponseType(StatusCodes.Status400BadRequest)]
+// [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+// [ProducesResponseType(StatusCodes.Status404NotFound)]
+// [ProducesResponseType(StatusCodes.Status403Forbidden)]
+// [HttpGet("Profile")]
+// public async Task> GetProfile()
+// {
+// var currUserId = this.GetIdOfCurrentUser();
+// var currUser = await _users.AsNoTracking()
+// .Include(e => e.Organization)
+// .Include(e => e.Roles!).ThenInclude(e => e.Permissions)
+// .Include(e => e.Roles!).ThenInclude(e => e.Pages)
+// .SingleOrDefaultAsync(e => e.Id == currUserId);
+
+// if (currUser is null) return NotFound();
+// if (!currUser.Enabled) return Forbid();
+
+// var result = currUser.ToProfileDto();
+
+// return Ok(result);
+// }
+
+// #endregion APIs
+
+// #region Local Methods
+
+// private async ValueTask ChangePasswordAsync(ChangePasswordDto dto)
+// {
+// var userId = HttpContext.User.FindFirstValue(JwtClaimTypes.Subject);
+
+// await using var transaction = _context.Database.BeginTransaction();
+
+// var user = await _users.SingleOrDefaultAsync(e => e.Id == userId);
+// if (user == default || !Verify(dto.OldPassword, user.Password))
+// throw new AuthenticationException("旧密码认证失败;");
+
+// user.Password = HashPassword(dto.NewPassword);
+// user.LastModificationTime = DateTime.UtcNow;
+
+// await _context.SaveChangesAsync();
+// await transaction.CommitAsync();
+// }
+
+// #endregion Local Methods
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Controllers/UsersController.cs b/AX.WebDrillServer/Controllers/UsersController.cs
new file mode 100644
index 0000000..f8b299c
--- /dev/null
+++ b/AX.WebDrillServer/Controllers/UsersController.cs
@@ -0,0 +1,164 @@
+using AX.WebDrillServer.Data;
+using AX.WebDrillServer.Dtos.Users;
+using AX.WebDrillServer.EntityMappers;
+using AX.WebDrillServer.Extensions;
+using AX.WebDrillServer.Models;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.EntityFrameworkCore;
+using NPOI.OpenXmlFormats.Spreadsheet;
+using NPOI.SS.Formula.Functions;
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.InteropServices;
+using System.Security.Claims;
+using static BCrypt.Net.BCrypt;
+
+namespace AX.IntegrationSystem.Controllers
+{
+ ///
+ /// 用户控制器
+ ///
+ [ApiController]
+ //[Authorize]
+ [Produces("application/json")]
+ [Route("api/[controller]")]
+ public class UsersController : ControllerBase
+ {
+ private readonly ApplicationDbContext _context;
+
+ public UsersController(ApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ #region APIs
+
+ ///
+ /// 创建用户
+ ///
+ /// 新增内容
+ ///
+ [HttpPost]
+ public async Task> Create([FromBody, BindRequired] UserDto dto)
+ {
+ try
+ {
+ await using var transaction = _context.Database.BeginTransaction();
+
+ var userId = this.GetIdOfCurrentUser();
+
+ var organization = await _context.Organizations.FindAsync(dto.OrganizationId);
+ if (organization == null)
+ throw new ValidationException($@"没有 {nameof(dto.OrganizationId)} 为 {dto.OrganizationId} 的组织");
+
+ // 映射普通属性
+ var model = dto.ToModel();
+
+ model.Password = HashPassword("12345678");
+ model.OrganizationId = organization.Id;
+ model.CreatorId = userId;
+ await _context.Users.AddAsync(model);
+
+ // 分配角色
+ _context.Users.Add(model);
+ User_Role user_Role1 = new User_Role
+ {
+ Id = Guid.NewGuid().ToString(),
+ UserId = userId,
+ RoleId = _context.Roles.Where(r => r.Name == "普通人员").ToList()[0].Id,
+ CreationTime = DateTime.Now,
+ };
+ _context.User_Roles.Add(user_Role1);
+
+ await _context.SaveChangesAsync();
+ await transaction.CommitAsync();
+
+ if (model is null) return BadRequest();
+ var result = model.ToDto();
+
+ return CreatedAtAction(nameof(Create), result);
+ }
+ catch (ValidationException e)
+ {
+ return ValidationProblem(e.Message);
+ }
+ catch (Exception e)
+ {
+ return Problem(e.Message);
+ }
+ }
+
+ ///
+ /// 获取用户列表
+ ///
+ ///
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [HttpGet]
+ public async Task>> GetAll()
+ {
+ var query = _context.Users.Where(a=>a.IsDeleted==false).ToList();
+
+ return Ok(query);
+ }
+
+ ///
+ /// 获取用户
+ ///
+ /// 用户 ID
+ ///
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpGet("{id}")]
+ public async Task> GetOne([FromRoute] string id)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 修改用户
+ ///
+ /// 用户 ID
+ /// 更新内容
+ ///
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpPatch("{id}")]
+ public async Task> Update([FromRoute] string id, [FromBody, BindRequired] UserDto dto)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 重置用户密码
+ ///
+ /// 用户 ID
+ ///
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpPatch("{id}/Password")]
+ public async Task> ResetPassword([FromRoute] string id)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 删除用户
+ ///
+ /// 用户 ID
+ ///
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpDelete("{id}")]
+ public async Task Delete([FromRoute] string id)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion APIs
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Data/ApplicationDbContext.cs b/AX.WebDrillServer/Data/ApplicationDbContext.cs
new file mode 100644
index 0000000..26b6041
--- /dev/null
+++ b/AX.WebDrillServer/Data/ApplicationDbContext.cs
@@ -0,0 +1,30 @@
+using AX.WebDrillServer.EntityConfigurations;
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace AX.WebDrillServer.Data
+{
+ public class ApplicationDbContext : DbContext
+ {
+ public ApplicationDbContext(DbContextOptions options)
+ : base(options)
+ { }
+
+ public DbSet Users { get; set; } = null!;
+ public DbSet Organizations { get; set; } = null!;
+ public DbSet Roles { get; set; } = null!;
+ public DbSet Premissions { get; set; } = null!;
+ public DbSet User_Roles { get; set; } = null!;
+ public DbSet Role_Premission { get; set; } = null!;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.ApplyConfiguration(new UserConfiguration());
+ modelBuilder.ApplyConfiguration(new OrganizationConfiguration());
+ modelBuilder.ApplyConfiguration(new RoleConfiguration());
+ modelBuilder.ApplyConfiguration(new PremissionConfiguration());
+ modelBuilder.ApplyConfiguration(new User_RoleConfiguration());
+ modelBuilder.ApplyConfiguration(new Role_PremissionConfiguration());
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Data/ApplicationDbContextExtensions.cs b/AX.WebDrillServer/Data/ApplicationDbContextExtensions.cs
new file mode 100644
index 0000000..400b700
--- /dev/null
+++ b/AX.WebDrillServer/Data/ApplicationDbContextExtensions.cs
@@ -0,0 +1,32 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace AX.WebDrillServer.Data
+{
+ public static class ApplicationDbContextExtensions
+ {
+ ///
+ /// 运行迁移
+ ///
+ ///
+ ///
+ public static IApplicationBuilder SetupDatabase(this WebApplication app)
+ {
+ using var scope = app.Services.CreateScope();
+ var serviceProvidoer = scope.ServiceProvider;
+ try
+ {
+ var context = serviceProvidoer.GetRequiredService();
+ context.Database.Migrate();
+ //context.Database.EnsureCreated();
+ context.SeedDefaultData();
+ }
+ catch (Exception e)
+ {
+ var logger = serviceProvidoer.GetRequiredService>();
+ logger.LogError(e, "在设置数据库过程中发生了错误!");
+ }
+
+ return app;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Data/ApplicationDbContextSeed.cs b/AX.WebDrillServer/Data/ApplicationDbContextSeed.cs
new file mode 100644
index 0000000..1fd5285
--- /dev/null
+++ b/AX.WebDrillServer/Data/ApplicationDbContextSeed.cs
@@ -0,0 +1,170 @@
+using AX.WebDrillServer.Enums;
+using AX.WebDrillServer.Models;
+using static BCrypt.Net.BCrypt;
+
+namespace AX.WebDrillServer.Data
+{
+ public enum RoleTypes
+ {
+ 超级管理员 = -1,
+ 普通人员 = 0,
+ 访客,
+ }
+ public static class ApplicationDbContextSeed
+ {
+ public const string SuperAdminId = "0";
+ public const string InitUserId = "1";
+ public const string InitPassword = "123456678";
+ private const int InitUserCount = 6;
+ public static void SeedDefaultData(this ApplicationDbContext context)
+ {
+ // 组织机构
+ if (!context.Organizations.Where(e => e.Code == "1").Any())
+ context.SeedOrganizations();
+ // 角色
+ if (!context.Roles.Where(e => e.Name == RoleTypes.超级管理员.ToString()).Any())
+ context.SeedRoles();
+ // 超级管理员
+ if (!context.Users.Where(e => e.Id == SuperAdminId).Any())
+ context.SeedSuperAdmin();
+ // 测试用户
+ if (!context.Users.Where(e => e.Id == InitUserId).Any())
+ context.SeedTestUsers();
+ // 测试用户角色
+ if (!context.User_Roles.Where(e => e.UserId == SuperAdminId).Any())
+ context.SeedUser_Roles();
+ }
+
+ private static void SeedRoles(this ApplicationDbContext context)
+ {
+ using var transaction = context.Database.BeginTransaction();
+ List roles = new List();
+ var role = new Role()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Name = RoleTypes.超级管理员.ToString(),
+ Introductions = "这是超级管理员",
+ };
+ var role1 = new Role()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Name = RoleTypes.普通人员.ToString(),
+ Introductions = "这是普通人员",
+ };
+ var role2 = new Role()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Name = RoleTypes.访客.ToString(),
+ Introductions = "这是访客",
+ };
+ roles.Add(role);
+ roles.Add(role1);
+ roles.Add(role2);
+ context.Roles.AddRange(roles);
+ context.SaveChanges();
+ transaction.Commit();
+ }
+ private static void SeedOrganizations(this ApplicationDbContext context)
+ {
+ using var transaction = context.Database.BeginTransaction();
+ List orgs = new List();
+ var org = new Organization()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Code = "1",
+ Name = "部局",
+ Level = OrganizationLevel.Department,
+ };
+ var org1 = new Organization()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Code = "1-1",
+ Name = "XXX总队",
+ Level = OrganizationLevel.Corps,
+ };
+ var org2 = new Organization()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Code = "1-1-1",
+ Name = "XXX大队",
+ Level = OrganizationLevel.Battalion,
+ };
+ var org3 = new Organization()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Code = "1-1-1-1",
+ Name = "XXX支队",
+ Level = OrganizationLevel.Brigade,
+ };
+ var org4 = new Organization()
+ {
+ Id = Guid.NewGuid().ToString(),
+ Code = "1-1-1-1-1",
+ Name = "XXX中队",
+ Level = OrganizationLevel.Squadron,
+ };
+ orgs.Add(org);
+ orgs.Add(org1);
+ orgs.Add(org2);
+ orgs.Add(org3);
+ orgs.Add(org4);
+ context.Organizations.AddRange(orgs);
+ context.SaveChanges();
+ transaction.Commit();
+ }
+ private static void SeedSuperAdmin(this ApplicationDbContext context)
+ {
+ using var transaction = context.Database.BeginTransaction();
+ User u = new User
+ {
+ Id = SuperAdminId,
+ UserName = "Admin",
+ Password = HashPassword(InitPassword),
+ };
+ context.Users.Add(u);
+ context.SaveChanges();
+ transaction.Commit();
+ }
+
+ private static void SeedTestUsers(this ApplicationDbContext context)
+ {
+ using var transaction = context.Database.BeginTransaction();
+ for (int i = 1; i <= InitUserCount; i++)
+ {
+ User u = new()
+ {
+ Id = i == 1 ? InitUserId : Guid.NewGuid().ToString(),
+ UserName = $"用户{i}",
+ Password = HashPassword(InitPassword),
+ };
+ context.Users.Add(u);
+ }
+ context.SaveChanges();
+ transaction.Commit();
+ }
+
+ private static void SeedUser_Roles(this ApplicationDbContext context)
+ {
+ using var transaction = context.Database.BeginTransaction();
+ User_Role user_Role = new User_Role
+ {
+ Id = Guid.NewGuid().ToString(),
+ UserId = SuperAdminId,
+ RoleId = context.Roles.Where(r => r.Name == RoleTypes.超级管理员.ToString()).ToList()[0].Id,
+ };
+ context.User_Roles.Add(user_Role);
+ for (int i = 1; i <= InitUserCount; i++)
+ {
+ User_Role user_Role1 = new User_Role
+ {
+ Id = Guid.NewGuid().ToString(),
+ UserId = context.Users.Where(r => r.UserName == $"用户{i}").ToList()[0].Id,
+ RoleId = context.Roles.Where(r => r.Name == (i % 2 == 0 ? RoleTypes.普通人员.ToString() : RoleTypes.访客.ToString())).ToList()[0].Id,
+ };
+ context.User_Roles.Add(user_Role1);
+ }
+ context.SaveChanges();
+ transaction.Commit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Dtos/DtoBase.cs b/AX.WebDrillServer/Dtos/DtoBase.cs
new file mode 100644
index 0000000..47ff431
--- /dev/null
+++ b/AX.WebDrillServer/Dtos/DtoBase.cs
@@ -0,0 +1,41 @@
+using System.Text.Json.Serialization;
+
+namespace AX.WebDrillServer.Dtos
+{
+ ///
+ /// DTO 基类
+ ///
+ public class DtoBase
+ {
+ ///
+ /// ID
+ ///
+ [JsonPropertyOrder(-1)]
+ public string Id { get; set; } = null!;
+
+ ///
+ /// 创建时间
+ ///
+ [JsonPropertyOrder(1)]
+ public DateTime? CreationTime { get; set; }
+
+ ///
+ /// 创建者 ID
+ ///
+ [JsonPropertyOrder(2)]
+
+ public string? CreatorId { get; set; }
+
+ ///
+ /// 最后修改时间
+ ///
+ [JsonPropertyOrder(3)]
+ public DateTime? LastModificationTime { get; set; }
+
+ ///
+ /// 最后修改者 ID
+ ///
+ [JsonPropertyOrder(4)]
+ public string? LastModifierId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Dtos/Organizations/OrganizationDto.cs b/AX.WebDrillServer/Dtos/Organizations/OrganizationDto.cs
new file mode 100644
index 0000000..69f5945
--- /dev/null
+++ b/AX.WebDrillServer/Dtos/Organizations/OrganizationDto.cs
@@ -0,0 +1,11 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace AX.WebDrillServer.Dtos.Organizations
+{
+ public class OrganizationDto : DtoBase
+ {
+ public string? Code { get; set; }
+ public string? Name { get; set; }
+ public string? Introductions { get; set; }
+ }
+}
diff --git a/AX.WebDrillServer/Dtos/Pagination.cs b/AX.WebDrillServer/Dtos/Pagination.cs
new file mode 100644
index 0000000..9e010ea
--- /dev/null
+++ b/AX.WebDrillServer/Dtos/Pagination.cs
@@ -0,0 +1,34 @@
+namespace AX.WebDrillServer.Dtos
+{
+ ///
+ /// 分页结果集。
+ ///
+ /// DtoBase
+ public class Pagination where T : DtoBase
+ {
+ ///
+ /// 当前页。
+ ///
+ public int PageNumber { get; set; }
+
+ ///
+ /// 每页个数。
+ ///
+ public int PageSize { get; set; }
+
+ ///
+ /// 总页数。
+ ///
+ public int TotalPages { get; set; }
+
+ ///
+ /// 总个数。
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 查询集合。
+ ///
+ public IEnumerable? Items { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Dtos/QueryOptions.cs b/AX.WebDrillServer/Dtos/QueryOptions.cs
new file mode 100644
index 0000000..22a7a46
--- /dev/null
+++ b/AX.WebDrillServer/Dtos/QueryOptions.cs
@@ -0,0 +1,30 @@
+using AX.WebDrillServer.Enums;
+
+namespace AX.WebDrillServer.Dtos
+{
+ ///
+ /// 查询条件。
+ ///
+ public class QueryOptionsBase
+ {
+ ///
+ /// 分页页数。
+ ///
+ public int? PageNumber { get; set; }
+
+ ///
+ /// 一页多少条。
+ ///
+ public int? PageSize { get; set; }
+
+ ///
+ /// 排序属性。
+ ///
+ public string? SortProperty { get; set; }
+
+ ///
+ /// 排序类型
+ ///
+ public SortType? SortType { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Dtos/Users/UserDto.cs b/AX.WebDrillServer/Dtos/Users/UserDto.cs
new file mode 100644
index 0000000..a76351b
--- /dev/null
+++ b/AX.WebDrillServer/Dtos/Users/UserDto.cs
@@ -0,0 +1,33 @@
+namespace AX.WebDrillServer.Dtos.Users
+{
+ ///
+ /// 用户
+ ///
+ public class UserDto : DtoBase
+ {
+ ///
+ /// 用户名
+ ///
+ public string Username { get; set; } = null!;
+
+ ///
+ /// 名称
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// 是否已启用
+ ///
+ public bool? Enabled { get; set; }
+
+ ///
+ /// 组织机构 ID
+ ///
+ public string? OrganizationId { get; set; }
+
+ ///
+ /// 组织机构名称
+ ///
+ public string? OrganizationName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/EntityConfigurations/EntityBaseConfiguration.cs b/AX.WebDrillServer/EntityConfigurations/EntityBaseConfiguration.cs
new file mode 100644
index 0000000..3bb5fb0
--- /dev/null
+++ b/AX.WebDrillServer/EntityConfigurations/EntityBaseConfiguration.cs
@@ -0,0 +1,19 @@
+using AX.WebDrillServer.Middlewares;
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace AX.WebDrillServer.EntityConfigurations
+{
+ public class EntityBaseConfiguration : IEntityTypeConfiguration where TEntity : EntityBase
+ {
+ public virtual void Configure(EntityTypeBuilder builder)
+ {
+ builder.Property(e => e.Id)
+ .IsUnicode(false)
+ .HasValueGenerator();
+
+ builder.HasQueryFilter(e => !e.IsDeleted);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/EntityConfigurations/OrganizationConfiguration.cs b/AX.WebDrillServer/EntityConfigurations/OrganizationConfiguration.cs
new file mode 100644
index 0000000..ac2e26b
--- /dev/null
+++ b/AX.WebDrillServer/EntityConfigurations/OrganizationConfiguration.cs
@@ -0,0 +1,13 @@
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace AX.WebDrillServer.EntityConfigurations
+{
+ public class OrganizationConfiguration : EntityBaseConfiguration
+ {
+ public override void Configure(EntityTypeBuilder builder)
+ {
+ base.Configure(builder);
+ }
+ }
+}
diff --git a/AX.WebDrillServer/EntityConfigurations/PremissionConfiguration.cs b/AX.WebDrillServer/EntityConfigurations/PremissionConfiguration.cs
new file mode 100644
index 0000000..17244f5
--- /dev/null
+++ b/AX.WebDrillServer/EntityConfigurations/PremissionConfiguration.cs
@@ -0,0 +1,17 @@
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace AX.WebDrillServer.EntityConfigurations
+{
+ public class PremissionConfiguration:EntityBaseConfiguration
+ {
+ public override void Configure(EntityTypeBuilder builder)
+ {
+ base.Configure(builder);
+
+ //builder.HasMany(u => u.Roles)
+ // .WithMany(r => r.Users)
+ // .UsingEntity();
+ }
+ }
+}
diff --git a/AX.WebDrillServer/EntityConfigurations/RoleConfiguration.cs b/AX.WebDrillServer/EntityConfigurations/RoleConfiguration.cs
new file mode 100644
index 0000000..90d209a
--- /dev/null
+++ b/AX.WebDrillServer/EntityConfigurations/RoleConfiguration.cs
@@ -0,0 +1,17 @@
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace AX.WebDrillServer.EntityConfigurations
+{
+ public class RoleConfiguration : EntityBaseConfiguration
+ {
+ public override void Configure(EntityTypeBuilder builder)
+ {
+ base.Configure(builder);
+
+ //builder.HasMany(u => u.Roles)
+ // .WithMany(r => r.Users)
+ // .UsingEntity();
+ }
+ }
+}
diff --git a/AX.WebDrillServer/EntityConfigurations/Role_PremissionConfiguration.cs b/AX.WebDrillServer/EntityConfigurations/Role_PremissionConfiguration.cs
new file mode 100644
index 0000000..fdba4ef
--- /dev/null
+++ b/AX.WebDrillServer/EntityConfigurations/Role_PremissionConfiguration.cs
@@ -0,0 +1,17 @@
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace AX.WebDrillServer.EntityConfigurations
+{
+ public class Role_PremissionConfiguration:EntityBaseConfiguration
+ {
+ public override void Configure(EntityTypeBuilder builder)
+ {
+ base.Configure(builder);
+
+ //builder.HasMany(u => u.Roles)
+ // .WithMany(r => r.Users)
+ // .UsingEntity();
+ }
+ }
+}
diff --git a/AX.WebDrillServer/EntityConfigurations/UserConfiguration.cs b/AX.WebDrillServer/EntityConfigurations/UserConfiguration.cs
new file mode 100644
index 0000000..51113e6
--- /dev/null
+++ b/AX.WebDrillServer/EntityConfigurations/UserConfiguration.cs
@@ -0,0 +1,17 @@
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace AX.WebDrillServer.EntityConfigurations
+{
+ public class UserConfiguration : EntityBaseConfiguration
+ {
+ public override void Configure(EntityTypeBuilder builder)
+ {
+ base.Configure(builder);
+
+ //builder.HasMany(u => u.Roles)
+ // .WithMany(r => r.Users)
+ // .UsingEntity();
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/EntityConfigurations/User_RoleConfiguration.cs b/AX.WebDrillServer/EntityConfigurations/User_RoleConfiguration.cs
new file mode 100644
index 0000000..cd89b20
--- /dev/null
+++ b/AX.WebDrillServer/EntityConfigurations/User_RoleConfiguration.cs
@@ -0,0 +1,17 @@
+using AX.WebDrillServer.Models;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace AX.WebDrillServer.EntityConfigurations
+{
+ public class User_RoleConfiguration:EntityBaseConfiguration
+ {
+ public override void Configure(EntityTypeBuilder builder)
+ {
+ base.Configure(builder);
+
+ //builder.HasMany(u => u.Roles)
+ // .WithMany(r => r.Users)
+ // .UsingEntity();
+ }
+ }
+}
diff --git a/AX.WebDrillServer/EntityMappers/EntityMapperConfig.cs b/AX.WebDrillServer/EntityMappers/EntityMapperConfig.cs
new file mode 100644
index 0000000..98e8b38
--- /dev/null
+++ b/AX.WebDrillServer/EntityMappers/EntityMapperConfig.cs
@@ -0,0 +1,41 @@
+using AX.WebDrillServer.Dtos;
+using AX.WebDrillServer.EntityConfigurations;
+using AX.WebDrillServer.Extensions;
+using AX.WebDrillServer.Models;
+using Mapster;
+using System.Linq.Expressions;
+
+namespace AX.WebDrillServer.EntityMappers
+{
+ internal static partial class EntityMapperConfig
+ {
+ public static void Initialize()
+ {
+#if DEBUG
+ // 如需开启 mapper 内的步进调试, 需安装 ExpressionDebugger 并启用此行;
+ TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileWithDebugInfo();
+#endif
+
+ TypeAdapterConfig.GlobalSettings.Default.IgnoreNullValues(true);
+ // 允许 Mapster 把 目标类型的 映射配置 应用到 目标类型的子类。
+ TypeAdapterConfig.GlobalSettings.AllowImplicitDestinationInheritance = true;
+
+ TypeAdapterConfig
+ .ForType()
+ .Ignore(dest => dest.CreationTime)
+ .Map(dest => dest.LastModificationTime,
+ src => src.LastModificationTime.ToUtc());
+
+ TypeAdapterConfig
+ .ForType()
+ .Map(dest => dest.CreationTime,
+ src => src.CreationTime.ToLocalTime())
+ .Map(dest => dest.LastModificationTime,
+ src => src.LastModificationTime.ToLocal());
+
+ //映射配置
+ UserMapperConfig.Initialize();
+ OrginzationConfig.Initialize();
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/EntityMappers/Mappers.cs b/AX.WebDrillServer/EntityMappers/Mappers.cs
new file mode 100644
index 0000000..fb8a6b8
--- /dev/null
+++ b/AX.WebDrillServer/EntityMappers/Mappers.cs
@@ -0,0 +1,55 @@
+using AX.WebDrillServer.Dtos.Users;
+using AX.WebDrillServer.Models;
+using Mapster;
+
+namespace AX.WebDrillServer.EntityMappers
+{
+ public static class UserMapper
+ {
+ public static User ToModel(this UserDto dto) =>
+ dto.Adapt();
+
+ public static UserDto ToDto(this User model) =>
+ model.Adapt();
+
+ public static void MapTo(this UserDto dto, User model) =>
+ dto.Adapt(model);
+ }
+
+ public static class UserMapperConfig
+ {
+ public static void Initialize()
+ {
+ //TypeAdapterConfig
+ // .ForType()
+ // .Map(dest => dest.OrganizationName,
+ // src => src.Organization != default ?
+ // src.Organization.Name :
+ // default);
+
+ //TypeAdapterConfig
+ // .ForType()
+ // .Map(dest => dest.Name,
+ // src => src.Name);
+
+ }
+ }
+ public static class OrginzationConfig
+ {
+ public static void Initialize()
+ {
+ //TypeAdapterConfig
+ // .ForType()
+ // .Map(dest => dest.OrganizationName,
+ // src => src.Organization != default ?
+ // src.Organization.Name :
+ // default);
+
+ //TypeAdapterConfig
+ // .ForType()
+ // .Map(dest => dest.Name,
+ // src => src.Name);
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Enums/OrganizationLevel.cs b/AX.WebDrillServer/Enums/OrganizationLevel.cs
new file mode 100644
index 0000000..007ae5a
--- /dev/null
+++ b/AX.WebDrillServer/Enums/OrganizationLevel.cs
@@ -0,0 +1,33 @@
+namespace AX.WebDrillServer.Enums
+{
+ ///
+ /// 组织机构级别(corps: 总队; brigade: 支队; battalion: 大队; squadron: 消防站)
+ ///
+ public enum OrganizationLevel
+ {
+ ///
+ /// 部局。
+ ///
+ Department = -1,
+
+ ///
+ /// 总队。
+ ///
+ Corps = 0,
+
+ ///
+ /// 支队。
+ ///
+ Brigade = 1,
+
+ ///
+ /// 大队。
+ ///
+ Battalion = 2,
+
+ ///
+ /// 中队。
+ ///
+ Squadron = 3
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Enums/SortType.cs b/AX.WebDrillServer/Enums/SortType.cs
new file mode 100644
index 0000000..cf2775b
--- /dev/null
+++ b/AX.WebDrillServer/Enums/SortType.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+
+namespace AX.WebDrillServer.Enums
+{
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum SortType
+ {
+ ASC,
+ DESC
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Errors/GlobalErrorCodes.cs b/AX.WebDrillServer/Errors/GlobalErrorCodes.cs
new file mode 100644
index 0000000..96f32d9
--- /dev/null
+++ b/AX.WebDrillServer/Errors/GlobalErrorCodes.cs
@@ -0,0 +1,27 @@
+namespace AX.WebDrillServer.Errors
+{
+ ///
+ /// 表示错误码。
+ ///
+ ///
+ /// 约定 60x 系列属于许可证错误;
+ /// 约定 70x 系列属于文件错误;
+ ///
+ public static class GlobalErrorCodes
+ {
+ /// 程序未经许可或许可证到期!
+ public const int E600 = 600;
+
+ /// 上传文件失败,因为未通过数据完整性校验,文件数据有可能丢失或被篡改!
+ public const int E700 = 700;
+
+ ///
+ /// 错误码相对应的消息。
+ ///
+ public static readonly Dictionary Messages = new()
+ {
+ { E600, "程序未经许可或许可证到期!" },
+ { E700, "上传文件失败,因为未通过数据完整性校验,文件数据有可能丢失或被篡改!" }
+ };
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Extensions/CollectionExtensions.cs b/AX.WebDrillServer/Extensions/CollectionExtensions.cs
new file mode 100644
index 0000000..530e8b7
--- /dev/null
+++ b/AX.WebDrillServer/Extensions/CollectionExtensions.cs
@@ -0,0 +1,33 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace AX.WebDrillServer.Extensions
+{
+ public static class CollectionExtensions
+ {
+ #region Collection
+
+ ///
+ /// 判断集合是否是 Null 或 Empty.
+ ///
+ ///
+ ///
+ public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? enumerable)
+ {
+ if (enumerable is null) return true;
+ return !enumerable.Any();
+ }
+
+ ///
+ /// 判断集合是否是有值.
+ ///
+ ///
+ ///
+ public static bool HasValue([NotNullWhen(false)] this IEnumerable? enumerable)
+ {
+ if (enumerable is null) return false;
+ return enumerable.Any();
+ }
+
+ #endregion Collection
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Extensions/ControllerExtensions.cs b/AX.WebDrillServer/Extensions/ControllerExtensions.cs
new file mode 100644
index 0000000..f09982f
--- /dev/null
+++ b/AX.WebDrillServer/Extensions/ControllerExtensions.cs
@@ -0,0 +1,92 @@
+using AX.WebDrillServer.Dtos;
+using AX.WebDrillServer.Enums;
+using AX.WebDrillServer.Middlewares.Jwts;
+using AX.WebDrillServer.Models;
+using Mapster;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System.Linq.Expressions;
+using System.Security.Claims;
+
+namespace AX.WebDrillServer.Extensions
+{
+ public static class ControllerExtensions
+ {
+ ///
+ /// 排序
+ ///
+ /// 实体类型
+ /// IQueryable
+ /// 排序所依据的属性
+ /// 升序或降序
+ ///
+ public static IOrderedQueryable Sort(
+ this IQueryable query,
+ string sortProperty = "Id",
+ SortType? sortType = SortType.ASC) where T : EntityBase
+ {
+ var param = Expression.Parameter(typeof(T), "x");
+ var body = sortProperty.Split('.').Aggregate(param, Expression.PropertyOrField);
+ var methodName = sortType switch
+ {
+ SortType.DESC => "OrderByDescending",
+ _ => "OrderBy"
+ };
+
+ return (IOrderedQueryable)query.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable),
+ methodName,
+ new[] { typeof(T), body.Type },
+ query.Expression,
+ Expression.Lambda(body, param))
+ );
+ }
+
+ ///
+ /// 分页
+ ///
+ /// GET 用 DTO
+ /// IQueryable
+ /// 页数
+ /// 每页项数
+ ///
+ public static async ValueTask> PaginateAsync(
+ this IQueryable query,
+ int? number,
+ int? size) where TDto : DtoBase
+ {
+ number ??= 1;
+ size ??= 10;
+ var count = await query.CountAsync();
+ if (number > 1)
+ query = query.Skip((number.Value - 1) * size.Value);
+ query = query.Take(size.Value);
+
+ var items = await query.Select(e => e.Adapt()).ToListAsync();
+
+ var result = new Pagination
+ {
+ PageNumber = number.Value,
+ PageSize = size.Value,
+ TotalPages = (int)Math.Ceiling((double)count / size.Value),
+ TotalCount = count,
+ Items = items
+ };
+
+ return result;
+ }
+
+ public static string GetIdOfCurrentUser(this ControllerBase controller) =>
+ controller.HttpContext.User.FindFirstValue(JwtClaimTypes.Subject);
+
+ public static string GetNameOfCurrentUser(this ControllerBase controller) =>
+ controller.HttpContext.User.FindFirstValue(JwtClaimTypes.Name);
+
+ public static string GetOrgIdOfCurrentUser(this ControllerBase controller) =>
+ controller.HttpContext.User.FindFirstValue(JwtClaimTypes.OrganizationId);
+
+ public static string GetOrgCodeOfCurrentUser(this ControllerBase controller) =>
+ controller.HttpContext.User.FindFirstValue(JwtClaimTypes.OrganizationCode);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Extensions/DateTimeExtensions.cs b/AX.WebDrillServer/Extensions/DateTimeExtensions.cs
new file mode 100644
index 0000000..9a8520f
--- /dev/null
+++ b/AX.WebDrillServer/Extensions/DateTimeExtensions.cs
@@ -0,0 +1,51 @@
+namespace AX.WebDrillServer.Extensions
+{
+ public static class DateTimeExtensions
+ {
+ ///
+ /// 转换为 UTC 时间
+ ///
+ /// DateTime 值
+ /// 转换后的 UTC 时间
+ public static DateTime? ToUtc(this DateTime? dateTime)
+ {
+ if (!dateTime.HasValue) return null;
+ return dateTime.Value.Kind != DateTimeKind.Utc ?
+ dateTime.Value.ToUniversalTime() :
+ dateTime.Value;
+ }
+
+ ///
+ /// 转换为 UTC 时间
+ ///
+ /// DateTime 值
+ /// 转换后的 UTC 时间
+ public static DateTime ToUtc(this DateTime dateTime) =>
+ dateTime.Kind != DateTimeKind.Utc ?
+ dateTime.ToUniversalTime() :
+ dateTime;
+
+ ///
+ /// 转换为本地时间
+ ///
+ /// DateTime 值
+ /// 转换后的本地时间
+ public static DateTime? ToLocal(this DateTime? dateTime)
+ {
+ if (!dateTime.HasValue) return null;
+ return dateTime.Value.Kind == DateTimeKind.Utc ?
+ dateTime.Value.ToLocalTime() :
+ dateTime.Value;
+ }
+
+ ///
+ /// 转换为本地时间
+ ///
+ /// DateTime 值
+ /// 转换后的本地时间
+ public static DateTime ToLocal(this DateTime dateTime) =>
+ dateTime.Kind == DateTimeKind.Utc ?
+ dateTime.ToLocalTime() :
+ dateTime;
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Extensions/EntityExtensions.cs b/AX.WebDrillServer/Extensions/EntityExtensions.cs
new file mode 100644
index 0000000..b49c411
--- /dev/null
+++ b/AX.WebDrillServer/Extensions/EntityExtensions.cs
@@ -0,0 +1,14 @@
+using AX.WebDrillServer.Models;
+
+namespace AX.WebDrillServer.Extensions
+{
+ public static class EntityExtensions
+ {
+ public static void Delete(this IEntityBase entity, string? userId)
+ {
+ entity.DeletionTime = DateTime.UtcNow;
+ entity.DeleterId = userId;
+ entity.IsDeleted = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Extensions/HubExtensions.cs b/AX.WebDrillServer/Extensions/HubExtensions.cs
new file mode 100644
index 0000000..5567b88
--- /dev/null
+++ b/AX.WebDrillServer/Extensions/HubExtensions.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.SignalR;
+
+namespace AX.WebDrillServer.Extensions
+{
+ public static class HubExtensions
+ {
+ public static string? GetNameOfCurrentUser(this HubCallerContext context) =>
+ context.User?.Identity?.Name;
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Extensions/JsonExtensions.cs b/AX.WebDrillServer/Extensions/JsonExtensions.cs
new file mode 100644
index 0000000..fe36fdd
--- /dev/null
+++ b/AX.WebDrillServer/Extensions/JsonExtensions.cs
@@ -0,0 +1,10 @@
+using System.Text.Json;
+
+namespace AX.WebDrillServer.Extensions
+{
+ public static class JsonExtensions
+ {
+ public static string ToJson(this object? obj) =>
+ JsonSerializer.Serialize(obj);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Extensions/StringExtensions.cs b/AX.WebDrillServer/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..4e03b84
--- /dev/null
+++ b/AX.WebDrillServer/Extensions/StringExtensions.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+
+namespace AX.WebDrillServer.Extensions
+{
+ public static class StringExtensions
+ {
+ public static bool IsNullOrEmpty([NotNullWhen(false)] this string? str) =>
+ string.IsNullOrEmpty(str);
+
+ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? str) =>
+ string.IsNullOrWhiteSpace(str);
+
+ public static bool HasValue([NotNullWhen(false)] this string? str) =>
+ !string.IsNullOrWhiteSpace(str);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Helpers/DateTimeHelper.cs b/AX.WebDrillServer/Helpers/DateTimeHelper.cs
new file mode 100644
index 0000000..405fa9f
--- /dev/null
+++ b/AX.WebDrillServer/Helpers/DateTimeHelper.cs
@@ -0,0 +1,30 @@
+using AX.WebDrillServer.Extensions;
+
+namespace AX.WebDrillServer.Helpers
+{
+ public class DateTimeHelper
+ {
+ public static (DateTime StartTime, DateTime EndTime) GetCurrentMonth()
+ {
+ var s = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).ToUtc();
+ var e = s.AddMonths(1);
+ return (s, e);
+ }
+
+ public static int GetTargetCount(DateTime month)
+ {
+ month = month.ToUtc();
+ int targetCount = 0;
+ var startDate = new DateTime(month.Year, month.Month, 1).ToUtc();
+ for (DateTime i = startDate; i < startDate.AddMonths(1); i = i.AddDays(1))
+ {
+ if (i.DayOfWeek == DayOfWeek.Tuesday || i.DayOfWeek == DayOfWeek.Thursday)
+ {
+ targetCount++;
+ }
+ }
+ return targetCount;
+ }
+
+ }
+}
diff --git a/AX.WebDrillServer/Helpers/ExcelHelper.cs b/AX.WebDrillServer/Helpers/ExcelHelper.cs
new file mode 100644
index 0000000..20c8846
--- /dev/null
+++ b/AX.WebDrillServer/Helpers/ExcelHelper.cs
@@ -0,0 +1,92 @@
+using NPOI.HSSF.UserModel;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+using System.Data;
+
+namespace AX.WebDrillServer.Helpers
+{
+ public class ExcelHelper
+ {
+ ///
+ /// 将excel中的数据导入到DataTable中
+ ///
+ ///
+ /// excel文件路径
+ /// 第一行是否是DataTable的列名
+ /// 返回的DataTable
+ public static DataTable ExcelToDataTable(Stream fileStream, string fileName, bool isFirstRowColumn)
+ {
+ ISheet sheet;
+ DataTable data = new DataTable();
+ int startRow = 0;
+ try
+ {
+
+ IWorkbook workbook;
+ if (fileName.IndexOf(".xlsx") > 0) // 2007版本
+ workbook = new XSSFWorkbook(fileStream);
+ else if (fileName.IndexOf(".xls") > 0) // 2003版本
+ workbook = new HSSFWorkbook(fileStream);
+ else
+ return data;
+
+
+ sheet = workbook.GetSheetAt(0);
+ if (sheet != null)
+ {
+ IRow firstRow = sheet.GetRow(0);
+ int cellCount = firstRow.LastCellNum; //一行最后一个cell的编号 即总的列数
+
+ if (isFirstRowColumn)
+ {
+ for (int i = firstRow.FirstCellNum; i < cellCount; ++i)
+ {
+ ICell cell = firstRow.GetCell(i);
+ if (cell != null)
+ {
+ string cellValue = cell.StringCellValue;
+ if (cellValue != null)
+ {
+ DataColumn column = new DataColumn(cellValue);
+ data.Columns.Add(column);
+ }
+ }
+ }
+ startRow = sheet.FirstRowNum + 1;
+ }
+ else
+ {
+ for (int i = firstRow.FirstCellNum; i < cellCount; i++)
+ {
+ DataColumn column = new DataColumn(i.ToString());
+ data.Columns.Add(column);
+ }
+ startRow = sheet.FirstRowNum;
+ }
+
+ //最后一列的标号
+ int rowCount = sheet.LastRowNum;
+ for (int i = startRow; i <= rowCount; ++i)
+ {
+ IRow row = sheet.GetRow(i);
+ if (row == null) continue; //没有数据的行默认是null
+
+ DataRow dataRow = data.NewRow();
+ for (int j = row.FirstCellNum; j < cellCount; ++j)
+ {
+ if (row.GetCell(j) != null) //同理,没有数据的单元格都默认是null
+ dataRow[j] = row.GetCell(j).ToString();
+ }
+ data.Rows.Add(dataRow);
+ }
+ }
+
+ return data;
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+ }
+}
diff --git a/AX.WebDrillServer/Helpers/OrganizationHelper.cs b/AX.WebDrillServer/Helpers/OrganizationHelper.cs
new file mode 100644
index 0000000..d74aa64
--- /dev/null
+++ b/AX.WebDrillServer/Helpers/OrganizationHelper.cs
@@ -0,0 +1,27 @@
+
+namespace AX.WebDrillServer.Helpers
+{
+ public class OrganizationHelper
+ {
+ ///
+ /// 生成组织机构代码
+ ///
+ /// 父组织代码
+ /// 同级组织代码最大值
+ /// 新的组织机构代码
+ public static string GenerateCode(
+ string? parentCode,
+ string? lastCodeOfSiblings)
+ {
+ parentCode ??= new string('0', 4);
+
+ var lastPartCode = lastCodeOfSiblings == null ?
+ new string('0', 4) :
+ (Convert.ToInt32(lastCodeOfSiblings.Split(new char[1] { '.' })[^1]) + 1).ToString(new string('0', 4));
+
+ var result = parentCode + '.' + lastPartCode;
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Hubs/INotificationClient.cs b/AX.WebDrillServer/Hubs/INotificationClient.cs
new file mode 100644
index 0000000..64f5718
--- /dev/null
+++ b/AX.WebDrillServer/Hubs/INotificationClient.cs
@@ -0,0 +1,29 @@
+
+
+namespace AX.WebDrillServer.Hubs
+{
+ public interface INotificationClient
+ {
+ /////
+ ///// 接收通知
+ /////
+ ///// 通知内容
+ /////
+ //Task ReceiveNotification(string content);
+
+ /////
+ ///// 接收通知
+ /////
+ ///// 接收通知 DTO
+ /////
+ //Task ReceiveNotification(ReceiveNotificationDto dto);
+
+ /////
+ ///// 接收通知
+ /////
+ ///// 发送者名称
+ ///// 通知内容
+ /////
+ //Task ReceiveNotification(string? senderName, string content);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Hubs/ITaskChatClient.cs b/AX.WebDrillServer/Hubs/ITaskChatClient.cs
new file mode 100644
index 0000000..b04ee57
--- /dev/null
+++ b/AX.WebDrillServer/Hubs/ITaskChatClient.cs
@@ -0,0 +1,27 @@
+namespace AX.WebDrillServer.Hubs
+{
+ public interface ITaskChatClient
+ {
+ // ///
+ // /// 接收消息
+ // ///
+ // /// 发送者名称
+ // /// 消息内容
+ // ///
+ // Task ReceiveMessage(string? senderName, string message);
+
+ // ///
+ // /// 接收消息
+ // ///
+ // /// 接收通知 DTO
+ // ///
+ // Task ReceiveMessage(ReceiveMessageDto dto);
+
+ // ///
+ // /// 接收在线列表
+ // ///
+ // /// 在线用户列表
+ // ///
+ // Task ReceiveMembers(IDictionary members);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Hubs/NotificationHub.cs b/AX.WebDrillServer/Hubs/NotificationHub.cs
new file mode 100644
index 0000000..9a3b6a3
--- /dev/null
+++ b/AX.WebDrillServer/Hubs/NotificationHub.cs
@@ -0,0 +1,67 @@
+using AX.WebDrillServer.Middlewares.Jwts;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.SignalR;
+using System.Security.Claims;
+
+namespace AX.WebDrillServer.Hubs
+{
+ [Authorize]
+ public class NotificationHub : Hub
+ {
+ private readonly ILogger _logger;
+
+ public NotificationHub(
+ ILogger logger
+ )
+ {
+ _logger = logger;
+ }
+
+#if !RELEASE
+
+ public async Task SendMessage(string message)
+ {
+ var userId = Context.UserIdentifier;
+ var userName = Context.User?.FindFirstValue(JwtClaimTypes.Name);
+ _logger.LogInformation("{message}", message);
+ //await Clients.All.ReceiveNotification(userName ?? userId, message);
+ }
+
+ public Task CallA(string message, int i)
+ {
+ return Task.CompletedTask;
+ }
+
+#endif
+
+ public override async Task OnConnectedAsync()
+ {
+ try
+ {
+ var userId = Context.UserIdentifier;
+ var userName = Context.User?.FindFirstValue(JwtClaimTypes.Name);
+ _logger.LogInformation("[{userId}][{name}] connected", userId, userName);
+ await base.OnConnectedAsync();
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+
+ public override async Task OnDisconnectedAsync(Exception? exception)
+ {
+ try
+ {
+ var userId = Context.UserIdentifier;
+ var userName = Context.User?.FindFirstValue(JwtClaimTypes.Name);
+ _logger.LogInformation("[{userId}][{name}] disconnected", userId, userName);
+ await base.OnDisconnectedAsync(exception);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Hubs/TaskChatHub.cs b/AX.WebDrillServer/Hubs/TaskChatHub.cs
new file mode 100644
index 0000000..4f6efc5
--- /dev/null
+++ b/AX.WebDrillServer/Hubs/TaskChatHub.cs
@@ -0,0 +1,123 @@
+using AX.WebDrillServer.Data;
+using AX.WebDrillServer.Extensions;
+using AX.WebDrillServer.Middlewares.Jwts;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.SignalR;
+using System.Collections.Concurrent;
+using System.Security.Claims;
+
+namespace AX.WebDrillServer.Hubs
+{
+ [Authorize]
+ public class TaskChatHub : Hub
+ {
+ // key: taskId, value: { key: user.Id, value: user.Name }
+ private static readonly ConcurrentDictionary> _allRoomUsers = new();
+
+ private readonly ApplicationDbContext _dbContext;
+
+ private readonly ILogger _logger;
+
+ public TaskChatHub(
+ ApplicationDbContext dbContext,
+ ILogger logger)
+ {
+ _dbContext = dbContext;
+ _logger = logger;
+ }
+
+ #region APIs
+
+ public async Task SendMessage(string taskId, string message)
+ {
+ var userId = Context.UserIdentifier;
+ await Task.CompletedTask;
+ }
+
+ public async Task AddGroup(string taskId)
+ {
+ //var userId = Context.UserIdentifier;
+ //if (string.IsNullOrWhiteSpace(userId)) throw new HubException(ErrorMessages[InvalidToken]);
+
+ //var chatGroup = await _dbContext.TaskChatGroups.SingleOrDefaultAsync(e => e.PlanTaskId == taskId);
+ //if (chatGroup == null)
+ //{
+ // var task = await _planTasks.FindAsync(taskId);
+ // if (task == null) throw new HubException(ErrorMessages[TaskNotFound]);
+ // chatGroup = await CreateTaskChatGroup(task);
+ //}
+
+ //var userName = Context.GetNameOfCurrentUser();
+ //var thisRoomUsers = _allRoomUsers.GetOrAdd(taskId, new ConcurrentDictionary());
+ //var success = thisRoomUsers.TryAdd(userId, userName ?? "");
+ //if (!success) throw new HubException(ErrorMessages[JoinRoomFailed]);
+ //await Groups.AddToGroupAsync(Context.ConnectionId, taskId);
+ //_logger.LogInformation("[{userId}][{userName}] 加入了聊天室 [{taskId}]", userId, userName, taskId);
+ await Task.CompletedTask;
+ }
+
+ public async Task LeaveRoom(string taskId)
+ {
+ var userId = Context.UserIdentifier;
+ var userName = Context.GetNameOfCurrentUser();
+ //if (string.IsNullOrWhiteSpace(userId)) throw new HubException(ErrorMessages[InvalidToken]);
+
+ var thisRoomUsers = _allRoomUsers.GetValueOrDefault(taskId);
+ if (thisRoomUsers != null)
+ {
+ var success = thisRoomUsers.TryRemove(userId, out var _);
+ if (success) await Groups.RemoveFromGroupAsync(Context.ConnectionId, taskId);
+ }
+
+ _logger.LogInformation("[{userId}][{userName}] 离开了聊天室 [{taskId}]", userId, userName, taskId);
+ }
+
+ public async Task GetRoomMembers(string taskId)
+ {
+ _allRoomUsers.TryGetValue(taskId, out var members);
+ //if (members != null)
+ // await Clients.Caller.ReceiveMembers(members);
+ }
+
+ public override async Task OnConnectedAsync()
+ {
+ try
+ {
+ var userId = Context.UserIdentifier;
+ var userName = Context.GetNameOfCurrentUser();
+ _logger.LogInformation("[{userId}][{name}] connected", userId, userName);
+
+ await base.OnConnectedAsync();
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+
+ public override async Task OnDisconnectedAsync(Exception? exception)
+ {
+ try
+ {
+ var userId = Context.UserIdentifier;
+ var userName = Context.User?.FindFirstValue(JwtClaimTypes.Name);
+ if (userId == null) _logger.LogError("无效的 userId: [{userId}]!", userId);
+
+ if (userId != null)
+ foreach (var room in _allRoomUsers)
+ if (room.Value.ContainsKey(userId))
+ room.Value.TryRemove(userId, out var _);
+
+ await base.OnDisconnectedAsync(exception);
+ _logger.LogInformation("[{userId}][{name}] 断开了连接", userId, userName);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+
+ #endregion APIs
+
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/License.dat b/AX.WebDrillServer/License.dat
new file mode 100644
index 0000000000000000000000000000000000000000..4a4c71a4e4abcc974fd18ba23c0911c6469b240d
GIT binary patch
literal 976
zcmV;>126oJ8BV=f9zEmau18$n&rfkqkU+muEw6`c-!+VWTb4TtZnI7ZTsYGDUStwO
zwz#PF(H*y_aUoEYXQvnW+CD4pjHtX8=iM9w0onqw%MU&oMQO3x8JN$
zy|B;q%Uw7^)gvIGHN
ztMTnBNPQYQO}0$sSXqhu{^Qv}#l<8OvkmMakGI_2eeuGxq)7E|@$pJWUGdO$Zk=aH
z*v~E;gYf2p##@wEOf5+ayZw{eRRb9uNL|6>SVM+1DmMiSQ1h>`%#+_?w+Nwy)v8Ql
zb;VSgFZV`Yzq9Gmdo#3_eGHGB4c;zpdWoQn+&sr-6d@~_gA(je5OVoi6T_OGUp>Qw
zO2!1P9jHo(OE%0m%|Kr$
z5KKvr^17xEp!9hKK_i}Js?PZ|O@e(0?p`+WaF-8MX4|q{WS>DVdFr#g#n#1POWEgM
zDu`cFu)7MCdK-MQm5dRRX&A1d(2@=*J@-qyO~1r?vqtO&z88oslH
z)2qh9PpExNEUwR^wD;?K$og5X7rof8w-d-uh?)>`1REP>tQe`8=kgv%Vn%!Bp)s#3
zEwo*n!qNJBV{(`Pz1F)4H4=rr4JdYJ3S?nYAQ4PqMjP#&mmW9KfUqncZG6sM3~Kq7R}va?Nt%D(N%V=B$vq5m6-w}}WNLnFUMVGQ0pOTkV2;%p)g@Hf&@szOlf-Jg=Zm-=6oNcHKgvXFX*gGFraq
zSShr>>j0N>`4_+M;**_p%lopu=P!R+;}DwWZvboXnbukYF5>2D#!4d=u{D_w*()$p
yxOiKSooL;S=UNF@z!X??8)OME?5+tw!mks*v__K%5+K!V#(obfXU$(HG2v-`L*%~z
literal 0
HcmV?d00001
diff --git a/AX.WebDrillServer/Middlewares/AESHelper.cs b/AX.WebDrillServer/Middlewares/AESHelper.cs
new file mode 100644
index 0000000..c4bf7b7
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/AESHelper.cs
@@ -0,0 +1,53 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// AES 256bit 加解密辅助类。
+ ///
+ public static class AESHelper
+ {
+ private const string Key = "Hn&~Z8pfPmy5FqkDjhgr9v$*xte4jh9C";
+ private const string IV = "KS1wTrhg&@Wa^uDW";
+
+ ///
+ /// AES 解密指定的数据。
+ ///
+ /// 要解密的数据
+ /// Key
+ /// IV
+ ///
+ public static byte[] Decrypt(byte[] data, string key = Key, string iv = IV)
+ {
+ if (data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException(nameof(key));
+
+ if (string.IsNullOrEmpty(iv))
+ throw new ArgumentNullException(nameof(iv));
+
+ var keyBytes = new byte[32];
+ var ivBytes = new byte[16];
+
+ Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(keyBytes.Length, '#')), keyBytes, keyBytes.Length);
+ Array.Copy(Encoding.UTF8.GetBytes(iv.PadRight(ivBytes.Length, '#')), ivBytes, ivBytes.Length);
+
+ using var aes = Aes.Create();
+ var decryptor = aes.CreateDecryptor(keyBytes, ivBytes);
+
+ using var memoryStream = new MemoryStream(data);
+ using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
+ using var tempMemory = new MemoryStream();
+ var readBytes = 0;
+ var buffer = new byte[1024];
+
+ while ((readBytes = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
+ tempMemory.Write(buffer, 0, readBytes);
+
+ return tempMemory.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/FileUploadOperationFilter.cs b/AX.WebDrillServer/Middlewares/FileUploadOperationFilter.cs
new file mode 100644
index 0000000..eaed815
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/FileUploadOperationFilter.cs
@@ -0,0 +1,37 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ public class FileUploadOperationFilter : IOperationFilter
+ {
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ if (context.ApiDescription.ActionDescriptor.Parameters.Any(w => w.ParameterType == typeof(IFormCollection)))
+ {
+ Dictionary schema = new()
+ {
+ ["fileName"] = new OpenApiSchema
+ {
+ Description = "Select file",
+ Type = "string",
+ Format = "binary"
+ }
+ };
+
+ Dictionary content = new()
+ {
+ ["multipart/form-data"] = new OpenApiMediaType
+ {
+ Schema = new OpenApiSchema { Type = "object", Properties = schema }
+ }
+ };
+
+ operation.RequestBody = new OpenApiRequestBody() { Content = content };
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonInputFormatter.cs b/AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonInputFormatter.cs
new file mode 100644
index 0000000..35bd32b
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonInputFormatter.cs
@@ -0,0 +1,27 @@
+using System.Text;
+
+namespace Microsoft.AspNetCore.Mvc.Formatters
+{
+ ///
+ /// 用于读取原生的 JSON 数据。
+ ///
+ public class RawJsonInputFormatter : TextInputFormatter
+ {
+ public RawJsonInputFormatter()
+ {
+ SupportedMediaTypes.Add("application/json");
+ SupportedEncodings.Add(Encoding.UTF8);
+ SupportedEncodings.Add(Encoding.Unicode);
+ }
+
+ public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
+ {
+ using var reader = context.ReaderFactory(context.HttpContext.Request.Body, encoding);
+ var content = await reader.ReadToEndAsync();
+ return await InputFormatterResult.SuccessAsync(content);
+ }
+
+ protected override bool CanReadType(Type type) =>
+ type == typeof(string);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonOutputFormatter.cs b/AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonOutputFormatter.cs
new file mode 100644
index 0000000..b2163ff
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonOutputFormatter.cs
@@ -0,0 +1,54 @@
+using System.Text;
+
+namespace Microsoft.AspNetCore.Mvc.Formatters
+{
+ public class RawJsonOutputFormatter : TextOutputFormatter
+ {
+ public RawJsonOutputFormatter()
+ {
+ SupportedMediaTypes.Add("application/json");
+ SupportedEncodings.Add(Encoding.UTF8);
+ SupportedEncodings.Add(Encoding.Unicode);
+ }
+
+ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
+ {
+ if (context == null)
+ throw new ArgumentNullException(nameof(context));
+
+ if (selectedEncoding == null)
+ throw new ArgumentNullException(nameof(selectedEncoding));
+
+ using var writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding);
+ if (context.Object is string str)
+ {
+ writer.Write(str);
+ }
+ else if (context.Object is List list)
+ {
+ writer.Write('[');
+
+ var index = 0;
+ var last = list.Count - 1;
+
+ for (var i = 0; i < list.Count; ++i)
+ {
+ writer.Write(list[i]);
+
+ if (index != last)
+ writer.Write(',');
+ }
+
+ writer.Write(']');
+ }
+ else
+ throw new InvalidOperationException();
+
+ await writer.FlushAsync();
+ }
+
+ protected override bool CanWriteType(Type? type) =>
+ type == typeof(string) ||
+ type == typeof(List);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/Jwts/IJwtService.cs b/AX.WebDrillServer/Middlewares/Jwts/IJwtService.cs
new file mode 100644
index 0000000..471beab
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/Jwts/IJwtService.cs
@@ -0,0 +1,25 @@
+using System.Security.Claims;
+
+namespace AX.WebDrillServer.Middlewares.Jwts
+{
+ ///
+ /// JWT 服务器接口。
+ ///
+ public interface IJwtService
+ {
+ ///
+ /// 创建一个 JWT。
+ ///
+ ///
+ ///
+ string Create(ClaimsIdentity identity);
+
+ ///
+ /// 验证 JWT 是否有效。
+ ///
+ ///
+ ///
+ ///
+ bool Validate(string token, out ClaimsPrincipal principal);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/Jwts/JwtClaimTypes.cs b/AX.WebDrillServer/Middlewares/Jwts/JwtClaimTypes.cs
new file mode 100644
index 0000000..a50bfef
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/Jwts/JwtClaimTypes.cs
@@ -0,0 +1,173 @@
+namespace AX.WebDrillServer.Middlewares.Jwts
+{
+ ///
+ /// 用于 JWT 的声明类型。
+ ///
+ public static class JwtClaimTypes
+ {
+ /// Unique Identifier for the End-User at the Issuer.
+ public const string Subject = "sub";
+
+ /// End-User's full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the End-User's locale and preferences.
+ public const string Name = "name";
+
+ /// Given name(s) or first name(s) of the End-User. Note that in some cultures, people can have multiple given names; all can be present, with the names being separated by space characters.
+ public const string GivenName = "given_name";
+
+ /// Surname(s) or last name(s) of the End-User. Note that in some cultures, people can have multiple family names or no family name; all can be present, with the names being separated by space characters.
+ public const string FamilyName = "family_name";
+
+ /// Middle name(s) of the End-User. Note that in some cultures, people can have multiple middle names; all can be present, with the names being separated by space characters. Also note that in some cultures, middle names are not used.
+ public const string MiddleName = "middle_name";
+
+ /// Casual name of the End-User that may or may not be the same as the given_name. For instance, a nickname value of Mike might be returned alongside a given_name value of Michael.
+ public const string NickName = "nickname";
+
+ /// Shorthand name by which the End-User wishes to be referred to at the RP, such as janedoe or j.doe. This value MAY be any valid JSON string including special characters such as @, /, or whitespace. The relying party MUST NOT rely upon this value being unique
+ /// The RP MUST NOT rely upon this value being unique, as discussed in http://openid.net/specs/openid-connect-basic-1_0-32.html#ClaimStability
+ public const string PreferredUserName = "preferred_username";
+
+ /// URL of the End-User's profile page. The contents of this Web page SHOULD be about the End-User.
+ public const string Profile = "profile";
+
+ /// URL of the End-User's profile picture. This URL MUST refer to an image file (for example, a PNG, JPEG, or GIF image file), rather than to a Web page containing an image.
+ /// Note that this URL SHOULD specifically reference a profile photo of the End-User suitable for displaying when describing the End-User, rather than an arbitrary photo taken by the End-User.
+ public const string Picture = "picture";
+
+ /// URL of the End-User's Web page or blog. This Web page SHOULD contain information published by the End-User or an organization that the End-User is affiliated with.
+ public const string WebSite = "website";
+
+ /// End-User's preferred e-mail address. Its value MUST conform to the RFC 5322 [RFC5322] addr-spec syntax. The relying party MUST NOT rely upon this value being unique
+ public const string Email = "email";
+
+ /// "true" if the End-User's e-mail address has been verified; otherwise "false".
+ /// When this Claim Value is "true", this means that the OP took affirmative steps to ensure that this e-mail address was controlled by the End-User at the time the verification was performed. The means by which an e-mail address is verified is context-specific, and dependent upon the trust framework or contractual agreements within which the parties are operating.
+ public const string EmailVerified = "email_verified";
+
+ /// End-User's gender. Values defined by this specification are "female" and "male". Other values MAY be used when neither of the defined values are applicable.
+ public const string Gender = "gender";
+
+ /// End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format. The year MAY be 0000, indicating that it is omitted. To represent only the year, YYYY format is allowed. Note that depending on the underlying platform's date related function, providing just year can result in varying month and day, so the implementers need to take this factor into account to correctly process the dates.
+ public const string BirthDate = "birthdate";
+
+ /// String from the time zone database (http://www.twinsun.com/tz/tz-link.htm) representing the End-User's time zone. For example, Europe/Paris or America/Los_Angeles.
+ public const string ZoneInfo = "zoneinfo";
+
+ /// End-User's locale, represented as a BCP47 [RFC5646] language tag. This is typically an ISO 639-1 Alpha-2 [ISO639‑1] language code in lowercase and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash. For example, en-US or fr-CA. As a compatibility note, some implementations have used an underscore as the separator rather than a dash, for example, en_US; Relying Parties MAY choose to accept this locale syntax as well.
+ public const string Locale = "locale";
+
+ /// End-User's preferred telephone number. E.164 (https://www.itu.int/rec/T-REC-E.164/e) is RECOMMENDED as the format of this Claim, for example, +1 (425) 555-1212 or +56 (2) 687 2400. If the phone number contains an extension, it is RECOMMENDED that the extension be represented using the RFC 3966 [RFC3966] extension syntax, for example, +1 (604) 555-1234;ext=5678.
+ public const string PhoneNumber = "phone_number";
+
+ /// True if the End-User's phone number has been verified; otherwise false. When this Claim Value is true, this means that the OP took affirmative steps to ensure that this phone number was controlled by the End-User at the time the verification was performed.
+ /// The means by which a phone number is verified is context-specific, and dependent upon the trust framework or contractual agreements within which the parties are operating. When true, the phone_number Claim MUST be in E.164 format and any extensions MUST be represented in RFC 3966 format.
+ public const string PhoneNumberVerified = "phone_number_verified";
+
+ /// End-User's preferred postal address. The value of the address member is a JSON structure containing some or all of the members defined in http://openid.net/specs/openid-connect-basic-1_0-32.html#AddressClaim
+ public const string Address = "address";
+
+ /// Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case sensitive strings. In the common special case when there is one audience, the aud value MAY be a single case sensitive string.
+ public const string Audience = "aud";
+
+ /// Issuer Identifier for the Issuer of the response. The iss value is a case sensitive URL using the https scheme that contains scheme, host, and optionally, port number and path components and no query or fragment components.
+ public const string Issuer = "iss";
+
+ /// The time before which the JWT MUST NOT be accepted for processing, specified as the number of seconds from 1970-01-01T0:0:0Z
+ public const string NotBefore = "nbf";
+
+ /// The exp (expiration time) claim identifies the expiration time on or after which the token MUST NOT be accepted for processing, specified as the number of seconds from 1970-01-01T0:0:0Z
+ public const string Expiration = "exp";
+
+ /// Time the End-User's information was last updated. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time.
+ public const string UpdatedAt = "updated_at";
+
+ /// The iat (issued at) claim identifies the time at which the JWT was issued, , specified as the number of seconds from 1970-01-01T0:0:0Z
+ public const string IssuedAt = "iat";
+
+ /// Authentication Methods References. JSON array of strings that are identifiers for authentication methods used in the authentication.
+ public const string AuthenticationMethod = "amr";
+
+ /// Session identifier. This represents a Session of an OP at an RP to a User Agent or device for a logged-in End-User. Its contents are unique to the OP and opaque to the RP.
+ public const string SessionId = "sid";
+
+ ///
+ /// Authentication Context Class Reference. String specifying an Authentication Context Class Reference value that identifies the Authentication Context Class that the authentication performed satisfied.
+ /// The value "0" indicates the End-User authentication did not meet the requirements of ISO/IEC 29115 level 1.
+ /// Authentication using a long-lived browser cookie, for instance, is one example where the use of "level 0" is appropriate.
+ /// Authentications with level 0 SHOULD NOT be used to authorize access to any resource of any monetary value.
+ /// (This corresponds to the OpenID 2.0 PAPE nist_auth_level 0.)
+ /// An absolute URI or an RFC 6711 registered name SHOULD be used as the acr value; registered names MUST NOT be used with a different meaning than that which is registered.
+ /// Parties using this claim will need to agree upon the meanings of the values used, which may be context-specific.
+ /// The acr value is a case sensitive string.
+ ///
+ public const string AuthenticationContextClassReference = "acr";
+
+ /// Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. When a max_age request is made or when auth_time is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL.
+ public const string AuthenticationTime = "auth_time";
+
+ /// The party to which the ID Token was issued. If present, it MUST contain the OAuth 2.0 Client ID of this party. This Claim is only needed when the ID Token has a single audience value and that audience is different than the authorized party. It MAY be included even when the authorized party is the same as the sole audience. The azp value is a case sensitive string containing a StringOrURI value.
+ public const string AuthorizedParty = "azp";
+
+ /// Access Token hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string.
+ public const string AccessTokenHash = "at_hash";
+
+ /// Code hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, then take the left-most 256 bits and base64url encode them. The c_hash value is a case sensitive string.
+ public const string AuthorizationCodeHash = "c_hash";
+
+ /// String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the nonce value sent in the Authentication Request. Authorization Servers SHOULD perform no other processing on nonce values used. The nonce value is a case sensitive string.
+ public const string Nonce = "nonce";
+
+ /// JWT ID. A unique identifier for the token, which can be used to prevent reuse of the token. These tokens MUST only be used once, unless conditions for reuse were negotiated between the parties; any such negotiation is beyond the scope of this specification.
+ public const string JwtId = "jti";
+
+ /// Defines a set of event statements that each may add additional claims to fully describe a single logical event that has occurred.
+ public const string Events = "events";
+
+ /// OAuth 2.0 Client Identifier valid at the Authorization Server.
+ public const string ClientId = "client_id";
+
+ /// OpenID Connect requests MUST contain the "openid" scope value. If the openid scope value is not present, the behavior is entirely unspecified. Other scope values MAY be present. Scope values used that are not understood by an implementation SHOULD be ignored.
+ public const string Scope = "scope";
+
+ /// The "act" (actor) claim provides a means within a JWT to express that delegation has occurred and identify the acting party to whom authority has been delegated.The "act" claim value is a JSON object and members in the JSON object are claims that identify the actor. The claims that make up the "act" claim identify and possibly provide additional information about the actor.
+ public const string Actor = "act";
+
+ /// The "may_act" claim makes a statement that one party is authorized to become the actor and act on behalf of another party. The claim value is a JSON object and members in the JSON object are claims that identify the party that is asserted as being eligible to act for the party identified by the JWT containing the claim.
+ public const string MayAct = "may_act";
+
+ ///
+ /// an identifier
+ ///
+ public const string Id = "id";
+
+ ///
+ /// The identity provider
+ ///
+ public const string IdentityProvider = "idp";
+
+ ///
+ /// The role
+ ///
+ public const string Role = "role";
+
+ ///
+ /// The reference token identifier
+ ///
+ public const string ReferenceTokenId = "reference_token_id";
+
+ ///
+ /// The confirmation
+ ///
+ public const string Confirmation = "cnf";
+
+ ///
+ /// 组织机构编号。
+ ///
+ public const string OrganizationId = "oid";
+
+ ///
+ /// 组织机构代码。
+ ///
+ public const string OrganizationCode = "ocode";
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/Jwts/JwtOptions.cs b/AX.WebDrillServer/Middlewares/Jwts/JwtOptions.cs
new file mode 100644
index 0000000..154b27d
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/Jwts/JwtOptions.cs
@@ -0,0 +1,33 @@
+namespace AX.WebDrillServer.Middlewares.Jwts
+{
+ ///
+ /// JWT 配置项。
+ ///
+ public class JwtOptions
+ {
+ ///
+ /// Secret。
+ ///
+ public string Secret { get; set; } = null!;
+
+ ///
+ /// Issuer。
+ ///
+ public string Issuer { get; set; } = null!;
+
+ ///
+ /// Audience。
+ ///
+ public string Audience { get; set; } = null!;
+
+ ///
+ /// Expires。
+ ///
+ public double Expires { get; set; }
+
+ ///
+ /// Refresh Expires。
+ ///
+ public double RefreshExpires { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/Jwts/JwtService.cs b/AX.WebDrillServer/Middlewares/Jwts/JwtService.cs
new file mode 100644
index 0000000..8248b92
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/Jwts/JwtService.cs
@@ -0,0 +1,85 @@
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+
+namespace AX.WebDrillServer.Middlewares.Jwts
+{
+ ///
+ /// JWT 服务。
+ ///
+ public class JwtService : IJwtService
+ {
+ private readonly IOptionsMonitor options;
+
+ public JwtService(IOptionsMonitor options)
+ {
+ this.options = options;
+ //var jwtOptions = options.CurrentValue;
+ }
+
+ ///
+ /// 创建一个 JWT。
+ ///
+ ///
+ ///
+ public string Create(ClaimsIdentity identity)
+ {
+ var jwtOptions = options.CurrentValue;
+ var now = DateTimeOffset.Now;
+ var expires = now.AddMinutes(jwtOptions.Expires);
+ var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Secret));
+ var creds = new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);
+ var token = new JwtSecurityToken(
+ issuer: jwtOptions.Issuer,
+ audience: jwtOptions.Audience,
+ claims: identity.Claims,
+ notBefore: now.DateTime,
+ expires: expires.DateTime,
+ signingCredentials: creds);
+
+ var handler = new JwtSecurityTokenHandler();
+ var jwt = handler.WriteToken(token);
+
+ return jwt;
+ }
+
+ ///
+ /// 验证 JWT 是否有效。
+ ///
+ ///
+ ///
+ ///
+ public bool Validate(string token, out ClaimsPrincipal principal)
+ {
+ var jwtOptions = options.CurrentValue;
+ var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Secret));
+ var validationParameters = new TokenValidationParameters
+ {
+ NameClaimType = JwtClaimTypes.Name,
+ RoleClaimType = JwtClaimTypes.Role,
+
+ ValidateIssuer = true,
+ ValidIssuer = jwtOptions.Issuer,
+ ValidateAudience = true,
+ ValidAudience = jwtOptions.Audience,
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = secret,
+ RequireExpirationTime = true,
+ ValidateLifetime = false
+ };
+ var handler = new JwtSecurityTokenHandler();
+ try
+ {
+ principal = handler.ValidateToken(token, validationParameters, out var jwt);
+ return true;
+ }
+ catch
+ {
+ principal = new ClaimsPrincipal();
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/LicenseFile.cs b/AX.WebDrillServer/Middlewares/LicenseFile.cs
new file mode 100644
index 0000000..5b73ae9
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/LicenseFile.cs
@@ -0,0 +1,18 @@
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// 许可证文件。
+ ///
+ public class LicenseFile
+ {
+ ///
+ /// 公钥。
+ ///
+ public string? PublicKey { get; set; }
+
+ ///
+ /// 许可证信息。
+ ///
+ public LicenseInfo? LicenseInfo { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/LicenseInfo.cs b/AX.WebDrillServer/Middlewares/LicenseInfo.cs
new file mode 100644
index 0000000..2e68dc5
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/LicenseInfo.cs
@@ -0,0 +1,57 @@
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// 许可证信息。
+ ///
+ public class LicenseInfo
+ {
+ ///
+ /// 注册码。
+ ///
+ public string? DeviceId { get; set; }
+
+ ///
+ /// 许可证类型。
+ ///
+ ///
+ /// 0 - 按单机;
+ /// 1 - 按机构;
+ ///
+ public int LicenseType { get; set; }
+
+ ///
+ /// 是否试用。
+ ///
+ public bool IsTrial { get; set; }
+
+ ///
+ /// 激活码。
+ ///
+ public string? ActivationCode { get; set; }
+
+ ///
+ /// 激活起始时间。
+ ///
+ public DateTimeOffset StartTime { get; set; }
+
+ ///
+ /// 激活结束时间。
+ ///
+ public DateTimeOffset EndTime { get; set; }
+
+ ///
+ /// 该许可证是否还可用。
+ ///
+ public bool IsActive { get; set; }
+
+ ///
+ /// 备注。扩展用。
+ ///
+ public string? Tag { get; set; }
+
+ ///
+ /// 单位名称。
+ ///
+ public string? CompanyName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/LicenseMiddleware.cs b/AX.WebDrillServer/Middlewares/LicenseMiddleware.cs
new file mode 100644
index 0000000..a05ad37
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/LicenseMiddleware.cs
@@ -0,0 +1,48 @@
+using AX.WebDrillServer.Errors;
+using System.Text.Json;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// 许可证中间件,用于对程序进行许可认证。
+ ///
+ public class LicenceMiddleware
+ {
+ private readonly RequestDelegate next;
+
+ ///
+ /// 创建一个许可证中间件实例。
+ ///
+ ///
+ public LicenceMiddleware(RequestDelegate next) =>
+ this.next = next;
+
+ public async Task Invoke(HttpContext context)
+ {
+ if (!ServerLicense.VerifyExpires())
+ {
+ var error = GlobalErrorCodes.Messages[GlobalErrorCodes.E600];
+ var json = JsonSerializer.Serialize(error);
+ context.Response.ContentType = "application/json";
+ context.Response.StatusCode = GlobalErrorCodes.E600;
+ await context.Response.WriteAsync(json);
+ }
+ else
+ await next.Invoke(context);
+ }
+ }
+
+ ///
+ /// 许可证中间件扩展类。
+ ///
+ public static class LicenceMiddlewareExtensions
+ {
+ ///
+ /// 使用许可证中间件。
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseLicence(this IApplicationBuilder builder) =>
+ builder.UseMiddleware();
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/NameUserIdProvider.cs b/AX.WebDrillServer/Middlewares/NameUserIdProvider.cs
new file mode 100644
index 0000000..cf4b44f
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/NameUserIdProvider.cs
@@ -0,0 +1,13 @@
+using AX.WebDrillServer.Middlewares.Jwts;
+using Microsoft.AspNetCore.SignalR;
+using System.Security.Claims;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ public class NameUserIdProvider : IUserIdProvider
+ {
+ public string? GetUserId(HubConnectionContext connection) =>
+ //connection.User?.Identity?.Name;
+ connection.User?.FindFirstValue(JwtClaimTypes.Subject);
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/ObjectId.cs b/AX.WebDrillServer/Middlewares/ObjectId.cs
new file mode 100644
index 0000000..57ca39d
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/ObjectId.cs
@@ -0,0 +1,588 @@
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Security;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// 表示一个对象 Id。
+ ///
+ /// 来自 MongoDB 的 ObjectId 算法。
+ public struct ObjectId : IComparable, IEquatable
+ {
+ private static readonly ObjectId emptyInstance = default;
+ private static readonly int staticMachine = (GetMachineHash() + GetAppDomainId()) & 0x00ffffff;
+ private static readonly short staticPid = GetPid();
+ private static int staticIncrement = (new Random()).Next();
+
+ private readonly int a;
+ private readonly int b;
+ private readonly int c;
+
+ ///
+ /// 创建新的 ObjectId 实例。
+ ///
+ ///
+ public ObjectId(byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes));
+
+ if (bytes.Length != 12)
+ throw new ArgumentException("Byte array must be 12 bytes long", nameof(bytes));
+
+ FromByteArray(bytes, 0, out a, out b, out c);
+ }
+
+ ///
+ /// 创建新的 ObjectId 实例。
+ ///
+ ///
+ ///
+ internal ObjectId(byte[] bytes, int index)
+ {
+ FromByteArray(bytes, index, out a, out b, out c);
+ }
+
+ ///
+ /// 创建新的 ObjectId 实例。
+ ///
+ /// The timestamp (expressed as a DateTime).
+ /// The machine hash.
+ /// The PID.
+ /// The increment.
+ public ObjectId(DateTime timestamp, int machine, short pid, int increment)
+ : this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
+ {
+ }
+
+ ///
+ /// 创建新的 ObjectId 实例。
+ ///
+ /// The timestamp.
+ /// The machine hash.
+ /// The PID.
+ /// The increment.
+ public ObjectId(int timestamp, int machine, short pid, int increment)
+ {
+ if ((machine & 0xff000000) != 0)
+ throw new ArgumentOutOfRangeException(nameof(machine), "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
+
+ if ((increment & 0xff000000) != 0)
+ throw new ArgumentOutOfRangeException(nameof(increment), "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
+
+ a = timestamp;
+ b = (machine << 8) | (((int)pid >> 8) & 0xff);
+ c = ((int)pid << 24) | increment;
+ }
+
+ ///
+ /// 创建新的 ObjectId 实例。
+ ///
+ /// The value.
+ public ObjectId(string value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ var bytes = ParseHexString(value);
+ FromByteArray(bytes, 0, out a, out b, out c);
+ }
+
+ ///
+ /// 获得空实例。
+ ///
+ public static ObjectId Empty
+ {
+ get { return emptyInstance; }
+ }
+
+ ///
+ /// 获得时间戳。
+ ///
+ public int Timestamp
+ {
+ get { return a; }
+ }
+
+ ///
+ /// 获得机器码。
+ ///
+ public int Machine
+ {
+ get { return (b >> 8) & 0xffffff; }
+ }
+
+ ///
+ /// 获得 PID。
+ ///
+ public short Pid
+ {
+ get { return (short)(((b << 8) & 0xff00) | ((c >> 24) & 0x00ff)); }
+ }
+
+ ///
+ /// 获得增量值。
+ ///
+ public int Increment
+ {
+ get { return c & 0xffffff; }
+ }
+
+ ///
+ /// 获得创建时间。
+ ///
+ public DateTime CreationTime
+ {
+ get { return DateTime.UnixEpoch.AddSeconds(Timestamp); }
+ }
+
+ public static bool operator <(ObjectId lhs, ObjectId rhs)
+ {
+ return lhs.CompareTo(rhs) < 0;
+ }
+
+ public static bool operator <=(ObjectId lhs, ObjectId rhs)
+ {
+ return lhs.CompareTo(rhs) <= 0;
+ }
+
+ public static bool operator ==(ObjectId lhs, ObjectId rhs)
+ {
+ return lhs.Equals(rhs);
+ }
+
+ public static bool operator !=(ObjectId lhs, ObjectId rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+ public static bool operator >=(ObjectId lhs, ObjectId rhs)
+ {
+ return lhs.CompareTo(rhs) >= 0;
+ }
+
+ public static bool operator >(ObjectId lhs, ObjectId rhs)
+ {
+ return lhs.CompareTo(rhs) > 0;
+ }
+
+ ///
+ /// 生成一个新的 ObjectId。
+ ///
+ public static ObjectId NewId()
+ {
+ return NewId(GetTimestampFromDateTime(DateTime.UtcNow));
+ }
+
+ ///
+ /// 根据指定的时间戳,生成一个新的 ObjectId。
+ ///
+ public static ObjectId NewId(DateTime timestamp)
+ {
+ return NewId(GetTimestampFromDateTime(timestamp));
+ }
+
+ ///
+ /// 根据指定的时间戳,生成一个新的 ObjectId。
+ ///
+ public static ObjectId NewId(int timestamp)
+ {
+ int increment = Interlocked.Increment(ref staticIncrement) & 0x00ffffff; // 只使用低位 3 字节
+ return new ObjectId(timestamp, staticMachine, staticPid, increment);
+ }
+
+ ///
+ /// 把 ObjectId 的各组件打包成一个字节数组。
+ ///
+ /// The timestamp.
+ /// The machine hash.
+ /// The PID.
+ /// The increment.
+ /// A byte array.
+ public static byte[] Pack(int timestamp, int machine, short pid, int increment)
+ {
+ if ((machine & 0xff000000) != 0)
+ throw new ArgumentOutOfRangeException(nameof(machine), "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
+
+ if ((increment & 0xff000000) != 0)
+ throw new ArgumentOutOfRangeException(nameof(increment), "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
+
+ byte[] bytes = new byte[12];
+
+ bytes[0] = (byte)(timestamp >> 24);
+ bytes[1] = (byte)(timestamp >> 16);
+ bytes[2] = (byte)(timestamp >> 8);
+ bytes[3] = (byte)(timestamp);
+ bytes[4] = (byte)(machine >> 16);
+ bytes[5] = (byte)(machine >> 8);
+ bytes[6] = (byte)(machine);
+ bytes[7] = (byte)(pid >> 8);
+ bytes[8] = (byte)(pid);
+ bytes[9] = (byte)(increment >> 16);
+ bytes[10] = (byte)(increment >> 8);
+ bytes[11] = (byte)(increment);
+
+ return bytes;
+ }
+
+ ///
+ /// 分析字符串,转换为 ObjectId 实例。
+ ///
+ public static ObjectId Parse(string str)
+ {
+ if (str == null)
+ throw new ArgumentNullException(nameof(str));
+
+ if (TryParse(str, out var objectId))
+ return objectId;
+ else
+ {
+ var message = string.Format("'{0}' is not a valid 24 digit hex string.", str);
+ throw new FormatException(message);
+ }
+ }
+
+ ///
+ /// 尝试分析字符串,转换为 ObjectId 实例。
+ ///
+ public static bool TryParse(string str, out ObjectId objectId)
+ {
+ if (str != null && str.Length == 24)
+ {
+ if (TryParseHexString(str, out byte[]? bytes) && bytes is not null)
+ {
+ objectId = new ObjectId(bytes);
+ return true;
+ }
+ }
+
+ objectId = default;
+ return false;
+ }
+
+ ///
+ /// 把一个字节数组解包成 ObjectId 各组件值。
+ ///
+ /// A byte array.
+ /// The timestamp.
+ /// The machine hash.
+ /// The PID.
+ /// The increment.
+ public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes));
+
+ if (bytes.Length != 12)
+ throw new ArgumentOutOfRangeException(nameof(bytes), "Byte array must be 12 bytes long.");
+
+ timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
+ machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6];
+ pid = (short)((bytes[7] << 8) + bytes[8]);
+ increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11];
+ }
+
+ private static int GetAppDomainId()
+ {
+#if NETSTANDARD1_5 || NETSTANDARD1_6
+ return 1;
+#else
+ return AppDomain.CurrentDomain.Id;
+#endif
+ }
+
+ ///
+ /// Gets the current process id. This method exists because of how CAS operates on the call stack, checking
+ /// for permissions before executing the method. Hence, if we inlined this call, the calling method would not execute
+ /// before throwing an exception requiring the try/catch at an even higher level that we don't necessarily control.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int GetCurrentProcessId()
+ {
+ return Environment.ProcessId;
+ }
+
+ private static int GetMachineHash()
+ {
+ // 替代 Dns.HostName 以便可以脱机工作
+ var machineName = GetMachineName();
+ return 0x00ffffff & machineName.GetHashCode(); // 使用前 3 个字节进行哈希
+ }
+
+ private static string GetMachineName()
+ {
+ return Environment.MachineName;
+ }
+
+ private static short GetPid()
+ {
+ try
+ {
+ return (short)GetCurrentProcessId(); // 只使用低位 2 字节
+ }
+ catch (SecurityException)
+ {
+ return 0;
+ }
+ }
+
+ private static int GetTimestampFromDateTime(DateTime timestamp)
+ {
+ var secondsSinceEpoch = (long)Math.Floor((ToUniversalTime(timestamp) - DateTime.UnixEpoch).TotalSeconds);
+
+ if (secondsSinceEpoch < int.MinValue || secondsSinceEpoch > int.MaxValue)
+ throw new ArgumentOutOfRangeException(nameof(timestamp));
+
+ return (int)secondsSinceEpoch;
+ }
+
+ private static void FromByteArray(byte[] bytes, int offset, out int a, out int b, out int c)
+ {
+ a = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
+ b = (bytes[offset + 4] << 24) | (bytes[offset + 5] << 16) | (bytes[offset + 6] << 8) | bytes[offset + 7];
+ c = (bytes[offset + 8] << 24) | (bytes[offset + 9] << 16) | (bytes[offset + 10] << 8) | bytes[offset + 11];
+ }
+
+ ///
+ /// 比较 ObjectId 大小。
+ ///
+ public int CompareTo(ObjectId other)
+ {
+ int result = ((uint)a).CompareTo((uint)other.a);
+ if (result != 0) { return result; }
+ result = ((uint)b).CompareTo((uint)other.b);
+ if (result != 0) { return result; }
+ return ((uint)c).CompareTo((uint)other.c);
+ }
+
+ ///
+ /// 比较相等性。
+ ///
+ public bool Equals(ObjectId rhs)
+ {
+ return
+ a == rhs.a &&
+ b == rhs.b &&
+ c == rhs.c;
+ }
+
+ ///
+ /// 比较相等性。
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is not null &&
+ obj is ObjectId id &&
+ Equals(id);
+ }
+
+ ///
+ /// 获得哈希码。
+ ///
+ public override int GetHashCode()
+ {
+ int hash = 17;
+ hash = 37 * hash + a.GetHashCode();
+ hash = 37 * hash + b.GetHashCode();
+ hash = 37 * hash + c.GetHashCode();
+ return hash;
+ }
+
+ ///
+ /// 把 ObjectId 转换为字节数组。
+ ///
+ public byte[] ToByteArray()
+ {
+ var bytes = new byte[12];
+ ToByteArray(bytes, 0);
+ return bytes;
+ }
+
+ ///
+ /// 把 ObjectId 转换为字节数组。
+ ///
+ public void ToByteArray(byte[] bytes, int offset)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes));
+
+ if (offset + 12 > bytes.Length)
+ throw new ArgumentException("Not enough room in destination buffer.", nameof(offset));
+
+ bytes[offset + 0] = (byte)(a >> 24);
+ bytes[offset + 1] = (byte)(a >> 16);
+ bytes[offset + 2] = (byte)(a >> 8);
+ bytes[offset + 3] = (byte)(a);
+ bytes[offset + 4] = (byte)(b >> 24);
+ bytes[offset + 5] = (byte)(b >> 16);
+ bytes[offset + 6] = (byte)(b >> 8);
+ bytes[offset + 7] = (byte)(b);
+ bytes[offset + 8] = (byte)(c >> 24);
+ bytes[offset + 9] = (byte)(c >> 16);
+ bytes[offset + 10] = (byte)(c >> 8);
+ bytes[offset + 11] = (byte)(c);
+ }
+
+ ///
+ /// 转换为字符串。
+ ///
+ public override string ToString()
+ {
+ var c = new char[24];
+ c[0] = ToHexChar((a >> 28) & 0x0f);
+ c[1] = ToHexChar((a >> 24) & 0x0f);
+ c[2] = ToHexChar((a >> 20) & 0x0f);
+ c[3] = ToHexChar((a >> 16) & 0x0f);
+ c[4] = ToHexChar((a >> 12) & 0x0f);
+ c[5] = ToHexChar((a >> 8) & 0x0f);
+ c[6] = ToHexChar((a >> 4) & 0x0f);
+ c[7] = ToHexChar(a & 0x0f);
+ c[8] = ToHexChar((b >> 28) & 0x0f);
+ c[9] = ToHexChar((b >> 24) & 0x0f);
+ c[10] = ToHexChar((b >> 20) & 0x0f);
+ c[11] = ToHexChar((b >> 16) & 0x0f);
+ c[12] = ToHexChar((b >> 12) & 0x0f);
+ c[13] = ToHexChar((b >> 8) & 0x0f);
+ c[14] = ToHexChar((b >> 4) & 0x0f);
+ c[15] = ToHexChar(b & 0x0f);
+ c[16] = ToHexChar((this.c >> 28) & 0x0f);
+ c[17] = ToHexChar((this.c >> 24) & 0x0f);
+ c[18] = ToHexChar((this.c >> 20) & 0x0f);
+ c[19] = ToHexChar((this.c >> 16) & 0x0f);
+ c[20] = ToHexChar((this.c >> 12) & 0x0f);
+ c[21] = ToHexChar((this.c >> 8) & 0x0f);
+ c[22] = ToHexChar((this.c >> 4) & 0x0f);
+ c[23] = ToHexChar(this.c & 0x0f);
+ return new string(c);
+ }
+
+ ///
+ /// 分析 16 进制字符串,转换为等价的字节数组。
+ ///
+ ///
+ ///
+ private static byte[] ParseHexString(string str)
+ {
+ if (str == null)
+ throw new ArgumentNullException(nameof(str));
+
+ if (!TryParseHexString(str, out byte[]? bytes) || bytes is null)
+ throw new FormatException("String should contain only hexadecimal digits.");
+
+ return bytes;
+ }
+
+ ///
+ /// 把整型转换为 16 进制字符。
+ ///
+ ///
+ /// The hex character.
+ private static char ToHexChar(int value)
+ {
+ return (char)(value + (value < 10 ? '0' : 'a' - 10));
+ }
+
+ /*
+ ///
+ /// 把字节数组转换为 16 进制字符串。
+ ///
+ ///
+ ///
+ private static string ToHexString(byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes));
+
+ var length = bytes.Length;
+ var c = new char[length * 2];
+
+ for (int i = 0, j = 0; i < length; i++)
+ {
+ var b = bytes[i];
+ c[j++] = ToHexChar(b >> 4);
+ c[j++] = ToHexChar(b & 0x0f);
+ }
+
+ return new string(c);
+ }
+ */
+
+ ///
+ /// 把 DateTime 值转换为 UTC 时间(特殊处理 MinValue 和 MaxValue)。
+ ///
+ private static DateTime ToUniversalTime(DateTime dateTime)
+ {
+ if (dateTime == DateTime.MinValue)
+ return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
+ else if (dateTime == DateTime.MaxValue)
+ return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
+ else
+ return dateTime.ToUniversalTime();
+ }
+
+ ///
+ /// 尝试分析 16 进制字符串,转换为等价的字节数组。
+ ///
+ private static bool TryParseHexString(string str, out byte[]? bytes)
+ {
+ bytes = null;
+
+ if (str == null)
+ return false;
+
+ var buffer = new byte[(str.Length + 1) / 2];
+
+ var i = 0;
+ var j = 0;
+
+ if ((str.Length % 2) == 1)
+ {
+ // if str has an odd length assume an implied leading "0"
+
+ if (!TryParseHexChar(str[i++], out int y))
+ return false;
+
+ buffer[j++] = (byte)y;
+ }
+
+ while (i < str.Length)
+ {
+ if (!TryParseHexChar(str[i++], out int x))
+ return false;
+
+ if (!TryParseHexChar(str[i++], out int y))
+ return false;
+
+ buffer[j++] = (byte)((x << 4) | y);
+ }
+
+ bytes = buffer;
+ return true;
+ }
+
+ private static bool TryParseHexChar(char c, out int value)
+ {
+ if (c >= '0' && c <= '9')
+ {
+ value = c - '0';
+ return true;
+ }
+
+ if (c >= 'a' && c <= 'f')
+ {
+ value = 10 + (c - 'a');
+ return true;
+ }
+
+ if (c >= 'A' && c <= 'F')
+ {
+ value = 10 + (c - 'A');
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/ObjectIdGenerator.cs b/AX.WebDrillServer/Middlewares/ObjectIdGenerator.cs
new file mode 100644
index 0000000..0774ccd
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/ObjectIdGenerator.cs
@@ -0,0 +1,16 @@
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.ValueGeneration;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// ObjectId 生成器。
+ ///
+ public class ObjectIdGenerator : ValueGenerator
+ {
+ public override bool GeneratesTemporaryValues => false;
+
+ public override string Next(EntityEntry entry) =>
+ ObjectId.NewId().ToString();
+ }
+}
\ No newline at end of file
diff --git a/AX.WebDrillServer/Middlewares/RSAHelper.cs b/AX.WebDrillServer/Middlewares/RSAHelper.cs
new file mode 100644
index 0000000..efc5430
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/RSAHelper.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// 非对称RSA加密辅助类。
+ ///
+ ///
+ /// 切记:公钥验签。
+ ///
+ public static class RSAHelper
+ {
+ ///
+ /// 根据公钥对数据签名进行验证。
+ ///
+ /// 公钥
+ /// 要验证的数据
+ /// 数据签名
+ /// 散列算法
+ /// 填充方式
+ ///
+ public static bool VerifyData(
+ string? publicKey,
+ string? data,
+ string? signdata,
+ string hashAlgorithm = nameof(HashAlgorithmName.SHA256),
+ RSASignaturePadding? padding = null)
+ {
+ if (string.IsNullOrWhiteSpace(publicKey))
+ throw new ArgumentException($"{publicKey}不能为空!");
+
+ if (string.IsNullOrWhiteSpace(data))
+ throw new ArgumentException($"{data}不能为空!");
+
+ if (string.IsNullOrWhiteSpace(signdata))
+ throw new ArgumentException($"{signdata}不能为空!");
+
+ HashAlgorithmName hash = hashAlgorithm switch
+ {
+ nameof(HashAlgorithmName.MD5) => HashAlgorithmName.MD5,
+ nameof(HashAlgorithmName.SHA1) => HashAlgorithmName.SHA1,
+ nameof(HashAlgorithmName.SHA256) => HashAlgorithmName.SHA256,
+ nameof(HashAlgorithmName.SHA384) => HashAlgorithmName.SHA384,
+ nameof(HashAlgorithmName.SHA512) => HashAlgorithmName.SHA512,
+ _ => throw new ArgumentOutOfRangeException(nameof(hashAlgorithm)),
+ };
+ if (padding == null)
+ padding = RSASignaturePadding.Pkcs1;
+
+ using RSA rsa = RSA.Create();
+ rsa.ImportKey(publicKey, false);
+
+ var dataBytes = Encoding.UTF8.GetBytes(data);
+ var signBytes = Convert.FromBase64String(signdata);
+ var result = rsa.VerifyData(dataBytes, signBytes, hash, padding);
+
+ return result;
+ }
+ }
+
+ internal static class RSAExtensions
+ {
+ private static RSAParameters privateKey;
+ private static RSAParameters publicKey;
+
+ public static void ImportKey(this RSA rsa, string key, bool isPrivate)
+ {
+ if (isPrivate)
+ {
+ if (privateKey.D == null ||
+ privateKey.DP == null ||
+ privateKey.DQ == null ||
+ privateKey.Exponent == null ||
+ privateKey.InverseQ == null ||
+ privateKey.Modulus == null ||
+ privateKey.P == null ||
+ privateKey.Q == null)
+ {
+ privateKey.D = new byte[256];
+ privateKey.DP = new byte[128];
+ privateKey.DQ = new byte[128];
+ privateKey.Exponent = new byte[3];
+ privateKey.InverseQ = new byte[128];
+ privateKey.Modulus = new byte[256];
+ privateKey.P = new byte[128];
+ privateKey.Q = new byte[128];
+
+ var bytes = Convert.FromBase64String(key);
+
+ Buffer.BlockCopy(bytes, 0, privateKey.D, 0, 256);
+ Buffer.BlockCopy(bytes, 256, privateKey.DP, 0, 128);
+ Buffer.BlockCopy(bytes, 256 + 128, privateKey.DQ, 0, 128);
+ Buffer.BlockCopy(bytes, 256 + 128 + 128, privateKey.Exponent, 0, 3);
+ Buffer.BlockCopy(bytes, 256 + 128 + 128 + 3, privateKey.InverseQ, 0, 128);
+ Buffer.BlockCopy(bytes, 256 + 128 + 128 + 3 + 128, privateKey.Modulus, 0, 256);
+ Buffer.BlockCopy(bytes, 256 + 128 + 128 + 3 + 128 + 256, privateKey.P, 0, 128);
+ Buffer.BlockCopy(bytes, 256 + 128 + 128 + 3 + 128 + 256 + 128, privateKey.Q, 0, 128);
+ }
+
+ rsa.ImportParameters(privateKey);
+ }
+ else
+ {
+ if (publicKey.Exponent == null ||
+ publicKey.Modulus == null)
+ {
+ publicKey.Exponent = new byte[3];
+ publicKey.Modulus = new byte[256];
+
+ var bytes = Convert.FromBase64String(key);
+
+ Buffer.BlockCopy(bytes, 0, publicKey.Exponent, 0, 3);
+ Buffer.BlockCopy(bytes, 3, publicKey.Modulus, 0, 256);
+ }
+
+ rsa.ImportParameters(publicKey);
+ }
+ }
+ }
+}
diff --git a/AX.WebDrillServer/Middlewares/ServerLicense.cs b/AX.WebDrillServer/Middlewares/ServerLicense.cs
new file mode 100644
index 0000000..0c67c1b
--- /dev/null
+++ b/AX.WebDrillServer/Middlewares/ServerLicense.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace AX.WebDrillServer.Middlewares
+{
+ ///
+ /// 服务器许可证。
+ ///
+ internal static class ServerLicense
+ {
+#if !RELEASE
+ private const string PublicKey = "AQABsYQ/sSNCUcrOAzFnSXOJZc5a8nzSjHSBzr/kjsTa0J4MsIBCsJ6uOfTswJgad6VmW6v8+oueqdwtLoKBr2W097Y/ZXzuBCQMqAxwpDAqqglgb1StMEUC6EJ2CSdKPW0u+NCuvMiOjm4Ao22rq/s3lWcN3if2gzOn78QDwO3edESgQ0izlpYaUZdH0j8Ksc3u6wGjauIs+3SOrgRY8dhnmf20z+FgTzjigN86su1GdCSxWMgZWcdedIzcbfLndwavRUvPxEXE7+o1pKG5MdTESGRw9IbhgMHLn89vmYvPyH2YpP9z+JpJV7ZPuUvPyHYQpuNnwpYzCWNFT9+ykQGjXQ==";
+#else
+ private const string PublicKey = "AQABpul6r7r+taD3Z/Zgj/9Kxtf+HOLa6CmBYxpZDxzaWU31LI/N3P53PrI54lJ/blHBgE+JjQtTOSuPyEh4dGma2tT/i9Ktnp47KAtftrEdN+UZSQYCOBoVWY1mDlSp8aqKXBSEDFyUhLVjXqUpDEIB7MVjO6GaNVqTldafoyEnsp6TuG0+5upVRiCmnZPZuphKytHPH2+e0nET9t0OeDAy8q61MrklRmNfl/XHvJowT+FGo/bw0W5HezzeeTWc/c+MgQBndPWPnBgOt643Oj7G5+SvMiz9SpYYBoqx9JkwpqsAyNi9XLnsCbkTw7Z8HEec3P4+ag1zJaDyrpe3gOrOAQ==";
+#endif
+
+ private const string LicenseFilename = "License.dat";
+
+ private static volatile bool initialized;
+ private static LicenseFile? licenseFile;
+
+ ///
+ /// 初始化。
+ ///
+ ///
+ public static bool Initialize()
+ {
+ try
+ {
+ if (!initialized)
+ {
+ var directory = Directory.GetCurrentDirectory();
+ var licenseFilePath = Path.Combine(directory, LicenseFilename);
+ Console.WriteLine(licenseFilePath);
+ var data = File.ReadAllBytes(licenseFilePath);
+ var decrypt = AESHelper.Decrypt(data);
+ var json = Encoding.UTF8.GetString(decrypt);
+
+ var deserializedLicenseFile = JsonSerializer.Deserialize(json);
+ licenseFile = deserializedLicenseFile ?? licenseFile;
+
+ if (VerifyData())
+ initialized = true;
+ }
+ }
+ catch
+ {
+ //吞掉异常
+ }
+
+ return initialized;
+ }
+
+ ///
+ /// 校验许可证有效期。
+ ///
+ ///
+ public static bool VerifyExpires()
+ {
+ if (initialized)
+ {
+ //校验有效期
+ var now = DateTimeOffset.Now;
+
+ return licenseFile?.LicenseInfo?.StartTime <= now ||
+ licenseFile?.LicenseInfo?.EndTime <= now;
+ }
+
+ return false;
+ }
+
+ ///
+ /// 校验许可证签名和激活码。
+ ///
+ ///
+ public static bool VerifyData()
+ {
+ if (licenseFile != null)
+ {
+ //校验许可证是否信任的CA签发
+ if (licenseFile.PublicKey != PublicKey)
+ return false;
+
+ if (!RSAHelper.VerifyData(licenseFile.PublicKey, licenseFile?.LicenseInfo?.DeviceId, licenseFile?.LicenseInfo?.ActivationCode))
+ return false;
+
+ //校验许可证类型
+ if (licenseFile?.LicenseInfo?.LicenseType != 0)
+ return false;
+
+ //校验许可证类型是否可用
+ if (!licenseFile.LicenseInfo.IsActive)
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/AX.WebDrillServer/Migrations/20220909012500_InitialCreate.Designer.cs b/AX.WebDrillServer/Migrations/20220909012500_InitialCreate.Designer.cs
new file mode 100644
index 0000000..4b8aab0
--- /dev/null
+++ b/AX.WebDrillServer/Migrations/20220909012500_InitialCreate.Designer.cs
@@ -0,0 +1,385 @@
+//
+using System;
+using AX.WebDrillServer.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace AX.WebDrillServer.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20220909012500_InitialCreate")]
+ partial class InitialCreate
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.Organization", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("Code")
+ .HasColumnType("text")
+ .HasComment("组织机构编码");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("Introductions")
+ .HasColumnType("text")
+ .HasComment("组织机构说明");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property("Level")
+ .HasColumnType("integer")
+ .HasComment("组织机构级别");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasComment("组织机构名称");
+
+ b.HasKey("Id");
+
+ b.ToTable("Organizations");
+ });
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.Premission", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("Introductions")
+ .HasColumnType("text")
+ .HasComment("权限说明");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasComment("权限名称");
+
+ b.Property("ParentId")
+ .HasColumnType("text")
+ .HasComment("权限Id");
+
+ b.HasKey("Id");
+
+ b.ToTable("Premissions");
+ });
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.Role", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("Introductions")
+ .HasColumnType("text")
+ .HasComment("角色说明");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasComment("角色名称");
+
+ b.Property("ParentId")
+ .HasColumnType("text")
+ .HasComment("角色父级Id");
+
+ b.HasKey("Id");
+
+ b.ToTable("Roles");
+ });
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.Role_Premission", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property("PremissionId")
+ .HasColumnType("text")
+ .HasComment("权限Id");
+
+ b.Property("RoleId")
+ .HasColumnType("text")
+ .HasComment("角色Id");
+
+ b.HasKey("Id");
+
+ b.ToTable("Role_Premission");
+ });
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.User", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property("OrganizationId")
+ .HasColumnType("text")
+ .HasComment("组织机构Id");
+
+ b.Property("Password")
+ .HasColumnType("text")
+ .HasComment("密码");
+
+ b.Property("UserName")
+ .HasColumnType("text")
+ .HasComment("用户名");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.User_Role", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property("RoleId")
+ .HasColumnType("text")
+ .HasComment("角色Id");
+
+ b.Property("UserId")
+ .HasColumnType("text")
+ .HasComment("用户Id");
+
+ b.HasKey("Id");
+
+ b.ToTable("User_Roles");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/AX.WebDrillServer/Migrations/20220909012500_InitialCreate.cs b/AX.WebDrillServer/Migrations/20220909012500_InitialCreate.cs
new file mode 100644
index 0000000..f27ea8a
--- /dev/null
+++ b/AX.WebDrillServer/Migrations/20220909012500_InitialCreate.cs
@@ -0,0 +1,159 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace AX.WebDrillServer.Migrations
+{
+ public partial class InitialCreate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Organizations",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", unicode: false, nullable: false, comment: "ID"),
+ CreationTime = table.Column(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
+ CreatorId = table.Column(type: "text", nullable: true, comment: "创建者 ID"),
+ LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
+ LastModifierId = table.Column(type: "text", nullable: true, comment: "最后修改者 ID"),
+ IsDeleted = table.Column(type: "boolean", nullable: false, comment: "是否删除"),
+ DeletionTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
+ DeleterId = table.Column(type: "text", nullable: true, comment: "删除者 ID"),
+ Code = table.Column(type: "text", nullable: true, comment: "组织机构编码"),
+ Name = table.Column(type: "text", nullable: true, comment: "组织机构名称"),
+ Level = table.Column(type: "integer", nullable: false, comment: "组织机构级别"),
+ Introductions = table.Column(type: "text", nullable: true, comment: "组织机构说明")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Organizations", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Premissions",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", unicode: false, nullable: false, comment: "ID"),
+ CreationTime = table.Column(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
+ CreatorId = table.Column(type: "text", nullable: true, comment: "创建者 ID"),
+ LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
+ LastModifierId = table.Column(type: "text", nullable: true, comment: "最后修改者 ID"),
+ IsDeleted = table.Column(type: "boolean", nullable: false, comment: "是否删除"),
+ DeletionTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
+ DeleterId = table.Column(type: "text", nullable: true, comment: "删除者 ID"),
+ ParentId = table.Column(type: "text", nullable: true, comment: "权限Id"),
+ Name = table.Column(type: "text", nullable: true, comment: "权限名称"),
+ Introductions = table.Column(type: "text", nullable: true, comment: "权限说明")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Premissions", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Role_Premission",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", unicode: false, nullable: false, comment: "ID"),
+ CreationTime = table.Column(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
+ CreatorId = table.Column(type: "text", nullable: true, comment: "创建者 ID"),
+ LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
+ LastModifierId = table.Column(type: "text", nullable: true, comment: "最后修改者 ID"),
+ IsDeleted = table.Column(type: "boolean", nullable: false, comment: "是否删除"),
+ DeletionTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
+ DeleterId = table.Column(type: "text", nullable: true, comment: "删除者 ID"),
+ RoleId = table.Column(type: "text", nullable: true, comment: "角色Id"),
+ PremissionId = table.Column(type: "text", nullable: true, comment: "权限Id")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Role_Premission", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Roles",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", unicode: false, nullable: false, comment: "ID"),
+ CreationTime = table.Column(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
+ CreatorId = table.Column(type: "text", nullable: true, comment: "创建者 ID"),
+ LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
+ LastModifierId = table.Column(type: "text", nullable: true, comment: "最后修改者 ID"),
+ IsDeleted = table.Column(type: "boolean", nullable: false, comment: "是否删除"),
+ DeletionTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
+ DeleterId = table.Column(type: "text", nullable: true, comment: "删除者 ID"),
+ ParentId = table.Column(type: "text", nullable: true, comment: "角色父级Id"),
+ Name = table.Column(type: "text", nullable: true, comment: "角色名称"),
+ Introductions = table.Column(type: "text", nullable: true, comment: "角色说明")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Roles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "User_Roles",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", unicode: false, nullable: false, comment: "ID"),
+ CreationTime = table.Column(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
+ CreatorId = table.Column(type: "text", nullable: true, comment: "创建者 ID"),
+ LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
+ LastModifierId = table.Column(type: "text", nullable: true, comment: "最后修改者 ID"),
+ IsDeleted = table.Column(type: "boolean", nullable: false, comment: "是否删除"),
+ DeletionTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
+ DeleterId = table.Column(type: "text", nullable: true, comment: "删除者 ID"),
+ UserId = table.Column(type: "text", nullable: true, comment: "用户Id"),
+ RoleId = table.Column(type: "text", nullable: true, comment: "角色Id")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_User_Roles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", unicode: false, nullable: false, comment: "ID"),
+ CreationTime = table.Column(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
+ CreatorId = table.Column(type: "text", nullable: true, comment: "创建者 ID"),
+ LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
+ LastModifierId = table.Column(type: "text", nullable: true, comment: "最后修改者 ID"),
+ IsDeleted = table.Column(type: "boolean", nullable: false, comment: "是否删除"),
+ DeletionTime = table.Column(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
+ DeleterId = table.Column(type: "text", nullable: true, comment: "删除者 ID"),
+ UserName = table.Column(type: "text", nullable: true, comment: "用户名"),
+ Password = table.Column(type: "text", nullable: true, comment: "密码"),
+ OrganizationId = table.Column(type: "text", nullable: true, comment: "组织机构Id")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Organizations");
+
+ migrationBuilder.DropTable(
+ name: "Premissions");
+
+ migrationBuilder.DropTable(
+ name: "Role_Premission");
+
+ migrationBuilder.DropTable(
+ name: "Roles");
+
+ migrationBuilder.DropTable(
+ name: "User_Roles");
+
+ migrationBuilder.DropTable(
+ name: "Users");
+ }
+ }
+}
diff --git a/AX.WebDrillServer/Migrations/ApplicationDbContextModelSnapshot.cs b/AX.WebDrillServer/Migrations/ApplicationDbContextModelSnapshot.cs
new file mode 100644
index 0000000..bfca0cb
--- /dev/null
+++ b/AX.WebDrillServer/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -0,0 +1,383 @@
+//
+using System;
+using AX.WebDrillServer.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace AX.WebDrillServer.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ partial class ApplicationDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.Organization", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("Code")
+ .HasColumnType("text")
+ .HasComment("组织机构编码");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("Introductions")
+ .HasColumnType("text")
+ .HasComment("组织机构说明");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property("Level")
+ .HasColumnType("integer")
+ .HasComment("组织机构级别");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasComment("组织机构名称");
+
+ b.HasKey("Id");
+
+ b.ToTable("Organizations");
+ });
+
+ modelBuilder.Entity("AX.WebDrillServer.Models.Premission", b =>
+ {
+ b.Property("Id")
+ .IsUnicode(false)
+ .HasColumnType("text")
+ .HasColumnOrder(0)
+ .HasComment("ID");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(1)
+ .HasComment("创建时间");
+
+ b.Property("CreatorId")
+ .HasColumnType("text")
+ .HasColumnOrder(2)
+ .HasComment("创建者 ID");
+
+ b.Property("DeleterId")
+ .HasColumnType("text")
+ .HasColumnOrder(7)
+ .HasComment("删除者 ID");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(6)
+ .HasComment("删除时间");
+
+ b.Property("Introductions")
+ .HasColumnType("text")
+ .HasComment("权限说明");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnOrder(5)
+ .HasComment("是否删除");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnOrder(3)
+ .HasComment("最后修改时间");
+
+ b.Property("LastModifierId")
+ .HasColumnType("text")
+ .HasColumnOrder(4)
+ .HasComment("最后修改者 ID");
+
+ b.Property