打分系统账户管理

This commit is contained in:
马梓彭 2026-08-10 22:26:02 +08:00
parent 0710367564
commit 2f8c145df7
16 changed files with 969 additions and 108 deletions

View File

@ -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<SysMenu>, ITransient
private readonly ISqlSugarRepository<SysMenu> _sysMenuRepository;
private readonly IEasyCachingProvider _easyCachingProvider;
private readonly AuthManager _authManager;
private readonly ISqlSugarRepository<SysUserRole> _sysUserRoleRepository;
private readonly IOptionsMonitor<RoleOptions> _roleOptionsMonitor;
public SysMenuService(ISqlSugarRepository<SysMenu> sysMenuRepository,
IEasyCachingProvider easyCachingProvider,
AuthManager authManager,
ISqlSugarRepository<SysUserRole> sysUserRoleRepository,
IOptionsMonitor<RoleOptions> roleOptionsMonitor) : base(sysMenuRepository)
AuthManager authManager) : base(sysMenuRepository)
{
_sysMenuRepository = sysMenuRepository;
_easyCachingProvider = easyCachingProvider;
_authManager = authManager;
_sysUserRoleRepository = sysUserRoleRepository;
_roleOptionsMonitor = roleOptionsMonitor;
}
/// <summary>
@ -185,23 +177,8 @@ public class SysMenuService : BaseService<SysMenu>, ITransient
[HttpGet]
public async Task<List<TreeSelectOutput>> 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<long>();
if (hiddenMenuIds.Any())
{
FilterMenus(list, hiddenMenuIds);
}
}
// 按当前用户已分配的菜单返回,避免越权看到未授权菜单(如客户端门户菜单)
var list = await QueryGrantedMenusAsync(includeDisabled: true);
return list.Adapt<List<TreeSelectOutput>>();
}
@ -269,86 +246,72 @@ public class SysMenuService : BaseService<SysMenu>, ITransient
}
/// <summary>
/// 检查当前登录用户是否是管理员
/// 检查当前登录用户是否拥有菜单管理的全量数据权限
/// </summary>
/// <returns></returns>
private async Task<bool> IsCurrentUserAdminAsync()
{
var userId = _authManager.UserId;
// 首先检查是否是超级管理员
return await HasPermissionAsync(PermissionCodeConst.MenuManage);
}
/// <summary>
/// 查询当前用户已被授权的菜单树(超级管理员返回全部)
/// </summary>
/// <param name="includeDisabled">是否包含已禁用的菜单,供菜单/角色配置页使用</param>
private async Task<List<SysMenu>> 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<SysRoleMenu>((menu, roleMenu) => menu.Id == roleMenu.MenuId)
.InnerJoin<SysRole>((menu, roleMenu, role) => roleMenu.RoleId == role.Id)
.InnerJoin<SysUserRole>((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);
}
/// <summary>
/// 过滤菜单,移除配置中指定的菜单ID及其子菜单
/// </summary>
/// <param name="menus">菜单列表</param>
/// <param name="hiddenMenuIds">需要隐藏的菜单ID列表</param>
private void FilterMenus(List<SysMenu> menus, List<long> 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);
}
}
}
/// <summary>
/// 菜单按钮树 - 仅在角色管理页面使用此接口时应用隐藏规则
/// 菜单按钮树 - 角色分配菜单时使用
/// </summary>
/// <returns></returns>
[DisplayName("菜单按钮树")]
[HttpGet]
public async Task<List<TreeSelectOutput>> TreeMenuButton()
{
long userId = _authManager.UserId;
List<SysMenu> menus;
if (_authManager.IsSuperAdmin)//超级管理员
{
menus = await _sysMenuRepository.AsQueryable().ToTreeAsync(x => x.Children, x => x.ParentId, null);
}
else
{
menus = await _sysMenuRepository.AsQueryable()
.InnerJoin<SysRoleMenu>((menu, roleMenu) => menu.Id == roleMenu.MenuId)
.InnerJoin<SysRole>((menu, roleMenu, role) => roleMenu.RoleId == role.Id)
.InnerJoin<SysUserRole>((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<long>();
if (hiddenMenuIds.Any())
{
FilterMenus(menus, hiddenMenuIds);
}
}
var menus = await QueryGrantedMenusAsync(includeDisabled: false);
return menus.Adapt<List<TreeSelectOutput>>();
}
/// <summary>
/// 严格权限校验
/// </summary>
/// <remarks>
/// 与 <see cref="CheckPermission"/> 的区别:权限码未配置时返回 false 而不是放行。
/// 用于数据范围/管理员判断,避免迁移脚本未执行时把全量数据权限误放给所有用户。
/// </remarks>
/// <param name="code">权限标识</param>
/// <returns></returns>
[NonAction]
public async Task<bool> HasPermissionAsync(string code)
{
if (_authManager.IsSuperAdmin) return true;
var authCodes = await GetAuthPermissionCodeList(_authManager.UserId);
return authCodes.Any(x => string.Equals(x, code, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// 校验权限
/// </summary>

View File

@ -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<SysRole>, ITransient
private readonly IEasyCachingProvider _easyCachingProvider;
private readonly IIdGenerator _idGenerator;
private readonly AuthManager _authManager;
private readonly IOptionsMonitor<RoleOptions> _roleOptionsMonitor;
private readonly SysMenuService _sysMenuService;
public SysRoleService(ISqlSugarRepository<SysRole> sysRoleRepository,
ISqlSugarRepository<SysRoleMenu> sysRoleMenuRepository,
@ -25,7 +24,7 @@ public class SysRoleService : BaseService<SysRole>, ITransient
IEasyCachingProvider easyCachingProvider,
IIdGenerator idGenerator,
AuthManager authManager,
IOptionsMonitor<RoleOptions> roleOptionsMonitor) : base(sysRoleRepository)
SysMenuService sysMenuService) : base(sysRoleRepository)
{
_sysRoleRepository = sysRoleRepository;
_sysRoleMenuRepository = sysRoleMenuRepository;
@ -33,7 +32,7 @@ public class SysRoleService : BaseService<SysRole>, ITransient
_easyCachingProvider = easyCachingProvider;
_idGenerator = idGenerator;
_authManager = authManager;
_roleOptionsMonitor = roleOptionsMonitor;
_sysMenuService = sysMenuService;
}
/// <summary>

View File

@ -0,0 +1,168 @@
namespace YD_Event.Application.Unit.Dtos;
/// <summary>
/// 单位账号查询输入
/// </summary>
public class UnitAccountQueryInput : Pagination
{
/// <summary>
/// 所属平台(0-报名平台 1-打分系统)
/// </summary>
[Required(ErrorMessage = "请选择所属平台")]
public UnitAccountPlatform Platform { get; set; }
/// <summary>
/// 单位名称
/// </summary>
public string? UnitName { get; set; }
/// <summary>
/// 账号
/// </summary>
public string? Account { get; set; }
/// <summary>
/// 生成开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 生成结束时间
/// </summary>
public DateTime? EndTime { get; set; }
}
/// <summary>
/// 添加单位账号输入
/// </summary>
public class AddUnitAccountInput
{
/// <summary>
/// 所属平台(0-报名平台 1-打分系统)
/// </summary>
[Required(ErrorMessage = "请选择所属平台")]
public UnitAccountPlatform Platform { get; set; }
/// <summary>
/// 单位名称
/// </summary>
[Required(ErrorMessage = "单位名称为必填项")]
[MaxLength(128, ErrorMessage = "单位名称限制128个字符内")]
public string UnitName { get; set; }
/// <summary>
/// 联系人
/// </summary>
[Required(ErrorMessage = "联系人为必填项")]
[MaxLength(16, ErrorMessage = "联系人限制16个字符内")]
public string ContactName { get; set; }
/// <summary>
/// 账号
/// </summary>
[Required(ErrorMessage = "账号为必填项")]
[MaxLength(32, ErrorMessage = "账号限制32个字符内")]
public string Account { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "密码为必填项")]
[MinLength(6, ErrorMessage = "密码长度至少6位")]
[MaxLength(32, ErrorMessage = "密码长度最多32位")]
public string Password { get; set; }
}
/// <summary>
/// 更新单位账号输入(不含密码,密码走重置接口)
/// </summary>
public class UpdateUnitAccountInput
{
/// <summary>
/// 主键
/// </summary>
[Required(ErrorMessage = "Id不能为空")]
public long Id { get; set; }
/// <summary>
/// 单位名称
/// </summary>
[Required(ErrorMessage = "单位名称为必填项")]
[MaxLength(128, ErrorMessage = "单位名称限制128个字符内")]
public string UnitName { get; set; }
/// <summary>
/// 联系人
/// </summary>
[Required(ErrorMessage = "联系人为必填项")]
[MaxLength(16, ErrorMessage = "联系人限制16个字符内")]
public string ContactName { get; set; }
/// <summary>
/// 账号
/// </summary>
[Required(ErrorMessage = "账号为必填项")]
[MaxLength(32, ErrorMessage = "账号限制32个字符内")]
public string Account { get; set; }
}
/// <summary>
/// 重置单位账号密码输入
/// </summary>
public class ResetUnitAccountPasswordInput
{
/// <summary>
/// 主键
/// </summary>
[Required(ErrorMessage = "Id不能为空")]
public long Id { get; set; }
/// <summary>
/// 新密码
/// </summary>
[Required(ErrorMessage = "密码为必填项")]
[MinLength(6, ErrorMessage = "密码长度至少6位")]
[MaxLength(32, ErrorMessage = "密码长度最多32位")]
public string Password { get; set; }
}
/// <summary>
/// 单位账号输出
/// </summary>
public class UnitAccountOutput
{
/// <summary>
/// 主键
/// </summary>
public long Id { get; set; }
/// <summary>
/// 单位名称
/// </summary>
public string? UnitName { get; set; }
/// <summary>
/// 联系人
/// </summary>
public string ContactName { get; set; }
/// <summary>
/// 账号
/// </summary>
public string Account { get; set; }
/// <summary>
/// 所属平台
/// </summary>
public UnitAccountPlatform? Platform { get; set; }
/// <summary>
/// 状态(0-启用 1-禁用)
/// </summary>
public AvailabilityStatus Status { get; set; }
/// <summary>
/// 生成时间
/// </summary>
public DateTime CreatedTime { get; set; }
}

View File

@ -0,0 +1,213 @@
using Microsoft.Extensions.Options;
using YD_Event.Application.Unit.Dtos;
using YD_Event.Core.Options;
namespace YD_Event.Application.Unit;
/// <summary>
/// 单位管理(报名平台 / 打分系统账号)
/// </summary>
public class UnitAccountService : BaseService<SysUser>, ITransient
{
private readonly ISqlSugarRepository<SysUser> _repository;
private readonly ISqlSugarRepository<SysUserRole> _userRoleRepository;
private readonly IIdGenerator _idGenerator;
private readonly IEasyCachingProvider _easyCachingProvider;
private readonly IOptionsMonitor<RoleOptions> _roleOptionsMonitor;
public UnitAccountService(ISqlSugarRepository<SysUser> repository,
ISqlSugarRepository<SysUserRole> userRoleRepository,
IIdGenerator idGenerator,
IEasyCachingProvider easyCachingProvider,
IOptionsMonitor<RoleOptions> roleOptionsMonitor) : base(repository)
{
_repository = repository;
_userRoleRepository = userRoleRepository;
_idGenerator = idGenerator;
_easyCachingProvider = easyCachingProvider;
_roleOptionsMonitor = roleOptionsMonitor;
}
/// <summary>
/// 单位账号分页查询
/// </summary>
[DisplayName("单位账号分页查询")]
[HttpGet("page")]
public async Task<PageResult<UnitAccountOutput>> 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);
}
/// <summary>
/// 单位账号详情
/// </summary>
[DisplayName("单位账号详情")]
[HttpGet("detail")]
public async Task<UnitAccountOutput> 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
};
}
/// <summary>
/// 添加单位账号
/// </summary>
[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();
}
/// <summary>
/// 更新单位账号
/// </summary>
[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();
}
/// <summary>
/// 重置单位账号密码
/// </summary>
[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);
}
/// <summary>
/// 启用/禁用单位账号(禁用后禁止登录)
/// </summary>
[DisplayName("启用禁用单位账号")]
[HttpPatch("setStatus")]
public override async Task SetStatus(AvailabilityDto dto)
{
await GetUnitAccountAsync(dto.Id);
await base.SetStatus(dto);
}
/// <summary>
/// 删除单位账号
/// </summary>
[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);
}
/// <summary>
/// 取单位账号,拦截对普通系统用户的越权操作
/// </summary>
private async Task<SysUser> GetUnitAccountAsync(long id)
{
var user = await _repository.GetByIdAsync(id);
if (user == null) throw Oops.Bah("单位账号不存在");
if (user.UnitPlatform == null) throw Oops.Bah("该账号不是单位账号,请在系统用户中管理");
return user;
}
/// <summary>
/// 平台对应的角色
/// </summary>
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);
}
}

View File

@ -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<SysUser>, ITransient
private readonly AuthManager _authManager;
private readonly IEasyCachingProvider _easyCachingProvider;
private readonly IIdGenerator _idGenerator;
private readonly IOptionsMonitor<RoleOptions> _roleOptionsMonitor;
public SysUserService(ISqlSugarRepository<SysUser> repository,
ISqlSugarRepository<SysUserRole> userRoleRepository,
@ -29,8 +26,7 @@ public class SysUserService : BaseService<SysUser>, ITransient
SysMenuService sysMenuService,
AuthManager authManager,
IEasyCachingProvider easyCachingProvider,
IIdGenerator idGenerator,
IOptionsMonitor<RoleOptions> roleOptionsMonitor) : base(repository)
IIdGenerator idGenerator) : base(repository)
{
_repository = repository;
_userRoleRepository = userRoleRepository;
@ -40,23 +36,15 @@ public class SysUserService : BaseService<SysUser>, ITransient
_authManager = authManager;
_easyCachingProvider = easyCachingProvider;
_idGenerator = idGenerator;
_roleOptionsMonitor = roleOptionsMonitor;
}
/// <summary>
/// 检查当前登录用户是否是管理员
/// 检查当前登录用户是否拥有用户管理的全量数据权限
/// </summary>
/// <returns></returns>
private async Task<bool> 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);
}
/// <summary>
@ -73,6 +61,7 @@ public class SysUserService : BaseService<SysUser>, 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<SysUser>, ITransient
else if (!isAdmin)
{
// 非管理员用户,排除掉拥有管理员和单位用户角色的用户
var excludedRoleIds = new List<long> { _roleOptionsMonitor.CurrentValue.AdminRoleId, _roleOptionsMonitor.CurrentValue.UnitUserRoleId };
var excludedRoleIds = new List<long>
{
(long)BuiltInRole.Admin,
(long)BuiltInRole.UnitUser,
(long)BuiltInRole.ScoringUnit
};
// 使用子查询来获取那些拥有被排除角色的用户ID
var excludedUserIds = await _userRoleRepository.AsQueryable()

View File

@ -13044,6 +13044,196 @@
<param name="input"></param>
<returns></returns>
</member>
<member name="T:YD_Event.Application.Unit.Dtos.UnitAccountQueryInput">
<summary>
单位账号查询输入
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountQueryInput.Platform">
<summary>
所属平台(0-报名平台 1-打分系统)
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountQueryInput.UnitName">
<summary>
单位名称
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountQueryInput.Account">
<summary>
账号
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountQueryInput.StartTime">
<summary>
生成开始时间
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountQueryInput.EndTime">
<summary>
生成结束时间
</summary>
</member>
<member name="T:YD_Event.Application.Unit.Dtos.AddUnitAccountInput">
<summary>
添加单位账号输入
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.AddUnitAccountInput.Platform">
<summary>
所属平台(0-报名平台 1-打分系统)
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.AddUnitAccountInput.UnitName">
<summary>
单位名称
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.AddUnitAccountInput.ContactName">
<summary>
联系人
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.AddUnitAccountInput.Account">
<summary>
账号
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.AddUnitAccountInput.Password">
<summary>
密码
</summary>
</member>
<member name="T:YD_Event.Application.Unit.Dtos.UpdateUnitAccountInput">
<summary>
更新单位账号输入(不含密码,密码走重置接口)
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UpdateUnitAccountInput.Id">
<summary>
主键
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UpdateUnitAccountInput.UnitName">
<summary>
单位名称
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UpdateUnitAccountInput.ContactName">
<summary>
联系人
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UpdateUnitAccountInput.Account">
<summary>
账号
</summary>
</member>
<member name="T:YD_Event.Application.Unit.Dtos.ResetUnitAccountPasswordInput">
<summary>
重置单位账号密码输入
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.ResetUnitAccountPasswordInput.Id">
<summary>
主键
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.ResetUnitAccountPasswordInput.Password">
<summary>
新密码
</summary>
</member>
<member name="T:YD_Event.Application.Unit.Dtos.UnitAccountOutput">
<summary>
单位账号输出
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountOutput.Id">
<summary>
主键
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountOutput.UnitName">
<summary>
单位名称
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountOutput.ContactName">
<summary>
联系人
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountOutput.Account">
<summary>
账号
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountOutput.Platform">
<summary>
所属平台
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountOutput.Status">
<summary>
状态(0-启用 1-禁用)
</summary>
</member>
<member name="P:YD_Event.Application.Unit.Dtos.UnitAccountOutput.CreatedTime">
<summary>
生成时间
</summary>
</member>
<member name="T:YD_Event.Application.Unit.UnitAccountService">
<summary>
单位管理(报名平台 / 打分系统账号)
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.Page(YD_Event.Application.Unit.Dtos.UnitAccountQueryInput)">
<summary>
单位账号分页查询
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.Detail(System.Int64)">
<summary>
单位账号详情
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.Add(YD_Event.Application.Unit.Dtos.AddUnitAccountInput)">
<summary>
添加单位账号
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.Update(YD_Event.Application.Unit.Dtos.UpdateUnitAccountInput)">
<summary>
更新单位账号
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.ResetPassword(YD_Event.Application.Unit.Dtos.ResetUnitAccountPasswordInput)">
<summary>
重置单位账号密码
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.SetStatus(YD_Event.Core.Shared.AvailabilityDto)">
<summary>
启用/禁用单位账号(禁用后禁止登录)
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.Delete(YD_Event.Core.Shared.KeyDto)">
<summary>
删除单位账号
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.GetUnitAccountAsync(System.Int64)">
<summary>
取单位账号,拦截对普通系统用户的越权操作
</summary>
</member>
<member name="M:YD_Event.Application.Unit.UnitAccountService.GetRoleId(YD_Event.Core.Enum.UnitAccountPlatform)">
<summary>
平台对应的角色
</summary>
</member>
<member name="T:YD_Event.Application.User.Dtos.AddSysUserInput">
<summary>
添加系统用户输入

View File

@ -169,6 +169,7 @@
"AdminRoleId": 1000,
"EventManagerRoleId": 1002,
"UnitUserRoleId": 1001,
"ScoringUnitRoleId": 1003,
"HiddenMenuIds": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009]
}
}

View File

@ -0,0 +1,32 @@
namespace YD_Event.Core.Const;
/// <summary>
/// 数据权限码(对应 SysMenu 中 Type = MenuType.DataScope 的记录)
/// </summary>
/// <remarks>
/// 这些权限码必须存在于 SysMenu 表并分配给相应角色,否则
/// SysMenuService.HasPermissionAsync 会判定为无权限(fail-closed)。
/// 新增权限码时需同步维护 InitData/sysmenu.txt 与 二期需求/migrations 下的迁移脚本。
/// </remarks>
public class PermissionCodeConst
{
/// <summary>
/// 系统用户 - 查看全部数据
/// </summary>
public const string UserManage = "system:user:manage";
/// <summary>
/// 系统角色 - 查看全部数据
/// </summary>
public const string RoleManage = "system:role:manage";
/// <summary>
/// 系统菜单 - 查看全部数据
/// </summary>
public const string MenuManage = "system:menu:manage";
/// <summary>
/// 订单 - 查看全部数据
/// </summary>
public const string OrderDataAll = "order:data:all";
}

View File

@ -96,6 +96,17 @@ public class SysUser : Entity<long>, ISoftDelete, ICreatedUserId, ICreatedTime,
/// </summary>
public DateTime? LockExpired { get; set; }
/// <summary>
/// 单位名称(单位账号专用)
/// </summary>
[SugarColumn(Length = 128)]
public string? UnitName { get; set; }
/// <summary>
/// 所属平台(null 表示普通系统用户,非单位管理创建的账号)
/// </summary>
public UnitAccountPlatform? UnitPlatform { get; set; }
/// <summary>
/// 标记删除
/// </summary>

View File

@ -0,0 +1,37 @@
namespace YD_Event.Core.Enum;
/// <summary>
/// 内置角色
/// </summary>
/// <remarks>
/// 枚举值与 InitData/sysrole.txt 的种子主键一一对应,属于系统契约,不可修改。
/// 仅用于「注册/建号时该分配哪个角色」和「列表中排除内置角色」这类固定映射;
/// 「能否查看全量数据」一律走 SysMenuService.HasPermissionAsync 的数据权限码,
/// 不要在此枚举上做管理员判断。
/// </remarks>
public enum BuiltInRole : long
{
/// <summary>
/// 系统管理员
/// </summary>
[Description("系统管理员")]
Admin = 1000,
/// <summary>
/// 单位用户(报名平台)
/// </summary>
[Description("单位用户")]
UnitUser = 1001,
/// <summary>
/// 赛事管理员
/// </summary>
[Description("赛事管理员")]
EventManager = 1002,
/// <summary>
/// 打分系统单位
/// </summary>
[Description("打分系统单位")]
ScoringUnit = 1003
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel;
namespace YD_Event.Core.Enum;
/// <summary>
/// 单位账号所属平台
/// </summary>
public enum UnitAccountPlatform
{
/// <summary>
/// 报名平台
/// </summary>
[Description("报名平台")]
Registration = 0,
/// <summary>
/// 打分系统
/// </summary>
[Description("打分系统")]
Scoring = 1
}

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

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

View File

@ -23,6 +23,11 @@ public class RoleOptions : IConfigurableOptions
/// </summary>
public long UnitUserRoleId { get; set; }
/// <summary>
/// 打分系统单位角色ID
/// </summary>
public long ScoringUnitRoleId { get; set; }
/// <summary>
/// 需要对非管理员用户隐藏的菜单ID列表
/// </summary>

View File

@ -4073,6 +4073,16 @@
账号锁定过期时间
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysUser.UnitName">
<summary>
单位名称(单位账号专用)
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysUser.UnitPlatform">
<summary>
所属平台(null 表示普通系统用户,非单位管理创建的账号)
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysUser.DeleteMark">
<summary>
标记删除
@ -4860,6 +4870,21 @@
个人\团队报名
</summary>
</member>
<member name="T:YD_Event.Core.Enum.UnitAccountPlatform">
<summary>
单位账号所属平台
</summary>
</member>
<member name="F:YD_Event.Core.Enum.UnitAccountPlatform.Registration">
<summary>
报名平台
</summary>
</member>
<member name="F:YD_Event.Core.Enum.UnitAccountPlatform.Scoring">
<summary>
打分系统
</summary>
</member>
<member name="T:YD_Event.Core.Enum.UnitType">
<summary>
参与单位类型
@ -5988,6 +6013,11 @@
单位用户角色ID
</summary>
</member>
<member name="P:YD_Event.Core.Options.RoleOptions.ScoringUnitRoleId">
<summary>
打分系统单位角色ID
</summary>
</member>
<member name="P:YD_Event.Core.Options.RoleOptions.HiddenMenuIds">
<summary>
需要对非管理员用户隐藏的菜单ID列表