ソースを参照

[Feat 0000] 支持自动登录功能

houzekong 3 ヶ月 前
コミット
8ee9ce4726

+ 8 - 1
src/api/sys/model/userModel.ts

@@ -11,6 +11,13 @@ export interface ThirdLoginParams {
   thirdType: string;
 }
 
+export interface AutoLoginParams {
+  /** 账号 */
+  account: string;
+  deptId: string;
+  username: string;
+}
+
 export interface RoleInfo {
   roleName: string;
   value: string;
@@ -23,7 +30,7 @@ export interface LoginResultModel {
   userId: string | number;
   token: string;
   role: RoleInfo;
-  userInfo?: any
+  userInfo?: any;
 }
 
 /**

+ 25 - 2
src/api/sys/user.ts

@@ -1,7 +1,7 @@
 import { defHttp } from '/@/utils/http/axios';
-import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel';
+import { LoginParams, LoginResultModel, GetUserInfoModel, AutoLoginParams } from './model/userModel';
 
-import { ErrorMessageMode } from '/#/axios';
+import { ErrorMessageMode, SuccessMessageMode } from '/#/axios';
 import { useMessage } from '/@/hooks/web/useMessage';
 import { useUserStoreWithOut } from '/@/store/modules/user';
 import { setAuthCache } from '/@/utils/auth';
@@ -43,6 +43,8 @@ enum Api {
   getLoginQrcode = '/sys/getLoginQrcode',
   //监控二维码扫描状态
   getQrcodeToken = '/sys/getQrcodeToken',
+  //陕西省局密闭第三方自动登录
+  autoLoginForSXMB = '/jeecg-system/sys/autoLoginForSXMB',
 }
 
 /**
@@ -177,6 +179,23 @@ export function thirdLogin(params, mode: ErrorMessageMode = 'modal') {
     }
   );
 }
+
+export function autoLoginApi(params: AutoLoginParams, mode: ErrorMessageMode = 'modal', successMode: SuccessMessageMode = 'success') {
+  return defHttp.get<LoginResultModel>(
+    {
+      url: Api.autoLoginForSXMB,
+      params,
+      headers: {
+        token: 'MTIzNDU2',
+      },
+    },
+    {
+      successMessageMode: successMode,
+      errorMessageMode: mode,
+    }
+  );
+}
+
 /**
  * @description: 获取第三方短信验证码
  */
@@ -217,3 +236,7 @@ export async function validateCasLogin(params) {
   const url = Api.validateCasLogin;
   return defHttp.get({ url: url, params });
 }
+
+export function autoLoginForSXMB(params) {
+  return defHttp.get({ url: Api.autoLoginForSXMB, params });
+}

+ 81 - 0
src/hooks/system/useAutoLogin.ts

@@ -0,0 +1,81 @@
+// import QueryString from 'qs';
+import { useUserStore } from '/@/store/modules/user';
+import { RouteLocationNormalized } from 'vue-router';
+import { useMessage } from '../web/useMessage';
+import { AUTO_LOGIN_URL_QUERY } from '/@/router/constant';
+import { AxiosError } from 'axios';
+
+/** 自动登录功能的Hook,该Hook是为了部署在同一局域网内的多套系统之间能够无缝切换 */
+export function useAutoLogin() {
+  // const userStore = useUserStore();
+  /** 获取自动登录的url */
+  // function getUrl(baseUrl: string, extraQuery: Record<string, string> = {}) {
+  //   const qs = QueryString.stringify({
+  //     [AUTO_LOGIN_URL_QUERY.key]: AUTO_LOGIN_URL_QUERY.val,
+  //     realname: userStore.getUserInfo.realname,
+  //     workNo: userStore.getUserInfo.workNo,
+  //     ...extraQuery,
+  //   });
+  //   return `${baseUrl}?${qs}`;
+  // }
+
+  /** 启用自动登录功能来跳转新的页面 */
+  // function open(url: string, target?: string) {
+  //   window.open(getUrl(url), target);
+  // }
+
+  /** 判断当前路由是否需要自动登录,本方法是同步方法,可以用于事先判断 */
+  function validateRoute(route: RouteLocationNormalized) {
+    if (route.query && route.query[AUTO_LOGIN_URL_QUERY.key] === AUTO_LOGIN_URL_QUERY.val) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * 用在路由守卫里,执行自动登录的逻辑,如果存在符合自动登录标准的query则执行自动登录,返回是否自动登录
+   *
+   * 该方法需要修改query
+   * */
+  async function doAutoLogin(route: RouteLocationNormalized): Promise<void> {
+    if (!validateRoute(route)) return;
+
+    const { account, deptId, username } = route.query;
+    if (!account || !deptId || !username) return;
+
+    const params = {
+      account: account as string,
+      deptId: deptId as string,
+      username: username as string,
+      checkKey: new Date().getTime(),
+    };
+    const userStore = useUserStore();
+    try {
+      await userStore.autoLogin({
+        ...params,
+        goHome: false,
+      });
+      delete route.query[AUTO_LOGIN_URL_QUERY.key];
+      delete route.query['account'];
+      delete route.query['deptId'];
+      delete route.query['username'];
+      return;
+    } catch (e) {
+      const message = useMessage().createMessage;
+      message.error((e as AxiosError).message);
+      throw e;
+    }
+  }
+
+  return {
+    /** 启用单点登录功能来跳转新的页面,参数与window.open一致 */
+    // open,
+    /** 用在跳转到的页面上,执行单点登录的逻辑 */
+    doAutoLogin,
+    /** 获取自动登录的url */
+    // getUrl,
+    /** 判断当前路由是否需要自动登录,本方法是同步方法,可以用于事先判断 */
+    validateRoute,
+  };
+}

+ 2 - 0
src/router/constant.ts

@@ -30,3 +30,5 @@ export const getParentLayout = (_name?: string) => {
       });
     });
 };
+
+export const AUTO_LOGIN_URL_QUERY = { key: 'auto-login', val: '1' };

+ 33 - 18
src/router/guard/permissionGuard.ts

@@ -9,10 +9,11 @@ import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
 
 import { RootRoute } from '/@/router/routes';
 
-import {isOAuth2AppEnv, isOAuth2DingAppEnv} from '/@/views/sys/login/useLogin';
-import { OAUTH2_THIRD_LOGIN_TENANT_ID } from "/@/enums/cacheEnum";
-import { setAuthCache } from "/@/utils/auth";
+import { isOAuth2AppEnv, isOAuth2DingAppEnv } from '/@/views/sys/login/useLogin';
+import { OAUTH2_THIRD_LOGIN_TENANT_ID } from '/@/enums/cacheEnum';
+import { setAuthCache } from '/@/utils/auth';
 import { PAGE_NOT_FOUND_NAME_404 } from '/@/router/constant';
+import { useAutoLogin } from '/@/hooks/system/useAutoLogin';
 
 const LOGIN_PATH = PageEnum.BASE_LOGIN;
 //auth2登录路由
@@ -27,11 +28,12 @@ const TOKEN_LOGIN = PageEnum.TOKEN_LOGIN;
 const ROOT_PATH = RootRoute.path;
 
 // 代码逻辑说明: [VUEN-2472]分享免登录------------
-const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH,SYS_FILES_PATH, TOKEN_LOGIN ];
+const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH, SYS_FILES_PATH, TOKEN_LOGIN];
 
 export function createPermissionGuard(router: Router) {
   const userStore = useUserStoreWithOut();
   const permissionStore = usePermissionStoreWithOut();
+  const { validateRoute, doAutoLogin } = useAutoLogin();
 
   // 自定义首页跳转次数
   let homePathJumpCount = 0;
@@ -50,16 +52,24 @@ export function createPermissionGuard(router: Router) {
       return;
     }
 
+    // 如果符合自动登录的相关条件则直接执行自动登录,覆盖原有的登录信息
+    if (validateRoute(to)) {
+      await doAutoLogin(to);
+      // 自动登录后会动态添加路由,此处应当重定向到fullPath,否则会加载404页面内容
+      next({ path: to.fullPath, replace: true, query: to.query });
+      return;
+    }
+
     const token = userStore.getToken;
 
     // Whitelist can be directly entered
     if (whitePathList.includes(to.path as PageEnum)) {
       if (to.path === LOGIN_PATH && token) {
         const isSessionTimeout = userStore.getSessionTimeout;
-        
+
         //TODO vben默认写法,暂时不知目的,有问题暂时先注释掉
         //await userStore.afterLoginAction();
-        
+
         try {
           if (!isSessionTimeout) {
             next((to.query?.redirect as string) || '/');
@@ -71,8 +81,8 @@ export function createPermissionGuard(router: Router) {
         //退出登录进入此逻辑
         //如果进入的页面是login页面并且当前是OAuth2app环境,并且token为空,就进入OAuth2登录页面
         // 代码逻辑说明: [QQYUN-3440]新建企业微信和钉钉配置表,通过租户模式隔离------------
-        if(to.query.tenantId){
-          setAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID,to.query.tenantId)
+        if (to.query.tenantId) {
+          setAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID, to.query.tenantId);
         }
         next({ path: OAUTH2_LOGIN_PAGE_PATH });
         return;
@@ -104,11 +114,11 @@ export function createPermissionGuard(router: Router) {
         //只有首次登陆并且是企业微信或者钉钉的情况下才会调用
         const href = window.location.href;
         //判断当前是auth2页面,并且是钉钉/企业微信,并且包含tenantId参数
-        if(isOAuth2AppEnv() && href.indexOf("/tenantId/")!= -1){
+        if (isOAuth2AppEnv() && href.indexOf('/tenantId/') != -1) {
           const params = to.params;
-          if(params && params.path && params.path.length>0){
+          if (params && params.path && params.path.length > 0) {
             //直接获取参数最后一位
-            setAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID,params.path[params.path.length-1])
+            setAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID, params.path[params.path.length - 1]);
           }
         }
         //---------【首次登陆并且是企业微信或者钉钉的情况下才会调用】------------------------------------------------
@@ -124,19 +134,24 @@ export function createPermissionGuard(router: Router) {
 
       // 代码逻辑说明: 【QQYUN-4713】登录代码调整逻辑有问题,改造待观察--
       if (to.fullPath) {
-        console.log("to.fullPath 1",to.fullPath)
-        console.log("to.path 2",to.path)
-        
+        console.log('to.fullPath 1', to.fullPath);
+        console.log('to.path 2', to.path);
+
         const getFullPath = to.fullPath;
-        if(getFullPath=='/' || getFullPath=='/500' || getFullPath=='/400' || getFullPath=='/login?redirect=/' || getFullPath=='/login?redirect=/login?redirect=/'){
+        if (
+          getFullPath == '/' ||
+          getFullPath == '/500' ||
+          getFullPath == '/400' ||
+          getFullPath == '/login?redirect=/' ||
+          getFullPath == '/login?redirect=/login?redirect=/'
+        ) {
           return;
         }
-        
+
         redirectData.query = {
           ...redirectData.query,
           // 代码逻辑说明: 修复登录成功后,没有正确重定向的问题
           redirect: to.fullPath,
-
         };
       }
       next(redirectData);
@@ -145,7 +160,7 @@ export function createPermissionGuard(router: Router) {
 
     //==============================【首次登录并且是企业微信或者钉钉的情况下才会调用】==================
     //判断是免登录页面,如果页面包含/tenantId/,那么就直接前往主页
-    if(isOAuth2AppEnv() && to.path.indexOf("/tenantId/") != -1){
+    if (isOAuth2AppEnv() && to.path.indexOf('/tenantId/') != -1) {
       // 代码逻辑说明: 【TV360X-2958】钉钉登录后打开了敲敲云,换其他账号登录后,再打开敲敲云显示的是原来账号的应用---
       if (isOAuth2DingAppEnv()) {
         next(OAUTH2_LOGIN_PAGE_PATH);

+ 7 - 1
src/store/modules/mine.ts

@@ -35,6 +35,7 @@ const DEFAULT_CONFIG = {
 
 export const useMineDepartmentStore = defineStore('mine-department-store', () => {
   // ==================== State 定义 ====================
+  const root = ref<EnfMineTreeItem>();
 
   /** 当前选中的部门,可能是执法处、区或者具体到矿井 */
   const depart = ref<MineDepartment>();
@@ -46,6 +47,7 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
   // const rawTree = ref<MineDepartment[]>([]);
 
   // ==================== Getter 计算属性 ====================
+  const getRoot = computed(() => root.value);
 
   /** 获取当前选中的部门 */
   const getDepart = computed(() => depart.value);
@@ -106,7 +108,10 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
 
       const r1 = r2.reduce((arr: MineDepartment[], node) => {
         const isRoot = node.orgType === TOP_LEVEL_ORG;
-        // 根节点不处理,放在这里return是因为有时请求返回的是单矿,也需要走上面的数据处理流程
+        // 根节点不处理,放在这里return是因为有时请求返回的是单矿,也需要走数据处理流程
+        if (isRoot) {
+          root.value = node;
+        }
         if (isRoot && node.orgType !== LEAF_LEVEL_ORG) return arr;
 
         const ele: MineDepartment = {
@@ -249,6 +254,7 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
     departTree,
 
     // --- Getter(只读计算属性)---
+    getRoot,
     getDepart,
     getDepartId,
     getDepartTree,

+ 27 - 6
src/store/modules/user.ts

@@ -1,19 +1,19 @@
 import type { UserInfo, LoginInfo } from '/#/store';
-import type { ErrorMessageMode } from '/#/axios';
+import type { ErrorMessageMode, SuccessMessageMode } from '/#/axios';
 import { defineStore } from 'pinia';
 import { store } from '/@/store';
 import { RoleEnum } from '/@/enums/roleEnum';
 import { PageEnum } from '/@/enums/pageEnum';
 import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY, LOGIN_INFO_KEY, DB_DICT_DATA_KEY, TENANT_ID, OAUTH2_THIRD_LOGIN_TENANT_ID } from '/@/enums/cacheEnum';
 import { getAuthCache, setAuthCache, removeAuthCache } from '/@/utils/auth';
-import { GetUserInfoModel, LoginParams, ThirdLoginParams } from '/@/api/sys/model/userModel';
-import { doLogout, getUserInfo, loginApi, phoneLoginApi, thirdLogin } from '/@/api/sys/user';
+import { AutoLoginParams, GetUserInfoModel, LoginParams, ThirdLoginParams } from '/@/api/sys/model/userModel';
+import { autoLoginApi, doLogout, getUserInfo, loginApi, phoneLoginApi, thirdLogin } from '/@/api/sys/user';
 import { useI18n } from '/@/hooks/web/useI18n';
 import { useMessage } from '/@/hooks/web/useMessage';
 import { router } from '/@/router';
-import { usePermissionStore } from '/@/store/modules/permission';
-import { RouteRecordRaw } from 'vue-router';
-import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
+// import { usePermissionStore } from '/@/store/modules/permission';
+// import { RouteRecordRaw } from 'vue-router';
+// import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
 import { isArray } from '/@/utils/is';
 import { useGlobSetting } from '/@/hooks/setting';
 import { JDragConfigEnum } from '/@/enums/jeecgEnum';
@@ -350,6 +350,27 @@ export const useUserStore = defineStore({
         return Promise.reject(error);
       }
     },
+    /** 用户自动登录,即不需要用户密码即可登录 */
+    async autoLogin(
+      params: AutoLoginParams & {
+        goHome?: boolean;
+        mode?: ErrorMessageMode;
+        successMode?: SuccessMessageMode;
+      }
+    ) {
+      const { goHome = true, mode, successMode = 'none', ...loginParams } = params;
+      // 进行加密
+      // const encryption = new AesEncryption({ key: loginCipher.key, iv: loginCipher.iv });
+      // loginParams.password = encryption.encryptByAES(loginParams.password);
+      // encryption.encryptByAES(loginParams.password);
+      const data = await autoLoginApi(loginParams, mode, successMode);
+      const { token } = data;
+      // save token
+      this.setToken(token);
+      // this.setTenant(userInfo.loginTenantId);
+      await this.afterLoginAction(goHome, data);
+      return;
+    },
     /**
      * 退出询问
      */

+ 2 - 2
src/views/system/user/user.data.ts

@@ -26,8 +26,8 @@ export const columns: BasicColumn[] = [
     dataIndex: 'userRoles',
     width: 100,
     customRender: ({ value }) => {
-      const nameList = [];
-      value.forEach((item) => {
+      const nameList: string[] = [];
+      (value || []).forEach((item: any) => {
         nameList.push(item['roleName']);
       });
       return nameList.toString();