diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/IProjectNameDataSeeder.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/IProjectNameDataSeeder.cs
new file mode 100644
index 000000000..a1e6c8708
--- /dev/null
+++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/IProjectNameDataSeeder.cs
@@ -0,0 +1,15 @@
+using System.Threading.Tasks;
+using Volo.Abp.Data;
+
+namespace PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.DataSeeder
+{
+ public interface IProjectNameDataSeeder
+ {
+ ///
+ /// 初始化数据
+ ///
+ /// 数据种子上下文
+ /// 任务
+ Task SeedAsync(DataSeedContext context);
+ }
+}
diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/ProjectNameDataSeederDataSeeder.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/ProjectNameDataSeederDataSeeder.cs
new file mode 100644
index 000000000..57276f6be
--- /dev/null
+++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/ProjectNameDataSeederDataSeeder.cs
@@ -0,0 +1,503 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.Guids;
+using Volo.Abp.Identity;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.Uow;
+using IdentityRole = Volo.Abp.Identity.IdentityRole;
+using IdentityUser = Volo.Abp.Identity.IdentityUser;
+
+namespace PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.DataSeeder
+{
+ public class ProjectNameDataSeederDataSeeder : IProjectNameDataSeeder, ITransientDependency
+ {
+ private readonly ICurrentTenant _currentTenant;
+ private readonly IGuidGenerator _guidGenerator;
+ private readonly ILogger _logger;
+ private readonly IRepository _orgRepository;
+ private readonly IRepository _workUnitRepository;
+ private readonly IRepository _userRepository;
+ private readonly IdentityUserManager _identityUserManager;
+ private readonly IdentityRoleManager _identityRoleManager;
+ private readonly IUnitOfWorkManager _unitOfWorkManager;
+
+ ///
+ /// 构造函数
+ ///
+ public ProjectNameDataSeeder(
+ ICurrentTenant currentTenant,
+ IGuidGenerator guidGenerator,
+ ILogger logger,
+ IRepository orgRepository,
+ IRepository workUnitRepository,
+ IRepository userRepository,
+ IdentityUserManager identityUserManager,
+ IdentityRoleManager identityRoleManager,
+ IUnitOfWorkManager unitOfWorkManager)
+ {
+ _currentTenant = currentTenant;
+ _guidGenerator = guidGenerator;
+ _logger = logger;
+ _orgRepository = orgRepository;
+ _workUnitRepository = workUnitRepository;
+ _userRepository = userRepository;
+ _identityUserManager = identityUserManager;
+ _identityRoleManager = identityRoleManager;
+ _unitOfWorkManager = unitOfWorkManager;
+ }
+
+ ///
+ /// 初始化数据
+ ///
+ /// 数据种子上下文
+ /// 任务
+ public async Task SeedAsync(DataSeedContext context)
+ {
+ using (_currentTenant.Change(context.TenantId))
+ {
+ _logger.LogInformation("开始初始化巡检数据...");
+
+ // 初始化角色
+ using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
+ {
+ await SeedRolesAsync();
+ await uow.CompleteAsync();
+ }
+
+ // 初始化单位数据
+ using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
+ {
+ await SeedWorkUnitsAsync();
+ await uow.CompleteAsync();
+ }
+
+ // 初始化组织数据
+ using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
+ {
+ await SeedOrgsAsync();
+ await uow.CompleteAsync();
+ }
+
+ // 初始化用户数据
+ using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
+ {
+ await SeedUsersAsync();
+ await uow.CompleteAsync();
+ }
+
+ _logger.LogInformation("巡检数据初始化完成");
+ }
+ }
+
+ ///
+ /// 初始化角色数据
+ ///
+ private async Task SeedRolesAsync()
+ {
+ // 超级管理员
+ await CreateRoleIfNotExistsAsync(
+ "超级管理员",
+ "系统超级管理员,拥有所有权限");
+
+ // 区委办公室-督查室管理员
+ await CreateRoleIfNotExistsAsync(
+ "区委办公室-督查室管理员",
+ "区委办公室督查室管理员");
+
+ // 责任领导秘书
+ await CreateRoleIfNotExistsAsync(
+ "责任领导秘书",
+ "负责协助责任领导进行工作");
+
+ // 责任单位管理员
+ await CreateRoleIfNotExistsAsync(
+ "责任单位管理员",
+ "负责管理责任单位的信息");
+
+ // 责任单位落实人员
+ await CreateRoleIfNotExistsAsync(
+ "责任单位落实人员",
+ "负责执行责任单位的任务");
+
+ // 责任单位分管领导
+ await CreateRoleIfNotExistsAsync(
+ "责任单位分管领导",
+ "负责管理责任单位的部分工作");
+
+ // 责任单位党组书记
+ await CreateRoleIfNotExistsAsync(
+ "责任单位党组书记",
+ "负责责任单位的党组工作");
+ }
+
+ ///
+ /// 创建角色(如果不存在)
+ ///
+ private async Task CreateRoleIfNotExistsAsync(string roleName, string description)
+ {
+ if (await _identityRoleManager.FindByNameAsync(roleName) == null)
+ {
+ var role = new IdentityRole(
+ _guidGenerator.Create(),
+ roleName,
+ _currentTenant.Id)
+ {
+ IsStatic = true,
+ IsPublic = true
+ };
+
+ await _identityRoleManager.CreateAsync(role);
+
+ _logger.LogInformation($"创建角色:{roleName}");
+ }
+ }
+
+ ///
+ /// 初始化单位数据
+ ///
+ private async Task SeedWorkUnitsAsync()
+ {
+ // 创建永川区
+ var yongchuanDistrict = await CreateWorkUnitIfNotExistsAsync(
+ "永川区",
+ "001",
+ "001",
+ "永川区");
+
+ // 创建区政府
+ var districtGovernment = await CreateWorkUnitIfNotExistsAsync(
+ "区政府",
+ "002",
+ "001.002",
+ "永川区政府",
+ yongchuanDistrict.Id);
+
+ // 创建区委办公室
+ var districtCommitteeOffice = await CreateWorkUnitIfNotExistsAsync(
+ "区委办公室",
+ "003",
+ "001.003",
+ "永川区委办公室",
+ yongchuanDistrict.Id);
+
+ // 创建督查室
+ await CreateWorkUnitIfNotExistsAsync(
+ "督查室",
+ "004",
+ "001.003.004",
+ "区委办公室督查室",
+ districtCommitteeOffice.Id);
+
+ // 创建区委扫黑办
+ await CreateWorkUnitIfNotExistsAsync(
+ "区委扫黑办",
+ "005",
+ "001.005",
+ "永川区委扫黑办",
+ yongchuanDistrict.Id);
+
+ // 创建区体育局
+ await CreateWorkUnitIfNotExistsAsync(
+ "区体育局",
+ "006",
+ "001.006",
+ "永川区体育局",
+ yongchuanDistrict.Id);
+
+ // 创建区统计局
+ await CreateWorkUnitIfNotExistsAsync(
+ "区统计局",
+ "007",
+ "001.007",
+ "永川区统计局",
+ yongchuanDistrict.Id);
+
+ // 创建区信访办
+ await CreateWorkUnitIfNotExistsAsync(
+ "区信访办",
+ "008",
+ "001.008",
+ "永川区信访办",
+ yongchuanDistrict.Id);
+
+ // 创建区医保局
+ await CreateWorkUnitIfNotExistsAsync(
+ "区医保局",
+ "009",
+ "001.009",
+ "永川区医保局",
+ yongchuanDistrict.Id);
+
+ // 创建区大数据发展局
+ await CreateWorkUnitIfNotExistsAsync(
+ "区大数据发展局",
+ "010",
+ "001.010",
+ "永川区大数据发展局",
+ yongchuanDistrict.Id);
+
+ // 创建区机关事务局
+ await CreateWorkUnitIfNotExistsAsync(
+ "区机关事务局",
+ "011",
+ "001.011",
+ "永川区机关事务局",
+ yongchuanDistrict.Id);
+
+ // 创建区广播电视局
+ await CreateWorkUnitIfNotExistsAsync(
+ "区广播电视局",
+ "012",
+ "001.012",
+ "永川区广播电视局",
+ yongchuanDistrict.Id);
+
+ // 创建区中新项目管理局
+ await CreateWorkUnitIfNotExistsAsync(
+ "区中新项目管理局",
+ "013",
+ "001.013",
+ "永川区中新项目管理局",
+ yongchuanDistrict.Id);
+
+ // 创建镇街
+ await CreateWorkUnitIfNotExistsAsync(
+ "镇街",
+ "014",
+ "001.014",
+ "永川区镇街",
+ yongchuanDistrict.Id);
+ }
+
+ ///
+ /// 创建单位(如果不存在)
+ ///
+ private async Task CreateWorkUnitIfNotExistsAsync(
+ string name,
+ string code,
+ string treeCode,
+ string description,
+ Guid? parentId = null)
+ {
+ var workUnit = await _workUnitRepository.FindAsync(w => w.Name == name);
+
+ if (workUnit == null)
+ {
+ workUnit = new WorkUnit(
+ _guidGenerator.Create(),
+ code,
+ treeCode,
+ name,
+ description)
+ {
+ ParentId = parentId
+ };
+
+ await _workUnitRepository.InsertAsync(workUnit);
+
+ _logger.LogInformation($"创建单位:{name}");
+ }
+
+ return workUnit;
+ }
+
+ ///
+ /// 初始化组织数据
+ ///
+ private async Task SeedOrgsAsync()
+ {
+ // 创建区委办公室-督查室
+ var supervisionOffice = await CreateOrgIfNotExistsAsync(
+ "区委办公室-督查室",
+ "001",
+ "001",
+ "区委办公室督查室");
+ }
+
+ ///
+ /// 创建组织(如果不存在)
+ ///
+ private async Task CreateOrgIfNotExistsAsync(
+ string name,
+ string code,
+ string treeCode,
+ string description,
+ Guid? parentId = null)
+ {
+ var org = await _orgRepository.FindAsync(o => o.Name == name);
+
+ if (org == null)
+ {
+ org = new Org(
+ _guidGenerator.Create(),
+ code,
+ treeCode,
+ name,
+ description)
+ {
+ ParentId = parentId
+ };
+
+ await _orgRepository.InsertAsync(org);
+
+ _logger.LogInformation($"创建组织:{name}");
+ }
+
+ return org;
+ }
+
+ ///
+ /// 初始化用户数据
+ ///
+ private async Task SeedUsersAsync()
+ {
+ // 获取所需单位
+ var supervisionOffice = await _workUnitRepository.FindAsync(w => w.Name == "督查室");
+ if (supervisionOffice == null)
+ {
+ _logger.LogError("未找到督查室单位,无法创建用户");
+ return;
+ }
+
+ // 获取所需组织
+ var supervisionOrg = await _orgRepository.FindAsync(o => o.Name == "区委办公室-督查室");
+ if (supervisionOrg == null)
+ {
+ _logger.LogError("未找到区委办公室-督查室组织,无法创建用户");
+ return;
+ }
+
+ // 查找超级管理员角色
+ var superAdminRole = await _identityRoleManager.FindByNameAsync("超级管理员");
+ if (superAdminRole == null)
+ {
+ _logger.LogError("未找到超级管理员角色,无法为用户分配角色");
+ return;
+ }
+
+ // 创建用户数据(使用固定用户名避免生成问题)
+ await CreateUserIfNotExistsAsync("李达康", "lidk001", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员");
+ await CreateUserIfNotExistsAsync("高育良", "gyl002", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员");
+ await CreateUserIfNotExistsAsync("祁同伟", "qtw003", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员");
+ await CreateUserIfNotExistsAsync("侯亮平", "hlp004", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员");
+ await CreateUserIfNotExistsAsync("赵瑞龙", "zrl005", GenderType.Female, supervisionOffice.Id, supervisionOrg.Id, "超级管理员");
+ await CreateUserIfNotExistsAsync("宋梦平", "smp006", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员, 部门领导");
+ await CreateUserIfNotExistsAsync("李太亮", "ltl007", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员, 部门领导");
+ await CreateUserIfNotExistsAsync("高峰", "gf008", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员, 部门领导");
+ await CreateUserIfNotExistsAsync("朱丽平", "zlp009", GenderType.Female, supervisionOffice.Id, supervisionOrg.Id, "超级管理员, 部门领导");
+ await CreateUserIfNotExistsAsync("安欣", "ax010", GenderType.Male, supervisionOffice.Id, supervisionOrg.Id, "超级管理员");
+ }
+
+ ///
+ /// 创建用户(如果不存在)
+ ///
+ private async Task CreateUserIfNotExistsAsync(
+ string name,
+ string userName,
+ GenderType gender,
+ Guid workUnitId,
+ Guid orgId,
+ string roles)
+ {
+ // 检查用户是否已存在
+ var existingUser = await _userRepository.FindAsync(u => u.NickName == name);
+ if (existingUser != null)
+ {
+ _logger.LogInformation($"用户[{name}]已存在,跳过创建");
+ return;
+ }
+
+ var identityUser = await _identityUserManager.FindByNameAsync(userName);
+ if (identityUser == null)
+ {
+ // 创建Identity用户
+ identityUser = new IdentityUser(
+ _guidGenerator.Create(),
+ userName,
+ $"{userName}@example.com",
+ _currentTenant.Id)
+ {
+ Name = name,
+ Surname = ""
+ };
+
+ // 设置默认密码 123456
+ var identityResult = await _identityUserManager.CreateAsync(identityUser, "123456");
+ if (!identityResult.Succeeded)
+ {
+ _logger.LogError($"创建Identity用户[{name}]失败: {string.Join(", ", identityResult.Errors.Select(e => e.Description))}");
+ return;
+ }
+
+ // 分配角色
+ if (!string.IsNullOrWhiteSpace(roles))
+ {
+ var roleNames = roles.Split(',', StringSplitOptions.RemoveEmptyEntries);
+ foreach (var roleName in roleNames)
+ {
+ var trimmedRoleName = roleName.Trim();
+ var role = await _identityRoleManager.FindByNameAsync(trimmedRoleName);
+ if (role != null)
+ {
+ var roleResult = await _identityUserManager.AddToRoleAsync(identityUser, trimmedRoleName);
+ if (!roleResult.Succeeded)
+ {
+ _logger.LogWarning($"为用户[{name}]分配角色[{trimmedRoleName}]失败: {string.Join(", ", roleResult.Errors.Select(e => e.Description))}");
+ }
+ }
+ else
+ {
+ _logger.LogWarning($"角色[{trimmedRoleName}]不存在,无法为用户[{name}]分配");
+ }
+ }
+ }
+
+ // 创建系统用户
+ var user = new User(
+ _guidGenerator.Create(),
+ name,
+ workUnitId,
+ identityUser.Id,
+ gender)
+ {
+ OrgId = orgId
+ };
+
+ // 保存用户
+ await _userRepository.InsertAsync(user);
+
+ _logger.LogInformation($"创建用户:{name},用户名:{userName}");
+ }
+ else
+ {
+ _logger.LogInformation($"Identity用户[{userName}]已存在,检查是否需要创建业务用户");
+
+ // 检查是否需要创建业务用户
+ var businessUser = await _userRepository.FindAsync(u => u.IdentityUserId == identityUser.Id);
+ if (businessUser == null)
+ {
+ // 创建系统用户
+ var user = new User(
+ _guidGenerator.Create(),
+ name,
+ workUnitId,
+ identityUser.Id,
+ gender)
+ {
+ OrgId = orgId
+ };
+
+ // 保存用户
+ await _userRepository.InsertAsync(user);
+
+ _logger.LogInformation($"为已存在的Identity用户创建业务用户:{name}");
+ }
+ }
+ }
+ }
+}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs
index 4a170551b..2adf0a13f 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs
@@ -13,6 +13,12 @@ public class ProjectNamePermissionDefinitionProvider : PermissionDefinitionProvi
group.AddPermission(
ProjectNamePermissions.ManageSettings,
L("Permission:ManageSettings"));
+
+ var userPermission = group.AddPermission(ProjectNamePermissions.User.Default, L("Permission:User"));
+ userPermission.AddChild(ProjectNamePermissions.User.Create, L("Permission:Create"));
+ userPermission.AddChild(ProjectNamePermissions.User.Update, L("Permission:Update"));
+ userPermission.AddChild(ProjectNamePermissions.User.Delete, L("Permission:Delete"));
+
}
private static LocalizableString L(string name)
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs
index 98a957ff9..fb03f41bc 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs
@@ -5,4 +5,12 @@ public static class ProjectNamePermissions
public const string GroupName = "ProjectName";
public const string ManageSettings = GroupName + ".ManageSettings";
+
+ public class User
+ {
+ public const string Default = GroupName + ".User";
+ public const string Update = Default + ".Update";
+ public const string Create = Default + ".Create";
+ public const string Delete = Default + ".Delete";
+ }
}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/CreateUpdateUserDto.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/CreateUpdateUserDto.cs
new file mode 100644
index 000000000..ae1e5e270
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/CreateUpdateUserDto.cs
@@ -0,0 +1,37 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace PackageName.CompanyName.ProjectName.Users.Dtos
+{
+ [Serializable]
+ public class CreateUpdateUserDto
+ {
+ ///
+ /// 用户名称
+ ///
+ [Required(ErrorMessage = "用户名称不能为空")]
+ [StringLength(50, ErrorMessage = "用户名称长度不能超过50个字符")]
+ public string NickName { get; set; }
+
+ ///
+ /// 密码
+ ///
+ [StringLength(20, MinimumLength = 6, ErrorMessage = "密码长度必须在6-20个字符之间")]
+ public string Password { get; set; }
+
+ ///
+ /// 联系方式
+ ///
+ public string ContactInfo { get; set; }
+
+ ///
+ /// 职位
+ ///
+ public string Position { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsActive { get; set; } = true;
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserDto.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserDto.cs
new file mode 100644
index 000000000..2c5dd4f3d
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserDto.cs
@@ -0,0 +1,39 @@
+using System;
+using Volo.Abp.Application.Dtos;
+
+namespace PackageName.CompanyName.ProjectName.Users.Dtos
+{
+ [Serializable]
+ public class UserDto : FullAuditedEntityDto
+ {
+ ///
+ /// 用户名称
+ ///
+ public string NickName { get; set; }
+
+ ///
+ /// Identity用户Id
+ ///
+ public Guid IdentityUserId { get; set; }
+
+ ///
+ /// 用户状态
+ ///
+ public bool IsActive { get; set; }
+
+ ///
+ /// 联系方式
+ ///
+ public string ContactInfo { get; set; }
+
+ ///
+ /// 职位
+ ///
+ public string Position { get; set; }
+
+ ///
+ /// 角色名称
+ ///
+ public string RoleNames { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserItemDto.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserItemDto.cs
new file mode 100644
index 000000000..92014ffbe
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserItemDto.cs
@@ -0,0 +1,39 @@
+using System;
+using Volo.Abp.Application.Dtos;
+
+namespace PackageName.CompanyName.ProjectName.Users.Dtos
+{
+ [Serializable]
+ public class UserItemDto : FullAuditedEntityDto
+ {
+ ///
+ /// 用户名称
+ ///
+ public string NickName { get; set; }
+
+ ///
+ /// Identity用户Id
+ ///
+ public Guid IdentityUserId { get; set; }
+
+ ///
+ /// 用户状态
+ ///
+ public bool IsActive { get; set; }
+
+ ///
+ /// 联系方式
+ ///
+ public string ContactInfo { get; set; }
+
+ ///
+ /// 职位
+ ///
+ public string Position { get; set; }
+
+ ///
+ /// 角色名称
+ ///
+ public string RoleNames { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserPagedAndSortedResultRequestDto.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserPagedAndSortedResultRequestDto.cs
new file mode 100644
index 000000000..9c7c26ee9
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/Dtos/UserPagedAndSortedResultRequestDto.cs
@@ -0,0 +1,14 @@
+using System;
+using Volo.Abp.Application.Dtos;
+
+namespace PackageName.CompanyName.ProjectName.Users.Dtos
+{
+ [Serializable]
+ public class UserPagedAndSortedResultRequestDto : PagedAndSortedResultRequestDto
+ {
+ ///
+ /// 用户名称
+ ///
+ public string NickName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/IUserAppService.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/IUserAppService.cs
new file mode 100644
index 000000000..606d0ddc7
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Users/IUserAppService.cs
@@ -0,0 +1,55 @@
+using PackageName.CompanyName.ProjectName.Users.Dtos;
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ ///
+ /// 用户应用服务接口
+ ///
+ public interface IUserAppService :
+ IApplicationService
+ {
+ ///
+ /// 创建用户
+ ///
+ Task CreateAsync(CreateUpdateUserDto input);
+
+ ///
+ /// 更新用户
+ ///
+ Task UpdateAsync(Guid id, CreateUpdateUserDto input);
+
+ ///
+ /// 删除用户
+ ///
+ Task DeleteAsync(Guid id);
+
+ ///
+ /// 获取用户
+ ///
+ Task GetAsync(Guid id);
+
+ ///
+ /// 获取用户列表
+ ///
+ Task> GetListAsync(UserPagedAndSortedResultRequestDto input);
+
+ ///
+ /// 修改用户密码
+ ///
+ Task ChangePasswordAsync(Guid id, string currentPassword, string newPassword);
+
+ ///
+ /// 重置用户密码(管理员操作)
+ ///
+ Task ResetPasswordAsync(Guid id, string newPassword);
+
+ ///
+ /// 启用或禁用用户
+ ///
+ Task SetUserActiveStatusAsync(Guid id, bool isActive);
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj
index b36251c2d..10f4f0ddc 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj
@@ -14,6 +14,7 @@
+
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs
index 0ace9b456..2dd396079 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs
@@ -1,4 +1,6 @@
using AutoMapper;
+using PackageName.CompanyName.ProjectName.Users;
+using PackageName.CompanyName.ProjectName.Users.Dtos;
namespace PackageName.CompanyName.ProjectName;
@@ -6,5 +8,13 @@ public class ProjectNameApplicationMapperProfile : Profile
{
public ProjectNameApplicationMapperProfile()
{
+ CreateMap()
+ .ForMember(d => d.IsActive, o => o.Ignore())
+ .ForMember(d => d.RoleNames, o => o.Ignore());
+ CreateMap()
+ .ForMember(d => d.IsActive, o => o.Ignore())
+ .ForMember(d => d.RoleNames, o => o.Ignore());
+ CreateMap(MemberList.None);
+
}
}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs
index 6e1cf009f..d8704f2ac 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs
@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Application;
using Volo.Abp.Authorization;
using Volo.Abp.AutoMapper;
+using Volo.Abp.BackgroundJobs;
using Volo.Abp.Modularity;
namespace PackageName.CompanyName.ProjectName;
@@ -12,6 +13,7 @@ namespace PackageName.CompanyName.ProjectName;
typeof(AbpDddApplicationModule),
typeof(ProjectNameDomainModule),
typeof(ProjectNameApplicationContractsModule),
+ typeof(AbpBackgroundJobsModule),
typeof(AbpDynamicQueryableApplicationModule))]
public class ProjectNameApplicationModule : AbpModule
{
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/Users/UserAppService.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/Users/UserAppService.cs
new file mode 100644
index 000000000..25eef3a6e
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/Users/UserAppService.cs
@@ -0,0 +1,198 @@
+using Microsoft.AspNetCore.Authorization;
+using PackageName.CompanyName.ProjectName.Permissions;
+using PackageName.CompanyName.ProjectName.Users.Dtos;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+using Volo.Abp.Identity;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ ///
+ /// 用户
+ ///
+ public class UserAppService : ApplicationService, IUserAppService
+ {
+ private readonly IUserRepository _userRepository;
+ private readonly IUserManager _userManager;
+ private readonly IdentityUserManager _identityUserManager;
+
+ public UserAppService(
+ IUserRepository userRepository,
+ IUserManager userManager,
+ IdentityUserManager identityUserManager)
+ {
+ _userRepository = userRepository;
+ _userManager = userManager;
+ _identityUserManager = identityUserManager;
+ }
+
+ ///
+ /// 创建用户
+ ///
+ [Authorize(ProjectNamePermissions.User.Create)]
+ public async Task CreateAsync(CreateUpdateUserDto input)
+ {
+ // 参数检查和验证逻辑可以在这里添加
+ if (string.IsNullOrEmpty(input.NickName))
+ {
+ throw new UserFriendlyException("昵称不能为空");
+ }
+ if (string.IsNullOrEmpty(input.Password))
+ {
+ throw new UserFriendlyException("密码不能为空");
+ }
+
+ // 使用UserManager创建用户
+ var user = await _userManager.CreateAsync(
+ input.NickName,
+ input.Password,
+ input.ContactInfo,
+ input.Position,
+ input.IsActive);
+
+ // 返回DTO对象
+ return await MapToUserDtoAsync(user);
+ }
+
+ ///
+ /// 更新用户
+ ///
+ [Authorize(ProjectNamePermissions.User.Update)]
+ public async Task UpdateAsync(Guid id, CreateUpdateUserDto input)
+ {
+ // 使用UserManager更新用户基本信息
+ var user = await _userManager.UpdateAsync(
+ id,
+ input.NickName,
+ input.Password,
+ input.ContactInfo,
+ input.Position,
+ input.IsActive);
+
+ // 返回DTO对象
+ return await MapToUserDtoAsync(user);
+ }
+
+ ///
+ /// 删除用户
+ ///
+ [Authorize(ProjectNamePermissions.User.Delete)]
+ public Task DeleteAsync(Guid id)
+ {
+ return _userManager.DeleteAsync(id);
+ }
+
+ ///
+ /// 获取用户
+ ///
+ [Authorize(ProjectNamePermissions.User.Default)]
+ public async Task GetAsync(Guid id)
+ {
+ var user = await _userManager.GetAsync(id);
+ return await MapToUserDtoAsync(user);
+ }
+
+ ///
+ /// 获取用户列表
+ ///
+ [Authorize(ProjectNamePermissions.User.Default)]
+ public async Task> GetListAsync(UserPagedAndSortedResultRequestDto input)
+ {
+ // 创建查询
+ var query = await CreateFilteredQueryAsync(input);
+
+ // 获取总记录数
+ var totalCount = await AsyncExecuter.CountAsync(query);
+
+ // 获取已排序和分页的查询结果
+ var users = await AsyncExecuter.ToListAsync(
+ query.OrderBy(input.Sorting ?? nameof(User.NickName))
+ .Skip(input.SkipCount)
+ .Take(input.MaxResultCount));
+
+ // 转换为DTO并返回
+ var userDtos = new List();
+ foreach (var user in users)
+ {
+ var userDto = ObjectMapper.Map(user);
+
+ // 填充角色信息
+ if (user.IdentityUser != null)
+ {
+ var roles = await _identityUserManager.GetRolesAsync(user.IdentityUser);
+ userDto.RoleNames = string.Join("、", roles);
+ userDto.IsActive = user.IdentityUser.LockoutEnd == null || user.IdentityUser.LockoutEnd < DateTimeOffset.Now;
+ }
+
+ userDtos.Add(userDto);
+ }
+
+ return new PagedResultDto(totalCount, userDtos);
+ }
+
+ ///
+ /// 修改用户密码
+ ///
+ [Authorize]
+ public async Task ChangePasswordAsync(Guid id, string currentPassword, string newPassword)
+ {
+ await _userManager.ChangePasswordAsync(id, currentPassword, newPassword);
+ }
+
+ ///
+ /// 重置用户密码(管理员操作)
+ ///
+ [Authorize(ProjectNamePermissions.User.Update)]
+ public async Task ResetPasswordAsync(Guid id, string newPassword)
+ {
+ await _userManager.ResetPasswordAsync(id, newPassword);
+ }
+
+ ///
+ /// 启用或禁用用户
+ ///
+ [Authorize(ProjectNamePermissions.User.Update)]
+ public async Task SetUserActiveStatusAsync(Guid id, bool isActive)
+ {
+ await _userManager.SetUserActiveStatusAsync(id, isActive);
+ }
+
+ ///
+ /// 创建基础查询,应用过滤条件
+ ///
+ protected async virtual Task> CreateFilteredQueryAsync(
+ UserPagedAndSortedResultRequestDto input)
+ {
+ // 获取基础查询,并加载相关实体
+ var query = await _userRepository.WithDetailsAsync(
+ x => x.IdentityUser);
+
+ // 应用过滤条件
+ return query
+ .WhereIf(!string.IsNullOrWhiteSpace(input.NickName),
+ x => x.NickName.Contains(input.NickName));
+ }
+
+ ///
+ /// 将User实体映射为UserDto,并填充权限信息
+ ///
+ private Task MapToUserDtoAsync(User user)
+ {
+ var userDto = ObjectMapper.Map(user);
+
+ // 设置用户状态
+ if (user.IdentityUser != null)
+ {
+ userDto.IsActive = user.IdentityUser.LockoutEnd == null || user.IdentityUser.LockoutEnd < DateTimeOffset.Now;
+ }
+
+ return Task.FromResult(userDto);
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj
index 160b74c5c..4158f0a15 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj
@@ -14,10 +14,14 @@
+
+
+
+
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs
index 401aa849e..6ca9ae998 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs
@@ -1,7 +1,9 @@
using LINGYUN.Abp.DataProtection;
+using LINGYUN.Abp.Identity;
using Microsoft.Extensions.DependencyInjection;
using PackageName.CompanyName.ProjectName.ObjectExtending;
using Volo.Abp.AutoMapper;
+using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending.Modularity;
@@ -12,6 +14,8 @@ namespace PackageName.CompanyName.ProjectName;
[DependsOn(
typeof(AbpAutoMapperModule),
typeof(AbpDataProtectionModule),
+ typeof(AbpIdentityDomainModule),
+ typeof(AbpBackgroundWorkersHangfireModule),
typeof(ProjectNameDomainSharedModule))]
public class ProjectNameDomainModule : AbpModule
{
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/IHaveTreeCode.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/IHaveTreeCode.cs
new file mode 100644
index 000000000..f1641be31
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/IHaveTreeCode.cs
@@ -0,0 +1,21 @@
+using System;
+using Volo.Abp.Auditing;
+
+namespace PackageName.CompanyName.ProjectName.TreeCodes
+{
+ ///
+ /// 定义具有树形编码的实体接口
+ ///
+ public interface IHaveTreeCode : IHasCreationTime
+ {
+ ///
+ /// 树形编码
+ ///
+ string TreeCode { get; set; }
+
+ ///
+ /// 父级Id
+ ///
+ Guid? ParentId { get; set; }
+ }
+}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/ITreeCodeGenerator.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/ITreeCodeGenerator.cs
new file mode 100644
index 000000000..0c3ce0754
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/ITreeCodeGenerator.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Entities;
+using Volo.Abp.Domain.Repositories;
+
+namespace PackageName.CompanyName.ProjectName.TreeCodes
+{
+ ///
+ /// 树形编码生成器接口
+ ///
+ public interface ITreeCodeGenerator
+ {
+ ///
+ /// 生成树形编码
+ ///
+ /// 实体类型
+ /// 仓储
+ /// 父级Id
+ /// 生成的树形编码
+ Task GenerateAsync(
+ IRepository repository,
+ Guid? parentId)
+ where TEntity : class, IEntity, IHaveTreeCode;
+
+ ///
+ /// 更新节点及其所有子节点的TreeCode
+ ///
+ Task UpdateTreeCodesAsync(
+ IRepository repository,
+ Guid entityId)
+ where TEntity : class, IEntity, IHaveTreeCode;
+ }
+}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/TreeCodeGenerator.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/TreeCodeGenerator.cs
new file mode 100644
index 000000000..2b08bc0f5
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/TreeCodes/TreeCodeGenerator.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Entities;
+using Volo.Abp.Domain.Repositories;
+
+namespace PackageName.CompanyName.ProjectName.TreeCodes
+{
+ ///
+ /// 树形编码生成器
+ ///
+ public class TreeCodeGenerator : ITreeCodeGenerator, ISingletonDependency
+ {
+ ///
+ /// 生成树形编码
+ ///
+ /// 实体类型
+ /// 仓储
+ /// 父级Id
+ /// 生成的树形编码
+ public async virtual Task GenerateAsync(
+ IRepository repository,
+ Guid? parentId)
+ where TEntity : class, IEntity, IHaveTreeCode
+ {
+ if (!parentId.HasValue)
+ {
+ // 生成根节点编码
+ return await GenerateRootCodeAsync(repository);
+ }
+ else
+ {
+ // 生成子节点编码
+ return await GenerateChildCodeAsync(repository, parentId.Value);
+ }
+ }
+
+ private async Task GenerateRootCodeAsync(
+ IRepository repository)
+ where TEntity : class, IEntity, IHaveTreeCode
+ {
+ var query = await repository.GetQueryableAsync();
+ query = query.Where(e => e.ParentId == null).OrderByDescending(e => e.CreationTime);
+ var maxCodeEntity = await repository.AsyncExecuter.FirstOrDefaultAsync(query);
+
+ if (maxCodeEntity == null)
+ {
+ return "0001";
+ }
+
+ int maxCode = int.Parse(maxCodeEntity.TreeCode.Split('.').LastOrDefault("0"));
+ return (maxCode + 1).ToString("D4");
+ }
+
+ private async Task GenerateChildCodeAsync(
+ IRepository repository,
+ Guid parentId)
+ where TEntity : class, IEntity, IHaveTreeCode
+ {
+ var parent = await repository.GetAsync(parentId);
+ if (parent == null)
+ {
+ throw new EntityNotFoundException(typeof(TEntity), parentId);
+ }
+
+ var query = await repository.GetQueryableAsync();
+ query = query.Where(e => e.ParentId == parentId).OrderByDescending(e => e.CreationTime);
+ var maxCodeEntity = await repository.AsyncExecuter.FirstOrDefaultAsync(query);
+
+ string newCode;
+ if (maxCodeEntity == null)
+ {
+ newCode = "0001";
+ }
+ else
+ {
+ int maxCode = int.Parse(maxCodeEntity.TreeCode.Split('.').Last());
+ newCode = (maxCode + 1).ToString("D4");
+ }
+
+ // 构建完整的TreeCode: 父TreeCode.新编码
+ return $"{parent.TreeCode}.{newCode}";
+ }
+
+ ///
+ /// 更新节点及其所有子节点的TreeCode
+ ///
+ public async virtual Task UpdateTreeCodesAsync(
+ IRepository repository,
+ Guid entityId)
+ where TEntity : class, IEntity, IHaveTreeCode
+ {
+ var entity = await repository.GetAsync(entityId);
+ var query = await repository.GetQueryableAsync();
+ var children = await repository.AsyncExecuter.ToListAsync(
+ query.Where(e => e.ParentId == entityId));
+
+ foreach (var child in children)
+ {
+ // 获取子节点编码(TreeCode最后一部分)或生成新编码
+ string childCode = child.TreeCode.Contains('.')
+ ? child.TreeCode.Split('.').Last()
+ : child.TreeCode;
+
+ child.TreeCode = $"{entity.TreeCode}.{childCode}";
+ await repository.UpdateAsync(child);
+
+ // 递归更新子节点
+ await UpdateTreeCodesAsync(repository, child.Id);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/IUserManager.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/IUserManager.cs
new file mode 100644
index 000000000..7d15b7792
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/IUserManager.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Services;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ ///
+ /// 用户管理服务接口
+ ///
+ public interface IUserManager : IDomainService
+ {
+ ///
+ /// 创建新用户
+ ///
+ Task CreateAsync(
+ string nickName,
+ string password,
+ string contactInfo = null,
+ string position = null,
+ bool isActive = true
+ );
+
+ ///
+ /// 更新用户信息
+ ///
+ Task UpdateAsync(
+ Guid id,
+ string nickName,
+ string password,
+ string contactInfo = null,
+ string position = null,
+ bool isActive = true);
+
+ ///
+ /// 删除用户
+ ///
+ Task DeleteAsync(Guid id);
+
+ ///
+ /// 修改用户密码
+ ///
+ Task ChangePasswordAsync(Guid id, string currentPassword, string newPassword);
+
+ ///
+ /// 重置用户密码
+ ///
+ Task ResetPasswordAsync(Guid id, string newPassword);
+
+ ///
+ /// 获取用户信息
+ ///
+ Task GetAsync(Guid id);
+
+ ///
+ /// 根据Identity用户ID获取用户
+ ///
+ Task FindByIdentityUserIdAsync(Guid identityUserId);
+
+ ///
+ /// 根据用户昵称查找用户
+ ///
+ Task FindByNickNameAsync(string nickName);
+
+ ///
+ /// 禁用或启用用户
+ ///
+ Task SetUserActiveStatusAsync(Guid id, bool isActive);
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/IUserRepository.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/IUserRepository.cs
new file mode 100644
index 000000000..75fac813b
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/IUserRepository.cs
@@ -0,0 +1,9 @@
+using System;
+using Volo.Abp.Domain.Repositories;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ public interface IUserRepository : IRepository
+ {
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/User.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/User.cs
new file mode 100644
index 000000000..855fb5116
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/User.cs
@@ -0,0 +1,59 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp.Domain.Entities.Auditing;
+using Volo.Abp.Identity;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ ///
+ /// 用户实体
+ ///
+ public class User : FullAuditedEntity
+ {
+ ///
+ /// 用户名称
+ ///
+ [MaxLength(50)]
+ public string NickName { get; set; }
+
+ ///
+ /// Identity用户Id
+ ///
+ public Guid IdentityUserId { get; set; }
+
+ ///
+ /// Identity用户
+ ///
+ public virtual IdentityUser IdentityUser { get; set; }
+
+ ///
+ /// 联系方式
+ ///
+ [MaxLength(50)]
+ public string ContactInfo { get; set; }
+
+ ///
+ /// 职位
+ ///
+ [MaxLength(50)]
+ public string Position { get; set; }
+
+ protected User()
+ {
+ }
+
+ public User(
+ Guid id,
+ string nickName,
+ Guid identityUserId,
+ string contactInfo = null,
+ string position = null
+ ) : base(id)
+ {
+ NickName = nickName;
+ IdentityUserId = identityUserId;
+ ContactInfo = contactInfo;
+ Position = position;
+ }
+ }
+}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/UserManager.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/UserManager.cs
new file mode 100644
index 000000000..bd616ba34
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/UserManager.cs
@@ -0,0 +1,406 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.Domain.Services;
+using Volo.Abp.Identity;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ ///
+ /// 用户管理服务,用于处理用户的CRUD操作
+ ///
+ public class UserManager : DomainService, IUserManager
+ {
+ private readonly IUserRepository _userRepository;
+ private readonly IdentityUserManager _identityUserManager;
+ private readonly ILogger _logger;
+
+ public UserManager(
+ IUserRepository userRepository,
+ IdentityUserManager identityUserManager,
+ ILogger logger)
+ {
+ _userRepository = userRepository;
+ _identityUserManager = identityUserManager;
+ _logger = logger;
+ }
+
+ ///
+ /// 创建新用户
+ ///
+ /// 用户昵称
+ /// 用户密码
+ /// 联系方式
+ /// 职位
+ /// 是否启用
+ /// 创建的用户实体
+ public async Task CreateAsync(
+ string nickName,
+ string password,
+ string contactInfo = null,
+ string position = null,
+ bool isActive = true)
+ {
+ // 参数验证
+ if (string.IsNullOrWhiteSpace(nickName))
+ {
+ throw new UserFriendlyException("用户名不能为空");
+ }
+
+ // 密码校验
+ if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
+ {
+ throw new UserFriendlyException("密码不能为空且长度不能少于6位");
+ }
+
+ // 检查用户昵称是否已存在
+ var existingUser = await _userRepository.FindAsync(u => u.NickName == nickName);
+ if (existingUser != null)
+ {
+ throw new UserFriendlyException($"昵称为 '{nickName}' 的用户已存在");
+ }
+
+ // 创建Identity用户
+ var identityUser = new IdentityUser(GuidGenerator.Create(), nickName, $"{nickName}@inspection.com");
+ var identityResult = await _identityUserManager.CreateAsync(identityUser, password);
+ if (!identityResult.Succeeded)
+ {
+ throw new UserFriendlyException("创建用户失败: " +
+ string.Join(", ", identityResult.Errors.Select(x => x.Description)));
+ }
+
+ // 设置用户状态
+ if (!isActive)
+ {
+ var lockoutResult = await _identityUserManager.SetLockoutEndDateAsync(identityUser, DateTimeOffset.MaxValue);
+ if (!lockoutResult.Succeeded)
+ {
+ throw new UserFriendlyException("设置用户状态失败: " +
+ string.Join(", ", lockoutResult.Errors.Select(e => e.Description)));
+ }
+ }
+
+ // 创建业务用户
+ var user = new User(
+ GuidGenerator.Create(),
+ nickName,
+ identityUser.Id,
+ contactInfo,
+ position
+ );
+
+ // 保存用户
+ await _userRepository.InsertAsync(user, true);
+
+ _logger.LogInformation($"创建了新用户:{nickName},ID:{user.Id}");
+
+ return user;
+ }
+
+ ///
+ /// 更新用户信息
+ ///
+ /// 用户ID
+ /// 用户昵称
+ /// 用户密码(可选,如不修改则传入null)
+ /// 联系方式
+ /// 职位
+ /// 是否启用
+ /// 更新后的用户实体
+ public async Task UpdateAsync(
+ Guid id,
+ string nickName,
+ string password,
+ string contactInfo = null,
+ string position = null,
+ bool isActive = true)
+ {
+ // 最大重试次数
+ const int maxRetries = 3;
+ int retryCount = 0;
+
+ while (true)
+ {
+ try
+ {
+ // 每次尝试时重新获取最新的用户数据
+ var user = await _userRepository.GetAsync(id, false);
+ if (user == null)
+ {
+ throw new UserFriendlyException("用户不存在");
+ }
+
+ // 检查用户昵称是否已被其他用户使用
+ var existingUser = await _userRepository.FindAsync(u => u.NickName == nickName && u.Id != id);
+ if (existingUser != null)
+ {
+ throw new UserFriendlyException($"昵称为 '{nickName}' 的用户已存在");
+ }
+
+ // 更新Identity用户
+ var identityUser = await _identityUserManager.FindByIdAsync(user.IdentityUserId.ToString());
+ if (identityUser == null)
+ {
+ throw new UserFriendlyException("Identity用户不存在");
+ }
+
+ // 更新用户名
+ var usernameResult = await _identityUserManager.SetUserNameAsync(identityUser, nickName);
+ if (!usernameResult.Succeeded)
+ {
+ throw new UserFriendlyException("更新用户名失败: " +
+ string.Join(", ", usernameResult.Errors.Select(e => e.Description)));
+ }
+
+ // 更新电子邮件
+ var emailResult = await _identityUserManager.SetEmailAsync(identityUser, $"{nickName}@inspection.com");
+ if (!emailResult.Succeeded)
+ {
+ throw new UserFriendlyException("更新电子邮件失败: " +
+ string.Join(", ", emailResult.Errors.Select(e => e.Description)));
+ }
+
+ // 如果提供了新密码,则更新密码
+ if (!string.IsNullOrEmpty(password))
+ {
+ // 移除当前密码
+ await _identityUserManager.RemovePasswordAsync(identityUser);
+ // 设置新密码
+ var passwordResult = await _identityUserManager.AddPasswordAsync(identityUser, password);
+ if (!passwordResult.Succeeded)
+ {
+ throw new UserFriendlyException("更新密码失败: " +
+ string.Join(", ", passwordResult.Errors.Select(e => e.Description)));
+ }
+ }
+
+ // 设置用户状态
+ await SetUserActiveStatusAsync(id, isActive);
+
+ // 更新用户信息
+ user.NickName = nickName;
+ user.ContactInfo = contactInfo;
+ user.Position = position;
+
+ // 保存更新
+ await _userRepository.UpdateAsync(user, true);
+
+ _logger.LogInformation($"更新了用户信息:{nickName},ID:{user.Id}");
+
+ return user;
+ }
+ catch (Volo.Abp.Data.AbpDbConcurrencyException ex)
+ {
+ // 增加重试计数
+ retryCount++;
+
+ // 如果达到最大重试次数,则抛出用户友好的异常
+ if (retryCount >= maxRetries)
+ {
+ throw new UserFriendlyException(
+ "更新用户信息失败:数据已被其他用户修改。请刷新页面后重试。",
+ "409", ex.Message,
+ ex);
+ }
+
+ // 短暂延迟后重试
+ await Task.Delay(100 * retryCount); // 逐步增加延迟时间
+
+ // 记录重试信息
+ _logger.LogWarning($"检测到用户[{id}]更新时发生并发冲突,正在进行第{retryCount}次重试...");
+ }
+ }
+ }
+
+ ///
+ /// 删除用户
+ ///
+ /// 用户ID
+ /// 操作任务
+ public async Task DeleteAsync(Guid id)
+ {
+ // 获取用户
+ var user = await _userRepository.GetAsync(id);
+ if (user == null)
+ {
+ throw new UserFriendlyException("用户不存在");
+ }
+
+ // 删除Identity用户
+ var identityUser = await _identityUserManager.FindByIdAsync(user.IdentityUserId.ToString());
+ if (identityUser != null)
+ {
+ var result = await _identityUserManager.DeleteAsync(identityUser);
+ if (!result.Succeeded)
+ {
+ throw new UserFriendlyException("删除Identity用户失败: " +
+ string.Join(", ", result.Errors.Select(e => e.Description)));
+ }
+ }
+
+ // 删除用户
+ await _userRepository.DeleteAsync(user);
+
+ _logger.LogInformation($"删除了用户,ID:{id}");
+ }
+
+ ///
+ /// 修改用户密码
+ ///
+ /// 用户ID
+ /// 当前密码
+ /// 新密码
+ /// 操作结果
+ public async Task ChangePasswordAsync(Guid id, string currentPassword, string newPassword)
+ {
+ if (string.IsNullOrEmpty(newPassword) || newPassword.Length < 6)
+ {
+ throw new UserFriendlyException("新密码不能为空且长度不能少于6位");
+ }
+
+ // 获取用户
+ var user = await _userRepository.GetAsync(id);
+ if (user == null)
+ {
+ throw new UserFriendlyException("用户不存在");
+ }
+
+ // 获取Identity用户
+ var identityUser = await _identityUserManager.FindByIdAsync(user.IdentityUserId.ToString());
+ if (identityUser == null)
+ {
+ throw new UserFriendlyException("Identity用户不存在");
+ }
+
+ // 修改密码
+ var result = await _identityUserManager.ChangePasswordAsync(identityUser, currentPassword, newPassword);
+ if (!result.Succeeded)
+ {
+ throw new UserFriendlyException("修改密码失败: " +
+ string.Join(", ", result.Errors.Select(e => e.Description)));
+ }
+
+ _logger.LogInformation($"用户[{user.NickName}]成功修改了密码");
+ }
+
+ ///
+ /// 重置用户密码(管理员操作)
+ ///
+ /// 用户ID
+ /// 新密码
+ /// 操作结果
+ public async Task ResetPasswordAsync(Guid id, string newPassword)
+ {
+ if (string.IsNullOrEmpty(newPassword) || newPassword.Length < 6)
+ {
+ throw new UserFriendlyException("新密码不能为空且长度不能少于6位");
+ }
+
+ // 获取用户
+ var user = await _userRepository.GetAsync(id);
+ if (user == null)
+ {
+ throw new UserFriendlyException("用户不存在");
+ }
+
+ // 获取Identity用户
+ var identityUser = await _identityUserManager.FindByIdAsync(user.IdentityUserId.ToString());
+ if (identityUser == null)
+ {
+ throw new UserFriendlyException("Identity用户不存在");
+ }
+
+ // 生成重置令牌
+ var token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityUser);
+
+ // 重置密码
+ var result = await _identityUserManager.ResetPasswordAsync(identityUser, token, newPassword);
+ if (!result.Succeeded)
+ {
+ throw new UserFriendlyException("重置密码失败: " +
+ string.Join(", ", result.Errors.Select(e => e.Description)));
+ }
+
+ _logger.LogInformation($"管理员重置了用户[{user.NickName}]的密码");
+ }
+
+ ///
+ /// 获取用户信息
+ ///
+ /// 用户ID
+ /// 用户实体
+ public async Task GetAsync(Guid id)
+ {
+ return await _userRepository.GetAsync(id);
+ }
+
+ ///
+ /// 根据Identity用户ID获取用户
+ ///
+ /// Identity用户ID
+ /// 用户实体,如果不存在则返回null
+ public async Task FindByIdentityUserIdAsync(Guid identityUserId)
+ {
+ return await _userRepository.FindAsync(u => u.IdentityUserId == identityUserId);
+ }
+
+ ///
+ /// 根据用户昵称查找用户
+ ///
+ /// 用户昵称
+ /// 用户实体,如果不存在则返回null
+ public async Task FindByNickNameAsync(string nickName)
+ {
+ return await _userRepository.FindAsync(u => u.NickName == nickName);
+ }
+
+ ///
+ /// 禁用或启用用户
+ ///
+ /// 用户ID
+ /// 是否启用
+ /// 操作任务
+ public async Task SetUserActiveStatusAsync(Guid id, bool isActive)
+ {
+ // 获取用户
+ var user = await _userRepository.GetAsync(id);
+ if (user == null)
+ {
+ throw new UserFriendlyException("用户不存在");
+ }
+
+ // 获取Identity用户
+ var identityUser = await _identityUserManager.FindByIdAsync(user.IdentityUserId.ToString());
+ if (identityUser == null)
+ {
+ throw new UserFriendlyException("Identity用户不存在");
+ }
+
+ // 设置用户状态
+ if (isActive)
+ {
+ // 启用用户
+ var result = await _identityUserManager.SetLockoutEndDateAsync(identityUser, null);
+ if (!result.Succeeded)
+ {
+ throw new UserFriendlyException($"启用用户失败: " +
+ string.Join(", ", result.Errors.Select(e => e.Description)));
+ }
+ }
+ else
+ {
+ // 禁用用户(设置永久锁定)
+ var result = await _identityUserManager.SetLockoutEndDateAsync(identityUser, DateTimeOffset.MaxValue);
+ if (!result.Succeeded)
+ {
+ throw new UserFriendlyException($"禁用用户失败: " +
+ string.Join(", ", result.Errors.Select(e => e.Description)));
+ }
+ }
+
+ _logger.LogInformation($"已{(isActive ? "启用" : "禁用")}用户[{user.NickName}]");
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj
index a7904db7d..ada1145a0 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj
@@ -28,6 +28,7 @@
+
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs
index c39c2923e..f3749036f 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs
@@ -1,4 +1,6 @@
-using Volo.Abp.Data;
+using Microsoft.EntityFrameworkCore;
+using PackageName.CompanyName.ProjectName.Users;
+using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore;
@@ -6,4 +8,5 @@ namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore;
[ConnectionStringName(ProjectNameDbProperties.ConnectionStringName)]
public interface IProjectNameDbContext : IEfCoreDbContext
{
+ DbSet Users { get; }
}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs
index 5cb2c015b..988271a16 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs
@@ -1,12 +1,16 @@
using LINGYUN.Abp.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
+using PackageName.CompanyName.ProjectName.Users;
using Volo.Abp.Data;
+using Volo.Abp.Identity.EntityFrameworkCore;
namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore;
[ConnectionStringName(ProjectNameDbProperties.ConnectionStringName)]
public class ProjectNameDbContext : AbpDataProtectionDbContext, IProjectNameDbContext
{
+ public virtual DbSet Users { get; set; }
+
public ProjectNameDbContext(
DbContextOptions options) : base(options)
{
@@ -14,8 +18,9 @@ public class ProjectNameDbContext : AbpDataProtectionDbContext(b =>
+ {
+ b.ToTable(ProjectNameDbProperties.DbTablePrefix + "Users", ProjectNameDbProperties.DbSchema);
+ b.ConfigureByConvention();
+ b.Property(x => x.NickName).HasComment("用户名称");
+ b.Property(x => x.IdentityUserId).HasComment("Identity用户Id");
+
+ // 用户与IdentityUser的关系(一对一)
+ b.HasOne(x => x.IdentityUser)
+ .WithOne()
+ .HasForeignKey(u => u.IdentityUserId)
+ .IsRequired()
+ .OnDelete(DeleteBehavior.Cascade);
+ });
}
}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs
index b18f43183..aac635d62 100644
--- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs
@@ -2,7 +2,9 @@
using LINGYUN.Abp.DataProtection.EntityFrameworkCore;
using LINGYUN.Abp.Saas.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
+using PackageName.CompanyName.ProjectName.Users;
using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Modularity;
#if MySQL
using Volo.Abp.EntityFrameworkCore.MySQL;
@@ -38,6 +40,7 @@ namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore;
#elif PostgreSql
typeof(AbpEntityFrameworkCorePostgreSqlModule),
#endif
+ typeof(AbpIdentityEntityFrameworkCoreModule),
typeof(AbpSaasEntityFrameworkCoreModule))]
public class ProjectNameEntityFrameworkCoreModule : AbpModule
{
@@ -70,7 +73,8 @@ public class ProjectNameEntityFrameworkCoreModule : AbpModule
context.Services.AddAbpDbContext(options =>
{
- options.AddDefaultRepositories();
+ options.AddDefaultRepositories(true);
+ options.AddRepository();
});
}
}
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/Users/UserRepository.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/Users/UserRepository.cs
new file mode 100644
index 000000000..72d1575bf
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/Users/UserRepository.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore;
+using PackageName.CompanyName.ProjectName.EntityFrameworkCore;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ public class UserRepository : EfCoreRepository, IUserRepository
+ {
+ public UserRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider)
+ {
+ }
+
+ public override async Task> WithDetailsAsync()
+ {
+ return (await GetDbSetAsync()).Include(x => x.IdentityUser);
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/Users/UserController.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/Users/UserController.cs
new file mode 100644
index 000000000..85d3f28f9
--- /dev/null
+++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/Users/UserController.cs
@@ -0,0 +1,134 @@
+using Microsoft.AspNetCore.Mvc;
+using PackageName.CompanyName.ProjectName.Users.Dtos;
+using System;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.Application.Dtos;
+
+namespace PackageName.CompanyName.ProjectName.Users
+{
+ ///
+ /// 用户管理控制器
+ ///
+ [RemoteService]
+ [Route("api/app/user")]
+ public class UserController : ProjectNameControllerBase
+ {
+ private readonly IUserAppService _userAppService;
+
+ public UserController(IUserAppService userAppService)
+ {
+ _userAppService = userAppService;
+ }
+
+ ///
+ /// 创建用户
+ ///
+ [HttpPost]
+ public async Task CreateAsync(CreateUpdateUserDto input)
+ {
+ return await _userAppService.CreateAsync(input);
+ }
+
+ ///
+ /// 更新用户
+ ///
+ [HttpPut("{id}")]
+ public async Task UpdateAsync(Guid id, CreateUpdateUserDto input)
+ {
+ return await _userAppService.UpdateAsync(id, input);
+ }
+
+ ///
+ /// 删除用户
+ ///
+ [HttpDelete("{id}")]
+ public async Task DeleteAsync(Guid id)
+ {
+ await _userAppService.DeleteAsync(id);
+ }
+
+ ///
+ /// 获取用户
+ ///
+ [HttpGet("{id}")]
+ public async Task GetAsync(Guid id)
+ {
+ return await _userAppService.GetAsync(id);
+ }
+
+ ///
+ /// 获取用户列表
+ ///
+ [HttpGet]
+ public async Task> GetListAsync(UserPagedAndSortedResultRequestDto input)
+ {
+ return await _userAppService.GetListAsync(input);
+ }
+
+ ///
+ /// 修改用户密码
+ ///
+ [HttpPost("{id}/change-password")]
+ public async Task ChangePasswordAsync(Guid id, [FromBody] ChangePasswordRequest request)
+ {
+ await _userAppService.ChangePasswordAsync(id, request.CurrentPassword, request.NewPassword);
+ }
+
+ ///
+ /// 重置用户密码(管理员操作)
+ ///
+ [HttpPost("{id}/reset-password")]
+ public async Task ResetPasswordAsync(Guid id, [FromBody] ResetPasswordRequest request)
+ {
+ await _userAppService.ResetPasswordAsync(id, request.NewPassword);
+ }
+
+ ///
+ /// 启用或禁用用户
+ ///
+ [HttpPost("{id}/set-active")]
+ public async Task SetUserActiveStatusAsync(Guid id, [FromBody] SetUserActiveRequest request)
+ {
+ await _userAppService.SetUserActiveStatusAsync(id, request.IsActive);
+ }
+ }
+
+ ///
+ /// 修改密码请求
+ ///
+ public class ChangePasswordRequest
+ {
+ ///
+ /// 当前密码
+ ///
+ public string CurrentPassword { get; set; }
+
+ ///
+ /// 新密码
+ ///
+ public string NewPassword { get; set; }
+ }
+
+ ///
+ /// 重置密码请求
+ ///
+ public class ResetPasswordRequest
+ {
+ ///
+ /// 新密码
+ ///
+ public string NewPassword { get; set; }
+ }
+
+ ///
+ /// 设置用户状态请求
+ ///
+ public class SetUserActiveRequest
+ {
+ ///
+ /// 是否启用
+ ///
+ public bool IsActive { get; set; }
+ }
+}