统一外部 access_token 存储与轻应用 URL 拼接,修复嵌入会话鉴权隔离。

将 access_token 纳入 scoped storage 迁移,yUserId 登录优先并独立持久化 token;轻应用/按钮管理默认改用 access_token 参数名,支持 hash 路由占位符替换与自动追加。
This commit is contained in:
fengxiangdebaozi committed 2026-06-29 23:00:31 +08:00
1 parent 8c6d08d6bf
commit 6378cd33bd
10 files changed
+210 -39

No files matched your search

@@ -49,7 +49,7 @@ interface ServerButton {
function toLoginParam(p: ServerLoginParam): TaskButtonLoginParam {
return {
key: p.key ?? "",
source: p.source || "accessToken",
source: p.source || "access_token",
customValue: p.custom_value ?? "",
};
}
@@ -28,6 +28,7 @@ import {
platformTypeChange,
} from './GeneralQA.mock';
import { requestCrudService } from '@/service/request-axios-service';
import { authStorageGet } from '@/core/auth/scoped-storage';
/**
* 通用问答组件
@@ -138,7 +139,7 @@ const GeneralQA: React.FC<{
const mockSingleRoundStoppedRef = useRef(false);
const mockMultiRoundStoppedRef = useRef(false);
const chatListRef = useRef<HTMLDivElement | null>(null);
const userId = useRef(localStorage.getItem('userId') || '387'); // 用户id
const userId = useRef(authStorageGet('userId') || '387'); // 用户id
const qaType = useRef('GENERAL'); // 问答类型
const chatDataId = useRef(); // 聊天窗口id
const compareChatId = useRef(); // 对比对话记录id
@@ -7,7 +7,7 @@
import type { LightApp, LightAppRouteParam } from "@/strategy-components/api/light-apps";
import { authStorageGet } from "@/core/auth/scoped-storage";
import { normalizeExternalAccessToken } from "@/core/auth/external-access-token";
import { readCurrentExternalAccessTokenValue } from "@/core/auth/external-access-token";
/** iframe 可挂载的父菜单(取自 core/page-layout/sidebar-menu.ts 的现有菜单 id)。 */
export const MOUNT_OPTIONS: { label: string; value: string }[] = [
@@ -29,7 +29,8 @@ export function mountLabel(value: string | null | undefined): string {
* userName / gateDepartName / typeName 取 consumer 接口 userDetailByYuserId 的返回值。
*/
export const LOGIN_SOURCE_OPTIONS: { label: string; value: string }[] = [
{ label: "accessToken", value: "accessToken" },
{ label: "access_token", value: "access_token" },
{ label: "accessToken(兼容旧配置)", value: "accessToken" },
{ label: "yUserId", value: "yUserId" },
{ label: "userName", value: "userName" },
{ label: "gateDepartName", value: "gateDepartName" },
@@ -143,8 +144,8 @@ export function resolveRouteParamValue(
}
if (param.source) {
const value = userInfo[param.source];
if (param.source === "accessToken") {
return normalizeExternalAccessToken(value);
if (param.source === "access_token" || param.source === "accessToken") {
return readCurrentExternalAccessTokenValue(userInfo);
}
return value == null ? "" : String(value);
}
@@ -157,15 +158,50 @@ export function resolveRouteParamValue(
* 兼容 hash 路由地址(如 `https://x#/route`)—— 管理员粘贴完整地址,参数自动追加在末尾,
* 落在 hash 路由的 query 上(与现有 XK/PG 页面 `?accessToken=` 拼接方式一致)。
*/
function appendQueryPairs(url: string, pairs: string[]): string {
if (pairs.length === 0) return url;
const hashIndex = url.indexOf("#");
if (hashIndex >= 0) {
const beforeHash = url.slice(0, hashIndex);
const hash = url.slice(hashIndex);
const sep = hash.includes("?") ? "&" : "?";
return `${beforeHash}${hash}${sep}${pairs.join("&")}`;
}
const sep = url.includes("?") ? "&" : "?";
return `${url}${sep}${pairs.join("&")}`;
}
function replaceAccessTokenParam(url: string, accessToken: string): { url: string; replaced: boolean } {
if (!accessToken) return { url, replaced: false };
let replaced = false;
const nextUrl = url.replace(/([?&]access_token)(?:=[^&#]*)?(?=[&#]|$)/g, (match) => {
replaced = true;
return `${match.split("=")[0]}=${encodeURIComponent(accessToken)}`;
});
return { url: nextUrl, replaced };
}
export function buildLightAppUrl(
app: Pick<LightApp, "url" | "routeParams">,
userInfo: Record<string, unknown> = readUserInfo(),
theme?: AppThemeColor,
): string {
const pairs = (app.routeParams ?? [])
.filter((p) => p.key)
.map((p) => `${encodeURIComponent(p.key)}=${encodeURIComponent(resolveRouteParamValue(p, userInfo, theme))}`);
if (pairs.length === 0) return app.url;
const sep = app.url.includes("?") ? "&" : "?";
return `${app.url}${sep}${pairs.join("&")}`;
const routeParams = app.routeParams ?? [];
const accessToken = readCurrentExternalAccessTokenValue(userInfo);
const tokenUrlResult = replaceAccessTokenParam(app.url, accessToken);
const pairs = routeParams
.filter((p) => (p.key ?? "").trim())
.filter((p) => (p.key ?? "").trim() !== "access_token" || !tokenUrlResult.replaced)
.map((p) => {
const key = (p.key ?? "").trim();
const value = key === "access_token" ? accessToken : resolveRouteParamValue(p, userInfo, theme);
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
});
const hasAccessTokenParam =
tokenUrlResult.replaced ||
routeParams.some((p) => (p.key ?? "").trim() === "access_token");
if (!hasAccessTokenParam && accessToken) {
pairs.push(`${encodeURIComponent("access_token")}=${encodeURIComponent(accessToken)}`);
}
return appendQueryPairs(tokenUrlResult.url, pairs);
}