Compare commits

...

30 Commits

Author SHA1 Message Date
杨栋梁 b622bf3c2b 房间销毁结果类型 3 years ago
杨栋梁 1c64c7e633 添加解散房间接口 3 years ago
杨栋梁 bcd68a299d 时间问题修改 3 years ago
杨栋梁 1801a37c8d 通讯中时间改为以JavaScripts为准的毫秒 3 years ago
杨栋梁 a36354bb1e 房间中通讯时间赋值为持续时间单位(毫秒) 3 years ago
杨栋梁 840d81e105 开始演练数据没有加入到集合中问题处理 3 years ago
杨栋梁 f3578237b1 推演相关API优化 3 years ago
杨栋梁 037b547ff8 推演相关API 3 years ago
杨栋梁 5c171943ea 演练相关接口 3 years ago
杨栋梁 4b9f6028a5 进入房间,房间密码为空时处理问题修改 3 years ago
杨栋梁 69c696609a 字段没有重命名导致脚本错误 3 years ago
杨栋梁 9f976712be 加入房间离开房间数据类枚举类型改为属性 3 years ago
杨栋梁 99cb0162ea 房间成员变化信息的类与web端匹配 3 years ago
杨栋梁 dfb91ab5ce 进入房间历来房间通讯组中成员改变处理 3 years ago
杨栋梁 8c9e9e4117 房主进入房间不需要密码验证 3 years ago
杨栋梁 3c86364867 加入房间离开房间返回值类型修改 3 years ago
杨栋梁 80cd4b5f9a 上下线通知web端 3 years ago
杨栋梁 834b7071ee 加入房间离开房间接口实现 3 years ago
刘向辉 e04a05061f 修改枚举序列化时为大驼峰命名法 3 years ago
杨栋梁 49daae5180 创建房间接口相关信息修改 3 years ago
杨栋梁 620c6d39fa 创建房间传入类型修改 3 years ago
杨栋梁 4ce3a949e2 json 枚举转换使用intValue模式 3 years ago
杨栋梁 3dd567225d 创建房间返回结果赋值 3 years ago
杨栋梁 50291cd67c 创建房间接口处理 3 years ago
杨栋梁 dbc97d5bcf 建立跟web端通讯类型接口 3 years ago
杨栋梁 83d6dca543 修改项目相关配置 3 years ago
杨栋梁 7415357b91 用户交互类型修改 3 years ago
杨栋梁 3bb3fdbbc7 用户登录界面 3 years ago
杨栋梁 5418c06356 更新user role user-role表结构 3 years ago
杨栋梁 7fbb7cd2b5 项目初始化 3 years ago
  1. 270
      .gitignore
  2. 79
      AX.WebDrillServer/AX.WebDrillServer.csproj
  3. 28
      AX.WebDrillServer/AX.WebDrillServer.sln
  4. 74
      AX.WebDrillServer/Constants/Permissions.cs
  5. 165
      AX.WebDrillServer/Controllers/AccountsController.cs
  6. 266
      AX.WebDrillServer/Controllers/UsersController.cs
  7. 30
      AX.WebDrillServer/Data/ApplicationDbContext.cs
  8. 32
      AX.WebDrillServer/Data/ApplicationDbContextExtensions.cs
  9. 167
      AX.WebDrillServer/Data/ApplicationDbContextSeed.cs
  10. 15
      AX.WebDrillServer/Dtos/Account/RefreshTokeDto.cs
  11. 29
      AX.WebDrillServer/Dtos/Account/UserAccountDto.cs
  12. 41
      AX.WebDrillServer/Dtos/DtoBase.cs
  13. 11
      AX.WebDrillServer/Dtos/Organizations/OrganizationDto.cs
  14. 34
      AX.WebDrillServer/Dtos/Pagination.cs
  15. 30
      AX.WebDrillServer/Dtos/QueryOptions.cs
  16. 37
      AX.WebDrillServer/Dtos/Users/UserDto.cs
  17. 14
      AX.WebDrillServer/Dtos/Users/UserForCreateDto.cs
  18. 30
      AX.WebDrillServer/Dtos/Users/UserUpdateDto.cs
  19. 19
      AX.WebDrillServer/EntityConfigurations/EntityBaseConfiguration.cs
  20. 13
      AX.WebDrillServer/EntityConfigurations/OrganizationConfiguration.cs
  21. 17
      AX.WebDrillServer/EntityConfigurations/PremissionConfiguration.cs
  22. 17
      AX.WebDrillServer/EntityConfigurations/RoleConfiguration.cs
  23. 17
      AX.WebDrillServer/EntityConfigurations/Role_PremissionConfiguration.cs
  24. 15
      AX.WebDrillServer/EntityConfigurations/UserConfiguration.cs
  25. 21
      AX.WebDrillServer/EntityConfigurations/User_RoleConfiguration.cs
  26. 41
      AX.WebDrillServer/EntityMappers/EntityMapperConfig.cs
  27. 67
      AX.WebDrillServer/EntityMappers/Mappers.cs
  28. 33
      AX.WebDrillServer/Enums/OrganizationLevel.cs
  29. 11
      AX.WebDrillServer/Enums/SortType.cs
  30. 27
      AX.WebDrillServer/Errors/GlobalErrorCodes.cs
  31. 33
      AX.WebDrillServer/Extensions/CollectionExtensions.cs
  32. 92
      AX.WebDrillServer/Extensions/ControllerExtensions.cs
  33. 76
      AX.WebDrillServer/Extensions/DateTimeExtensions.cs
  34. 14
      AX.WebDrillServer/Extensions/EntityExtensions.cs
  35. 10
      AX.WebDrillServer/Extensions/HubExtensions.cs
  36. 10
      AX.WebDrillServer/Extensions/JsonExtensions.cs
  37. 17
      AX.WebDrillServer/Extensions/StringExtensions.cs
  38. 30
      AX.WebDrillServer/Helpers/DateTimeHelper.cs
  39. 92
      AX.WebDrillServer/Helpers/ExcelHelper.cs
  40. 27
      AX.WebDrillServer/Helpers/OrganizationHelper.cs
  41. 578
      AX.WebDrillServer/Hubs/FireDeductionHub.cs
  42. 29
      AX.WebDrillServer/Hubs/INotificationClient.cs
  43. 27
      AX.WebDrillServer/Hubs/ITaskChatClient.cs
  44. 67
      AX.WebDrillServer/Hubs/NotificationHub.cs
  45. 123
      AX.WebDrillServer/Hubs/TaskChatHub.cs
  46. BIN
      AX.WebDrillServer/License.dat
  47. 53
      AX.WebDrillServer/Middlewares/AESHelper.cs
  48. 37
      AX.WebDrillServer/Middlewares/FileUploadOperationFilter.cs
  49. 27
      AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonInputFormatter.cs
  50. 54
      AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonOutputFormatter.cs
  51. 25
      AX.WebDrillServer/Middlewares/Jwts/IJwtService.cs
  52. 173
      AX.WebDrillServer/Middlewares/Jwts/JwtClaimTypes.cs
  53. 33
      AX.WebDrillServer/Middlewares/Jwts/JwtOptions.cs
  54. 85
      AX.WebDrillServer/Middlewares/Jwts/JwtService.cs
  55. 18
      AX.WebDrillServer/Middlewares/LicenseFile.cs
  56. 57
      AX.WebDrillServer/Middlewares/LicenseInfo.cs
  57. 48
      AX.WebDrillServer/Middlewares/LicenseMiddleware.cs
  58. 13
      AX.WebDrillServer/Middlewares/NameUserIdProvider.cs
  59. 588
      AX.WebDrillServer/Middlewares/ObjectId.cs
  60. 16
      AX.WebDrillServer/Middlewares/ObjectIdGenerator.cs
  61. 125
      AX.WebDrillServer/Middlewares/RSAHelper.cs
  62. 107
      AX.WebDrillServer/Middlewares/ServerLicense.cs
  63. 420
      AX.WebDrillServer/Migrations/20220909065901_InitialCreate.Designer.cs
  64. 181
      AX.WebDrillServer/Migrations/20220909065901_InitialCreate.cs
  65. 418
      AX.WebDrillServer/Migrations/ApplicationDbContextModelSnapshot.cs
  66. 44
      AX.WebDrillServer/Models/EntityBase.cs
  67. 43
      AX.WebDrillServer/Models/IEntityBase.cs
  68. 21
      AX.WebDrillServer/Models/Organization.cs
  69. 17
      AX.WebDrillServer/Models/Premission.cs
  70. 19
      AX.WebDrillServer/Models/Role.cs
  71. 15
      AX.WebDrillServer/Models/Role_Premission.cs
  72. 21
      AX.WebDrillServer/Models/User.cs
  73. 17
      AX.WebDrillServer/Models/UserGroup.cs
  74. 15
      AX.WebDrillServer/Models/UserGroup_Role.cs
  75. 16
      AX.WebDrillServer/Models/UserGroup_User.cs
  76. 20
      AX.WebDrillServer/Models/User_Role.cs
  77. 279
      AX.WebDrillServer/Program.cs
  78. 30
      AX.WebDrillServer/Properties/launchSettings.json
  79. 71
      AX.WebDrillServer/Services/Emails/EmailService.cs
  80. 19
      AX.WebDrillServer/Services/Emails/IEmailService.cs
  81. 46
      AX.WebDrillServer/Services/FireDeductionHub/Drill.cs
  82. 15
      AX.WebDrillServer/Services/FireDeductionHub/DrillShowData.cs
  83. 8
      AX.WebDrillServer/Services/FireDeductionHub/EndDrillData.cs
  84. 8
      AX.WebDrillServer/Services/FireDeductionHub/EndDrillResultData.cs
  85. 31
      AX.WebDrillServer/Services/FireDeductionHub/FireDeductionRoom.cs
  86. 56
      AX.WebDrillServer/Services/FireDeductionHub/FireDeductionRoomEnums.cs
  87. 15
      AX.WebDrillServer/Services/FireDeductionHub/FireDeductionUser.cs
  88. 11
      AX.WebDrillServer/Services/FireDeductionHub/PlayerOrder.cs
  89. 9
      AX.WebDrillServer/Services/FireDeductionHub/RoomCreateData.cs
  90. 8
      AX.WebDrillServer/Services/FireDeductionHub/RoomCreateResultData.cs
  91. 8
      AX.WebDrillServer/Services/FireDeductionHub/RoomDisposeResultData.cs
  92. 9
      AX.WebDrillServer/Services/FireDeductionHub/RoomEnterData.cs
  93. 8
      AX.WebDrillServer/Services/FireDeductionHub/RoomEnterResultData.cs
  94. 8
      AX.WebDrillServer/Services/FireDeductionHub/RoomLeaveData.cs
  95. 8
      AX.WebDrillServer/Services/FireDeductionHub/RoomLeaveResultData.cs
  96. 196
      AX.WebDrillServer/Services/FireDeductionHub/RoomManager.cs
  97. 10
      AX.WebDrillServer/Services/FireDeductionHub/RoomSendInfo.cs
  98. 25
      AX.WebDrillServer/Services/FireDeductionHub/RoomShowInfo.cs
  99. 9
      AX.WebDrillServer/Services/FireDeductionHub/RoommateChangeData.cs
  100. 10
      AX.WebDrillServer/Services/FireDeductionHub/StartDrillData.cs
  101. Some files were not shown because too many files have changed in this diff Show More

270
.gitignore vendored

@ -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

79
AX.WebDrillServer/AX.WebDrillServer.csproj

@ -0,0 +1,79 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Configurations>Debug;Release;Test</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<OutputPath>bin\Debug\</OutputPath>
<!-- NoWarn>1701;1702;1591</NoWarn -->
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Test|AnyCPU'">
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<OutputPath>bin\Test\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<!-- NoWarn>1701;1702;1591</NoWarn -->
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<OutputPath>bin\Release\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<!-- NoWarn>1701;1702;1591</NoWarn -->
</PropertyGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<CopyToPublishDirectory>false</CopyToPublishDirectory>
</Content>
</ItemGroup>
<!--<ItemGroup>
<Content Update="appsettings.json">
<CopyToPublishDirectory>true</CopyToPublishDirectory>
</Content>
</ItemGroup>-->
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="ExpressionDebugger" Version="2.2.1" />
<PackageReference Include="MailKit" Version="3.3.0" />
<PackageReference Include="Mapster" Version="7.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.8" />
<!-- PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.7" / -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.7" ExcludeAssets="All" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
<PackageReference Include="NPOI" Version="2.5.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations\" />
</ItemGroup>
</Project>

28
AX.WebDrillServer/AX.WebDrillServer.sln

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32825.248
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AX.WebDrillServer", "AX.WebDrillServer.csproj", "{B594C541-C030-412E-8B89-D38C24765D2D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Test|Any CPU = Test|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B594C541-C030-412E-8B89-D38C24765D2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B594C541-C030-412E-8B89-D38C24765D2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B594C541-C030-412E-8B89-D38C24765D2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B594C541-C030-412E-8B89-D38C24765D2D}.Release|Any CPU.Build.0 = Release|Any CPU
{B594C541-C030-412E-8B89-D38C24765D2D}.Test|Any CPU.ActiveCfg = Test|Any CPU
{B594C541-C030-412E-8B89-D38C24765D2D}.Test|Any CPU.Build.0 = Test|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C069A07B-08CC-43A1-A87B-6D54D901FB11}
EndGlobalSection
EndGlobal

74
AX.WebDrillServer/Constants/Permissions.cs

@ -0,0 +1,74 @@
namespace AX.WebDrillServer.Constants
{
/// <summary>
/// 系统权限。
/// </summary>
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 单位管理
}
}

165
AX.WebDrillServer/Controllers/AccountsController.cs

@ -0,0 +1,165 @@
using AX.WebDrillServer.Data;
using AX.WebDrillServer.Dtos.Account;
using AX.WebDrillServer.Dtos.Users;
using AX.WebDrillServer.EntityMappers;
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
{
/// <summary>
/// 账号控制器
/// </summary>
[ApiController]
[Authorize]
[Produces("application/json")]
[Route("api/[controller]")]
public class AccountsController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly IMemoryCache _memoryCache;
private readonly IOptionsMonitor<JwtOptions> _jwtOptions;
private readonly IJwtService _jwtService;
public AccountsController(
ApplicationDbContext context,
IMemoryCache memoryCache,
IOptionsMonitor<JwtOptions> jwtOptions,
IJwtService jwtService)
{
_context = context;
_memoryCache = memoryCache;
_jwtOptions = jwtOptions;
_jwtService = jwtService;
}
#region APIs
/// <summary>
/// 登录
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[HttpPost("[action]")]
public async Task<ActionResult<UserAccountDto>> SignIn([FromBody] UserForCreateDto dto)
{
var UserList = _context.Users.Where(u => u.UserName == dto.UserName).ToList();
if (!UserList.Any())
{
return Problem("用户名或密码错误!");
}
else
{
User user = null!;
bool ok = false;
foreach (var item in UserList)
{
user = item;
if (!Verify(dto.Password, user.Password))
{
continue;
}
else
{
ok = true;
break;
}
}
if (!ok)
{
return Problem("用户名或密码错误");
}
else
{
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.Subject, user!.Id),
new Claim(JwtClaimTypes.Name, user.UserName ?? string.Empty),
new Claim(JwtClaimTypes.OrganizationId, user.OrganizationId ?? string.Empty),
//new Claim(JwtClaimTypes.OrganizationCode, user.Organization?.Code ?? string.Empty),
};
var identity = new ClaimsIdentity(claims);
var token = _jwtService.Create(identity);
var refreshToken = Guid.NewGuid().ToString("N");
var jwtOptions = _jwtOptions.CurrentValue;
await Task.Run(
() =>
{
_memoryCache.Set(refreshToken, dto.UserName, DateTimeOffset.Now.AddMinutes(jwtOptions.RefreshExpires));
});
var result = user.ToDto(token, refreshToken, jwtOptions.Expires);
return Ok(result);
}
}
}
/// <summary>
/// 登出系统。
/// </summary>
/// <returns></returns>
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status200OK)]
[HttpPost("SignOut")]
public ActionResult AccountSignOut()
{
//TODO: 把 JWT 放入黑名单,其它地方则验证黑名单,将来再处理
return Ok();
}
/// <summary>
/// 刷新令牌。
/// </summary>
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[HttpPost("[action]")]
public ActionResult<RefreshTokeDto> RefreshToken([FromBody] RefreshTokeDto dto)
{
if (string.IsNullOrEmpty(dto.RefreshToken) ||
string.IsNullOrEmpty(dto.Token))
return BadRequest();
#if RELEASE
//校验缓存中是否有该刷新令牌
if (!_memoryCache.TryGetValue<string>(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 RefreshTokeDto()
{
Token = newToken,
RefreshToken = dto.RefreshToken,
Expires = jwtOptions.Expires
};
return Ok(result);
}
#endregion
}
}

266
AX.WebDrillServer/Controllers/UsersController.cs

@ -0,0 +1,266 @@
using AX.WebDrillServer.Data;
using AX.WebDrillServer.Dtos.Users;
using AX.WebDrillServer.EntityMappers;
using AX.WebDrillServer.Extensions;
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 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.WebDrillServer.Controllers
{
/// <summary>
/// 用户控制器
/// </summary>
[ApiController]
[Authorize]
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly ApplicationDbContext _context;
public UsersController(ApplicationDbContext context)
{
_context = context;
}
#region APIs
/// <summary>
/// 创建用户
/// </summary>
/// <param name="dto">新增内容</param>
/// <returns></returns>
[HttpPost]
public async Task<ActionResult<UserDto>> Create([FromBody, BindRequired] UserForCreateDto dto)
{
try
{
var user = await _context.Users.SingleOrDefaultAsync(u => u.UserName == dto.UserName);
if (user != null)//用户名不允许重复
{
return BadRequest("已经存在该用户!");
}
User model = new User();
var Organization = await _context.Organizations.Where(o => o.Code == "1-1-1-1-1").FirstOrDefaultAsync();
if (Organization is null) return BadRequest("组织机构初始化失败!");
var Role = await _context.Roles.Where(r => r.Name == RoleTypes..ToString()).FirstOrDefaultAsync();
if (Role is null) return BadRequest("角色始化失败!");
// 映射普通属性
model.UserName = dto.UserName;
model.Password = HashPassword(dto.Password);
model.OrganizationId = Organization.Id;
model.CreatorId = model.Id;
await _context.Users.AddAsync(model);
// 分配默认角色
User_Role user_Role1 = new User_Role
{
UserId = model.Id,
User = model,
RoleId = Role.Id,
Role = Role,
};
await _context.User_Roles.AddAsync(user_Role1);
await _context.SaveChangesAsync();
if (model is null) return BadRequest();
var result = model.ToDto();
result.OrganizationName = Organization.Name;
return CreatedAtAction(nameof(Create), result);
}
catch (ValidationException e)
{
return ValidationProblem(e.Message);
}
catch (Exception e)
{
return Problem(e.Message);
}
}
/// <summary>
/// 获取用户列表
/// </summary>
/// <returns></returns>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[HttpGet]
public async Task<ActionResult<List<UserDto>>> GetAll()
{
List<User> query = new List<User>();
try
{
await Task.Run(() => query = _context.Users.Where(a => a.IsDeleted == false).ToList());
}
catch (Exception e)
{
return Problem(e.Message);
}
return Ok(query);
}
/// <summary>
/// 获取用户
/// </summary>
/// <param name="id">用户 ID</param>
/// <returns></returns>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetOneById([FromRoute] string id)
{
try
{
var user = await _context.Users.AsNoTracking().SingleOrDefaultAsync(a => a.Id == id);
return Ok(user!);
}
catch (Exception e)
{
return Problem(e.Message);
}
}
/// <summary>
/// 获取用户
/// </summary>
/// <param name="name">用户 UserName</param>
/// <returns></returns>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpGet("name={name}")]
public async Task<ActionResult<UserDto>> GetOneByName([FromRoute] string name)
{
try
{
var user = await _context.Users.AsNoTracking().SingleOrDefaultAsync(a => a.UserName == name);
return Ok(user!);
}
catch (Exception e)
{
return Problem(e.Message);
}
}
/// <summary>
/// 修改用户
/// </summary>
/// <param name="id">用户 ID</param>
/// <param name="dto">更新内容</param>
/// <returns></returns>
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPatch("{id}")]
public async Task<ActionResult<UserUpdateDto>> Update([FromRoute] string id, [FromBody, BindRequired] UserUpdateDto dto)
{
try
{
var model = await _context.Users.Where(e => e.Id == id)
.SingleOrDefaultAsync();
if (model == default) return NotFound("用户不存在");
var userId = HttpContext.User.FindFirstValue(JwtClaimTypes.Subject);
// 映射普通属性
UserMapper.MapTo(dto, model);
Console.WriteLine(_context.Entry(model).State);
model.LastModifierId = model.Id;
model.LastModificationTime = DateTime.UtcNow;
await _context.SaveChangesAsync();
return NoContent();
}
catch (Exception e)
{
return Problem(e.Message);
}
}
/// <summary>
/// 重置用户密码
/// </summary>
/// <param name="id">用户 ID</param>
/// <returns></returns>
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPatch("{id}/Password")]
public async Task<ActionResult<UserDto>> ResetPassword([FromRoute] string id)
{
try
{
var model = await _context.Users.Where(e => e.Id == id)
.SingleOrDefaultAsync();
if (model == default) return NotFound();
var userId = HttpContext.User.FindFirstValue(JwtClaimTypes.Subject);
model.Password = HashPassword(ApplicationDbContextSeed.InitPassword);
model.LastModifierId = userId;
model.LastModificationTime = DateTime.UtcNow;
await _context.SaveChangesAsync();
return NoContent();
}
catch (Exception e)
{
return Problem(e.Message);
}
}
/// <summary>
/// 删除用户
/// </summary>
/// <param name="id">用户 ID</param>
/// <returns></returns>
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpDelete("{id}")]
public async Task<ActionResult> Delete([FromRoute] string id)
{
try
{
var model = await _context.Users.Where(e => e.Id == id)
.SingleOrDefaultAsync();
if (model == default) return NotFound();
var userId = HttpContext.User.FindFirstValue(JwtClaimTypes.Subject);
model.IsDeleted = true;
model.LastModifierId = userId;
model.LastModificationTime = DateTime.UtcNow;
model.DeletionTime = DateTime.UtcNow;
await _context.SaveChangesAsync();
return NoContent();
}
catch (Exception e)
{
return Problem(e.Message);
}
}
#endregion APIs
}
}

30
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<ApplicationDbContext> options)
: base(options)
{ }
public DbSet<User> Users { get; set; } = null!;
public DbSet<Organization> Organizations { get; set; } = null!;
public DbSet<Role> Roles { get; set; } = null!;
public DbSet<Premission> Premissions { get; set; } = null!;
public DbSet<User_Role> User_Roles { get; set; } = null!;
public DbSet<Role_Premission> 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());
}
}
}

32
AX.WebDrillServer/Data/ApplicationDbContextExtensions.cs

@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Data
{
public static class ApplicationDbContextExtensions
{
/// <summary>
/// 运行迁移
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder SetupDatabase(this WebApplication app)
{
using var scope = app.Services.CreateScope();
var serviceProvidoer = scope.ServiceProvider;
try
{
var context = serviceProvidoer.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
//context.Database.EnsureCreated();
context.SeedDefaultData();
}
catch (Exception e)
{
var logger = serviceProvidoer.GetRequiredService<ILogger<Program>>();
logger.LogError(e, "在设置数据库过程中发生了错误!");
}
return app;
}
}
}

167
AX.WebDrillServer/Data/ApplicationDbContextSeed.cs

@ -0,0 +1,167 @@
using AX.WebDrillServer.Enums;
using AX.WebDrillServer.Models;
using System.Reflection;
using static BCrypt.Net.BCrypt;
namespace AX.WebDrillServer.Data
{
public enum RoleTypes
{
= -1,
,
访,
,
,
,
,
,
}
public static class ApplicationDbContextSeed
{
public const string SuperAdminId = "0";
public const string InitUserId = "1";
public const string InitPassword = "12345678";
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();
}
private static void SeedRoles(this ApplicationDbContext context)
{
using var transaction = context.Database.BeginTransaction();
foreach (RoleTypes item in Enum.GetValues(typeof(RoleTypes)))
{
var role = new Role()
{
Name = item.ToString(),
Introductions = $"这是{item}",
};
context.Roles.Add(role);
}
context.SaveChanges();
transaction.Commit();
}
private static void SeedOrganizations(this ApplicationDbContext context)
{
using var transaction = context.Database.BeginTransaction();
var org = new Organization()
{
Code = "1",
Name = "部局",
Level = OrganizationLevel.Department,
};
context.Organizations.Add(org);
var org1 = new Organization()
{
Code = "1-1",
Name = "XXX总队",
Level = OrganizationLevel.Corps,
};
context.Organizations.Add(org1);
var org2 = new Organization()
{
Code = "1-1-1",
Name = "XXX支队",
Level = OrganizationLevel.Brigade,
};
context.Organizations.Add(org2);
var org3 = new Organization()
{
Code = "1-1-1-1",
Name = "XXX大队",
Level = OrganizationLevel.Battalion,
};
context.Organizations.Add(org3);
var org4 = new Organization()
{
Code = "1-1-1-1-1",
Name = "XXX中队",
Level = OrganizationLevel.Squadron,
};
context.Organizations.Add(org4);
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),
OrganizationId = context.Organizations.Where(o => o.Code == "1").ToList()[0].Id,
User_Roles = new List<User_Role>(),
};
//分配角色
User_Role user_Role = new User_Role
{
UserId = SuperAdminId,
User = u,
RoleId = context.Roles.Where(r => r.Name == RoleTypes..ToString()).ToList()[0].Id,
Role = context.Roles.Where(r => r.Name == RoleTypes..ToString()).ToList()[0],
};
context.User_Roles.Add(user_Role);
u.User_Roles.Add(user_Role);
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()
{
UserName = $"用户{i}",
Password = HashPassword(InitPassword),
OrganizationId = context.Organizations.Where(o => o.Code == "1-1-1-1-1").ToList()[0].Id,
User_Roles = new List<User_Role>(),
};
if (i == 1) u.Id = "1";
//分配角色
User_Role user_Role = new User_Role
{
UserId = u.Id,
User = u,
RoleId = context.Roles.Where(r => r.Name == RoleTypes..ToString()).ToList()[0].Id,
Role = context.Roles.Where(r => r.Name == RoleTypes..ToString()).ToList()[0],
};
u.User_Roles.Add(user_Role);
context.User_Roles.Add(user_Role);
if (i > 2)
{
User_Role user_Role1 = new User_Role
{
UserId = u.Id,
User = u,
RoleId = context.Roles.Where(r => r.Name == ((RoleTypes)(i - 1)).ToString()).ToList()[0].Id,
Role = context.Roles.Where(r => r.Name == ((RoleTypes)(i - 1)).ToString()).ToList()[0],
};
u.User_Roles.Add(user_Role1);
context.User_Roles.Add(user_Role1);
}
context.Users.Add(u);
}
context.SaveChanges();
transaction.Commit();
}
}
}

15
AX.WebDrillServer/Dtos/Account/RefreshTokeDto.cs

@ -0,0 +1,15 @@
namespace AX.WebDrillServer.Dtos.Account
{
public class RefreshTokeDto
{
public string Token { get; set; } = null!;
/// <summary>
/// 刷新令牌。
/// </summary>
public string RefreshToken { get; set; } = null!;
/// <summary>
/// 到期时间。
/// </summary>
public double Expires { get; set; } = 20;
}
}

29
AX.WebDrillServer/Dtos/Account/UserAccountDto.cs

@ -0,0 +1,29 @@
namespace AX.WebDrillServer.Dtos.Account
{
/// <summary>
/// 身份信息
/// </summary>
public class UserAccountDto
{
/// <summary>
/// 用户Id
/// </summary>
public string Id { get; set; } = null!;
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; } = null!;
/// <summary>
/// JWT。
/// </summary>
public string Token { get; set; } = null!;
/// <summary>
/// 刷新令牌。
/// </summary>
public string RefreshToken { get; set; } = null!;
/// <summary>
/// 到期时间。
/// </summary>
public double Expires { get; set; } = 20;
}
}

41
AX.WebDrillServer/Dtos/DtoBase.cs

@ -0,0 +1,41 @@
using System.Text.Json.Serialization;
namespace AX.WebDrillServer.Dtos
{
/// <summary>
/// DTO 基类
/// </summary>
public class DtoBase
{
/// <summary>
/// ID
/// </summary>
[JsonPropertyOrder(-1)]
public string Id { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
[JsonPropertyOrder(1)]
public DateTime? CreationTime { get; set; }
/// <summary>
/// 创建者 ID
/// </summary>
[JsonPropertyOrder(2)]
public string? CreatorId { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
[JsonPropertyOrder(3)]
public DateTime? LastModificationTime { get; set; }
/// <summary>
/// 最后修改者 ID
/// </summary>
[JsonPropertyOrder(4)]
public string? LastModifierId { get; set; }
}
}

11
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; }
}
}

34
AX.WebDrillServer/Dtos/Pagination.cs

@ -0,0 +1,34 @@
namespace AX.WebDrillServer.Dtos
{
/// <summary>
/// 分页结果集。
/// </summary>
/// <typeparam name="T">DtoBase</typeparam>
public class Pagination<T> where T : DtoBase
{
/// <summary>
/// 当前页。
/// </summary>
public int PageNumber { get; set; }
/// <summary>
/// 每页个数。
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 总页数。
/// </summary>
public int TotalPages { get; set; }
/// <summary>
/// 总个数。
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 查询集合。
/// </summary>
public IEnumerable<T>? Items { get; set; }
}
}

30
AX.WebDrillServer/Dtos/QueryOptions.cs

@ -0,0 +1,30 @@
using AX.WebDrillServer.Enums;
namespace AX.WebDrillServer.Dtos
{
/// <summary>
/// 查询条件。
/// </summary>
public class QueryOptionsBase
{
/// <summary>
/// 分页页数。
/// </summary>
public int? PageNumber { get; set; }
/// <summary>
/// 一页多少条。
/// </summary>
public int? PageSize { get; set; }
/// <summary>
/// 排序属性。
/// </summary>
public string? SortProperty { get; set; }
/// <summary>
/// 排序类型
/// </summary>
public SortType? SortType { get; set; }
}
}

37
AX.WebDrillServer/Dtos/Users/UserDto.cs

@ -0,0 +1,37 @@
namespace AX.WebDrillServer.Dtos.Users
{
/// <summary>
/// 用户
/// </summary>
public class UserDto : DtoBase
{
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; } = null!;
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = null!;
/// <summary>
/// 是否已启用
/// </summary>
public bool? Enabled { get; set; }
/// <summary>
/// 组织机构 ID
/// </summary>
public string? OrganizationId { get; set; }
/// <summary>
/// 组织机构名称
/// </summary>
public string? OrganizationName { get; set; }
/// <summary>
/// 角色Id
/// </summary>
public string? RoleId { get; set; }
/// <summary>
/// 角色名称
/// </summary>
public string? RoleName { get; set; }
}
}

14
AX.WebDrillServer/Dtos/Users/UserForCreateDto.cs

@ -0,0 +1,14 @@
namespace AX.WebDrillServer.Dtos.Users
{
public class UserForCreateDto
{
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; } = null!;
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = null!;
}
}

30
AX.WebDrillServer/Dtos/Users/UserUpdateDto.cs

@ -0,0 +1,30 @@
namespace AX.WebDrillServer.Dtos.Users
{
public class UserUpdateDto
{
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; } = null!;
/// <summary>
/// 是否已启用
/// </summary>
public bool? Enabled { get; set; }
/// <summary>
/// 组织机构 ID
/// </summary>
public string? OrganizationId { get; set; }
/// <summary>
/// 组织机构名称
/// </summary>
public string? OrganizationName { get; set; }
/// <summary>
/// 角色Id
/// </summary>
public string? RoleId { get; set; }
/// <summary>
/// 角色名称
/// </summary>
public string? RoleName { get; set; }
}
}

19
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<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : EntityBase
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.Property(e => e.Id)
.IsUnicode(false)
.HasValueGenerator<ObjectIdGenerator>();
builder.HasQueryFilter(e => !e.IsDeleted);
}
}
}

13
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<Organization>
{
public override void Configure(EntityTypeBuilder<Organization> builder)
{
base.Configure(builder);
}
}
}

17
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<Premission>
{
public override void Configure(EntityTypeBuilder<Premission> builder)
{
base.Configure(builder);
//builder.HasMany(u => u.Roles)
// .WithMany(r => r.Users)
// .UsingEntity<UserRole>();
}
}
}

17
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<Role>
{
public override void Configure(EntityTypeBuilder<Role> builder)
{
base.Configure(builder);
//builder.HasMany(u => u.Roles)
// .WithMany(r => r.Users)
// .UsingEntity<UserRole>();
}
}
}

17
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<Role_Premission>
{
public override void Configure(EntityTypeBuilder<Role_Premission> builder)
{
base.Configure(builder);
//builder.HasMany(u => u.Roles)
// .WithMany(r => r.Users)
// .UsingEntity<UserRole>();
}
}
}

15
AX.WebDrillServer/EntityConfigurations/UserConfiguration.cs

@ -0,0 +1,15 @@
using AX.WebDrillServer.Models;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace AX.WebDrillServer.EntityConfigurations
{
public class UserConfiguration : EntityBaseConfiguration<User>
{
public override void Configure(EntityTypeBuilder<User> builder)
{
base.Configure(builder);
builder.HasIndex(x => x.UserName) //添加唯一索引
.IsUnique(true);
}
}
}

21
AX.WebDrillServer/EntityConfigurations/User_RoleConfiguration.cs

@ -0,0 +1,21 @@
using AX.WebDrillServer.Models;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace AX.WebDrillServer.EntityConfigurations
{
public class User_RoleConfiguration : EntityBaseConfiguration<User_Role>
{
public override void Configure(EntityTypeBuilder<User_Role> builder)
{
base.Configure(builder);
builder.HasOne(u => u.User)
.WithMany(r => r.User_Roles)
.HasForeignKey(e => e.UserId);
builder.HasOne(u => u.Role)
.WithMany(r => r.User_Roles)
.HasForeignKey(e => e.RoleId);
}
}
}

41
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<DtoBase, EntityBase>
.ForType()
.Ignore(dest => dest.CreationTime)
.Map(dest => dest.LastModificationTime,
src => src.LastModificationTime.ToUtc());
TypeAdapterConfig<EntityBase, DtoBase>
.ForType()
.Map(dest => dest.CreationTime,
src => src.CreationTime.ToLocalTime())
.Map(dest => dest.LastModificationTime,
src => src.LastModificationTime.ToLocal());
//映射配置
UserMapperConfig.Initialize();
OrginzationConfig.Initialize();
}
}
}

67
AX.WebDrillServer/EntityMappers/Mappers.cs

@ -0,0 +1,67 @@
using AX.WebDrillServer.Data;
using AX.WebDrillServer.Dtos.Account;
using AX.WebDrillServer.Dtos.Users;
using AX.WebDrillServer.Models;
using Mapster;
namespace AX.WebDrillServer.EntityMappers
{
public static class UserMapper
{
public static UserAccountDto ToDto(this User model, string token, string refreshToken, double expires)
{
var dto = model.Adapt<UserAccountDto>();
dto.Token = token;
dto.RefreshToken = refreshToken;
dto.Expires = expires;
return dto;
}
public static User ToModel(this UserDto dto) =>
dto.Adapt<User>();
public static User ToModel(this UserUpdateDto dto) =>
dto.Adapt<User>();
public static UserDto ToDto(this User model) =>
model.Adapt<UserDto>();
public static void MapTo(this UserDto dto, User model) =>
dto.Adapt(model);
public static void MapTo(this UserUpdateDto dto, User model) =>
dto.Adapt(model);
}
public static class UserMapperConfig
{
public static void Initialize()
{
//TypeAdapterConfig<User, UserUpdateDto>
// .ForType()
// .Map(dest => dest.Username,
// src => src.UserName);
//TypeAdapterConfig<UserDto, User>
// .ForType()
// .Map(dest => dest.Id,
// src => src.Id);
}
}
public static class OrginzationConfig
{
public static void Initialize()
{
//TypeAdapterConfig<User, UserDto>
// .ForType()
// .Map(dest => dest.OrganizationName,
// src => src.Organization != default ?
// src.Organization.Name :
// default);
//TypeAdapterConfig<User, IdentityDto>
// .ForType()
// .Map(dest => dest.Name,
// src => src.Name);
}
}
}

33
AX.WebDrillServer/Enums/OrganizationLevel.cs

@ -0,0 +1,33 @@
namespace AX.WebDrillServer.Enums
{
/// <summary>
/// 组织机构级别(corps: 总队; brigade: 支队; battalion: 大队; squadron: 消防站)
/// </summary>
public enum OrganizationLevel
{
/// <summary>
/// 部局。
/// </summary>
Department = -1,
/// <summary>
/// 总队。
/// </summary>
Corps = 0,
/// <summary>
/// 支队。
/// </summary>
Brigade = 1,
/// <summary>
/// 大队。
/// </summary>
Battalion = 2,
/// <summary>
/// 中队。
/// </summary>
Squadron = 3
}
}

11
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
}
}

27
AX.WebDrillServer/Errors/GlobalErrorCodes.cs

@ -0,0 +1,27 @@
namespace AX.WebDrillServer.Errors
{
/// <summary>
/// 表示错误码。
/// </summary>
/// <remarks>
/// 约定 60x 系列属于许可证错误;
/// 约定 70x 系列属于文件错误;
/// </remarks>
public static class GlobalErrorCodes
{
/// <summary>程序未经许可或许可证到期!</summary>
public const int E600 = 600;
/// <summary>上传文件失败,因为未通过数据完整性校验,文件数据有可能丢失或被篡改!</summary>
public const int E700 = 700;
/// <summary>
/// 错误码相对应的消息。
/// </summary>
public static readonly Dictionary<int, string> Messages = new()
{
{ E600, "程序未经许可或许可证到期!" },
{ E700, "上传文件失败,因为未通过数据完整性校验,文件数据有可能丢失或被篡改!" }
};
}
}

33
AX.WebDrillServer/Extensions/CollectionExtensions.cs

@ -0,0 +1,33 @@
using System.Diagnostics.CodeAnalysis;
namespace AX.WebDrillServer.Extensions
{
public static class CollectionExtensions
{
#region Collection
/// <summary>
/// 判断集合是否是 Null 或 Empty.
/// </summary>
/// <param name="enumerable"></param>
/// <returns></returns>
public static bool IsNullOrEmpty<T>([NotNullWhen(false)] this IEnumerable<T>? enumerable)
{
if (enumerable is null) return true;
return !enumerable.Any();
}
/// <summary>
/// 判断集合是否是有值.
/// </summary>
/// <param name="enumerable"></param>
/// <returns></returns>
public static bool HasValue<T>([NotNullWhen(false)] this IEnumerable<T>? enumerable)
{
if (enumerable is null) return false;
return enumerable.Any();
}
#endregion Collection
}
}

92
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
{
/// <summary>
/// 排序
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
/// <param name="query">IQueryable</param>
/// <param name="sortProperty">排序所依据的属性</param>
/// <param name="sortType">升序或降序</param>
/// <returns></returns>
public static IOrderedQueryable<T> Sort<T>(
this IQueryable<T> query,
string sortProperty = "Id",
SortType? sortType = SortType.ASC) where T : EntityBase
{
var param = Expression.Parameter(typeof(T), "x");
var body = sortProperty.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);
var methodName = sortType switch
{
SortType.DESC => "OrderByDescending",
_ => "OrderBy"
};
return (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), body.Type },
query.Expression,
Expression.Lambda(body, param))
);
}
/// <summary>
/// 分页
/// </summary>
/// <typeparam name="TDto">GET 用 DTO</typeparam>
/// <param name="query">IQueryable</param>
/// <param name="number">页数</param>
/// <param name="size">每页项数</param>
/// <returns></returns>
public static async ValueTask<Pagination<TDto>> PaginateAsync<TDto>(
this IQueryable<EntityBase> 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<TDto>()).ToListAsync();
var result = new Pagination<TDto>
{
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);
}
}

76
AX.WebDrillServer/Extensions/DateTimeExtensions.cs

@ -0,0 +1,76 @@
namespace AX.WebDrillServer.Extensions
{
public static class DateTimeExtensions
{
/// <summary>
/// 转换为 UTC 时间
/// </summary>
/// <param name="dateTime">DateTime 值</param>
/// <returns>转换后的 UTC 时间</returns>
public static DateTime? ToUtc(this DateTime? dateTime)
{
if (!dateTime.HasValue) return null;
return dateTime.Value.Kind != DateTimeKind.Utc ?
dateTime.Value.ToUniversalTime() :
dateTime.Value;
}
/// <summary>
/// 转换为 UTC 时间
/// </summary>
/// <param name="dateTime">DateTime 值</param>
/// <returns>转换后的 UTC 时间</returns>
public static DateTime ToUtc(this DateTime dateTime) =>
dateTime.Kind != DateTimeKind.Utc ?
dateTime.ToUniversalTime() :
dateTime;
/// <summary>
/// 转换为本地时间
/// </summary>
/// <param name="dateTime">DateTime 值</param>
/// <returns>转换后的本地时间</returns>
public static DateTime? ToLocal(this DateTime? dateTime)
{
if (!dateTime.HasValue) return null;
return dateTime.Value.Kind == DateTimeKind.Utc ?
dateTime.Value.ToLocalTime() :
dateTime.Value;
}
/// <summary>
/// 转换为本地时间
/// </summary>
/// <param name="dateTime">DateTime 值</param>
/// <returns>转换后的本地时间</returns>
public static DateTime ToLocal(this DateTime dateTime) =>
dateTime.Kind == DateTimeKind.Utc ?
dateTime.ToLocalTime() :
dateTime;
/// <summary>
/// 在Javascript中,时间保存的是从UnixEpoch开始,
/// 即从1970年1月1日00:00:00.000开始到现在的毫秒数
/// 所以,C#时间到Javascript时间的转换
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
public static long ToUnixTicks(this DateTime time)
{
return (long)TimeSpan.FromTicks(time.Ticks - DateTime.UnixEpoch.Ticks).TotalMilliseconds - TimeZoneInfo.Local.GetUtcOffset(time).Hours * 60 * 60 * 1000;
}
/// <summary>
/// 将Unix时间戳转换为时间
/// </summary>
/// <param name="timeStamp"></param>
/// <returns></returns>
public static DateTime GetDateTimeFromUnix(long timeStamp)
{
DateTime startDt = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), TimeZoneInfo.Local);
long lTime = timeStamp * 10000;
TimeSpan toNow = new TimeSpan(lTime);
return startDt.Add(toNow);
}
}
}

14
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;
}
}
}

10
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;
}
}

10
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);
}
}

17
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);
}
}

30
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;
}
}
}

92
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
{
/// <summary>
/// 将excel中的数据导入到DataTable中
/// </summary>
/// <param name="fileStream"></param>
/// <param name="fileName">excel文件路径</param>
/// <param name="isFirstRowColumn">第一行是否是DataTable的列名</param>
/// <returns>返回的DataTable</returns>
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;
}
}
}
}

27
AX.WebDrillServer/Helpers/OrganizationHelper.cs

@ -0,0 +1,27 @@

namespace AX.WebDrillServer.Helpers
{
public class OrganizationHelper
{
/// <summary>
/// 生成组织机构代码
/// </summary>
/// <param name="parentCode">父组织代码</param>
/// <param name="lastCodeOfSiblings">同级组织代码最大值</param>
/// <returns>新的组织机构代码</returns>
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;
}
}
}

578
AX.WebDrillServer/Hubs/FireDeductionHub.cs

@ -0,0 +1,578 @@
using AX.WebDrillServer.Data;
using AX.WebDrillServer.Extensions;
using AX.WebDrillServer.Middlewares.Jwts;
using AX.WebDrillServer.Models;
using AX.WebDrillServer.Services.FireDeductionHub;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.VisualBasic;
using NPOI.SS.Util;
using System;
using System.Collections.Concurrent;
using System.Data;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AX.WebDrillServer.Hubs
{
//[Authorize]
public class FireDeductionHub : Hub
{
private readonly RoomManager roomManager;
private readonly ApplicationDbContext _dbContext;
private readonly ILogger<FireDeductionHub> _logger;
public FireDeductionHub(
ApplicationDbContext dbContext,
ILogger<FireDeductionHub> logger,
RoomManager roomManager)
{
_dbContext = dbContext;
_logger = logger;
this.roomManager = roomManager;
}
#region 房间相关API
/// <summary>
/// 获取当前房间列表
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public List<RoomShowInfo> CallServer_GetRoomList(string userId)
{
return roomManager.GetAllRooms();
}
/// <summary>
/// 根据Socket连接Id获取当前用户
/// </summary>
/// <param name="connectionId"></param>
/// <returns></returns>
public FireDeductionUser? CallServer_GetUser(string connectionId)
{
return roomManager.GetUserByConnectionId(connectionId);
}
/// <summary>
/// 创建房间
/// </summary>
/// <param name="createInfo"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<RoomCreateResultData> CallServer_CreateRoom(RoomCreateData createInfo)
{
try
{
RoomCreateResultData resultData = new RoomCreateResultData()!;
var user = roomManager.GetUser(createInfo.UserId);
if (user == null)//查询不到创建房间的用户
{
resultData.RoomCreateResult = RoomCreateResult.NoUserExist;
return resultData;
}
if (string.IsNullOrEmpty(createInfo.Name))//房间名称为空
{
resultData.RoomCreateResult = RoomCreateResult.RoomNameEmpty;
return resultData;
}
if (roomManager.IsRoomNameSame(user.UserId, createInfo.Name))//同一用户创建了相同名称的房间
{
resultData.RoomCreateResult = RoomCreateResult.RoomReName;
return resultData;
}
FireDeductionRoom room = new()
{
Owner = createInfo.UserId,
OwnerName = user.UserName,
RoomId = Guid.NewGuid().ToString(),
RoomName = createInfo.Name,
Password = createInfo.Password
};
roomManager.AddRoom(room);
//移除逻辑:创建房间直接进入房间的处理逻辑由web端主动发起进入房间
//user.RoomId = room.RoomId;
//room.Users.Add(user);
//await Groups.AddToGroupAsync(user.ConnectionId, room.RoomId);
await Clients.All.SendAsync("callWeb_refreshRoomList", roomManager.GetAllRooms());
resultData.CreatedRoom = roomManager.RoomToInfo(room);
resultData.RoomCreateResult = RoomCreateResult.Success;
return resultData;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 进入房间
/// </summary>
/// <param name="enterInfo"></param>
/// <returns></returns>
public async Task<RoomEnterResultData> CallServer_EnterRoom(RoomEnterData enterInfo)
{
try
{
RoomEnterResultData resultData = new();
var room = roomManager.GetRoom(enterInfo.RoomId);
if (room == null)//根据id查不到房间
{
resultData.EnterResult = RoomEnterResult.RoomError;
return resultData;
}
var user = roomManager.GetUser(enterInfo.UserId);
if (user == null)//根据id查不到用户
{
resultData.EnterResult = RoomEnterResult.NoUserExist;
return resultData;
}
if (enterInfo.UserId == room.Owner)//房主免密
{
enterInfo.Password = room.Password;
}
if (string.IsNullOrEmpty(room.Password) || room.Password == enterInfo.Password)
{
//roomManager.AddUser(user);
roomManager.AddUserToRoom(user, room.RoomId);
user.RoomId = room.RoomId;
RoommateChangeData data = new()
{
ChangeType = RoommateChangeType.Add,
UserData = user,
AllUsers = room.Users
};
//添加到通讯组中
await Groups.AddToGroupAsync(user.ConnectionId, room.RoomId);
//通知web端刷新房间用户列表
await Clients.Group(room.RoomId).SendAsync("callWeb_changeRoomate", data);
//如果演练已经开始用户加入到演练的用户列表中
if (room.Drills.Count > 0)
{
room.Drills[room.Drills.Count - 1].Users.Add(user);
}
resultData.RoomId = room.RoomId;
resultData.EnterResult = RoomEnterResult.Success;
return resultData;
}
else
{//密码错误
resultData.EnterResult = RoomEnterResult.RoomError;
return resultData;
}
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// 离开房间
/// </summary>
/// <param name="leaveInfo"></param>
/// <returns></returns>
public async Task<RoomLeaveResultData> CallServer_LeaveRoom(RoomLeaveData leaveInfo)
{
try
{
RoomLeaveResultData resultData = new();
var room = roomManager.GetRoom(leaveInfo.RoomId);
if (room == null)//根据id查不到房间
{
resultData.LeaveResult = RoomLeaveResult.RoomError;
return resultData;
}
var user = roomManager.GetUser(leaveInfo.UserId);
if (user == null)//根据id查不到用户
{
resultData.LeaveResult = RoomLeaveResult.NoUserExist;
return resultData;
}
//roomManager.RemoveUser(leaveInfo.UserId);
roomManager.ReMoveUserFromRoom(user, room.RoomId);
user.RoomId = "";
RoommateChangeData data = new()
{
ChangeType = RoommateChangeType.Remove,
UserData = user,
AllUsers = room.Users
};
//从通讯组中移出
await Groups.RemoveFromGroupAsync(user.ConnectionId, room.RoomId);
//通知web端刷新房间用户列表
await Clients.Group(room.RoomId).SendAsync("callWeb_changeRoomate", data);
resultData.RoomId = room.RoomId;
resultData.LeaveResult = RoomLeaveResult.Success;
return resultData;
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// 解散房间
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<RoomDisposeResultData> CallServer_DisposeRoom()
{
RoomDisposeResultData resultData = new();
var userId = Context.UserIdentifier;
try
{
var room = roomManager.GetRoomByUserId(userId!);
if (room == null)
{
resultData.Result = RoomDisposeResult.Failed;
resultData.RoomId = "";
return resultData;
}
else
{
if (room.Owner == userId)
{
resultData.Result = RoomDisposeResult.Success;
resultData.RoomId = room.RoomId;
await Clients.Group(room.RoomId).SendAsync("CallWeb_disposeRoom", resultData);
//房间中用户的房间信息重置
foreach (var item in room.Users)
{
item.RoomId = "";
}
//房间缓存移除该房间
roomManager.RemoveRoom(room.RoomId);
//通知web端刷新房间列表
await Clients.All.SendAsync("callWeb_refreshRoomList", roomManager.GetAllRooms());
return resultData;
}
else
{
resultData.Result = RoomDisposeResult.Failed;
resultData.RoomId = room.RoomId;
return resultData;
}
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
#endregion
#region 推演API
public async Task<StartDrillResultData> CallServer_StartDrill(StartDrillData startData)
{
StartDrillResultData resultData = new();
var userId = Context.UserIdentifier;
try
{
var room = roomManager.GetRoomByUserId(userId!);
if (room == null)
{
resultData.ResultType = DrillResult.Failed;
return resultData;
}
else
{
if (room.Owner == userId)//判断是否是房主
{
resultData.ResultType = DrillResult.Success;
resultData.StartData = new()
{
Name = startData.Name,
StartTime = DateTime.Now.ToUnixTicks(),
};
room.State = RoomState.Playing;
Drill drill = new()
{
DrillName = startData.Name,
StartTime = DateTime.Now.ToUnixTicks(),
DrillState = DrillState.Playing
};
foreach (var item in room.Users)
{
drill.Users.Add(item);
}
drill.Id = Guid.NewGuid().ToString();
drill.RoomId = room.RoomId;
room.Drills.Add(drill);
await Clients.Group(room.RoomId).SendAsync("CallWeb_StartDrill", resultData);
return resultData;
}
else
{
resultData.ResultType = DrillResult.Failed;
return resultData;
}
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public async Task<EndDrillResultData> CallServer_EndDrill()
{
EndDrillResultData resultData = new();
var userId = Context.UserIdentifier;
try
{
var room = roomManager.GetRoomByUserId(userId!);
if (room == null)
{
resultData.ResultType = DrillResult.Failed;
return resultData;
}
else
{
if (room.Owner == userId)
{
resultData.ResultType = DrillResult.Success;
resultData.EndData = new()
{
Name = room.Drills[0].DrillName!,
EndTime = DateTime.Now.ToUnixTicks(), //Ticks是一个以0 .1纳秒为单位的时间戳
};
room.State = RoomState.Over;
room.Drills[room!.Drills.Count - 1].EndTime = resultData.EndData.EndTime;
room.Drills[room!.Drills.Count - 1].DrillState = DrillState.Over;
await Clients.Group(room.RoomId).SendAsync("CallWeb_EndDrill", resultData);
return resultData;
}
else
{
resultData.ResultType = DrillResult.Failed;
return resultData;
}
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public DrillShowData CallServer_GetDrillShowData()
{
var userId = Context.UserIdentifier;
try
{
var room = roomManager.GetRoomByUserId(userId!);
if (room == null)
{
throw new Exception("用户房间信息有误!");
}
else
{
if (room.Drills.Count > 0)
{
//计算DrillTime
DrillShowData resultData = new()
{
DrillTime = room.Drills[room!.Drills.Count - 1].GetDrillLastTime(),
Name = room.Drills[room!.Drills.Count - 1].DrillName,
Owner = roomManager.GetUser(room!.Owner)!,
EndTime = room.Drills[room!.Drills.Count - 1].EndTime,
StartTime = room.Drills[room!.Drills.Count - 1].StartTime,
Players = room.Drills[room!.Drills.Count - 1].Users,
};
return resultData;
}
else
{
throw new Exception("演练还没开始!");
}
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public async Task CallServer_SendOrder(PlayerOrder data)
{
var userId = Context.UserIdentifier;
try
{
var room = roomManager.GetRoomByUserId(userId!);
if (room != null)
{
data.UserId = userId!;
//持续时间
data.Time = room.Drills[room!.Drills.Count - 1].GetDrillLastTime();
//转发给房间中的其他人
await Clients.GroupExcept(room.RoomId, Context.ConnectionId).SendAsync("CallWeb_sendOthersOrder", data);
//存储一份数据
var info = new RoomSendInfo()
{
UserId = userId!,
Time = data.Time,
RoomId = room.RoomId,
InfoData = data.Data.ToJson(),
};
room.Drills[room!.Drills.Count - 1].Infos.Add(info);
roomManager.SaveRoomSendInfo(info);
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public List<PlayerOrder> CallServer_GetCurrentState()
{
var userId = Context.UserIdentifier;
var resultData = new List<PlayerOrder>();
try
{
var room = roomManager.GetRoomByUserId(userId!);
if (room != null)
{
var data = room.Drills[room.Drills.Count - 1].Infos;
foreach (var item in data)
{
PlayerOrder result = new PlayerOrder
{
UserId = item.UserId!,
Data = item.InfoData!,
Time = item.Time,
};
resultData.Add(result);
}
return resultData;
}
else
{
throw (new Exception("房间信息有误!"));
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
#endregion
#region 上线下线处理
/// <summary>
/// 上线处理
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
try
{
var userId = Context.UserIdentifier;
var userName = Context.GetNameOfCurrentUser();
var user = roomManager.GetUser(userId!);
if (user != null)
{
user.Online = true;
if (user.ConnectionId != Context.ConnectionId)
user.ConnectionId = Context.ConnectionId;
var room = roomManager.GetRoom(user.UserId);
if (room != null && !string.IsNullOrEmpty(user.RoomId))
{
//断线重连
RoommateChangeData data = new()
{
ChangeType = RoommateChangeType.OnLine,
UserData = user,
};
//通知web端刷新房间用户状态
await Clients.Group(room.RoomId).SendAsync("callWeb_changeRoomate", data);
}
}
else
{
user = new FireDeductionUser
{
ConnectionId = Context.ConnectionId,
UserId = userId!,
UserName = userName!
};
roomManager.AddUser(user);
}
_logger.LogInformation("[{userId}][{name}] connected", userId, userName);
await base.OnConnectedAsync();
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// 离线处理
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
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)
{
var user = roomManager.GetUser(userId!);
if (user != null && !string.IsNullOrEmpty(user.RoomId))
{
user.Online = false;
var room = roomManager.GetRoom(user.RoomId);
if (room != null)
{
//断线
RoommateChangeData data = new()
{
ChangeType = RoommateChangeType.OffLine,
UserData = user,
};
//通知web端刷新房间用户状态
await Clients.Group(room.RoomId).SendAsync("callWeb_changeRoomate", data);
}
}
}
await base.OnDisconnectedAsync(exception);
_logger.LogInformation("[{userId}][{name}] 断开了连接", userId, userName);
}
catch (Exception)
{
throw;
}
}
}
#endregion
}

29
AX.WebDrillServer/Hubs/INotificationClient.cs

@ -0,0 +1,29 @@

namespace AX.WebDrillServer.Hubs
{
public interface INotificationClient
{
///// <summary>
///// 接收通知
///// </summary>
///// <param name="content">通知内容</param>
///// <returns></returns>
//Task ReceiveNotification(string content);
///// <summary>
///// 接收通知
///// </summary>
///// <param name="dto">接收通知 DTO</param>
///// <returns></returns>
//Task ReceiveNotification(ReceiveNotificationDto dto);
///// <summary>
///// 接收通知
///// </summary>
///// <param name="senderName">发送者名称</param>
///// <param name="content">通知内容</param>
///// <returns></returns>
//Task ReceiveNotification(string? senderName, string content);
}
}

27
AX.WebDrillServer/Hubs/ITaskChatClient.cs

@ -0,0 +1,27 @@
namespace AX.WebDrillServer.Hubs
{
public interface ITaskChatClient
{
// /// <summary>
// /// 接收消息
// /// </summary>
// /// <param name="senderName">发送者名称</param>
// /// <param name="message">消息内容</param>
// /// <returns></returns>
// Task ReceiveMessage(string? senderName, string message);
// /// <summary>
// /// 接收消息
// /// </summary>
// /// <param name="dto">接收通知 DTO</param>
// /// <returns></returns>
// Task ReceiveMessage(ReceiveMessageDto dto);
// /// <summary>
// /// 接收在线列表
// /// </summary>
// /// <param name="members">在线用户列表</param>
// /// <returns></returns>
// Task ReceiveMembers(IDictionary<string, string> members);
}
}

67
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<INotificationClient>
{
private readonly ILogger<NotificationHub> _logger;
public NotificationHub(
ILogger<NotificationHub> 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;
}
}
}
}

123
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<ITaskChatClient>
{
// key: taskId, value: { key: user.Id, value: user.Name }
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, string>> _allRoomUsers = new();
private readonly ApplicationDbContext _dbContext;
private readonly ILogger<TaskChatHub> _logger;
public TaskChatHub(
ApplicationDbContext dbContext,
ILogger<TaskChatHub> 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<string, string>());
//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
}
}

BIN
AX.WebDrillServer/License.dat

Binary file not shown.

53
AX.WebDrillServer/Middlewares/AESHelper.cs

@ -0,0 +1,53 @@
using System.Security.Cryptography;
using System.Text;
namespace AX.WebDrillServer.Middlewares
{
/// <summary>
/// AES 256bit 加解密辅助类。
/// </summary>
public static class AESHelper
{
private const string Key = "Hn&~Z8pfPmy5FqkDjhgr9v$*xte4jh9C";
private const string IV = "KS1wTrhg&@Wa^uDW";
/// <summary>
/// AES 解密指定的数据。
/// </summary>
/// <param name="data">要解密的数据</param>
/// <param name="key">Key</param>
/// <param name="iv">IV</param>
/// <returns></returns>
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();
}
}
}

37
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<string, OpenApiSchema> schema = new()
{
["fileName"] = new OpenApiSchema
{
Description = "Select file",
Type = "string",
Format = "binary"
}
};
Dictionary<string, OpenApiMediaType> content = new()
{
["multipart/form-data"] = new OpenApiMediaType
{
Schema = new OpenApiSchema { Type = "object", Properties = schema }
}
};
operation.RequestBody = new OpenApiRequestBody() { Content = content };
}
}
}
}

27
AX.WebDrillServer/Middlewares/JsonFormatters/RawJsonInputFormatter.cs

@ -0,0 +1,27 @@
using System.Text;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
/// <summary>
/// 用于读取原生的 JSON 数据。
/// </summary>
public class RawJsonInputFormatter : TextInputFormatter
{
public RawJsonInputFormatter()
{
SupportedMediaTypes.Add("application/json");
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
public override async Task<InputFormatterResult> 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);
}
}

54
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<string> 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<string>);
}
}

25
AX.WebDrillServer/Middlewares/Jwts/IJwtService.cs

@ -0,0 +1,25 @@
using System.Security.Claims;
namespace AX.WebDrillServer.Middlewares.Jwts
{
/// <summary>
/// JWT 服务器接口。
/// </summary>
public interface IJwtService
{
/// <summary>
/// 创建一个 JWT。
/// </summary>
/// <param name="identity"></param>
/// <returns></returns>
string Create(ClaimsIdentity identity);
/// <summary>
/// 验证 JWT 是否有效。
/// </summary>
/// <param name="token"></param>
/// <param name="principal"></param>
/// <returns></returns>
bool Validate(string token, out ClaimsPrincipal principal);
}
}

173
AX.WebDrillServer/Middlewares/Jwts/JwtClaimTypes.cs

@ -0,0 +1,173 @@
namespace AX.WebDrillServer.Middlewares.Jwts
{
/// <summary>
/// 用于 JWT 的声明类型。
/// </summary>
public static class JwtClaimTypes
{
/// <summary>Unique Identifier for the End-User at the Issuer.</summary>
public const string Subject = "sub";
/// <summary>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.</summary>
public const string Name = "name";
/// <summary>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.</summary>
public const string GivenName = "given_name";
/// <summary>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.</summary>
public const string FamilyName = "family_name";
/// <summary>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.</summary>
public const string MiddleName = "middle_name";
/// <summary>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.</summary>
public const string NickName = "nickname";
/// <summary>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</summary>
/// <remarks>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 </remarks>
public const string PreferredUserName = "preferred_username";
/// <summary>URL of the End-User's profile page. The contents of this Web page SHOULD be about the End-User.</summary>
public const string Profile = "profile";
/// <summary>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.</summary>
/// <remarks>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.</remarks>
public const string Picture = "picture";
/// <summary>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.</summary>
public const string WebSite = "website";
/// <summary>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</summary>
public const string Email = "email";
/// <summary>"true" if the End-User's e-mail address has been verified; otherwise "false".</summary>
/// <remarks>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.</remarks>
public const string EmailVerified = "email_verified";
/// <summary>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.</summary>
public const string Gender = "gender";
/// <summary>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.</summary>
public const string BirthDate = "birthdate";
/// <summary>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.</summary>
public const string ZoneInfo = "zoneinfo";
/// <summary>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.</summary>
public const string Locale = "locale";
/// <summary>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.</summary>
public const string PhoneNumber = "phone_number";
/// <summary>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.</summary>
/// <remarks>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.</remarks>
public const string PhoneNumberVerified = "phone_number_verified";
/// <summary>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 </summary>
public const string Address = "address";
/// <summary>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.</summary>
public const string Audience = "aud";
/// <summary>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.</summary>
public const string Issuer = "iss";
/// <summary>The time before which the JWT MUST NOT be accepted for processing, specified as the number of seconds from 1970-01-01T0:0:0Z</summary>
public const string NotBefore = "nbf";
/// <summary>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</summary>
public const string Expiration = "exp";
/// <summary>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.</summary>
public const string UpdatedAt = "updated_at";
/// <summary>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</summary>
public const string IssuedAt = "iat";
/// <summary>Authentication Methods References. JSON array of strings that are identifiers for authentication methods used in the authentication.</summary>
public const string AuthenticationMethod = "amr";
/// <summary>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.</summary>
public const string SessionId = "sid";
/// <summary>
/// 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.
/// </summary>
public const string AuthenticationContextClassReference = "acr";
/// <summary>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.</summary>
public const string AuthenticationTime = "auth_time";
/// <summary>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.</summary>
public const string AuthorizedParty = "azp";
/// <summary> 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.</summary>
public const string AccessTokenHash = "at_hash";
/// <summary>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.</summary>
public const string AuthorizationCodeHash = "c_hash";
/// <summary>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.</summary>
public const string Nonce = "nonce";
/// <summary>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.</summary>
public const string JwtId = "jti";
/// <summary>Defines a set of event statements that each may add additional claims to fully describe a single logical event that has occurred.</summary>
public const string Events = "events";
/// <summary>OAuth 2.0 Client Identifier valid at the Authorization Server.</summary>
public const string ClientId = "client_id";
/// <summary>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.</summary>
public const string Scope = "scope";
/// <summary>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.</summary>
public const string Actor = "act";
/// <summary>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.</summary>
public const string MayAct = "may_act";
/// <summary>
/// an identifier
/// </summary>
public const string Id = "id";
/// <summary>
/// The identity provider
/// </summary>
public const string IdentityProvider = "idp";
/// <summary>
/// The role
/// </summary>
public const string Role = "role";
/// <summary>
/// The reference token identifier
/// </summary>
public const string ReferenceTokenId = "reference_token_id";
/// <summary>
/// The confirmation
/// </summary>
public const string Confirmation = "cnf";
/// <summary>
/// 组织机构编号。
/// </summary>
public const string OrganizationId = "oid";
/// <summary>
/// 组织机构代码。
/// </summary>
public const string OrganizationCode = "ocode";
}
}

33
AX.WebDrillServer/Middlewares/Jwts/JwtOptions.cs

@ -0,0 +1,33 @@
namespace AX.WebDrillServer.Middlewares.Jwts
{
/// <summary>
/// JWT 配置项。
/// </summary>
public class JwtOptions
{
/// <summary>
/// Secret。
/// </summary>
public string Secret { get; set; } = null!;
/// <summary>
/// Issuer。
/// </summary>
public string Issuer { get; set; } = null!;
/// <summary>
/// Audience。
/// </summary>
public string Audience { get; set; } = null!;
/// <summary>
/// Expires。
/// </summary>
public double Expires { get; set; }
/// <summary>
/// Refresh Expires。
/// </summary>
public double RefreshExpires { get; set; }
}
}

85
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
{
/// <summary>
/// JWT 服务。
/// </summary>
public class JwtService : IJwtService
{
private readonly IOptionsMonitor<JwtOptions> options;
public JwtService(IOptionsMonitor<JwtOptions> options)
{
this.options = options;
//var jwtOptions = options.CurrentValue;
}
/// <summary>
/// 创建一个 JWT。
/// </summary>
/// <param name="identity"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 验证 JWT 是否有效。
/// </summary>
/// <param name="token"></param>
/// <param name="principal"></param>
/// <returns></returns>
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;
}
}
}
}

18
AX.WebDrillServer/Middlewares/LicenseFile.cs

@ -0,0 +1,18 @@
namespace AX.WebDrillServer.Middlewares
{
/// <summary>
/// 许可证文件。
/// </summary>
public class LicenseFile
{
/// <summary>
/// 公钥。
/// </summary>
public string? PublicKey { get; set; }
/// <summary>
/// 许可证信息。
/// </summary>
public LicenseInfo? LicenseInfo { get; set; }
}
}

57
AX.WebDrillServer/Middlewares/LicenseInfo.cs

@ -0,0 +1,57 @@
namespace AX.WebDrillServer.Middlewares
{
/// <summary>
/// 许可证信息。
/// </summary>
public class LicenseInfo
{
/// <summary>
/// 注册码。
/// </summary>
public string? DeviceId { get; set; }
/// <summary>
/// 许可证类型。
/// </summary>
/// <remarks>
/// 0 - 按单机;
/// 1 - 按机构;
/// </remarks>
public int LicenseType { get; set; }
/// <summary>
/// 是否试用。
/// </summary>
public bool IsTrial { get; set; }
/// <summary>
/// 激活码。
/// </summary>
public string? ActivationCode { get; set; }
/// <summary>
/// 激活起始时间。
/// </summary>
public DateTimeOffset StartTime { get; set; }
/// <summary>
/// 激活结束时间。
/// </summary>
public DateTimeOffset EndTime { get; set; }
/// <summary>
/// 该许可证是否还可用。
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// 备注。扩展用。
/// </summary>
public string? Tag { get; set; }
/// <summary>
/// 单位名称。
/// </summary>
public string? CompanyName { get; set; }
}
}

48
AX.WebDrillServer/Middlewares/LicenseMiddleware.cs

@ -0,0 +1,48 @@
using AX.WebDrillServer.Errors;
using System.Text.Json;
namespace AX.WebDrillServer.Middlewares
{
/// <summary>
/// 许可证中间件,用于对程序进行许可认证。
/// </summary>
public class LicenceMiddleware
{
private readonly RequestDelegate next;
/// <summary>
/// 创建一个许可证中间件实例。
/// </summary>
/// <param name="next"></param>
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);
}
}
/// <summary>
/// 许可证中间件扩展类。
/// </summary>
public static class LicenceMiddlewareExtensions
{
/// <summary>
/// 使用许可证中间件。
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseLicence(this IApplicationBuilder builder) =>
builder.UseMiddleware<LicenceMiddleware>();
}
}

13
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);
}
}

588
AX.WebDrillServer/Middlewares/ObjectId.cs

@ -0,0 +1,588 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Security;
namespace AX.WebDrillServer.Middlewares
{
/// <summary>
/// 表示一个对象 Id。
/// </summary>
/// <remarks>来自 MongoDB 的 ObjectId 算法。</remarks>
public struct ObjectId : IComparable<ObjectId>, IEquatable<ObjectId>
{
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;
/// <summary>
/// 创建新的 ObjectId 实例。
/// </summary>
/// <param name="bytes"></param>
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);
}
/// <summary>
/// 创建新的 ObjectId 实例。
/// </summary>
/// <param name="bytes"></param>
/// <param name="index"></param>
internal ObjectId(byte[] bytes, int index)
{
FromByteArray(bytes, index, out a, out b, out c);
}
/// <summary>
/// 创建新的 ObjectId 实例。
/// </summary>
/// <param name="timestamp">The timestamp (expressed as a DateTime).</param>
/// <param name="machine">The machine hash.</param>
/// <param name="pid">The PID.</param>
/// <param name="increment">The increment.</param>
public ObjectId(DateTime timestamp, int machine, short pid, int increment)
: this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
{
}
/// <summary>
/// 创建新的 ObjectId 实例。
/// </summary>
/// <param name="timestamp">The timestamp.</param>
/// <param name="machine">The machine hash.</param>
/// <param name="pid">The PID.</param>
/// <param name="increment">The increment.</param>
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;
}
/// <summary>
/// 创建新的 ObjectId 实例。
/// </summary>
/// <param name="value">The value.</param>
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);
}
/// <summary>
/// 获得空实例。
/// </summary>
public static ObjectId Empty
{
get { return emptyInstance; }
}
/// <summary>
/// 获得时间戳。
/// </summary>
public int Timestamp
{
get { return a; }
}
/// <summary>
/// 获得机器码。
/// </summary>
public int Machine
{
get { return (b >> 8) & 0xffffff; }
}
/// <summary>
/// 获得 PID。
/// </summary>
public short Pid
{
get { return (short)(((b << 8) & 0xff00) | ((c >> 24) & 0x00ff)); }
}
/// <summary>
/// 获得增量值。
/// </summary>
public int Increment
{
get { return c & 0xffffff; }
}
/// <summary>
/// 获得创建时间。
/// </summary>
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;
}
/// <summary>
/// 生成一个新的 ObjectId。
/// </summary>
public static ObjectId NewId()
{
return NewId(GetTimestampFromDateTime(DateTime.UtcNow));
}
/// <summary>
/// 根据指定的时间戳,生成一个新的 ObjectId。
/// </summary>
public static ObjectId NewId(DateTime timestamp)
{
return NewId(GetTimestampFromDateTime(timestamp));
}
/// <summary>
/// 根据指定的时间戳,生成一个新的 ObjectId。
/// </summary>
public static ObjectId NewId(int timestamp)
{
int increment = Interlocked.Increment(ref staticIncrement) & 0x00ffffff; // 只使用低位 3 字节
return new ObjectId(timestamp, staticMachine, staticPid, increment);
}
/// <summary>
/// 把 ObjectId 的各组件打包成一个字节数组。
/// </summary>
/// <param name="timestamp">The timestamp.</param>
/// <param name="machine">The machine hash.</param>
/// <param name="pid">The PID.</param>
/// <param name="increment">The increment.</param>
/// <returns>A byte array.</returns>
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;
}
/// <summary>
/// 分析字符串,转换为 ObjectId 实例。
/// </summary>
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);
}
}
/// <summary>
/// 尝试分析字符串,转换为 ObjectId 实例。
/// </summary>
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;
}
/// <summary>
/// 把一个字节数组解包成 ObjectId 各组件值。
/// </summary>
/// <param name="bytes">A byte array.</param>
/// <param name="timestamp">The timestamp.</param>
/// <param name="machine">The machine hash.</param>
/// <param name="pid">The PID.</param>
/// <param name="increment">The increment.</param>
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
}
/// <summary>
/// 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.
/// </summary>
[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];
}
/// <summary>
/// 比较 ObjectId 大小。
/// </summary>
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);
}
/// <summary>
/// 比较相等性。
/// </summary>
public bool Equals(ObjectId rhs)
{
return
a == rhs.a &&
b == rhs.b &&
c == rhs.c;
}
/// <summary>
/// 比较相等性。
/// </summary>
public override bool Equals(object? obj)
{
return obj is not null &&
obj is ObjectId id &&
Equals(id);
}
/// <summary>
/// 获得哈希码。
/// </summary>
public override int GetHashCode()
{
int hash = 17;
hash = 37 * hash + a.GetHashCode();
hash = 37 * hash + b.GetHashCode();
hash = 37 * hash + c.GetHashCode();
return hash;
}
/// <summary>
/// 把 ObjectId 转换为字节数组。
/// </summary>
public byte[] ToByteArray()
{
var bytes = new byte[12];
ToByteArray(bytes, 0);
return bytes;
}
/// <summary>
/// 把 ObjectId 转换为字节数组。
/// </summary>
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);
}
/// <summary>
/// 转换为字符串。
/// </summary>
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);
}
/// <summary>
/// 分析 16 进制字符串,转换为等价的字节数组。
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 把整型转换为 16 进制字符。
/// </summary>
/// <param name="value"></param>
/// <returns>The hex character.</returns>
private static char ToHexChar(int value)
{
return (char)(value + (value < 10 ? '0' : 'a' - 10));
}
/*
/// <summary>
/// 把字节数组转换为 16 进制字符串。
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
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);
}
*/
/// <summary>
/// 把 DateTime 值转换为 UTC 时间(特殊处理 MinValue 和 MaxValue)。
/// </summary>
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();
}
/// <summary>
/// 尝试分析 16 进制字符串,转换为等价的字节数组。
/// </summary>
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;
}
}
}

16
AX.WebDrillServer/Middlewares/ObjectIdGenerator.cs

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ValueGeneration;
namespace AX.WebDrillServer.Middlewares
{
/// <summary>
/// ObjectId 生成器。
/// </summary>
public class ObjectIdGenerator : ValueGenerator<string>
{
public override bool GeneratesTemporaryValues => false;
public override string Next(EntityEntry entry) =>
ObjectId.NewId().ToString();
}
}

125
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
{
/// <summary>
/// 非对称RSA加密辅助类。
/// </summary>
/// <remarks>
/// 切记:公钥验签。
/// </remarks>
public static class RSAHelper
{
/// <summary>
/// 根据公钥对数据签名进行验证。
/// </summary>
/// <param name="publicKey">公钥</param>
/// <param name="data">要验证的数据</param>
/// <param name="signdata">数据签名</param>
/// <param name="hashAlgorithm">散列算法</param>
/// <param name="padding">填充方式</param>
/// <returns></returns>
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);
}
}
}
}

107
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
{
/// <summary>
/// 服务器许可证。
/// </summary>
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;
/// <summary>
/// 初始化。
/// </summary>
/// <returns></returns>
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<LicenseFile>(json);
licenseFile = deserializedLicenseFile ?? licenseFile;
if (VerifyData())
initialized = true;
}
}
catch
{
//吞掉异常
}
return initialized;
}
/// <summary>
/// 校验许可证有效期。
/// </summary>
/// <returns></returns>
public static bool VerifyExpires()
{
if (initialized)
{
//校验有效期
var now = DateTimeOffset.Now;
return licenseFile?.LicenseInfo?.StartTime <= now ||
licenseFile?.LicenseInfo?.EndTime <= now;
}
return false;
}
/// <summary>
/// 校验许可证签名和激活码。
/// </summary>
/// <returns></returns>
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;
}
}
}

420
AX.WebDrillServer/Migrations/20220909065901_InitialCreate.Designer.cs generated

@ -0,0 +1,420 @@
// <auto-generated />
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("20220909065901_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<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<string>("Code")
.HasColumnType("text")
.HasComment("组织机构编码");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<string>("Introductions")
.HasColumnType("text")
.HasComment("组织机构说明");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<int>("Level")
.HasColumnType("integer")
.HasComment("组织机构级别");
b.Property<string>("Name")
.HasColumnType("text")
.HasComment("组织机构名称");
b.HasKey("Id");
b.ToTable("Organizations");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Premission", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<string>("Introductions")
.HasColumnType("text")
.HasComment("权限说明");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("Name")
.HasColumnType("text")
.HasComment("权限名称");
b.Property<string>("ParentId")
.HasColumnType("text")
.HasComment("权限Id");
b.HasKey("Id");
b.ToTable("Premissions");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Role", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<string>("Introductions")
.HasColumnType("text")
.HasComment("角色说明");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("Name")
.HasColumnType("text")
.HasComment("角色名称");
b.Property<string>("ParentId")
.HasColumnType("text")
.HasComment("角色父级Id");
b.HasKey("Id");
b.ToTable("Roles");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Role_Premission", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("PremissionId")
.HasColumnType("text")
.HasComment("权限Id");
b.Property<string>("RoleId")
.HasColumnType("text")
.HasComment("角色Id");
b.HasKey("Id");
b.ToTable("Role_Premission");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("OrganizationId")
.HasColumnType("text")
.HasComment("组织机构Id");
b.Property<string>("Password")
.HasColumnType("text")
.HasComment("密码");
b.Property<string>("UserName")
.HasColumnType("text")
.HasComment("用户名");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User_Role", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text")
.HasComment("角色Id");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text")
.HasComment("用户Id");
b.HasKey("Id");
b.HasIndex("RoleId");
b.HasIndex("UserId");
b.ToTable("User_Roles");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User_Role", b =>
{
b.HasOne("AX.WebDrillServer.Models.Role", "Role")
.WithMany("User_Roles")
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AX.WebDrillServer.Models.User", "User")
.WithMany("User_Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Role", b =>
{
b.Navigation("User_Roles");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User", b =>
{
b.Navigation("User_Roles");
});
#pragma warning restore 612, 618
}
}
}

181
AX.WebDrillServer/Migrations/20220909065901_InitialCreate.cs

@ -0,0 +1,181 @@
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<string>(type: "text", unicode: false, nullable: false, comment: "ID"),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
CreatorId = table.Column<string>(type: "text", nullable: true, comment: "创建者 ID"),
LastModificationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
LastModifierId = table.Column<string>(type: "text", nullable: true, comment: "最后修改者 ID"),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, comment: "是否删除"),
DeletionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
DeleterId = table.Column<string>(type: "text", nullable: true, comment: "删除者 ID"),
Code = table.Column<string>(type: "text", nullable: true, comment: "组织机构编码"),
Name = table.Column<string>(type: "text", nullable: true, comment: "组织机构名称"),
Level = table.Column<int>(type: "integer", nullable: false, comment: "组织机构级别"),
Introductions = table.Column<string>(type: "text", nullable: true, comment: "组织机构说明")
},
constraints: table =>
{
table.PrimaryKey("PK_Organizations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Premissions",
columns: table => new
{
Id = table.Column<string>(type: "text", unicode: false, nullable: false, comment: "ID"),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
CreatorId = table.Column<string>(type: "text", nullable: true, comment: "创建者 ID"),
LastModificationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
LastModifierId = table.Column<string>(type: "text", nullable: true, comment: "最后修改者 ID"),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, comment: "是否删除"),
DeletionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
DeleterId = table.Column<string>(type: "text", nullable: true, comment: "删除者 ID"),
ParentId = table.Column<string>(type: "text", nullable: true, comment: "权限Id"),
Name = table.Column<string>(type: "text", nullable: true, comment: "权限名称"),
Introductions = table.Column<string>(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<string>(type: "text", unicode: false, nullable: false, comment: "ID"),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
CreatorId = table.Column<string>(type: "text", nullable: true, comment: "创建者 ID"),
LastModificationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
LastModifierId = table.Column<string>(type: "text", nullable: true, comment: "最后修改者 ID"),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, comment: "是否删除"),
DeletionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
DeleterId = table.Column<string>(type: "text", nullable: true, comment: "删除者 ID"),
RoleId = table.Column<string>(type: "text", nullable: true, comment: "角色Id"),
PremissionId = table.Column<string>(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<string>(type: "text", unicode: false, nullable: false, comment: "ID"),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
CreatorId = table.Column<string>(type: "text", nullable: true, comment: "创建者 ID"),
LastModificationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
LastModifierId = table.Column<string>(type: "text", nullable: true, comment: "最后修改者 ID"),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, comment: "是否删除"),
DeletionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
DeleterId = table.Column<string>(type: "text", nullable: true, comment: "删除者 ID"),
ParentId = table.Column<string>(type: "text", nullable: true, comment: "角色父级Id"),
Name = table.Column<string>(type: "text", nullable: true, comment: "角色名称"),
Introductions = table.Column<string>(type: "text", nullable: true, comment: "角色说明")
},
constraints: table =>
{
table.PrimaryKey("PK_Roles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<string>(type: "text", unicode: false, nullable: false, comment: "ID"),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
CreatorId = table.Column<string>(type: "text", nullable: true, comment: "创建者 ID"),
LastModificationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
LastModifierId = table.Column<string>(type: "text", nullable: true, comment: "最后修改者 ID"),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, comment: "是否删除"),
DeletionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
DeleterId = table.Column<string>(type: "text", nullable: true, comment: "删除者 ID"),
UserName = table.Column<string>(type: "text", nullable: true, comment: "用户名"),
Password = table.Column<string>(type: "text", nullable: true, comment: "密码"),
OrganizationId = table.Column<string>(type: "text", nullable: true, comment: "组织机构Id")
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "User_Roles",
columns: table => new
{
Id = table.Column<string>(type: "text", unicode: false, nullable: false, comment: "ID"),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间"),
CreatorId = table.Column<string>(type: "text", nullable: true, comment: "创建者 ID"),
LastModificationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最后修改时间"),
LastModifierId = table.Column<string>(type: "text", nullable: true, comment: "最后修改者 ID"),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, comment: "是否删除"),
DeletionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "删除时间"),
DeleterId = table.Column<string>(type: "text", nullable: true, comment: "删除者 ID"),
UserId = table.Column<string>(type: "text", nullable: false, comment: "用户Id"),
RoleId = table.Column<string>(type: "text", nullable: false, comment: "角色Id")
},
constraints: table =>
{
table.PrimaryKey("PK_User_Roles", x => x.Id);
table.ForeignKey(
name: "FK_User_Roles_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_User_Roles_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_User_Roles_RoleId",
table: "User_Roles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "IX_User_Roles_UserId",
table: "User_Roles",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Organizations");
migrationBuilder.DropTable(
name: "Premissions");
migrationBuilder.DropTable(
name: "Role_Premission");
migrationBuilder.DropTable(
name: "User_Roles");
migrationBuilder.DropTable(
name: "Roles");
migrationBuilder.DropTable(
name: "Users");
}
}
}

418
AX.WebDrillServer/Migrations/ApplicationDbContextModelSnapshot.cs

@ -0,0 +1,418 @@
// <auto-generated />
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<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<string>("Code")
.HasColumnType("text")
.HasComment("组织机构编码");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<string>("Introductions")
.HasColumnType("text")
.HasComment("组织机构说明");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<int>("Level")
.HasColumnType("integer")
.HasComment("组织机构级别");
b.Property<string>("Name")
.HasColumnType("text")
.HasComment("组织机构名称");
b.HasKey("Id");
b.ToTable("Organizations");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Premission", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<string>("Introductions")
.HasColumnType("text")
.HasComment("权限说明");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("Name")
.HasColumnType("text")
.HasComment("权限名称");
b.Property<string>("ParentId")
.HasColumnType("text")
.HasComment("权限Id");
b.HasKey("Id");
b.ToTable("Premissions");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Role", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<string>("Introductions")
.HasColumnType("text")
.HasComment("角色说明");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("Name")
.HasColumnType("text")
.HasComment("角色名称");
b.Property<string>("ParentId")
.HasColumnType("text")
.HasComment("角色父级Id");
b.HasKey("Id");
b.ToTable("Roles");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Role_Premission", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("PremissionId")
.HasColumnType("text")
.HasComment("权限Id");
b.Property<string>("RoleId")
.HasColumnType("text")
.HasComment("角色Id");
b.HasKey("Id");
b.ToTable("Role_Premission");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("OrganizationId")
.HasColumnType("text")
.HasComment("组织机构Id");
b.Property<string>("Password")
.HasColumnType("text")
.HasComment("密码");
b.Property<string>("UserName")
.HasColumnType("text")
.HasComment("用户名");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User_Role", b =>
{
b.Property<string>("Id")
.IsUnicode(false)
.HasColumnType("text")
.HasColumnOrder(0)
.HasComment("ID");
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(1)
.HasComment("创建时间");
b.Property<string>("CreatorId")
.HasColumnType("text")
.HasColumnOrder(2)
.HasComment("创建者 ID");
b.Property<string>("DeleterId")
.HasColumnType("text")
.HasColumnOrder(7)
.HasComment("删除者 ID");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(6)
.HasComment("删除时间");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnOrder(5)
.HasComment("是否删除");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("timestamp with time zone")
.HasColumnOrder(3)
.HasComment("最后修改时间");
b.Property<string>("LastModifierId")
.HasColumnType("text")
.HasColumnOrder(4)
.HasComment("最后修改者 ID");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text")
.HasComment("角色Id");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text")
.HasComment("用户Id");
b.HasKey("Id");
b.HasIndex("RoleId");
b.HasIndex("UserId");
b.ToTable("User_Roles");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User_Role", b =>
{
b.HasOne("AX.WebDrillServer.Models.Role", "Role")
.WithMany("User_Roles")
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AX.WebDrillServer.Models.User", "User")
.WithMany("User_Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
});
modelBuilder.Entity("AX.WebDrillServer.Models.Role", b =>
{
b.Navigation("User_Roles");
});
modelBuilder.Entity("AX.WebDrillServer.Models.User", b =>
{
b.Navigation("User_Roles");
});
#pragma warning restore 612, 618
}
}
}

44
AX.WebDrillServer/Models/EntityBase.cs

@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace AX.WebDrillServer.Models
{
[NotMapped]
public class EntityBase : IEntityBase
{
[Comment("ID")]
[Key]
[Unicode(false)]
[Column(Order = 0)]
public string Id { get; set; } = null!;
[Comment("创建时间")]
[Column(Order = 1)]
public DateTime CreationTime { get; set; } = DateTime.UtcNow;
[Comment("创建者 ID")]
[Column(Order = 2)]
public string? CreatorId { get; set; }
[Comment("最后修改时间")]
[Column(Order = 3)]
public DateTime? LastModificationTime { get; set; }
[Comment("最后修改者 ID")]
[Column(Order = 4)]
public string? LastModifierId { get; set; }
[Comment("是否删除")]
[Column(Order = 5)]
public bool IsDeleted { get; set; } = false;
[Comment("删除时间")]
[Column(Order = 6)]
public DateTime? DeletionTime { get; set; }
[Comment("删除者 ID")]
[Column(Order = 7)]
public string? DeleterId { get; set; }
}
}

43
AX.WebDrillServer/Models/IEntityBase.cs

@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace AX.WebDrillServer.Models
{
public interface IEntityBase
{
[Comment("ID")]
[Key]
[Unicode(false)]
[Column(Order = 0)]
public string Id { get; set; }
[Comment("创建时间")]
[Column(Order = 1)]
public DateTime CreationTime { get; set; }
[Comment("创建者 ID")]
[Column(Order = 2)]
public string? CreatorId { get; set; }
[Comment("最后修改时间")]
[Column(Order = 3)]
public DateTime? LastModificationTime { get; set; }
[Comment("最后修改者 ID")]
[Column(Order = 4)]
public string? LastModifierId { get; set; }
[Comment("是否删除")]
[Column(Order = 5)]
public bool IsDeleted { get; set; }
[Comment("删除时间")]
[Column(Order = 6)]
public DateTime? DeletionTime { get; set; }
[Comment("删除者 ID")]
[Column(Order = 7)]
public string? DeleterId { get; set; }
}
}

21
AX.WebDrillServer/Models/Organization.cs

@ -0,0 +1,21 @@
using AX.WebDrillServer.Enums;
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 组织机构表
/// </summary>
public class Organization : EntityBase
{
[Comment("组织机构编码")]
public string? Code { get; set; }
[Comment("组织机构名称")]
public string? Name { get; set; }
[Comment("组织机构级别")]
public OrganizationLevel Level { get; set; }
[Comment("组织机构说明")]
public string? Introductions { get; set; }
}
}

17
AX.WebDrillServer/Models/Premission.cs

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 权限表
/// </summary>
public class Premission : EntityBase
{
[Comment("权限Id")]
public string? ParentId { get; set; }
[Comment("权限名称")]
public string? Name { get; set; }
[Comment("权限说明")]
public string? Introductions { get; set; }
}
}

19
AX.WebDrillServer/Models/Role.cs

@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 角色表
/// </summary>
public class Role : EntityBase
{
[Comment("角色父级Id")]
public string? ParentId { get; set; }
[Comment("角色名称")]
public string? Name { get; set; }
[Comment("角色说明")]
public string? Introductions { get; set; }
[Comment("用户角色关系")]
public ICollection<User_Role>? User_Roles { get; set; }
}
}

15
AX.WebDrillServer/Models/Role_Premission.cs

@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 角色权限关联表
/// </summary>
public class Role_Premission:EntityBase
{
[Comment("角色Id")]
public string? RoleId { get; set; }
[Comment("权限Id")]
public string? PremissionId { get; set; }
}
}

21
AX.WebDrillServer/Models/User.cs

@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 用户表
/// 一个用户表明一个账号,用登录系统
/// </summary>
//[Index(nameof(UserName), IsUnique = true)]//添加唯一索引
public class User : EntityBase
{
[Comment("用户名")]
public string UserName { get; set; } = null!;
[Comment("密码")]
public string Password { get; set; } = null!;
[Comment("组织机构Id")]
public string? OrganizationId { get; set; }
[Comment("用户角色关系")]
public ICollection<User_Role> User_Roles { get; set; } = null!;
}
}

17
AX.WebDrillServer/Models/UserGroup.cs

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 用户组表
/// </summary>
public class UserGroup : EntityBase
{
[Comment("角色组父级Id")]
public string? ParentId { get; set; }
[Comment("用户组名")]
public string? Name { get; set; }
[Comment("用户组说明")]
public string? Introductions { get; set; }
}
}

15
AX.WebDrillServer/Models/UserGroup_Role.cs

@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 用户组角色关联表
/// </summary>
public class UserGroup_Role : EntityBase
{
[Comment("用户组Id")]
public string? UserGroupId { get; set; }
[Comment("角色Id")]
public string? RoleId { get; set; }
}
}

16
AX.WebDrillServer/Models/UserGroup_User.cs

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 角色组角色关联表
/// </summary>
public class UserGroup_User : EntityBase
{
[Comment("角色组Id")]
public string? GroupId { get; set; }
[Comment("角色Id")]
public string? UserId { get; set; }
}
}

20
AX.WebDrillServer/Models/User_Role.cs

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
namespace AX.WebDrillServer.Models
{
/// <summary>
/// 用户角色关联表
/// </summary>
public class User_Role : EntityBase
{
[Comment("用户Id")]
public string UserId { get; set; } = null!;
[Comment("用户")]
public User User { get; set; } = null!;
[Comment("角色Id")]
public string RoleId { get; set; } = null!;
[Comment("角色")]
public Role Role { get; set; } = null!;
}
}

279
AX.WebDrillServer/Program.cs

@ -0,0 +1,279 @@
using AX.WebDrillServer.Data;
using AX.WebDrillServer.EntityMappers;
using AX.WebDrillServer.Errors;
using AX.WebDrillServer.Extensions;
using AX.WebDrillServer.Hubs;
using AX.WebDrillServer.Middlewares;
using AX.WebDrillServer.Middlewares.Jwts;
using AX.WebDrillServer.Services.FireDeductionHub;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
const string ProductName = "沙盘推演";
const string ProductVersion = "v1.0";
const string ApiVersion = "v1";
#region Banner
var banners = "Banners";
if (Directory.Exists(banners))
{
var files = Directory.GetFiles(banners);
var random = new Random();
var file = files[random.Next(0, files.Length)];
var banner = File.ReadAllText(file);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write(banner);
Console.WriteLine("\n\n");
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("按 CTRL+C 键退出程序...\n\n");
Console.ForegroundColor = ConsoleColor.White;
Console.Title = $"{ProductName} {ProductVersion}";
#endregion Banner
#region ServerLicense
if (!ServerLicense.Initialize())
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(GlobalErrorCodes.Messages[GlobalErrorCodes.E600]);
Console.WriteLine("\n\n");
}
#endregion ServerLicense
var builder = WebApplication.CreateBuilder(args);
#region Identities
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("JwtSettings"));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Secret"]));
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = secret,
ValidateAudience = true,
ValidAudience = builder.Configuration["JwtSettings:Audience"],
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
// SignalR Jwt
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (accessToken.HasValue() &&
path.StartsWithSegments("/hubs"))
context.Token = accessToken;
return Task.CompletedTask;
}
};
});
builder.Services.AddSingleton<IJwtService, JwtService>();
builder.Services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
builder.Services.AddAuthorization();
#endregion Identities
// Email Service
//builder.Services.AddSingleton<IEmailService, EmailService>();
builder.Services.AddSingleton<RoomManager>();
builder.Services.AddMemoryCache();
builder.Services
.AddSignalR(options =>
{
#if !RELEASE
options.EnableDetailedErrors = true;
#endif
})
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true;
options.PayloadSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.PayloadSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
//全局配置signalr json 转枚举使用stringName还是intValue, 枚举采用大驼峰命名法(区别于属性和api的小驼峰)
options.PayloadSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
//#region Custom Services
//builder.Services.AddTransient<INotificationService, NotificationService>();
//#endregion Custom Services
builder.Services
.AddControllers(options =>
{
options.InputFormatters.Insert(0, new RawJsonInputFormatter());
options.OutputFormatters.Insert(0, new RawJsonOutputFormatter());
//options.Filters.Add(new AuthorizeFilter());
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
//全局配置Controller json 转枚举使用stringName还是intValue
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
});
builder.Services.AddEndpointsApiExplorer();
#region Swagger
if (builder.Environment.IsDevelopment() || builder.Environment.IsStaging())
{
builder.Services.AddSwaggerGen(options =>
{
options.CustomSchemaIds(type => type.ToString());
options.SwaggerDoc(ApiVersion, new OpenApiInfo { Title = $"{ProductName} API", Version = ApiVersion });
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath, true);
options.IgnoreObsoleteActions();
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "使用 JWT Bearer 模式进行授权。示例: \"Bearer {token}\"",
Name = "Authorization", //Jwt default param name
In = ParameterLocation.Header, //Jwt store address
Type = SecuritySchemeType.ApiKey, //Security scheme type
Scheme = "Bearer",
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
},
Array.Empty<string>()
}
});
// File Filter
options.OperationFilter<FileUploadOperationFilter>();
});
}
#endregion Swagger
#region Database Context
builder.Services.AddDbContextPool<ApplicationDbContext>((provider, options) =>
{
if (builder.Environment.IsDevelopment() || builder.Environment.IsStaging())
options.EnableSensitiveDataLogging();
options.UseNpgsql(builder.Configuration.GetConnectionString("PostgreSQL"),
// 全局启用拆分查询, 详见: https://docs.microsoft.com/zh-cn/ef/core/querying/single-split-queries;
opts => opts.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
});
#endregion Database Context
#region Mapper
EntityMapperConfig.Initialize();
#endregion Mapper
var app = builder.Build();
#region Setup Database
app.SetupDatabase();
#endregion Setup Database
#region Development
if (app.Environment.IsDevelopment() || app.Environment.IsStaging())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint($"/swagger/{ApiVersion}/swagger.json", $"{ProductName} API {ApiVersion}");
options.EnablePersistAuthorization();
});
app.UseReDoc(options =>
{
options.RoutePrefix = "docs";
options.DocumentTitle = $"{ProductName} API {ApiVersion}";
options.SpecUrl($"/swagger/{ApiVersion}/swagger.json");
options.EnableUntrustedSpec();
options.ScrollYOffset(10);
options.HideHostname();
options.HideDownloadButton();
options.ExpandResponses("200,201");
options.RequiredPropsFirst();
//c.NoAutoAuth();
//c.PathInMiddlePanel();
//c.HideLoading();
//c.NativeScrollbars();
//c.DisableSearch();
//c.OnlyRequiredInSamples();
//c.SortPropsAlphabetically();
});
app.UseHttpLogging();
}
#endregion Development
//app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
//app.MapHub<NotificationHub>("/hubs/notification");
//app.MapHub<TaskChatHub>("/hubs/taskchat");
app.MapHub<FireDeductionHub>("/hubs/FireDeductionHub");
app.Run();

30
AX.WebDrillServer/Properties/launchSettings.json

@ -0,0 +1,30 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:3582",
"sslPort": 44303
}
},
"profiles": {
"AX.WebDrillServer": {
"commandName": "Project",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:7011;http://localhost:5011",
"dotnetRunMessages": true
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

71
AX.WebDrillServer/Services/Emails/EmailService.cs

@ -0,0 +1,71 @@
using AX.WebDrillServer.Models;
using MailKit.Net.Smtp;
using MimeKit;
namespace AX.WebDrillServer.Services.Emails
{
public class EmailService : IEmailService
{
private readonly ILogger<EmailService> _logger;
private readonly IConfiguration _configuration;
public EmailService(
ILogger<EmailService> logger,
IConfiguration configuration
)
{
_logger = logger;
_configuration = configuration;
}
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="users">收件用户列表</param>
/// <param name="subject">主题</param>
/// <param name="text">内容</param>
/// <returns></returns>
public async Task SendMailAsync(ICollection<User> users, string subject, string text)
{
var enabled = _configuration.GetValue<bool>("EmailSettings:Enabled");
var senderName = _configuration["EmailSettings:SenderName"];
var senderAddress = _configuration["EmailSettings:SenderAddress"];
var smtpProviderHost = _configuration["EmailSettings:SmtpProviderHost"];
var smtpProviderPort = _configuration.GetValue<ushort>("EmailSettings:SmtpProviderPort");
var secret = _configuration["EmailSettings:Secret"];
// 若关闭邮件功能, 则直接跳出;
if (!enabled) return;
if (string.IsNullOrWhiteSpace(secret))
{
_logger.LogCritical("未配置邮件服务密钥;");
return;
}
var message = new MimeMessage();
message.From.Add(new MailboxAddress(senderName, senderAddress));
message.Subject = subject;
message.Body = new TextPart("plain") { Text = text };
try
{
using var client = new SmtpClient();
await client.ConnectAsync(smtpProviderHost, smtpProviderPort);
await client.AuthenticateAsync(senderName, secret);
var response = await client.SendAsync(message);
await client.DisconnectAsync(true);
if (response.StartsWith("Mail OK"))
_logger.LogInformation("邮件发送成功");
else
_logger.LogWarning("邮件发送失败, 详情: {Response}", response);
}
catch (Exception e)
{
_logger.LogError("邮件发送失败; 详情: {Error}", e);
}
}
}
}

19
AX.WebDrillServer/Services/Emails/IEmailService.cs

@ -0,0 +1,19 @@
using AX.WebDrillServer.Models;
namespace AX.WebDrillServer.Services.Emails
{
/// <summary>
/// 邮件服务接口
/// </summary>
public interface IEmailService
{
///// <summary>
///// 发送邮件
///// </summary>
///// <param name="users">收件用户列表</param>
///// <param name="subject">主题</param>
///// <param name="text">内容</param>
///// <returns></returns>
//public Task SendMailAsync(ICollection<User> users, string subject, string text);
}
}

46
AX.WebDrillServer/Services/FireDeductionHub/Drill.cs

@ -0,0 +1,46 @@
using AX.WebDrillServer.Extensions;
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class Drill
{
public string Id { get; set; } = null!;
public string RoomId { get; set; } = null!;
public DrillState DrillState { get; set; }
/// <summary>
/// 演练名称
/// </summary>
public string? DrillName { get; set; }
public long StartTime { get; set; }
public long EndTime { get; set; } = 0;
public List<FireDeductionUser> Users { get; set; } = new List<FireDeductionUser>();
public List<RoomSendInfo> Infos = new List<RoomSendInfo>();
/// <summary>
/// 获取推演时间,如果传入时间表示获取传入时间到开始时间的时间
/// </summary>
/// <param name="time">毫秒</param>
/// <returns></returns>
public long GetDrillLastTime(long time = -1)
{
if (EndTime != 0)//已经结束
{
return (EndTime - StartTime);
}
else
{
if (time == -1)
{
return (DateTime.Now.ToUnixTicks() - StartTime);
}
else
{
return (time - StartTime);
}
}
}
}
}

15
AX.WebDrillServer/Services/FireDeductionHub/DrillShowData.cs

@ -0,0 +1,15 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class DrillShowData
{
public string? Name { get; set; }
public long StartTime { get; set; }
public long EndTime { get; set; }
public long DrillTime { get; set; }
public FireDeductionUser Owner { get; set; } = null!;
public List<FireDeductionUser> Players { get; set; } = new List<FireDeductionUser>();
}
}

8
AX.WebDrillServer/Services/FireDeductionHub/EndDrillData.cs

@ -0,0 +1,8 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class EndDrillData
{
public string Name { get; set; } = null!;
public long EndTime { get; set; }
}
}

8
AX.WebDrillServer/Services/FireDeductionHub/EndDrillResultData.cs

@ -0,0 +1,8 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class EndDrillResultData
{
public DrillResult ResultType { get; set; }
public EndDrillData EndData { get; set; } = null!;
}
}

31
AX.WebDrillServer/Services/FireDeductionHub/FireDeductionRoom.cs

@ -0,0 +1,31 @@
using Org.BouncyCastle.Asn1.X509;
namespace AX.WebDrillServer.Services.FireDeductionHub
{
/// <summary>
/// 沙盘推演房间
/// </summary>
public class FireDeductionRoom
{
public string RoomId { get; set; } = null!;
public string RoomName { get; set; } = null!;
/// <summary>
/// 房间密码
/// </summary>
public string? Password;
public RoomState State { get; set; } = RoomState.Ready;
/// <summary>
/// 房间最大人数
/// </summary>
public int MaxPersons = 100;
/// <summary>
/// 房间所属,默认是创建者
/// </summary>
public string Owner { get; set; } = null!;
public string OwnerName { get; set; } = null!;
public List<Drill> Drills = new List<Drill>();
public List<FireDeductionUser> Users { get; set; } = new List<FireDeductionUser>();
}
}

56
AX.WebDrillServer/Services/FireDeductionHub/FireDeductionRoomEnums.cs

@ -0,0 +1,56 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public enum RoomCreateResult
{
Success = 0,//成功
RoomReName,//房间重名
RoomNameEmpty,//房间名为空
NoUserExist,//用户不存在
}
public enum RoomState
{
Ready = 0,//准备
Playing,//进行中
Over,//结束
}
public enum RoomEnterResult
{
Success = 0,//成功
RoomError,//房间信息有误
NoUserExist,//用户信息有误
}
public enum RoomLeaveResult
{
Success = 0,//成功
RoomError,//房间信息有误
NoUserExist,//用户信息有误
}
public enum RoommateChangeType
{
Add = 0,//新增
Remove,//移除
OnLine,//上线
OffLine,//下线
}
public enum RoomDisposeResult
{
Success,//成功
Failed,//失败
}
public enum DrillResult
{
Success,//成功
Failed,//失败
}
public enum DrillState
{
Pause = 0,//暂停中
Playing,//进行中
Over,//结束
}
public class FireDeductionRoomEnums
{
}
}

15
AX.WebDrillServer/Services/FireDeductionHub/FireDeductionUser.cs

@ -0,0 +1,15 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
/// <summary>
/// 沙盘推演用户
/// </summary>
public class FireDeductionUser
{
public string UserId { get; set; } = null!;
public string UserName { get; set; } = null!;
public bool Online { get; set; } = true;
public bool Left { get; set; }
public string? RoomId { get; set; }
public string ConnectionId { get; set; } = null!;
}
}

11
AX.WebDrillServer/Services/FireDeductionHub/PlayerOrder.cs

@ -0,0 +1,11 @@
using System.Text.Json;
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class PlayerOrder
{
public string UserId { get; set; } = null!;
public long Time { get; set; }
public object Data { get; set; } = null!;
}
}

9
AX.WebDrillServer/Services/FireDeductionHub/RoomCreateData.cs

@ -0,0 +1,9 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomCreateData
{
public string UserId { get; set; } = null!;
public string? Password { get; set; }
public string Name { get; set; } = null!;
}
}

8
AX.WebDrillServer/Services/FireDeductionHub/RoomCreateResultData.cs

@ -0,0 +1,8 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomCreateResultData
{
public RoomCreateResult RoomCreateResult { get; set; }
public RoomShowInfo? CreatedRoom { get; set; }
}
}

8
AX.WebDrillServer/Services/FireDeductionHub/RoomDisposeResultData.cs

@ -0,0 +1,8 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomDisposeResultData
{
public RoomDisposeResult Result { get; set; }
public string RoomId { get; set; } = null!;
}
}

9
AX.WebDrillServer/Services/FireDeductionHub/RoomEnterData.cs

@ -0,0 +1,9 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomEnterData
{
public string RoomId { get; set; } = null!;
public string UserId { get; set; } = null!;
public string? Password { get; set; }
}
}

8
AX.WebDrillServer/Services/FireDeductionHub/RoomEnterResultData.cs

@ -0,0 +1,8 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomEnterResultData
{
public RoomEnterResult EnterResult { get; set; }
public string RoomId { get; set; } = null!;
}
}

8
AX.WebDrillServer/Services/FireDeductionHub/RoomLeaveData.cs

@ -0,0 +1,8 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomLeaveData
{
public string RoomId { get; set; } = null!;
public string UserId { get; set; } = null!;
}
}

8
AX.WebDrillServer/Services/FireDeductionHub/RoomLeaveResultData.cs

@ -0,0 +1,8 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomLeaveResultData
{
public RoomLeaveResult LeaveResult { get; set; }
public string RoomId { get; set; } = null!;
}
}

196
AX.WebDrillServer/Services/FireDeductionHub/RoomManager.cs

@ -0,0 +1,196 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomManager
{
private readonly List<FireDeductionRoom> fireDeductionRooms = new();
private readonly List<FireDeductionUser> fireDeductionUsers = new();
private readonly List<RoomSendInfo> roomSendInfos = new();
public FireDeductionRoom? GetRoomByUserId(string userId)
{
lock (this)
{
var user = fireDeductionUsers.Where(u => u.UserId == userId).SingleOrDefault();
if (user != null)
{
return fireDeductionRooms.Where(r => r.RoomId == user.RoomId).SingleOrDefault();
}
else
{
return null;
}
}
}
public bool SaveRoomSendInfo(RoomSendInfo info)
{
bool result = false;
lock (this)
{
if (!roomSendInfos.Contains(info))
{
roomSendInfos.Add(info);
result = true;
//TODO:存储数据方法
}
else
{
result = false;
}
return result;
}
}
public List<RoomSendInfo> GetRoomSendInfoByRoomId(string roomId)
{
lock (this)
{
return roomSendInfos.Where(x => x.RoomId == roomId).ToList();
}
}
public RoomShowInfo RoomToInfo(FireDeductionRoom source, RoomShowInfo? info = null)
{
info ??= new();
info.RoomId = source.RoomId;
info.RoomName = source.RoomName;
info.Owner = source.Owner;
info.OwnerName = source.OwnerName;
info.IsLock = !string.IsNullOrEmpty(source.Password);
info.MaxPersons = source.MaxPersons;
info.State = source.State;
return info;
}
public bool IsRoomNameSame(string OwnerId, string RoomName)
{
lock (this)
{
var room = fireDeductionRooms.Where(r => r.Owner == OwnerId && r.RoomName == RoomName).SingleOrDefault();
return room != null;
}
}
public List<RoomShowInfo> GetAllRooms()
{
lock (this)
{
List<RoomShowInfo> infos = new();
foreach (var item in fireDeductionRooms)
{
infos.Add(RoomToInfo(item));
}
return infos;
}
}
public List<FireDeductionUser> GetAllUsers()
{
lock (this)
{
return fireDeductionUsers;
}
}
public FireDeductionRoom? GetRoom(string roomId, string? password)
{
lock (this)
{
return fireDeductionRooms.Where(r => r.RoomId == roomId && r.Password == password).SingleOrDefault();
}
}
public FireDeductionRoom? GetRoom(string roomId)
{
lock (this)
{
return fireDeductionRooms.Where(r => r.RoomId == roomId).SingleOrDefault();
}
}
public FireDeductionUser? GetUser(string userId)
{
lock (this)
{
return fireDeductionUsers.Where(r => r.UserId == userId).SingleOrDefault();
}
}
public FireDeductionUser? GetUserByConnectionId(string conneciontId)
{
lock (this)
{
return fireDeductionUsers.Where(r => r.ConnectionId == conneciontId).SingleOrDefault();
}
}
public void AddRoom(FireDeductionRoom room)
{
lock (this)
{
if (!fireDeductionRooms.Contains(room))
{
fireDeductionRooms.Add(room);
}
}
}
public void RemoveRoom(string roomId)
{
lock (this)
{
for (int i = 0; i < fireDeductionRooms.Count; i++)
{
if (fireDeductionRooms[i].RoomId == roomId)
{
fireDeductionRooms.RemoveAt(i);
}
}
}
}
public void AddUserToRoom(FireDeductionUser user, string RoomId)
{
lock (this)
{
var room = fireDeductionRooms.Where(r => r.RoomId == RoomId).SingleOrDefault();
if (room != null)
{
if (!room.Users.Contains(user))
{
room.Users.Add(user);
}
}
}
}
public void AddUser(FireDeductionUser user)
{
lock (this)
{
if (!fireDeductionUsers.Contains(user))
{
fireDeductionUsers.Add(user);
}
}
}
public void ReMoveUserFromRoom(FireDeductionUser user, string RoomId)
{
lock (this)
{
var room = fireDeductionRooms.Where(r => r.RoomId == RoomId).SingleOrDefault();
if (room != null)
{
if (room.Users.Contains(user))
{
room.Users.Remove(user);
}
}
}
}
public void RemoveUser(string userId)
{
lock (this)
{
for (int i = 0; i < fireDeductionUsers.Count; i++)
{
if (fireDeductionUsers[i].UserId == userId)
{
fireDeductionUsers.RemoveAt(i);
}
}
}
}
}
}

10
AX.WebDrillServer/Services/FireDeductionHub/RoomSendInfo.cs

@ -0,0 +1,10 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoomSendInfo
{
public string RoomId { get; set; } = null!;
public string UserId { get; set; } = null!;
public long Time;
public string? InfoData;
}
}

25
AX.WebDrillServer/Services/FireDeductionHub/RoomShowInfo.cs

@ -0,0 +1,25 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
/// <summary>
/// 给定的房间信息
/// </summary>
public class RoomShowInfo
{
public string RoomId { get; set; } = null!;
public string RoomName { get; set; } = null!;
public RoomState State { get; set; } = RoomState.Ready;
/// <summary>
/// 房间最大人数
/// </summary>
public int MaxPersons = 100;
/// <summary>
/// 房间所属,默认是创建者
/// </summary>
public string Owner { get; set; } = null!;
public string OwnerName { get; set; } = null!;
/// <summary>
/// 是否有密码
/// </summary>
public bool IsLock { get; set; }
}
}

9
AX.WebDrillServer/Services/FireDeductionHub/RoommateChangeData.cs

@ -0,0 +1,9 @@
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class RoommateChangeData
{
public RoommateChangeType ChangeType { get; set; }
public FireDeductionUser UserData { get; set; } = null!;
public List<FireDeductionUser> AllUsers { get; set; } = new List<FireDeductionUser>();
}
}

10
AX.WebDrillServer/Services/FireDeductionHub/StartDrillData.cs

@ -0,0 +1,10 @@
using System.Data;
namespace AX.WebDrillServer.Services.FireDeductionHub
{
public class StartDrillData
{
public string Name { get; set; } = null!;
public long StartTime { get; set; }
}
}

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

Loading…
Cancel
Save