/**
* 匿名通道(契约 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}`;
}