多角色账号按 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
@@ -168,7 +168,29 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
x.Id, x.Code, x.Name, x.MajorId,
MajorName = x.Major!.Name,
CollegeName = x.Major.College!.Name,
x.Grade, x.CounselorName, x.IsEnabled, x.SortOrder
x.Grade, x.CounselorUserId, x.CounselorName,
x.IsEnabled, x.SortOrder
})
.ToListAsync(cancellationToken));
[HttpGet("counselors")]
[Authorize(Roles = Administrators)]
public async Task<ActionResult<object>> GetCounselors(CancellationToken cancellationToken) =>
Ok(await db.Users.AsNoTracking()
.Where(user =>
user.IsEnabled &&
db.UserRoles.Any(userRole =>
userRole.UserId == user.Id &&
db.Roles.Any(role =>
role.Id == userRole.RoleId &&
role.Name == SystemRoles.Counselor)))
.OrderBy(x => x.DisplayName)
.Select(x => new
{
x.Id,
x.DisplayName,
x.StaffNumber,
x.CollegeId
})
.ToListAsync(cancellationToken));
@@ -180,6 +202,11 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
{
if (!await db.Majors.AnyAsync(x => x.Id == request.MajorId, cancellationToken))
return ValidationProblem("所选专业不存在。");
var counselorName = await GetCounselorNameAsync(
request.CounselorUserId,
cancellationToken);
if (request.CounselorUserId.HasValue && counselorName is null)
return ValidationProblem("所选辅导员账号不存在、已停用或没有辅导员角色。");
var entity = new AdministrativeClass
{
@@ -187,7 +214,8 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
Name = request.Name.Trim(),
MajorId = request.MajorId,
Grade = request.Grade,
CounselorName = request.CounselorName?.Trim(),
CounselorUserId = request.CounselorUserId,
CounselorName = counselorName,
SortOrder = request.SortOrder,
IsEnabled = request.IsEnabled
};
@@ -203,10 +231,16 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
{
var entity = await db.AdministrativeClasses.FindAsync([id], cancellationToken);
if (entity is null) return NotFound();
var counselorName = await GetCounselorNameAsync(
request.CounselorUserId,
cancellationToken);
if (request.CounselorUserId.HasValue && counselorName is null)
return ValidationProblem("所选辅导员账号不存在、已停用或没有辅导员角色。");
ApplyCatalog(entity, request);
entity.MajorId = request.MajorId;
entity.Grade = request.Grade;
entity.CounselorName = request.CounselorName?.Trim();
entity.CounselorUserId = request.CounselorUserId;
entity.CounselorName = counselorName;
await db.SaveChangesAsync(cancellationToken);
return entity;
}
@@ -409,6 +443,24 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
return CreatedAtAction(action, new { id = entity.Id }, entity);
}
private async Task<string?> GetCounselorNameAsync(
Guid? userId,
CancellationToken cancellationToken)
{
if (!userId.HasValue) return null;
return await db.Users.AsNoTracking()
.Where(user =>
user.Id == userId &&
user.IsEnabled &&
db.UserRoles.Any(userRole =>
userRole.UserId == user.Id &&
db.Roles.Any(role =>
role.Id == userRole.RoleId &&
role.Name == SystemRoles.Counselor)))
.Select(x => x.DisplayName)
.FirstOrDefaultAsync(cancellationToken);
}
private static void ApplyCatalog(CatalogEntity entity, CatalogRequest request)
{
entity.Code = request.Code.Trim();
@@ -442,7 +494,7 @@ public sealed record ClassRequest(
string Code, string Name, int SortOrder, bool IsEnabled,
Guid MajorId,
[Range(2000, 2200)] int Grade,
[MaxLength(50)] string? CounselorName)
Guid? CounselorUserId)
: CatalogRequest(Code, Name, SortOrder, IsEnabled);
public sealed record TermRequest(