多角色账号按 All > College > Class > Self 自动取最高权限。

学院管理员限制在所属学院。
辅导员通过稳定账号 ID 绑定行政班,避免重名串班。
教师只能访问本人档案、授课课程和所授课学生。
学生只能访问本人档案及所在班级课程。
教师/学生角色会自动校验并绑定工号或学号档案。
超级管理员可在用户页面调整角色、学院、工号/学号,并预览生效后的数据范围。
This commit is contained in:
2026-07-24 14:54:30 +08:00 Unverified
parent 0b463fa4f2
commit bcc4d33bd5
26 changed files with 2333 additions and 88 deletions
@@ -0,0 +1,89 @@
using System.Security.Claims;
using Jiaowu.Api.Domain.Identity;
namespace Jiaowu.Api.Infrastructure.Auth;
public interface ICurrentUserDataScope
{
CurrentUserScope Current { get; }
}
public sealed record CurrentUserScope(
Guid UserId,
string? DisplayName,
Guid? CollegeId,
DataScope Scope,
IReadOnlySet<string> Roles)
{
public bool IsInRole(string role) => Roles.Contains(role);
public bool CanAccessCollege(Guid collegeId) =>
Scope == DataScope.All ||
Scope == DataScope.College && CollegeId == collegeId;
public Guid? RestrictedCollegeId => Scope switch
{
DataScope.All => null,
DataScope.College => CollegeId ?? Guid.Empty,
_ => Guid.Empty
};
}
public sealed class CurrentUserDataScope(IHttpContextAccessor httpContextAccessor)
: ICurrentUserDataScope
{
public CurrentUserScope Current
{
get
{
var principal = httpContextAccessor.HttpContext?.User;
var roles = new HashSet<string>(
principal?.FindAll(ClaimTypes.Role).Select(x => x.Value) ?? [],
StringComparer.OrdinalIgnoreCase);
return new CurrentUserScope(
ParseGuid(principal?.FindFirstValue(ClaimTypes.NameIdentifier)),
principal?.FindFirstValue("display_name") ??
principal?.FindFirstValue(ClaimTypes.Name),
ParseNullableGuid(principal?.FindFirstValue("college_id")),
EffectiveDataScopeResolver.Resolve(roles),
roles);
}
}
private static Guid ParseGuid(string? value) =>
Guid.TryParse(value, out var id) ? id : Guid.Empty;
private static Guid? ParseNullableGuid(string? value) =>
Guid.TryParse(value, out var id) ? id : null;
}
public static class EffectiveDataScopeResolver
{
private static readonly IReadOnlyDictionary<string, DataScope> RoleScopes =
new Dictionary<string, DataScope>(StringComparer.OrdinalIgnoreCase)
{
[SystemRoles.SuperAdmin] = DataScope.All,
[SystemRoles.AcademicAdmin] = DataScope.All,
[SystemRoles.Leader] = DataScope.All,
[SystemRoles.CollegeAdmin] = DataScope.College,
[SystemRoles.Counselor] = DataScope.Class,
[SystemRoles.Teacher] = DataScope.Self,
[SystemRoles.Student] = DataScope.Self
};
public static DataScope Resolve(IEnumerable<string> roles)
{
var effectiveScope = DataScope.Self;
foreach (var role in roles)
{
if (RoleScopes.TryGetValue(role, out var roleScope) &&
roleScope > effectiveScope)
{
effectiveScope = roleScope;
}
}
return effectiveScope;
}
}