diff --git a/.gitignore b/.gitignore
index 3eab8f8..8b26b32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,9 @@ src/main/resources/application-prod.yml
# IDE 运行日志
.run/
+# 本地排查脚本(含平台 app-secret,禁止入库)
+.scratch/
+
# 备份与残留
*.bak
*.orig
diff --git a/README.md b/README.md
index 9d7399e..26d1036 100644
--- a/README.md
+++ b/README.md
@@ -239,7 +239,6 @@ agent:
dormnotice: http://base.yunhaixingtong.com/dormnotice
familyactivity: http://base.yunhaixingtong.com/familyactivity
coursehomework: http://base.yunhaixingtong.com/coursehomework
- resourcelibrary: http://base.yunhaixingtong.com/resourcelibrary
profession: http://base.yunhaixingtong.com/profession
```
@@ -372,6 +371,7 @@ HTTP 状态都是 **200**,但响应体固定是:
> `http://base.yunhaixingtong.com/resourcelibrary`,实测该路径 **404**;
> 前端 `ResourceLibraryFront/.env.dev` 里资源库真实主机是 `https://base.bestsch.com/BschZyk`。
> 需与资源库团队确认正确的 base(含 context)后再改。
+> **★ 2026-10-12 处理**:该模块(`ResourceTools` 及配置)已整体移除,**尚未接通**,待部署位置确认后再接回。
### 11.6 复核补记(2026-10-10 19:0x)——「请假可以了、其他模块没接通」
@@ -422,9 +422,12 @@ HTTP 状态都是 **200**,但响应体固定是:
| dormnotice | 宿舍通知箱 | `/par/notices` |
| familyactivity | 活动广场 | `/par/activities` |
| coursehomework | 今日作业 | `/stu/today` |
-| resourcelibrary | 资源管理 | `/resource` |
| profession | 发展首页 | `/home` |
+> ★ 2026-10-12:`resourcelibrary`(资源库)已**按用户要求暂时移除**(该模块前端在 base 域 **404**、真实主机疑为
+> `https://base.bestsch.com/BschZyk`,尚未接通)——已从 `ResourceTools`、`agent.modules`、
+> `agent.module-landing` 与前端 `MODULE_*` 表中一并去掉;待其部署位置确认后再接回。
+
运维可在 `application.yml` 用 `agent.module-landing.<模块>=<路由>` 覆盖;
工具自身声明(`write(...)` / `readAt(...)`)优先级更高。
diff --git a/src/main/java/com/yunhai/agent/auth/AgentRole.java b/src/main/java/com/yunhai/agent/auth/AgentRole.java
index cf54a05..1f0cd1c 100644
--- a/src/main/java/com/yunhai/agent/auth/AgentRole.java
+++ b/src/main/java/com/yunhai/agent/auth/AgentRole.java
@@ -3,18 +3,27 @@ package com.yunhai.agent.auth;
/**
* 智能体侧的角色分类(由平台 {@code BoaUser} 解析而来)。
*
- *
用途:决定「这个身份能用哪些工具」——学生侧(请假申请 / 交作业 / 报名活动 / 看公告)
- * 与教职工侧(发公告 / 审批请假 / 发布资源 / 专业发展)严格分开。
+ *
用途:决定「这个身份能用哪些工具」——学生侧(请假申请 / 交作业 / 报名活动 / 看公告)、
+ * 家长侧(报名活动 / 宿舍通知回执)与教职工侧(发公告 / 审批请假 / 专业发展)严格分开。
+ *
+ *
对齐平台 UserBase 的「用户身份」字典(门户/GetUserInfo 的 {@code currentRole.roleId}):
+ *
+ * 1 老师 2 校领导 3 学生 4 家长 5 局领导 6 工作号 7 管理员
+ *
+ * 映射到本枚举:1/6 → 教师,3 → 学生,4 → 家长,2/5/7 → 管理员。
*/
public enum AgentRole {
/** 学生 */
STUDENT("学生"),
- /** 教师(含班主任) */
+ /** 教师(含班主任、工作号/教职工) */
TEACHER("教师"),
- /** 管理员(校管 / 分管领导 / 教务 / 校长) */
+ /** 家长:家校活动的报名方、宿舍通知的回执方 */
+ PARENT("家长"),
+
+ /** 管理员(校领导 / 局领导 / 管理员) */
ADMIN("管理员"),
/** 访客:未登录或无法识别角色,只能使用公共只读能力 */
diff --git a/src/main/java/com/yunhai/agent/auth/RolePolicy.java b/src/main/java/com/yunhai/agent/auth/RolePolicy.java
index 48bf62f..2d597bb 100644
--- a/src/main/java/com/yunhai/agent/auth/RolePolicy.java
+++ b/src/main/java/com/yunhai/agent/auth/RolePolicy.java
@@ -14,13 +14,14 @@ import java.util.Set;
* 集中成一张可读的表比散落在 7 个模块的工具定义里更容易审阅与改动;
* 若某个工具需要就近声明,用 {@code ToolDef.allow(...)} 显式指定即可(本策略会尊重已有声明)。
*
- * 三类名单:
+ *
名单:
*
* - {@link #STAFF_ONLY} —— 教职工侧(教师 + 管理员):审批、发布、统计、宿管、专业发展;
- * - {@link #STUDENT_ONLY} —— 学生侧:交作业、报名活动、销假;
+ * - {@link #STUDENT_ONLY} —— 学生侧:交作业、销假;
+ * - {@link #STUDENT_OR_PARENT} —— 家校活动报名:家长与学生都可发起;
* - {@link #SHARED} —— 学生与教职工都可能发起的:请假申请/撤销。
*
- * 未列入任何名单的工具 = 所有已识别角色都可用(如各类只读查询)。
+ * 未列入任何名单的工具 = 所有已识别角色都可用(如各类只读查询、宿舍通知回执)。
*/
public final class RolePolicy {
@@ -43,10 +44,14 @@ public final class RolePolicy {
/** 学生专属 */
private static final Set STUDENT_ONLY = new HashSet<>(Arrays.asList(
"homework.myList", "homework.detail", "homework.result", "homework.submit",
- "activity.signup", "activity.cancelSignup",
"leave.reportBack"
));
+ /** 学生与家长共用(家校活动报名由家长或学生发起) */
+ private static final Set STUDENT_OR_PARENT = new HashSet<>(Arrays.asList(
+ "activity.signup", "activity.cancelSignup"
+ ));
+
/** 学生与教职工共用(发起/撤销自己的事项) */
private static final Set SHARED = new HashSet<>(Arrays.asList(
"leave.submit", "leave.withdraw"
@@ -71,6 +76,8 @@ public final class RolePolicy {
d.allow(AgentRole.TEACHER, AgentRole.ADMIN);
} else if (STUDENT_ONLY.contains(n)) {
d.allow(AgentRole.STUDENT);
+ } else if (STUDENT_OR_PARENT.contains(n)) {
+ d.allow(AgentRole.STUDENT, AgentRole.PARENT);
} else if (SHARED.contains(n)) {
d.allow(AgentRole.STUDENT, AgentRole.TEACHER, AgentRole.ADMIN);
}
diff --git a/src/main/java/com/yunhai/agent/auth/RoleResolver.java b/src/main/java/com/yunhai/agent/auth/RoleResolver.java
index d0d8827..6301057 100644
--- a/src/main/java/com/yunhai/agent/auth/RoleResolver.java
+++ b/src/main/java/com/yunhai/agent/auth/RoleResolver.java
@@ -4,6 +4,7 @@ import com.yunhai.agent.config.AgentSettings;
import com.yunhai.platform.utils.model.BoaRoleInfo;
import com.yunhai.platform.utils.model.BoaUser;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.LinkedHashSet;
@@ -19,17 +20,28 @@ import java.util.concurrent.ConcurrentHashMap;
* 平台里一个账号可以同时挂多个身份:{@code BoaUser.roleIds} 是全部身份的角色号,
* {@code BoaUser.currentRole} 是用户在门户里当前激活的那个身份
* (等价于 {@code YschBaseOpenApi.getCurrentIdentity(userId)})。
- * 例如实测账号 10008 同时是「学生 + 管理员 + 教师」——若把所有身份并集,
- * 权限只增不减、能力边界完全模糊(所有人都是全量 54 项)。
- * 因此这里只认当前身份:与学校门户、其它业务模块的口径保持一致。
+ * 实测账号 10008(李晶/lijing)挂着「老师(1)」与「管理员(7)」,门户当前身份是「老师」——
+ * 若把所有身份并集,权限只增不减、能力边界完全模糊。
+ * 因此这里只认当前身份:与学校门户、其它业务模块的口径保持一致(2026-10-10 改)。
*
- * 判定规则(角色号可在 {@code agent.roles.*} 配置)
+ * ★ 角色号实测校准(2026-10-12,采样 200+ 账号的 currentRole / roles 取得)
+ *
+ * 1 老师 2 校领导 3 学生 4 家长 5 局领导 6 工作号 7 管理员
+ * → 本枚举:1/6 = 教师,3 = 学生,4 = 家长,2/5/7 = 管理员
+ *
+ * 数据源与门户顶栏、{@code /notice/api/base/GetUserInfo} 一致(都是 {@code BoaUser.currentRole})。
+ * 本工程此前的默认映射(student=1 / teacher=2,3 / admin=4..7)是猜的,
+ * 把 roleId=1 误当学生 —— 于是「李晶(老师)」在助手里显示成「学生」。
+ * 现已按 {@code agent.roles.*} 配置修正(默认值见 {@link AgentSettings.Roles})。
+ *
+ * 判定顺序
*
- * - 优先取 {@code currentRole.getRoleId()},命中 {@code agent.roles.student/teacher/admin}
- * 即获得对应角色,且只获得这一个身份;
- * - 当前身份是管理员时,按 {@code agent.roles.admin-also-teacher=true} 追加教师能力;
- * - 当前身份号未在配置里声明(或账号没有当前身份)→ 回退按 {@code roleIds}/{@code type}
- * 的旧口径判定,并打 WARN 日志提示补配置;
+ * - 当前身份:先按 {@code roleId} 命中 {@code agent.roles.*};
+ * 角色号没配到,再按 {@code roleName} 关键词兜底(老师/教师/班主任 → 教师,
+ * 管理员/校长/主任/教务… → 管理员,家长 → 家长,学生 → 学生);命中即只返回该身份;
+ * - 当前身份是管理员时,按 {@code admin-also-teacher=true} 追加教师能力;
+ * - 无当前身份(或当前身份识别不出)→ 回退按 {@code roleIds} 逐个匹配配置;
+ * - 仍然什么都没识别出 → 按 {@code BoaUser.type} 兜底(0=学生、2=教职工、1=超管);
* - 什么都识别不出 → 访客(仅公共只读能力)。
*
*/
@@ -37,6 +49,12 @@ import java.util.concurrent.ConcurrentHashMap;
@Component
public class RoleResolver {
+ /** 角色名关键词:命中即判定(角色号未配置时的兜底,跨学校更稳) */
+ private static final String[] ADMIN_WORDS = {
+ "管理", "校长", "园长", "局长", "领导", "主任", "教务", "督导", "书记", "党委"
+ };
+ private static final String[] TEACHER_WORDS = {"教师", "老师", "班主任", "教工"};
+
private final AgentSettings settings;
/** 每个用户上次记录过的身份描述,避免每请求刷屏(只在身份变化时打一次 INFO) */
@@ -58,20 +76,21 @@ public class RoleResolver {
// 1) ★ 平台「当前身份」优先 —— 一个账号只生效一个身份
BoaRoleInfo current = user.getCurrentRole();
- if (current != null && current.getRoleId() != null) {
- Set hit = match(current.getRoleId(), cfg);
+ if (current != null && (current.getRoleId() != null || StringUtils.isNotBlank(current.getRoleName()))) {
+ Set hit = matchCurrent(current, cfg);
if (!hit.isEmpty()) {
applyAdminAlsoTeacher(hit, cfg);
logResolved(user, current, hit, "当前身份");
return hit;
}
- log.warn("[身份] 当前身份 roleId={} roleName={} 未命中 agent.roles.* 配置,"
- + "暂按账号全部身份兜底(userId={}, roleIds={})—— 建议把该角色号补进配置",
+ log.warn("[身份] 当前身份 roleId={} roleName={} 既未命中 agent.roles.* 配置、"
+ + "角色名也不含可识别关键词,暂按账号全部身份兜底(userId={}, roleIds={})"
+ + "—— 建议把该角色号补进配置",
current.getRoleId(), current.getRoleName(), user.getId(), user.getRoleIds());
}
- // 2) 兜底:没有当前身份,或当前身份号未配置
- if (user.getType() != null && user.getType() == 1) {
+ // 2) 兜底:账号全部角色号 + 超管口径
+ if (user.getType() != null && cfg.getAdminUserTypes().contains(user.getType())) {
roles.add(AgentRole.ADMIN);
}
List roleIds = user.getRoleIds();
@@ -82,13 +101,18 @@ public class RoleResolver {
}
}
}
- applyAdminAlsoTeacher(roles, cfg);
+ // 3) 仍未识别出任何角色 → 按 BoaUser.type 兜底(学生没有角色号,只能靠这一条)
+ if (roles.isEmpty()) {
+ roles.addAll(matchByUserType(user.getType(), cfg));
+ }
+
+ applyAdminAlsoTeacher(roles, cfg);
if (roles.isEmpty()) {
roles.add(AgentRole.GUEST);
}
logResolved(user, current, roles,
- current == null ? "无当前身份,按账号全部身份" : "当前身份未配置,按账号全部身份");
+ current == null ? "无当前身份,按账号全部身份" : "当前身份未识别,按账号全部身份");
return roles;
}
@@ -107,6 +131,15 @@ public class RoleResolver {
return sb.toString();
}
+ /** 当前身份 → 智能体角色:角色号配置优先,角色名关键词兜底 */
+ private Set matchCurrent(BoaRoleInfo current, AgentSettings.Roles cfg) {
+ Set hit = match(current.getRoleId(), cfg);
+ if (hit.isEmpty() && StringUtils.isNotBlank(current.getRoleName())) {
+ hit = matchByRoleName(current.getRoleName().trim());
+ }
+ return hit;
+ }
+
/** 单个角色号 → 智能体角色(一个号理论上可命中多个分组,全部保留) */
private Set match(Integer roleId, AgentSettings.Roles cfg) {
Set hit = new LinkedHashSet<>();
@@ -122,6 +155,51 @@ public class RoleResolver {
if (contains(cfg.getStudent(), roleId)) {
hit.add(AgentRole.STUDENT);
}
+ if (contains(cfg.getParent(), roleId)) {
+ hit.add(AgentRole.PARENT);
+ }
+ return hit;
+ }
+
+ /** 角色名 → 智能体角色(角色号没配到时的兜底,避免「未识别 → 访客」) */
+ private Set matchByRoleName(String name) {
+ Set hit = new LinkedHashSet<>();
+ if (StringUtils.isBlank(name)) {
+ return hit;
+ }
+ if (containsAny(name, ADMIN_WORDS)) {
+ hit.add(AgentRole.ADMIN);
+ }
+ if (containsAny(name, TEACHER_WORDS)) {
+ hit.add(AgentRole.TEACHER);
+ }
+ if (name.contains("学生")) {
+ hit.add(AgentRole.STUDENT);
+ }
+ if (name.contains("家长")) {
+ hit.add(AgentRole.PARENT);
+ }
+ return hit;
+ }
+
+ /** {@code BoaUser.type} → 智能体角色(学生没有角色号,这是学生的唯一入口) */
+ private Set matchByUserType(Integer type, AgentSettings.Roles cfg) {
+ Set hit = new LinkedHashSet<>();
+ if (type == null) {
+ return hit;
+ }
+ if (contains(cfg.getStudentUserTypes(), type)) {
+ hit.add(AgentRole.STUDENT);
+ }
+ if (contains(cfg.getTeacherUserTypes(), type)) {
+ hit.add(AgentRole.TEACHER);
+ }
+ if (contains(cfg.getAdminUserTypes(), type)) {
+ hit.add(AgentRole.ADMIN);
+ }
+ if (contains(cfg.getParentUserTypes(), type)) {
+ hit.add(AgentRole.PARENT);
+ }
return hit;
}
@@ -148,6 +226,15 @@ public class RoleResolver {
}
}
+ private boolean containsAny(String name, String[] words) {
+ for (String w : words) {
+ if (name.contains(w)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean contains(List list, int v) {
return list != null && list.contains(v);
}
diff --git a/src/main/java/com/yunhai/agent/config/AgentSettings.java b/src/main/java/com/yunhai/agent/config/AgentSettings.java
index c08f20f..aa6149e 100644
--- a/src/main/java/com/yunhai/agent/config/AgentSettings.java
+++ b/src/main/java/com/yunhai/agent/config/AgentSettings.java
@@ -51,7 +51,6 @@ public class AgentSettings {
* dormnotice → /par/notices 宿舍通知箱
* familyactivity → /par/activities 活动广场
* coursehomework → /stu/today 今日作业
- * resourcelibrary → /resource 资源管理
* profession → /home 发展首页
*
* 运维可在 {@code application.yml} 用 {@code agent.module-landing.<模块>=<路由>} 覆盖单个模块;
@@ -67,7 +66,6 @@ public class AgentSettings {
m.put("dormnotice", "/par/notices");
m.put("familyactivity", "/par/activities");
m.put("coursehomework", "/stu/today");
- m.put("resourcelibrary", "/resource");
m.put("profession", "/home");
return m;
}
@@ -173,16 +171,34 @@ public class AgentSettings {
*/
@Data
public static class Roles {
- /** 学生角色号(默认 1;请按 UserBase 校准) */
- private List student = new ArrayList<>(Arrays.asList(1));
+ /** 学生角色号。★ 实测校准(2026-10-12):{@code 3 = 学生} */
+ private List student = new ArrayList<>(Arrays.asList(3));
- /** 教师角色号(默认 2=教师、3=班主任;请按 UserBase 校准) */
- private List teacher = new ArrayList<>(Arrays.asList(2, 3));
+ /** 教师角色号。★ 实测校准:{@code 1 = 老师}、{@code 6 = 工作号}(教职工工号身份) */
+ private List teacher = new ArrayList<>(Arrays.asList(1, 6));
- /** 管理员角色号(默认 4=校管、5=分管领导、6=教务、7=校长;请按 UserBase 校准) */
- private List admin = new ArrayList<>(Arrays.asList(4, 5, 6, 7));
+ /** 家长角色号。★ 实测校准:{@code 4 = 家长} */
+ private List parent = new ArrayList<>(Arrays.asList(4));
- /** 管理员是否天然拥有教师能力(默认 true:校管/领导可用教师侧工具) */
+ /** 管理员角色号。★ 实测校准:{@code 2 = 校领导}、{@code 5 = 局领导}、{@code 7 = 管理员} */
+ private List admin = new ArrayList<>(Arrays.asList(2, 5, 7));
+
+ /**
+ * 兜底口径之一:账号既没有「当前身份」、也没挂任何已识别的角色号时,
+ * 按 {@code BoaUser.type} 判定。实测 {@code 0 = 学生}。
+ */
+ private List studentUserTypes = new ArrayList<>(Arrays.asList(0));
+
+ /** 兜底口径之二:{@code 2 = 教职工} */
+ private List teacherUserTypes = new ArrayList<>(Arrays.asList(2));
+
+ /** 兜底口径之三:{@code 1 = 平台超管} */
+ private List adminUserTypes = new ArrayList<>(Arrays.asList(1));
+
+ /** 兜底口径之四:家长(平台暂未见稳定的 {@code type} 口径,留空待校准) */
+ private List parentUserTypes = new ArrayList<>();
+
+ /** 管理员是否天然拥有教师能力(默认 true:校领导/局领导/管理员可用教师侧工具) */
private boolean adminAlsoTeacher = true;
}
}
diff --git a/src/main/java/com/yunhai/agent/service/AgentOrchestrator.java b/src/main/java/com/yunhai/agent/service/AgentOrchestrator.java
index 7292aac..4750de2 100644
--- a/src/main/java/com/yunhai/agent/service/AgentOrchestrator.java
+++ b/src/main/java/com/yunhai/agent/service/AgentOrchestrator.java
@@ -940,7 +940,6 @@ public class AgentOrchestrator {
case "dormnotice": return "宿舍通知";
case "familyactivity": return "家校活动";
case "coursehomework": return "课程作业";
- case "resourcelibrary": return "资源库";
case "profession": return "教师专业发展";
default: return module;
}
diff --git a/src/main/java/com/yunhai/agent/tool/ToolRegistry.java b/src/main/java/com/yunhai/agent/tool/ToolRegistry.java
index eb523a7..e8e2afc 100644
--- a/src/main/java/com/yunhai/agent/tool/ToolRegistry.java
+++ b/src/main/java/com/yunhai/agent/tool/ToolRegistry.java
@@ -10,7 +10,6 @@ import com.yunhai.agent.tool.defs.DormNoticeTools;
import com.yunhai.agent.tool.defs.HomeworkTools;
import com.yunhai.agent.tool.defs.LeaveTools;
import com.yunhai.agent.tool.defs.ProfessionTools;
-import com.yunhai.agent.tool.defs.ResourceTools;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@@ -75,7 +74,6 @@ public class ToolRegistry {
register(DormNoticeTools.defs());
register(ActivityTools.defs());
register(HomeworkTools.defs());
- register(ResourceTools.defs());
register(ProfessionTools.defs());
// 套用「学生 / 教师」角色能力策略(未显式声明角色的工具在此落定)
RolePolicy.apply(tools.values());
diff --git a/src/main/java/com/yunhai/agent/tool/defs/ResourceTools.java b/src/main/java/com/yunhai/agent/tool/defs/ResourceTools.java
deleted file mode 100644
index faf5c7c..0000000
--- a/src/main/java/com/yunhai/agent/tool/defs/ResourceTools.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.yunhai.agent.tool.defs;
-
-import com.yunhai.agent.tool.ToolDef;
-import com.yunhai.agent.tool.ToolParam;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 资源库模块工具 —— 后端 ResourceLibrary,前缀 /api/resource、/api/category、/api/textbook。
- */
-public final class ResourceTools {
-
- public static final String MODULE = "resourcelibrary";
-
- private ResourceTools() {
- }
-
- public static List defs() {
- List list = new ArrayList<>();
-
- list.add(ToolDef.of("resource.search", MODULE, "检索资源",
- "按关键词/分类/学科检索教学资源(课件、教案、试题等)。用户问“找一份 XX 的课件”“有没有 XX 资料”时用它。",
- "GET", "/api/resource/List")
- .p(ToolParam.optional("keyword", "string", "关键词:资源名称 / 编码 / 上传人"),
- ToolParam.optional("categoryId", "integer", "分类节点 id(含子节点)"),
- ToolParam.optional("subject", "string", "学科"),
- ToolParam.optional("type", "string", "资源类型"),
- ToolParam.optional("orderBy", "string", "排序:time_desc 最新 / download_desc 下载最多 / view_desc 浏览最多"),
- ToolParam.optional("pageIndex", "integer", "页码").dft("1"),
- ToolParam.optional("pageSize", "integer", "每页条数").dft("10"))
- .readAt("/resource"));
-
- list.add(ToolDef.of("resource.detail", MODULE, "资源详情",
- "查看一份资源的详细信息(分类、学科、格式、下载量等)。",
- "GET", "/api/resource/Detail")
- .p(ToolParam.required("id", "integer", "资源 id,来自 resource.search"))
- .readAt("/resource"));
-
- list.add(ToolDef.of("resource.categoryTree", MODULE, "资源分类树",
- "资源分类树(学段 → 学科 → 年级册次 → 章节),用于按分类筛选资源。",
- "GET", "/api/category/Tree")
- .readAt("/resource"));
-
- list.add(ToolDef.of("resource.collect", MODULE, "收藏资源",
- "收藏或取消收藏一份资源。",
- "POST_FORM", "/api/resource/Collect")
- .p(ToolParam.required("id", "integer", "资源 id")
- .label("资源"))
- .write("/resource/collect"));
-
- list.add(ToolDef.of("resource.textbookOptions", MODULE, "教材选项",
- "查询可选的教材版本 / 学科 / 出版社。",
- "GET", "/api/textbook/Options")
- .readAt("/textbook"));
-
- return list;
- }
-}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 32a20bd..daeab94 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -109,11 +109,22 @@ agent:
browser-exec:
enabled: true
- # ★ 角色映射:按本校 UserBase 角色体系校准(BoaUser.type=1 恒为管理员)
+ # ★ 角色映射:按本校用户中心(UserBase)「用户身份」字典实测校准 ★
+ # 平台身份号 = 门户顶栏 / GetUserInfo 的 currentRole.roleId(采样 200+ 账号取得):
+ # 1 = 老师 2 = 校领导 3 = 学生 4 = 家长 5 = 局领导 6 = 工作号 7 = 管理员
+ # 映射到助手角色:1/6 → 教师,3 → 学生,4 → 家长,2/5/7 → 管理员
+ # ⚠️ 此前默认(student=1 / teacher=2,3 / admin=4..7)是猜的,把 roleId=1 当成学生,
+ # 于是「李晶(老师)」在助手里被显示成「学生」——这是本次修正的根因。
roles:
- student: [1] # 学生
- teacher: [2, 3] # 教师 / 班主任
- admin: [4, 5, 6, 7] # 校管 / 分管领导 / 教务 / 校长
+ student: [3] # 学生
+ teacher: [1, 6] # 老师 / 工作号(教职工)
+ parent: [4] # 家长
+ admin: [2, 5, 7] # 校领导 / 局领导 / 管理员
+ # 账号既无「当前身份」、也没挂已识别角色号时的兜底口径(按 BoaUser.type)
+ student-user-types: [0] # 学生
+ teacher-user-types: [2] # 教职工
+ admin-user-types: [1] # 平台超管
+ parent-user-types: [] # 家长(平台暂未见稳定口径,留空待校准)
admin-also-teacher: true # 管理员同时拥有教师侧能力
# --- 业务模块基地址(key 必须与工具定义里的 MODULE 常量一致)---
@@ -124,7 +135,6 @@ agent:
dormnotice: http://base.yunhaixingtong.com/dormnotice
familyactivity: http://base.yunhaixingtong.com/familyactivity
coursehomework: http://base.yunhaixingtong.com/coursehomework
- resourcelibrary: http://base.yunhaixingtong.com/resourcelibrary
profession: http://base.yunhaixingtong.com/profession
# --- 模块默认落地页(深链兜底)---
@@ -139,7 +149,6 @@ agent:
dormnotice: /par/notices # 宿舍通知箱
familyactivity: /par/activities # 活动广场
coursehomework: /stu/today # 今日作业
- resourcelibrary: /resource # 资源管理
profession: /home # 发展首页
# 密钥链验签依赖 Redis(sign:app:*)。关掉 Redis 健康指标:Redis 抖动不应把 /actuator/health
diff --git a/src/test/java/com/yunhai/agent/RolePermissionTest.java b/src/test/java/com/yunhai/agent/RolePermissionTest.java
index 7bbb0c7..fbe7f8b 100644
--- a/src/test/java/com/yunhai/agent/RolePermissionTest.java
+++ b/src/test/java/com/yunhai/agent/RolePermissionTest.java
@@ -73,64 +73,100 @@ class RolePermissionTest {
@Test
void multiIdentityAccountUsesCurrentRoleOnly() {
- // 实测账号形态:10008 同时挂着 学生(1) + 教师(2) + 管理员(4),当前身份 = 学生
- BoaUser u = userWithCurrent(10008, 1, "学生", 1, 2, 4);
+ // 实测账号形态(李晶 / lijing, userId=10008):挂着 老师(1) + 管理员(7),门户当前身份 = 老师
+ BoaUser u = userWithCurrent(10008, 1, "老师", 1, 7);
Set roles = resolver.resolve(u);
- assertTrue(roles.contains(AgentRole.STUDENT), "当前身份是学生,应识别为学生");
- assertFalse(roles.contains(AgentRole.TEACHER), "不应因账号另挂教师身份而获得教师能力");
+ assertTrue(roles.contains(AgentRole.TEACHER), "当前身份是老师,应识别为教师");
assertFalse(roles.contains(AgentRole.ADMIN), "不应因账号另挂管理员身份而获得管理员能力");
+ assertFalse(roles.contains(AgentRole.STUDENT), "老师不是学生");
List visible = registry.visibleTools(null, roles);
- assertTrue(visible(visible, "homework.submit"), "学生当前身份应可用 homework.submit");
- assertFalse(visible(visible, "notice.publish"), "学生当前身份不应看到 notice.publish");
- assertFalse(visible(visible, "profession.myPoints"), "学生当前身份不应看到 profession.*");
+ assertTrue(visible(visible, "notice.publish"), "教师当前身份应可用 notice.publish");
+ assertTrue(visible(visible, "leave.audit"), "教师当前身份应可用 leave.audit");
+ assertFalse(visible(visible, "homework.submit"), "教师当前身份不应看到学生交作业");
+ assertFalse(visible(visible, "activity.signup"), "教师当前身份不应看到学生报名活动");
}
@Test
void switchingCurrentRoleSwitchesCapabilities() {
- BoaUser u = userWithCurrent(10008, 1, "学生", 1, 2, 4);
- assertTrue(resolver.resolve(u).contains(AgentRole.STUDENT));
+ // 同一账号,在门户里把当前身份从「老师」切到「管理员」→ 同一账号能力随之变化
+ BoaUser u = userWithCurrent(10008, 1, "老师", 1, 7);
+ assertTrue(resolver.resolve(u).contains(AgentRole.TEACHER));
- // 门户把当前身份切成教师 → 同一账号能力随之变化
- u.getCurrentRole().setRoleId(2);
- u.getCurrentRole().setRoleName("教师");
- Set asTeacher = resolver.resolve(u);
- assertTrue(asTeacher.contains(AgentRole.TEACHER));
- assertFalse(asTeacher.contains(AgentRole.STUDENT));
- List teacherTools = registry.visibleTools(null, asTeacher);
- assertTrue(visible(teacherTools, "notice.publish"), "教师当前身份应可用 notice.publish");
- assertFalse(visible(teacherTools, "homework.submit"), "教师当前身份不应看到 homework.submit");
-
- // 切成管理员 → 管理员 + 教师(admin-also-teacher 默认 true)
- u.getCurrentRole().setRoleId(4);
+ u.getCurrentRole().setRoleId(7);
u.getCurrentRole().setRoleName("管理员");
Set asAdmin = resolver.resolve(u);
assertTrue(asAdmin.contains(AgentRole.ADMIN));
- assertTrue(asAdmin.contains(AgentRole.TEACHER));
+ assertTrue(asAdmin.contains(AgentRole.TEACHER), "管理员默认兼有教师能力(admin-also-teacher)");
assertFalse(asAdmin.contains(AgentRole.STUDENT), "管理员当前身份不应含学生专属能力");
+
+ // 切回老师 → 管理员能力消失
+ u.getCurrentRole().setRoleId(1);
+ u.getCurrentRole().setRoleName("老师");
+ Set back = resolver.resolve(u);
+ assertTrue(back.contains(AgentRole.TEACHER));
+ assertFalse(back.contains(AgentRole.ADMIN), "切回老师后不应还留着管理员能力");
}
@Test
void unknownCurrentRoleFallsBackToAccountRoles() {
- // 当前身份号未在 agent.roles.* 里声明 → 回退按账号全部身份判定(避免误判成访客)
- BoaUser u = userWithCurrent(200, 999, "自定义角色", 1, 2);
+ // 当前身份号未在 agent.roles.* 里声明、角色名也不是常见词 → 回退按账号全部身份判定(避免误判成访客)
+ BoaUser u = userWithCurrent(200, 999, "自定义角色", 1, 7);
Set roles = resolver.resolve(u);
- assertTrue(roles.contains(AgentRole.STUDENT), "回退后应保留账号的学生身份");
assertTrue(roles.contains(AgentRole.TEACHER), "回退后应保留账号的教师身份");
+ assertTrue(roles.contains(AgentRole.ADMIN), "回退后应保留账号的管理员身份");
+ }
+
+ @Test
+ void roleNameKeywordFallbackWhenRoleIdNotConfigured() {
+ // 角色号没配到、但角色名可识别 → 按名称判定,不应掉成访客
+ assertTrue(resolver.resolve(userWithCurrent(300, 88, "班主任", 88)).contains(AgentRole.TEACHER),
+ "「班主任」应按名称识别为教师");
+ assertTrue(resolver.resolve(userWithCurrent(301, 99, "教务主任", 99)).contains(AgentRole.ADMIN),
+ "「教务主任」应按名称识别为管理员");
+ assertTrue(resolver.resolve(userWithCurrent(303, 77, "家长", 77)).contains(AgentRole.PARENT),
+ "「家长」应按名称识别为家长");
+ BoaUser stu = user(302);
+ stu.setType(0);
+ assertTrue(resolver.resolve(stu).contains(AgentRole.STUDENT), "type=0 应识别为学生");
+ }
+
+ @Test
+ void platformRoleDictionaryMapsToExpectedRoles() {
+ // 平台「用户身份」字典:1老师 2校领导 3学生 4家长 5局领导 6工作号 7管理员
+ assertTrue(resolver.resolve(userWithCurrent(11, 1, "老师", 1)).contains(AgentRole.TEACHER));
+ assertTrue(resolver.resolve(userWithCurrent(13, 3, "学生", 3)).contains(AgentRole.STUDENT));
+ assertTrue(resolver.resolve(userWithCurrent(14, 4, "家长", 4)).contains(AgentRole.PARENT));
+ // 6 = 工作号(教职工)
+ Set gs = resolver.resolve(userWithCurrent(16, 6, "工作号", 6));
+ assertTrue(gs.contains(AgentRole.TEACHER), "roleId=6(工作号)应识别为教师");
+ assertFalse(gs.contains(AgentRole.ADMIN), "工作号不是管理员");
+ // 2=校领导、5=局领导、7=管理员 → 管理员,并兼有教师能力
+ for (int rid : new int[]{2, 5, 7}) {
+ Set roles = resolver.resolve(userWithCurrent(400 + rid, rid, "领导", rid));
+ assertTrue(roles.contains(AgentRole.ADMIN), "roleId=" + rid + " 应识别为管理员");
+ assertTrue(roles.contains(AgentRole.TEACHER), "管理员应兼有教师能力");
+ assertFalse(roles.contains(AgentRole.STUDENT), "管理员不是学生");
+ }
}
@Test
void accountWithoutCurrentRoleStillResolvesByRoleIds() {
// 老口径:平台没下发 currentRole 时,仍按 roleIds 判定(不能变成访客)
- Set roles = resolver.resolve(user(300, 2));
- assertTrue(roles.contains(AgentRole.TEACHER));
+ assertTrue(resolver.resolve(user(300, 1)).contains(AgentRole.TEACHER), "roleId=1 = 老师");
+ assertTrue(resolver.resolve(user(301, 3)).contains(AgentRole.STUDENT), "roleId=3 = 学生");
+ assertTrue(resolver.resolve(user(302, 7)).contains(AgentRole.ADMIN), "roleId=7 = 管理员");
}
@Test
void studentSeesOnlyStudentSideTools() {
- Set roles = resolver.resolve(user(101, 1));
- assertTrue(roles.contains(AgentRole.STUDENT), "roleId=1 应识别为学生");
+ // 学生形态(实测):当前身份 roleId=3(平台「用户身份」字典:3=学生)
+ BoaUser stu = userWithCurrent(101, 3, "学生", 3);
+ Set roles = resolver.resolve(stu);
+ assertTrue(roles.contains(AgentRole.STUDENT), "roleId=3 应识别为学生");
+ assertFalse(roles.contains(AgentRole.TEACHER), "学生不是教师");
+ assertFalse(roles.contains(AgentRole.PARENT), "学生不是家长");
List visible = registry.visibleTools(null, roles);
// 学生能做:交作业、报名活动、请假申请
@@ -145,10 +181,32 @@ class RolePermissionTest {
assertFalse(visible(visible, "dorm.buildings"), "学生不应看到宿舍管理侧");
}
+ @Test
+ void parentSeesActivityToolsButNotStudentOrStaff() {
+ // 家长形态(实测):当前身份 roleId=4(平台字典:4=家长)
+ BoaUser p = userWithCurrent(201, 4, "家长", 4);
+ Set roles = resolver.resolve(p);
+ assertTrue(roles.contains(AgentRole.PARENT), "roleId=4 应识别为家长");
+ assertFalse(roles.contains(AgentRole.STUDENT), "家长不是学生");
+ assertFalse(roles.contains(AgentRole.TEACHER), "家长不是教师");
+
+ List visible = registry.visibleTools(null, roles);
+ // 家长能做:看活动广场、报名/取消报名、宿舍通知回执
+ assertTrue(visible(visible, "activity.plaza"), "家长应能看活动广场");
+ assertTrue(visible(visible, "activity.signup"), "家长应能报名活动");
+ assertTrue(visible(visible, "activity.cancelSignup"), "家长应能取消报名");
+ assertTrue(visible(visible, "dorm.submitReceipt"), "家长应能提交宿舍通知回执");
+ // 家长不做:学生作业、请假申请/审批、发布公告
+ assertFalse(visible(visible, "homework.submit"), "家长不应看到学生交作业");
+ assertFalse(visible(visible, "leave.submit"), "家长不应看到请假申请");
+ assertFalse(visible(visible, "leave.audit"), "家长不应看到审批");
+ assertFalse(visible(visible, "notice.publish"), "家长不应看到发布公告");
+ }
+
@Test
void teacherSeesStaffToolsButNotStudentHomework() {
- Set roles = resolver.resolve(user(102, 2));
- assertTrue(roles.contains(AgentRole.TEACHER), "roleId=2 应识别为教师");
+ Set roles = resolver.resolve(user(102, 1));
+ assertTrue(roles.contains(AgentRole.TEACHER), "roleId=1 应识别为教师(老师)");
List visible = registry.visibleTools(null, roles);
// 教师能做:审批、发布、专业发展