Forráskód Böngészése

feat: add business auth token flow

haifeng.zhang 2 hete
szülő
commit
453871f915

+ 23 - 0
src/api/auth/index.ts

@@ -0,0 +1,23 @@
+import { service } from '@/utils/request'
+import { formHeader } from '@/utils/request/header'
+import type { ApiResponse } from '@/api/types'
+
+export enum AuthApi {
+  GET_TOKEN = '/auth/getToken',
+}
+
+export interface GetAuthTokenReq {
+  contactPerson: string
+}
+
+export const getAuthToken = (data: GetAuthTokenReq) => {
+  const body = new URLSearchParams()
+  body.set('contactPerson', data.contactPerson)
+
+  return service.post<ApiResponse<string>>(AuthApi.GET_TOKEN, body, {
+    isAuth: false,
+    headers: {
+      ...formHeader,
+    },
+  })
+}

+ 3 - 3
src/api/questionReqort/index.ts

@@ -1,6 +1,6 @@
 import { service } from '@/utils/request'
-import { AddQuestionReportDataReq, PageQuestionReportDataReq, QuestionDetailRes, QuestionListRes, QuestionType, QuestionTypeListRes } from './types'
-import { ApiResponse, PageReq, PageResp } from '../types'
+import type { AddQuestionReportDataReq, PageQuestionReportDataReq, QuestionDetailRes, QuestionListRes, QuestionType, QuestionTypeListRes } from './types'
+import type { ApiResponse, PageReq, PageResp } from '../types'
 export enum questionApi {
   /** 新增上报问题  */
   ADD_QUESTION_REPORT_DATA = '/questionReport/addQuestionReportData',
@@ -54,4 +54,4 @@ export const getQuestionReportDetail = (id: string) => {
 
 export const getQuestionTypeList = (params: {page:number,limit:number,category:QuestionType}) => {
   return service.get<ApiResponse<PageResp<QuestionTypeListRes>>>(questionApi.QUESTION_TYPE_LIST,{params})
-}
+}

+ 2 - 2
src/api/questionReqort/types.ts

@@ -1,4 +1,4 @@
-import { PageReq } from '../types'
+import type { PageReq } from '../types'
 
 /** 新增上报问题req */
 export interface AddQuestionReportDataReq {
@@ -201,4 +201,4 @@ export enum QuestionType {
   SUGGESTION = '3',
   QUESTION = '4',
   SERVICE = '5'
-}
+}

+ 4 - 4
src/api/questionsApi/index.ts

@@ -1,6 +1,6 @@
 import { service } from '@/utils/request'
-import { getPageDataReq, getPageDataRes } from './types'
-import { ApiResponse, PageResp } from '../types'
+import type { getPageDataReq, getPageDataRes } from './types'
+import type { ApiResponse, PageResp } from '../types'
 
 export enum reportAPi {
   /** 问答库列表 */
@@ -16,5 +16,5 @@ export const getPageData = (params: getPageDataReq) => {
 
 /** 问答库详情 */
 export const getDetail = (uuid: string) => {
-  return service.get<getPageDataRes>(reportAPi.GET_DETAIL, { params: { uuid } })
-}
+  return service.get<ApiResponse<getPageDataRes>>(reportAPi.GET_DETAIL, { params: { uuid } })
+}

+ 2 - 2
src/api/questionsApi/types.ts

@@ -1,4 +1,4 @@
-import { PageReq } from '../types'
+import type { PageReq } from '../types'
 
 
 /** 问答库列表req */
@@ -39,4 +39,4 @@ export interface getPageDataRes {
     updateTime?: number;
     updateUser?: string;
     uuid?: string;
-}
+}

+ 3 - 3
src/api/reportService/index.ts

@@ -1,6 +1,6 @@
 import { service } from '@/utils/request'
-import { addReportServiceDataReq, PageServiceReportDataReq, ReportServiceRes } from './types'
-import { ApiResponse, PageResp } from '../types'
+import type { addReportServiceDataReq, PageServiceReportDataReq, ReportServiceRes } from './types'
+import type { ApiResponse, PageResp } from '../types'
 
 export enum reportAPi {
   /** 新增上报服务 */
@@ -25,4 +25,4 @@ export const pageServiceReportData = (params: PageServiceReportDataReq) => {
 /** 上报服务详情 */
 export const getServiceReportDetail = (uuid: string) => {
   return service.get<ApiResponse<ReportServiceRes>>(reportAPi.SERVICE_REPORT_DETAIL + `/${uuid}`)
-}
+}

+ 2 - 2
src/api/reportService/types.ts

@@ -1,4 +1,4 @@
-import { PageReq } from '../types'
+import type { PageReq } from '../types'
 
 /** 新增上报服务req */
 export interface addReportServiceDataReq {
@@ -77,4 +77,4 @@ export interface ReportServiceRes {
   updateTime: string
   isDelete: number
   serviceAnswer: string
-}
+}

+ 12 - 0
src/pages/home/index.vue

@@ -9,6 +9,7 @@ import {
   syncExternalVisitorProfile,
 } from '@/api/wecom';
 import type { WecomVisitorProfile } from '@/api/wecom/types';
+import { ensureBusinessAuthByVisitorProfile } from '@/utils/businessAuth/ensure'
 
 const AUTH_PAGE = '/pages/index/index'
 
@@ -90,6 +91,14 @@ const cacheLooksUsable = (cached: WecomVisitorProfile | null): cached is WecomVi
   !!cached &&
   !!(cached.name || cached.mobile || cached.internalUserid || cached.externalUserId || cached.unionid)
 
+const tryEnsureBusinessAuth = async () => {
+  try {
+    await ensureBusinessAuthByVisitorProfile()
+  } catch (err) {
+    console.error('业务认证预热失败:', err)
+  }
+}
+
 const goReauthorize = () => {
   uni.reLaunch({ url: AUTH_PAGE })
 }
@@ -100,11 +109,13 @@ onLoad(async (option) => {
   if (code) {
     try {
       await bootstrapWithCode(code)
+      await tryEnsureBusinessAuth()
       void tryPrefetchOwnerInBackground()
     } catch {
       const cached = getCachedWecomVisitorProfile()
       if (cacheLooksUsable(cached)) {
         applyVisitorFromCachedProfile(cached)
+        await tryEnsureBusinessAuth()
         void tryPrefetchOwnerInBackground()
       } else {
         goReauthorize()
@@ -118,6 +129,7 @@ onLoad(async (option) => {
   const cached = getCachedWecomVisitorProfile()
   if (cacheLooksUsable(cached)) {
     applyVisitorFromCachedProfile(cached)
+    await tryEnsureBusinessAuth()
     void tryPrefetchOwnerInBackground()
     return
   }

+ 87 - 0
src/store/businessAuth/index.ts

@@ -0,0 +1,87 @@
+import { defineStore } from 'pinia'
+import { computed, ref } from 'vue'
+import StorageUtil from '@/utils/storage/storage'
+import { getAuthToken } from '@/api/auth'
+import { showNotify } from 'vant'
+
+const BUSINESS_TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000
+const BUSINESS_TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000
+
+const useBusinessAuthStore = defineStore('businessAuth', () => {
+  const businessTokenInfo = ref<string | null>(StorageUtil.getItem('businessTokenInfo') || null)
+  const businessTokenExpireAt = ref<number | null>(StorageUtil.getItem('businessTokenExpireAt') || null)
+  const refreshingPromise = ref<Promise<string> | null>(null)
+
+  const getBusinessTokenInfo = computed(() => businessTokenInfo.value)
+  const getBusinessTokenExpireAt = computed(() => businessTokenExpireAt.value)
+
+  const clearBusinessTokenInfo = () => {
+    businessTokenInfo.value = null
+    businessTokenExpireAt.value = null
+    StorageUtil.removeItem('businessTokenInfo')
+    StorageUtil.removeItem('businessTokenExpireAt')
+  }
+
+  const setBusinessTokenInfo = (token: string, expireAt = Date.now() + BUSINESS_TOKEN_EXPIRE_MS) => {
+    businessTokenInfo.value = token
+    businessTokenExpireAt.value = expireAt
+    StorageUtil.setItem('businessTokenInfo', token)
+    StorageUtil.setItem('businessTokenExpireAt', expireAt)
+  }
+
+  const getCachedBusinessToken = () => {
+    const token = businessTokenInfo.value || StorageUtil.getItem('businessTokenInfo') || ''
+    const expireAt = businessTokenExpireAt.value || Number(StorageUtil.getItem('businessTokenExpireAt') || 0)
+    if (!token || !expireAt) return ''
+    if (Date.now() >= expireAt - BUSINESS_TOKEN_REFRESH_BUFFER_MS) return ''
+    return token
+  }
+
+  const fetchBusinessToken = async (contactPerson: string) => {
+    const trimmed = String(contactPerson || '').trim()
+    if (!trimmed) {
+      throw new Error('contactPerson不能为空')
+    }
+
+    const { data } = await getAuthToken({ contactPerson: trimmed })
+    const token = String(data || '').trim()
+    if (!token) {
+      throw new Error('business token empty')
+    }
+
+    setBusinessTokenInfo(token)
+    return token
+  }
+
+  const ensureBusinessToken = async (contactPerson: string) => {
+    const cached = getCachedBusinessToken()
+    if (cached) return cached
+
+    if (!refreshingPromise.value) {
+      refreshingPromise.value = fetchBusinessToken(contactPerson).catch((err) => {
+        clearBusinessTokenInfo()
+        throw err
+      }).finally(() => {
+        refreshingPromise.value = null
+      })
+    }
+
+    return refreshingPromise.value
+  }
+
+  const handleBusinessTokenExpired = () => {
+    clearBusinessTokenInfo()
+    showNotify({ type: 'warning', message: '业务认证已失效,请重新进入页面' })
+  }
+
+  return {
+    getBusinessTokenInfo,
+    getBusinessTokenExpireAt,
+    setBusinessTokenInfo,
+    clearBusinessTokenInfo,
+    ensureBusinessToken,
+    handleBusinessTokenExpired,
+  }
+})
+
+export default useBusinessAuthStore

+ 4 - 2
src/subPages/pages/questions/detail.vue

@@ -20,15 +20,17 @@
 
 <script lang="ts" setup>
 import { getDetail } from '@/api/questionsApi';
-import { getPageDataRes } from '@/api/questionsApi/types';
+import type { getPageDataRes } from '@/api/questionsApi/types';
 import { onLoad } from '@dcloudio/uni-app';
 import { ref } from 'vue';
+import { ensureBusinessAuthByVisitorProfile } from '@/utils/businessAuth/ensure';
 
 const onClickLeft = () => history.back();
 
 const questDetail = ref<getPageDataRes>()
 onLoad(async (option) => {
   if (option && option.id) {
+    await ensureBusinessAuthByVisitorProfile()
     const { id } = option;
     const { data } = await getDetail(id);
     questDetail.value = data;
@@ -39,4 +41,4 @@ onLoad(async (option) => {
 
 <style lang="scss" scoped>
 
-</style>
+</style>

+ 11 - 8
src/subPages/pages/questions/index.vue

@@ -8,7 +8,7 @@
         <!-- 列表 -->
         <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
             <!-- <van-cell v-for="item in list" :key="item" :title="item" /> -->
-            <div class="vanlist-box" v-for="item in list" :key="item">
+            <div class="vanlist-box" v-for="item in list" :key="item.uuid || item.title">
                 <div class="vanlist-box_top">
                     <div class="title">{{ item.title }}</div>
                     <van-button size="mini" type="primary" round @click = "handleGoDetail(item)">查看详情</van-button>
@@ -21,14 +21,16 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref } from 'vue'
+import { ref } from 'vue'
 import { getPageData } from '@/api/questionsApi'
+import { ensureBusinessAuthByVisitorProfile } from '@/utils/businessAuth/ensure'
+import type { getPageDataRes } from '@/api/questionsApi/types'
 
 const onClickLeft = () => history.back();
 const isShow = ref(false);
 // 搜索
 const title = ref('');
-const onSearch = (val) => {
+const onSearch = (val: string) => {
     title.value = val;
     pageInfo.value.pageNumber = 1;
     list.value = [];
@@ -36,12 +38,12 @@ const onSearch = (val) => {
     getPage();
 }
 // 列表
-const list = ref([]);
+const list = ref<getPageDataRes[]>([]);
 const loading = ref(false);
 const finished = ref(false);
 
-const onLoad = () => {
-    getPage()
+const onLoad = async () => {
+    await getPage()
 };
 
 const pageInfo = ref({
@@ -49,6 +51,7 @@ const pageInfo = ref({
   pageSize: 10,
 })
 const getPage = async() => {
+    await ensureBusinessAuthByVisitorProfile()
     const { data } = await getPageData({
         pageNumber: pageInfo.value.pageNumber,
         pageSize: pageInfo.value.pageSize,
@@ -70,7 +73,7 @@ const getPage = async() => {
 }
 
 /** 跳转问题详情 */
-const handleGoDetail = (item:any) => {
+const handleGoDetail = (item: getPageDataRes) => {
   uni.navigateTo({
     url: `/subPages/pages/questions/detail?id=${item.uuid}`,
   })
@@ -102,4 +105,4 @@ const handleGoKnowledge = () => {
         }
     }
 }
-</style>
+</style>

+ 3 - 1
src/subPages/pages/reportProblems/detail.vue

@@ -8,9 +8,10 @@
 -->
 <script lang="ts" setup>
 import { getQuestionReportDetail } from '@/api/questionReqort';
-import { QuestionDetailRes } from '@/api/questionReqort/types';
+import type { QuestionDetailRes } from '@/api/questionReqort/types';
 import { onLoad } from '@dcloudio/uni-app';
 import { computed, ref } from 'vue';
+import { ensureBusinessAuthByVisitorProfile } from '@/utils/businessAuth/ensure';
 
 
 const questDetail = ref<QuestionDetailRes>()
@@ -39,6 +40,7 @@ const loadDetail = async (id: string) => {
 
 onLoad(async (option) => {
   if (option && option.id) {
+    await ensureBusinessAuthByVisitorProfile()
     await loadDetail(String(option.id))
   }
 })

+ 19 - 0
src/subPages/pages/reportProblems/index.vue

@@ -6,6 +6,7 @@ import { onMounted, ref, watch } from 'vue'
 import GridAddress from '@/components/address/gridAddress/index.vue'
 import { showNotify } from 'vant'
 import { onLoad } from '@dcloudio/uni-app'
+import { ensureBusinessAuthByVisitorProfile } from '@/utils/businessAuth/ensure'
 
 const active = ref(0)
 
@@ -46,6 +47,15 @@ const formData = ref<ReportFormData>(createInitialFormData())
 const range = ref<{ text: string; value: string }[]>([])
 const submitting = ref(false)
 const mobileRegex = /^1[3-9]\d{9}$/
+let businessAuthPromise: Promise<boolean> | null = null
+const ensureBusinessAuthReady = () => {
+  if (!businessAuthPromise) {
+    businessAuthPromise = ensureBusinessAuthByVisitorProfile().finally(() => {
+      businessAuthPromise = null
+    })
+  }
+  return businessAuthPromise
+}
 
 const rules = ref({
   questionType: [{ required: true, message: '请选择类型' }],
@@ -65,6 +75,7 @@ const handleAddressFinish = async(value: AddressSelection) => {
   formData.value.addrName = value.selectedOptions[value.tabIndex]?.areaName || ''
   addressList.value = value
   try {
+    await ensureBusinessAuthReady()
     const { data } = await getPerson({parentCode:value.value})
     personData.value = (data || []) as GridPerson[]
   } catch (e) {
@@ -111,6 +122,7 @@ const fillVisitorPhone = () => {
 const getOwnerPhone = async () => {
   if (!ownerId.value) return
 
+  await ensureBusinessAuthReady()
   const res = await getWecomInternalUser(ownerId.value)
   ownerPhone.value = res.mobile || ''
 }
@@ -149,6 +161,7 @@ const submit = async () => {
     )?.areaCode || '' // 添加兜底处理
     formData.value.contactPersonPhone = formData.value.contactPhone || ''
     console.log('formData.value',formData.value)
+    await ensureBusinessAuthReady()
 
     /* 获取群主手机号(不阻塞流程,报错也继续执行) */
     try {
@@ -224,6 +237,7 @@ const questList = async () => {
   listRequesting.value = true
   loading.value = true
   try {
+    await ensureBusinessAuthReady()
     const currentPage = pageInfo.value.pageNumber
     const reqParams: PageQuestionReportDataReq = {
       pageNumber: currentPage,
@@ -282,6 +296,11 @@ const changeQuestionType = (e: any) => {
 
 onMounted(async () => {
   fillVisitorName()
+  try {
+    await ensureBusinessAuthReady()
+  } catch (e) {
+    console.error('业务认证初始化失败:', e)
+  }
 
   // 问题类型下拉
   try {

+ 3 - 2
src/subPages/pages/reportServer/detail.vue

@@ -1,14 +1,15 @@
 <script lang="ts" setup>
-import { QuestionDetailRes } from '@/api/questionReqort/types';
 import { getServiceReportDetail } from '@/api/reportService';
-import { ReportServiceRes } from '@/api/reportService/types';
+import type { ReportServiceRes } from '@/api/reportService/types';
 import { onLoad } from '@dcloudio/uni-app';
 import { ref } from 'vue';
+import { ensureBusinessAuthByVisitorProfile } from '@/utils/businessAuth/ensure';
 
 
 const reportServiceDetail = ref<ReportServiceRes>()
 onLoad(async (option) => {
   if (option && option.id) {
+    await ensureBusinessAuthByVisitorProfile()
     const { id } = option;
     const { data } = await getServiceReportDetail(id);
     reportServiceDetail.value = data;

+ 32 - 12
src/subPages/pages/reportServer/index.vue

@@ -1,11 +1,12 @@
 <script lang="ts" setup>
 import { addServiceReportData, pageServiceReportData } from '@/api/reportService'
-import { addReportServiceDataReq, ReportServiceRes } from '@/api/reportService/types'
+import type { addReportServiceDataReq, ReportServiceRes } from '@/api/reportService/types'
 import { onMounted, ref } from 'vue'
 import GridAddress from '@/components/address/gridAddress/index.vue'
 import { getQuestionTypeList } from '@/api/questionReqort'
 import { QuestionType } from '@/api/questionReqort/types'
 import { showNotify } from 'vant'
+import { ensureBusinessAuthByVisitorProfile } from '@/utils/businessAuth/ensure'
 
 const active = ref(0)
 
@@ -20,6 +21,15 @@ const rules = ref({
 })
 // 服务类型
 const range = ref<{ text: string; value: string }[]>([])
+let businessAuthPromise: Promise<boolean> | null = null
+const ensureBusinessAuthReady = () => {
+  if (!businessAuthPromise) {
+    businessAuthPromise = ensureBusinessAuthByVisitorProfile().finally(() => {
+      businessAuthPromise = null
+    })
+  }
+  return businessAuthPromise
+}
 
 const submit = async () => {
   if (!formData.value.serviceType) {
@@ -49,15 +59,9 @@ const submit = async () => {
 
 
 
+  await ensureBusinessAuthReady()
   const { data } = await addServiceReportData({
     ...formData.value,
-    streetCode: addressList.value.selectedOptions[1] ? addressList.value.selectedOptions[1].areaCode : '',
-    streetName: addressList.value.selectedOptions[1] ? addressList.value.selectedOptions[1].areaName : '',
-    communityCode: addressList.value.selectedOptions[2] ? addressList.value.selectedOptions[2].areaCode : '',
-    communityName: addressList.value.selectedOptions[2] ? addressList.value.selectedOptions[2].areaName : '',
-    grid: addressList.value.selectedOptions[3] ? addressList.value.selectedOptions[3].areaCode : '',
-    gridName: addressList.value.selectedOptions[3] ? addressList.value.selectedOptions[3].areaName : '',
-    createUser:'admin'
   })
   if (data) {
     uni.showToast({
@@ -81,6 +85,7 @@ const pageInfo = ref({
 })
 const reportServiceList = async () => {
   loading.value = true
+  await ensureBusinessAuthReady()
   const { data } = await pageServiceReportData({
     pageNumber: pageInfo.value.pageNumber,
     pageSize: pageInfo.value.pageSize,
@@ -102,11 +107,21 @@ const handleGoDetail = (item: ReportServiceRes) => {
 }
 
 /** 选择地区 */
-const addressList = ref([])
+interface AddressOption {
+  areaName: string
+  areaCode: string
+}
+
+interface AddressSelection {
+  value: string
+  tabIndex: number
+  selectedOptions: AddressOption[]
+}
+
+const addressList = ref<AddressSelection | null>(null)
 const areaShow = ref(false)
-const handleAddressFinish = (value) => {
-  console.log("啊哈哈",value)
-  formData.value.area = value.selectedOptions[value.tabIndex].areaName
+const handleAddressFinish = (value: AddressSelection) => {
+  formData.value.area = value.selectedOptions[value.tabIndex]?.areaName || ''
   addressList.value = value
 }
 const hanleSelectArea = () => {
@@ -114,6 +129,11 @@ const hanleSelectArea = () => {
 }
 
 onMounted(async () => {
+  try {
+    await ensureBusinessAuthReady()
+  } catch (e) {
+    console.error('业务认证初始化失败:', e)
+  }
   const { data } = await getQuestionTypeList({
     page: 1,
     limit: 9999,

+ 17 - 0
src/utils/businessAuth/ensure.ts

@@ -0,0 +1,17 @@
+import useBusinessAuthStore from '@/store/businessAuth'
+import { getCachedWecomVisitorProfile } from '@/api/wecom'
+
+export const ensureBusinessAuthByVisitorProfile = async () => {
+  const profile = getCachedWecomVisitorProfile()
+  const mobile = (profile?.mobile || '').trim()
+  if (!mobile) return false
+
+  const businessAuth = useBusinessAuthStore()
+  await businessAuth.ensureBusinessToken(mobile)
+  return true
+}
+
+export const clearBusinessAuthToken = () => {
+  const businessAuth = useBusinessAuthStore()
+  businessAuth.clearBusinessTokenInfo()
+}

+ 4 - 1
src/utils/request/errcode.ts

@@ -15,6 +15,8 @@ export enum ErrCode {
 export enum BusinessErrCode {
     /** 暂未登录或token已经过期! */
     UNAUTHORIZED = 401,
+    /** TOKEN失效 */
+    TOKEN_EXPIRED = 10021,
 }
 
 export const errCodeMsgKV: Record<ErrCode, string> = {
@@ -28,4 +30,5 @@ export const errCodeMsgKV: Record<ErrCode, string> = {
 
 export const businessErrCodeMsgKV: Record<BusinessErrCode, string> = {
     [BusinessErrCode.UNAUTHORIZED]: '401-暂未登录或token已经过期!',
-}
+    [BusinessErrCode.TOKEN_EXPIRED]: 'TOKEN失效',
+}

+ 2 - 0
src/utils/request/header.ts

@@ -3,6 +3,8 @@ export enum HeaderKey {
   CONTENT_TYPE = 'Content-Type',
   /** token验证头 */
   AUTHORIZATION = 'Authorization',
+  /** 业务token验证头 */
+  TOKEN = 'token',
 }
 
 /**

+ 28 - 23
src/utils/request/requestHandler.ts

@@ -35,6 +35,12 @@ export function resolveUserAuthToken(): string {
   return readTokenFromStore() || readTokenFromStorage() || ''
 }
 
+export function resolveBusinessAuthToken(): string {
+  const raw = StorageUtil.getItem('businessTokenInfo')
+  if (raw == null) return ''
+  return String(raw).trim()
+}
+
 export function getAuthTokenChannelSnapshot(): AuthTokenChannelSnapshot {
   const fromStore = readTokenFromStore()
   const fromStorage = readTokenFromStorage()
@@ -47,36 +53,35 @@ export function getAuthTokenChannelSnapshot(): AuthTokenChannelSnapshot {
   }
 }
 
-function isPlainParamsObject(params: unknown): params is Record<string, unknown> {
-  return (
-    !!params &&
-    typeof params === 'object' &&
-    !Array.isArray(params) &&
-    !(params instanceof URLSearchParams)
-  )
-}
-
-/**
- * 处理 token:请求头 + query params.token(后端从参数取 token 时统一补齐)
- */
+/** 处理认证请求头:保留原 Authorization,同时追加业务 token 头 */
 export function handleToken(requestConfig: AxiosRequestConfig) {
   if (!requestConfig.isAuth) return
 
-  const resolved = resolveUserAuthToken()
+  const userToken = resolveUserAuthToken()
+  const businessToken = resolveBusinessAuthToken()
 
-  if (requestConfig.headers && requestConfig.headers instanceof AxiosHeaders) {
-    if (!requestConfig.headers.get('authorization') && resolved) {
-      requestConfig.headers.set(HeaderKey.AUTHORIZATION, resolved)
+  if (!requestConfig.headers) {
+    requestConfig.headers = {}
+  }
+
+  if (requestConfig.headers instanceof AxiosHeaders) {
+    if (!requestConfig.headers.get('authorization') && userToken) {
+      requestConfig.headers.set(HeaderKey.AUTHORIZATION, userToken)
     }
+    if (!requestConfig.headers.get('token') && businessToken) {
+      requestConfig.headers.set(HeaderKey.TOKEN, businessToken)
+    }
+    return
   }
 
-  const prev = requestConfig.params
-  const base: Record<string, unknown> = isPlainParamsObject(prev) ? { ...prev } : {}
-  const existing = String(base.token ?? '').trim()
-  if (!existing && resolved) {
-    base.token = resolved
+  const headers = requestConfig.headers as Record<string, unknown>
+  const hasAuthorization = headers[HeaderKey.AUTHORIZATION] || headers.authorization
+  const hasBusinessToken = headers[HeaderKey.TOKEN] || headers.token
+
+  if (!hasAuthorization && userToken) {
+    headers[HeaderKey.AUTHORIZATION] = userToken
   }
-  if (Object.keys(base).length > 0) {
-    requestConfig.params = base
+  if (!hasBusinessToken && businessToken) {
+    headers[HeaderKey.TOKEN] = businessToken
   }
 }

+ 35 - 34
src/utils/request/responseHandler.ts

@@ -1,8 +1,9 @@
-import {ApiResponse} from "@/api/types";
-import {AxiosError, AxiosResponse} from "axios";
-import {BusinessErrCode, businessErrCodeMsgKV, ErrCode, errCodeMsgKV} from "@/utils/request/errcode";
+import type { ApiResponse } from '@/api/types'
+import type { AxiosError, AxiosResponse } from 'axios'
+import { BusinessErrCode, businessErrCodeMsgKV, ErrCode, errCodeMsgKV } from '@/utils/request/errcode'
 import 'vant/es/notify/style'
-import { showNotify } from 'vant';
+import { showNotify } from 'vant'
+import { clearBusinessAuthToken } from '@/utils/businessAuth/ensure'
 
 
 /**
@@ -10,28 +11,29 @@ import { showNotify } from 'vant';
  *
  * @returns 返回响应success
  */
-export function handleInnerCodeErr(respData:ApiResponse) {
-    if(respData.code != 200) {
-        // 展示默认错误提示信息
-        const outMsgInfo = businessErrCodeMsgKV[respData.code as BusinessErrCode]
-        if(outMsgInfo) {
-            // 提示错误信息
-            showNotify({ type: 'warning', message: respData.msg });
-        }else {
-            showNotify({ type: 'warning', message:'服务器发生了错误,请重试' });
-        }
-        return false
+export function handleInnerCodeErr(respData: ApiResponse) {
+  if (respData.code !== 200) {
+    const outMsgInfo = businessErrCodeMsgKV[respData.code as BusinessErrCode]
+    if (outMsgInfo) {
+      showNotify({ type: 'warning', message: respData.msg })
+    } else {
+      showNotify({ type: 'warning', message: '服务器发生了错误,请重试' })
     }
-    return true
+    return false
+  }
+  return true
 }
 
 /**
  * 处理http响应码为200的响应
  */
-export const handleNormalResponse = async (resp:AxiosResponse<ApiResponse>) => {
+export const handleNormalResponse = async (resp: AxiosResponse<ApiResponse>) => {
   // 处理已知错误码
   if (resp?.data?.code) {
     switch (resp.data.code) {
+      case BusinessErrCode.TOKEN_EXPIRED:
+        clearBusinessAuthToken()
+        break
       case ErrCode.UNAUTHORIZED:
         // 执行登出操作
         break
@@ -46,22 +48,21 @@ export const handleNormalResponse = async (resp:AxiosResponse<ApiResponse>) => {
 /**
  * 处理http响应码不为200的响应
  */
-export const handleHttpError = (err:AxiosError) => {
-    if(err.code === 'ECONNABORTED') {
-        return Promise.reject(err)
-    }
+export const handleHttpError = (err: AxiosError) => {
+  if (err.code === 'ECONNABORTED') {
+    return Promise.reject(err)
+  }
 
-    // 展示默认错误提示信息
-    const msg = errCodeMsgKV[err.response?.status as ErrCode]
-    if(err.code === 'ECONNABORTED' && err.message.includes('timeout')) {
-        showNotify({ type: 'warning', message: '请求超时,请重试!' });
-    }else if(err.message === 'Network Error') {
-        showNotify({ type: 'warning', message: '服务器错误或网络错误, 请稍后再试!' });
-    }else if(msg) {
-        showNotify({ type: 'warning', message: msg });
-    }else {
-        showNotify({ type: 'warning', message: '服务器发出了未知错误!' });
-    }
+  const msg = errCodeMsgKV[err.response?.status as ErrCode]
+  if (err.code === 'ECONNABORTED' && err.message.includes('timeout')) {
+    showNotify({ type: 'warning', message: '请求超时,请重试!' })
+  } else if (err.message === 'Network Error') {
+    showNotify({ type: 'warning', message: '服务器错误或网络错误, 请稍后再试!' })
+  } else if (msg) {
+    showNotify({ type: 'warning', message: msg })
+  } else {
+    showNotify({ type: 'warning', message: '服务器发出了未知错误!' })
+  }
 
-    return Promise.reject(err)
-}
+  return Promise.reject(err)
+}

+ 14 - 4
src/utils/storage/storage.ts

@@ -1,8 +1,8 @@
 /**
  * localStorage 封装
  */
-import globalConfig from "@/config/global";
-import {StorageData} from "@/utils/storage/type";
+import globalConfig from '@/config/global'
+import type { StorageData } from '@/utils/storage/type'
 
 class StorageUtil {
     /**
@@ -27,11 +27,21 @@ class StorageUtil {
     }
 
     /**
+     * 删除命名空间下指定键
+     * @param key
+     */
+    static removeItem<K extends keyof StorageData>(key: K) {
+        const storage = this.getStorage()
+        const { [key]: _removed, ...rest } = storage
+        localStorage.setItem(globalConfig.storeNamespace, JSON.stringify(rest))
+    }
+
+    /**
      * 通过传入key 获取对应的值
      * @param key
      * @returns 获取对应的值
      */
-    static getItem<key extends keyof StorageData>(key: key):StorageData[key] {
+    static getItem<key extends keyof StorageData>(key: key): StorageData[key] {
         const storage = this.getStorage()
         return storage[key]
     }
@@ -44,4 +54,4 @@ class StorageUtil {
     }
 }
 
-export default StorageUtil
+export default StorageUtil

+ 10 - 6
src/utils/storage/type.ts

@@ -1,8 +1,12 @@
-import {BaseUserInfo} from "@/api/system/user/types";
+import type { BaseUserInfo } from '@/api/system/user/types'
 
 export interface StorageData {
-    /** 当前用户token信息  */
-    userTokenInfo?: string;
-    /** 当前登录用户基本信息 */
-    userBaseInfo: BaseUserInfo;
-}
+  /** 当前用户token信息  */
+  userTokenInfo?: string
+  /** 当前登录用户基本信息 */
+  userBaseInfo: BaseUserInfo
+  /** 当前业务认证token信息 */
+  businessTokenInfo?: string
+  /** 当前业务认证token过期时间戳 */
+  businessTokenExpireAt?: number
+}