mirror of
https://github.com/thousmile/molly-multi-tenant.git
synced 2025-12-30 04:32:26 +00:00
1.修复 登录用户未关联角色,报错
2.web 报错用户密码,盐加密
This commit is contained in:
@@ -31,7 +31,6 @@
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "2.4.1",
|
||||
"js-base64": "^3.7.5",
|
||||
"js-cookie": "3.0.5",
|
||||
"lodash-es": "4.17.21",
|
||||
"mitt": "3.0.1",
|
||||
"normalize.css": "8.0.1",
|
||||
|
||||
8
tenant-web-ui/pnpm-lock.yaml
generated
8
tenant-web-ui/pnpm-lock.yaml
generated
@@ -29,9 +29,6 @@ dependencies:
|
||||
js-base64:
|
||||
specifier: ^3.7.5
|
||||
version: 3.7.5
|
||||
js-cookie:
|
||||
specifier: 3.0.5
|
||||
version: 3.0.5
|
||||
lodash-es:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
@@ -3362,11 +3359,6 @@ packages:
|
||||
nopt: 6.0.0
|
||||
dev: true
|
||||
|
||||
/js-cookie@3.0.5:
|
||||
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||
engines: {node: '>=14'}
|
||||
dev: false
|
||||
|
||||
/js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
dev: true
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Plus } from "@element-plus/icons-vue"
|
||||
import type { UploadProps } from "element-plus"
|
||||
import { deleteFile } from "@/api/upload"
|
||||
import { getEnvBaseURLPrefix } from "@/utils"
|
||||
import { getToken } from "@/utils/cache/cookies"
|
||||
import { getToken } from "@/utils/cache/local-storage"
|
||||
import { useUserStoreHook } from "@/store/modules/user"
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Plus } from "@element-plus/icons-vue"
|
||||
import type { UploadProps } from "element-plus"
|
||||
import { deleteFile } from "@/api/upload"
|
||||
import { getEnvBaseURLPrefix } from "@/utils"
|
||||
import { getToken } from "@/utils/cache/cookies"
|
||||
import { getToken } from "@/utils/cache/local-storage"
|
||||
import { useUserStoreHook } from "@/store/modules/user"
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -2,15 +2,17 @@ const SYSTEM_NAME = "molly"
|
||||
|
||||
/** 缓存数据时用到的 Key */
|
||||
class CacheKey {
|
||||
static readonly TOKEN = `${SYSTEM_NAME}-token-key`
|
||||
static readonly CONFIG_LAYOUT = `${SYSTEM_NAME}-config-layout-key`
|
||||
static readonly SIDEBAR_STATUS = `${SYSTEM_NAME}-sidebar-status-key`
|
||||
static readonly ACTIVE_THEME_NAME = `${SYSTEM_NAME}-active-theme-name-key`
|
||||
static readonly VISITED_VIEWS = `${SYSTEM_NAME}-visited-views-key`
|
||||
static readonly CACHED_VIEWS = `${SYSTEM_NAME}-cached-views-key`
|
||||
static readonly TENANT_ID = `${SYSTEM_NAME}-tenant-id-key`
|
||||
static readonly CONTROL_SIZE = `${SYSTEM_NAME}-control-size`
|
||||
static readonly USER_AND_PASSWORD = `${SYSTEM_NAME}-user-and-password`
|
||||
static readonly CONTROL_SIZE = `${SYSTEM_NAME}-control-size-key`
|
||||
|
||||
static readonly TOKEN = `${SYSTEM_NAME}-token`
|
||||
static readonly USER_AND_PASSWORD = `${SYSTEM_NAME}-uap`
|
||||
static readonly PROJECT_ID = `${SYSTEM_NAME}-project-id`
|
||||
static readonly LAST_TENANT_ID = `${SYSTEM_NAME}-last-tenant-id`
|
||||
}
|
||||
|
||||
export default CacheKey
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useSettingsStore } from "@/store/modules/settings"
|
||||
import logo from "@/assets/layouts/logo.png?url"
|
||||
@@ -15,7 +16,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { tenant } = useTenantStoreHook()
|
||||
|
||||
const tenant = computed(() => {
|
||||
return useTenantStoreHook().tenant
|
||||
})
|
||||
|
||||
const { layoutMode } = storeToRefs(settingsStore)
|
||||
</script>
|
||||
|
||||
@@ -23,11 +28,11 @@ const { layoutMode } = storeToRefs(settingsStore)
|
||||
<div class="layout-logo-container" :class="{ collapse: props.collapse, 'layout-mode-top': layoutMode === 'top' }">
|
||||
<transition name="layout-logo-fade">
|
||||
<router-link v-if="props.collapse" key="collapse" to="/">
|
||||
<img v-if="tenant" :src="tenant.logo" class="layout-logo" />
|
||||
<img v-if="tenant" :key="tenant.tenantId" :src="tenant.logo" class="layout-logo" />
|
||||
<img v-else :src="logo" class="layout-logo" />
|
||||
</router-link>
|
||||
<router-link v-else key="expand" to="/">
|
||||
<img v-if="tenant" :src="tenant.logo" class="layout-logo-text" />
|
||||
<img v-if="tenant" :key="tenant.tenantId" :src="tenant.logo" class="layout-logo-text" />
|
||||
<img v-else :src="layoutMode !== 'left' ? logoText2 : logoText1" class="layout-logo-text" />
|
||||
</router-link>
|
||||
</transition>
|
||||
@@ -43,9 +48,11 @@ const { layoutMode } = storeToRefs(settingsStore)
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
.layout-logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-logo-text {
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
@@ -64,6 +71,7 @@ const { layoutMode } = storeToRefs(settingsStore)
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.layout-logo-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useNoticeStoreHook } from "@/store/modules/notice"
|
||||
|
||||
import { ElMessage } from "element-plus"
|
||||
import { useTitle } from "@/hooks/useTitle"
|
||||
import { getToken } from "@/utils/cache/cookies"
|
||||
import { getToken } from "@/utils/cache/local-storage"
|
||||
import { fixBlankPage } from "@/utils/fix-blank-page"
|
||||
import { setRouteChange } from "@/hooks/useRouteListener"
|
||||
import { isWhiteList, loginUrl } from "@/config/white-list"
|
||||
@@ -89,10 +89,6 @@ async function initBasicData() {
|
||||
// 获取用户信息
|
||||
if (userStore.userInfo === undefined || !userStore.userInfo) {
|
||||
await userStore.getUserInfo()
|
||||
}
|
||||
|
||||
// 获取当前登录用户,所在的租户信息
|
||||
if (tenantStore.tenant === undefined || !tenantStore.tenant) {
|
||||
await tenantStore.getLoginUserTenant()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { computed, ref } from "vue"
|
||||
import { Client } from "@stomp/stompjs"
|
||||
// @ts-ignore
|
||||
import SockJS from "sockjs-client/dist/sockjs.min.js"
|
||||
import { getToken } from "@/utils/cache/cookies"
|
||||
import { getToken } from "@/utils/cache/local-storage"
|
||||
import { useUserStoreHook } from "./user"
|
||||
import { getEnvBaseURL } from "@/utils"
|
||||
import { IPushMessage } from "@/types/base"
|
||||
|
||||
@@ -3,10 +3,11 @@ import { defineStore } from "pinia"
|
||||
import { ref } from "vue"
|
||||
import { ISimpleProject } from "@/types/base"
|
||||
import { defaultProject } from "@/utils"
|
||||
import { setCurrentProject as setProject, getCurrentProject as getProject } from "@/utils/cache/local-storage"
|
||||
|
||||
export const useProjectStore = defineStore("project", () => {
|
||||
// 当前项目
|
||||
const currentProject = ref<ISimpleProject>(defaultProject)
|
||||
const currentProject = ref<ISimpleProject>(getProject())
|
||||
|
||||
// 获取当前项目
|
||||
const getCurrentProjectId = (): number => {
|
||||
@@ -24,13 +25,14 @@ export const useProjectStore = defineStore("project", () => {
|
||||
}
|
||||
|
||||
// 设置当前项目信息
|
||||
const setCurrentProject = (tenant: ISimpleProject): void => {
|
||||
currentProject.value = tenant
|
||||
const setCurrentProject = (project: ISimpleProject): void => {
|
||||
currentProject.value = project
|
||||
setProject(project)
|
||||
}
|
||||
|
||||
// 重置当前操作的项目
|
||||
const resetCurrentProject = (): void => {
|
||||
currentProject.value = defaultProject
|
||||
setCurrentProject(defaultProject)
|
||||
}
|
||||
|
||||
// 获取当前项目
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ElMessage } from "element-plus"
|
||||
import { useNoticeStoreHook } from "./notice"
|
||||
import { useSettingsStore } from "./settings"
|
||||
import { useTagsViewStore } from "./tags-view"
|
||||
import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
|
||||
import { getToken, removeToken, setToken } from "@/utils/cache/local-storage"
|
||||
import { loginApi, getUserInfoApi, getUserPermsApi, logoutApi } from "@/api/login"
|
||||
import { type IPermsButton, ILoginData, IPermsMenus, ILoginUserInfo } from "@/types/pms"
|
||||
import { resetRouter } from "@/router"
|
||||
|
||||
14
tenant-web-ui/src/utils/cache/cookies.ts
vendored
14
tenant-web-ui/src/utils/cache/cookies.ts
vendored
@@ -1,14 +0,0 @@
|
||||
/** 统一处理 Cookie */
|
||||
|
||||
import CacheKey from "@/constants/cache-key"
|
||||
import Cookies from "js-cookie"
|
||||
|
||||
export const getToken = () => {
|
||||
return Cookies.get(CacheKey.TOKEN)
|
||||
}
|
||||
export const setToken = (token: string) => {
|
||||
Cookies.set(CacheKey.TOKEN, token)
|
||||
}
|
||||
export const removeToken = () => {
|
||||
Cookies.remove(CacheKey.TOKEN)
|
||||
}
|
||||
89
tenant-web-ui/src/utils/cache/local-storage.ts
vendored
89
tenant-web-ui/src/utils/cache/local-storage.ts
vendored
@@ -7,6 +7,23 @@ import { type TagView } from "@/store/modules/tags-view"
|
||||
import { type LayoutSettings } from "@/config/layouts"
|
||||
import { ILoginData } from "@/types/pms"
|
||||
import { encode, decode } from "js-base64"
|
||||
import { ISimpleProject } from "@/types/base"
|
||||
import { defaultProject, hashCode } from ".."
|
||||
import { isJSON } from "../validate"
|
||||
|
||||
//#region token
|
||||
export const getToken = () => {
|
||||
return localStorage.getItem(CacheKey.TOKEN)
|
||||
}
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
localStorage.setItem(CacheKey.TOKEN, token)
|
||||
}
|
||||
|
||||
export const removeToken = () => {
|
||||
localStorage.removeItem(CacheKey.TOKEN)
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 系统布局配置
|
||||
export const getConfigLayout = () => {
|
||||
@@ -44,6 +61,7 @@ export const setActiveThemeName = (themeName: ThemeName) => {
|
||||
//#endregion
|
||||
|
||||
//#region 标签栏
|
||||
|
||||
export const getVisitedViews = () => {
|
||||
const json = localStorage.getItem(CacheKey.VISITED_VIEWS)
|
||||
return JSON.parse(json ?? "[]") as TagView[]
|
||||
@@ -81,16 +99,47 @@ export const setControlSize = (size: string) => {
|
||||
|
||||
//#region 获取保存的 用户名和密码
|
||||
export const getUserAndPassword = () => {
|
||||
const jsonStr = localStorage.getItem(CacheKey.USER_AND_PASSWORD)
|
||||
if (jsonStr) {
|
||||
return JSON.parse(decode(jsonStr)) as ILoginData
|
||||
const base64Str = localStorage.getItem(CacheKey.USER_AND_PASSWORD)
|
||||
if (base64Str) {
|
||||
// 第一次 base64 解密
|
||||
const desVal1 = decode(base64Str)
|
||||
if (desVal1) {
|
||||
// 根据 userAgent 生成 hashCode
|
||||
const hc = hashCode(navigator.userAgent)
|
||||
// hashCode 。base64 加密
|
||||
const salt = encode(hc.toString())
|
||||
// 祛除盐
|
||||
const desVal2Str = desVal1.replaceAll(`${salt}=.`, "")
|
||||
const desVal3Json = decode(desVal2Str)
|
||||
if (isJSON(desVal3Json)) {
|
||||
const data = JSON.parse(desVal3Json) as ILoginData
|
||||
let saltPassword = decode(data.password)
|
||||
saltPassword = saltPassword.replaceAll(`${hc}`, "")
|
||||
const password = saltPassword.replaceAll(`${salt}`, "")
|
||||
data.password = password
|
||||
return data
|
||||
}
|
||||
}
|
||||
removeUserAndPassword()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 保存 用户名和密码
|
||||
export function setUserAndPassword(data: ILoginData) {
|
||||
return localStorage.setItem(CacheKey.USER_AND_PASSWORD, encode(JSON.stringify(data)))
|
||||
// 根据 userAgent 生成 hashCode
|
||||
const hc = hashCode(navigator.userAgent)
|
||||
// hashCode 。base64 加密
|
||||
const salt = encode(hc.toString())
|
||||
// 将 用户密码加盐,再加密
|
||||
data.password = encode(`${hc}${data.password}${salt}`)
|
||||
// 将 用户名和密码转json 。base64 加密
|
||||
const encodeVal = encode(JSON.stringify(data))
|
||||
// 再 拼接上盐
|
||||
const newVal = `${salt}=.${encodeVal}`
|
||||
// 再次 base64 加密
|
||||
const newVal2 = encode(newVal)
|
||||
return localStorage.setItem(CacheKey.USER_AND_PASSWORD, newVal2)
|
||||
}
|
||||
|
||||
// 删除 用户名和密码
|
||||
@@ -99,3 +148,35 @@ export function removeUserAndPassword() {
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 项目选择
|
||||
|
||||
// 获取当前操作的项目
|
||||
export const getCurrentProject = () => {
|
||||
let jsonStr = localStorage.getItem(CacheKey.PROJECT_ID)
|
||||
if (!jsonStr || !isJSON(jsonStr)) {
|
||||
setCurrentProject(defaultProject)
|
||||
jsonStr = localStorage.getItem(CacheKey.PROJECT_ID)
|
||||
}
|
||||
return JSON.parse(jsonStr!) as ISimpleProject
|
||||
}
|
||||
|
||||
// 设置当前操作的项目
|
||||
export const setCurrentProject = (project: ISimpleProject) => {
|
||||
const jsonStr = JSON.stringify(project)
|
||||
localStorage.setItem(CacheKey.PROJECT_ID, jsonStr)
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
// 获取 上一次登录的租户Id
|
||||
export const getLastTenantId = () => {
|
||||
return localStorage.getItem(CacheKey.LAST_TENANT_ID)
|
||||
}
|
||||
|
||||
// 设置 上一次登录的租户Id
|
||||
export const setLastTenantId = (tenantId: string) => {
|
||||
localStorage.setItem(CacheKey.LAST_TENANT_ID, tenantId)
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -104,6 +104,24 @@ export const resetConfigLayout = () => {
|
||||
location.reload()
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串,转为 hashCode
|
||||
* @param {string} str
|
||||
* @returns {number}
|
||||
*/
|
||||
export const hashCode = (str: string) => {
|
||||
let hash = 0
|
||||
if (str.length == 0) {
|
||||
return hash
|
||||
}
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i)
|
||||
hash = (hash << 5) - hash + char
|
||||
hash = hash & hash
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {Object}
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios, { AxiosResponse, type AxiosInstance, type AxiosRequestConfig } fro
|
||||
import { useUserStoreHook } from "@/store/modules/user"
|
||||
import { ElMessage, ElMessageBox } from "element-plus"
|
||||
import { get, merge } from "lodash-es"
|
||||
import { getToken } from "./cache/cookies"
|
||||
import { getToken } from "./cache/local-storage"
|
||||
import { useProjectStoreHook } from "@/store/modules/project"
|
||||
import { getEnvBaseURLPrefix } from "."
|
||||
import { ISimpleProject } from "@/types/base"
|
||||
|
||||
@@ -118,3 +118,13 @@ export function isTelphone(value: string) {
|
||||
const reg = /^[1][0-9]{10}$|^0\d{2,3}-?\d{7,8}$/
|
||||
return reg.test(value)
|
||||
}
|
||||
|
||||
/** 字符串是否为 Json */
|
||||
export function isJSON(value: string) {
|
||||
try {
|
||||
JSON.parse(value)
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,16 @@ import { User, Lock, Key, Picture, Loading } from "@element-plus/icons-vue"
|
||||
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { getEnvBaseURL } from "@/utils"
|
||||
import { getUserAndPassword, setUserAndPassword, removeUserAndPassword } from "@/utils/cache/local-storage"
|
||||
import {
|
||||
getUserAndPassword,
|
||||
setUserAndPassword,
|
||||
removeUserAndPassword,
|
||||
setLastTenantId,
|
||||
getLastTenantId
|
||||
} from "@/utils/cache/local-storage"
|
||||
import { type ILoginData } from "@/types/pms"
|
||||
import { merge } from "lodash-es"
|
||||
import { merge, clone } from "lodash-es"
|
||||
import { useProjectStoreHook } from "@/store/modules/project"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -70,13 +77,23 @@ const handleLogin = () => {
|
||||
useUserStore()
|
||||
.userLogin(loginFormData)
|
||||
.then(() => {
|
||||
// 获取 上一次登录的租户Id。如果和本次不同,清空项目缓存
|
||||
if (getLastTenantId() !== loginFormData.tenantId) {
|
||||
// 将项目,重置为 默认项目
|
||||
useProjectStoreHook().resetCurrentProject()
|
||||
}
|
||||
// 登录成功后,是否记住密码 ?
|
||||
loginFormData.rememberMe ? setUserAndPassword(loginFormData) : removeUserAndPassword()
|
||||
if (loginFormData.rememberMe) {
|
||||
const saveUserInfo = clone(loginFormData)
|
||||
setUserAndPassword(saveUserInfo)
|
||||
} else {
|
||||
removeUserAndPassword()
|
||||
}
|
||||
setLastTenantId(loginFormData.tenantId)
|
||||
router.push({ path: "/" })
|
||||
})
|
||||
.catch(() => {
|
||||
createCode()
|
||||
loginFormData.password = ""
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
|
||||
Reference in New Issue
Block a user