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

Merge branch 'encryptionFun-optimize'

lifanagju_citu 3 napja
szülő
commit
b2bc8c8aa6

+ 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
+    }
+  })
+}

+ 27 - 11
layout/components/authModal/login/index.vue

@@ -16,7 +16,7 @@
         <!-- 手机号快捷登录 -->
         <view v-if="current === 0">
           <button v-if="!protocol" class="send-button MiSans-Medium" @click="showProtocolToast">手机号快捷登录</button>
-          <button v-else class="send-button MiSans-Medium" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">手机号快捷登录</button>
+          <button v-else class="send-button MiSans-Medium" :loading="phoneNumberLoading" :disabled="phoneNumberLoading" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">手机号快捷登录</button>
         </view>
 
         <!-- 短信验证码登录 -->
@@ -65,7 +65,7 @@
           </uni-forms-item>
         </uni-forms>
 
-        <button v-if="current !== 0" class="send-button MiSans-Medium" @tap="handleLogin"> 登 录  </button>
+        <button v-if="current !== 0" class="send-button MiSans-Medium" :loading="smsLoginLoading" :disabled="smsLoginLoading" @tap="handleLogin"> 登 录  </button>
         <view class="agreement-box ss-flex ss-row-center">
           <uni-icons size="20" :type="protocol ? 'checkbox-filled' : 'circle'" :color="protocol ? '#00B760' : '#ccc'" @tap="protocol = !protocol"></uni-icons>
           <view class="color-999 ss-flex ss-col-center ss-m-l-8 font-size-13">
@@ -122,7 +122,7 @@
       </uni-forms>
       <view class="register login color-primary ss-p-b-5 MiSans-Normal" style="text-align: end;" @tap="handleChangeLogin">已有账户?去登陆</view>
       <view>
-        <button class="send-button" @tap="handleRegister"> 注 册 </button>
+        <button class="send-button" :loading="registerLoading" :disabled="registerLoading" @tap="handleRegister"> 注 册 </button>
       </view>
       <view class="color-999 ss-flex ss-col-center ss-row-center ss-m-l-8 font-size-13" style="margin-bottom: 30px;">
         <span class="MiSans-Normal">点击注册即代表您同意</span>
@@ -150,6 +150,9 @@ const current = ref(0)
 const accountLoginRef = ref()
 const smsLoginRef = ref()
 const registerForm = ref()
+const phoneNumberLoading = ref(false)
+const smsLoginLoading = ref(false)
+const registerLoading = ref(false)
 const protocol = ref(false)
 const state = ref({
   isMobileEnd: false, // 手机号输入完毕
@@ -246,6 +249,8 @@ const getPhoneNumber = async (e) => {
     return
   }
   changeType.value = 'login'
+  phoneNumberLoading.value = true
+
   wx.login({
     success: async (result) => {
       const wxLoginCode = result?.code || ''
@@ -255,8 +260,12 @@ const getPhoneNumber = async (e) => {
         state: e.detail.encryptedData,
       }
       await useUserStore.handleSmsLogin(query, current.value)
+      phoneNumberLoading.value = false
     },
-    fail:(res)=> { console.log("获取登录凭证code失败!", res) }
+    fail:(res)=> { 
+      phoneNumberLoading.value = false
+      console.log("获取登录凭证code失败!", res)
+    }
   })
 }
 
@@ -264,10 +273,11 @@ async function handleRegister () {
   const validate = await unref(registerForm).validate()
   if (!validate) return
   try {
+    registerLoading.value = true
     await useUserStore.handleRegister(state.value.register)
     changeType.value = 'login'
   } finally {
-
+    registerLoading.value = false
   }
 }
 
@@ -276,12 +286,18 @@ const handleLogin = async () => {
   if (!protocol.value) return uni.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
   const validate = await unref(current.value === 1 ? smsLoginRef : accountLoginRef).validate()
   if (!validate) return
-  const query = current.value === 1 ? state.value.sms : state.value.account
-  Object.assign(query, {
-    account: query.phone
-  })
-  
-  await useUserStore.handleSmsLogin(query, current.value)
+
+  try {
+    smsLoginLoading.value = true
+    const query = current.value === 1 ? state.value.sms : state.value.account
+    Object.assign(query, {
+      account: query.phone
+    })
+    await useUserStore.handleSmsLogin(query, current.value)
+    
+  } finally {
+    smsLoginLoading.value = false
+  }
 }
 </script>
 

+ 2 - 2
layout/components/authModal/selectUserType/index.vue

@@ -2,7 +2,7 @@
 <template>
   <scroll-view class="scrollBox" scroll-y="true">
     <template v-if="showSelect">
-      <view class="text-center ss-m-t-50 MiSans-Medium" style="color: #666; font-weight: 400; font-size: 18px; margin-top: 100px;">请选择当前角色</view>
+      <view class="text-center ss-m-t-50 MiSans-Medium" style="color: #666; font-weight: 400; font-size: 18px; margin-top: 50px;">请选择当前角色</view>
       <view class="ss-p-50">
         <uni-card class="ss-m-t-30" @tap="handleClickRole(0)">
           <view class="d-flex flex-column align-center ss-p-y-50">
@@ -40,7 +40,7 @@ const handleClickRole = (type) => {
   } else {
     // 普通用户
     showAuthModal('necessaryInfo')
-    uni.setStorageSync('necessaryInfoReady', 'fddeaddc47868b')
+    uni.setStorageSync('necessaryInfoReady', 'student')
   }
 }
 

+ 1 - 1
pagesB/websiteLoginVerification/index.vue

@@ -118,7 +118,7 @@ const handleTokenLogin = async () => {
 		}
 	}
   else showAuthModal('necessaryInfo')
-  uni.setStorageSync('necessaryInfoReady', necessaryInfoReady ? 'ready' : 'fddeaddc47868b')
+  uni.setStorageSync('necessaryInfoReady', necessaryInfoReady ? 'ready' : 'student')
 }
 
 // 微信登录

+ 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
+    }
+  }
+)

+ 2 - 1
store/user.js

@@ -55,6 +55,7 @@ export const userStore = defineStore('user', {
         uni.showToast({
           title: '登录成功'
         })
+        closeAuthModal()
       }
       this.accountInfo = data
       const res = await this.getInfo()
@@ -108,7 +109,7 @@ export const userStore = defineStore('user', {
       data.necessaryInfoReady = necessaryInfoReady
       if (necessaryInfoReady) closeAuthModal()
       else showAuthModal('necessaryInfo')
-      uni.setStorageSync('necessaryInfoReady', necessaryInfoReady ? 'ready' : 'fddeaddc47868b');
+      uni.setStorageSync('necessaryInfoReady', necessaryInfoReady ? 'ready' : 'student');
     },
 
     // 获取人才信息

+ 12 - 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,26 @@ 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)

+ 1 - 1
utils/position.js

@@ -124,7 +124,7 @@ const preferred = ref({})
 const getSystemWebContent = async () => {
   const { data } = await getWebContent()
   // 优选集团
-  preferred.value = data.appPreferredGroup
+  preferred.value = data?.appPreferredGroup
 }
 getSystemWebContent()
 

+ 87 - 7
utils/request.js

@@ -11,6 +11,8 @@ 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'
 
@@ -25,6 +27,8 @@ const getEventList = () => {
 }
 getEventList()
 
+const errorData = []
+
 const options = {
 	// 显示操作成功消息 默认不显示
 	showSuccess: false,
@@ -85,7 +89,7 @@ const http = new Request({
  * @description 请求拦截器
  */
 http.interceptors.request.use(
-	(config) => {
+	async (config) => {
     const useUserStore = userStore()
     // 自定义处理【auth 授权】:必须登录的接口,则跳出 AuthModal 登录弹窗
 		if (config.custom.auth && !useUserStore.isLogin) {
@@ -133,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)
 		}
 
@@ -187,6 +217,8 @@ http.interceptors.response.use(
       return
     }
 
+		const config = response.config
+
     // 自定处理【loading 加载中】:如果需要显示 loading,则关闭 loading
 		response.config.custom.showLoading && closeLoading();
 
@@ -197,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('前台打印: 重复请求,请稍后重试')
@@ -258,6 +307,9 @@ http.interceptors.response.use(
 				case 429:
 					errorMessage = '请求频繁, 请稍后再访问';
 					break;
+				case 440:
+					errorMessage = '网络请求出错'; // 签名异常
+					break;
 				case 500:
 					errorMessage = '服务器开小差啦,请稍后再试~';
 					break;
@@ -374,6 +426,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(() => {})
@@ -381,7 +461,7 @@ const getIntegral = (url) => {
 
 /** 获得必填人才信息情况 */
 export const showNecessaryInfoPopup = () => {
-  return uni.getStorageSync('necessaryInfoReady') === 'fddeaddc47868b';
+  return uni.getStorageSync('necessaryInfoReady') === 'student';
 }
 
 /** 获得访问令牌 */