소스 검색

签名错误时取返回的时间戳作为服务器时间,再次发起请求,请求第三次还是签名不正确的话弹出'网络请求错误!'。(签名时间戳取值 = 用户当地时间+服务器时间与当地时间的时间戳。避免用户当地时间的时间戳可能与服务器不是一个时区导致签名不匹配)

lifanagju_citu 1 개월 전
부모
커밋
9efe6f96e1
5개의 변경된 파일167개의 추가작업 그리고 20개의 파일을 삭제
  1. 25 0
      api/system.js
  2. 34 0
      store/system.js
  3. 10 0
      utils/index.js
  4. 11 7
      utils/openEncryption.js
  5. 87 13
      utils/request.js

+ 25 - 0
api/system.js

@@ -0,0 +1,25 @@
+import request from "@/utils/request"
+
+// 版本信息(服务器时间戳)
+export const getVersion = () => {
+  return request({
+    url: '/app-api/menduner/system/get/version',
+    method: 'GET',
+    custom: {
+      showLoading: false,
+      auth: false
+    }
+  })
+}
+
+export const sendError = (data) => {
+  return request({
+    url: '/app-api/menduner/system/error-record/create',
+    method: 'POST',
+    data,
+    custom: {
+      showLoading: false,
+      auth: false
+    }
+  })
+}

+ 34 - 0
store/system.js

@@ -0,0 +1,34 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import { getVersion } from '@/api/system'
+
+export const useSystem = defineStore('system', 
+  () => {
+    const systemInfo = ref({
+      timeDifference: undefined, // 服务器时间 - 浏览器时间
+      beijingTimestamp: 0
+    })
+
+    const setTimeDifference = (Difference) => {
+      systemInfo.value.timeDifference = Number(Difference) || 0
+    }
+
+    const getTimeDifference = async () => {
+      try {
+        const res = await getVersion()
+        const time = res?.data?.time
+        const _now = new Date().getTime()
+        const _tem = time - _now
+        return _tem
+      } catch (err) {
+        console.log('getTimeDifference-err:', err)
+      }
+    }
+
+    return {
+      systemInfo,
+      setTimeDifference,
+      getTimeDifference
+    }
+  }
+)

+ 10 - 0
utils/index.js

@@ -168,6 +168,16 @@ export const removeEmptyProperties = (obj) => {
   )
 }
 
+// 替换无效编码序列(解决 decodeURIComponent 报错 "URI malformed" 的问题)
+export const decodeURIComponentSafe = (str) => {
+  try {
+    return decodeURIComponent(str);
+  } catch (e) {
+    // 替换无效的百分号编码为空字符串
+    return decodeURIComponent(str.replace(/%(?![\da-f]{2})/gi, ''));
+  }
+}
+
 export const FenYuanTransform = (count, type='toYuan') => {
   if ((count - 0) === 0) return 0
   if (!count) return ''

+ 11 - 7
utils/openEncryption.js

@@ -12,6 +12,7 @@
 
 import { generateUUID } from "@/utils/index" 
 import { sha256 } from 'js-sha256'
+import { decodeURIComponentSafe } from '@/utils/index'
 
 /**
  * 
@@ -19,22 +20,25 @@ import { sha256 } from 'js-sha256'
  * @param { Object } body
  * @returns 
 */
-export const encryptionFun = ({raw, body, appId, AppSecret}) => {
+export const encryptionFun = ({raw, body, appId, AppSecret, timestamp}) => {
   const initSign = {
     appId,
     nonce: generateUUID(),
-    timestamp: new Date().getTime() + 3000,
+    timestamp,
   }
-  const _initSign = Object.keys(initSign).reduce((str, key) => str += `&${key}=${initSign[key]}`, '')
-  const paramsStr = _initSign.slice(1, _initSign.length) + AppSecret
+  const _initSignArr = Object.keys(initSign).map(key => {
+    return `${key}=${initSign[key]}`
+  })
+  const _initSign = _initSignArr.join('&')
+
+  const paramsStr = _initSign + AppSecret
   let str = ''
   if (raw) {
-    str += decodeURIComponent(raw)
+    str += decodeURIComponentSafe(raw)
   }
   if (body && Object.keys(body).length) {
-    str += decodeURIComponent(JSON.stringify(body))
+    str += decodeURIComponentSafe(JSON.stringify(body))
   }
-  // console.log('str:', str, 'paramsStr:', paramsStr)
   return {
     ...initSign,
     sign: sha256(str + paramsStr)

+ 87 - 13
utils/request.js

@@ -11,17 +11,13 @@ import { showAuthModal } from '@/hooks/useModal'
 import { rewardEventTrackClick } from '@/api/integral'
 import { getSuffixAfterPrefix } from '@/utils/prefixUrl'
 import { encryptionFun } from '@/utils/openEncryption'
+import { useSystem } from '@/store/system'
+import { sendError } from '@/api/system'
 
 import qs from 'qs'
 
-const RETURNED_API = '/admin-api/menduner/reward/event-track/click'
 // 规则配置跟踪列表
-// const eventRules = uni.getStorageSync('eventList')?.length ? JSON.parse(uni.getStorageSync('eventList')) : []
-// 请求成功后触发
-// const requestCompletionTrigger = eventRules ? eventRules.filter(_e => _e.triggerType === '0') : []
-// 点击触发
-// const clickTrigger = eventRules ? eventRules.filter(_e => _e.triggerType === '1') : []
-
+const RETURNED_API = '/admin-api/menduner/reward/event-track/click'
 let requestCompletionTrigger = []
 let clickTrigger = []
 const getEventList = () => {
@@ -31,6 +27,8 @@ const getEventList = () => {
 }
 getEventList()
 
+const errorData = []
+
 const options = {
 	// 显示操作成功消息 默认不显示
 	showSuccess: false,
@@ -90,7 +88,7 @@ const http = new Request({
  * @description 请求拦截器
  */
 http.interceptors.request.use(
-	(config) => {
+	async (config) => {
     const useUserStore = userStore()
     // 自定义处理【auth 授权】:必须登录的接口,则跳出 AuthModal 登录弹窗
 		if (config.custom.auth && !useUserStore.isLogin) {
@@ -139,12 +137,38 @@ http.interceptors.request.use(
 		
 		// 开启参数加密
 		if (config.custom?.openEncryption) {
+      const { getTimeDifference, setTimeDifference, systemInfo } = useSystem()
 			const raw = config.url.split('?')[1]
-			const body = {
-				...config.data,
-				...config.params
-			}
-			const header = encryptionFun({raw, body, appId: 'web_client', AppSecret: 'fa0fc0b5098b974b'})
+      const body = {
+        ...typeof config.data === 'string' ? JSON.parse(config.data) : config.data,
+        ...typeof config.params === 'string' ? JSON.parse(config.params) : config.params
+      }
+      if (!systemInfo?.timeDifference) {
+        const _difference = await getTimeDifference()
+        setTimeDifference(_difference)
+      }
+
+      const header = encryptionFun({
+        raw,
+        body,
+        appId: 'web_client',
+        AppSecret: 'fa0fc0b5098b974b',
+        timestamp: new Date().getTime() + (Number(systemInfo?.timeDifference) || 0),
+      })
+      const content = {
+        data: config.data,
+        params: config.params,
+        body,
+        raw,
+        config
+      }
+			// 记录请求备用-签名错误后重试
+
+      errorData.push({
+        time: header.timestamp,
+        url: config.url,
+        content
+      })
 			Object.assign(config.header, header)
 		}
 
@@ -193,6 +217,8 @@ http.interceptors.response.use(
       return
     }
 
+		const config = response.config
+
     // 自定处理【loading 加载中】:如果需要显示 loading,则关闭 loading
 		response.config.custom.showLoading && closeLoading();
 
@@ -203,6 +229,23 @@ http.interceptors.response.use(
         return handleRefreshToken(response.config);
       }
 
+			// 签名错误
+			if (response.data.code === 440) {
+				// 更新服务器与用户时间差的值
+      	const { setTimeDifference } = useSystem()
+				if (Number(response?.data?.msg)) setTimeDifference(response.data.msg - new Date().getTime())
+				// 请求重试
+				signErrRetry(response.config)
+				// 保存错误信息
+				const _index = errorData.findIndex(e => e.url === config.url && e.time === +config.header.timestamp)
+				if (_index > -1) {
+					const _d = errorData.splice(_index, 1)
+					const _item = _d[0]
+					sendError({ content: JSON.stringify(_item.content), mark: _item.time + '' })
+				}
+				response.config.custom.showError = false // 不弹错误弹窗
+			}
+
       // 错误提示
 			if (response.config.custom.showError) {
 				if (response.data?.msg === '重复请求,请稍后重试') console.error('前台打印: 重复请求,请稍后重试')
@@ -266,6 +309,9 @@ http.interceptors.response.use(
 				case 429:
 					errorMessage = '请求频繁, 请稍后再访问';
 					break;
+				case 440:
+					errorMessage = '网络请求出错'; // 签名异常
+					break;
 				case 500:
 					errorMessage = '服务器开小差啦,请稍后再试~';
 					break;
@@ -384,6 +430,34 @@ const handleAuthorized = () => {
   })
 }
 
+/**
+ * 处理440签名错误
+*/
+const maxRetries = 2 // 设置签名错误重试请求次数,超出次数弹出错误
+const retryDelay = 1000 // 请求延迟
+const signErrRetry = (config) => {
+	if (config) {
+		if (!config?.custom?.retryCount) {
+			config.custom.retryCount = 0
+		}
+	
+		// 超过重试次数
+		if (config.custom.retryCount >= maxRetries) {
+			uni.showToast({
+				title: '网络请求错误!',
+				icon: 'none',
+				mask: false,
+			});
+			return Promise.reject({ statusCode: 440 })
+		}
+		// 重试
+		config.custom.retryCount++
+		setTimeout(() => {
+			return request(config)
+		}, retryDelay)
+	}
+}
+
 // 触发获取积分
 const getIntegral = (url) => {
   rewardEventTrackClick(url).then(() => {})