用户上传请假佐证材料时请求打到 /homeleave/api/base/FileUpload,被网关直接 404。 根因:默认上传地址按 "/" + ToolDef.module + "/api/base/FileUpload" 拼 —— 把「模块 key」当成了路由前缀。而请假的 key 是 homeleave、真实前缀是 /home-leave(差一个连字符)。实测 /home-leave/... 回 302 走 CAS, /homeleave/... 回 caddy 404。 - 新增 AgentOrchestrator.pathPrefix(module, baseUrl):从 agent.modules 的配置 地址推出站点根下的路径前缀;未配置时退回 key,尾部斜杠会被吃掉 (否则会拼出 //api/base/FileUpload) - buildForm 的上传地址改用它拼接(作业仍走 uploadPathOverride,不受影响) - 其余 4 个模块的 key 与前缀同名,行为不变(curl 实测 /campusnotice、 /dormnotice、/familyactivity、/coursehomework/api/file/Upload 均 302 可达) - 补 2 条契约测试锁住这个行为;并修正 ModuleFormParityTest 里被一并写错的 homeleave 地址 —— 正是它把同一个错误复制进了测试,导致一直测不出来
This commit is contained in:
@@ -641,6 +641,49 @@ public class AgentOrchestrator {
|
||||
return missing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块在站点根下的**路径前缀**(`homeleave` → `/home-leave`)。
|
||||
*
|
||||
* <p>★ 不能用模块 key 直接拼:key 只是 `agent.modules` 的内部标识,真实路由前缀可能不同 ——
|
||||
* 请假正是这种情况,key 是 `homeleave` 而前缀是 `/home-leave`(差一个连字符)。
|
||||
* 早先附件上传按 `"/" + def.getModule() + "/api/base/FileUpload"` 拼,于是请假的佐证材料
|
||||
* POST 到 `/homeleave/api/base/FileUpload`,被网关当成不存在的应用直接 **404**
|
||||
* (实测:`/home-leave/...` 回 302 走 CAS,`/homeleave/...` 回 caddy 404)。
|
||||
*
|
||||
* @param module 模块标识(`ToolDef.module`)
|
||||
* @return 路径前缀,以 `/` 开头且不以 `/` 结尾;模块未配置时退回 `/<module>`
|
||||
*/
|
||||
private String modulePathPrefix(String module) {
|
||||
return pathPrefix(module, settings.getModules().get(module));
|
||||
}
|
||||
|
||||
/**
|
||||
* 由模块的**完整地址**推出它在站点根下的路径前缀(`homeleave` + `…/home-leave` → `/home-leave`)。
|
||||
*
|
||||
* @param module 模块标识(仅用于兜底)
|
||||
* @param moduleBaseUrl `agent.modules` 里配置的地址,如 `http://host/home-leave`
|
||||
*/
|
||||
public static String pathPrefix(String module, String moduleBaseUrl) {
|
||||
if (StringUtils.isBlank(moduleBaseUrl)) {
|
||||
return "/" + module;
|
||||
}
|
||||
String rest = moduleBaseUrl;
|
||||
int scheme = rest.indexOf("://");
|
||||
if (scheme >= 0) {
|
||||
rest = rest.substring(scheme + 3);
|
||||
}
|
||||
int slash = rest.indexOf('/');
|
||||
if (slash < 0) {
|
||||
// 形如 http://host(没有路径部分)⇒ 模块就挂在站点根,退回 key 前缀
|
||||
return "/" + module;
|
||||
}
|
||||
String path = rest.substring(slash);
|
||||
while (path.length() > 1 && path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
return path.isEmpty() ? "/" + module : path;
|
||||
}
|
||||
|
||||
/** 由工具定义生成补全表单 */
|
||||
private FormRequest buildForm(ToolDef def, Map<String, Object> args, List<ToolParam> missing,
|
||||
CallerContext caller) {
|
||||
@@ -672,11 +715,12 @@ public class AgentOrchestrator {
|
||||
for (ToolParam p : shown) {
|
||||
FormField f = p.toFormField();
|
||||
// 附件控件:补上传地址(模块的附件上传只认浏览器 CAS 会话,必须前端带登录态 POST)。
|
||||
// 缺省按 「/<模块>/api/base/FileUpload」,个别模块不同(作业是 /api/file/Upload)时由参数覆盖。
|
||||
// 缺省按「模块真实前缀 + /api/base/FileUpload」,个别模块上传接口不同(作业是 /api/file/Upload)
|
||||
// 时由参数覆盖。★ 前缀必须取自 agent.modules 的配置,不能用模块 key 拼(见 modulePathPrefix)。
|
||||
if ("file".equals(f.getWidget())) {
|
||||
f.setUploadPath(StringUtils.isNotBlank(p.getUploadPathOverride())
|
||||
? p.getUploadPathOverride()
|
||||
: "/" + def.getModule() + "/api/base/FileUpload");
|
||||
: modulePathPrefix(def.getModule()) + "/api/base/FileUpload");
|
||||
}
|
||||
// 字典类字段(如请假类型、班级、学生):优先交给前端用**用户自己的登录态**代取。
|
||||
//
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.yunhai.agent;
|
||||
|
||||
import com.yunhai.agent.config.AgentSettings;
|
||||
import com.yunhai.agent.model.BrowserToolRequest;
|
||||
import com.yunhai.agent.service.AgentOrchestrator;
|
||||
import com.yunhai.agent.tool.FormField;
|
||||
import com.yunhai.agent.tool.ToolDef;
|
||||
import com.yunhai.agent.tool.ToolExecutor;
|
||||
@@ -48,7 +49,9 @@ class ModuleFormParityTest {
|
||||
settings.getModules().put("campusnotice", "http://base.yunhaixingtong.com/campusnotice");
|
||||
settings.getModules().put("dormnotice", "http://base.yunhaixingtong.com/dormnotice");
|
||||
settings.getModules().put("familyactivity", "http://base.yunhaixingtong.com/familyactivity");
|
||||
settings.getModules().put("homeleave", "http://base.yunhaixingtong.com/homeleave");
|
||||
// ★ 前缀是 /home-leave(差一个连字符)—— 早先这里跟着代码一起写成 /homeleave,
|
||||
// 于是「附件上传地址拼错」这个 bug 在测试里也被一并复制、永远测不出来。
|
||||
settings.getModules().put("homeleave", "http://base.yunhaixingtong.com/home-leave");
|
||||
settings.getModules().put("coursehomework", "http://base.yunhaixingtong.com/coursehomework");
|
||||
registry = new ToolRegistry(settings);
|
||||
registry.init();
|
||||
@@ -226,7 +229,45 @@ class ModuleFormParityTest {
|
||||
call.getFollowUp().getPathAndQuery(), "第二步地址要带模块前缀,并留 {data} 给首步的 id");
|
||||
}
|
||||
|
||||
/* ==================== ⑥ 附件控件渲染 ==================== */
|
||||
/* ==================== ⑥ 附件上传地址:前缀必须取自配置,不能用模块 key 拼 ==================== */
|
||||
|
||||
/**
|
||||
* ★ 回归:附件上传地址的前缀必须取自 `agent.modules` 的配置。
|
||||
*
|
||||
* <p>请假的 key 是 {@code homeleave},真实路由前缀是 {@code /home-leave} —— 差一个连字符。
|
||||
* 早先按 {@code "/" + def.getModule() + "/api/base/FileUpload"} 拼,请求打到
|
||||
* {@code /homeleave/api/base/FileUpload},被网关当成不存在的应用直接 **404**
|
||||
* —— 用户上传佐证材料时实际撞到的就是这个。
|
||||
*/
|
||||
@Test
|
||||
void uploadPathPrefixComesFromConfiguredUrlNotModuleKey() {
|
||||
assertEquals("/home-leave",
|
||||
AgentOrchestrator.pathPrefix("homeleave", "http://base.yunhaixingtong.com/home-leave"),
|
||||
"请假的前缀带连字符,不能拿 key(homeleave) 顶替");
|
||||
assertEquals("/home-leave/api/base/FileUpload",
|
||||
AgentOrchestrator.pathPrefix("homeleave", "http://base.yunhaixingtong.com/home-leave")
|
||||
+ "/api/base/FileUpload");
|
||||
assertEquals("/campusnotice",
|
||||
AgentOrchestrator.pathPrefix("campusnotice", "http://base.yunhaixingtong.com/campusnotice"));
|
||||
assertEquals("/dormnotice",
|
||||
AgentOrchestrator.pathPrefix("dormnotice", "http://base.yunhaixingtong.com/dormnotice/"),
|
||||
"尾部斜杠要吃掉,否则会拼出 //api/base/FileUpload");
|
||||
// 地址没有路径部分 / 完全未配置 → 退回 key 前缀,至少不会拼出空串
|
||||
assertEquals("/x", AgentOrchestrator.pathPrefix("x", "http://host"));
|
||||
assertEquals("/y", AgentOrchestrator.pathPrefix("y", null));
|
||||
}
|
||||
|
||||
/** 四个走默认上传接口的模块都要能拼出「以 / 开头且无双斜杠」的地址 */
|
||||
@Test
|
||||
void everyModuleResolvesAnAbsoluteUploadPath() {
|
||||
for (String m : Arrays.asList("campusnotice", "dormnotice", "familyactivity", "homeleave")) {
|
||||
String p = AgentOrchestrator.pathPrefix(m, settings.getModules().get(m)) + "/api/base/FileUpload";
|
||||
assertTrue(p.startsWith("/"), m + " 的上传地址要以 / 开头,实际:" + p);
|
||||
assertFalse(p.contains("//"), m + " 的上传地址不能出现双斜杠,实际:" + p);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== ⑦ 附件控件渲染 ==================== */
|
||||
|
||||
@Test
|
||||
void scalarValueIsWrappedForArrayParams() {
|
||||
|
||||
Reference in New Issue
Block a user