多角色账号按 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
@@ -1,8 +1,8 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -13,13 +13,17 @@ namespace Jiaowu.Api.Controllers;
[ApiController]
[Authorize(Roles = ReadRoles)]
[Route("api/personnel")]
public sealed class PersonnelController(AppDbContext db) : ControllerBase
public sealed class PersonnelController(
AppDbContext db,
ICurrentUserDataScope currentUserDataScope) : ControllerBase
{
private const string ReadRoles =
SystemRoles.SuperAdmin + "," +
SystemRoles.AcademicAdmin + "," +
SystemRoles.CollegeAdmin + "," +
SystemRoles.Counselor;
SystemRoles.Counselor + "," +
SystemRoles.Teacher + "," +
SystemRoles.Student;
private const string WriteRoles =
SystemRoles.SuperAdmin + "," +
@@ -33,10 +37,7 @@ public sealed class PersonnelController(AppDbContext db) : ControllerBase
{
var page = NormalizePage(query.Page);
var pageSize = NormalizePageSize(query.PageSize);
var source = db.Teachers.AsNoTracking().AsQueryable();
var scopedCollegeId = GetScopedCollegeId();
if (scopedCollegeId.HasValue)
source = source.Where(x => x.CollegeId == scopedCollegeId.Value);
var source = ApplyTeacherScope(db.Teachers.AsNoTracking());
if (query.CollegeId.HasValue)
source = source.Where(x => x.CollegeId == query.CollegeId);
if (!string.IsNullOrWhiteSpace(query.Keyword))
@@ -155,13 +156,7 @@ public sealed class PersonnelController(AppDbContext db) : ControllerBase
{
var page = NormalizePage(query.Page);
var pageSize = NormalizePageSize(query.PageSize);
var source = db.Students.AsNoTracking().AsQueryable();
var scopedCollegeId = GetScopedCollegeId();
if (scopedCollegeId.HasValue)
{
source = source.Where(x =>
x.AdministrativeClass!.Major!.CollegeId == scopedCollegeId.Value);
}
var source = ApplyStudentScope(db.Students.AsNoTracking());
if (query.CollegeId.HasValue)
{
source = source.Where(x =>
@@ -303,21 +298,54 @@ public sealed class PersonnelController(AppDbContext db) : ControllerBase
return await SaveNoContentAsync(cancellationToken);
}
private Guid? GetScopedCollegeId()
private IQueryable<Teacher> ApplyTeacherScope(IQueryable<Teacher> source)
{
if (!User.IsInRole(SystemRoles.CollegeAdmin) &&
!User.IsInRole(SystemRoles.Counselor))
var scope = currentUserDataScope.Current;
if (scope.Scope == DataScope.All) return source;
if (scope.Scope == DataScope.College)
return source.Where(x => x.CollegeId == scope.RestrictedCollegeId);
if (scope.Scope == DataScope.Class)
{
return null;
return source.Where(teacher => db.TeachingTasks.Any(task =>
task.Teachers.Any(item => item.TeacherId == teacher.Id) &&
task.Classes.Any(item =>
item.AdministrativeClass!.CounselorUserId == scope.UserId)));
}
return Guid.TryParse(User.FindFirstValue("college_id"), out var collegeId)
? collegeId
: Guid.Empty;
var userId = scope.UserId;
return scope.IsInRole(SystemRoles.Teacher)
? source.Where(x => x.UserId == userId)
: source.Where(_ => false);
}
private IQueryable<Student> ApplyStudentScope(IQueryable<Student> source)
{
var scope = currentUserDataScope.Current;
if (scope.Scope == DataScope.All) return source;
if (scope.Scope == DataScope.College)
{
return source.Where(x =>
x.AdministrativeClass!.Major!.CollegeId == scope.RestrictedCollegeId);
}
if (scope.Scope == DataScope.Class)
{
return source.Where(x =>
x.AdministrativeClass!.CounselorUserId == scope.UserId);
}
var userId = scope.UserId;
var isTeacher = scope.IsInRole(SystemRoles.Teacher);
var isStudent = scope.IsInRole(SystemRoles.Student);
return source.Where(student =>
isStudent && student.UserId == userId ||
isTeacher && db.TeachingTasks.Any(task =>
task.Teachers.Any(item => item.Teacher!.UserId == userId) &&
task.Classes.Any(item =>
item.AdministrativeClassId == student.AdministrativeClassId)));
}
private bool CanAccessCollege(Guid collegeId) =>
!GetScopedCollegeId().HasValue || GetScopedCollegeId() == collegeId;
currentUserDataScope.Current.CanAccessCollege(collegeId);
private async Task<ActionResult?> ValidateCollegeAsync(
Guid collegeId,