Compare commits

...

72 Commits
master ... norm

Author SHA1 Message Date
root b3e8943169 Device List curd 2 months ago
hyh 35c339418d websocket 控制是否返回发送信息 2 months ago
hyh 9f50481ebd feat: 优化 WebSocket 中间件,添加消息缓冲、错误处理和性能监控功能 2 months ago
root c484d3aa23 html 可以显示收到数据 2 months ago
root 47b4f6e0e8 CellularManagement.WebSocket 类库代码优化 2 months ago
hyh 0e321f828b 重构 WebSocket 处理程序结构,优化消息处理流程 2 months ago
hyh b7982d530e 优化了IncomingMessageProcessor类的注释,使其更加清晰和准确 2 months ago
hyh 67076199b0 feat: 添加 WebSocket 消息处理相关功能 2 months ago
hyh 716637c03b WebSocket 文件重新分类 2 months ago
hyh 5227ebfcf4 feat: 添加 WebSocket 消息处理功能 2 months ago
hyh fd1d2b1e70 feat: 同步所有更改,包括认证相关功能和UI更新 2 months ago
hyh 3d2ab3a071 feat: 优化登出功能,添加详细日志记录和性能监控 2 months ago
hyh ff634ad4aa 添加登录日志功能和相关配置 2 months ago
hyh 1a2c88c6d8 feat: 添加用户登录日志功能,优化用户管理界面 2 months ago
hyh 128f3be343 refactor: 规范化代码,将 CreatedTime 改为 createdAt 3 months ago
hyh 6114cb226d CellularManagement.Infrastructure 去除 CellularManagement.Application 引用 3 months ago
root dc74694c68 feat: add captcha service and update cache service 3 months ago
root 85930db010 refactor: 规范化代码结构和实现 3 months ago
root a551bda406 docs: 添加 norm 分支说明文档 3 months ago
root b0a379f9b2 refactor: 重构代码以符合DDD架构 - 将OperationResult迁移到领域层 - 优化服务接口位置 - 更新依赖注入配置 3 months ago
hyh 3d41e2e275 更新用户注册相关功能 3 months ago
hyh 2a75722a4d feat: 添加用户注册功能 - 实现注册表单和验证 - 添加手机号码字段 - 优化表单验证规则 - 添加 Toast 通知组件 3 months ago
hyh 14bc6be277 feat: 角色管理页面UI与功能优化,支持列设置、密度切换、搜索体验提升等 3 months ago
hyh 09eaa7efbe feat: 优化角色表格布局并增加Radix UI分页功能 3 months ago
hyh 986487be47 docs: 更新 README.md,添加组件库使用说明 3 months ago
hyh 492eaa169d feat(role): 角色分页查询与命名规范,参照用户分页实现 3 months ago
hyh 0a4eff6cf6 feat: 统一使用 Radix UI 和 Shadcn UI 组件库 3 months ago
hyh 29dd65651f fix: 优化内容区空白和页面布局,DashboardHome/rolesView体验提升 3 months ago
hyh 423f5335be docs: 更新 README.md,添加角色管理模块说明 3 months ago
hyh c4cdd28411 refactor: 重构角色管理模块,分离命令和查询处理器 3 months ago
hyh 845357e59b docs: 完善项目文档 3 months ago
hyh 6a17a8fb0a feat: 添加角色管理功能 - 实现角色列表、创建、编辑、删除功能,更新项目文档 3 months ago
hyh 7cc1fd7a09 docs: 更新权限管理功能说明 3 months ago
hyh 83a6cc88ce feat(auth): 优化用户认证时的权限检查逻辑 3 months ago
hyh 042ed88bc0 删除备份 3 months ago
hyh 2fe019001f feat: 完善JWT认证和用户管理功能 3 months ago
hyh ea307a2077 refactor: 将获取当前用户接口从Auth移动到User控制器 3 months ago
hyh fe78db0f53 重构角色权限管理,优化代码结构 3 months ago
hyh 75f24121d1 feat: 实现角色权限分配和移除功能 3 months ago
hyh 81d5f2243e 更新权限相关实体和仓储,重新生成数据库迁移 3 months ago
hyh 2ef5a02eca 更新权限相关实体和仓储 3 months ago
hyh 3b237ed510 refactor: 重构用户角色仓储,实现命令查询职责分离 3 months ago
hyh fd14a6acfc feat: 添加认证相关功能和前端配置 3 months ago
hyh d18b2bb245 refactor: 将相对路径导入替换为 @ 别名,并修复 LoginPage 中的类型错误 3 months ago
hyh 2339a4273c 更新:重构布局组件和认证相关功能 3 months ago
hyh 4dcb222883 重构:更新认证和仪表板页面结构,优化路由配置 3 months ago
hyh 2c72231a05 重构前端项目结构,优化UI组件 3 months ago
hyh 71953e6277 重构前端代码结构,优化路由和页面组织 3 months ago
hyh 6e5a8eb8a7 添加Web UI项目文件 3 months ago
root b300a64521 Remove old web project files 3 months ago
root 219261ef93 Add UsersController and related features for user management 3 months ago
root 50802175b9 Add CellularManagement.Web and ignore node_modules 3 months ago
hyh eb970f1004 feat(ws): 更新WebSocket连接管理器的日志记录 3 months ago
hyh bb79ab6b8b feat(ws): 增强WebSocket消息处理器的日志输出和响应数据 3 months ago
hyh da9345dbad 修复 PipelineBuilder 中的类型转换和 Logger 问题 3 months ago
hyh efcb2882a1 feat: 添加WebSocket功能,包括消息处理管道、中间件和WebSocket服务 3 months ago
hyh 65e729867e feat: 添加消息记录标签页系统,发送消息时自动切换到对应标签页 3 months ago
hyh aec7c755bc Initial commit on ws branch 3 months ago
hyh f57dc0e001 为WebSocket相关文件添加详细的中文注释 3 months ago
hyh bb9cd356c9 feat: 更新WebSocket客户端,添加消息类型支持和自动重连功能 3 months ago
hyh 0363255029 docs: 更新API测试文件,添加WebSocket和认证相关端点 3 months ago
hyh 85cecfb653 docs: 删除根目录下的appsettings.README.md 3 months ago
hyh 3dafc526f5 docs: 移动appsettings.README.md到正确位置并更新内容 3 months ago
hyh 884dd79538 docs: 更新appsettings.README.md,完善配置项说明 3 months ago
hyh 414f68bbe4 docs: 更新appsettings.README.md,完善配置项说明 3 months ago
hyh 5084ae78ff docs: 添加appsettings.README.md配置文件说明文档 3 months ago
hyh 68f47da591 docs: 更新README.md,添加WebSocket功能说明 3 months ago
hyh 57292385ad 更新:添加WebSocket相关功能实现 3 months ago
hyh e0bb742fbe feat: 添加角色管理功能 3 months ago
hyh 554ca522f6 添加用户角色相关功能 3 months ago
hyh 6916280258 更新 .gitignore 并移除已跟踪的 bin 和 obj 文件 3 months ago
hyh f7aff85d75 Initial commit to test branch 3 months ago
  1. 5
      .gitignore
  2. 13
      CellularManagement.sln
  3. 225
      README.md
  4. 211
      docs/JWT-Implementation-Guide.md
  5. 217
      docs/JWT-Service-Registration-Guide.md
  6. 25
      scripts/update-database.ps1
  7. 73
      src/CellularManagement.Application/Behaviours/ValidationBehaviour.cs
  8. 10
      src/CellularManagement.Application/CellularManagement.Application.csproj
  9. 57
      src/CellularManagement.Application/Common/Extensions/EmailValidationExtensions.cs
  10. 30
      src/CellularManagement.Application/DependencyInjection.cs
  11. 19
      src/CellularManagement.Application/Features/Auth/Commands/AuthenticateUser/AuthenticateUserCommand.cs
  12. 76
      src/CellularManagement.Application/Features/Auth/Commands/AuthenticateUser/AuthenticateUserCommandHandler.cs
  13. 28
      src/CellularManagement.Application/Features/Auth/Commands/AuthenticateUser/AuthenticateUserCommandValidator.cs
  14. 283
      src/CellularManagement.Application/Features/Auth/Commands/BaseLoginCommandHandler.cs
  15. 19
      src/CellularManagement.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommand.cs
  16. 82
      src/CellularManagement.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs
  17. 29
      src/CellularManagement.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandValidator.cs
  18. 10
      src/CellularManagement.Application/Features/Auth/Commands/GenerateCaptcha/GenerateCaptchaCommand.cs
  19. 79
      src/CellularManagement.Application/Features/Auth/Commands/GenerateCaptcha/GenerateCaptchaCommandHandler.cs
  20. 7
      src/CellularManagement.Application/Features/Auth/Commands/GenerateCaptcha/GenerateCaptchaResponse.cs
  21. 21
      src/CellularManagement.Application/Features/Auth/Commands/Login/LoginRequest.cs
  22. 45
      src/CellularManagement.Application/Features/Auth/Commands/Login/LoginRequestExample.cs
  23. 20
      src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommand.cs
  24. 243
      src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommandHandler.cs
  25. 19
      src/CellularManagement.Application/Features/Auth/Commands/RefreshToken/RefreshTokenCommand.cs
  26. 148
      src/CellularManagement.Application/Features/Auth/Commands/RefreshToken/RefreshTokenCommandHandler.cs
  27. 22
      src/CellularManagement.Application/Features/Auth/Commands/RefreshToken/RefreshTokenResponse.cs
  28. 48
      src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommand.cs
  29. 114
      src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommandHandler.cs
  30. 64
      src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommandValidator.cs
  31. 20
      src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserResponse.cs
  32. 43
      src/CellularManagement.Application/Features/Auth/Commands/SendVerificationCode/SendVerificationCodeCommand.cs
  33. 92
      src/CellularManagement.Application/Features/Auth/Commands/SendVerificationCode/SendVerificationCodeCommandHandler.cs
  34. 38
      src/CellularManagement.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommand.cs
  35. 78
      src/CellularManagement.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs
  36. 21
      src/CellularManagement.Application/Features/Auth/Models/AuthenticateUserResponse.cs
  37. 21
      src/CellularManagement.Application/Features/Auth/Models/EmailLoginResponse.cs
  38. 44
      src/CellularManagement.Application/Features/Auth/Models/LoginResponse.cs
  39. 65
      src/CellularManagement.Application/Features/Auth/Models/UserInfo.cs
  40. 61
      src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs
  41. 72
      src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs
  42. 25
      src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommand.cs
  43. 55
      src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs
  44. 67
      src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs
  45. 83
      src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs
  46. 26
      src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQuery.cs
  47. 54
      src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs
  48. 64
      src/CellularManagement.Application/Features/Devices/Queries/GetDevices/DeviceDto.cs
  49. 34
      src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQuery.cs
  50. 92
      src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs
  51. 44
      src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesResponse.cs
  52. 19
      src/CellularManagement.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommand.cs
  53. 64
      src/CellularManagement.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs
  54. 10
      src/CellularManagement.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionResponse.cs
  55. 21
      src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommand.cs
  56. 76
      src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommandHandler.cs
  57. 19
      src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommandValidator.cs
  58. 17
      src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsResponse.cs
  59. 21
      src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommand.cs
  60. 76
      src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommandHandler.cs
  61. 19
      src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommandValidator.cs
  62. 17
      src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsResponse.cs
  63. 20
      src/CellularManagement.Application/Features/RolePermissions/Queries/GetRolePermissions/GetRolePermissionsQuery.cs
  64. 70
      src/CellularManagement.Application/Features/RolePermissions/Queries/GetRolePermissions/GetRolePermissionsQueryHandler.cs
  65. 65
      src/CellularManagement.Application/Features/RolePermissions/Queries/GetRolePermissions/GetRolePermissionsResponse.cs
  66. 26
      src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleCommand.cs
  67. 10
      src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleResponse.cs
  68. 13
      src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleCommand.cs
  69. 10
      src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleResponse.cs
  70. 117
      src/CellularManagement.Application/Features/Roles/Commands/RoleCommandHandler.cs
  71. 41
      src/CellularManagement.Application/Features/Roles/Queries/GetAllRolesQuery.cs
  72. 13
      src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleQuery.cs
  73. 32
      src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleResponse.cs
  74. 119
      src/CellularManagement.Application/Features/Roles/Queries/RoleQueryHandler.cs
  75. 18
      src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserCommand.cs
  76. 92
      src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserCommandHandler.cs
  77. 39
      src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserCommandValidator.cs
  78. 8
      src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserResponse.cs
  79. 11
      src/CellularManagement.Application/Features/Users/Commands/DeleteUser/DeleteUserCommand.cs
  80. 83
      src/CellularManagement.Application/Features/Users/Commands/DeleteUser/DeleteUserCommandHandler.cs
  81. 8
      src/CellularManagement.Application/Features/Users/Commands/DeleteUser/DeleteUserResponse.cs
  82. 20
      src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserCommand.cs
  83. 72
      src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserCommandHandler.cs
  84. 37
      src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserCommandValidator.cs
  85. 8
      src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserResponse.cs
  86. 16
      src/CellularManagement.Application/Features/Users/Queries/Dtos/UserDto.cs
  87. 20
      src/CellularManagement.Application/Features/Users/Queries/GetAllUsers/GetAllUsersQuery.cs
  88. 100
      src/CellularManagement.Application/Features/Users/Queries/GetAllUsers/GetAllUsersQueryHandler.cs
  89. 35
      src/CellularManagement.Application/Features/Users/Queries/GetAllUsers/GetAllUsersResponse.cs
  90. 11
      src/CellularManagement.Application/Features/Users/Queries/GetUserById/GetUserByIdQuery.cs
  91. 71
      src/CellularManagement.Application/Features/Users/Queries/GetUserById/GetUserByIdQueryHandler.cs
  92. 10
      src/CellularManagement.Application/Features/Users/Queries/GetUserById/GetUserByIdResponse.cs
  93. 362
      src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Application.deps.json
  94. BIN
      src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Application.dll
  95. BIN
      src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Application.pdb
  96. BIN
      src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Domain.dll
  97. BIN
      src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Domain.pdb
  98. 164
      src/CellularManagement.Application/obj/CellularManagement.Application.csproj.nuget.dgspec.json
  99. 19
      src/CellularManagement.Application/obj/CellularManagement.Application.csproj.nuget.g.props
  100. 6
      src/CellularManagement.Application/obj/CellularManagement.Application.csproj.nuget.g.targets

5
.gitignore

@ -38,4 +38,7 @@ artifacts/
*.suo
*.user
*.userosscache
*.sln.docstates
*.sln.docstates
# Node modules
node_modules/

13
CellularManagement.sln

@ -15,14 +15,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CellularManagement.Applicat
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CellularManagement.Infrastructure", "src\CellularManagement.Infrastructure\CellularManagement.Infrastructure.csproj", "{6CF192C7-060B-4328-B9F4-B05BD7401232}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CellularManagement.WebSocket", "src\CellularManagement.WebSocket\CellularManagement.WebSocket.csproj", "{155D6C93-9A46-45CD-B21F-0F60719515D0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0E228A93-4B80-450E-BDBB-BAF576E6FC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E228A93-4B80-450E-BDBB-BAF576E6FC73}.Debug|Any CPU.Build.0 = Debug|Any CPU
@ -44,6 +43,13 @@ Global
{6CF192C7-060B-4328-B9F4-B05BD7401232}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CF192C7-060B-4328-B9F4-B05BD7401232}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CF192C7-060B-4328-B9F4-B05BD7401232}.Release|Any CPU.Build.0 = Release|Any CPU
{155D6C93-9A46-45CD-B21F-0F60719515D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{155D6C93-9A46-45CD-B21F-0F60719515D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{155D6C93-9A46-45CD-B21F-0F60719515D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{155D6C93-9A46-45CD-B21F-0F60719515D0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0E228A93-4B80-450E-BDBB-BAF576E6FC73} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
@ -51,5 +57,6 @@ Global
{95F5B3F8-8293-43D0-98CF-88A9A224D20E} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
{FE169440-2554-4810-97D6-CC44808AAA56} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
{6CF192C7-060B-4328-B9F4-B05BD7401232} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
{155D6C93-9A46-45CD-B21F-0F60719515D0} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
EndGlobalSection
EndGlobal

225
README.md

@ -1,36 +1,189 @@
# CellularManagement 项目说明
## 项目概述
CellularManagement 是一个基于 Clean Architecture 架构的蜂窝网络管理系统。该项目采用领域驱动设计(DDD)原则,实现了高度模块化和可维护的代码结构。
CellularManagement 是一个基于 Clean Architecture 架构的蜂窝网络管理系统。该项目采用领域驱动设计(DDD)原则,实现了高度模块化和可维护的代码结构。系统提供了完整的用户认证、权限管理、实时通信等功能,适用于大规模分布式部署场景。
## 技术栈
- .NET 8.0
- 最新的 .NET 运行时和框架特性
- 高性能的异步编程模型
- 内置的依赖注入容器
- Entity Framework Core
- 支持多种数据库提供程序
- 强大的 LINQ 查询功能
- 自动迁移和数据库版本控制
- ASP.NET Core
- RESTful API 支持
- WebSocket 实时通信
- 中间件管道
- Clean Architecture
- 清晰的层次结构
- 依赖倒置原则
- 领域驱动设计
- DDD (领域驱动设计)
- 领域模型
- 聚合根
- 值对象
- 领域事件
- WebSocket
- 实时双向通信
- 连接状态管理
- 消息广播
## 项目结构
```
CellularManagement/
├── src/
│ ├── CellularManagement.Application/ # 应用层
│ │ ├── Common/ # 通用组件
│ │ ├── Features/ # 功能模块
│ │ └── Services/ # 应用服务
│ ├── CellularManagement.Domain/ # 领域层
│ │ ├── Entities/ # 领域实体
│ │ ├── Events/ # 领域事件
│ │ ├── Interfaces/ # 领域接口
│ │ └── ValueObjects/ # 值对象
│ ├── CellularManagement.Infrastructure/ # 基础设施层
│ │ ├── Persistence/ # 数据持久化
│ │ ├── Services/ # 基础设施服务
│ │ └── Identity/ # 身份认证
│ └── CellularManagement.WebApi/ # 表现层
│ ├── Controllers/ # API 控制器
│ ├── Middleware/ # 中间件
│ └── Filters/ # 过滤器
```
## 主要功能
- 用户认证与授权
- 角色管理
- 缓存服务
- JWT令牌管理
- 密钥轮换服务
### 用户认证与授权
- JWT 令牌认证
- 基于标准的 JWT 规范
- 支持令牌刷新机制
- 自动令牌过期处理
- 基于角色的访问控制 (RBAC)
- 灵活的角色定义
- 角色继承关系
- 动态角色分配
- 细粒度的权限管理
- 功能级权限控制
- 数据级权限控制
- 操作级权限控制
- 权限代码化存储
- 统一的权限标识符
- 权限分组管理
- 权限描述文档
- 多角色权限合并
- 自动权限去重
- 权限优先级处理
- 权限冲突解决
### 角色管理
- 角色创建与编辑
- 角色权限分配
- 角色继承关系
- 角色用户管理
### 缓存服务
- 分布式缓存支持
- 多级缓存策略
- 缓存自动失效
- 缓存预热机制
### JWT令牌管理
- 令牌生成与验证
- 令牌刷新机制
- 令牌黑名单
- 安全策略配置
### 密钥轮换服务
- 自动密钥轮换
- 密钥版本管理
- 密钥备份恢复
- 安全审计日志
### WebSocket实时通信
- 实时消息推送
- 点对点消息
- 广播消息
- 分组消息
- 连接管理
- 连接状态监控
- 自动重连机制
- 心跳检测
- 消息管道处理
- 消息过滤
- 消息转换
- 消息路由
- 分布式WebSocket管理
- 集群支持
- 会话共享
- 负载均衡
- 性能监控
- 连接数统计
- 消息吞吐量
- 延迟监控
## 角色管理模块
角色管理模块提供了完整的角色 CRUD 操作,采用 CQRS 模式实现,将命令和查询职责分离。
### 功能特性
- 创建角色
- 删除角色
- 获取单个角色
- 获取所有角色
### API 端点
| 方法 | 端点 | 描述 |
|--------|-------------------|--------------|
| POST | /api/roles/create | 创建新角色 |
| DELETE | /api/roles/{id} | 删除角色 |
| GET | /api/roles/{id} | 获取单个角色 |
| GET | /api/roles | 获取所有角色 |
### 代码结构
```
Features/Roles/
├── Commands/
│ ├── CreateRole/
│ │ ├── CreateRoleCommand.cs
│ │ ├── CreateRoleCommandHandler.cs
│ │ └── CreateRoleResponse.cs
│ ├── DeleteRole/
│ │ ├── DeleteRoleCommand.cs
│ │ ├── DeleteRoleCommandHandler.cs
│ │ └── DeleteRoleResponse.cs
│ └── RoleCommandHandler.cs
└── Queries/
├── GetRole/
│ ├── GetRoleQuery.cs
│ └── GetRoleResponse.cs
├── GetAllRolesQuery.cs
└── RoleQueryHandler.cs
```
### 设计模式
- CQRS(命令查询职责分离)
- 单一职责原则
- 依赖注入
- 异常处理
- 日志记录
## 开发环境要求
- .NET 8.0 SDK
- 最新的 .NET 开发工具
- 跨平台支持
- Visual Studio 2022 或 VS Code
- 完整的 IDE 支持
- 调试工具
- 代码分析
- SQL Server (可选,根据实际需求)
- 支持其他数据库
- 数据迁移工具
## 如何运行
1. 克隆项目
@ -50,9 +203,56 @@ dotnet run --project src/CellularManagement.WebApi
## 项目特点
- 采用 Clean Architecture 架构,实现关注点分离
- 清晰的层次结构
- 依赖倒置原则
- 领域驱动设计
- 使用 DDD 设计模式,提高代码可维护性
- 领域模型
- 聚合根
- 值对象
- 模块化设计,便于扩展和维护
- 功能模块化
- 插件化架构
- 配置驱动
- 完善的依赖注入机制
- 自动注册
- 生命周期管理
- 作用域控制
- 实时通信支持
- WebSocket
- 消息队列
- 事件总线
- 分布式架构支持
- 服务发现
- 负载均衡
- 配置中心
- 性能监控和指标收集
- 性能计数器
- 健康检查
- 日志追踪
## 开发指南
### 代码规范
- 遵循 C# 编码规范
- 使用 XML 文档注释
- 编写单元测试
- 代码审查流程
### 分支管理
- master: 主分支,稳定版本
- develop: 开发分支
- feature/*: 功能分支
- hotfix/*: 紧急修复分支
### 提交规范
- feat: 新功能
- fix: 修复
- docs: 文档
- style: 格式
- refactor: 重构
- test: 测试
- chore: 构建
## 贡献指南
1. Fork 项目
@ -62,4 +262,15 @@ dotnet run --project src/CellularManagement.WebApi
5. 创建 Pull Request
## 许可证
[MIT License](LICENSE)
[MIT License](LICENSE)
## 分支说明
### norm 分支
- 创建时间:2024年
- 分支目的:规范化代码结构和实现
- 主要更新:
- 代码结构优化
- 命名规范统一
- 注释完善
- 文档更新

211
docs/JWT-Implementation-Guide.md

@ -0,0 +1,211 @@
# JWT 实现指南
## 1. JwtOptions(配置类)
### 1.1 主要职责
- 存储和管理 JWT 相关的所有配置项
- 提供配置验证功能
- 支持从配置文件加载配置
### 1.2 关键配置项
```csharp
public class JwtOptions
{
public string SecretKey { get; set; } // JWT 密钥
public string Issuer { get; set; } // 颁发者
public string Audience { get; set; } // 受众
public int ExpiryMinutes { get; set; } // 访问令牌过期时间
public int RefreshTokenExpiryDays { get; set; } // 刷新令牌过期时间
public int ClockSkewMinutes { get; set; } // 时钟偏差
public int KeyRotationDays { get; set; } // 密钥轮换间隔
public int MinKeyLength { get; set; } // 密钥最小长度
}
```
### 1.3 配置验证
- 验证所有必需字段不为空
- 验证数值字段的有效性
- 验证密钥格式和长度
## 2. JwtOptionsExtensions(扩展方法类)
### 2.1 主要职责
- 提供 JwtOptions 的扩展功能
- 实现密钥强度验证
- 提供熵值计算功能
### 2.2 关键方法
```csharp
public static class JwtOptionsExtensions
{
// 验证密钥强度
public static void ValidateKeyStrength(this JwtOptions options)
// 计算字符串熵值
private static double CalculateEntropy(string input)
}
```
### 2.3 验证标准
- 密钥不能为空
- 密钥必须是有效的 Base64 字符串
- 密钥长度必须满足最小长度要求
- 密钥熵值必须大于 3.5(确保足够的随机性)
## 3. KeyRotationService(密钥管理服务)
### 3.1 主要职责
- 管理 JWT 密钥的生命周期
- 实现密钥自动轮换
- 提供密钥生成和验证功能
### 3.2 关键功能
- 密钥初始化
- 密钥轮换
- 密钥强度验证
- 密钥缓存管理
### 3.3 安全特性
- 定期自动轮换密钥
- 支持密钥预热
- 防止密钥泄露
- 密钥强度保证
## 4. JwtProvider(JWT 令牌提供者)
### 4.1 主要职责
- 生成 JWT 访问令牌和刷新令牌
- 验证 JWT 令牌
- 管理令牌黑名单
- 提供令牌解析功能
### 4.2 关键方法
```csharp
public interface IJwtProvider
{
string GenerateAccessToken(IEnumerable<Claim> claims);
string GenerateRefreshToken(IEnumerable<Claim> claims);
bool ValidateToken(string token);
void RevokeToken(string token);
void AddToBlacklist(string token);
IEnumerable<Claim> GetClaimsFromToken(string token);
}
```
### 4.3 安全特性
- 令牌撤销机制
- 令牌黑名单
- 完整的令牌验证
- 支持自定义声明
## 5. JwtBearerOptionsSetup(JWT Bearer 认证配置)
### 5.1 主要职责
- 配置 ASP.NET Core JWT Bearer 认证
- 设置令牌验证参数
- 配置认证事件处理
### 5.2 关键配置
```csharp
public class JwtBearerOptionsSetup : IConfigureOptions<JwtBearerOptions>
{
public void Configure(JwtBearerOptions options)
{
// 配置令牌验证参数
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = _jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = _jwtOptions.Audience,
ValidateLifetime = true,
// ... 其他配置
};
}
}
```
### 5.3 安全特性
- 强制使用 HTTPS
- 完整的令牌验证
- 详细的日志记录
- 异常处理机制
## 安全最佳实践
1. **密钥管理**
- 使用足够长的随机密钥
- 定期轮换密钥
- 使用安全的密钥存储机制
2. **令牌安全**
- 设置合理的过期时间
- 实现令牌撤销机制
- 使用令牌黑名单
3. **传输安全**
- 强制使用 HTTPS
- 验证令牌签名
- 验证令牌来源
4. **配置安全**
- 验证所有配置项
- 使用强类型配置
- 避免硬编码敏感信息
## 使用示例
### 1. 配置 JWT 选项
```csharp
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName));
```
### 2. 注册服务
```csharp
services.AddScoped<IJwtProvider, JwtProvider>();
services.AddScoped<IKeyRotationService, KeyRotationService>();
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>();
```
### 3. 使用 JWT 提供者
```csharp
public class AuthController : ControllerBase
{
private readonly IJwtProvider _jwtProvider;
public AuthController(IJwtProvider jwtProvider)
{
_jwtProvider = jwtProvider;
}
public IActionResult Login(LoginRequest request)
{
// 验证用户
var claims = GetUserClaims(user);
var token = _jwtProvider.GenerateAccessToken(claims);
return Ok(new { token });
}
}
```
## 注意事项
1. **密钥管理**
- 不要在代码中硬编码密钥
- 使用环境变量或密钥管理服务
- 定期轮换密钥
2. **令牌配置**
- 设置合理的过期时间
- 启用所有安全验证
- 使用 HTTPS
3. **错误处理**
- 实现完整的错误处理
- 记录详细的日志
- 返回适当的错误信息
4. **性能考虑**
- 使用缓存机制
- 优化令牌验证
- 控制令牌大小

217
docs/JWT-Service-Registration-Guide.md

@ -0,0 +1,217 @@
# JWT 服务注册指南
## 服务注册概览
JWT 相关的服务注册主要分布在两个位置:
1. `Program.cs` - Web API 层的服务注册
2. `DependencyInjection.cs` - 基础设施层的服务注册
## 1. 基础设施层注册 (DependencyInjection.cs)
### 1.1 JWT 配置注册
```csharp
// 配置 JWT 选项
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName));
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>();
services.AddScoped<IJwtProvider, JwtProvider>();
```
说明:
- `Configure<JwtOptions>`: 从配置文件加载 JWT 配置
- `JwtBearerOptionsSetup`: 配置 JWT Bearer 认证选项
- `JwtProvider`: 实现 JWT 令牌的生成和验证
### 1.2 密钥管理服务注册
```csharp
// 注册密钥轮换服务
services.AddSingleton<IKeyRotationService, KeyRotationService>();
services.AddHostedService<KeyRotationBackgroundService>();
```
说明:
- `KeyRotationService`: 管理 JWT 密钥的生命周期
- `KeyRotationBackgroundService`: 后台服务,定期执行密钥轮换
### 1.3 认证服务注册
```csharp
// 配置JWT认证
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer();
services.AddAuthorization();
```
说明:
- 设置默认认证方案为 JWT Bearer
- 启用授权服务
## 2. Web API 层注册 (Program.cs)
### 2.1 JWT 配置注册
```csharp
// 配置JWT认证
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("JwtOptions"));
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>();
```
### 2.2 认证服务注册
```csharp
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer();
```
### 2.3 Swagger 配置
```csharp
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
```
## 3. 服务注册优化建议
### 3.1 避免重复注册
目前存在重复注册的问题:
1. JWT 配置在两个地方都进行了注册
2. 认证服务在两个地方都进行了配置
建议优化方案:
```csharp
// 在 DependencyInjection.cs 中统一注册
public static IServiceCollection AddJwtServices(
this IServiceCollection services,
IConfiguration configuration)
{
// 配置 JWT 选项
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName));
// 注册 JWT 服务
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>();
services.AddScoped<IJwtProvider, JwtProvider>();
services.AddSingleton<IKeyRotationService, KeyRotationService>();
services.AddHostedService<KeyRotationBackgroundService>();
// 配置认证
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer();
services.AddAuthorization();
return services;
}
```
### 3.2 配置文件结构
建议的 JWT 配置结构:
```json
{
"JwtOptions": {
"SecretKey": "your-secret-key",
"Issuer": "your-issuer",
"Audience": "your-audience",
"ExpiryMinutes": 15,
"RefreshTokenExpiryDays": 7,
"ClockSkewMinutes": 5,
"KeyRotationDays": 30,
"MinKeyLength": 64
}
}
```
## 4. 中间件配置
### 4.1 认证中间件
```csharp
// 启用认证中间件
app.UseAuthentication();
// 启用授权中间件
app.UseAuthorization();
```
### 4.2 HTTPS 重定向
```csharp
// 启用 HTTPS 重定向
app.UseHttpsRedirection();
```
## 5. 使用建议
1. **配置管理**
- 使用强类型配置
- 集中管理配置项
- 避免硬编码敏感信息
2. **服务注册**
- 使用扩展方法组织服务注册
- 避免重复注册
- 遵循依赖注入最佳实践
3. **安全配置**
- 启用 HTTPS
- 配置适当的 CORS 策略
- 实现完整的认证和授权
4. **开发体验**
- 配置 Swagger 文档
- 提供详细的错误信息
- 实现适当的日志记录
## 6. 常见问题
1. **配置加载失败**
- 检查配置文件路径
- 验证配置节点名称
- 确保配置值格式正确
2. **认证失败**
- 检查令牌格式
- 验证密钥配置
- 确认过期时间设置
3. **密钥轮换问题**
- 检查轮换间隔设置
- 验证密钥生成逻辑
- 确保缓存正确更新
4. **性能问题**
- 优化令牌验证
- 使用适当的缓存策略
- 控制令牌大小

25
scripts/update-database.ps1

@ -0,0 +1,25 @@
# 设置错误时停止执行
$ErrorActionPreference = "Stop"
# 设置项目路径
$projectPath = "src/CellularManagement.Infrastructure"
$startupProjectPath = "src/CellularManagement.WebAPI"
# 设置迁移名称
$migrationName = "AddLoginLogs"
# 检查是否已存在迁移
$migrations = dotnet ef migrations list --project $projectPath --startup-project $startupProjectPath
if ($migrations -match $migrationName) {
Write-Host "迁移 '$migrationName' 已存在,跳过创建迁移步骤"
} else {
# 创建迁移
Write-Host "正在创建迁移 '$migrationName'..."
dotnet ef migrations add $migrationName --project $projectPath --startup-project $startupProjectPath
}
# 更新数据库
Write-Host "正在更新数据库..."
dotnet ef database update --project $projectPath --startup-project $startupProjectPath
Write-Host "数据库更新完成!"

73
src/CellularManagement.Application/Behaviours/ValidationBehaviour.cs

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Logging;
namespace CellularManagement.Application.Behaviours
{
/// <summary>
/// 请求验证行为类
/// </summary>
/// <typeparam name="TRequest">请求类型</typeparam>
/// <typeparam name="TResponse">响应类型</typeparam>
public sealed class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
private readonly ILogger<ValidationBehaviour<TRequest, TResponse>> _logger;
public ValidationBehaviour(
IEnumerable<IValidator<TRequest>> validators,
ILogger<ValidationBehaviour<TRequest, TResponse>> logger)
{
_validators = validators ?? throw new ArgumentNullException(nameof(validators));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// 处理请求验证
/// </summary>
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!_validators.Any())
{
return await next();
}
try
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(result => result.Errors)
.Where(failure => failure != null)
.ToList();
if (failures.Any())
{
_logger.LogWarning(
"验证失败 - {RequestType} - 错误: {@Errors}",
typeof(TRequest).Name,
failures);
throw new ValidationException(failures);
}
return await next();
}
catch (Exception ex)
{
_logger.LogError(ex, "验证过程中发生错误 - {RequestType}", typeof(TRequest).Name);
throw;
}
}
}
}

10
src/CellularManagement.Application/CellularManagement.Application.csproj

@ -1,11 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\CellularManagement.Domain\CellularManagement.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.0" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.8" />
<PackageReference Include="UAParser" Version="3.0.0" />
</ItemGroup>
<PropertyGroup>

57
src/CellularManagement.Application/Common/Extensions/EmailValidationExtensions.cs

@ -0,0 +1,57 @@
using System.Text.RegularExpressions;
namespace CellularManagement.Application.Common.Extensions;
/// <summary>
/// 邮箱验证扩展方法
/// 提供邮箱地址格式验证的功能
/// </summary>
public static class EmailValidationExtensions
{
/// <summary>
/// RFC 5322 标准的邮箱验证正则表达式
/// </summary>
private static readonly Regex EmailRegex = new(
@"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// 验证邮箱地址格式是否有效
/// </summary>
/// <remarks>
/// 验证规则:
/// 1. 不能为空或空白字符
/// 2. 必须符合 RFC 5322 标准格式
/// 3. 本地部分(@前)长度不超过64个字符
/// 4. 域名部分(@后)长度不超过255个字符
/// </remarks>
/// <param name="email">要验证的邮箱地址</param>
/// <returns>如果邮箱地址格式有效返回 true,否则返回 false</returns>
public static bool IsValidEmail(this string email)
{
if (string.IsNullOrWhiteSpace(email))
{
return false;
}
// 检查邮箱长度
if (email.Length > 254) // RFC 5321 规定的最大长度
{
return false;
}
// 检查本地部分和域名部分的长度
var parts = email.Split('@');
if (parts.Length != 2)
{
return false;
}
if (parts[0].Length > 64 || parts[1].Length > 255)
{
return false;
}
return EmailRegex.IsMatch(email);
}
}

30
src/CellularManagement.Application/DependencyInjection.cs

@ -1,19 +1,47 @@
using Microsoft.Extensions.DependencyInjection;
using MediatR;
using FluentValidation;
using CellularManagement.Application.Behaviours;
using CellularManagement.Domain.Services;
using Microsoft.Extensions.Configuration;
using CellularManagement.Domain.Options;
namespace CellularManagement.Application;
/// <summary>
/// 应用层依赖注入扩展
/// 负责注册应用层所需的所有服务
/// </summary>
public static class DependencyInjection
{
/// <summary>
/// 添加应用层服务
/// 包括命令/查询处理、验证等服务
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="configuration">配置</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
public static IServiceCollection AddApplication(
this IServiceCollection services,
IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
// 获取当前程序集
var assembly = typeof(DependencyInjection).Assembly;
// 配置 MediatR 服务
// 1. 注册程序集中的所有处理器
// 2. 添加验证行为中间件,用于自动验证请求
services.AddMediatR(configuration =>
configuration
.RegisterServicesFromAssembly(assembly)
.AddOpenBehavior(typeof(ValidationBehaviour<,>)));
// 注册验证器
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
return services;
}
}

19
src/CellularManagement.Application/Features/Auth/Commands/AuthenticateUser/AuthenticateUserCommand.cs

@ -0,0 +1,19 @@
using CellularManagement.Application.Features.Auth.Models;
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser;
/// <summary>
/// 用户认证命令
/// </summary>
public sealed record AuthenticateUserCommand(
/// <summary>
/// 账号
/// </summary>
string UserName,
/// <summary>
/// 密码
/// </summary>
string Password) : IRequest<OperationResult<AuthenticateUserResponse>>;

76
src/CellularManagement.Application/Features/Auth/Commands/AuthenticateUser/AuthenticateUserCommandHandler.cs

@ -0,0 +1,76 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Application.Common.Extensions;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Services;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using System;
using CellularManagement.Domain.Common;
using Microsoft.AspNetCore.Http;
using UAParser;
using Microsoft.EntityFrameworkCore;
using CellularManagement.Application.Features.Auth.Models;
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser;
/// <summary>
/// 账号登录命令处理器
/// </summary>
public sealed class AuthenticateUserCommandHandler : BaseLoginCommandHandler<AuthenticateUserCommand, AuthenticateUserResponse>
{
/// <summary>
/// 初始化处理器
/// </summary>
public AuthenticateUserCommandHandler(
UserManager<AppUser> userManager,
IJwtProvider jwtProvider,
ILogger<AuthenticateUserCommandHandler> logger,
IUserRoleRepository userRoleRepository,
IRolePermissionRepository rolePermissionRepository,
IUnitOfWork unitOfWork,
ILoginLogRepository loginLogRepository,
IHttpContextAccessor httpContextAccessor)
: base(userManager, jwtProvider, logger, userRoleRepository, rolePermissionRepository, unitOfWork, loginLogRepository, httpContextAccessor)
{
}
/// <summary>
/// 处理认证请求
/// </summary>
public override Task<OperationResult<AuthenticateUserResponse>> Handle(
AuthenticateUserCommand request,
CancellationToken cancellationToken)
{
return HandleLoginAsync(request, cancellationToken);
}
/// <summary>
/// 查找用户
/// </summary>
protected override Task<AppUser?> FindUserAsync(AuthenticateUserCommand request)
{
return _userManager.FindByNameAsync(request.UserName);
}
/// <summary>
/// 获取用户标识
/// </summary>
protected override string GetUserIdentifier(AuthenticateUserCommand request)
{
return request.UserName;
}
/// <summary>
/// 获取密码
/// </summary>
protected override string GetPassword(AuthenticateUserCommand request)
{
return request.Password;
}
}

28
src/CellularManagement.Application/Features/Auth/Commands/AuthenticateUser/AuthenticateUserCommandValidator.cs

@ -0,0 +1,28 @@
using FluentValidation;
using CellularManagement.Application.Common;
using CellularManagement.Application.Common.Extensions;
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser;
/// <summary>
/// 用户认证命令验证器
/// </summary>
public sealed class AuthenticateUserCommandValidator : AbstractValidator<AuthenticateUserCommand>
{
/// <summary>
/// 初始化验证器
/// </summary>
public AuthenticateUserCommandValidator()
{
// 验证账号
RuleFor(x => x.UserName)
.NotEmpty().WithMessage("账号不能为空")
.MaximumLength(256).WithMessage("账号长度不能超过256个字符");
// 验证密码
RuleFor(x => x.Password)
.NotEmpty().WithMessage("密码不能为空")
.MinimumLength(6).WithMessage("密码长度不能少于6个字符")
.MaximumLength(50).WithMessage("密码长度不能超过50个字符");
}
}

283
src/CellularManagement.Application/Features/Auth/Commands/BaseLoginCommandHandler.cs

@ -0,0 +1,283 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Services;
using System.Threading.Tasks;
using System.Threading;
using CellularManagement.Domain.Common;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using CellularManagement.Application.Features.Auth.Models;
namespace CellularManagement.Application.Features.Auth.Commands;
/// <summary>
/// 登录命令处理器基类
/// </summary>
public abstract class BaseLoginCommandHandler<TCommand, TResponse> : IRequestHandler<TCommand, OperationResult<TResponse>>
where TCommand : IRequest<OperationResult<TResponse>>
where TResponse : class
{
protected readonly UserManager<AppUser> _userManager;
protected readonly IJwtProvider _jwtProvider;
protected readonly ILogger _logger;
protected readonly IUserRoleRepository _userRoleRepository;
protected readonly IRolePermissionRepository _rolePermissionRepository;
protected readonly IUnitOfWork _unitOfWork;
protected readonly ILoginLogRepository _loginLogRepository;
protected readonly IHttpContextAccessor _httpContextAccessor;
private const int MaxRetryAttempts = 3;
/// <summary>
/// 初始化处理器
/// </summary>
protected BaseLoginCommandHandler(
UserManager<AppUser> userManager,
IJwtProvider jwtProvider,
ILogger logger,
IUserRoleRepository userRoleRepository,
IRolePermissionRepository rolePermissionRepository,
IUnitOfWork unitOfWork,
ILoginLogRepository loginLogRepository,
IHttpContextAccessor httpContextAccessor)
{
_userManager = userManager;
_jwtProvider = jwtProvider;
_logger = logger;
_userRoleRepository = userRoleRepository;
_rolePermissionRepository = rolePermissionRepository;
_unitOfWork = unitOfWork;
_loginLogRepository = loginLogRepository;
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 处理登录请求
/// </summary>
public abstract Task<OperationResult<TResponse>> Handle(TCommand request, CancellationToken cancellationToken);
/// <summary>
/// 查找用户
/// </summary>
protected abstract Task<AppUser?> FindUserAsync(TCommand request);
/// <summary>
/// 获取用户标识
/// </summary>
protected abstract string GetUserIdentifier(TCommand request);
/// <summary>
/// 获取密码
/// </summary>
protected abstract string GetPassword(TCommand request);
/// <summary>
/// 验证凭据
/// </summary>
protected virtual async Task<bool> ValidateCredentialsAsync(TCommand request, AppUser user)
{
return await _userManager.CheckPasswordAsync(user, GetPassword(request));
}
/// <summary>
/// 处理登录逻辑
/// </summary>
protected async Task<OperationResult<TResponse>> HandleLoginAsync(TCommand request, CancellationToken cancellationToken)
{
try
{
var httpContext = _httpContextAccessor.HttpContext;
var ipAddress = httpContext?.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
var userAgent = httpContext?.Request.Headers["User-Agent"].ToString() ?? "Unknown";
// 检查IP是否被限制
if (await _loginLogRepository.IsIpRestrictedAsync(ipAddress, cancellationToken))
{
_logger.LogWarning("IP {IpAddress} 已被限制登录", ipAddress);
return OperationResult<TResponse>.CreateFailure("登录尝试次数过多,请稍后再试");
}
// 解析设备信息
var parser = UAParser.Parser.GetDefault();
var clientInfo = parser.Parse(userAgent);
// 查找用户
var user = await FindUserAsync(request);
var userIdentifier = GetUserIdentifier(request);
// 创建登录日志
var loginLog = new LoginLog
{
Id = Guid.NewGuid(),
UserId = user?.Id ?? "Unknown",
LoginTime = DateTime.UtcNow,
IpAddress = ipAddress,
UserAgent = userAgent,
Browser = clientInfo.UA.ToString(),
OperatingSystem = clientInfo.OS.ToString()
};
if (user == null)
{
loginLog.IsSuccess = false;
loginLog.FailureReason = "用户不存在";
await _loginLogRepository.AddAsync(loginLog, cancellationToken);
_logger.LogWarning("用户 {UserIdentifier} 不存在", userIdentifier);
return OperationResult<TResponse>.CreateFailure("账号或密码错误");
}
// 检查用户是否已删除
if (user.IsDeleted)
{
loginLog.IsSuccess = false;
loginLog.FailureReason = "用户已被删除";
await _loginLogRepository.AddAsync(loginLog, cancellationToken);
_logger.LogWarning("用户 {UserIdentifier} 已被删除", userIdentifier);
return OperationResult<TResponse>.CreateFailure("用户已被删除");
}
// 检查用户是否已禁用
if (!user.IsActive)
{
loginLog.IsSuccess = false;
loginLog.FailureReason = "用户已被禁用";
await _loginLogRepository.AddAsync(loginLog, cancellationToken);
_logger.LogWarning("用户 {UserIdentifier} 已被禁用", userIdentifier);
return OperationResult<TResponse>.CreateFailure("用户已被禁用");
}
// 验证凭据
var isValidCredentials = await ValidateCredentialsAsync(request, user);
if (!isValidCredentials)
{
loginLog.IsSuccess = false;
loginLog.FailureReason = "验证失败";
await _loginLogRepository.AddAsync(loginLog, cancellationToken);
_logger.LogWarning("用户 {UserIdentifier} 验证失败", userIdentifier);
return OperationResult<TResponse>.CreateFailure("验证失败");
}
// 更新最后登录时间(带并发控制)
var retryCount = 0;
var updateSuccess = false;
while (!updateSuccess && retryCount < MaxRetryAttempts)
{
try
{
await _unitOfWork.ExecuteTransactionAsync(async () =>
{
user.LastLoginTime = DateTime.UtcNow;
var result = await _userManager.UpdateAsync(user);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("更新用户最后登录时间失败: {Errors}", string.Join(", ", errors));
throw new InvalidOperationException(string.Join(", ", errors));
}
}, cancellationToken: cancellationToken);
updateSuccess = true;
}
catch (DbUpdateConcurrencyException)
{
retryCount++;
_logger.LogWarning("用户 {UserIdentifier} 更新时发生并发冲突,重试次数: {RetryCount}",
userIdentifier, retryCount);
if (retryCount >= MaxRetryAttempts)
{
_logger.LogError("用户 {UserIdentifier} 更新失败,超过最大重试次数", userIdentifier);
return OperationResult<TResponse>.CreateFailure("系统繁忙,请稍后重试");
}
// 重新获取最新数据
user = await _userManager.FindByIdAsync(user.Id);
if (user == null)
{
return OperationResult<TResponse>.CreateFailure("用户不存在");
}
}
}
// 获取用户角色
var roles = await _userRoleRepository.GetUserRolesAsync(user.Id, cancellationToken);
// 创建用户声明
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.Name, user.UserName!),
new(ClaimTypes.Email, user.Email!)
};
// 添加最后登录时间声明(如果存在)
if (user.LastLoginTime.HasValue)
{
claims.Add(new Claim("LastLoginTime", user.LastLoginTime.Value.ToUniversalTime().ToString("o")));
}
// 添加角色声明
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
// 获取所有角色的权限
var permissions = new Dictionary<string, bool>();
foreach (var role in roles)
{
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(role, cancellationToken);
foreach (var rolePermission in rolePermissions)
{
if (!permissions.ContainsKey(rolePermission.Permission.Code))
{
permissions[rolePermission.Permission.Code] = true;
}
}
}
// 生成访问令牌
var accessToken = _jwtProvider.GenerateAccessToken(claims);
// 生成刷新令牌
var refreshToken = _jwtProvider.GenerateRefreshToken(claims);
// 获取令牌过期时间
var expiresAt = _jwtProvider.GetTokenExpiration(accessToken);
// 创建用户信息
var userInfo = new UserInfo(
user.Id,
user.UserName!,
user.RealName,
user.Email!,
user.PhoneNumber,
roles.ToList().AsReadOnly(),
permissions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
// 记录成功的登录日志
loginLog.IsSuccess = true;
await _loginLogRepository.AddAsync(loginLog, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("用户 {UserIdentifier} 认证成功", userIdentifier);
// 返回认证结果
var response = (TResponse)Activator.CreateInstance(
typeof(TResponse),
accessToken,
refreshToken,
expiresAt,
userInfo)!;
return OperationResult<TResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "用户认证失败");
return OperationResult<TResponse>.CreateFailure("认证失败,请稍后重试");
}
}
}

19
src/CellularManagement.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommand.cs

@ -0,0 +1,19 @@
using CellularManagement.Domain.Common;
using MediatR;
using CellularManagement.Application.Features.Auth.Models;
namespace CellularManagement.Application.Features.Auth.Commands.EmailLogin;
/// <summary>
/// 邮箱登录命令
/// </summary>
public sealed record EmailLoginCommand(
/// <summary>
/// 邮箱
/// </summary>
string Email,
/// <summary>
/// 验证码
/// </summary>
string VerificationCode) : IRequest<OperationResult<EmailLoginResponse>>;

82
src/CellularManagement.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandHandler.cs

@ -0,0 +1,82 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Services;
using System.Threading.Tasks;
using System.Threading;
using CellularManagement.Domain.Common;
using Microsoft.AspNetCore.Http;
using CellularManagement.Application.Features.Auth.Models;
namespace CellularManagement.Application.Features.Auth.Commands.EmailLogin;
/// <summary>
/// 邮箱登录命令处理器
/// </summary>
public sealed class EmailLoginCommandHandler : BaseLoginCommandHandler<EmailLoginCommand, EmailLoginResponse>
{
private readonly IEmailVerificationService _emailVerificationService;
/// <summary>
/// 初始化处理器
/// </summary>
public EmailLoginCommandHandler(
UserManager<AppUser> userManager,
IJwtProvider jwtProvider,
ILogger<EmailLoginCommandHandler> logger,
IUserRoleRepository userRoleRepository,
IRolePermissionRepository rolePermissionRepository,
IUnitOfWork unitOfWork,
ILoginLogRepository loginLogRepository,
IHttpContextAccessor httpContextAccessor,
IEmailVerificationService emailVerificationService)
: base(userManager, jwtProvider, logger, userRoleRepository, rolePermissionRepository, unitOfWork, loginLogRepository, httpContextAccessor)
{
_emailVerificationService = emailVerificationService;
}
/// <summary>
/// 处理认证请求
/// </summary>
public override Task<OperationResult<EmailLoginResponse>> Handle(
EmailLoginCommand request,
CancellationToken cancellationToken)
{
return HandleLoginAsync(request, cancellationToken);
}
/// <summary>
/// 查找用户
/// </summary>
protected override Task<AppUser?> FindUserAsync(EmailLoginCommand request)
{
return _userManager.FindByEmailAsync(request.Email);
}
/// <summary>
/// 获取用户标识
/// </summary>
protected override string GetUserIdentifier(EmailLoginCommand request)
{
return request.Email;
}
/// <summary>
/// 获取密码
/// </summary>
protected override string GetPassword(EmailLoginCommand request)
{
return request.VerificationCode;
}
/// <summary>
/// 验证凭据
/// </summary>
protected override async Task<bool> ValidateCredentialsAsync(EmailLoginCommand request, AppUser user)
{
// 验证邮箱验证码
return await _emailVerificationService.VerifyCodeAsync(request.Email, request.VerificationCode);
}
}

29
src/CellularManagement.Application/Features/Auth/Commands/EmailLogin/EmailLoginCommandValidator.cs

@ -0,0 +1,29 @@
using FluentValidation;
using CellularManagement.Application.Common.Extensions;
namespace CellularManagement.Application.Features.Auth.Commands.EmailLogin;
/// <summary>
/// 邮箱登录命令验证器
/// </summary>
public sealed class EmailLoginCommandValidator : AbstractValidator<EmailLoginCommand>
{
/// <summary>
/// 初始化验证器
/// </summary>
public EmailLoginCommandValidator()
{
// 验证邮箱
RuleFor(x => x.Email)
.NotEmpty().WithMessage("邮箱不能为空")
.MaximumLength(256).WithMessage("邮箱长度不能超过256个字符")
.Must(x => x.IsValidEmail())
.WithMessage("邮箱格式不正确");
// 验证验证码
RuleFor(x => x.VerificationCode)
.NotEmpty().WithMessage("验证码不能为空")
.Length(6).WithMessage("验证码长度必须为6位")
.Matches("^[0-9]+$").WithMessage("验证码只能包含数字");
}
}

10
src/CellularManagement.Application/Features/Auth/Commands/GenerateCaptcha/GenerateCaptchaCommand.cs

@ -0,0 +1,10 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Auth.Commands.GenerateCaptcha;
public class GenerateCaptchaCommand : IRequest<OperationResult<GenerateCaptchaResponse>>
{
public int Length { get; set; } = 4;
public string? ClientId { get; set; }
}

79
src/CellularManagement.Application/Features/Auth/Commands/GenerateCaptcha/GenerateCaptchaCommandHandler.cs

@ -0,0 +1,79 @@
using MediatR;
using CellularManagement.Domain.Services;
using CellularManagement.Application.Common;
using System.Text;
using CellularManagement.Domain.Common;
using Microsoft.Extensions.Caching.Memory;
namespace CellularManagement.Application.Features.Auth.Commands.GenerateCaptcha;
public class GenerateCaptchaCommandHandler : IRequestHandler<GenerateCaptchaCommand, OperationResult<GenerateCaptchaResponse>>
{
private readonly ICaptchaService _captchaService;
private readonly ICacheService _cacheService;
private const string RATE_LIMIT_KEY_PREFIX = "captcha_rate_limit:";
private const int MAX_REQUESTS_PER_MINUTE = 30;
public GenerateCaptchaCommandHandler(
ICaptchaService captchaService,
ICacheService cacheService)
{
_captchaService = captchaService;
_cacheService = cacheService;
}
public async Task<OperationResult<GenerateCaptchaResponse>> Handle(
GenerateCaptchaCommand request,
CancellationToken cancellationToken)
{
try
{
// 获取客户端IP或标识
var clientId = request.ClientId ?? "anonymous";
var rateLimitKey = $"{RATE_LIMIT_KEY_PREFIX}{clientId}";
// 检查请求频率
var requestCount = _cacheService.Get<int>(rateLimitKey);
if (requestCount >= MAX_REQUESTS_PER_MINUTE)
{
var remainingTime = _cacheService.Get<DateTime>($"{rateLimitKey}:expiry");
var seconds = remainingTime != default
? (int)(remainingTime - DateTime.UtcNow).TotalSeconds
: 60;
return OperationResult<GenerateCaptchaResponse>.CreateFailure(
$"请求过于频繁,请{seconds}秒后再试");
}
// 更新请求计数
var rateLimitOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(1));
_cacheService.Set(rateLimitKey, requestCount + 1, rateLimitOptions);
_cacheService.Set($"{rateLimitKey}:expiry", DateTime.UtcNow.AddMinutes(1), rateLimitOptions);
// 生成验证码
var (code, imageBytes) = _captchaService.GenerateCaptcha(request.Length);
// 生成唯一ID
var captchaId = Guid.NewGuid().ToString();
// 将验证码存入缓存,设置5分钟过期
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5));
_cacheService.Set($"captcha:{captchaId}", code, cacheOptions);
// 将图片转换为Base64
var base64Image = Convert.ToBase64String(imageBytes);
return await Task.FromResult(OperationResult<GenerateCaptchaResponse>.CreateSuccess(new GenerateCaptchaResponse
{
CaptchaId = captchaId,
ImageBase64 = base64Image
}));
}
catch (Exception ex)
{
return await Task.FromResult(OperationResult<GenerateCaptchaResponse>.CreateFailure("生成验证码失败"));
}
}
}

7
src/CellularManagement.Application/Features/Auth/Commands/GenerateCaptcha/GenerateCaptchaResponse.cs

@ -0,0 +1,7 @@
namespace CellularManagement.Application.Features.Auth.Commands.GenerateCaptcha;
public class GenerateCaptchaResponse
{
public string CaptchaId { get; set; }
public string ImageBase64 { get; set; }
}

21
src/CellularManagement.Application/Features/Auth/Commands/Login/LoginRequest.cs

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.Auth.Commands.Login;
/// <summary>
/// 登录请求
/// </summary>
public class LoginRequest
{
/// <summary>
/// 用户名
/// </summary>
[Required(ErrorMessage = "账号不能为空")]
public string UserName { get; set; } = string.Empty;
/// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "密码不能为空")]
public string Password { get; set; } = string.Empty;
}

45
src/CellularManagement.Application/Features/Auth/Commands/Login/LoginRequestExample.cs

@ -0,0 +1,45 @@
using Swashbuckle.AspNetCore.Filters;
namespace CellularManagement.Application.Features.Auth.Commands.Login;
/// <summary>
/// 登录请求示例
/// </summary>
public class LoginRequestExample : IExamplesProvider<LoginRequest>
{
/// <summary>
/// 获取示例
/// </summary>
/// <returns>登录请求示例</returns>
public LoginRequest GetExamples()
{
return new LoginRequest
{
UserName = "hyh",
Password = "Hyh@123456"
};
}
}
/// <summary>
/// 登录请求示例提供者
/// </summary>
public class LoginRequestExamples : IMultipleExamplesProvider<LoginRequest>
{
/// <summary>
/// 获取示例
/// </summary>
/// <returns>登录请求示例集合</returns>
public IEnumerable<SwaggerExample<LoginRequest>> GetExamples()
{
yield return SwaggerExample.Create(
"默认用户",
"使用默认测试账号",
new LoginRequest
{
UserName = "hyh",
Password = "Hyh@123456"
}
);
}
}

20
src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommand.cs

@ -0,0 +1,20 @@
using MediatR;
using CellularManagement.Domain.Common;
namespace CellularManagement.Application.Features.Auth.Commands.Logout;
/// <summary>
/// 登出命令
/// </summary>
public class LogoutCommand : IRequest<OperationResult<bool>>
{
/// <summary>
/// 访问令牌
/// </summary>
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 刷新令牌
/// </summary>
public string RefreshToken { get; set; } = string.Empty;
}

243
src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommandHandler.cs

@ -0,0 +1,243 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Services;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Entities;
using System.Threading;
using System.Threading.Tasks;
using System.Security.Claims;
using System.Collections.Generic;
using System.Diagnostics;
namespace CellularManagement.Application.Features.Auth.Commands.Logout;
/// <summary>
/// 登出命令处理器
/// </summary>
public class LogoutCommandHandler : IRequestHandler<LogoutCommand, OperationResult<bool>>
{
private readonly IJwtProvider _jwtProvider;
private readonly ILogger<LogoutCommandHandler> _logger;
private readonly ILoginLogRepository _loginLogRepository;
private readonly IUnitOfWork _unitOfWork;
private static readonly Action<ILogger, string, Exception?> LogTokenValidationError =
LoggerMessage.Define<string>(LogLevel.Warning, new EventId(1, "TokenValidationError"), "令牌验证错误: {Message}");
private static readonly Action<ILogger, string, string, Exception?> LogTokenProcessingError =
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(2, "TokenProcessingError"), "处理令牌 {TokenType} 时发生错误: {Message}");
/// <summary>
/// 初始化处理器
/// </summary>
public LogoutCommandHandler(
IJwtProvider jwtProvider,
ILogger<LogoutCommandHandler> logger,
ILoginLogRepository loginLogRepository,
IUnitOfWork unitOfWork)
{
_jwtProvider = jwtProvider;
_logger = logger;
_loginLogRepository = loginLogRepository;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 处理登出请求
/// </summary>
public async Task<OperationResult<bool>> Handle(LogoutCommand request, CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
var correlationId = Guid.NewGuid().ToString();
_logger.LogInformation("[{CorrelationId}] 开始处理登出请求", correlationId);
try
{
// 快速验证令牌存在性
if (string.IsNullOrEmpty(request.AccessToken) || string.IsNullOrEmpty(request.RefreshToken))
{
_logger.LogWarning("[{CorrelationId}] 令牌为空", correlationId);
return OperationResult<bool>.CreateFailure("无效的请求");
}
_logger.LogDebug("[{CorrelationId}] 开始验证令牌", correlationId);
string? userId = null;
bool isAccessTokenValid = false;
bool isRefreshTokenValid = false;
var tokenValidationSw = Stopwatch.StartNew();
try
{
// 验证访问令牌
_logger.LogDebug("[{CorrelationId}] 开始验证访问令牌", correlationId);
var accessTokenClaims = _jwtProvider.GetClaimsFromToken(request.AccessToken);
if (accessTokenClaims.Any())
{
var tokenType = accessTokenClaims.FirstOrDefault(c => c.Type == "token_type")?.Value;
_logger.LogDebug("[{CorrelationId}] 访问令牌类型: {TokenType}", correlationId, tokenType);
if (tokenType == "access_token")
{
userId = accessTokenClaims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
isAccessTokenValid = !string.IsNullOrEmpty(userId);
_logger.LogDebug("[{CorrelationId}] 访问令牌验证结果: {IsValid}, 用户ID: {UserId}",
correlationId, isAccessTokenValid, userId);
}
else
{
_logger.LogWarning("[{CorrelationId}] 访问令牌类型不匹配: {TokenType}", correlationId, tokenType);
}
}
}
catch (Exception ex)
{
LogTokenValidationError(_logger, "访问令牌验证失败", ex);
}
try
{
// 验证刷新令牌
_logger.LogDebug("[{CorrelationId}] 开始验证刷新令牌", correlationId);
var refreshTokenClaims = _jwtProvider.GetClaimsFromToken(request.RefreshToken);
if (refreshTokenClaims.Any())
{
var tokenType = refreshTokenClaims.FirstOrDefault(c => c.Type == "token_type")?.Value;
_logger.LogDebug("[{CorrelationId}] 刷新令牌类型: {TokenType}", correlationId, tokenType);
if (tokenType == "refresh_token")
{
var refreshTokenUserId = refreshTokenClaims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
isRefreshTokenValid = !string.IsNullOrEmpty(refreshTokenUserId) &&
(string.IsNullOrEmpty(userId) || userId == refreshTokenUserId);
if (isRefreshTokenValid && string.IsNullOrEmpty(userId))
{
userId = refreshTokenUserId;
}
_logger.LogDebug("[{CorrelationId}] 刷新令牌验证结果: {IsValid}, 用户ID: {UserId}",
correlationId, isRefreshTokenValid, refreshTokenUserId);
}
else
{
_logger.LogWarning("[{CorrelationId}] 刷新令牌类型不匹配: {TokenType}", correlationId, tokenType);
}
}
}
catch (Exception ex)
{
LogTokenValidationError(_logger, "刷新令牌验证失败", ex);
}
tokenValidationSw.Stop();
_logger.LogInformation("[{CorrelationId}] 令牌验证完成,耗时: {ElapsedMs}ms",
correlationId, tokenValidationSw.ElapsedMilliseconds);
// 如果两个令牌都无效,直接返回成功
if (!isAccessTokenValid && !isRefreshTokenValid)
{
_logger.LogInformation("[{CorrelationId}] 所有令牌无效,直接返回成功", correlationId);
return OperationResult<bool>.CreateSuccess(true);
}
// 记录登出日志
if (!string.IsNullOrEmpty(userId))
{
_logger.LogDebug("[{CorrelationId}] 开始记录登出日志,用户ID: {UserId}", correlationId, userId);
var logSw = Stopwatch.StartNew();
await RecordLogoutLogAsync(userId, correlationId, cancellationToken);
logSw.Stop();
_logger.LogInformation("[{CorrelationId}] 登出日志记录完成,耗时: {ElapsedMs}ms",
correlationId, logSw.ElapsedMilliseconds);
}
// 异步处理令牌撤销和黑名单
_ = Task.Run(async () =>
{
var tokenProcessingSw = Stopwatch.StartNew();
try
{
if (isAccessTokenValid)
{
_logger.LogDebug("[{CorrelationId}] 开始处理访问令牌撤销和黑名单", correlationId);
await ProcessTokenAsync(request.AccessToken, "访问令牌", correlationId);
}
if (isRefreshTokenValid)
{
_logger.LogDebug("[{CorrelationId}] 开始处理刷新令牌撤销和黑名单", correlationId);
await ProcessTokenAsync(request.RefreshToken, "刷新令牌", correlationId);
}
}
catch (Exception ex)
{
LogTokenProcessingError(_logger, "令牌处理", "处理令牌撤销和黑名单时发生异常", ex);
}
finally
{
tokenProcessingSw.Stop();
_logger.LogInformation("[{CorrelationId}] 令牌处理完成,耗时: {ElapsedMs}ms",
correlationId, tokenProcessingSw.ElapsedMilliseconds);
}
});
sw.Stop();
_logger.LogInformation("[{CorrelationId}] 登出请求处理完成,总耗时: {ElapsedMs}ms",
correlationId, sw.ElapsedMilliseconds);
return OperationResult<bool>.CreateSuccess(true);
}
catch (Exception ex)
{
sw.Stop();
_logger.LogError(ex, "[{CorrelationId}] 处理登出请求时发生异常,耗时: {ElapsedMs}ms",
correlationId, sw.ElapsedMilliseconds);
return OperationResult<bool>.CreateFailure("系统错误");
}
}
/// <summary>
/// 记录登出日志
/// </summary>
private async Task RecordLogoutLogAsync(string userId, string correlationId, CancellationToken cancellationToken)
{
try
{
var loginLog = new LoginLog
{
Id = Guid.NewGuid(),
UserId = userId,
LoginTime = DateTime.UtcNow,
IsSuccess = true
};
await _loginLogRepository.AddAsync(loginLog, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogDebug("[{CorrelationId}] 登出日志记录成功", correlationId);
}
catch (Exception ex)
{
_logger.LogError(ex, "[{CorrelationId}] 记录登出日志时发生异常", correlationId);
throw;
}
}
/// <summary>
/// 处理令牌撤销和黑名单
/// </summary>
private async Task ProcessTokenAsync(string token, string tokenType, string correlationId)
{
try
{
var sw = Stopwatch.StartNew();
_jwtProvider.RevokeToken(token);
_jwtProvider.AddToBlacklist(token);
sw.Stop();
_logger.LogDebug("[{CorrelationId}] {TokenType}处理完成,耗时: {ElapsedMs}ms",
correlationId, tokenType, sw.ElapsedMilliseconds);
await Task.CompletedTask;
}
catch (Exception ex)
{
LogTokenProcessingError(_logger, tokenType, "处理令牌时发生异常", ex);
throw;
}
}
}

19
src/CellularManagement.Application/Features/Auth/Commands/RefreshToken/RefreshTokenCommand.cs

@ -0,0 +1,19 @@
using MediatR;
using System.ComponentModel.DataAnnotations;
using CellularManagement.Application.Features.Auth.Commands.AuthenticateUser;
using CellularManagement.Domain.Common;
using CellularManagement.Application.Features.Auth.Models;
namespace CellularManagement.Application.Features.Auth.Commands.RefreshToken;
/// <summary>
/// 刷新令牌命令
/// </summary>
public class RefreshTokenCommand : IRequest<OperationResult<AuthenticateUserResponse>>
{
/// <summary>
/// 刷新令牌
/// </summary>
[Required(ErrorMessage = "刷新令牌不能为空")]
public string RefreshToken { get; set; } = string.Empty;
}

148
src/CellularManagement.Application/Features/Auth/Commands/RefreshToken/RefreshTokenCommandHandler.cs

@ -0,0 +1,148 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Application.Common;
using System.Security.Claims;
using CellularManagement.Application.Features.Auth.Commands.AuthenticateUser;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Services;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using System.Collections.Generic;
using System;
using CellularManagement.Domain.Common;
using CellularManagement.Application.Features.Auth.Models;
namespace CellularManagement.Application.Features.Auth.Commands.RefreshToken;
/// <summary>
/// 刷新令牌命令处理器
/// </summary>
public sealed class RefreshTokenCommandHandler : IRequestHandler<RefreshTokenCommand, OperationResult<AuthenticateUserResponse>>
{
private readonly IJwtProvider _jwtProvider;
private readonly ILogger<RefreshTokenCommandHandler> _logger;
private readonly IUserRoleRepository _userRoleRepository;
private readonly IRolePermissionRepository _rolePermissionRepository;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="jwtProvider">JWT提供者</param>
/// <param name="logger">日志记录器</param>
/// <param name="userRoleRepository">用户角色仓储</param>
/// <param name="rolePermissionRepository">角色权限仓储</param>
public RefreshTokenCommandHandler(
IJwtProvider jwtProvider,
ILogger<RefreshTokenCommandHandler> logger,
IUserRoleRepository userRoleRepository,
IRolePermissionRepository rolePermissionRepository)
{
_jwtProvider = jwtProvider;
_logger = logger;
_userRoleRepository = userRoleRepository;
_rolePermissionRepository = rolePermissionRepository;
}
/// <summary>
/// 处理刷新令牌请求
/// </summary>
/// <param name="request">刷新令牌命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>刷新令牌结果</returns>
public async Task<OperationResult<AuthenticateUserResponse>> Handle(
RefreshTokenCommand request,
CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始处理刷新令牌请求");
// 验证刷新令牌
if (!_jwtProvider.ValidateToken(request.RefreshToken))
{
_logger.LogWarning("刷新令牌验证失败");
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌");
}
// 获取令牌中的声明
var claims = _jwtProvider.GetClaimsFromToken(request.RefreshToken);
if (!claims.Any())
{
_logger.LogWarning("刷新令牌中未找到声明");
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌");
}
// 检查令牌类型
var tokenType = _jwtProvider.GetTokenType(request.RefreshToken);
if (tokenType != "refresh_token")
{
_logger.LogWarning("令牌类型不是刷新令牌");
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌");
}
// 检查令牌是否在黑名单中
if (_jwtProvider.GetAllClaims(request.RefreshToken).ContainsKey("blacklisted"))
{
_logger.LogWarning("刷新令牌已被列入黑名单");
return OperationResult<AuthenticateUserResponse>.CreateFailure("刷新令牌已被撤销");
}
// 获取用户ID
var userId = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
_logger.LogWarning("刷新令牌中未找到用户ID");
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌");
}
// 获取用户角色
var roles = await _userRoleRepository.GetUserRolesAsync(userId, cancellationToken);
// 获取所有角色的权限
var permissions = new Dictionary<string, bool>();
foreach (var role in roles)
{
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(role, cancellationToken);
foreach (var rolePermission in rolePermissions)
{
if (!permissions.ContainsKey(rolePermission.Permission.Code))
{
permissions[rolePermission.Permission.Code] = true;
}
}
}
// 生成新的访问令牌
var accessToken = _jwtProvider.GenerateAccessToken(claims);
// 生成新的刷新令牌
var refreshToken = _jwtProvider.GenerateRefreshToken(claims);
// 撤销旧的刷新令牌
_jwtProvider.RevokeToken(request.RefreshToken);
// 获取令牌过期时间
var expiresAt = _jwtProvider.GetTokenExpiration(accessToken);
// 创建用户信息
var userInfo = new UserInfo(
userId,
claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value ?? string.Empty,
claims.FirstOrDefault(c => c.Type == "RealName")?.Value,
claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty,
claims.FirstOrDefault(c => c.Type == ClaimTypes.MobilePhone)?.Value,
roles.ToList().AsReadOnly(),
permissions);
_logger.LogInformation("刷新令牌成功");
return OperationResult<AuthenticateUserResponse>.CreateSuccess(
new AuthenticateUserResponse(accessToken, refreshToken, expiresAt, userInfo));
}
catch (Exception ex)
{
_logger.LogError(ex, "刷新令牌时发生异常");
return OperationResult<AuthenticateUserResponse>.CreateFailure("刷新令牌失败,请稍后重试");
}
}
}

22
src/CellularManagement.Application/Features/Auth/Commands/RefreshToken/RefreshTokenResponse.cs

@ -0,0 +1,22 @@
namespace CellularManagement.Application.Features.Auth.Commands.RefreshToken;
/// <summary>
/// 刷新令牌响应
/// </summary>
public class RefreshTokenResponse
{
/// <summary>
/// 访问令牌
/// </summary>
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 刷新令牌
/// </summary>
public string RefreshToken { get; set; } = string.Empty;
/// <summary>
/// 过期时间(分钟)
/// </summary>
public int ExpiresIn { get; set; }
}

48
src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommand.cs

@ -0,0 +1,48 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser;
/// <summary>
/// 用户注册命令
/// </summary>
public sealed record RegisterUserCommand(
/// <summary>
/// 账号
/// </summary>
string UserName,
/// <summary>
/// 用户名
/// </summary>
string RealName,
/// <summary>
/// 邮箱
/// </summary>
string Email,
/// <summary>
/// 密码
/// </summary>
string Password,
/// <summary>
/// 确认密码
/// </summary>
string ConfirmPassword,
/// <summary>
/// 电话号码
/// </summary>
string? PhoneNumber,
/// <summary>
/// 验证码ID
/// </summary>
string CaptchaId,
/// <summary>
/// 验证码
/// </summary>
string CaptchaCode) : IRequest<OperationResult<RegisterUserResponse>>;

114
src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommandHandler.cs

@ -0,0 +1,114 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Services;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Exceptions;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser;
/// <summary>
/// 用户注册命令处理器
/// </summary>
public sealed class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, OperationResult<RegisterUserResponse>>
{
private readonly IUserRegistrationService _userRegistrationService;
private readonly ICaptchaVerificationService _captchaVerificationService;
private readonly ILogger<RegisterUserCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
public RegisterUserCommandHandler(
IUserRegistrationService userRegistrationService,
ICaptchaVerificationService captchaVerificationService,
ILogger<RegisterUserCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_userRegistrationService = userRegistrationService;
_captchaVerificationService = captchaVerificationService;
_logger = logger;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult<RegisterUserResponse>> Handle(
RegisterUserCommand request,
CancellationToken cancellationToken)
{
try
{
// 验证图形验证码
var(IsSuccess, errorMessage) = await _captchaVerificationService.VerifyCaptchaAsync(request.CaptchaId, request.CaptchaCode);
if (!IsSuccess)
{
return OperationResult<RegisterUserResponse>.CreateFailure(errorMessage?? "验证码验证失败");
}
// 创建用户实体
var user = new AppUser
{
UserName = request.UserName,
RealName = request.RealName,
Email = request.Email,
PhoneNumber = request.PhoneNumber
};
// 在事务中执行用户注册和角色分配
await _unitOfWork.ExecuteTransactionAsync(async () =>
{
// 注册用户
var (success, errorMessage) = await _userRegistrationService.RegisterUserAsync(user, request.Password);
if (!success)
{
_logger.LogWarning("注册用户失败: {Error}", errorMessage);
throw new UserRegistrationException(errorMessage!);
}
// 分配角色
(success, errorMessage) = await _userRegistrationService.AssignUserRoleAsync(user);
if (!success)
{
_logger.LogWarning("分配角色失败: {Error}", errorMessage);
throw new RoleAssignmentException(errorMessage!);
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
});
_logger.LogInformation("用户 {UserName} 注册成功", request.UserName);
return OperationResult<RegisterUserResponse>.CreateSuccess(
new RegisterUserResponse(
Id: user.Id,
UserName: user.UserName,
RealName: user.RealName ?? user.UserName));
}
catch (UserNameAlreadyExistsException ex)
{
_logger.LogWarning(ex, "账号已存在");
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message);
}
catch (EmailAlreadyExistsException ex)
{
_logger.LogWarning(ex, "邮箱已存在");
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message);
}
catch (RoleAssignmentException ex)
{
_logger.LogError(ex, "角色分配失败");
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message);
}
catch (UserRegistrationException ex)
{
_logger.LogError(ex, "用户注册失败");
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "用户 {UserName} 注册失败", request.UserName);
return OperationResult<RegisterUserResponse>.CreateFailure("系统错误,请稍后重试");
}
}
}

64
src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommandValidator.cs

@ -0,0 +1,64 @@
using FluentValidation;
using CellularManagement.Application.Common.Extensions;
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser;
/// <summary>
/// 用户注册命令验证器
/// </summary>
public sealed class RegisterUserCommandValidator : AbstractValidator<RegisterUserCommand>
{
/// <summary>
/// 初始化验证器
/// </summary>
public RegisterUserCommandValidator()
{
// 验证账号
RuleFor(x => x.UserName)
.NotEmpty().WithMessage("账号不能为空")
.MinimumLength(3).WithMessage("账号长度不能少于3个字符")
.MaximumLength(50).WithMessage("账号长度不能超过50个字符")
.Matches("^[a-zA-Z0-9_]+$").WithMessage("账号只能包含字母、数字和下划线");
// 验证用户名
RuleFor(x => x.RealName)
.NotEmpty().WithMessage("用户名不能为空")
.MaximumLength(50).WithMessage("用户名长度不能超过50个字符");
// 验证邮箱
RuleFor(x => x.Email)
.NotEmpty().WithMessage("邮箱不能为空")
.MaximumLength(256).WithMessage("邮箱长度不能超过256个字符")
.Must(x => x.IsValidEmail()).WithMessage("邮箱格式不正确");
// 验证密码
RuleFor(x => x.Password)
.NotEmpty().WithMessage("密码不能为空")
.MinimumLength(6).WithMessage("密码长度不能少于6个字符")
.MaximumLength(50).WithMessage("密码长度不能超过50个字符")
.Matches("[A-Z]").WithMessage("密码必须包含至少一个大写字母")
.Matches("[a-z]").WithMessage("密码必须包含至少一个小写字母")
.Matches("[0-9]").WithMessage("密码必须包含至少一个数字")
.Matches("[^a-zA-Z0-9]").WithMessage("密码必须包含至少一个特殊字符");
// 验证确认密码
RuleFor(x => x.ConfirmPassword)
.NotEmpty().WithMessage("确认密码不能为空")
.Equal(x => x.Password).WithMessage("两次输入的密码不一致");
// 验证验证码ID
RuleFor(x => x.CaptchaId)
.NotEmpty().WithMessage("验证码ID不能为空");
// 验证验证码
RuleFor(x => x.CaptchaCode)
.NotEmpty().WithMessage("验证码不能为空")
.Length(4, 6).WithMessage("验证码长度必须在4-6个字符之间")
.Matches("^[A-Z0-9]+$").WithMessage("验证码只能包含大写字母和数字");
// 验证电话号码
RuleFor(x => x.PhoneNumber)
.Matches(@"^1[3-9]\d{9}$").When(x => !string.IsNullOrEmpty(x.PhoneNumber))
.WithMessage("电话号码格式不正确");
}
}

20
src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserResponse.cs

@ -0,0 +1,20 @@
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser;
/// <summary>
/// 用户注册响应
/// </summary>
public sealed record RegisterUserResponse(
/// <summary>
/// 用户ID
/// </summary>
string Id,
/// <summary>
/// 账号
/// </summary>
string UserName,
/// <summary>
/// 用户名
/// </summary>
string RealName);

43
src/CellularManagement.Application/Features/Auth/Commands/SendVerificationCode/SendVerificationCodeCommand.cs

@ -0,0 +1,43 @@
using MediatR;
using CellularManagement.Domain.Common;
namespace CellularManagement.Application.Features.Auth.Commands.SendVerificationCode;
/// <summary>
/// 发送验证码命令
/// 用于请求向指定邮箱发送验证码
/// </summary>
public sealed record SendVerificationCodeCommand : IRequest<OperationResult<SendVerificationCodeResponse>>
{
/// <summary>
/// 目标邮箱地址
/// </summary>
public string Email { get; init; } = string.Empty;
/// <summary>
/// 图形验证码ID
/// </summary>
public string CaptchaId { get; init; } = string.Empty;
/// <summary>
/// 图形验证码
/// </summary>
public string CaptchaCode { get; init; } = string.Empty;
}
/// <summary>
/// 发送验证码响应
/// 包含验证码发送的结果信息
/// </summary>
public sealed record SendVerificationCodeResponse
{
/// <summary>
/// 是否发送成功
/// </summary>
public bool Success { get; init; }
/// <summary>
/// 验证码有效期(分钟)
/// </summary>
public int ExpirationMinutes { get; init; }
}

92
src/CellularManagement.Application/Features/Auth/Commands/SendVerificationCode/SendVerificationCodeCommandHandler.cs

@ -0,0 +1,92 @@
using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using CellularManagement.Domain.Services;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Options;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
namespace CellularManagement.Application.Features.Auth.Commands.SendVerificationCode;
/// <summary>
/// 发送验证码命令处理器
/// 处理发送验证码的业务逻辑
/// </summary>
public sealed class SendVerificationCodeCommandHandler : IRequestHandler<SendVerificationCodeCommand, OperationResult<SendVerificationCodeResponse>>
{
private readonly IEmailVerificationService _emailVerificationService;
private readonly ICaptchaVerificationService _captchaVerificationService;
private readonly ILogger<SendVerificationCodeCommandHandler> _logger;
private readonly EmailVerificationOptions _options;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="emailVerificationService">邮箱验证服务</param>
/// <param name="captchaVerificationService">验证码验证服务</param>
/// <param name="logger">日志记录器</param>
/// <param name="options">邮箱验证码配置选项</param>
public SendVerificationCodeCommandHandler(
IEmailVerificationService emailVerificationService,
ICaptchaVerificationService captchaVerificationService,
ILogger<SendVerificationCodeCommandHandler> logger,
IOptions<EmailVerificationOptions> options)
{
_emailVerificationService = emailVerificationService;
_captchaVerificationService = captchaVerificationService;
_logger = logger;
_options = options.Value;
}
/// <summary>
/// 处理发送验证码请求
/// </summary>
/// <param name="request">发送验证码命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发送结果</returns>
public async Task<OperationResult<SendVerificationCodeResponse>> Handle(
SendVerificationCodeCommand request,
CancellationToken cancellationToken)
{
try
{
// 验证图形验证码
var(IsSuccess, errorMessage) = await _captchaVerificationService.VerifyCaptchaAsync(request.CaptchaId, request.CaptchaCode);
if (!IsSuccess)
{
return OperationResult<SendVerificationCodeResponse>.CreateFailure(errorMessage ?? "验证码验证失败");
}
// 记录开始发送验证码
_logger.LogInformation("开始向邮箱 {Email} 发送验证码", request.Email);
// 调用服务发送验证码
var success = await _emailVerificationService.GenerateAndSendVerificationCodeAsync(request.Email);
if (!success)
{
_logger.LogWarning("向邮箱 {Email} 发送验证码失败", request.Email);
return OperationResult<SendVerificationCodeResponse>.CreateFailure("发送验证码失败");
}
// 记录发送成功
_logger.LogInformation("成功向邮箱 {Email} 发送验证码", request.Email);
// 返回成功响应
return OperationResult<SendVerificationCodeResponse>.CreateSuccess(
new SendVerificationCodeResponse
{
Success = true,
ExpirationMinutes = _options.VerificationCodeExpirationMinutes
});
}
catch (System.Exception ex)
{
// 记录异常
_logger.LogError(ex, "向邮箱 {Email} 发送验证码时发生异常", request.Email);
return OperationResult<SendVerificationCodeResponse>.CreateFailure("发送验证码时发生错误");
}
}
}

38
src/CellularManagement.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommand.cs

@ -0,0 +1,38 @@
using MediatR;
using CellularManagement.Domain.Common;
namespace CellularManagement.Application.Features.Auth.Commands.VerifyCode;
/// <summary>
/// 验证验证码命令
/// 用于验证用户输入的验证码是否正确
/// </summary>
public sealed record VerifyCodeCommand : IRequest<OperationResult<VerifyCodeResponse>>
{
/// <summary>
/// 邮箱地址
/// </summary>
public string Email { get; init; } = string.Empty;
/// <summary>
/// 用户输入的验证码
/// </summary>
public string Code { get; init; } = string.Empty;
}
/// <summary>
/// 验证验证码响应
/// 包含验证结果信息
/// </summary>
public sealed record VerifyCodeResponse
{
/// <summary>
/// 验证是否成功
/// </summary>
public bool Success { get; init; }
/// <summary>
/// 验证失败的原因(如果验证失败)
/// </summary>
public string? FailureReason { get; init; }
}

78
src/CellularManagement.Application/Features/Auth/Commands/VerifyCode/VerifyCodeCommandHandler.cs

@ -0,0 +1,78 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Services;
using CellularManagement.Domain.Common;
using System.Threading;
using System.Threading.Tasks;
namespace CellularManagement.Application.Features.Auth.Commands.VerifyCode;
/// <summary>
/// 验证验证码命令处理器
/// 处理验证码验证的业务逻辑
/// </summary>
public sealed class VerifyCodeCommandHandler : IRequestHandler<VerifyCodeCommand, OperationResult<VerifyCodeResponse>>
{
private readonly IEmailVerificationService _emailVerificationService;
private readonly ILogger<VerifyCodeCommandHandler> _logger;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="emailVerificationService">邮箱验证服务</param>
/// <param name="logger">日志记录器</param>
public VerifyCodeCommandHandler(
IEmailVerificationService emailVerificationService,
ILogger<VerifyCodeCommandHandler> logger)
{
_emailVerificationService = emailVerificationService;
_logger = logger;
}
/// <summary>
/// 处理验证验证码请求
/// </summary>
/// <param name="request">验证验证码命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>验证结果</returns>
public async Task<OperationResult<VerifyCodeResponse>> Handle(
VerifyCodeCommand request,
CancellationToken cancellationToken)
{
try
{
// 记录开始验证
_logger.LogInformation("开始验证邮箱 {Email} 的验证码", request.Email);
// 调用服务验证验证码
var isValid = await _emailVerificationService.VerifyCodeAsync(request.Email, request.Code);
if (!isValid)
{
_logger.LogWarning("邮箱 {Email} 的验证码验证失败", request.Email);
return OperationResult<VerifyCodeResponse>.CreateSuccess(
new VerifyCodeResponse
{
Success = false,
FailureReason = "验证码无效或已过期"
});
}
// 记录验证成功
_logger.LogInformation("邮箱 {Email} 的验证码验证成功", request.Email);
// 返回成功响应
return OperationResult<VerifyCodeResponse>.CreateSuccess(
new VerifyCodeResponse
{
Success = true
});
}
catch (System.Exception ex)
{
// 记录异常
_logger.LogError(ex, "验证邮箱 {Email} 的验证码时发生异常", request.Email);
return OperationResult<VerifyCodeResponse>.CreateFailure("验证验证码时发生错误");
}
}
}

21
src/CellularManagement.Application/Features/Auth/Models/AuthenticateUserResponse.cs

@ -0,0 +1,21 @@
using System;
namespace CellularManagement.Application.Features.Auth.Models;
/// <summary>
/// 账号登录响应
/// </summary>
public class AuthenticateUserResponse : LoginResponse
{
/// <summary>
/// 初始化响应
/// </summary>
public AuthenticateUserResponse(
string accessToken,
string refreshToken,
DateTime expiresAt,
UserInfo user)
: base(accessToken, refreshToken, expiresAt, user)
{
}
}

21
src/CellularManagement.Application/Features/Auth/Models/EmailLoginResponse.cs

@ -0,0 +1,21 @@
using System;
namespace CellularManagement.Application.Features.Auth.Models;
/// <summary>
/// 邮箱登录响应
/// </summary>
public class EmailLoginResponse : LoginResponse
{
/// <summary>
/// 初始化响应
/// </summary>
public EmailLoginResponse(
string accessToken,
string refreshToken,
DateTime expiresAt,
UserInfo user)
: base(accessToken, refreshToken, expiresAt, user)
{
}
}

44
src/CellularManagement.Application/Features/Auth/Models/LoginResponse.cs

@ -0,0 +1,44 @@
using System;
namespace CellularManagement.Application.Features.Auth.Models;
/// <summary>
/// 登录响应基类
/// </summary>
public abstract class LoginResponse
{
/// <summary>
/// 访问令牌
/// </summary>
public string AccessToken { get; }
/// <summary>
/// 刷新令牌
/// </summary>
public string RefreshToken { get; }
/// <summary>
/// 过期时间
/// </summary>
public DateTime ExpiresAt { get; }
/// <summary>
/// 用户信息
/// </summary>
public UserInfo User { get; }
/// <summary>
/// 初始化响应
/// </summary>
protected LoginResponse(
string accessToken,
string refreshToken,
DateTime expiresAt,
UserInfo user)
{
AccessToken = accessToken;
RefreshToken = refreshToken;
ExpiresAt = expiresAt;
User = user;
}
}

65
src/CellularManagement.Application/Features/Auth/Models/UserInfo.cs

@ -0,0 +1,65 @@
using System.Collections.Generic;
namespace CellularManagement.Application.Features.Auth.Models;
/// <summary>
/// 用户信息
/// </summary>
public class UserInfo
{
/// <summary>
/// 用户ID
/// </summary>
public string Id { get; }
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; }
/// <summary>
/// 真实姓名
/// </summary>
public string? RealName { get; }
/// <summary>
/// 邮箱
/// </summary>
public string Email { get; }
/// <summary>
/// 手机号
/// </summary>
public string? PhoneNumber { get; }
/// <summary>
/// 角色列表
/// </summary>
public IReadOnlyList<string> Roles { get; }
/// <summary>
/// 权限字典
/// </summary>
public IReadOnlyDictionary<string, bool> Permissions { get; }
/// <summary>
/// 初始化用户信息
/// </summary>
public UserInfo(
string id,
string userName,
string? realName,
string email,
string? phoneNumber,
IReadOnlyList<string> roles,
IReadOnlyDictionary<string, bool> permissions)
{
Id = id;
UserName = userName;
RealName = realName;
Email = email;
PhoneNumber = phoneNumber;
Roles = roles;
Permissions = permissions;
}
}

61
src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs

@ -0,0 +1,61 @@
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice;
/// <summary>
/// 创建设备命令
/// </summary>
public class CreateDeviceCommand : IRequest<OperationResult<Device>>
{
/// <summary>
/// 设备名称
/// </summary>
[Required(ErrorMessage = "设备名称不能为空")]
[MaxLength(100, ErrorMessage = "设备名称不能超过100个字符")]
public string DeviceName { get; set; }
/// <summary>
/// 协议版本
/// </summary>
[MaxLength(50, ErrorMessage = "协议版本不能超过50个字符")]
public string ProtocolVersion { get; set; }
/// <summary>
/// 支持的频段
/// </summary>
[MaxLength(100, ErrorMessage = "支持的频段不能超过100个字符")]
public string SupportBands { get; set; }
/// <summary>
/// 硬件版本
/// </summary>
[MaxLength(50, ErrorMessage = "硬件版本不能超过50个字符")]
public string HardwareVersion { get; set; }
/// <summary>
/// 序列号
/// </summary>
[Required(ErrorMessage = "序列号不能为空")]
[MaxLength(100, ErrorMessage = "序列号不能超过100个字符")]
public string SerialNumber { get; set; }
/// <summary>
/// 备注
/// </summary>
[MaxLength(500, ErrorMessage = "备注不能超过500个字符")]
public string Comment { get; set; }
/// <summary>
/// IP地址
/// </summary>
[MaxLength(50, ErrorMessage = "IP地址不能超过50个字符")]
public string IPAddress { get; set; }
/// <summary>
/// 是否禁用
/// </summary>
public bool IsDisabled { get; set; }
}

72
src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs

@ -0,0 +1,72 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice;
/// <summary>
/// 创建设备命令处理器
/// </summary>
public class CreateDeviceCommandHandler : IRequestHandler<CreateDeviceCommand, OperationResult<Device>>
{
private readonly IDeviceRepository _deviceRepository;
private readonly ILogger<CreateDeviceCommandHandler> _logger;
/// <summary>
/// 初始化命令处理器
/// </summary>
public CreateDeviceCommandHandler(
IDeviceRepository deviceRepository,
ILogger<CreateDeviceCommandHandler> logger)
{
_deviceRepository = deviceRepository;
_logger = logger;
}
/// <summary>
/// 处理创建设备命令
/// </summary>
public async Task<OperationResult<Device>> Handle(CreateDeviceCommand request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("Creating new device with serial number: {SerialNumber}", request.SerialNumber);
// 检查序列号是否已存在
if (await _deviceRepository.ExistsBySerialNumberAsync(request.SerialNumber, cancellationToken: cancellationToken))
{
_logger.LogWarning("Device with serial number {SerialNumber} already exists", request.SerialNumber);
return OperationResult<Device>.CreateFailure($"设备序列号 {request.SerialNumber} 已存在");
}
// 创建设备实体
var device = new Device
{
DeviceID = Guid.NewGuid().ToString(),
DeviceName = request.DeviceName,
ProtocolVersion = request.ProtocolVersion,
SupportBands = request.SupportBands,
HardwareVersion = request.HardwareVersion,
SerialNumber = request.SerialNumber,
Comment = request.Comment,
IPAddress = request.IPAddress,
IsDisabled = false,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
// 保存设备
await _deviceRepository.CreateAsync(device, cancellationToken);
_logger.LogInformation("Successfully created device with ID: {DeviceId}", device.DeviceID);
return OperationResult<Device>.CreateSuccess("设备创建成功", device);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating device with serial number: {SerialNumber}", request.SerialNumber);
return OperationResult<Device>.CreateFailure($"创建设备时发生错误: {ex.Message}");
}
}
}

25
src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommand.cs

@ -0,0 +1,25 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice;
/// <summary>
/// 删除设备命令
/// </summary>
public class DeleteDeviceCommand : IRequest<OperationResult<bool>>
{
/// <summary>
/// 设备ID
/// </summary>
[Required(ErrorMessage = "设备ID不能为空")]
public string DeviceId { get; set; }
/// <summary>
/// 初始化删除设备命令
/// </summary>
public DeleteDeviceCommand(string deviceId)
{
DeviceId = deviceId;
}
}

55
src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs

@ -0,0 +1,55 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories;
namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice;
/// <summary>
/// 删除设备命令处理器
/// </summary>
public class DeleteDeviceCommandHandler : IRequestHandler<DeleteDeviceCommand, OperationResult<bool>>
{
private readonly IDeviceRepository _deviceRepository;
private readonly ILogger<DeleteDeviceCommandHandler> _logger;
/// <summary>
/// 初始化删除设备命令处理器
/// </summary>
public DeleteDeviceCommandHandler(
IDeviceRepository deviceRepository,
ILogger<DeleteDeviceCommandHandler> logger)
{
_deviceRepository = deviceRepository;
_logger = logger;
}
/// <summary>
/// 处理删除设备命令
/// </summary>
public async Task<OperationResult<bool>> Handle(DeleteDeviceCommand request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始处理删除设备命令,设备ID: {DeviceId}", request.DeviceId);
var result = await _deviceRepository.DeleteByIdAsync(request.DeviceId, cancellationToken);
if (result)
{
_logger.LogInformation("设备删除成功,设备ID: {DeviceId}", request.DeviceId);
return OperationResult<bool>.CreateSuccess(true);
}
else
{
_logger.LogWarning("设备删除失败,设备ID: {DeviceId}", request.DeviceId);
return OperationResult<bool>.CreateFailure("设备删除失败");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "删除设备时发生错误,设备ID: {DeviceId}", request.DeviceId);
return OperationResult<bool>.CreateFailure($"删除设备时发生错误: {ex.Message}");
}
}
}

67
src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs

@ -0,0 +1,67 @@
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice;
/// <summary>
/// 更新设备命令
/// </summary>
public class UpdateDeviceCommand : IRequest<OperationResult<Device>>
{
/// <summary>
/// 设备ID
/// </summary>
[Required(ErrorMessage = "设备ID不能为空")]
public string DeviceID { get; set; }
/// <summary>
/// 设备名称
/// </summary>
[Required(ErrorMessage = "设备名称不能为空")]
[MaxLength(100, ErrorMessage = "设备名称不能超过100个字符")]
public string DeviceName { get; set; }
/// <summary>
/// 协议版本
/// </summary>
[MaxLength(50, ErrorMessage = "协议版本不能超过50个字符")]
public string ProtocolVersion { get; set; }
/// <summary>
/// 支持的频段
/// </summary>
[MaxLength(100, ErrorMessage = "支持的频段不能超过100个字符")]
public string SupportBands { get; set; }
/// <summary>
/// 硬件版本
/// </summary>
[MaxLength(50, ErrorMessage = "硬件版本不能超过50个字符")]
public string HardwareVersion { get; set; }
/// <summary>
/// 序列号
/// </summary>
[Required(ErrorMessage = "序列号不能为空")]
[MaxLength(100, ErrorMessage = "序列号不能超过100个字符")]
public string SerialNumber { get; set; }
/// <summary>
/// 备注
/// </summary>
[MaxLength(500, ErrorMessage = "备注不能超过500个字符")]
public string Comment { get; set; }
/// <summary>
/// IP地址
/// </summary>
[MaxLength(50, ErrorMessage = "IP地址不能超过50个字符")]
public string IPAddress { get; set; }
/// <summary>
/// 是否禁用
/// </summary>
public bool IsDisabled { get; set; }
}

83
src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs

@ -0,0 +1,83 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice;
/// <summary>
/// 更新设备命令处理器
/// </summary>
public class UpdateDeviceCommandHandler : IRequestHandler<UpdateDeviceCommand, OperationResult<Device>>
{
private readonly IDeviceRepository _deviceRepository;
private readonly ILogger<UpdateDeviceCommandHandler> _logger;
/// <summary>
/// 初始化命令处理器
/// </summary>
public UpdateDeviceCommandHandler(
IDeviceRepository deviceRepository,
ILogger<UpdateDeviceCommandHandler> logger)
{
_deviceRepository = deviceRepository;
_logger = logger;
}
/// <summary>
/// 处理更新设备命令
/// </summary>
public async Task<OperationResult<Device>> Handle(UpdateDeviceCommand request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("Updating device with ID: {DeviceId}", request.DeviceID);
// 检查设备是否存在
var existingDevice = await _deviceRepository.GetByIdAsync(request.DeviceID, cancellationToken);
if (existingDevice == null)
{
_logger.LogWarning("Device not found with ID: {DeviceId}", request.DeviceID);
return OperationResult<Device>.CreateFailure($"未找到ID为 {request.DeviceID} 的设备");
}
// 如果序列号发生变化,检查新序列号是否已存在
if (existingDevice.SerialNumber != request.SerialNumber)
{
if (await _deviceRepository.ExistsBySerialNumberAsync(request.SerialNumber, request.DeviceID, cancellationToken))
{
_logger.LogWarning("Device with serial number {SerialNumber} already exists", request.SerialNumber);
return OperationResult<Device>.CreateFailure($"设备序列号 {request.SerialNumber} 已存在");
}
}
// 更新设备属性
existingDevice.DeviceName = request.DeviceName;
existingDevice.ProtocolVersion = request.ProtocolVersion;
existingDevice.SupportBands = request.SupportBands;
existingDevice.HardwareVersion = request.HardwareVersion;
existingDevice.SerialNumber = request.SerialNumber;
existingDevice.Comment = request.Comment;
existingDevice.IPAddress = request.IPAddress;
existingDevice.IsDisabled = request.IsDisabled;
existingDevice.UpdatedAt = DateTime.UtcNow;
// 保存更新
var result = await _deviceRepository.UpdateAsync(existingDevice, cancellationToken);
if (!result)
{
_logger.LogWarning("Failed to update device with ID: {DeviceId}", request.DeviceID);
return OperationResult<Device>.CreateFailure("更新设备失败");
}
_logger.LogInformation("Successfully updated device with ID: {DeviceId}", request.DeviceID);
return OperationResult<Device>.CreateSuccess("设备更新成功", existingDevice);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating device with ID: {DeviceId}", request.DeviceID);
return OperationResult<Device>.CreateFailure($"更新设备时发生错误: {ex.Message}");
}
}
}

26
src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQuery.cs

@ -0,0 +1,26 @@
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.Devices.Queries.GetDeviceById;
/// <summary>
/// 根据ID获取设备查询
/// </summary>
public class GetDeviceByIdQuery : IRequest<OperationResult<Device>>
{
/// <summary>
/// 设备ID
/// </summary>
[Required(ErrorMessage = "设备ID不能为空")]
public string DeviceId { get; set; }
/// <summary>
/// 初始化查询
/// </summary>
public GetDeviceByIdQuery(string deviceId)
{
DeviceId = deviceId;
}
}

54
src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs

@ -0,0 +1,54 @@
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using MediatR;
using Microsoft.Extensions.Logging;
namespace CellularManagement.Application.Features.Devices.Queries.GetDeviceById;
/// <summary>
/// 根据ID获取设备查询处理器
/// </summary>
public class GetDeviceByIdQueryHandler : IRequestHandler<GetDeviceByIdQuery, OperationResult<Device>>
{
private readonly IDeviceRepository _deviceRepository;
private readonly ILogger<GetDeviceByIdQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetDeviceByIdQueryHandler(
IDeviceRepository deviceRepository,
ILogger<GetDeviceByIdQueryHandler> logger)
{
_deviceRepository = deviceRepository;
_logger = logger;
}
/// <summary>
/// 处理查询请求
/// </summary>
public async Task<OperationResult<Device>> Handle(GetDeviceByIdQuery request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始查询设备,设备ID: {DeviceId}", request.DeviceId);
var device = await _deviceRepository.GetByIdAsync(request.DeviceId, cancellationToken);
if (device == null)
{
_logger.LogWarning("未找到设备,设备ID: {DeviceId}", request.DeviceId);
return OperationResult<Device>.CreateFailure($"未找到ID为 {request.DeviceId} 的设备");
}
_logger.LogInformation("成功查询到设备,设备ID: {DeviceId}", request.DeviceId);
return OperationResult<Device>.CreateSuccess(device);
}
catch (Exception ex)
{
_logger.LogError(ex, "查询设备时发生错误,设备ID: {DeviceId}", request.DeviceId);
return OperationResult<Device>.CreateFailure($"查询设备时发生错误: {ex.Message}");
}
}
}

64
src/CellularManagement.Application/Features/Devices/Queries/GetDevices/DeviceDto.cs

@ -0,0 +1,64 @@
using System;
namespace CellularManagement.Application.Features.Devices.Queries.GetDevices;
/// <summary>
/// 设备数据传输对象
/// </summary>
public class DeviceDto
{
/// <summary>
/// 设备ID
/// </summary>
public string DeviceId { get; set; }
/// <summary>
/// 设备名称
/// </summary>
public string DeviceName { get; set; }
/// <summary>
/// 协议版本
/// </summary>
public string ProtocolVersion { get; set; }
/// <summary>
/// 支持的频段
/// </summary>
public string SupportBands { get; set; }
/// <summary>
/// 硬件版本
/// </summary>
public string HardwareVersion { get; set; }
/// <summary>
/// 序列号
/// </summary>
public string SerialNumber { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Comment { get; set; }
/// <summary>
/// IP地址
/// </summary>
public string IPAddress { get; set; }
/// <summary>
/// 是否禁用
/// </summary>
public bool IsDisabled { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
}

34
src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQuery.cs

@ -0,0 +1,34 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.Devices.Queries.GetDevices;
/// <summary>
/// 获取设备列表查询
/// </summary>
public class GetDevicesQuery : IRequest<OperationResult<GetDevicesResponse>>
{
/// <summary>
/// 页码,从1开始
/// </summary>
[Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")]
public int PageNumber { get; set; } = 1;
/// <summary>
/// 每页记录数
/// </summary>
[Range(1, 100, ErrorMessage = "每页记录数必须在1-100之间")]
public int PageSize { get; set; } = 10;
/// <summary>
/// 搜索关键词
/// </summary>
[MaxLength(100, ErrorMessage = "搜索关键词不能超过100个字符")]
public string? SearchTerm { get; set; }
/// <summary>
/// 是否禁用
/// </summary>
public bool? IsDisabled { get; set; }
}

92
src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs

@ -0,0 +1,92 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace CellularManagement.Application.Features.Devices.Queries.GetDevices;
/// <summary>
/// 获取设备列表查询处理器
/// </summary>
public class GetDevicesQueryHandler : IRequestHandler<GetDevicesQuery, OperationResult<GetDevicesResponse>>
{
private readonly IDeviceRepository _deviceRepository;
private readonly ILogger<GetDevicesQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetDevicesQueryHandler(
IDeviceRepository deviceRepository,
ILogger<GetDevicesQueryHandler> logger)
{
_deviceRepository = deviceRepository;
_logger = logger;
}
/// <summary>
/// 处理获取设备列表查询
/// </summary>
public async Task<OperationResult<GetDevicesResponse>> Handle(GetDevicesQuery request, CancellationToken cancellationToken)
{
try
{
// 验证请求参数
var validationContext = new ValidationContext(request);
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateObject(request, validationContext, validationResults, true))
{
var errorMessages = validationResults.Select(r => r.ErrorMessage).ToList();
_logger.LogWarning("Invalid request parameters: {Errors}", string.Join(", ", errorMessages));
return OperationResult<GetDevicesResponse>.CreateFailure(errorMessages);
}
_logger.LogInformation("Getting devices with page: {PageNumber}, size: {PageSize}, search: {SearchTerm}, isDisabled: {IsDisabled}",
request.PageNumber, request.PageSize, request.SearchTerm, request.IsDisabled);
// 获取分页数据
var (totalCount, items) = await _deviceRepository.GetPagedAsync(
request.PageNumber,
request.PageSize,
request.SearchTerm,
request.IsDisabled,
cancellationToken);
// 构建响应
var response = new GetDevicesResponse
{
TotalCount = totalCount,
PageNumber = request.PageNumber,
PageSize = request.PageSize,
TotalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize),
HasPreviousPage = request.PageNumber > 1,
HasNextPage = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize),
Items = items.Select(d => new DeviceDto
{
DeviceId = d.DeviceID,
DeviceName = d.DeviceName,
ProtocolVersion = d.ProtocolVersion,
SupportBands = d.SupportBands,
HardwareVersion = d.HardwareVersion,
SerialNumber = d.SerialNumber,
Comment = d.Comment,
IPAddress = d.IPAddress,
IsDisabled = d.IsDisabled,
CreatedAt = d.CreatedAt,
UpdatedAt = d.UpdatedAt
}).ToList()
};
_logger.LogInformation("Successfully retrieved {Count} devices", items.Count());
return OperationResult<GetDevicesResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting devices");
return OperationResult<GetDevicesResponse>.CreateFailure($"获取设备列表时发生错误: {ex.Message}");
}
}
}

44
src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesResponse.cs

@ -0,0 +1,44 @@
using System.Collections.Generic;
namespace CellularManagement.Application.Features.Devices.Queries.GetDevices;
/// <summary>
/// 设备列表响应
/// </summary>
public class GetDevicesResponse
{
/// <summary>
/// 总记录数
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 当前页码
/// </summary>
public int PageNumber { get; set; }
/// <summary>
/// 每页记录数
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 总页数
/// </summary>
public int TotalPages { get; set; }
/// <summary>
/// 是否有上一页
/// </summary>
public bool HasPreviousPage { get; set; }
/// <summary>
/// 是否有下一页
/// </summary>
public bool HasNextPage { get; set; }
/// <summary>
/// 设备列表
/// </summary>
public List<DeviceDto> Items { get; set; } = new();
}

19
src/CellularManagement.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommand.cs

@ -0,0 +1,19 @@
using MediatR;
using CellularManagement.Application.Common;
using CellularManagement.Domain.Common;
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission;
/// <summary>
/// 创建权限命令
/// </summary>
public sealed record CreatePermissionCommand(
/// <summary>
/// 权限名称
/// </summary>
string Name,
/// <summary>
/// 权限描述
/// </summary>
string? Description) : IRequest<OperationResult<CreatePermissionResponse>>;

64
src/CellularManagement.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs

@ -0,0 +1,64 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Application.Common;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Common;
using System;
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission;
/// <summary>
/// 创建权限命令处理器
/// </summary>
public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermissionCommand, OperationResult<CreatePermissionResponse>>
{
private readonly IPermissionRepository _permissionRepository;
private readonly ILogger<CreatePermissionCommandHandler> _logger;
/// <summary>
/// 初始化处理器
/// </summary>
public CreatePermissionCommandHandler(
IPermissionRepository permissionRepository,
ILogger<CreatePermissionCommandHandler> logger)
{
_permissionRepository = permissionRepository;
_logger = logger;
}
/// <summary>
/// 处理创建权限请求
/// </summary>
public async Task<OperationResult<CreatePermissionResponse>> Handle(
CreatePermissionCommand request,
CancellationToken cancellationToken)
{
try
{
// 检查权限是否已存在
var existingPermission = await _permissionRepository.GetByNameAsync(request.Name, cancellationToken);
if (existingPermission != null)
{
_logger.LogWarning("权限 {PermissionName} 已存在", request.Name);
return OperationResult<CreatePermissionResponse>.CreateFailure("权限已存在");
}
// 创建权限
var permission = Permission.Create(request.Name, request.Description);
var createdPermission = await _permissionRepository.AddAsync(permission, cancellationToken);
_logger.LogInformation("权限 {PermissionName} 创建成功", request.Name);
return OperationResult<CreatePermissionResponse>.CreateSuccess(
new CreatePermissionResponse(createdPermission.Id));
}
catch (Exception ex)
{
_logger.LogError(ex, "创建权限 {PermissionName} 失败", request.Name);
return OperationResult<CreatePermissionResponse>.CreateFailure("创建权限失败,请稍后重试");
}
}
}

10
src/CellularManagement.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionResponse.cs

@ -0,0 +1,10 @@
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission;
/// <summary>
/// 创建权限响应
/// </summary>
public sealed record CreatePermissionResponse(
/// <summary>
/// 权限ID
/// </summary>
string PermissionId);

21
src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommand.cs

@ -0,0 +1,21 @@
using System.Collections.Generic;
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions;
/// <summary>
/// 添加角色权限命令
/// </summary>
public class AddRolePermissionsCommand : IRequest<OperationResult<AddRolePermissionsResponse>>
{
/// <summary>
/// 角色ID
/// </summary>
public string RoleId { get; set; } = string.Empty;
/// <summary>
/// 权限ID列表
/// </summary>
public IEnumerable<string> PermissionIds { get; set; } = new List<string>();
}

76
src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommandHandler.cs

@ -0,0 +1,76 @@
using MediatR;
using CellularManagement.Domain.Repositories;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System;
using System.Linq;
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions;
/// <summary>
/// 添加角色权限命令处理器
/// </summary>
public class AddRolePermissionsCommandHandler : IRequestHandler<AddRolePermissionsCommand, OperationResult<AddRolePermissionsResponse>>
{
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ILogger<AddRolePermissionsCommandHandler> _logger;
public AddRolePermissionsCommandHandler(
IRolePermissionRepository rolePermissionRepository,
ILogger<AddRolePermissionsCommandHandler> logger)
{
_rolePermissionRepository = rolePermissionRepository;
_logger = logger;
}
public async Task<OperationResult<AddRolePermissionsResponse>> Handle(
AddRolePermissionsCommand request,
CancellationToken cancellationToken)
{
try
{
// 获取角色当前已有的权限
var existingPermissions = await _rolePermissionRepository.GetPermissionsByRoleIdAsync(request.RoleId, cancellationToken);
var existingPermissionIds = existingPermissions.Select(p => p.Id).ToList();
// 过滤出需要新增的权限ID
var newPermissionIds = request.PermissionIds.Except(existingPermissionIds).ToList();
if (!newPermissionIds.Any())
{
return OperationResult<AddRolePermissionsResponse>.CreateFailure("所有权限已存在,无需添加");
}
// 添加新的角色权限
var addedRolePermissions = await _rolePermissionRepository.AddRolePermissionsAsync(
request.RoleId,
newPermissionIds,
cancellationToken);
var response = new AddRolePermissionsResponse
{
AddedPermissionIds = newPermissionIds,
FailedPermissionIds = request.PermissionIds.Except(newPermissionIds)
};
_logger.LogInformation(
"成功为角色 {RoleId} 添加 {Count} 个权限",
request.RoleId,
newPermissionIds.Count);
return OperationResult<AddRolePermissionsResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"为角色 {RoleId} 添加权限时发生错误",
request.RoleId);
return OperationResult<AddRolePermissionsResponse>.CreateFailure(
"添加角色权限失败,请稍后重试");
}
}
}

19
src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommandValidator.cs

@ -0,0 +1,19 @@
using FluentValidation;
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions;
/// <summary>
/// 添加角色权限命令验证器
/// </summary>
public class AddRolePermissionsCommandValidator : AbstractValidator<AddRolePermissionsCommand>
{
public AddRolePermissionsCommandValidator()
{
RuleFor(x => x.RoleId)
.NotEmpty().WithMessage("角色ID不能为空");
RuleFor(x => x.PermissionIds)
.NotEmpty().WithMessage("权限ID列表不能为空")
.Must(x => x.Any()).WithMessage("至少需要指定一个权限ID");
}
}

17
src/CellularManagement.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsResponse.cs

@ -0,0 +1,17 @@
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions;
/// <summary>
/// 添加角色权限响应
/// </summary>
public class AddRolePermissionsResponse
{
/// <summary>
/// 添加成功的权限ID列表
/// </summary>
public IEnumerable<string> AddedPermissionIds { get; set; } = new List<string>();
/// <summary>
/// 添加失败的权限ID列表
/// </summary>
public IEnumerable<string> FailedPermissionIds { get; set; } = new List<string>();
}

21
src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommand.cs

@ -0,0 +1,21 @@
using System.Collections.Generic;
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions;
/// <summary>
/// 删除角色权限命令
/// </summary>
public class DeleteRolePermissionsCommand : IRequest<OperationResult<DeleteRolePermissionsResponse>>
{
/// <summary>
/// 角色ID
/// </summary>
public string RoleId { get; set; } = string.Empty;
/// <summary>
/// 要删除的权限ID列表
/// </summary>
public IEnumerable<string> PermissionIds { get; set; } = new List<string>();
}

76
src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommandHandler.cs

@ -0,0 +1,76 @@
using MediatR;
using CellularManagement.Domain.Repositories;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using System;
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions;
/// <summary>
/// 删除角色权限命令处理器
/// </summary>
public class DeleteRolePermissionsCommandHandler : IRequestHandler<DeleteRolePermissionsCommand, OperationResult<DeleteRolePermissionsResponse>>
{
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ILogger<DeleteRolePermissionsCommandHandler> _logger;
public DeleteRolePermissionsCommandHandler(
IRolePermissionRepository rolePermissionRepository,
ILogger<DeleteRolePermissionsCommandHandler> logger)
{
_rolePermissionRepository = rolePermissionRepository;
_logger = logger;
}
public async Task<OperationResult<DeleteRolePermissionsResponse>> Handle(
DeleteRolePermissionsCommand request,
CancellationToken cancellationToken)
{
try
{
// 获取角色当前已有的权限
var existingPermissions = await _rolePermissionRepository.GetPermissionsByRoleIdAsync(request.RoleId, cancellationToken);
var existingPermissionIds = existingPermissions.Select(p => p.Id).ToList();
// 过滤出实际存在的权限ID
var validPermissionIds = request.PermissionIds.Intersect(existingPermissionIds).ToList();
if (!validPermissionIds.Any())
{
return OperationResult<DeleteRolePermissionsResponse>.CreateFailure("指定的权限不存在,无需删除");
}
// 删除角色权限
var deletedCount = await _rolePermissionRepository.DeleteRolePermissionsAsync(
request.RoleId,
validPermissionIds,
cancellationToken);
var response = new DeleteRolePermissionsResponse
{
DeletedCount = deletedCount,
FailedPermissionIds = request.PermissionIds.Except(validPermissionIds)
};
_logger.LogInformation(
"成功从角色 {RoleId} 删除 {Count} 个权限",
request.RoleId,
deletedCount);
return OperationResult<DeleteRolePermissionsResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"从角色 {RoleId} 删除权限时发生错误",
request.RoleId);
return OperationResult<DeleteRolePermissionsResponse>.CreateFailure(
"删除角色权限失败,请稍后重试");
}
}
}

19
src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommandValidator.cs

@ -0,0 +1,19 @@
using FluentValidation;
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions;
/// <summary>
/// 删除角色权限命令验证器
/// </summary>
public class DeleteRolePermissionsCommandValidator : AbstractValidator<DeleteRolePermissionsCommand>
{
public DeleteRolePermissionsCommandValidator()
{
RuleFor(x => x.RoleId)
.NotEmpty().WithMessage("角色ID不能为空");
RuleFor(x => x.PermissionIds)
.NotEmpty().WithMessage("权限ID列表不能为空")
.Must(x => x.Any()).WithMessage("至少需要指定一个要删除的权限ID");
}
}

17
src/CellularManagement.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsResponse.cs

@ -0,0 +1,17 @@
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions;
/// <summary>
/// 删除角色权限响应
/// </summary>
public class DeleteRolePermissionsResponse
{
/// <summary>
/// 成功删除的权限数量
/// </summary>
public int DeletedCount { get; set; }
/// <summary>
/// 删除失败的权限ID列表
/// </summary>
public IEnumerable<string> FailedPermissionIds { get; set; } = new List<string>();
}

20
src/CellularManagement.Application/Features/RolePermissions/Queries/GetRolePermissions/GetRolePermissionsQuery.cs

@ -0,0 +1,20 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.RolePermissions.Queries.GetRolePermissions;
/// <summary>
/// 获取角色权限查询命令
/// </summary>
public class GetRolePermissionsQuery : IRequest<OperationResult<GetRolePermissionsResponse>>
{
/// <summary>
/// 角色ID
/// </summary>
public string RoleId { get; set; } = string.Empty;
/// <summary>
/// 是否包含权限详情
/// </summary>
public bool IncludeDetails { get; set; } = true;
}

70
src/CellularManagement.Application/Features/RolePermissions/Queries/GetRolePermissions/GetRolePermissionsQueryHandler.cs

@ -0,0 +1,70 @@
using MediatR;
using CellularManagement.Domain.Repositories;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using System;
namespace CellularManagement.Application.Features.RolePermissions.Queries.GetRolePermissions;
/// <summary>
/// 获取角色权限查询处理器
/// </summary>
public class GetRolePermissionsQueryHandler : IRequestHandler<GetRolePermissionsQuery, OperationResult<GetRolePermissionsResponse>>
{
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ILogger<GetRolePermissionsQueryHandler> _logger;
public GetRolePermissionsQueryHandler(
IRolePermissionRepository rolePermissionRepository,
ILogger<GetRolePermissionsQueryHandler> logger)
{
_rolePermissionRepository = rolePermissionRepository;
_logger = logger;
}
public async Task<OperationResult<GetRolePermissionsResponse>> Handle(
GetRolePermissionsQuery request,
CancellationToken cancellationToken)
{
try
{
// 获取角色权限
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(
request.RoleId,
cancellationToken);
if (!rolePermissions.Any())
{
return OperationResult<GetRolePermissionsResponse>.CreateFailure("未找到角色权限信息");
}
// 构建响应
var response = new GetRolePermissionsResponse
{
RoleId = request.RoleId,
RoleName = rolePermissions.First().Role.Name,
Permissions = rolePermissions.Select(rp => PermissionDto.FromEntity(rp.Permission))
};
_logger.LogInformation(
"成功获取角色 {RoleId} 的权限信息,共 {Count} 个权限",
request.RoleId,
response.Permissions.Count());
return OperationResult<GetRolePermissionsResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"获取角色 {RoleId} 的权限信息时发生错误",
request.RoleId);
return OperationResult<GetRolePermissionsResponse>.CreateFailure(
"获取角色权限信息失败,请稍后重试");
}
}
}

65
src/CellularManagement.Application/Features/RolePermissions/Queries/GetRolePermissions/GetRolePermissionsResponse.cs

@ -0,0 +1,65 @@
using System.Collections.Generic;
using CellularManagement.Domain.Entities;
namespace CellularManagement.Application.Features.RolePermissions.Queries.GetRolePermissions;
/// <summary>
/// 获取角色权限响应
/// </summary>
public class GetRolePermissionsResponse
{
/// <summary>
/// 角色ID
/// </summary>
public string RoleId { get; set; } = string.Empty;
/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; } = string.Empty;
/// <summary>
/// 权限列表
/// </summary>
public IEnumerable<PermissionDto> Permissions { get; set; } = new List<PermissionDto>();
}
/// <summary>
/// 权限数据传输对象
/// </summary>
public class PermissionDto
{
/// <summary>
/// 权限ID
/// </summary>
public string Id { get; set; } = string.Empty;
/// <summary>
/// 权限名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 权限编码
/// </summary>
public string Code { get; set; } = string.Empty;
/// <summary>
/// 权限描述
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 从权限实体创建DTO
/// </summary>
public static PermissionDto FromEntity(Permission permission)
{
return new PermissionDto
{
Id = permission.Id,
Name = permission.Name,
Code = permission.Code,
Description = permission.Description
};
}
}

26
src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleCommand.cs

@ -0,0 +1,26 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Roles.Commands.CreateRole;
/// <summary>
/// 创建角色命令
/// </summary>
/// <example>
/// {
/// "name": "Manager",
/// "description": "部门经理角色,拥有管理权限"
/// }
/// </example>
public sealed record CreateRoleCommand(
/// <summary>
/// 角色名称
/// </summary>
/// <example>Manager</example>
string Name,
/// <summary>
/// 角色描述
/// </summary>
/// <example>部门经理角色,拥有管理权限</example>
string? Description) : IRequest<OperationResult<CreateRoleResponse>>;

10
src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleResponse.cs

@ -0,0 +1,10 @@
namespace CellularManagement.Application.Features.Roles.Commands.CreateRole;
/// <summary>
/// 创建角色响应
/// </summary>
public sealed record CreateRoleResponse(
/// <summary>
/// 角色ID
/// </summary>
string RoleId);

13
src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleCommand.cs

@ -0,0 +1,13 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Roles.Commands.DeleteRole;
/// <summary>
/// 删除角色命令
/// </summary>
public sealed record DeleteRoleCommand(
/// <summary>
/// 角色ID
/// </summary>
string RoleId) : IRequest<OperationResult<DeleteRoleResponse>>;

10
src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleResponse.cs

@ -0,0 +1,10 @@
namespace CellularManagement.Application.Features.Roles.Commands.DeleteRole;
/// <summary>
/// 删除角色响应
/// </summary>
public sealed record DeleteRoleResponse(
/// <summary>
/// 是否删除成功
/// </summary>
bool Success);

117
src/CellularManagement.Application/Features/Roles/Commands/RoleCommandHandler.cs

@ -0,0 +1,117 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Application.Features.Roles.Commands.CreateRole;
using CellularManagement.Application.Features.Roles.Commands.DeleteRole;
using CellularManagement.Domain.Common;
using System.Linq;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CellularManagement.Application.Features.Roles.Commands;
/// <summary>
/// 角色命令处理器
/// </summary>
public sealed class RoleCommandHandler :
IRequestHandler<CreateRoleCommand, OperationResult<CreateRoleResponse>>,
IRequestHandler<DeleteRoleCommand, OperationResult<DeleteRoleResponse>>
{
private readonly RoleManager<AppRole> _roleManager;
private readonly ILogger<RoleCommandHandler> _logger;
/// <summary>
/// 初始化处理器
/// </summary>
public RoleCommandHandler(
RoleManager<AppRole> roleManager,
ILogger<RoleCommandHandler> logger)
{
_roleManager = roleManager;
_logger = logger;
}
/// <summary>
/// 处理创建角色请求
/// </summary>
public async Task<OperationResult<CreateRoleResponse>> Handle(
CreateRoleCommand request,
CancellationToken cancellationToken)
{
try
{
// 检查角色是否已存在
var existingRole = await _roleManager.FindByNameAsync(request.Name);
if (existingRole != null)
{
_logger.LogWarning("角色 {RoleName} 已存在", request.Name);
return OperationResult<CreateRoleResponse>.CreateFailure("角色已存在");
}
// 创建角色
var role = new AppRole
{
Name = request.Name,
Description = request.Description
};
var result = await _roleManager.CreateAsync(role);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("创建角色失败: {Errors}", string.Join(", ", errors));
return OperationResult<CreateRoleResponse>.CreateFailure(errors);
}
_logger.LogInformation("角色 {RoleName} 创建成功", request.Name);
return OperationResult<CreateRoleResponse>.CreateSuccess(
new CreateRoleResponse(role.Id));
}
catch (Exception ex)
{
_logger.LogError(ex, "创建角色 {RoleName} 失败", request.Name);
return OperationResult<CreateRoleResponse>.CreateFailure("创建角色失败,请稍后重试");
}
}
/// <summary>
/// 处理删除角色请求
/// </summary>
public async Task<OperationResult<DeleteRoleResponse>> Handle(
DeleteRoleCommand request,
CancellationToken cancellationToken)
{
try
{
// 查找角色
var role = await _roleManager.FindByIdAsync(request.RoleId);
if (role == null)
{
_logger.LogWarning("角色 {RoleId} 不存在", request.RoleId);
return OperationResult<DeleteRoleResponse>.CreateFailure("角色不存在");
}
// 删除角色
var result = await _roleManager.DeleteAsync(role);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("删除角色失败: {Errors}", string.Join(", ", errors));
return OperationResult<DeleteRoleResponse>.CreateFailure(errors);
}
_logger.LogInformation("角色 {RoleId} 删除成功", request.RoleId);
return OperationResult<DeleteRoleResponse>.CreateSuccess(
new DeleteRoleResponse(true));
}
catch (Exception ex)
{
_logger.LogError(ex, "删除角色 {RoleId} 失败", request.RoleId);
return OperationResult<DeleteRoleResponse>.CreateFailure("删除角色失败,请稍后重试");
}
}
}

41
src/CellularManagement.Application/Features/Roles/Queries/GetAllRolesQuery.cs

@ -0,0 +1,41 @@
using MediatR;
using CellularManagement.Application.Common;
using System;
using CellularManagement.Domain.Common;
using System.Collections.Generic;
namespace CellularManagement.Application.Features.Roles.Queries;
/// <summary>
/// 获取所有角色的查询,支持分页和按名称模糊查询
/// </summary>
public sealed record GetAllRolesQuery(
int PageNumber = 1,
int PageSize = 10,
string? RoleName = null
) : IRequest<OperationResult<GetAllRolesResponse>>;
/// <summary>
/// 获取所有角色响应,包含角色列表和分页信息
/// </summary>
public sealed record GetAllRolesResponse(
List<RoleDto> Roles,
int TotalCount,
int PageNumber,
int PageSize
)
{
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
public bool HasPreviousPage => PageNumber > 1;
public bool HasNextPage => PageNumber < TotalPages;
}
/// <summary>
/// 角色数据传输对象
/// </summary>
public record RoleDto(
string Id,
string Name,
string Description,
DateTime CreatedAt,
DateTime UpdatedAt);

13
src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleQuery.cs

@ -0,0 +1,13 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Roles.Queries.GetRole;
/// <summary>
/// 获取角色查询
/// </summary>
public sealed record GetRoleQuery(
/// <summary>
/// 角色ID
/// </summary>
string RoleId) : IRequest<OperationResult<GetRoleResponse>>;

32
src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleResponse.cs

@ -0,0 +1,32 @@
using System;
namespace CellularManagement.Application.Features.Roles.Queries.GetRole;
/// <summary>
/// 获取角色响应
/// </summary>
public sealed record GetRoleResponse(
/// <summary>
/// 角色ID
/// </summary>
string Id,
/// <summary>
/// 角色名称
/// </summary>
string Name,
/// <summary>
/// 角色描述
/// </summary>
string? Description,
/// <summary>
/// 创建时间
/// </summary>
DateTime CreatedAt,
/// <summary>
/// 更新时间
/// </summary>
DateTime UpdatedAt);

119
src/CellularManagement.Application/Features/Roles/Queries/RoleQueryHandler.cs

@ -0,0 +1,119 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Application.Features.Roles.Queries.GetRole;
using CellularManagement.Application.Features.Roles.Queries;
using Microsoft.EntityFrameworkCore;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System;
using System.Linq;
namespace CellularManagement.Application.Features.Roles.Queries;
/// <summary>
/// 角色查询处理器
/// </summary>
public sealed class RoleQueryHandler :
IRequestHandler<GetRoleQuery, OperationResult<GetRoleResponse>>,
IRequestHandler<GetAllRolesQuery, OperationResult<GetAllRolesResponse>>
{
private readonly RoleManager<AppRole> _roleManager;
private readonly ILogger<RoleQueryHandler> _logger;
/// <summary>
/// 初始化处理器
/// </summary>
public RoleQueryHandler(
RoleManager<AppRole> roleManager,
ILogger<RoleQueryHandler> logger)
{
_roleManager = roleManager;
_logger = logger;
}
/// <summary>
/// 处理获取角色请求
/// </summary>
public async Task<OperationResult<GetRoleResponse>> Handle(
GetRoleQuery request,
CancellationToken cancellationToken)
{
try
{
// 查找角色
var role = await _roleManager.FindByIdAsync(request.RoleId);
if (role == null)
{
_logger.LogWarning("角色 {RoleId} 不存在", request.RoleId);
return OperationResult<GetRoleResponse>.CreateFailure("角色不存在");
}
_logger.LogInformation("获取角色 {RoleId} 成功", request.RoleId);
return OperationResult<GetRoleResponse>.CreateSuccess(
new GetRoleResponse(
role.Id,
role.Name,
role.Description,
role.CreatedAt,
role.UpdatedAt));
}
catch (Exception ex)
{
_logger.LogError(ex, "获取角色 {RoleId} 失败", request.RoleId);
return OperationResult<GetRoleResponse>.CreateFailure("获取角色失败,请稍后重试");
}
}
/// <summary>
/// 处理获取所有角色请求(分页+模糊查询)
/// </summary>
public async Task<OperationResult<GetAllRolesResponse>> Handle(
GetAllRolesQuery request,
CancellationToken cancellationToken)
{
try
{
var query = _roleManager.Roles.AsQueryable();
if (!string.IsNullOrWhiteSpace(request.RoleName))
{
query = query.Where(r => r.Name.Contains(request.RoleName));
}
var totalCount = await query.CountAsync(cancellationToken);
var roles = await query
.Skip((request.PageNumber - 1) * request.PageSize)
.Take(request.PageSize)
.ToListAsync(cancellationToken);
var roleDtos = roles.Select(role => new RoleDto(
role.Id,
role.Name,
role.Description,
role.CreatedAt,
role.UpdatedAt
)).ToList();
var response = new GetAllRolesResponse(
roleDtos,
totalCount,
request.PageNumber,
request.PageSize
);
_logger.LogInformation("成功获取所有角色,共 {Count} 个", totalCount);
return OperationResult<GetAllRolesResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取所有角色失败");
return OperationResult<GetAllRolesResponse>.CreateFailure("获取角色列表失败,请稍后重试");
}
}
}

18
src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserCommand.cs

@ -0,0 +1,18 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Users.Commands.CreateUser;
/// <summary>
/// 创建用户命令
/// 用于创建新用户的命令对象
/// </summary>
/// <param name="UserName">账号</param>
/// <param name="Email">电子邮箱</param>
/// <param name="PhoneNumber">电话号码</param>
/// <param name="Password">密码</param>
public sealed record CreateUserCommand(
string UserName,
string Email,
string PhoneNumber,
string Password) : IRequest<OperationResult<CreateUserResponse>>;

92
src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserCommandHandler.cs

@ -0,0 +1,92 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System;
using System.Linq;
namespace CellularManagement.Application.Features.Users.Commands.CreateUser;
/// <summary>
/// 创建用户命令处理器
/// 处理创建新用户的业务逻辑
/// </summary>
public sealed class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, OperationResult<CreateUserResponse>>
{
private readonly UserManager<AppUser> _userManager;
private readonly ILogger<CreateUserCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="userManager">用户管理器,用于管理用户身份</param>
/// <param name="logger">日志记录器</param>
/// <param name="unitOfWork">工作单元,用于事务管理</param>
public CreateUserCommandHandler(
UserManager<AppUser> userManager,
ILogger<CreateUserCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_userManager = userManager;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 处理创建用户命令
/// </summary>
/// <param name="request">创建用户命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果,包含创建的用户ID</returns>
public async Task<OperationResult<CreateUserResponse>> Handle(
CreateUserCommand request,
CancellationToken cancellationToken)
{
try
{
// 检查用户是否已存在
var existingUser = await _userManager.FindByEmailAsync(request.Email);
if (existingUser != null)
{
_logger.LogWarning("用户邮箱 {Email} 已存在", request.Email);
return OperationResult<CreateUserResponse>.CreateFailure("用户已存在");
}
// 创建新用户
var user = new AppUser
{
UserName = request.UserName,
Email = request.Email,
PhoneNumber = request.PhoneNumber
};
// 在事务中创建用户
await _unitOfWork.ExecuteTransactionAsync(async () =>
{
var result = await _userManager.CreateAsync(user, request.Password);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("创建用户失败: {Errors}", string.Join(", ", errors));
throw new InvalidOperationException(string.Join(", ", errors));
}
}, cancellationToken: cancellationToken);
_logger.LogInformation("用户 {UserId} 创建成功", user.Id);
return OperationResult<CreateUserResponse>.CreateSuccess(
new CreateUserResponse(user.Id));
}
catch (Exception ex)
{
_logger.LogError(ex, "创建用户时发生错误");
_logger.LogError(ex, "Error creating user");
return OperationResult<CreateUserResponse>.CreateFailure("Failed to create user");
}
}
}

39
src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserCommandValidator.cs

@ -0,0 +1,39 @@
using FluentValidation;
using CellularManagement.Application.Common.Extensions;
namespace CellularManagement.Application.Features.Users.Commands.CreateUser;
public sealed class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
// 验证账号
RuleFor(x => x.UserName)
.NotEmpty().WithMessage("账号不能为空")
.MinimumLength(3).WithMessage("账号长度不能少于3个字符")
.MaximumLength(50).WithMessage("账号长度不能超过50个字符")
.Matches("^[a-zA-Z0-9_]+$").WithMessage("账号只能包含字母、数字和下划线");
// 验证邮箱
RuleFor(x => x.Email)
.NotEmpty().WithMessage("邮箱不能为空")
.MaximumLength(256).WithMessage("邮箱长度不能超过256个字符")
.Must(x => x.IsValidEmail()).WithMessage("邮箱格式不正确");
// 验证密码
RuleFor(x => x.Password)
.NotEmpty().WithMessage("密码不能为空")
.MinimumLength(6).WithMessage("密码长度不能少于6个字符")
.MaximumLength(50).WithMessage("密码长度不能超过50个字符")
.Matches("[A-Z]").WithMessage("密码必须包含至少一个大写字母")
.Matches("[a-z]").WithMessage("密码必须包含至少一个小写字母")
.Matches("[0-9]").WithMessage("密码必须包含至少一个数字")
.Matches("[^a-zA-Z0-9]").WithMessage("密码必须包含至少一个特殊字符");
// 验证电话号码
RuleFor(x => x.PhoneNumber)
.Matches(@"^1[3-9]\d{9}$").When(x => !string.IsNullOrEmpty(x.PhoneNumber))
.WithMessage("电话号码格式不正确");
}
}

8
src/CellularManagement.Application/Features/Users/Commands/CreateUser/CreateUserResponse.cs

@ -0,0 +1,8 @@
namespace CellularManagement.Application.Features.Users.Commands.CreateUser;
/// <summary>
/// 创建用户响应
/// 包含创建成功后的用户ID
/// </summary>
/// <param name="UserId">用户ID</param>
public sealed record CreateUserResponse(string UserId);

11
src/CellularManagement.Application/Features/Users/Commands/DeleteUser/DeleteUserCommand.cs

@ -0,0 +1,11 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Users.Commands.DeleteUser;
/// <summary>
/// 删除用户命令
/// 用于删除指定用户的命令对象
/// </summary>
/// <param name="UserId">要删除的用户ID</param>
public sealed record DeleteUserCommand(string UserId) : IRequest<OperationResult<DeleteUserResponse>>;

83
src/CellularManagement.Application/Features/Users/Commands/DeleteUser/DeleteUserCommandHandler.cs

@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using System;
namespace CellularManagement.Application.Features.Users.Commands.DeleteUser;
/// <summary>
/// 删除用户命令处理器
/// 处理删除用户的业务逻辑
/// </summary>
public sealed class DeleteUserCommandHandler : IRequestHandler<DeleteUserCommand, OperationResult<DeleteUserResponse>>
{
private readonly UserManager<AppUser> _userManager;
private readonly ILogger<DeleteUserCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="userManager">用户管理器,用于管理用户身份</param>
/// <param name="logger">日志记录器</param>
/// <param name="unitOfWork">工作单元,用于事务管理</param>
public DeleteUserCommandHandler(
UserManager<AppUser> userManager,
ILogger<DeleteUserCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_userManager = userManager;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 处理删除用户命令
/// </summary>
/// <param name="request">删除用户命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果,包含删除的用户ID</returns>
public async Task<OperationResult<DeleteUserResponse>> Handle(
DeleteUserCommand request,
CancellationToken cancellationToken)
{
try
{
// 查找要删除的用户
var user = await _userManager.FindByIdAsync(request.UserId);
if (user == null)
{
_logger.LogWarning("用户 {UserId} 不存在", request.UserId);
return OperationResult<DeleteUserResponse>.CreateFailure("用户不存在");
}
// 在事务中删除用户
await _unitOfWork.ExecuteTransactionAsync(async () =>
{
var result = await _userManager.DeleteAsync(user);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("删除用户失败: {Errors}", string.Join(", ", errors));
throw new InvalidOperationException(string.Join(", ", errors));
}
}, cancellationToken: cancellationToken);
_logger.LogInformation("用户 {UserId} 删除成功", user.Id);
return OperationResult<DeleteUserResponse>.CreateSuccess(
new DeleteUserResponse(user.Id));
}
catch (Exception ex)
{
_logger.LogError(ex, "删除用户时发生错误");
return OperationResult<DeleteUserResponse>.CreateFailure("删除用户失败");
}
}
}

8
src/CellularManagement.Application/Features/Users/Commands/DeleteUser/DeleteUserResponse.cs

@ -0,0 +1,8 @@
namespace CellularManagement.Application.Features.Users.Commands.DeleteUser;
/// <summary>
/// 删除用户响应
/// 包含删除成功后的用户ID
/// </summary>
/// <param name="UserId">用户ID</param>
public sealed record DeleteUserResponse(string UserId);

20
src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserCommand.cs

@ -0,0 +1,20 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser;
/// <summary>
/// 更新用户命令
/// 用于更新指定用户信息的命令对象
/// </summary>
/// <param name="UserId">要更新的用户ID</param>
/// <param name="UserName">新的账号</param>
/// <param name="RealName">新的用户名</param>
/// <param name="Email">新的电子邮箱</param>
/// <param name="PhoneNumber">新的电话号码</param>
public sealed record UpdateUserCommand(
string UserId,
string UserName,
string RealName,
string Email,
string PhoneNumber) : IRequest<OperationResult<UpdateUserResponse>>;

72
src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserCommandHandler.cs

@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using System;
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser;
public sealed class UpdateUserCommandHandler : IRequestHandler<UpdateUserCommand, OperationResult<UpdateUserResponse>>
{
private readonly UserManager<AppUser> _userManager;
private readonly ILogger<UpdateUserCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
public UpdateUserCommandHandler(
UserManager<AppUser> userManager,
ILogger<UpdateUserCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_userManager = userManager;
_logger = logger;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult<UpdateUserResponse>> Handle(
UpdateUserCommand request,
CancellationToken cancellationToken)
{
try
{
var user = await _userManager.FindByIdAsync(request.UserId);
if (user == null)
{
_logger.LogWarning("User {UserId} not found", request.UserId);
return OperationResult<UpdateUserResponse>.CreateFailure("用户不存在");
}
// Update user properties if provided
if (request.UserName != null) user.UserName = request.UserName;
if (request.RealName != null) user.RealName = request.RealName;
if (request.Email != null) user.Email = request.Email;
if (request.PhoneNumber != null) user.PhoneNumber = request.PhoneNumber;
// Update user in transaction
await _unitOfWork.ExecuteTransactionAsync(async () =>
{
var result = await _userManager.UpdateAsync(user);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("Failed to update user: {Errors}", string.Join(", ", errors));
throw new InvalidOperationException(string.Join(", ", errors));
}
}, cancellationToken: cancellationToken);
_logger.LogInformation("User {UserId} updated successfully", user.Id);
return OperationResult<UpdateUserResponse>.CreateSuccess(
new UpdateUserResponse(user.Id));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating user");
return OperationResult<UpdateUserResponse>.CreateFailure("更新用户失败");
}
}
}

37
src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserCommandValidator.cs

@ -0,0 +1,37 @@
using FluentValidation;
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser;
public sealed class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand>
{
public UpdateUserCommandValidator()
{
RuleFor(x => x.UserId)
.NotEmpty().WithMessage("用户ID不能为空");
When(x => x.UserName != null, () =>
{
RuleFor(x => x.UserName)
.Length(3, 50).WithMessage("账号长度必须在3-50个字符之间")
.Matches("^[a-zA-Z0-9_]+$").WithMessage("账号只能包含字母、数字和下划线");
});
When(x => x.RealName != null, () =>
{
RuleFor(x => x.RealName)
.MaximumLength(50).WithMessage("用户名长度不能超过50个字符");
});
When(x => x.Email != null, () =>
{
RuleFor(x => x.Email)
.EmailAddress().WithMessage("邮箱格式不正确");
});
When(x => x.PhoneNumber != null, () =>
{
RuleFor(x => x.PhoneNumber)
.Matches(@"^1[3-9]\d{9}$").WithMessage("手机号格式不正确");
});
}
}

8
src/CellularManagement.Application/Features/Users/Commands/UpdateUser/UpdateUserResponse.cs

@ -0,0 +1,8 @@
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser;
/// <summary>
/// 更新用户响应
/// 包含更新成功后的用户ID
/// </summary>
/// <param name="UserId">用户ID</param>
public sealed record UpdateUserResponse(string UserId);

16
src/CellularManagement.Application/Features/Users/Queries/Dtos/UserDto.cs

@ -0,0 +1,16 @@
namespace CellularManagement.Application.Features.Users.Queries.Dtos;
/// <summary>
/// 用户数据传输对象
/// 用于在API层和领域层之间传输用户数据
/// </summary>
/// <param name="UserId">用户ID</param>
/// <param name="UserName">用户名</param>
/// <param name="Email">电子邮箱</param>
/// <param name="PhoneNumber">电话号码</param>
public sealed record UserDto(
string UserId,
string UserName,
string RealName,
string Email,
string PhoneNumber, DateTime CreatedAt, bool IsActive);

20
src/CellularManagement.Application/Features/Users/Queries/GetAllUsers/GetAllUsersQuery.cs

@ -0,0 +1,20 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Users.Queries.GetAllUsers;
/// <summary>
/// 获取所有用户查询
/// 支持按用户名、邮箱、手机号进行条件查询
/// </summary>
/// <param name="PageNumber">页码,从1开始</param>
/// <param name="PageSize">每页记录数</param>
/// <param name="UserName">用户名(可选,支持模糊查询)</param>
/// <param name="Email">邮箱(可选,支持模糊查询)</param>
/// <param name="PhoneNumber">手机号(可选,支持模糊查询)</param>
public sealed record GetAllUsersQuery(
int PageNumber = 1,
int PageSize = 10,
string? UserName = null,
string? Email = null,
string? PhoneNumber = null) : IRequest<OperationResult<GetAllUsersResponse>>;

100
src/CellularManagement.Application/Features/Users/Queries/GetAllUsers/GetAllUsersQueryHandler.cs

@ -0,0 +1,100 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Application.Features.Users.Queries.Dtos;
using Microsoft.EntityFrameworkCore;
using CellularManagement.Domain.Common;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using System;
namespace CellularManagement.Application.Features.Users.Queries.GetAllUsers;
/// <summary>
/// 获取所有用户查询处理器
/// 处理获取系统中所有用户信息的业务逻辑
/// </summary>
public sealed class GetAllUsersQueryHandler : IRequestHandler<GetAllUsersQuery, OperationResult<GetAllUsersResponse>>
{
private readonly UserManager<AppUser> _userManager;
private readonly ILogger<GetAllUsersQueryHandler> _logger;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="userManager">用户管理器,用于管理用户身份</param>
/// <param name="logger">日志记录器</param>
public GetAllUsersQueryHandler(
UserManager<AppUser> userManager,
ILogger<GetAllUsersQueryHandler> logger)
{
_userManager = userManager;
_logger = logger;
}
/// <summary>
/// 处理获取所有用户命令
/// </summary>
/// <param name="request">查询命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果,包含所有用户信息列表</returns>
public async Task<OperationResult<GetAllUsersResponse>> Handle(
GetAllUsersQuery request,
CancellationToken cancellationToken)
{
try
{
// 构建基础查询
var query = _userManager.Users.AsQueryable();
// 应用条件过滤
if (!string.IsNullOrWhiteSpace(request.UserName))
{
query = query.Where(u => u.UserName.Contains(request.UserName));
}
if (!string.IsNullOrWhiteSpace(request.Email))
{
query = query.Where(u => u.Email.Contains(request.Email));
}
if (!string.IsNullOrWhiteSpace(request.PhoneNumber))
{
query = query.Where(u => u.PhoneNumber.Contains(request.PhoneNumber));
}
// 获取总记录数
var totalCount = await query.CountAsync(cancellationToken);
// 应用分页
var users = await query
.Skip((request.PageNumber - 1) * request.PageSize)
.Take(request.PageSize)
.ToListAsync(cancellationToken);
// 转换为DTO对象
var userDtos = users.Select(user => new UserDto(
user.Id,
user.UserName,
user.RealName,
user.Email,
user.PhoneNumber,user.CreatedTime,user.IsActive)).ToList();
// 构建响应对象
var response = new GetAllUsersResponse(
userDtos,
totalCount,
request.PageNumber,
request.PageSize);
return OperationResult<GetAllUsersResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取所有用户时发生错误");
return OperationResult<GetAllUsersResponse>.CreateFailure("获取用户列表失败");
}
}
}

35
src/CellularManagement.Application/Features/Users/Queries/GetAllUsers/GetAllUsersResponse.cs

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using CellularManagement.Application.Features.Users.Queries.Dtos;
namespace CellularManagement.Application.Features.Users.Queries.GetAllUsers;
/// <summary>
/// 获取所有用户响应
/// 包含用户列表和分页信息
/// </summary>
/// <param name="Users">用户列表</param>
/// <param name="TotalCount">总记录数</param>
/// <param name="PageNumber">当前页码</param>
/// <param name="PageSize">每页记录数</param>
public sealed record GetAllUsersResponse(
List<UserDto> Users,
int TotalCount,
int PageNumber,
int PageSize)
{
/// <summary>
/// 总页数
/// </summary>
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
/// <summary>
/// 是否有上一页
/// </summary>
public bool HasPreviousPage => PageNumber > 1;
/// <summary>
/// 是否有下一页
/// </summary>
public bool HasNextPage => PageNumber < TotalPages;
}

11
src/CellularManagement.Application/Features/Users/Queries/GetUserById/GetUserByIdQuery.cs

@ -0,0 +1,11 @@
using CellularManagement.Domain.Common;
using MediatR;
namespace CellularManagement.Application.Features.Users.Queries.GetUserById;
/// <summary>
/// 获取用户详情查询
/// 用于获取指定用户详细信息的查询对象
/// </summary>
/// <param name="UserId">要查询的用户ID</param>
public sealed record GetUserByIdQuery(string UserId) : IRequest<OperationResult<GetUserByIdResponse>>;

71
src/CellularManagement.Application/Features/Users/Queries/GetUserById/GetUserByIdQueryHandler.cs

@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Application.Features.Users.Queries.Dtos;
using CellularManagement.Domain.Common;
using System.Threading;
using System.Threading.Tasks;
using System;
namespace CellularManagement.Application.Features.Users.Queries.GetUserById;
/// <summary>
/// 根据ID查询用户处理器
/// 处理根据ID查询用户信息的业务逻辑
/// </summary>
public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, OperationResult<GetUserByIdResponse>>
{
private readonly UserManager<AppUser> _userManager;
private readonly ILogger<GetUserByIdQueryHandler> _logger;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="userManager">用户管理器,用于管理用户身份</param>
/// <param name="logger">日志记录器</param>
public GetUserByIdQueryHandler(
UserManager<AppUser> userManager,
ILogger<GetUserByIdQueryHandler> logger)
{
_userManager = userManager;
_logger = logger;
}
/// <summary>
/// 处理根据ID查询用户命令
/// </summary>
/// <param name="request">查询用户命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果,包含查询到的用户信息</returns>
public async Task<OperationResult<GetUserByIdResponse>> Handle(
GetUserByIdQuery request,
CancellationToken cancellationToken)
{
try
{
// 根据ID查询用户
var user = await _userManager.FindByIdAsync(request.UserId);
if (user == null)
{
_logger.LogWarning("用户 {UserId} 不存在", request.UserId);
return OperationResult<GetUserByIdResponse>.CreateFailure("用户不存在");
}
var dto = new UserDto(user.Id,
user.UserName,
user.RealName,
user.Email,
user.PhoneNumber,user.CreatedTime, user.IsActive);
// 构建响应对象
var response = new GetUserByIdResponse(dto);
return OperationResult<GetUserByIdResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "查询用户时发生错误");
return OperationResult<GetUserByIdResponse>.CreateFailure("查询用户失败");
}
}
}

10
src/CellularManagement.Application/Features/Users/Queries/GetUserById/GetUserByIdResponse.cs

@ -0,0 +1,10 @@
using CellularManagement.Application.Features.Users.Queries.Dtos;
namespace CellularManagement.Application.Features.Users.Queries.GetUserById;
/// <summary>
/// 用户查询响应
/// 包含查询到的用户详细信息
/// </summary>
/// <param name="User">用户信息</param>
public sealed record GetUserByIdResponse(UserDto User);

362
src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Application.deps.json

@ -1,362 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"CellularManagement.Application/1.0.0": {
"dependencies": {
"CellularManagement.Domain": "1.0.0",
"Microsoft.Extensions.DependencyInjection": "8.0.0"
},
"runtime": {
"CellularManagement.Application.dll": {}
}
},
"Microsoft.AspNetCore.Cryptography.Internal/7.0.0": {
"runtime": {
"lib/net7.0/Microsoft.AspNetCore.Cryptography.Internal.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51819"
}
}
},
"Microsoft.AspNetCore.Cryptography.KeyDerivation/7.0.0": {
"dependencies": {
"Microsoft.AspNetCore.Cryptography.Internal": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.AspNetCore.Cryptography.KeyDerivation.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51819"
}
}
},
"Microsoft.AspNetCore.Identity.EntityFrameworkCore/7.0.0": {
"dependencies": {
"Microsoft.EntityFrameworkCore.Relational": "7.0.0",
"Microsoft.Extensions.Identity.Stores": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.AspNetCore.Identity.EntityFrameworkCore.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51819"
}
}
},
"Microsoft.EntityFrameworkCore/7.0.0": {
"dependencies": {
"Microsoft.EntityFrameworkCore.Abstractions": "7.0.0",
"Microsoft.EntityFrameworkCore.Analyzers": "7.0.0",
"Microsoft.Extensions.Caching.Memory": "7.0.0",
"Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.Logging": "7.0.0"
},
"runtime": {
"lib/net6.0/Microsoft.EntityFrameworkCore.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51807"
}
}
},
"Microsoft.EntityFrameworkCore.Abstractions/7.0.0": {
"runtime": {
"lib/net6.0/Microsoft.EntityFrameworkCore.Abstractions.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51807"
}
}
},
"Microsoft.EntityFrameworkCore.Analyzers/7.0.0": {},
"Microsoft.EntityFrameworkCore.Relational/7.0.0": {
"dependencies": {
"Microsoft.EntityFrameworkCore": "7.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0"
},
"runtime": {
"lib/net6.0/Microsoft.EntityFrameworkCore.Relational.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51807"
}
}
},
"Microsoft.Extensions.Caching.Abstractions/7.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Caching.Abstractions.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"Microsoft.Extensions.Caching.Memory/7.0.0": {
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "7.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
"Microsoft.Extensions.Options": "7.0.0",
"Microsoft.Extensions.Primitives": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Caching.Memory.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"Microsoft.Extensions.Configuration.Abstractions/7.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"Microsoft.Extensions.DependencyInjection/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Identity.Core/7.0.0": {
"dependencies": {
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "7.0.0",
"Microsoft.Extensions.Logging": "7.0.0",
"Microsoft.Extensions.Options": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Identity.Core.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51819"
}
}
},
"Microsoft.Extensions.Identity.Stores/7.0.0": {
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "7.0.0",
"Microsoft.Extensions.Identity.Core": "7.0.0",
"Microsoft.Extensions.Logging": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Identity.Stores.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51819"
}
}
},
"Microsoft.Extensions.Logging/7.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
"Microsoft.Extensions.Options": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Logging.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/7.0.0": {
"runtime": {
"lib/net7.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"Microsoft.Extensions.Options/7.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "7.0.0"
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"Microsoft.Extensions.Primitives/7.0.0": {
"runtime": {
"lib/net7.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"CellularManagement.Domain/1.0.0": {
"dependencies": {
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "7.0.0",
"Microsoft.EntityFrameworkCore": "7.0.0",
"Microsoft.Extensions.DependencyInjection": "8.0.0"
},
"runtime": {
"CellularManagement.Domain.dll": {
"assemblyVersion": "1.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"CellularManagement.Application/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.AspNetCore.Cryptography.Internal/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-hFF+HOqtiNrGtO5ZxLVAFo1ksDLQWf8IHEmGRmcF9azlUWvDLZp8+W8gDyLBcGcY5m3ugEvKy/ncElxO4d0NtQ==",
"path": "microsoft.aspnetcore.cryptography.internal/7.0.0",
"hashPath": "microsoft.aspnetcore.cryptography.internal.7.0.0.nupkg.sha512"
},
"Microsoft.AspNetCore.Cryptography.KeyDerivation/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-rCQddWkUxGmObeftM0YVyFOPcXkXDEWKGCc4F1viRLEL4ojIbdKwbOYBSf5hfWDR+NO0aGq8r3a8COvNYN/bZA==",
"path": "microsoft.aspnetcore.cryptography.keyderivation/7.0.0",
"hashPath": "microsoft.aspnetcore.cryptography.keyderivation.7.0.0.nupkg.sha512"
},
"Microsoft.AspNetCore.Identity.EntityFrameworkCore/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-mtomuG24wGpvdblVQUj/JHIZ1i8oNhRNHr0V0re8fTkv15hz+AQLdtwbdd6FdINNeXiKi3kGmzZ7PE1KOyzoSg==",
"path": "microsoft.aspnetcore.identity.entityframeworkcore/7.0.0",
"hashPath": "microsoft.aspnetcore.identity.entityframeworkcore.7.0.0.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-9W+IfmAzMrp2ZpKZLhgTlWljSBM9Erldis1us61DAGi+L7Q6vilTbe1G2zDxtYO8F2H0I0Qnupdx5Cp4s2xoZw==",
"path": "microsoft.entityframeworkcore/7.0.0",
"hashPath": "microsoft.entityframeworkcore.7.0.0.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore.Abstractions/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Pfu3Zjj5+d2Gt27oE9dpGiF/VobBB+s5ogrfI9sBsXQE1SG49RqVz5+IyeNnzhyejFrPIQsPDRMchhcojy4Hbw==",
"path": "microsoft.entityframeworkcore.abstractions/7.0.0",
"hashPath": "microsoft.entityframeworkcore.abstractions.7.0.0.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore.Analyzers/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Qkd2H+jLe37o5ku+LjT6qf7kAHY75Yfn2bBDQgqr13DTOLYpEy1Mt93KPFjaZvIu/srEcbfGGMRL7urKm5zN8Q==",
"path": "microsoft.entityframeworkcore.analyzers/7.0.0",
"hashPath": "microsoft.entityframeworkcore.analyzers.7.0.0.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore.Relational/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-eQiYygtR2xZ0Uy7KtiFRHpoEx/U8xNwbNRgu1pEJgSxbJLtg6tDL1y2YcIbSuIRSNEljXIIHq/apEhGm1QL70g==",
"path": "microsoft.entityframeworkcore.relational/7.0.0",
"hashPath": "microsoft.entityframeworkcore.relational.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Caching.Abstractions/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==",
"path": "microsoft.extensions.caching.abstractions/7.0.0",
"hashPath": "microsoft.extensions.caching.abstractions.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Caching.Memory/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==",
"path": "microsoft.extensions.caching.memory/7.0.0",
"hashPath": "microsoft.extensions.caching.memory.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Abstractions/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==",
"path": "microsoft.extensions.configuration.abstractions/7.0.0",
"hashPath": "microsoft.extensions.configuration.abstractions.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"path": "microsoft.extensions.dependencyinjection/8.0.0",
"hashPath": "microsoft.extensions.dependencyinjection.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==",
"path": "microsoft.extensions.dependencyinjection.abstractions/8.0.0",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Identity.Core/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cq11jroq2szFcXLJ0IW5BlI7oqq3ZGCu1mXCnpJ8VIvhvpIzf30AOoWR/w3YRVdAgkYzxbUQpKGZd+oxAKQhLA==",
"path": "microsoft.extensions.identity.core/7.0.0",
"hashPath": "microsoft.extensions.identity.core.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Identity.Stores/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-feaaluQbzJAMMluwSc7Rebm7IEVAD8/5GWt0dMYLE0tcc6gAsHYjBIBrPzmTstORd7k405Qo18FPF/jTfRsM0A==",
"path": "microsoft.extensions.identity.stores/7.0.0",
"hashPath": "microsoft.extensions.identity.stores.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==",
"path": "microsoft.extensions.logging/7.0.0",
"hashPath": "microsoft.extensions.logging.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==",
"path": "microsoft.extensions.logging.abstractions/7.0.0",
"hashPath": "microsoft.extensions.logging.abstractions.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Options/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==",
"path": "microsoft.extensions.options/7.0.0",
"hashPath": "microsoft.extensions.options.7.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==",
"path": "microsoft.extensions.primitives/7.0.0",
"hashPath": "microsoft.extensions.primitives.7.0.0.nupkg.sha512"
},
"CellularManagement.Domain/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

BIN
src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Application.dll

Binary file not shown.

BIN
src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Application.pdb

Binary file not shown.

BIN
src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Domain.dll

Binary file not shown.

BIN
src/CellularManagement.Application/bin/Debug/net8.0/CellularManagement.Domain.pdb

Binary file not shown.

164
src/CellularManagement.Application/obj/CellularManagement.Application.csproj.nuget.dgspec.json

@ -1,164 +0,0 @@
{
"format": 1,
"restore": {
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj": {}
},
"projects": {
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj",
"projectName": "CellularManagement.Application",
"projectPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj",
"packagesPath": "C:\\Users\\changeself\\.nuget\\packages\\",
"outputPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\changeself\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net8.0"
],
"sources": {
"D:\\NuGetPackages": {},
"https://api.nuget.org/v3/index.json": {},
"https://www.nuget.org/api/v2": {}
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"projectReferences": {
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj": {
"projectPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj"
}
}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.100"
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": {
"target": "Package",
"version": "[8.0.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.105/PortableRuntimeIdentifierGraph.json"
}
}
},
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj",
"projectName": "CellularManagement.Domain",
"projectPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj",
"packagesPath": "C:\\Users\\changeself\\.nuget\\packages\\",
"outputPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\changeself\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net8.0"
],
"sources": {
"D:\\NuGetPackages": {},
"https://api.nuget.org/v3/index.json": {},
"https://www.nuget.org/api/v2": {}
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.100"
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"dependencies": {
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": {
"target": "Package",
"version": "[7.0.0, )"
},
"Microsoft.EntityFrameworkCore": {
"target": "Package",
"version": "[7.0.0, )"
},
"Microsoft.Extensions.DependencyInjection": {
"target": "Package",
"version": "[7.0.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.105/PortableRuntimeIdentifierGraph.json"
}
}
}
}
}

19
src/CellularManagement.Application/obj/CellularManagement.Application.csproj.nuget.g.props

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\changeself\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.12.2</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\changeself\.nuget\packages\" />
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore\7.0.0\buildTransitive\net6.0\Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore\7.0.0\buildTransitive\net6.0\Microsoft.EntityFrameworkCore.props')" />
</ImportGroup>
</Project>

6
src/CellularManagement.Application/obj/CellularManagement.Application.csproj.nuget.g.targets

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\7.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\7.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
</ImportGroup>
</Project>

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

Loading…
Cancel
Save