Browse Source

perf: refresh token && delete console

xingyu 2 years atrás
parent
commit
21cf922723

+ 2 - 2
yudao-ui-admin-vue3/src/api/system/user/profile/index.ts

@@ -24,6 +24,6 @@ export const updateUserPwdApi = (oldPassword: string, newPassword: string) => {
 }
 
 // 用户头像上传
-export const uploadAvatarApi = (params) => {
-  return request.upload({ url: '/system/user/profile/update-avatar', params })
+export const uploadAvatarApi = (data) => {
+  return request.upload({ url: '/system/user/profile/update-avatar', data: data })
 }

+ 0 - 3
yudao-ui-admin-vue3/src/components/Verifition/src/Verify.vue

@@ -102,7 +102,6 @@ export default {
      * @description 刷新
      * */
     const refresh = () => {
-      console.log(instance.value)
       if (instance.value.refresh) {
         instance.value.refresh()
       }
@@ -271,7 +270,6 @@ export default {
   -moz-box-sizing: content-box;
   box-sizing: content-box;
   border: 1px solid #ddd;
-  -webkit-border-radius: 4px;
 }
 
 .verify-bar-area .verify-move-block {
@@ -284,7 +282,6 @@ export default {
   -moz-box-sizing: content-box;
   box-sizing: content-box;
   box-shadow: 0 0 2px #888888;
-  -webkit-border-radius: 1px;
 }
 
 .verify-bar-area .verify-move-block:hover {

+ 0 - 1
yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifySlide.vue

@@ -242,7 +242,6 @@ export default {
         //兼容移动端
         var x = e.touches[0].pageX
       }
-      console.log(barArea)
       startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left)
       startMoveTime.value = +new Date() //开始滑动的时间
       if (isEnd.value == false) {

+ 59 - 17
yudao-ui-admin-vue3/src/config/axios/index.ts

@@ -1,10 +1,11 @@
 import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
-import { ElMessage, ElNotification } from 'element-plus'
+import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
 import qs from 'qs'
 import { config } from '@/config/axios/config'
-import { getAccessToken, getTenantId, removeToken } from '@/utils/auth'
+import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth'
 import errorCode from './errorCode'
 import { useI18n } from '@/hooks/web/useI18n'
+import { resetRouter } from '@/router'
 
 const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
 const BASE_URL = import.meta.env.VITE_BASE_URL
@@ -20,9 +21,9 @@ const ignoreMsgs = [
 export const isRelogin = { show: false }
 // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
 // 请求队列
-// const requestList = []
+let requestList: any[] = []
 // 是否正在刷新中
-// const isRefreshToken = false
+let isRefreshToken = false
 
 export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH]
 
@@ -91,6 +92,7 @@ service.interceptors.request.use(
 service.interceptors.response.use(
   async (response: AxiosResponse<Recordable>) => {
     const { data } = response
+    console.info(data)
     if (!data) {
       // 返回“[HTTP]请求没有返回值”;
       throw new Error()
@@ -112,16 +114,38 @@ service.interceptors.response.use(
       return Promise.reject(msg)
     } else if (code === 401) {
       // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
-      return handleAuthorized()
-      // if (!isRefreshToken) {
-      //   isRefreshToken = true
-      //   // 1. 如果获取不到刷新令牌,则只能执行登出操作
-      //   if (!getRefreshToken()) {
-      //     return handleAuthorized()
-      //   }
-      //   // 2. 进行刷新访问令牌
-      //   // TODO: 引入refreshToken会循环依赖报错
-      // }
+      if (!isRefreshToken) {
+        isRefreshToken = true
+        // 1. 如果获取不到刷新令牌,则只能执行登出操作
+        if (!getRefreshToken()) {
+          return handleAuthorized()
+        }
+        // 2. 进行刷新访问令牌
+        try {
+          const refreshTokenRes = await refreshToken()
+          // 2.1 刷新成功,则回放队列的请求 + 当前请求
+          setToken(refreshTokenRes.data)
+          requestList.forEach((cb: any) => cb())
+          return service(response.config)
+        } catch (e) {
+          // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
+          // 2.2 刷新失败,只回放队列的请求
+          requestList.forEach((cb: any) => cb())
+          // 提示是否要登出。即不回放当前请求!不然会形成递归
+          return handleAuthorized()
+        } finally {
+          requestList = []
+          isRefreshToken = false
+        }
+      } else {
+        // 添加到队列,等待刷新获取到新的令牌
+        return new Promise((resolve) => {
+          requestList.push(() => {
+            ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
+            resolve(service(response.config))
+          })
+        })
+      }
     } else if (code === 500) {
       ElMessage.error(t('sys.api.errMsg500'))
       return Promise.reject(new Error(msg))
@@ -165,14 +189,32 @@ service.interceptors.response.use(
     return Promise.reject(error)
   }
 )
+
+const refreshToken = async () => {
+  return await service({
+    url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(),
+    method: 'post'
+  })
+}
 const handleAuthorized = () => {
   const { t } = useI18n()
   if (!isRelogin.show) {
-    removeToken()
     isRelogin.show = true
-    ElNotification.error(t('sys.api.timeoutMessage'))
+    ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
+      confirmButtonText: t('login.relogin'),
+      cancelButtonText: t('common.cancel'),
+      type: 'warning'
+    })
+      .then(() => {
+        removeToken()
+        resetRouter() // 重置静态路由表
+        isRelogin.show = false
+        location.href = '/'
+      })
+      .catch(() => {
+        isRelogin.show = false
+      })
   }
-  location.href = '/'
   return Promise.reject(t('sys.api.timeoutMessage'))
 }
 export { service }

+ 6 - 5
yudao-ui-admin-vue3/src/router/index.ts

@@ -10,6 +10,7 @@ import { createRouter, createWebHashHistory } from 'vue-router'
 import { usePermissionStoreWithOut } from '@/store/modules/permission'
 import { useDictStoreWithOut } from '@/store/modules/dict'
 import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
+import { isRelogin } from '@/config/axios'
 
 const permissionStore = usePermissionStoreWithOut()
 
@@ -48,14 +49,14 @@ router.beforeEach(async (to, from, next) => {
       next({ path: '/' })
     } else {
       if (!dictStore.getIsSetDict) {
+        isRelogin.show = true
         // 获取所有字典
         const res = await listSimpleDictDataApi()
-        if (res) {
-          dictStore.setDictMap(res)
-          dictStore.setIsSetDict(true)
-        }
+        dictStore.setDictMap(res)
+        dictStore.setIsSetDict(true)
       }
       if (permissionStore.getIsAddRouters) {
+        isRelogin.show = false
         next()
         return
       }
@@ -77,7 +78,7 @@ router.beforeEach(async (to, from, next) => {
     if (whiteList.indexOf(to.path) !== -1) {
       next()
     } else {
-      next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
+      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
     }
   }
 })

+ 30 - 26
yudao-ui-admin-vue3/src/views/Profile/components/UserAvatar.vue

@@ -6,12 +6,12 @@ import { ElRow, ElCol, ElUpload, ElMessage, ElDialog } from 'element-plus'
 import { propTypes } from '@/utils/propTypes'
 import { uploadAvatarApi } from '@/api/system/user/profile'
 const cropper = ref()
+const dialogVisible = ref(false)
+const cropperVisible = ref(false)
 const props = defineProps({
   img: propTypes.string.def('')
 })
-const state = reactive({
-  dialogVisible: false,
-  cropperVisible: false,
+const options = reactive({
   dialogTitle: '编辑头像',
   options: {
     img: props.img, //裁剪图片的地址
@@ -27,8 +27,11 @@ const state = reactive({
 })
 /** 编辑头像 */
 const editCropper = () => {
-  state.dialogVisible = true
-  state.cropperVisible = true
+  dialogVisible.value = true
+}
+// 打开弹出层结束时的回调
+const modalOpened = () => {
+  cropperVisible.value = true
 }
 /** 向左旋转 */
 const rotateLeft = () => {
@@ -44,7 +47,7 @@ const changeScale = (num: number) => {
   cropper.value.changeScale(num)
 }
 // 覆盖默认的上传行为
-const requestUpload = () => {}
+const requestUpload: any = () => {}
 /** 上传预处理 */
 const beforeUpload = (file: Blob) => {
   if (file.type.indexOf('image/') == -1) {
@@ -54,64 +57,65 @@ const beforeUpload = (file: Blob) => {
     reader.readAsDataURL(file)
     reader.onload = () => {
       if (reader.result) {
-        state.options.img = reader.result as string
+        options.options.img = reader.result as string
       }
     }
   }
 }
 /** 上传图片 */
 const uploadImg = () => {
-  cropper.value.getCropBlob((data) => {
+  cropper.value.getCropBlob((data: any) => {
     let formData = new FormData()
     formData.append('avatarfile', data)
     uploadAvatarApi(formData)
   })
 }
 /** 实时预览 */
-const realTime = (data) => {
-  state.previews = data
+const realTime = (data: any) => {
+  options.previews = data
 }
 watch(
   () => props.img,
   () => {
     if (props.img) {
-      state.options.img = props.img
-      state.previews.img = props.img
-      state.previews.url = props.img
+      options.options.img = props.img
+      options.previews.img = props.img
+      options.previews.url = props.img
     }
   }
 )
 </script>
 <template>
   <div class="user-info-head" @click="editCropper()">
-    <img :src="state.options.img" title="点击上传头像" class="img-circle img-lg" alt="" />
+    <img :src="options.options.img" title="点击上传头像" class="img-circle img-lg" alt="" />
   </div>
   <el-dialog
-    v-model="state.dialogVisible"
-    :title="state.dialogTitle"
-    width="50%"
-    :maxHeight="350"
+    v-model="dialogVisible"
+    :title="options.dialogTitle"
+    width="800px"
+    append-to-body
     style="padding: 30px 20px"
+    @opened="modalOpened"
   >
     <el-row>
       <el-col :xs="24" :md="12" style="height: 350px">
         <VueCropper
           ref="cropper"
-          :img="state.options.img"
+          :img="options.options.img"
           :info="true"
-          :autoCrop="state.options.autoCrop"
-          :autoCropWidth="state.options.autoCropWidth"
-          :autoCropHeight="state.options.autoCropHeight"
-          :fixedBox="state.options.fixedBox"
+          :autoCrop="options.options.autoCrop"
+          :autoCropWidth="options.options.autoCropWidth"
+          :autoCropHeight="options.options.autoCropHeight"
+          :fixedBox="options.options.fixedBox"
           @real-time="realTime"
-          v-if="state.cropperVisible"
+          v-if="cropperVisible"
         />
       </el-col>
       <el-col :xs="24" :md="12" style="height: 350px">
         <div class="avatar-upload-preview">
           <img
-            :src="state.previews.url"
-            :style="state.previews.img"
+            :src="options.previews.url"
+            :style="options.previews.img"
             style="!max-width: 100%"
             alt=""
           />

+ 0 - 1
yudao-ui-admin-vue3/src/views/system/menu/index.vue

@@ -47,7 +47,6 @@ const getTree = async () => {
   const res = await MenuApi.listSimpleMenusApi()
   const menu = { id: 0, name: '主类目', children: [] as any[] }
   menu.children = handleTree(res)
-  console.info(menu)
   menuOptions.value = menu
 }
 // ========== 查询 ==========

+ 1 - 1
yudao-ui-admin-vue3/src/views/system/oauth2/client/index.vue

@@ -155,7 +155,7 @@ getList()
     </Table>
   </ContentWrap>
 
-  <Dialog v-model="dialogVisible" :title="dialogTitle">
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%" maxHeight="420px">
     <!-- 对话框(添加 / 修改) -->
     <Form
       v-if="['create', 'update'].includes(actionType)"

+ 1 - 4
yudao-ui-admin-vue3/src/views/system/role/index.vue

@@ -110,7 +110,7 @@ const defaultProps = {
   label: 'name',
   value: 'id'
 }
-const treeOptions = ref([]) // 菜单树形结构
+const treeOptions = ref<any[]>([]) // 菜单树形结构
 const treeRef = ref<InstanceType<typeof ElTree>>()
 const dialogScopeVisible = ref(false)
 const dialogScopeTitle = ref('数据权限')
@@ -133,7 +133,6 @@ const handleScope = async (type: string, row: RoleVO) => {
     const menuRes = await listSimpleMenusApi()
     treeOptions.value = handleTree(menuRes)
     const role = await PermissionApi.listRoleMenusApi(row.id)
-    console.info(role)
     if (role) {
       // treeRef.value!.setCheckedKeys(role as unknown as Array<number>)
       defaultCheckedKeys.value = role
@@ -142,7 +141,6 @@ const handleScope = async (type: string, row: RoleVO) => {
     const deptRes = await listSimpleDeptApi()
     treeOptions.value = handleTree(deptRes)
     const role = await RoleApi.getRoleApi(row.id)
-    console.info(role)
     dataScopeForm.dataScope = role.dataScope
     if (role.dataScopeDeptIds) {
       // treeRef.value!.setCheckedKeys(role.dataScopeDeptIds as unknown as Array<number>, false)
@@ -155,7 +153,6 @@ const handleScope = async (type: string, row: RoleVO) => {
 // 保存权限
 const submitScope = async () => {
   const keys = treeRef.value!.getCheckedKeys(false) as unknown as Array<number>
-  console.info(keys)
   if ('data' === actionScopeType.value) {
     const data = ref<PermissionAssignRoleDataScopeReqVO>({
       roleId: dataScopeForm.id,

+ 1 - 3
yudao-ui-admin-vue3/src/views/system/tenantPackage/index.vue

@@ -19,7 +19,7 @@ const defaultProps = {
   value: 'id'
 }
 // ========== 创建菜单树结构 ==========
-const menuOptions = ref([]) // 树形结构
+const menuOptions = ref<any[]>([]) // 树形结构
 const treeRef = ref<InstanceType<typeof ElTree>>()
 const treeNodeAll = ref(false)
 // 全选/全不选
@@ -84,11 +84,9 @@ const submitForm = async () => {
     if (actionType.value === 'create') {
       await TenantPackageApi.createTenantPackageTypeApi(data)
       ElMessage.success(t('common.createSuccess'))
-      console.log('new data')
     } else {
       await TenantPackageApi.updateTenantPackageTypeApi(data)
       ElMessage.success(t('common.updateSuccess'))
-      console.log('edit data')
     }
     // 操作成功,重新加载列表
     dialogVisible.value = false