diff --git a/YD_Event.Application/Menu/SysMenuService.cs b/YD_Event.Application/Menu/SysMenuService.cs index d2c33c8..814586d 100644 --- a/YD_Event.Application/Menu/SysMenuService.cs +++ b/YD_Event.Application/Menu/SysMenuService.cs @@ -1,7 +1,5 @@ using YD_Event.Application.Auth; using YD_Event.Application.Menu.Dtos; -using YD_Event.Core.Options; -using Microsoft.Extensions.Options; using Microsoft.OpenApi; namespace YD_Event.Application.Menu; @@ -13,20 +11,14 @@ public class SysMenuService : BaseService, ITransient private readonly ISqlSugarRepository _sysMenuRepository; private readonly IEasyCachingProvider _easyCachingProvider; private readonly AuthManager _authManager; - private readonly ISqlSugarRepository _sysUserRoleRepository; - private readonly IOptionsMonitor _roleOptionsMonitor; public SysMenuService(ISqlSugarRepository sysMenuRepository, IEasyCachingProvider easyCachingProvider, - AuthManager authManager, - ISqlSugarRepository sysUserRoleRepository, - IOptionsMonitor roleOptionsMonitor) : base(sysMenuRepository) + AuthManager authManager) : base(sysMenuRepository) { _sysMenuRepository = sysMenuRepository; _easyCachingProvider = easyCachingProvider; _authManager = authManager; - _sysUserRoleRepository = sysUserRoleRepository; - _roleOptionsMonitor = roleOptionsMonitor; } /// @@ -185,23 +177,8 @@ public class SysMenuService : BaseService, ITransient [HttpGet] public async Task> TreeSelect() { - var list = await _sysMenuRepository.AsQueryable() - .OrderBy(x => x.Sort) - .OrderBy(x => x.Id) - .WithCache() - .ToTreeAsync(x => x.Children, x => x.ParentId, null); - - // 检查当前用户是否是管理员,如果不是管理员则过滤指定菜单 - var isAdmin = await IsCurrentUserAdminAsync(); - if (!isAdmin) - { - var hiddenMenuIds = _roleOptionsMonitor.CurrentValue.HiddenMenuIds ?? new List(); - if (hiddenMenuIds.Any()) - { - FilterMenus(list, hiddenMenuIds); - } - } - + // 按当前用户已分配的菜单返回,避免越权看到未授权菜单(如客户端门户菜单) + var list = await QueryGrantedMenusAsync(includeDisabled: true); return list.Adapt>(); } @@ -269,86 +246,72 @@ public class SysMenuService : BaseService, ITransient } /// - /// 检查当前登录用户是否是管理员 + /// 检查当前登录用户是否拥有菜单管理的全量数据权限 /// /// private async Task IsCurrentUserAdminAsync() { - var userId = _authManager.UserId; - // 首先检查是否是超级管理员 + return await HasPermissionAsync(PermissionCodeConst.MenuManage); + } + + /// + /// 查询当前用户已被授权的菜单树(超级管理员返回全部) + /// + /// 是否包含已禁用的菜单,供菜单/角色配置页使用 + private async Task> QueryGrantedMenusAsync(bool includeDisabled) + { if (_authManager.IsSuperAdmin) { - return true; + return await _sysMenuRepository.AsQueryable() + .OrderBy(x => x.Sort) + .OrderBy(x => x.Id) + .ToTreeAsync(x => x.Children, x => x.ParentId, null); } - // 检查用户是否拥有管理员角色 - return await _sysUserRoleRepository.IsAnyAsync(x => x.UserId == userId && x.RoleId == _roleOptionsMonitor.CurrentValue.AdminRoleId); + + long userId = _authManager.UserId; + return await _sysMenuRepository.AsQueryable() + .InnerJoin((menu, roleMenu) => menu.Id == roleMenu.MenuId) + .InnerJoin((menu, roleMenu, role) => roleMenu.RoleId == role.Id) + .InnerJoin((menu, roleMenu, role, userRole) => role.Id == userRole.RoleId) + .Where((menu, roleMenu, role, userRole) => role.Status == AvailabilityStatus.Enable && + userRole.UserId == userId) + .WhereIF(!includeDisabled, (menu, roleMenu, role, userRole) => menu.Status == AvailabilityStatus.Enable) + .OrderBy(menu => menu.Sort) + .OrderBy(menu => menu.Id) + .Select(menu => menu) + .ToTreeAsync(x => x.Children, x => x.ParentId, null); } /// - /// 过滤菜单,移除配置中指定的菜单ID及其子菜单 - /// - /// 菜单列表 - /// 需要隐藏的菜单ID列表 - private void FilterMenus(List menus, List hiddenMenuIds) - { - for (int i = menus.Count - 1; i >= 0; i--) - { - var menu = menus[i]; - // 如果当前菜单在隐藏列表中,移除它 - if (hiddenMenuIds.Contains(menu.Id)) - { - menus.RemoveAt(i); - continue; - } - // 递归处理子菜单 - if (menu.Children != null && menu.Children.Any()) - { - FilterMenus(menu.Children, hiddenMenuIds); - } - } - } - - /// - /// 菜单按钮树 - 仅在角色管理页面使用此接口时应用隐藏规则 + /// 菜单按钮树 - 角色分配菜单时使用 /// /// [DisplayName("菜单按钮树")] [HttpGet] public async Task> TreeMenuButton() { - long userId = _authManager.UserId; - List menus; - if (_authManager.IsSuperAdmin)//超级管理员 - { - menus = await _sysMenuRepository.AsQueryable().ToTreeAsync(x => x.Children, x => x.ParentId, null); - } - else - { - menus = await _sysMenuRepository.AsQueryable() - .InnerJoin((menu, roleMenu) => menu.Id == roleMenu.MenuId) - .InnerJoin((menu, roleMenu, role) => roleMenu.RoleId == role.Id) - .InnerJoin((menu, roleMenu, role, userRole) => role.Id == userRole.RoleId) - .Where((menu, roleMenu, role, userRole) => menu.Status == AvailabilityStatus.Enable && - role.Status == AvailabilityStatus.Enable && - userRole.UserId == userId) - .Select(menu => menu) - .ToTreeAsync(x => x.Children, x => x.ParentId, null); - } - - // 检查当前用户是否是管理员,如果不是管理员则过滤指定菜单 - var isAdmin = await IsCurrentUserAdminAsync(); - if (!isAdmin) - { - var hiddenMenuIds = _roleOptionsMonitor.CurrentValue.HiddenMenuIds ?? new List(); - if (hiddenMenuIds.Any()) - { - FilterMenus(menus, hiddenMenuIds); - } - } - + var menus = await QueryGrantedMenusAsync(includeDisabled: false); return menus.Adapt>(); } + /// + /// 严格权限校验 + /// + /// + /// 与 的区别:权限码未配置时返回 false 而不是放行。 + /// 用于数据范围/管理员判断,避免迁移脚本未执行时把全量数据权限误放给所有用户。 + /// + /// 权限标识 + /// + [NonAction] + public async Task HasPermissionAsync(string code) + { + if (_authManager.IsSuperAdmin) return true; + + var authCodes = await GetAuthPermissionCodeList(_authManager.UserId); + return authCodes.Any(x => string.Equals(x, code, StringComparison.OrdinalIgnoreCase)); + } + /// /// 校验权限 /// diff --git a/YD_Event.Application/Role/SysRoleService.cs b/YD_Event.Application/Role/SysRoleService.cs index 5203724..6cb72ef 100644 --- a/YD_Event.Application/Role/SysRoleService.cs +++ b/YD_Event.Application/Role/SysRoleService.cs @@ -1,8 +1,7 @@ using YD_Event.Application.Auth; +using YD_Event.Application.Menu; using YD_Event.Application.Role.Dtos; -using YD_Event.Core.Options; using YD_Event.Core.Shared; -using Microsoft.Extensions.Options; namespace YD_Event.Application.Role; @@ -17,7 +16,7 @@ public class SysRoleService : BaseService, ITransient private readonly IEasyCachingProvider _easyCachingProvider; private readonly IIdGenerator _idGenerator; private readonly AuthManager _authManager; - private readonly IOptionsMonitor _roleOptionsMonitor; + private readonly SysMenuService _sysMenuService; public SysRoleService(ISqlSugarRepository sysRoleRepository, ISqlSugarRepository sysRoleMenuRepository, @@ -25,7 +24,7 @@ public class SysRoleService : BaseService, ITransient IEasyCachingProvider easyCachingProvider, IIdGenerator idGenerator, AuthManager authManager, - IOptionsMonitor roleOptionsMonitor) : base(sysRoleRepository) + SysMenuService sysMenuService) : base(sysRoleRepository) { _sysRoleRepository = sysRoleRepository; _sysRoleMenuRepository = sysRoleMenuRepository; @@ -33,7 +32,7 @@ public class SysRoleService : BaseService, ITransient _easyCachingProvider = easyCachingProvider; _idGenerator = idGenerator; _authManager = authManager; - _roleOptionsMonitor = roleOptionsMonitor; + _sysMenuService = sysMenuService; } /// diff --git a/YD_Event.Application/Unit/Dtos/UnitAccountDtos.cs b/YD_Event.Application/Unit/Dtos/UnitAccountDtos.cs new file mode 100644 index 0000000..203a269 --- /dev/null +++ b/YD_Event.Application/Unit/Dtos/UnitAccountDtos.cs @@ -0,0 +1,168 @@ +namespace YD_Event.Application.Unit.Dtos; + +/// +/// 单位账号查询输入 +/// +public class UnitAccountQueryInput : Pagination +{ + /// + /// 所属平台(0-报名平台 1-打分系统) + /// + [Required(ErrorMessage = "请选择所属平台")] + public UnitAccountPlatform Platform { get; set; } + + /// + /// 单位名称 + /// + public string? UnitName { get; set; } + + /// + /// 账号 + /// + public string? Account { get; set; } + + /// + /// 生成开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 生成结束时间 + /// + public DateTime? EndTime { get; set; } +} + +/// +/// 添加单位账号输入 +/// +public class AddUnitAccountInput +{ + /// + /// 所属平台(0-报名平台 1-打分系统) + /// + [Required(ErrorMessage = "请选择所属平台")] + public UnitAccountPlatform Platform { get; set; } + + /// + /// 单位名称 + /// + [Required(ErrorMessage = "单位名称为必填项")] + [MaxLength(128, ErrorMessage = "单位名称限制128个字符内")] + public string UnitName { get; set; } + + /// + /// 联系人 + /// + [Required(ErrorMessage = "联系人为必填项")] + [MaxLength(16, ErrorMessage = "联系人限制16个字符内")] + public string ContactName { get; set; } + + /// + /// 账号 + /// + [Required(ErrorMessage = "账号为必填项")] + [MaxLength(32, ErrorMessage = "账号限制32个字符内")] + public string Account { get; set; } + + /// + /// 密码 + /// + [Required(ErrorMessage = "密码为必填项")] + [MinLength(6, ErrorMessage = "密码长度至少6位")] + [MaxLength(32, ErrorMessage = "密码长度最多32位")] + public string Password { get; set; } +} + +/// +/// 更新单位账号输入(不含密码,密码走重置接口) +/// +public class UpdateUnitAccountInput +{ + /// + /// 主键 + /// + [Required(ErrorMessage = "Id不能为空")] + public long Id { get; set; } + + /// + /// 单位名称 + /// + [Required(ErrorMessage = "单位名称为必填项")] + [MaxLength(128, ErrorMessage = "单位名称限制128个字符内")] + public string UnitName { get; set; } + + /// + /// 联系人 + /// + [Required(ErrorMessage = "联系人为必填项")] + [MaxLength(16, ErrorMessage = "联系人限制16个字符内")] + public string ContactName { get; set; } + + /// + /// 账号 + /// + [Required(ErrorMessage = "账号为必填项")] + [MaxLength(32, ErrorMessage = "账号限制32个字符内")] + public string Account { get; set; } +} + +/// +/// 重置单位账号密码输入 +/// +public class ResetUnitAccountPasswordInput +{ + /// + /// 主键 + /// + [Required(ErrorMessage = "Id不能为空")] + public long Id { get; set; } + + /// + /// 新密码 + /// + [Required(ErrorMessage = "密码为必填项")] + [MinLength(6, ErrorMessage = "密码长度至少6位")] + [MaxLength(32, ErrorMessage = "密码长度最多32位")] + public string Password { get; set; } +} + +/// +/// 单位账号输出 +/// +public class UnitAccountOutput +{ + /// + /// 主键 + /// + public long Id { get; set; } + + /// + /// 单位名称 + /// + public string? UnitName { get; set; } + + /// + /// 联系人 + /// + public string ContactName { get; set; } + + /// + /// 账号 + /// + public string Account { get; set; } + + /// + /// 所属平台 + /// + public UnitAccountPlatform? Platform { get; set; } + + /// + /// 状态(0-启用 1-禁用) + /// + public AvailabilityStatus Status { get; set; } + + /// + /// 生成时间 + /// + public DateTime CreatedTime { get; set; } +} diff --git a/YD_Event.Application/Unit/UnitAccountService.cs b/YD_Event.Application/Unit/UnitAccountService.cs new file mode 100644 index 0000000..3a57874 --- /dev/null +++ b/YD_Event.Application/Unit/UnitAccountService.cs @@ -0,0 +1,213 @@ +using Microsoft.Extensions.Options; +using YD_Event.Application.Unit.Dtos; +using YD_Event.Core.Options; + +namespace YD_Event.Application.Unit; + +/// +/// 单位管理(报名平台 / 打分系统账号) +/// +public class UnitAccountService : BaseService, ITransient +{ + private readonly ISqlSugarRepository _repository; + private readonly ISqlSugarRepository _userRoleRepository; + private readonly IIdGenerator _idGenerator; + private readonly IEasyCachingProvider _easyCachingProvider; + private readonly IOptionsMonitor _roleOptionsMonitor; + + public UnitAccountService(ISqlSugarRepository repository, + ISqlSugarRepository userRoleRepository, + IIdGenerator idGenerator, + IEasyCachingProvider easyCachingProvider, + IOptionsMonitor roleOptionsMonitor) : base(repository) + { + _repository = repository; + _userRoleRepository = userRoleRepository; + _idGenerator = idGenerator; + _easyCachingProvider = easyCachingProvider; + _roleOptionsMonitor = roleOptionsMonitor; + } + + /// + /// 单位账号分页查询 + /// + [DisplayName("单位账号分页查询")] + [HttpGet("page")] + public async Task> Page([FromQuery] UnitAccountQueryInput input) + { + return await _repository.AsQueryable() + .Where(x => x.UnitPlatform == input.Platform) + .WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), x => x.UnitName.Contains(input.UnitName)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Account), x => x.Account.Contains(input.Account)) + .WhereIF(input.StartTime.HasValue, x => x.CreatedTime >= input.StartTime.Value.Date) + // 结束日期按当天 23:59:59 处理,避免只传日期时漏掉当天的数据 + .WhereIF(input.EndTime.HasValue, x => x.CreatedTime < input.EndTime.Value.Date.AddDays(1)) + .OrderByDescending(x => x.CreatedTime) + .Select(x => new UnitAccountOutput + { + Id = x.Id, + UnitName = x.UnitName, + ContactName = x.Name, + Account = x.Account, + Platform = x.UnitPlatform, + Status = x.Status, + CreatedTime = x.CreatedTime + }) + .ToPagedListAsync(input); + } + + /// + /// 单位账号详情 + /// + [DisplayName("单位账号详情")] + [HttpGet("detail")] + public async Task Detail([FromQuery] long id) + { + var user = await GetUnitAccountAsync(id); + + return new UnitAccountOutput + { + Id = user.Id, + UnitName = user.UnitName, + ContactName = user.Name, + Account = user.Account, + Platform = user.UnitPlatform, + Status = user.Status, + CreatedTime = user.CreatedTime + }; + } + + /// + /// 添加单位账号 + /// + [DisplayName("添加单位账号")] + [HttpPost("add")] + [UnitOfWork] + public async Task Add(AddUnitAccountInput input) + { + // 账号是全局登录标识,需在整张 SysUser 表内查重 + if (await _repository.IsAnyAsync(x => x.Account == input.Account)) + { + throw Oops.Bah($"账号 {input.Account} 已存在"); + } + + var user = new SysUser + { + Id = _idGenerator.NextId(), + Account = input.Account, + Name = input.ContactName, + UnitName = input.UnitName, + UnitPlatform = input.Platform, + Gender = Gender.Unknown, + OrgId = 0, + Status = AvailabilityStatus.Enable, + DeleteMark = false, + CreatedTime = DateTime.Now + }; + // 加密盐依赖主键,必须在 Id 生成之后计算 + user.Password = MD5Encryption.Encrypt($"{_idGenerator.Encode(user.Id)}{input.Password}"); + + await _repository.InsertAsync(user); + + await _userRoleRepository.InsertAsync(new SysUserRole + { + Id = _idGenerator.NextId(), + UserId = user.Id, + RoleId = GetRoleId(input.Platform), + CreatedTime = DateTime.Now, + DeleteMark = false + }); + + await ClearCache(); + } + + /// + /// 更新单位账号 + /// + [DisplayName("更新单位账号")] + [HttpPut("edit")] + [UnitOfWork] + public async Task Update(UpdateUnitAccountInput input) + { + var user = await GetUnitAccountAsync(input.Id); + + if (await _repository.IsAnyAsync(x => x.Account == input.Account && x.Id != input.Id)) + { + throw Oops.Bah($"账号 {input.Account} 已被使用"); + } + + user.Account = input.Account; + user.Name = input.ContactName; + user.UnitName = input.UnitName; + + await _repository.UpdateAsync(user); + await ClearCache(); + } + + /// + /// 重置单位账号密码 + /// + [DisplayName("重置单位账号密码")] + [HttpPatch("resetPassword")] + public async Task ResetPassword(ResetUnitAccountPasswordInput input) + { + await GetUnitAccountAsync(input.Id); + + string password = MD5Encryption.Encrypt($"{_idGenerator.Encode(input.Id)}{input.Password}"); + await _repository.UpdateAsync(x => new SysUser + { + Password = password + }, x => x.Id == input.Id); + } + + /// + /// 启用/禁用单位账号(禁用后禁止登录) + /// + [DisplayName("启用禁用单位账号")] + [HttpPatch("setStatus")] + public override async Task SetStatus(AvailabilityDto dto) + { + await GetUnitAccountAsync(dto.Id); + await base.SetStatus(dto); + } + + /// + /// 删除单位账号 + /// + [DisplayName("删除单位账号")] + [HttpDelete("delete")] + [UnitOfWork] + public override async Task Delete(KeyDto dto) + { + await GetUnitAccountAsync(dto.Id); + await base.Delete(dto); + await _userRoleRepository.SoftDeleteAsync(x => x.UserId == dto.Id); + } + + /// + /// 取单位账号,拦截对普通系统用户的越权操作 + /// + private async Task GetUnitAccountAsync(long id) + { + var user = await _repository.GetByIdAsync(id); + if (user == null) throw Oops.Bah("单位账号不存在"); + if (user.UnitPlatform == null) throw Oops.Bah("该账号不是单位账号,请在系统用户中管理"); + return user; + } + + /// + /// 平台对应的角色 + /// + private long GetRoleId(UnitAccountPlatform platform) + { + var options = _roleOptionsMonitor.CurrentValue; + return platform == UnitAccountPlatform.Scoring + ? options.ScoringUnitRoleId + : options.UnitUserRoleId; + } + + internal override Task ClearCache() + { + return _easyCachingProvider.RemoveByPrefixAsync(CacheConst.PermissionKey); + } +} diff --git a/YD_Event.Application/User/SysUserService.cs b/YD_Event.Application/User/SysUserService.cs index 0d4bdb5..c7cacb3 100644 --- a/YD_Event.Application/User/SysUserService.cs +++ b/YD_Event.Application/User/SysUserService.cs @@ -1,9 +1,7 @@ using YD_Event.Application.Auth; using YD_Event.Application.Menu; using YD_Event.Application.User.Dtos; -using YD_Event.Core.Options; using YD_Event.Core.Shared; -using Microsoft.Extensions.Options; namespace YD_Event.Application.User; @@ -20,7 +18,6 @@ public class SysUserService : BaseService, ITransient private readonly AuthManager _authManager; private readonly IEasyCachingProvider _easyCachingProvider; private readonly IIdGenerator _idGenerator; - private readonly IOptionsMonitor _roleOptionsMonitor; public SysUserService(ISqlSugarRepository repository, ISqlSugarRepository userRoleRepository, @@ -29,8 +26,7 @@ public class SysUserService : BaseService, ITransient SysMenuService sysMenuService, AuthManager authManager, IEasyCachingProvider easyCachingProvider, - IIdGenerator idGenerator, - IOptionsMonitor roleOptionsMonitor) : base(repository) + IIdGenerator idGenerator) : base(repository) { _repository = repository; _userRoleRepository = userRoleRepository; @@ -40,23 +36,15 @@ public class SysUserService : BaseService, ITransient _authManager = authManager; _easyCachingProvider = easyCachingProvider; _idGenerator = idGenerator; - _roleOptionsMonitor = roleOptionsMonitor; } /// - /// 检查当前登录用户是否是管理员 + /// 检查当前登录用户是否拥有用户管理的全量数据权限 /// /// private async Task IsCurrentUserAdminAsync() { - var userId = _authManager.UserId; - // 首先检查是否是超级管理员(用户ID为1) - if (_authManager.IsSuperAdmin) - { - return true; - } - // 检查用户是否拥有管理员角色 - return await _userRoleRepository.IsAnyAsync(x => x.UserId == userId && x.RoleId == _roleOptionsMonitor.CurrentValue.AdminRoleId); + return await _sysMenuService.HasPermissionAsync(PermissionCodeConst.UserManage); } /// @@ -73,6 +61,7 @@ public class SysUserService : BaseService, ITransient // 构造基本查询 var query = _repository.AsQueryable() .Where(x => x.Id > 1) // 排除超级管理员或特定ID + .Where(x => x.UnitPlatform == null) // 排除单位管理创建的报名平台/打分系统账号 .WhereIF(!string.IsNullOrWhiteSpace(dto.Name), x => x.Name.Contains(dto.Name)) .WhereIF(!string.IsNullOrWhiteSpace(dto.Account), x => x.Account.Contains(dto.Account)) .WhereIF(dto.StartTime.HasValue, x => x.CreatedTime >= dto.StartTime.Value) @@ -86,7 +75,12 @@ public class SysUserService : BaseService, ITransient else if (!isAdmin) { // 非管理员用户,排除掉拥有管理员和单位用户角色的用户 - var excludedRoleIds = new List { _roleOptionsMonitor.CurrentValue.AdminRoleId, _roleOptionsMonitor.CurrentValue.UnitUserRoleId }; + var excludedRoleIds = new List + { + (long)BuiltInRole.Admin, + (long)BuiltInRole.UnitUser, + (long)BuiltInRole.ScoringUnit + }; // 使用子查询来获取那些拥有被排除角色的用户ID var excludedUserIds = await _userRoleRepository.AsQueryable() diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index b0ed7f3..fe971a1 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -13044,6 +13044,196 @@ + + + 单位账号查询输入 + + + + + 所属平台(0-报名平台 1-打分系统) + + + + + 单位名称 + + + + + 账号 + + + + + 生成开始时间 + + + + + 生成结束时间 + + + + + 添加单位账号输入 + + + + + 所属平台(0-报名平台 1-打分系统) + + + + + 单位名称 + + + + + 联系人 + + + + + 账号 + + + + + 密码 + + + + + 更新单位账号输入(不含密码,密码走重置接口) + + + + + 主键 + + + + + 单位名称 + + + + + 联系人 + + + + + 账号 + + + + + 重置单位账号密码输入 + + + + + 主键 + + + + + 新密码 + + + + + 单位账号输出 + + + + + 主键 + + + + + 单位名称 + + + + + 联系人 + + + + + 账号 + + + + + 所属平台 + + + + + 状态(0-启用 1-禁用) + + + + + 生成时间 + + + + + 单位管理(报名平台 / 打分系统账号) + + + + + 单位账号分页查询 + + + + + 单位账号详情 + + + + + 添加单位账号 + + + + + 更新单位账号 + + + + + 重置单位账号密码 + + + + + 启用/禁用单位账号(禁用后禁止登录) + + + + + 删除单位账号 + + + + + 取单位账号,拦截对普通系统用户的越权操作 + + + + + 平台对应的角色 + + 添加系统用户输入 diff --git a/YD_Event.Application/applicationsettings.json b/YD_Event.Application/applicationsettings.json index b872025..8e220a7 100644 --- a/YD_Event.Application/applicationsettings.json +++ b/YD_Event.Application/applicationsettings.json @@ -169,6 +169,7 @@ "AdminRoleId": 1000, "EventManagerRoleId": 1002, "UnitUserRoleId": 1001, + "ScoringUnitRoleId": 1003, "HiddenMenuIds": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009] } } \ No newline at end of file diff --git a/YD_Event.Core/Const/PermissionCodeConst.cs b/YD_Event.Core/Const/PermissionCodeConst.cs new file mode 100644 index 0000000..d87deef --- /dev/null +++ b/YD_Event.Core/Const/PermissionCodeConst.cs @@ -0,0 +1,32 @@ +namespace YD_Event.Core.Const; + +/// +/// 数据权限码(对应 SysMenu 中 Type = MenuType.DataScope 的记录) +/// +/// +/// 这些权限码必须存在于 SysMenu 表并分配给相应角色,否则 +/// SysMenuService.HasPermissionAsync 会判定为无权限(fail-closed)。 +/// 新增权限码时需同步维护 InitData/sysmenu.txt 与 二期需求/migrations 下的迁移脚本。 +/// +public class PermissionCodeConst +{ + /// + /// 系统用户 - 查看全部数据 + /// + public const string UserManage = "system:user:manage"; + + /// + /// 系统角色 - 查看全部数据 + /// + public const string RoleManage = "system:role:manage"; + + /// + /// 系统菜单 - 查看全部数据 + /// + public const string MenuManage = "system:menu:manage"; + + /// + /// 订单 - 查看全部数据 + /// + public const string OrderDataAll = "order:data:all"; +} diff --git a/YD_Event.Core/Entities/SysUser.cs b/YD_Event.Core/Entities/SysUser.cs index 49b36e5..1b6457e 100644 --- a/YD_Event.Core/Entities/SysUser.cs +++ b/YD_Event.Core/Entities/SysUser.cs @@ -96,6 +96,17 @@ public class SysUser : Entity, ISoftDelete, ICreatedUserId, ICreatedTime, /// public DateTime? LockExpired { get; set; } + /// + /// 单位名称(单位账号专用) + /// + [SugarColumn(Length = 128)] + public string? UnitName { get; set; } + + /// + /// 所属平台(null 表示普通系统用户,非单位管理创建的账号) + /// + public UnitAccountPlatform? UnitPlatform { get; set; } + /// /// 标记删除 /// diff --git a/YD_Event.Core/Enum/BuiltInRole.cs b/YD_Event.Core/Enum/BuiltInRole.cs new file mode 100644 index 0000000..e8ee15c --- /dev/null +++ b/YD_Event.Core/Enum/BuiltInRole.cs @@ -0,0 +1,37 @@ +namespace YD_Event.Core.Enum; + +/// +/// 内置角色 +/// +/// +/// 枚举值与 InitData/sysrole.txt 的种子主键一一对应,属于系统契约,不可修改。 +/// 仅用于「注册/建号时该分配哪个角色」和「列表中排除内置角色」这类固定映射; +/// 「能否查看全量数据」一律走 SysMenuService.HasPermissionAsync 的数据权限码, +/// 不要在此枚举上做管理员判断。 +/// +public enum BuiltInRole : long +{ + /// + /// 系统管理员 + /// + [Description("系统管理员")] + Admin = 1000, + + /// + /// 单位用户(报名平台) + /// + [Description("单位用户")] + UnitUser = 1001, + + /// + /// 赛事管理员 + /// + [Description("赛事管理员")] + EventManager = 1002, + + /// + /// 打分系统单位 + /// + [Description("打分系统单位")] + ScoringUnit = 1003 +} diff --git a/YD_Event.Core/Enum/UnitAccountPlatform.cs b/YD_Event.Core/Enum/UnitAccountPlatform.cs new file mode 100644 index 0000000..71aa182 --- /dev/null +++ b/YD_Event.Core/Enum/UnitAccountPlatform.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; + +namespace YD_Event.Core.Enum; + +/// +/// 单位账号所属平台 +/// +public enum UnitAccountPlatform +{ + /// + /// 报名平台 + /// + [Description("报名平台")] + Registration = 0, + + /// + /// 打分系统 + /// + [Description("打分系统")] + Scoring = 1 +} diff --git a/YD_Event.Core/InitData/sysmenu.txt b/YD_Event.Core/InitData/sysmenu.txt index 6942a16..43b47e1 100644 --- a/YD_Event.Core/InitData/sysmenu.txt +++ b/YD_Event.Core/InitData/sysmenu.txt @@ -1889,5 +1889,143 @@ "DeleteMark": false, "CreatedUserId": 1000, "CreatedTime": "2023-06-01" + }, + { + "Id": 1080, + "Name": "单位管理", + "Type": 1, + "Code": "system:unit", + "ParentId": 1070, + "RouteName": "systemunit", + "Path": "/system/unit", + "Component": "system/unit/index", + "Redirect": "", + "Icon": "ele-OfficeBuilding", + "IsIframe": "0", + "Link": "", + "IsVisible": "1", + "IsKeepAlive": "1", + "IsFixed": "0", + "Status": 0, + "Sort": 6, + "Remark": "报名平台/打分系统账号管理", + "DeleteMark": false, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01" + }, + { + "Id": 1081, + "Name": "新增", + "Type": 2, + "Code": "system:unit:add", + "ParentId": 1080, + "RouteName": "btn_system_unit_add", + "Path": "", + "Component": "", + "Redirect": "", + "Icon": "", + "IsIframe": "0", + "Link": "", + "IsVisible": "0", + "IsKeepAlive": "1", + "IsFixed": "0", + "Status": 0, + "Sort": 1, + "Remark": "", + "DeleteMark": false, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01" + }, + { + "Id": 1082, + "Name": "编辑", + "Type": 2, + "Code": "system:unit:edit", + "ParentId": 1080, + "RouteName": "btn_system_unit_edit", + "Path": "", + "Component": "", + "Redirect": "", + "Icon": "", + "IsIframe": "0", + "Link": "", + "IsVisible": "0", + "IsKeepAlive": "1", + "IsFixed": "0", + "Status": 0, + "Sort": 2, + "Remark": "", + "DeleteMark": false, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01" + }, + { + "Id": 1083, + "Name": "删除", + "Type": 2, + "Code": "system:unit:delete", + "ParentId": 1080, + "RouteName": "btn_system_unit_delete", + "Path": "", + "Component": "", + "Redirect": "", + "Icon": "", + "IsIframe": "0", + "Link": "", + "IsVisible": "0", + "IsKeepAlive": "1", + "IsFixed": "0", + "Status": 0, + "Sort": 3, + "Remark": "", + "DeleteMark": false, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01" + }, + { + "Id": 1084, + "Name": "启用禁用", + "Type": 2, + "Code": "system:unit:status", + "ParentId": 1080, + "RouteName": "btn_system_unit_status", + "Path": "", + "Component": "", + "Redirect": "", + "Icon": "", + "IsIframe": "0", + "Link": "", + "IsVisible": "0", + "IsKeepAlive": "1", + "IsFixed": "0", + "Status": 0, + "Sort": 4, + "Remark": "", + "DeleteMark": false, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01" + }, + { + "Id": 1085, + "Name": "重置密码", + "Type": 2, + "Code": "system:unit:resetpwd", + "ParentId": 1080, + "RouteName": "btn_system_unit_resetpwd", + "Path": "", + "Component": "", + "Redirect": "", + "Icon": "", + "IsIframe": "0", + "Link": "", + "IsVisible": "0", + "IsKeepAlive": "1", + "IsFixed": "0", + "Status": 0, + "Sort": 5, + "Remark": "", + "DeleteMark": false, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01" } ] \ No newline at end of file diff --git a/YD_Event.Core/InitData/sysrole.txt b/YD_Event.Core/InitData/sysrole.txt index 2518985..fc44674 100644 --- a/YD_Event.Core/InitData/sysrole.txt +++ b/YD_Event.Core/InitData/sysrole.txt @@ -31,5 +31,16 @@ "DeleteMark": false, "CreatedUserId": 1000, "CreatedTime": "2023-06-01" + }, + { + "Id": 1003, + "Name": "打分系统单位", + "Code": "scoringunit", + "Status": 0, + "Sort": 4, + "Remark": "打分系统单位账号", + "DeleteMark": false, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01" } ] \ No newline at end of file diff --git a/YD_Event.Core/InitData/sysrolemenu.txt b/YD_Event.Core/InitData/sysrolemenu.txt index 36f161b..98fa6f6 100644 --- a/YD_Event.Core/InitData/sysrolemenu.txt +++ b/YD_Event.Core/InitData/sysrolemenu.txt @@ -678,5 +678,53 @@ "CreatedUserId": 1000, "CreatedTime": "2023-06-01", "DeleteMark": false + }, + { + "Id": 1101, + "RoleId": 1000, + "MenuId": 1080, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01", + "DeleteMark": false + }, + { + "Id": 1102, + "RoleId": 1000, + "MenuId": 1081, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01", + "DeleteMark": false + }, + { + "Id": 1103, + "RoleId": 1000, + "MenuId": 1082, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01", + "DeleteMark": false + }, + { + "Id": 1104, + "RoleId": 1000, + "MenuId": 1083, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01", + "DeleteMark": false + }, + { + "Id": 1105, + "RoleId": 1000, + "MenuId": 1084, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01", + "DeleteMark": false + }, + { + "Id": 1106, + "RoleId": 1000, + "MenuId": 1085, + "CreatedUserId": 1000, + "CreatedTime": "2023-06-01", + "DeleteMark": false } ] \ No newline at end of file diff --git a/YD_Event.Core/Options/RoleOptions.cs b/YD_Event.Core/Options/RoleOptions.cs index 6a5e3c3..8f5b65e 100644 --- a/YD_Event.Core/Options/RoleOptions.cs +++ b/YD_Event.Core/Options/RoleOptions.cs @@ -23,6 +23,11 @@ public class RoleOptions : IConfigurableOptions /// public long UnitUserRoleId { get; set; } + /// + /// 打分系统单位角色ID + /// + public long ScoringUnitRoleId { get; set; } + /// /// 需要对非管理员用户隐藏的菜单ID列表 /// diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml index 77fe234..ebd16b3 100644 --- a/YD_Event.Core/YD_Event.Core.xml +++ b/YD_Event.Core/YD_Event.Core.xml @@ -4073,6 +4073,16 @@ 账号锁定过期时间 + + + 单位名称(单位账号专用) + + + + + 所属平台(null 表示普通系统用户,非单位管理创建的账号) + + 标记删除 @@ -4860,6 +4870,21 @@ 个人\团队报名 + + + 单位账号所属平台 + + + + + 报名平台 + + + + + 打分系统 + + 参与单位类型 @@ -5988,6 +6013,11 @@ 单位用户角色ID + + + 打分系统单位角色ID + + 需要对非管理员用户隐藏的菜单ID列表