携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情
设计思路:
此管理器,主要是为登录页面服务和首页退出系统服务
支持子系统登录和多端登录模式,封装后
平台后期可定制化登录或者多登录切换,实现相应
公有化接口函数
即可
设计实践:
生产环境模式:
开发环境模式:
/*
* @Description: 登录管理器
* @Author: ffzheng
*/
import { BaseStore, Service, Config, IM } from "@basic-library";
import { cache } from "@basic-utils";
import { useIntervalFn } from '@vueuse/core'
import { useHistory } from '@hooks'
/**
* 设置缓存数据
* @param {*} inData
* @returns
*/
const initData = (inData)=>{
// 更新用户中心数据以及状态
cache.setCache("token", inData.token, "local");
cache.setCache("accountId", inData.accountId, "local");
BaseStore.app.updateUserInfo(inData);
BaseStore.app.updateLoginStatus(true);
}
/**
* 获取子应用
* @param {*} NO
*/
const getSubApps = () => {
const { subSystemList } = BaseStore.app.userInfo
return subSystemList || []
}
/**
* 获取子应用--默认取第一个
* @param {*} NO
*/
const findDefSubApp = () => {
const subApp = getSubApps()
if(subApp && subApp.length){
return subApp[0].path
}
return
}
/**
* 设置租户信息
* @param {*} data 租户ID
*/
const setTenant = (data)=>{
cache.setCache("tenantId", data, "local");
}
/**
* 获取租户信息
* @param {*} no
*/
const getTenantList = ()=>{
return Service.useHttp("queryTenantList")
}
/**
* 设置用户
* @param {*} accountName
* @param {*} password
* @param {*} tenantId
*/
const setRememberUser = (accountName, password, tenantId)=>{
cache.setCache('remember',true,'cookie')
cache.setCache('accountName',accountName,'cookie')
cache.setCache('password',Base64.toBase64(password),'cookie')
cache.setCache('tenantId',tenantId,'cookie')
}
/**
* 获取用户
* @param {*} no
*/
const getRememberUser = ()=>{
const remember = cache.getCache('remember','cookie')
const accountName = cache.getCache('accountName','cookie')
const rePassValue = cache.getCache('password','cookie')
const tenantId = cache.getCache('tenantId','cookie')
const password = rePassValue ? Base64.fromBase64(rePassValue) :''
return {
remember,
accountName,
password,
tenantId
}
}
/**
* 移除记住用户
*/
const removeRememberUser = ()=>{
cache.removeCookies('remember')
cache.removeCookies('accountName')
cache.removeCookies('password')
cache.removeCookies('tenantId')
}
/**应用路径拼装--斜杠路径补全
* @param {*} url
* @returns
*/
const getMicroUrl = (url) => {
let path = url
const protocol = window.location.protocol
const host = window.location.host
if(path.indexOf('/') !== 0) path = `/${path}`
if(path.lastIndexOf('/') !== path.length -1) path = `${path}/`
if(path.indexOf('/') == -1) path = `/${path}/`
return `${protocol}//${host}${path}`
}
/**
* 当前应用跳转
* @param {*} route 地址
*/
const redirectApp = (route = '/login') => {
window.location.href = window._IS_RUN_MICRO_MODE ? getMicroUrl('main') : route
}
/**
* 登录应用-跳转子应用
*/
const redirectSubApp = () => {
const subApp = findDefSubApp()
const devApp = '/platform'
window.location.href = window._IS_RUN_MICRO_MODE ? getMicroUrl(subApp) : devApp
}
/**
* 获取验证码
*/
const queryCaptcha = async() => {
return await Service.useHttp('queryCaptcha')
}
/**
* @description: 登出
* @return {*}
*/
const logout = async() => {
return await Service.useHttp('logout')
}
/**
* @description: 登出--跳转
* @return {*}
*/
const redirect = () => {
localStorage.clear()
sessionStorage.clear()
IM.closeConnection()
redirectApp()
}
/**
* 创建心跳连接
* @param {*} params NO
*/
const createHeartEvent = () => {
const { pause, resume } = useIntervalFn(async() => {
// 心跳检测
await run()
}, Config.BSConfig?.HeartTime || 30000, { immediate: true })
// 检测运行
const run = async() => {
const { success,returnObj } = await Service.useHttp("queryCurrentAccount")
if(success){
if(returnObj){
pause()
initData(returnObj)
useHistory("/")
}
resume()
}else{
pause()
}
}
// 运行心跳检测
run()
}
export default {
initData,
setTenant,
getTenantList,
setRememberUser,
getRememberUser,
removeRememberUser,
createHeartEvent,
queryCaptcha,
logout,
redirect,
redirectApp,
findDefSubApp,
getSubApps,
redirectSubApp
};
暴露出这么多,看起来有点烦躁,不过还是要耐下心来,聊聊
登录具备基本的功能不做过多解释
看看代码即可,比如,记住用户管理、初始化用户数据、验证码、设置租户(业务需要)
需要简单解释下的
登录/登出API
疑问就出来了,为什么要封装成redirect或者获取应用配置跳转呢?
主要原因:
- 多个客户端逻辑公用
- 有专门的登录应用,来负责子应用运行
- 子应用有自己的登录,主要是独立部署或者开发环境调试
大体上明白了吧,这个肯定也需要后端进行配置
后端需要提供登录后
1、当前用户所拥有的子系统,拿到当前子系统信息,然后进行切换跳转,目前默认第一个,后期发展可能会进行多应用切换
2、当前用户登录子应用所在会话
logout
redirect
redirectApp
findDefSubApp
getSubApps
redirectSubApp
代码中,可以看到,window._IS_RUN_MICRO_MODE
,这个全局变量,标记当前子应用的运行环境,是开发环境还是线上环境
子应用入口页面,这么搞就可以了
window._IS_RUN_MICRO_MODE = true
if(process.env.NODE_ENV === 'development'){
window._IS_RUN_MICRO_MODE = false
}
心跳检查 createHeartEvent
主要是使用了,定时器,这个定时器的参数可配置,页面进入后,执行一次,然后间隔时间执行。
实现:
用户正常会话内,非法关闭页面的情况,再次访问根目录,直接跳转系统首页。
这个逻辑很重要,各大云厂商都有相应逻辑实现,不能直接跳转到登录让重新登录。为什么呢?
架构设计,不允许客户端进行多租户访问。
有的ToB类系统是运行的,这个保存中立吧。
因为这个问题,上次一个项目都被搞栏过,差点跑路
设计上没沟通清楚,前端实现根路径是登录,认为支持多会话模式,但服务端是单一会话模式,就导致多用户登录,使用的一个会话,这样业务数据写入后,数据就串了。
因为我们业务,用户数据是根据租户走的。
这也许是一个悲伤的故事....
Login.vue
页面初始化
/**
* 加载验证码
*/
const loadVerifyCode = (params) => {
SecurityRefs.value.refreshCode()
userForm.captcha = ''
}
onMounted(async () => {
systemName.value = Config.BSConfig?.systemName;
const {remember, accountName, password, tenantId} = LoginLoader.getRememberUser();
// 记住用户
if(remember){
userForm.accountName = accountName
userForm.password = password
rememberPass.value = true
userForm.tenantId = tenantId
}
// 加载验证码
loadVerifyCode()
});
<el-form-item prop="captcha" label="">
<div class="captcha-text">
<el-input placeholder="请输入验证码"
v-model="userForm.captcha"
onkeyup="value=value.replace(/[^\w\.\/]/ig,'')"
@keyup.enter="submitForm()" maxlength=20>
</el-input>
</div>
<div class="captcha-code">
<Security ref="SecurityRefs"></Security>
</div>
</el-form-item>
- 获取记住用户信息,登录表单赋值
- 验证码加载-验证码实现,可移步Vue3 实现图形验证码功能
登录
/**
* @desc 表单校验
* @param {Object} NO
*/
const submitForm = async () => {
elmRefs.value.validate((loginValid) => {
if (!loginValid) {
EduMessage({
type: "error",
message: "请输入账号/密码/学校",
});
return;
}
jumpLogin();
});
};
/**
* @desc 登录
*
*/
const jumpLogin = async () => {
setConfig()
const { accountName, password, captcha } = userForm
const result = await Service.useHttp({
name: "loginService",
headers: { captcha }
}, {
accountName,
password,
});
loading.value = false
if (result?.success) {
if (!loginAfterService(result)) {
// 刷新验证码
loadVerifyCode()
return
}
LoginLoader.redirectSubApp()
} else {
// 刷新验证码
loadVerifyCode()
}
};
/**
* 登录时,配置设置
*/
const setConfig=()=>{
LoginLoader.setTenant(userForm.tenantId);
loading.value = true
}
基本逻辑还是围绕,登录事前和时后去做什么
登录时
-配置设置
登录后
-loginAfterService,权限认证、用户数据加载、记住用户
应用中,使用到了登录管理器中的
1、LoginLoader.getRememberUser --获取记住用户信息
2、LoginLoader.setTenant --设置租户ID
3、LoginLoader.initData --加载用户相关数据
4、LoginLoader.setRememberUser --设置记住用户信息
5、LoginLoader.removeRememberUser --移除记住用户信息
6、LoginLoader.logout --登出系统请求
7、LoginLoader.redirect --登出请求跳转
8、LoginLoader.createHeartEvent --页面心跳监听
看起来有点复杂,但是都是常用的逻辑
针对Loader管理器的一系列,请看下方: