/** * 匿名通道(契约 v2 §3.2/C6/C15,T15)。 * *

两把探针必须走原生 fetch,不走 alova:alova 实例的 302 拦截对全部 `/api/*` 生效且 * 没有 per-request 豁免位(C15 的代码事实,alova-core.ts:58-65),而引导期要问的问题恰恰是 * "这个人有没有会话"——用 alova 去问,无会话者当场被整页带去 CAS,双探针就没了意义。 * 探针只回数据,不跳转。

* *

提交回落 alova:`/anon` 面不存在 302 语义(无会话墙),业务错误提示链与登录面同一条; * 上传端点由本模块给出 URL 串(附件控件按注入的 URL 走,见 Fill.vue 的 provide)。

*/ import conf from "@/assets/config/host.config"; import Utils from "@/utils"; import { alovaInstance } from "@/utils/alova/alova-iview"; import type { ResolvedForm, SubmitRequest, SubmitView } from "@/types/answer"; /** 平台统一响应信封(探针只看 code,不套 alova 的错误处理) */ interface Envelope { code: number; msg?: string; data?: T; } /** 会话探针的结果:有没有会话 + 有会话时顺手把用户带回来(省一次重复请求) */ export interface SessionProbe { logged: boolean; user: any | null; } /** * 探针请求。带 `yschapirequest: 1` 是给 CAS 过滤器认"这是 AJAX"的记号——不带它,墙内未登录会回 * HTML 跳转而不是 JSON 的 code=302 信封,fetch 会跟着跳到 CAS 域名然后撞 CORS,探针就变成了一次 * 无声的失败。`yschreferer` 同 alova 实例的头,保持两条通道对后端看起来是同一个前端。 */ async function probe(path: string): Promise> { try { const res = await fetch(`${conf.serviceHost}${path}`, { credentials: "include", headers: { yschapirequest: "1", yschreferer: Utils.strToHexCharCode(window.location.href) } }); if (!res.ok) return { code: -1, msg: `HTTP ${res.status}` }; return (await res.json()) as Envelope; } catch (e: any) { return { code: -1, msg: e?.message || "探针请求失败" }; } } /** 探针 1(C15 第一跳):当前浏览器有没有 CAS 会话。非 0(含 302/网络错)一律按"无会话"回答 */ export async function probeSession(): Promise { const envelope = await probe("/api/base/GetUserInfo"); return { logged: envelope.code === 0, user: envelope.code === 0 ? envelope.data ?? null : null }; } /** * 探针 2(C15 第二跳):这张表是不是匿名表单。 * 后端对"非匿名表单"和"压根不存在"给同一条 PARAM 表单不存在(C6/V54 防探测), * 所以这里只需要"是不是 0"一个判据,msg 不进 UI。 */ export async function probeAnonForm(code: string, appCode?: string): Promise { const query = appCode ? `&appCode=${encodeURIComponent(appCode)}` : ""; const envelope = await probe(`/anon/form/resolve?code=${encodeURIComponent(code)}${query}`); return envelope.code === 0 && envelope.data ? envelope.data : null; } /** 匿名提交(响应形状与登录面同一条:{answerId, answerNo, replayed},C7 幂等命中 200+原 answerId) */ export function submitAnonAnswer(body: SubmitRequest) { return alovaInstance.Post("/anon/form/submit", body).send(); } /** * 匿名上传的端点寻址(`code` 必带:后端只有表单能定 sch_id,落错校提交时的附件归属就查不到这行)。 * 返回的是给附件控件用的完整请求路径,appCode 以 query 参数交出去。 */ export function anonUploadPath(code: string, appCode?: string): string { const query = appCode ? `&appCode=${encodeURIComponent(appCode)}` : ""; return `/anon/form/upload?code=${encodeURIComponent(code)}${query}`; }