fix: unify all aihr collations, stop silent logout on biz 403, enable knowledge apps
线上现象:「问」页语音提问报 401(请求不带 token),用户观察为
「点了几个页面后登录状态失效」。
根因链(逐环生产验证):
1. 问师傅 /api/knowledge/query 对真实登录返回业务 403
「当前登录端未启用知识问答应用」——生产 aihr_knowledge_app 为空表,
多租户知识平台的应用注册/空间绑定/角色授权从未在生产配置(P1 遗留)。
2. 前端 readPayload 把 403 也视为认证失败并 clearAuth() —— 业务 403
触发静默登出,localStorage token 被清。
3. 之后语音 ASR 请求裸奔 -> 401「未能读取到有效 token」。
同期回归:凌晨对 aihr_practice_assignment.ext_party_id 的单列 collation
热修复,使其与 aihr_practice_session(仍 0900_ai_ci)的 JOIN 反向失配,
/practice/mistakes 500。
修复:
- backend/script/sql/update/aihr_20260718_unify_all_collations_mysql8.sql:
47 张 aihr 表全部 CONVERT 到运行时基准 collation(生产=general_ci),
终结 collation 打地鼠;本地验证后已在生产执行
(schema 备份 /opt/wygj/backups/aihr-schema-before-unify-20260718041407.sql),
13 项移动端接口回归全 200(含 mistakes)。
- api.ts/auth.ts:本地登录态仅在真正 401(HTTP 或业务码)时清除,并经
redirectToLoginForAuthLoss 记住当前页自动回登录(2s 防抖,登录页内不跳);
业务 403(权限拒绝,如录音归属/知识空间)只报错不登出;
authenticatedFileUrl 同步收紧(403 不再清登录态)。
- 生产数据配置(幂等 INSERT,业务范围保守):
· aihr_knowledge_app 注册 yc_admin(pc)/yc_mobile(app) SESSION 应用
· yc_mobile 绑定 SOP 域 5 库(1001/1002/1003/1201/1202),
employee/supervisor ROLE READ;yc_admin 绑定全部 13 库,
hr_operator/superadmin MANAGE
· 财务/销售/行政等大库(1004-1010)暂仅管理端,待业务确认再放开
生产验证:
- 问师傅 query 200,真实回答+引用「投诉处理 SOP v1.0」
- E2E:登录后全 tab 浏览+语音+文字提问,token 全程稳定(storage 打桩零触发)
- 无效 token 点语音提问 -> 自动回登录页(原为死胡同 toast)
- H5 已发布(备份 h5-before-20260718042238)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
|||||||
|
-- Unify the collation of EVERY aihr table (tables and all string columns).
|
||||||
|
--
|
||||||
|
-- History: the 2026-07-18 release aligned the 18 newly added tables to the
|
||||||
|
-- collation of aihr_knowledge_info.tenant_id, and a same-day hotfix aligned
|
||||||
|
-- aihr_practice_assignment.ext_party_id. Both were partial: older tables kept
|
||||||
|
-- utf8mb4_0900_ai_ci columns (e.g. aihr_practice_session.ext_party_id), so any
|
||||||
|
-- join between the two families still raises error 1267
|
||||||
|
-- (Illegal mix of collations). Seen in production on
|
||||||
|
-- /api/aihr/mobile/practice/assignments and /api/aihr/mobile/practice/mistakes.
|
||||||
|
--
|
||||||
|
-- This migration converts ALL aihr tables to the canonical collation derived
|
||||||
|
-- at run time, ending the per-join whack-a-mole. CONVERT TO rewrites the table
|
||||||
|
-- default plus every character column, and is idempotent.
|
||||||
|
|
||||||
|
SET @aihr_uni_collation := (
|
||||||
|
SELECT collation_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'aihr_knowledge_info'
|
||||||
|
AND column_name = 'tenant_id'
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
SET @aihr_uni_collation := COALESCE(@aihr_uni_collation, 'utf8mb4_0900_ai_ci');
|
||||||
|
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_candidate_employee_link` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_candidate_material` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_case_record` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_interview_result` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_answer_feedback` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_attach` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_fragment` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_gap` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_import_task` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_info` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_upload_item` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_model_config` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_model_provider` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_onboard_task` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_org_snapshot` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_position_responsibility` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_annotation` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_assignment` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_audio` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_audio_upload` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_calibration` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_rubric` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_rubric_dimension` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_scenario` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_session` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_prompt_template` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_qualification_gate` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_sop_answer_review` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_sop_applicability` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_community_answer` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_community_question` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_incentive_rule` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_admin_audit` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_app` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_app_space` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_query_log` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_knowledge_space_grant` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_learning_question` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_onboard_exam` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_onboard_exam_answer` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_onboard_exam_attempt` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_onboard_exam_question` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_onboard_exam_target` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_points_ledger` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_practice_question_feedback` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_web_ai_query_audit` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
SET @aihr_uni_ddl := CONCAT('ALTER TABLE `aihr_web_search_provider` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_uni_collation);
|
||||||
|
PREPARE aihr_uni_stmt FROM @aihr_uni_ddl; EXECUTE aihr_uni_stmt; DEALLOCATE PREPARE aihr_uni_stmt;
|
||||||
|
|
||||||
|
SET @aihr_uni_ddl := NULL;
|
||||||
|
SET @aihr_uni_collation := NULL;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ApiPayload } from '@/types/api';
|
import type { ApiPayload } from '@/types/api';
|
||||||
import { clearAuth, getAuth } from './auth';
|
import { clearAuth, getAuth, redirectToLoginForAuthLoss } from './auth';
|
||||||
|
|
||||||
const apiBase = import.meta.env.VITE_API_BASE || '/dev-api';
|
const apiBase = import.meta.env.VITE_API_BASE || '/dev-api';
|
||||||
|
|
||||||
@@ -48,8 +48,11 @@ export const readPayload = <T>(statusCode: number, data: unknown, clearAuthOnUna
|
|||||||
const payload = data as ApiPayload<T>;
|
const payload = data as ApiPayload<T>;
|
||||||
const code = typeof payload?.code === 'number' ? payload.code : undefined;
|
const code = typeof payload?.code === 'number' ? payload.code : undefined;
|
||||||
if (statusCode < 200 || statusCode >= 300 || (code !== undefined && code !== 200)) {
|
if (statusCode < 200 || statusCode >= 300 || (code !== undefined && code !== 200)) {
|
||||||
if (clearAuthOnUnauthorized && (statusCode === 401 || statusCode === 403 || code === 401 || code === 403)) {
|
// Only a real login failure (401) drops the local session; business 403
|
||||||
|
// (permission denied) must surface as a message, never a silent logout.
|
||||||
|
if (clearAuthOnUnauthorized && (statusCode === 401 || code === 401)) {
|
||||||
clearAuth();
|
clearAuth();
|
||||||
|
redirectToLoginForAuthLoss();
|
||||||
}
|
}
|
||||||
throw new ApiError(payload?.msg || `HTTP ${statusCode}`, statusCode, code);
|
throw new ApiError(payload?.msg || `HTTP ${statusCode}`, statusCode, code);
|
||||||
}
|
}
|
||||||
@@ -61,7 +64,10 @@ export const parsePayloadText = (statusCode: number, text: string, clearAuthOnUn
|
|||||||
try {
|
try {
|
||||||
return JSON.parse(text || '{}');
|
return JSON.parse(text || '{}');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (clearAuthOnUnauthorized && (statusCode === 401 || statusCode === 403)) clearAuth();
|
if (clearAuthOnUnauthorized && statusCode === 401) {
|
||||||
|
clearAuth();
|
||||||
|
redirectToLoginForAuthLoss();
|
||||||
|
}
|
||||||
throw error instanceof SyntaxError
|
throw error instanceof SyntaxError
|
||||||
? new ApiError(statusCode >= 400 ? `HTTP ${statusCode}` : '响应解析失败', statusCode)
|
? new ApiError(statusCode >= 400 ? `HTTP ${statusCode}` : '响应解析失败', statusCode)
|
||||||
: error;
|
: error;
|
||||||
@@ -138,7 +144,10 @@ export const authenticatedFileUrl = async (url: string): Promise<string> => {
|
|||||||
resolve(URL.createObjectURL(xhr.response));
|
resolve(URL.createObjectURL(xhr.response));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (xhr.status === 401 || xhr.status === 403) clearAuth();
|
if (xhr.status === 401) {
|
||||||
|
clearAuth();
|
||||||
|
redirectToLoginForAuthLoss();
|
||||||
|
}
|
||||||
reject(new Error(`HTTP ${xhr.status}`));
|
reject(new Error(`HTTP ${xhr.status}`));
|
||||||
};
|
};
|
||||||
xhr.onerror = () => reject(new Error('网络请求失败'));
|
xhr.onerror = () => reject(new Error('网络请求失败'));
|
||||||
@@ -155,7 +164,10 @@ export const authenticatedFileUrl = async (url: string): Promise<string> => {
|
|||||||
resolve(response.tempFilePath);
|
resolve(response.tempFilePath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.statusCode === 401 || response.statusCode === 403) clearAuth();
|
if (response.statusCode === 401) {
|
||||||
|
clearAuth();
|
||||||
|
redirectToLoginForAuthLoss();
|
||||||
|
}
|
||||||
reject(new Error(`HTTP ${response.statusCode}`));
|
reject(new Error(`HTTP ${response.statusCode}`));
|
||||||
},
|
},
|
||||||
fail: () => reject(new Error('网络请求失败'))
|
fail: () => reject(new Error('网络请求失败'))
|
||||||
|
|||||||
@@ -160,6 +160,29 @@ export const rememberLoginRedirect = (url: string) => {
|
|||||||
uni.setStorageSync(loginRedirectKey, url);
|
uni.setStorageSync(loginRedirectKey, url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lastAuthLossRedirectAt = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Central recovery for a lost login (HTTP/biz 401): remember the current page,
|
||||||
|
* then relaunch to login. Debounced because parallel requests can 401 together.
|
||||||
|
* Business 403 must NOT end up here — permission denials are not logouts.
|
||||||
|
*/
|
||||||
|
export const redirectToLoginForAuthLoss = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastAuthLossRedirectAt < 2000) return;
|
||||||
|
lastAuthLossRedirectAt = now;
|
||||||
|
try {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const current = pages[pages.length - 1] as { route?: string; $page?: { fullPath?: string } } | undefined;
|
||||||
|
const path = current?.$page?.fullPath || (current?.route ? `/${current.route}` : '');
|
||||||
|
if (path.startsWith('/pages/auth/')) return;
|
||||||
|
if (path.startsWith('/pages/')) uni.setStorageSync(loginRedirectKey, path);
|
||||||
|
} catch {
|
||||||
|
// Best effort only; still return to login below.
|
||||||
|
}
|
||||||
|
uni.reLaunch({ url: '/pages/auth/login/index' });
|
||||||
|
};
|
||||||
|
|
||||||
export const consumeLoginRedirect = () => {
|
export const consumeLoginRedirect = () => {
|
||||||
const url = uni.getStorageSync(loginRedirectKey) || '';
|
const url = uni.getStorageSync(loginRedirectKey) || '';
|
||||||
uni.removeStorageSync(loginRedirectKey);
|
uni.removeStorageSync(loginRedirectKey);
|
||||||
|
|||||||
Reference in New Issue
Block a user