fix(auth): move admin session to HttpOnly cookies

This commit is contained in:
Kroytz committed 2026-07-19 00:25:39 +08:00
1 parent 0d2165cf80
commit 17b2ba49a8
39 files changed
+191 -219

No files matched your search

@@ -60,6 +60,12 @@
<artifactId>bizlog-sdk</artifactId> <artifactId>bizlog-sdk</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 业务组件 --> <!-- 业务组件 -->
<dependency> <dependency>
<groupId>co.yixiang.boot</groupId> <groupId>co.yixiang.boot</groupId>
@@ -48,4 +48,10 @@ public class SecurityProperties {
* PasswordEncoder 加密复杂度,越高开销越大 * PasswordEncoder 加密复杂度,越高开销越大
*/ */
private Integer passwordEncoderLength = 4; private Integer passwordEncoderLength = 4;
/**
* 管理端认证 Cookie 是否仅通过 HTTPS 发送
*/
@NotNull(message = "Cookie Secure 开关不能为空")
private Boolean cookieSecure = true;
} }
@@ -11,6 +11,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.util.WebUtils;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections; import java.util.Collections;
@@ -22,6 +23,9 @@ import java.util.Collections;
*/ */
public class SecurityFrameworkUtils { public class SecurityFrameworkUtils {
public static final String ACCESS_TOKEN_COOKIE = "yshop_admin_access_token";
public static final String REFRESH_TOKEN_COOKIE = "yshop_admin_refresh_token";
/** /**
* HEADER 认证头 value 的前缀 * HEADER 认证头 value 的前缀
*/ */
@@ -39,11 +43,14 @@ public class SecurityFrameworkUtils {
*/ */
public static String obtainAuthorization(HttpServletRequest request, public static String obtainAuthorization(HttpServletRequest request,
String headerName, String parameterName) { String headerName, String parameterName) {
// 1. 获得 Token。优先级:Header > Parameter // 1. 获得 Token。优先级:Header > Parameter > HttpOnly Cookie
String token = request.getHeader(headerName); String token = request.getHeader(headerName);
if (StrUtil.isEmpty(token)) { if (StrUtil.isEmpty(token)) {
token = request.getParameter(parameterName); token = request.getParameter(parameterName);
} }
if (StrUtil.isEmpty(token) && WebUtils.getCookie(request, ACCESS_TOKEN_COOKIE) != null) {
token = WebUtils.getCookie(request, ACCESS_TOKEN_COOKIE).getValue();
}
if (!StringUtils.hasText(token)) { if (!StringUtils.hasText(token)) {
return null; return null;
} }
@@ -0,0 +1,21 @@
package co.yixiang.yshop.framework.security.core.util;
import jakarta.servlet.http.Cookie;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.junit.jupiter.api.Assertions.assertEquals;
class SecurityFrameworkUtilsTest {
@Test
void obtainAuthorizationUsesCookieWhenHeaderIsMissing() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(new Cookie(SecurityFrameworkUtils.ACCESS_TOKEN_COOKIE, "cookie-token"));
assertEquals("cookie-token", SecurityFrameworkUtils.obtainAuthorization(request, "Authorization", "token"));
request.addHeader("Authorization", "Bearer header-token");
assertEquals("header-token", SecurityFrameworkUtils.obtainAuthorization(request, "Authorization", "token"));
}
}
@@ -24,19 +24,28 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.WebUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success; import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
import static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet; import static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.ACCESS_TOKEN_COOKIE;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.REFRESH_TOKEN_COOKIE;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 认证") @Tag(name = "管理后台 - 认证")
@@ -69,15 +78,25 @@ public class AuthController {
return success(authService.login(reqVO)); return success(authService.login(reqVO));
} }
@PostMapping("/login-cookie")
@PermitAll
@Operation(summary = "使用账号密码登录(HttpOnly Cookie)")
public CommonResult<Boolean> loginCookie(@RequestBody @Valid AuthLoginReqVO reqVO,
HttpServletResponse response) {
writeTokenCookies(response, authService.login(reqVO));
return success(true);
}
@PostMapping("/logout") @PostMapping("/logout")
@PermitAll @PermitAll
@Operation(summary = "登出系统") @Operation(summary = "登出系统")
public CommonResult<Boolean> logout(HttpServletRequest request) { public CommonResult<Boolean> logout(HttpServletRequest request, HttpServletResponse response) {
String token = SecurityFrameworkUtils.obtainAuthorization(request, String token = SecurityFrameworkUtils.obtainAuthorization(request,
securityProperties.getTokenHeader(), securityProperties.getTokenParameter()); securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
if (StrUtil.isNotBlank(token)) { if (StrUtil.isNotBlank(token)) {
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType()); authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
} }
clearTokenCookies(response);
return success(true); return success(true);
} }
@@ -89,6 +108,18 @@ public class AuthController {
return success(authService.refreshToken(refreshToken)); return success(authService.refreshToken(refreshToken));
} }
@PostMapping("/refresh-token-cookie")
@PermitAll
@Operation(summary = "刷新令牌(HttpOnly Cookie)")
public CommonResult<Boolean> refreshTokenCookie(HttpServletRequest request, HttpServletResponse response) {
if (WebUtils.getCookie(request, REFRESH_TOKEN_COOKIE) == null) {
throw exception(UNAUTHORIZED);
}
writeTokenCookies(response,
authService.refreshToken(WebUtils.getCookie(request, REFRESH_TOKEN_COOKIE).getValue()));
return success(true);
}
@GetMapping("/get-permission-info") @GetMapping("/get-permission-info")
@Operation(summary = "获取登录用户的权限信息") @Operation(summary = "获取登录用户的权限信息")
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() { public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
@@ -124,6 +155,15 @@ public class AuthController {
return success(authService.smsLogin(reqVO)); return success(authService.smsLogin(reqVO));
} }
@PostMapping("/sms-login-cookie")
@PermitAll
@Operation(summary = "使用短信验证码登录(HttpOnly Cookie)")
public CommonResult<Boolean> smsLoginCookie(@RequestBody @Valid AuthSmsLoginReqVO reqVO,
HttpServletResponse response) {
writeTokenCookies(response, authService.smsLogin(reqVO));
return success(true);
}
@PostMapping("/send-sms-code") @PostMapping("/send-sms-code")
@PermitAll @PermitAll
@Operation(summary = "发送手机验证码") @Operation(summary = "发送手机验证码")
@@ -154,4 +194,28 @@ public class AuthController {
return success(authService.socialLogin(reqVO)); return success(authService.socialLogin(reqVO));
} }
@PostMapping("/social-login-cookie")
@PermitAll
@Operation(summary = "社交快捷登录(HttpOnly Cookie)")
public CommonResult<Boolean> socialQuickLoginCookie(@RequestBody @Valid AuthSocialLoginReqVO reqVO,
HttpServletResponse response) {
writeTokenCookies(response, authService.socialLogin(reqVO));
return success(true);
}
private void writeTokenCookies(HttpServletResponse response, AuthLoginRespVO token) {
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(ACCESS_TOKEN_COOKIE, token.getAccessToken()).toString());
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(REFRESH_TOKEN_COOKIE, token.getRefreshToken()).toString());
}
private void clearTokenCookies(HttpServletResponse response) {
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(ACCESS_TOKEN_COOKIE, "").maxAge(Duration.ZERO).toString());
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(REFRESH_TOKEN_COOKIE, "").maxAge(Duration.ZERO).toString());
}
private ResponseCookie.ResponseCookieBuilder tokenCookie(String name, String value) {
return ResponseCookie.from(name, value).httpOnly(true).secure(securityProperties.getCookieSecure())
.sameSite("Strict").path("/");
}
} }
@@ -192,6 +192,7 @@ yshop:
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试; enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
security: security:
mock-enable: true mock-enable: true
cookie-secure: false
xss: xss:
enable: false enable: false
exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
+4 -5
View File
@@ -1,5 +1,4 @@
import request from '@/config/axios' import request from '@/config/axios'
import { getRefreshToken } from '@/utils/auth'
import type { UserLoginVO } from './types' import type { UserLoginVO } from './types'
export interface SmsCodeVO { export interface SmsCodeVO {
@@ -14,12 +13,12 @@ export interface SmsLoginVO {
// 登录 // 登录
export const login = (data: UserLoginVO) => { export const login = (data: UserLoginVO) => {
return request.post({ url: '/system/auth/login', data }) return request.post({ url: '/system/auth/login-cookie', data })
} }
// 刷新访问令牌 // 刷新访问令牌
export const refreshToken = () => { export const refreshToken = () => {
return request.post({ url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken() }) return request.post({ url: '/system/auth/refresh-token-cookie' })
} }
// 使用租户名,获得租户编号 // 使用租户名,获得租户编号
@@ -49,13 +48,13 @@ export const sendSmsCode = (data: SmsCodeVO) => {
// 短信验证码登录 // 短信验证码登录
export const smsLogin = (data: SmsLoginVO) => { export const smsLogin = (data: SmsLoginVO) => {
return request.post({ url: '/system/auth/sms-login', data }) return request.post({ url: '/system/auth/sms-login-cookie', data })
} }
// 社交快捷登录,使用 code 授权码 // 社交快捷登录,使用 code 授权码
export function socialLogin(type: string, code: string, state: string) { export function socialLogin(type: string, code: string, state: string) {
return request.post({ return request.post({
url: '/system/auth/social-login', url: '/system/auth/social-login-cookie',
data: { data: {
type, type,
code, code,
-10
View File
@@ -7,16 +7,6 @@ export type UserLoginVO = {
socialState?: string socialState?: string
} }
export type TokenType = {
id: number // 编号
accessToken: string // 访问令牌
refreshToken: string // 刷新令牌
userId: number // 用户编号
userType: number //用户类型
clientId: string //客户端编号
expiresTime: number //过期时间
}
export type UserVO = { export type UserVO = {
id: number id: number
username: string username: string
@@ -6,7 +6,7 @@ import { propTypes } from '@/utils/propTypes'
import { isNumber } from '@/utils/is' import { isNumber } from '@/utils/is'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale' import { useLocaleStore } from '@/store/modules/locale'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getTenantId } from '@/utils/auth'
defineOptions({ name: 'Editor' }) defineOptions({ name: 'Editor' })
@@ -104,10 +104,8 @@ const editorConfig = computed((): IEditorConfig => {
// 自定义增加 http header // 自定义增加 http header
headers: { headers: {
Accept: '*', Accept: '*',
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId() 'tenant-id': getTenantId()
}, },
// 跨域是否传递 cookie ,默认为 false // 跨域是否传递 cookie ,默认为 false
withCredentials: true, withCredentials: true,
@@ -99,7 +99,7 @@
<el-col :span="12" style="text-align: right"> <el-col :span="12" style="text-align: right">
<el-upload <el-upload
:action="uploadApi" :action="uploadApi"
:headers="headers" :with-credentials="true"
:file-list="[]" :file-list="[]"
:on-progress="handleProgress" :on-progress="handleProgress"
:before-upload="beforeUpload" :before-upload="beforeUpload"
@@ -182,7 +182,6 @@ import {
putObj as materialgroupEdit putObj as materialgroupEdit
} from '@/api/tools/materialgroup' } from '@/api/tools/materialgroup'
import { getPage, addObj, delObj } from '@/api/tools/material' import { getPage, addObj, delObj } from '@/api/tools/material'
import { getAccessToken } from '@/utils/auth'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@@ -218,10 +217,6 @@ const props = defineProps({
} }
}) })
const headers = ref({
// Authorization: getToken()
Authorization: 'Bearer ' + getAccessToken(),
})
//const value = ref(props.value) //const value = ref(props.value)
const dialogVisible = ref(false) const dialogVisible = ref(false)
const url = ref('') const url = ref('')
@@ -50,7 +50,7 @@
<el-col :span="12" style="text-align: right"> <el-col :span="12" style="text-align: right">
<el-upload <el-upload
:action="uploadApi" :action="uploadApi"
:headers="headers" :with-credentials="true"
:file-list="[]" :file-list="[]"
:on-progress="handleProgress" :on-progress="handleProgress"
:before-upload="beforeUpload" :before-upload="beforeUpload"
@@ -133,7 +133,6 @@ import {
putObj as materialgroupEdit putObj as materialgroupEdit
} from '@/api/tools/materialgroup' } from '@/api/tools/materialgroup'
import { getPage, addObj, delObj } from '@/api/tools/material' import { getPage, addObj, delObj } from '@/api/tools/material'
import { getAccessToken } from '@/utils/auth'
import '../../../../public/UEditor/dialogs/internal' import '../../../../public/UEditor/dialogs/internal'
const props = defineProps({ const props = defineProps({
@@ -170,10 +169,6 @@ const props = defineProps({
} }
}) })
const headers = ref({
// Authorization: getToken()
Authorization: 'Bearer ' + getAccessToken(),
})
const value = ref(props.value) const value = ref(props.value)
// const dialogVisible = ref(false) // const dialogVisible = ref(false)
// const url = ref('') // const url = ref('')
+11 -29
View File
@@ -9,11 +9,10 @@ import axios, {
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import qs from 'qs' import qs from 'qs'
import { config } from '@/config/axios/config' import { config } from '@/config/axios/config'
import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth' import { getTenantId, removeAuthenticated } from '@/utils/auth'
import errorCode from './errorCode' import errorCode from './errorCode'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import { deleteUserCache } from '@/hooks/web/useCache'
const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
const { result_code, base_url, request_timeout } = config const { result_code, base_url, request_timeout } = config
@@ -30,30 +29,16 @@ export const isRelogin = { show: false }
let requestList: any[] = [] let requestList: any[] = []
// 是否正在刷新中 // 是否正在刷新中
let isRefreshToken = false let isRefreshToken = false
// 请求白名单,无须token的接口
const whiteList: string[] = ['/login', '/refresh-token']
// 创建axios实例 // 创建axios实例
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
baseURL: base_url, // api 的 base_url baseURL: base_url, // api 的 base_url
timeout: request_timeout, // 请求超时时间 timeout: request_timeout, // 请求超时时间
withCredentials: false // 禁用 Cookie 等信息 withCredentials: true
}) })
// request拦截器 // request拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
// 是否需要设置 token
let isToken = (config!.headers || {}).isToken === false
whiteList.some((v) => {
if (config.url) {
config.url.indexOf(v) > -1
return (isToken = false)
}
})
if (getAccessToken() && !isToken) {
;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
}
// 设置租户 // 设置租户
if (tenantEnable && tenantEnable === 'true') { if (tenantEnable && tenantEnable === 'true') {
const tenantId = getTenantId() const tenantId = getTenantId()
@@ -117,16 +102,10 @@ service.interceptors.response.use(
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
if (!isRefreshToken) { if (!isRefreshToken) {
isRefreshToken = true isRefreshToken = true
// 1. 如果获取不到刷新令牌,则只能执行登出操作 // 使用 HttpOnly Cookie 刷新访问令牌
if (!getRefreshToken()) {
return handleAuthorized()
}
// 2. 进行刷新访问令牌
try { try {
const refreshTokenRes = await refreshToken() await refreshToken()
// 2.1 刷新成功,则回放队列的请求 + 当前请求 // 2.1 刷新成功,则回放队列的请求 + 当前请求
setToken((await refreshTokenRes).data.data)
config.headers!.Authorization = 'Bearer ' + getAccessToken()
requestList.forEach((cb: any) => { requestList.forEach((cb: any) => {
cb() cb()
}) })
@@ -148,7 +127,6 @@ service.interceptors.response.use(
// 添加到队列,等待刷新获取到新的令牌 // 添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => { return new Promise((resolve) => {
requestList.push(() => { requestList.push(() => {
config.headers!.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
resolve(service(config)) resolve(service(config))
}) })
}) })
@@ -200,7 +178,12 @@ service.interceptors.response.use(
const refreshToken = async () => { const refreshToken = async () => {
axios.defaults.headers.common['tenant-id'] = getTenantId() axios.defaults.headers.common['tenant-id'] = getTenantId()
return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken()) const response = await axios.post(base_url + '/system/auth/refresh-token-cookie', null, {
withCredentials: true
})
if (response.data?.code !== result_code) {
throw new Error(response.data?.msg)
}
} }
const handleAuthorized = () => { const handleAuthorized = () => {
const { t } = useI18n() const { t } = useI18n()
@@ -218,8 +201,7 @@ const handleAuthorized = () => {
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
resetRouter() // 重置静态路由表 resetRouter() // 重置静态路由表
deleteUserCache() // 删除用户缓存 removeAuthenticated()
removeToken()
isRelogin.show = false isRelogin.show = false
// 干掉token后再走一次路由让它过router.beforeEach的校验 // 干掉token后再走一次路由让它过router.beforeEach的校验
window.location.href = window.location.href window.location.href = window.location.href
@@ -1,14 +1,13 @@
import type { App } from 'vue' import type { App } from 'vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { useUserStoreWithOut } from '@/store/modules/user'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
export function hasPermi(app: App<Element>) { export function hasPermi(app: App<Element>) {
app.directive('hasPermi', (el, binding) => { app.directive('hasPermi', (el, binding) => {
const { wsCache } = useCache()
const { value } = binding const { value } = binding
const all_permission = '*:*:*' const all_permission = '*:*:*'
const permissions = wsCache.get(CACHE_KEY.USER).permissions const permissions = useUserStoreWithOut().getPermissions
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value const permissionFlag = value
@@ -1,14 +1,13 @@
import type { App } from 'vue' import type { App } from 'vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { useUserStoreWithOut } from '@/store/modules/user'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
export function hasRole(app: App<Element>) { export function hasRole(app: App<Element>) {
app.directive('hasRole', (el, binding) => { app.directive('hasRole', (el, binding) => {
const { wsCache } = useCache()
const { value } = binding const { value } = binding
const super_admin = 'admin' const super_admin = 'admin'
const roles = wsCache.get(CACHE_KEY.USER).roles const roles = useUserStoreWithOut().getRoles
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const roleFlag = value const roleFlag = value
@@ -7,9 +7,6 @@ import WebStorageCache from 'web-storage-cache'
type CacheType = 'localStorage' | 'sessionStorage' type CacheType = 'localStorage' | 'sessionStorage'
export const CACHE_KEY = { export const CACHE_KEY = {
// 用户相关
ROLE_ROUTERS: 'roleRouters',
USER: 'user',
// 系统设置 // 系统设置
IS_DARK: 'isDark', IS_DARK: 'isDark',
LANG: 'lang', LANG: 'lang',
@@ -30,10 +27,3 @@ export const useCache = (type: CacheType = 'localStorage') => {
wsCache wsCache
} }
} }
export const deleteUserCache = () => {
const { wsCache } = useCache()
wsCache.delete(CACHE_KEY.USER)
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
// 注意,不要清理 LoginForm 登录表单
}
@@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import { deleteUserCache } from '@/hooks/web/useCache'
import { useLockStore } from '@/store/modules/lock' import { useLockStore } from '@/store/modules/lock'
import { useNow } from '@/hooks/web/useNow' import { useNow } from '@/hooks/web/useNow'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
@@ -50,7 +49,6 @@ async function unLock() {
async function goLogin() { async function goLogin() {
await userStore.loginOut().catch(() => {}) await userStore.loginOut().catch(() => {})
// 登出后清理 // 登出后清理
deleteUserCache() // 清空用户缓存
tagsViewStore.delAllViews() tagsViewStore.delAllViews()
resetRouter() // 重置静态路由表 resetRouter() // 重置静态路由表
lockStore.resetLockInfo() lockStore.resetLockInfo()
+3 -3
View File
@@ -1,7 +1,7 @@
import router from './router' import router from './router'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import { isRelogin } from '@/config/axios/service' import { isRelogin } from '@/config/axios/service'
import { getAccessToken } from '@/utils/auth' import { isAuthenticated } from '@/utils/auth'
import { useTitle } from '@/hooks/web/useTitle' import { useTitle } from '@/hooks/web/useTitle'
import { useNProgress } from '@/hooks/web/useNProgress' import { useNProgress } from '@/hooks/web/useNProgress'
import { usePageLoading } from '@/hooks/web/usePageLoading' import { usePageLoading } from '@/hooks/web/usePageLoading'
@@ -60,7 +60,7 @@ const whiteList = [
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
start() start()
loadStart() loadStart()
if (getAccessToken()) { if (isAuthenticated()) {
if (to.path === '/login') { if (to.path === '/login') {
next({ path: '/' }) next({ path: '/' })
} else { } else {
@@ -76,7 +76,7 @@ router.beforeEach(async (to, from, next) => {
await userStore.setUserInfoAction() await userStore.setUserInfoAction()
isRelogin.show = false isRelogin.show = false
// 后端过滤菜单 // 后端过滤菜单
await permissionStore.generateRoutes() await permissionStore.generateRoutes(userStore.getMenus)
permissionStore.getAddRouters.forEach((route) => { permissionStore.getAddRouters.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表 router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
}) })
@@ -3,9 +3,6 @@ import { store } from '@/store'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import remainingRouter from '@/router/modules/remaining' import remainingRouter from '@/router/modules/remaining'
import { flatMultiLevelRoutes, generateRoute } from '@/utils/routerHelper' import { flatMultiLevelRoutes, generateRoute } from '@/utils/routerHelper'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export interface PermissionState { export interface PermissionState {
routers: AppRouteRecordRaw[] routers: AppRouteRecordRaw[]
@@ -31,13 +28,9 @@ export const usePermissionStore = defineStore('permission', {
} }
}, },
actions: { actions: {
async generateRoutes(): Promise<unknown> { async generateRoutes(res: AppCustomRouteRecordRaw[] = []): Promise<unknown> {
return new Promise<void>(async (resolve) => { return new Promise<void>(async (resolve) => {
// 获得菜单列表,它在登录的时候,setUserInfoAction 方法中已经进行获取 // 获得菜单列表,它在登录的时候,setUserInfoAction 方法中已经进行获取
let res: AppCustomRouteRecordRaw[] = []
if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {
res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]
}
const routerMap: AppRouteRecordRaw[] = generateRoute(res) const routerMap: AppRouteRecordRaw[] = generateRoute(res)
// 动态路由,404一定要放到最后面 // 动态路由,404一定要放到最后面
this.addRouters = routerMap.concat([ this.addRouters = routerMap.concat([
+18 -25
View File
@@ -1,24 +1,22 @@
import { store } from '@/store' import { store } from '@/store'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { getAccessToken, removeToken } from '@/utils/auth' import { isAuthenticated, removeAuthenticated } from '@/utils/auth'
import { CACHE_KEY, useCache, deleteUserCache } from '@/hooks/web/useCache'
import { getInfo, loginOut } from '@/api/login' import { getInfo, loginOut } from '@/api/login'
const { wsCache } = useCache()
interface UserVO { interface UserVO {
id: number id: number
avatar: string avatar: string
nickname: string nickname: string
deptId: number deptId: number
shopId?: number
} }
interface UserInfoVO { interface UserInfoVO {
// USER 缓存
permissions: string[] permissions: string[]
roles: string[] roles: string[]
isSetUser: boolean isSetUser: boolean
user: UserVO user: UserVO
menus: AppCustomRouteRecordRaw[]
} }
export const useUserStore = defineStore('admin-user', { export const useUserStore = defineStore('admin-user', {
@@ -31,7 +29,8 @@ export const useUserStore = defineStore('admin-user', {
avatar: '', avatar: '',
nickname: '', nickname: '',
deptId: 0 deptId: 0
} },
menus: []
}), }),
getters: { getters: {
getPermissions(): string[] { getPermissions(): string[] {
@@ -45,49 +44,43 @@ export const useUserStore = defineStore('admin-user', {
}, },
getUser(): UserVO { getUser(): UserVO {
return this.user return this.user
},
getMenus(): AppCustomRouteRecordRaw[] {
return this.menus
} }
}, },
actions: { actions: {
async setUserInfoAction() { async setUserInfoAction() {
if (!getAccessToken()) { if (!isAuthenticated()) {
this.resetState() this.resetState()
return null return null
} }
let userInfo = wsCache.get(CACHE_KEY.USER) const userInfo = await getInfo()
if (!userInfo) {
userInfo = await getInfo()
}
this.permissions = userInfo.permissions this.permissions = userInfo.permissions
this.roles = userInfo.roles this.roles = userInfo.roles
this.user = userInfo.user this.user = userInfo.user
this.menus = userInfo.menus
this.isSetUser = true this.isSetUser = true
wsCache.set(CACHE_KEY.USER, userInfo)
wsCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus)
}, },
async setUserAvatarAction(avatar: string) { async setUserAvatarAction(avatar: string) {
const userInfo = wsCache.get(CACHE_KEY.USER)
// NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`
this.user.avatar = avatar this.user.avatar = avatar
userInfo.user.avatar = avatar
wsCache.set(CACHE_KEY.USER, userInfo)
}, },
async setUserNicknameAction(nickname: string) { async setUserNicknameAction(nickname: string) {
const userInfo = wsCache.get(CACHE_KEY.USER)
// NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`
this.user.nickname = nickname this.user.nickname = nickname
userInfo.user.nickname = nickname
wsCache.set(CACHE_KEY.USER, userInfo)
}, },
async loginOut() { async loginOut() {
await loginOut() try {
removeToken() await loginOut()
deleteUserCache() // 删除用户缓存 } finally {
this.resetState() removeAuthenticated()
this.resetState()
}
}, },
resetState() { resetState() {
this.permissions = [] this.permissions = []
this.roles = [] this.roles = []
this.isSetUser = false this.isSetUser = false
this.menus = []
this.user = { this.user = {
id: 0, id: 0,
avatar: '', avatar: '',
+9 -29
View File
@@ -1,38 +1,18 @@
import { useCache, CACHE_KEY } from '@/hooks/web/useCache' import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
import { TokenType } from '@/api/login/types'
const { wsCache } = useCache() const { wsCache } = useCache()
const { wsCache: sessionCache } = useCache('sessionStorage')
const AccessTokenKey = 'ACCESS_TOKEN' wsCache.delete('ACCESS_TOKEN')
const RefreshTokenKey = 'REFRESH_TOKEN' wsCache.delete('REFRESH_TOKEN')
wsCache.delete('user')
wsCache.delete('roleRouters')
// 获取token const AuthenticatedKey = 'AUTHENTICATED'
export const getAccessToken = () => {
// 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')
}
// 刷新token export const isAuthenticated = () => sessionCache.get(AuthenticatedKey) === true
export const getRefreshToken = () => { export const setAuthenticated = () => sessionCache.set(AuthenticatedKey, true)
return wsCache.get(RefreshTokenKey) export const removeAuthenticated = () => sessionCache.delete(AuthenticatedKey)
}
// 设置token
export const setToken = (token: TokenType) => {
wsCache.set(RefreshTokenKey, token.refreshToken)
wsCache.set(AccessTokenKey, token.accessToken)
}
// 删除token
export const removeToken = () => {
wsCache.delete(AccessTokenKey)
wsCache.delete(RefreshTokenKey)
}
/** 格式化token(jwt格式) */
export const formatToken = (token: string): string => {
return 'Bearer ' + token
}
// ========== 账号相关 ========== // ========== 账号相关 ==========
export type LoginFormType = { export type LoginFormType = {
+3 -5
View File
@@ -1,4 +1,4 @@
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { useUserStoreWithOut } from '@/store/modules/user'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
@@ -9,10 +9,9 @@ const { t } = useI18n() // 国际化
*/ */
export function checkPermi(value: string[]) { export function checkPermi(value: string[]) {
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const { wsCache } = useCache()
const permissionDatas = value const permissionDatas = value
const all_permission = '*:*:*' const all_permission = '*:*:*'
const permissions = wsCache.get(CACHE_KEY.USER).permissions const permissions = useUserStoreWithOut().getPermissions
const hasPermission = permissions.some((permission) => { const hasPermission = permissions.some((permission) => {
return all_permission === permission || permissionDatas.includes(permission) return all_permission === permission || permissionDatas.includes(permission)
}) })
@@ -30,10 +29,9 @@ export function checkPermi(value: string[]) {
*/ */
export function checkRole(value: string[]) { export function checkRole(value: string[]) {
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const { wsCache } = useCache()
const permissionRoles = value const permissionRoles = value
const super_admin = 'admin' const super_admin = 'admin'
const roles = wsCache.get(CACHE_KEY.USER).roles const roles = useUserStoreWithOut().getRoles
const hasRole = roles.some((role) => { const hasRole = roles.some((role) => {
return super_admin === role || permissionRoles.includes(role) return super_admin === role || permissionRoles.includes(role)
}) })
@@ -69,9 +69,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { useUserStore } from '@/store/modules/user'
const { wsCache } = useCache()
const grid =ref({ const grid =ref({
@@ -84,10 +82,10 @@ const grid =ref({
const user = wsCache.get(CACHE_KEY.USER) const userStore = useUserStore()
const isShow = ref(true) const isShow = ref(true)
if(user.user.shopId > 0) { if ((userStore.user.shopId ?? 0) > 0) {
isShow.value = false isShow.value = false
} }
@@ -251,8 +251,8 @@ const tryLogin = async () => {
const code = route?.query?.code as string const code = route?.query?.code as string
const state = route?.query?.state as string const state = route?.query?.state as string
const res = await LoginApi.socialLogin(type, code, state) await LoginApi.socialLogin(type, code, state)
authUtil.setToken(res) authUtil.setAuthenticated()
router.push({ path: redirect || '/' }) router.push({ path: redirect || '/' })
} catch (err) {} } catch (err) {}
@@ -297,7 +297,7 @@ const handleLogin = async (params) => {
} else { } else {
authUtil.removeLoginForm() authUtil.removeLoginForm()
} }
authUtil.setToken(res) authUtil.setAuthenticated()
if (!redirect) { if (!redirect) {
redirect = '/' redirect = '/'
} }
@@ -16,7 +16,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-left: 10px; padding-right: 10px"> <el-col :span="24" style="padding-left: 10px; padding-right: 10px">
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName"> <el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
<el-input <el-input
v-model="loginData.loginForm.tenantName" v-model="loginData.loginForm.tenantName"
:placeholder="t('login.tenantNamePlaceholder')" :placeholder="t('login.tenantNamePlaceholder')"
@@ -79,7 +79,6 @@
mode="pop" mode="pop"
@success="handleLogin" @success="handleLogin"
/> />
</el-row> </el-row>
</el-form> </el-form>
</template> </template>
@@ -131,12 +130,10 @@ const loginData = reactive({
} }
}) })
// 获取验证码 // 获取验证码
const getCode = async () => { const getCode = async () => {
// 情况一,未开启:则直接登录 // 情况一,未开启:则直接登录
console.log('oginData.captchaEnable:',loginData.captchaEnable) console.log('oginData.captchaEnable:', loginData.captchaEnable)
if (loginData.captchaEnable === 'false') { if (loginData.captchaEnable === 'false') {
await handleLogin({}) await handleLogin({})
} else { } else {
@@ -189,7 +186,7 @@ const handleLogin = async (params) => {
} else { } else {
authUtil.removeLoginForm() authUtil.removeLoginForm()
} }
authUtil.setToken(res) authUtil.setAuthenticated()
if (!redirect.value) { if (!redirect.value) {
redirect.value = '/' redirect.value = '/'
} }
@@ -97,7 +97,7 @@ import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useIcon } from '@/hooks/web/useIcon' import { useIcon } from '@/hooks/web/useIcon'
import { setTenantId, setToken } from '@/utils/auth' import { setTenantId, setAuthenticated } from '@/utils/auth'
import { usePermissionStore } from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission'
import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login' import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login'
import LoginFormTitle from './LoginFormTitle.vue' import LoginFormTitle from './LoginFormTitle.vue'
@@ -195,8 +195,8 @@ const signIn = async () => {
smsVO.loginSms.mobile = loginData.loginForm.mobileNumber smsVO.loginSms.mobile = loginData.loginForm.mobileNumber
smsVO.loginSms.code = loginData.loginForm.code smsVO.loginSms.code = loginData.loginForm.code
await smsLogin(smsVO.loginSms) await smsLogin(smsVO.loginSms)
.then(async (res) => { .then(async () => {
setToken(res) setAuthenticated()
if (!redirect.value) { if (!redirect.value) {
redirect.value = '/' redirect.value = '/'
} }
@@ -69,16 +69,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import { useWebSocket } from '@vueuse/core' import { useWebSocket } from '@vueuse/core'
import { getAccessToken } from '@/utils/auth'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
defineOptions({ name: 'InfraWebSocket' }) defineOptions({ name: 'InfraWebSocket' })
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const server = ref( const server = ref((import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws')) // WebSocket 服务地址
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken()
) // WebSocket 服务地址
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开 const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色 const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
@@ -106,12 +106,11 @@
import * as ShopApi from '@/api/mall/store/shop' import * as ShopApi from '@/api/mall/store/shop'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import MyMap from './map.vue' import MyMap from './map.vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { useUserStore } from '@/store/modules/user'
const { wsCache } = useCache()
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const userStore = useUserStore()
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
@@ -164,9 +163,7 @@ const isShow = ref(true)
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
const user = wsCache.get(CACHE_KEY.USER) if ((userStore.user.shopId ?? 0) > 0) {
if(user.user.shopId > 0) {
isShow.value = false isShow.value = false
} }
dialogVisible.value = true dialogVisible.value = true
@@ -35,7 +35,7 @@
<el-col :span="12" class="col-add"> <el-col :span="12" class="col-add">
<el-upload <el-upload
:action="UPLOAD_URL" :action="UPLOAD_URL"
:headers="HEADERS" :with-credentials="true"
multiple multiple
:limit="1" :limit="1"
:file-list="fileList" :file-list="fileList"
@@ -59,12 +59,10 @@
import WxMaterialSelect from '@/views/mp/components/wx-material-select' import WxMaterialSelect from '@/views/mp/components/wx-material-select'
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
import type { UploadRawFile } from 'element-plus' import type { UploadRawFile } from 'element-plus'
import { getAccessToken } from '@/utils/auth'
import { Reply } from './types' import { Reply } from './types'
const message = useMessage() const message = useMessage()
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary' const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
const props = defineProps<{ const props = defineProps<{
modelValue: Reply modelValue: Reply
@@ -12,7 +12,7 @@
<div class="thumb-but"> <div class="thumb-but">
<el-upload <el-upload
:action="UPLOAD_URL" :action="UPLOAD_URL"
:headers="HEADERS" :with-credentials="true"
multiple multiple
:limit="1" :limit="1"
:file-list="fileList" :file-list="fileList"
@@ -62,13 +62,11 @@
import WxMaterialSelect from '@/views/mp/components/wx-material-select' import WxMaterialSelect from '@/views/mp/components/wx-material-select'
import type { UploadRawFile } from 'element-plus' import type { UploadRawFile } from 'element-plus'
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
import { getAccessToken } from '@/utils/auth'
import { Reply } from './types' import { Reply } from './types'
const message = useMessage() const message = useMessage()
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary' const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
const props = defineProps<{ const props = defineProps<{
modelValue: Reply modelValue: Reply
@@ -31,7 +31,7 @@
<el-col :span="12"> <el-col :span="12">
<el-upload <el-upload
:action="UPLOAD_URL" :action="UPLOAD_URL"
:headers="HEADERS" :with-credentials="true"
multiple multiple
:limit="1" :limit="1"
:file-list="fileList" :file-list="fileList"
@@ -53,13 +53,11 @@ import WxVideoPlayer from '@/views/mp/components/wx-video-play'
import WxMaterialSelect from '@/views/mp/components/wx-material-select' import WxMaterialSelect from '@/views/mp/components/wx-material-select'
import type { UploadRawFile } from 'element-plus' import type { UploadRawFile } from 'element-plus'
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
import { getAccessToken } from '@/utils/auth'
import { Reply } from './types' import { Reply } from './types'
const message = useMessage() const message = useMessage()
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary' const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() }
const props = defineProps<{ const props = defineProps<{
modelValue: Reply modelValue: Reply
@@ -33,7 +33,7 @@
<el-col :span="12" class="col-add"> <el-col :span="12" class="col-add">
<el-upload <el-upload
:action="UPLOAD_URL" :action="UPLOAD_URL"
:headers="HEADERS" :with-credentials="true"
multiple multiple
:limit="1" :limit="1"
:file-list="fileList" :file-list="fileList"
@@ -57,12 +57,10 @@ import WxMaterialSelect from '@/views/mp/components/wx-material-select'
import WxVoicePlayer from '@/views/mp/components/wx-voice-play' import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
import type { UploadRawFile } from 'element-plus' import type { UploadRawFile } from 'element-plus'
import { getAccessToken } from '@/utils/auth'
import { Reply } from './types' import { Reply } from './types'
const message = useMessage() const message = useMessage()
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary' const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
const props = defineProps<{ const props = defineProps<{
modelValue: Reply modelValue: Reply
@@ -17,7 +17,7 @@
<div class="thumb-but"> <div class="thumb-but">
<el-upload <el-upload
:action="UPLOAD_URL" :action="UPLOAD_URL"
:headers="HEADERS" :with-credentials="true"
multiple multiple
:limit="1" :limit="1"
:file-list="fileList" :file-list="fileList"
@@ -61,14 +61,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import WxMaterialSelect from '@/views/mp/components/wx-material-select' import WxMaterialSelect from '@/views/mp/components/wx-material-select'
import { getAccessToken } from '@/utils/auth'
import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus' import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
import { NewsItem } from './types' import { NewsItem } from './types'
const message = useMessage() const message = useMessage()
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址 const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
const props = defineProps<{ const props = defineProps<{
modelValue: NewsItem modelValue: NewsItem
@@ -1,5 +1,5 @@
import { IEditorConfig } from '@wangeditor/editor' import { IEditorConfig } from '@wangeditor/editor'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getTenantId } from '@/utils/auth'
const message = useMessage() const message = useMessage()
@@ -31,10 +31,8 @@ export const createEditorConfig = (
// 自定义增加 http header // 自定义增加 http header
headers: { headers: {
Accept: '*', Accept: '*',
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId() 'tenant-id': getTenantId()
}, },
// 跨域是否传递 cookie ,默认为 false // 跨域是否传递 cookie ,默认为 false
withCredentials: true, withCredentials: true,
@@ -1,7 +1,7 @@
<template> <template>
<el-upload <el-upload
:action="UPLOAD_URL" :action="UPLOAD_URL"
:headers="HEADERS" :with-credentials="true"
multiple multiple
:limit="1" :limit="1"
:file-list="fileList" :file-list="fileList"
@@ -20,14 +20,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { UploadProps, UploadUserFile } from 'element-plus' import type { UploadProps, UploadUserFile } from 'element-plus'
import { import { UPLOAD_URL, UploadData, UploadType, beforeImageUpload, beforeVoiceUpload } from './upload'
HEADERS,
UPLOAD_URL,
UploadData,
UploadType,
beforeImageUpload,
beforeVoiceUpload
} from './upload'
const message = useMessage() const message = useMessage()
@@ -2,7 +2,7 @@
<el-dialog title="新建视频" v-model="showDialog" width="600px"> <el-dialog title="新建视频" v-model="showDialog" width="600px">
<el-upload <el-upload
:action="UPLOAD_URL" :action="UPLOAD_URL"
:headers="HEADERS" :with-credentials="true"
multiple multiple
:limit="1" :limit="1"
:file-list="fileList" :file-list="fileList"
@@ -55,7 +55,7 @@ import type {
UploadProps, UploadProps,
UploadUserFile UploadUserFile
} from 'element-plus' } from 'element-plus'
import { HEADERS, UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from './upload' import { UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from './upload'
const message = useMessage() const message = useMessage()
@@ -1,8 +1,6 @@
import type { UploadProps, UploadRawFile } from 'element-plus' import type { UploadProps, UploadRawFile } from 'element-plus'
import { getAccessToken } from '@/utils/auth'
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 请求头
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址 const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址
interface UploadData { interface UploadData {
@@ -22,7 +20,6 @@ const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile)
useBeforeUpload(UploadType.Video, 10)(rawFile) useBeforeUpload(UploadType.Video, 10)(rawFile)
export { export {
HEADERS,
UPLOAD_URL, UPLOAD_URL,
UploadType, UploadType,
UploadData, UploadData,
@@ -115,13 +115,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as MenuApi from '@/api/system/menu' import * as MenuApi from '@/api/system/menu'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants' import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
defineOptions({ name: 'SystemMenuForm' }) defineOptions({ name: 'SystemMenuForm' })
const { wsCache } = useCache()
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
@@ -213,8 +211,6 @@ const submitForm = async () => {
emit('success') emit('success')
} finally { } finally {
formLoading.value = false formLoading.value = false
// 清空,从而触发刷新
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
} }
} }
@@ -125,11 +125,9 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { handleTree } from '@/utils/tree' import { handleTree } from '@/utils/tree'
import * as MenuApi from '@/api/system/menu' import * as MenuApi from '@/api/system/menu'
import MenuForm from './MenuForm.vue' import MenuForm from './MenuForm.vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
defineOptions({ name: 'SystemMenu' }) defineOptions({ name: 'SystemMenu' })
const { wsCache } = useCache()
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
@@ -184,9 +182,6 @@ const toggleExpandAll = () => {
const refreshMenu = async () => { const refreshMenu = async () => {
try { try {
await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存') await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
// 清空,从而触发刷新
wsCache.delete(CACHE_KEY.USER)
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
// 刷新浏览器 // 刷新浏览器
location.reload() location.reload()
} catch {} } catch {}
@@ -7,6 +7,7 @@
:auto-upload="false" :auto-upload="false"
:disabled="formLoading" :disabled="formLoading"
:headers="uploadHeaders" :headers="uploadHeaders"
:with-credentials="true"
:limit="1" :limit="1"
:on-error="submitFormError" :on-error="submitFormError"
:on-exceed="handleExceed" :on-exceed="handleExceed"
@@ -42,7 +43,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getTenantId } from '@/utils/auth'
import download from '@/utils/download' import download from '@/utils/download'
defineOptions({ name: 'SystemUserImportForm' }) defineOptions({ name: 'SystemUserImportForm' })
@@ -75,7 +76,6 @@ const submitForm = async () => {
} }
// 提交请求 // 提交请求
uploadHeaders.value = { uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId() 'tenant-id': getTenantId()
} }
formLoading.value = true formLoading.value = true