多角色账号按 All > College > Class > Self 自动取最高权限。
学院管理员限制在所属学院。 辅导员通过稳定账号 ID 绑定行政班,避免重名串班。 教师只能访问本人档案、授课课程和所授课学生。 学生只能访问本人档案及所在班级课程。 教师/学生角色会自动校验并绑定工号或学号档案。 超级管理员可在用户页面调整角色、学院、工号/学号,并预览生效后的数据范围。
This commit is contained in:
1 parent
0b463fa4f2
commit
bcc4d33bd5
26 files changed
+2333
-88
No files matched your search
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
当前已实现系统登录与角色权限、基础数据、用户管理、教师档案、学生档案、课程库、培养方案、教学任务、排课课表和首页统计。人员及课程列表支持组合筛选、服务端分页和完整增删改查;培养方案支持课程模块、专业年级版本、复制新版本、发布锁定和旧版本归档;教学任务支持学期课程开设、多教师、合班、容量校验、发布与结课;排课支持单双周、周次节次、教室容量、教师/行政班/教室冲突校验和版本化发布。
|
当前已实现系统登录与角色权限、基础数据、用户管理、教师档案、学生档案、课程库、培养方案、教学任务、排课课表和首页统计。人员及课程列表支持组合筛选、服务端分页和完整增删改查;培养方案支持课程模块、专业年级版本、复制新版本、发布锁定和旧版本归档;教学任务支持学期课程开设、多教师、合班、容量校验、发布与结课;排课支持单双周、周次节次、教室容量、教师/行政班/教室冲突校验和版本化发布。
|
||||||
|
|
||||||
|
权限采用后端强制校验的角色与数据范围模型。多角色账号按 `All > College > Class > Self` 取最高数据范围:校级角色可访问全校数据,院系管理员限定本学院,辅导员通过稳定的账号 ID 绑定所带行政班,教师和学生限定本人及当前教学关系;前端菜单和路由限制仅作为交互辅助,不替代 API 授权。
|
||||||
|
|
||||||
## 本地开发:热更新模式
|
## 本地开发:热更新模式
|
||||||
|
|
||||||
本地开发固定使用 SQLite。首次启动会自动创建 `src/Jiaowu.Api/data/jiaowu-dev.sqlite` 并写入演示组织数据。
|
本地开发固定使用 SQLite。首次启动会自动创建 `src/Jiaowu.Api/data/jiaowu-dev.sqlite` 并写入演示组织数据。
|
||||||
@@ -20,7 +22,17 @@ npm install
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
访问 `http://localhost:5173`,开发账号为 `admin`,密码为 `Admin@123456`。
|
访问 `http://localhost:5173`。本地开发会自动创建以下分级权限账号:
|
||||||
|
|
||||||
|
| 数据范围 | 账号 | 密码 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 超级管理员 | `admin` | `Admin@123456` |
|
||||||
|
| 校级教务 | `academic` | `Academic@123456` |
|
||||||
|
| 学院教务 | `college` | `College@123456` |
|
||||||
|
| 所带班级 | `counselor` | `Counselor@123456` |
|
||||||
|
| 教师本人 | `teacher` | `Teacher@123456` |
|
||||||
|
| 学生本人 | `student` | `Student@123456` |
|
||||||
|
| 领导查看 | `leader` | `Leader@123456` |
|
||||||
|
|
||||||
## 本地开发:单服务模式
|
## 本地开发:单服务模式
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,15 @@ try {
|
|||||||
$headers = @{ Authorization = "Bearer $($login.token)" }
|
$headers = @{ Authorization = "Bearer $($login.token)" }
|
||||||
$dashboard = Invoke-RestMethod -Uri 'http://localhost:5255/api/dashboard' -Headers $headers
|
$dashboard = Invoke-RestMethod -Uri 'http://localhost:5255/api/dashboard' -Headers $headers
|
||||||
$campuses = Invoke-RestMethod -Uri 'http://localhost:5255/api/base-data/campuses' -Headers $headers
|
$campuses = Invoke-RestMethod -Uri 'http://localhost:5255/api/base-data/campuses' -Headers $headers
|
||||||
|
$classes = Invoke-RestMethod -Uri 'http://localhost:5255/api/base-data/classes' -Headers $headers
|
||||||
|
$counselors = Invoke-RestMethod -Uri 'http://localhost:5255/api/base-data/counselors' -Headers $headers
|
||||||
|
$assignedClassCount = @($classes | Where-Object {
|
||||||
|
$null -ne $_.counselorUserId -and
|
||||||
|
@($counselors).id -contains $_.counselorUserId
|
||||||
|
}).Count
|
||||||
|
if ($assignedClassCount -lt 1) {
|
||||||
|
throw 'No administrative class is linked to a counselor account.'
|
||||||
|
}
|
||||||
$teachers = Invoke-RestMethod -Uri 'http://localhost:5255/api/personnel/teachers?page=1&pageSize=10' -Headers $headers
|
$teachers = Invoke-RestMethod -Uri 'http://localhost:5255/api/personnel/teachers?page=1&pageSize=10' -Headers $headers
|
||||||
$students = Invoke-RestMethod -Uri 'http://localhost:5255/api/personnel/students?page=1&pageSize=10' -Headers $headers
|
$students = Invoke-RestMethod -Uri 'http://localhost:5255/api/personnel/students?page=1&pageSize=10' -Headers $headers
|
||||||
$courses = Invoke-RestMethod -Uri 'http://localhost:5255/api/courses?page=1&pageSize=10' -Headers $headers
|
$courses = Invoke-RestMethod -Uri 'http://localhost:5255/api/courses?page=1&pageSize=10' -Headers $headers
|
||||||
@@ -82,6 +91,77 @@ try {
|
|||||||
-Uri "http://localhost:5255/api/schedules/plans/$($schedulePlans[0].id)" `
|
-Uri "http://localhost:5255/api/schedules/plans/$($schedulePlans[0].id)" `
|
||||||
-Headers $headers
|
-Headers $headers
|
||||||
}
|
}
|
||||||
|
$managedUsers = Invoke-RestMethod -Uri 'http://localhost:5255/api/users' -Headers $headers
|
||||||
|
$teacherAccount = @($managedUsers) |
|
||||||
|
Where-Object { $_.userName -eq 'teacher' } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($null -eq $teacherAccount) {
|
||||||
|
throw 'Development teacher account was not seeded.'
|
||||||
|
}
|
||||||
|
$teacherAccessBody = @{
|
||||||
|
staffNumber = $teacherAccount.staffNumber
|
||||||
|
collegeId = $teacherAccount.collegeId
|
||||||
|
roles = @('Teacher')
|
||||||
|
} | ConvertTo-Json
|
||||||
|
Invoke-RestMethod `
|
||||||
|
-Method Put `
|
||||||
|
-Uri "http://localhost:5255/api/users/$($teacherAccount.id)/roles" `
|
||||||
|
-Headers $headers `
|
||||||
|
-ContentType 'application/json' `
|
||||||
|
-Body $teacherAccessBody
|
||||||
|
|
||||||
|
$scopeScenarios = @(
|
||||||
|
@{
|
||||||
|
UserName = 'college'; Password = 'College@123456'; Scope = 'College'
|
||||||
|
Teachers = 2; Students = 3; Courses = 3
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
UserName = 'counselor'; Password = 'Counselor@123456'; Scope = 'Class'
|
||||||
|
Teachers = 1; Students = 3; Courses = 1
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
UserName = 'teacher'; Password = 'Teacher@123456'; Scope = 'Self'
|
||||||
|
Teachers = 1; Students = 3; Courses = 1
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
UserName = 'student'; Password = 'Student@123456'; Scope = 'Self'
|
||||||
|
Teachers = 0; Students = 1; Courses = 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
$scopeChecks = foreach ($scenario in $scopeScenarios) {
|
||||||
|
$scenarioLoginBody = @{
|
||||||
|
userName = $scenario.UserName
|
||||||
|
password = $scenario.Password
|
||||||
|
} | ConvertTo-Json
|
||||||
|
$scenarioLogin = Invoke-RestMethod `
|
||||||
|
-Method Post `
|
||||||
|
-Uri 'http://localhost:5255/api/auth/login' `
|
||||||
|
-ContentType 'application/json' `
|
||||||
|
-Body $scenarioLoginBody
|
||||||
|
$scenarioHeaders = @{ Authorization = "Bearer $($scenarioLogin.token)" }
|
||||||
|
$scenarioTeachers = Invoke-RestMethod `
|
||||||
|
-Uri 'http://localhost:5255/api/personnel/teachers?page=1&pageSize=10' `
|
||||||
|
-Headers $scenarioHeaders
|
||||||
|
$scenarioStudents = Invoke-RestMethod `
|
||||||
|
-Uri 'http://localhost:5255/api/personnel/students?page=1&pageSize=10' `
|
||||||
|
-Headers $scenarioHeaders
|
||||||
|
$scenarioCourses = Invoke-RestMethod `
|
||||||
|
-Uri 'http://localhost:5255/api/courses?page=1&pageSize=10' `
|
||||||
|
-Headers $scenarioHeaders
|
||||||
|
if ($scenarioLogin.user.effectiveDataScope -ne $scenario.Scope -or
|
||||||
|
$scenarioTeachers.total -ne $scenario.Teachers -or
|
||||||
|
$scenarioStudents.total -ne $scenario.Students -or
|
||||||
|
$scenarioCourses.total -ne $scenario.Courses) {
|
||||||
|
throw ("Data-scope check failed for {0}: scope={1}, teachers={2}, students={3}, courses={4}." -f
|
||||||
|
$scenario.UserName,
|
||||||
|
$scenarioLogin.user.effectiveDataScope,
|
||||||
|
$scenarioTeachers.total,
|
||||||
|
$scenarioStudents.total,
|
||||||
|
$scenarioCourses.total)
|
||||||
|
}
|
||||||
|
"$($scenario.UserName):$($scenario.Scope)"
|
||||||
|
}
|
||||||
|
|
||||||
$frontend = Invoke-WebRequest -Uri 'http://localhost:5255/' -TimeoutSec 5
|
$frontend = Invoke-WebRequest -Uri 'http://localhost:5255/' -TimeoutSec 5
|
||||||
$spaFallback = Invoke-WebRequest -Uri 'http://localhost:5255/base-data' -TimeoutSec 5
|
$spaFallback = Invoke-WebRequest -Uri 'http://localhost:5255/base-data' -TimeoutSec 5
|
||||||
$unknownApiParameters = @{
|
$unknownApiParameters = @{
|
||||||
@@ -97,6 +177,7 @@ try {
|
|||||||
User = $login.user.displayName
|
User = $login.user.displayName
|
||||||
Term = $dashboard.currentTerm.name
|
Term = $dashboard.currentTerm.name
|
||||||
Campuses = @($campuses).Count
|
Campuses = @($campuses).Count
|
||||||
|
CounselorAssignments = $assignedClassCount
|
||||||
Teachers = $teachers.total
|
Teachers = $teachers.total
|
||||||
Students = $students.total
|
Students = $students.total
|
||||||
Courses = $courses.total
|
Courses = $courses.total
|
||||||
@@ -105,6 +186,8 @@ try {
|
|||||||
TeachingTasks = $teachingTasks.total
|
TeachingTasks = $teachingTasks.total
|
||||||
Schedules = @($schedulePlans).Count
|
Schedules = @($schedulePlans).Count
|
||||||
ScheduleEntries = if ($null -ne $scheduleDetail) { @($scheduleDetail.entries).Count } else { 0 }
|
ScheduleEntries = if ($null -ne $scheduleDetail) { @($scheduleDetail.entries).Count } else { 0 }
|
||||||
|
AccessUpdate = $true
|
||||||
|
ScopeChecks = $scopeChecks -join ', '
|
||||||
StaticIndex = $frontend.Content.Contains('明序教务管理系统')
|
StaticIndex = $frontend.Content.Contains('明序教务管理系统')
|
||||||
SpaFallback = $spaFallback.StatusCode
|
SpaFallback = $spaFallback.StatusCode
|
||||||
ApiNotFound = $unknownApi.StatusCode
|
ApiNotFound = $unknownApi.StatusCode
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ public sealed class AuthController(
|
|||||||
user.UserName!,
|
user.UserName!,
|
||||||
user.DisplayName,
|
user.DisplayName,
|
||||||
roles,
|
roles,
|
||||||
user.CollegeId));
|
user.CollegeId,
|
||||||
|
EffectiveDataScopeResolver.Resolve(roles).ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@@ -67,12 +68,14 @@ public sealed class AuthController(
|
|||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var roles = await userManager.GetRolesAsync(user);
|
||||||
return new CurrentUserResponse(
|
return new CurrentUserResponse(
|
||||||
user.Id,
|
user.Id,
|
||||||
user.UserName!,
|
user.UserName!,
|
||||||
user.DisplayName,
|
user.DisplayName,
|
||||||
await userManager.GetRolesAsync(user),
|
roles,
|
||||||
user.CollegeId);
|
user.CollegeId,
|
||||||
|
EffectiveDataScopeResolver.Resolve(roles).ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,4 +90,5 @@ public sealed record CurrentUserResponse(
|
|||||||
string UserName,
|
string UserName,
|
||||||
string DisplayName,
|
string DisplayName,
|
||||||
IEnumerable<string> Roles,
|
IEnumerable<string> Roles,
|
||||||
Guid? CollegeId);
|
Guid? CollegeId,
|
||||||
|
string EffectiveDataScope);
|
||||||
Loaded 3 of 26 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user