实现refreshToken与accessToken

This commit is contained in:
qumeng039@126.com 2025-08-15 14:40:59 +08:00
parent 423ee48c85
commit 6dbbddcf5e
10 changed files with 105 additions and 53 deletions

BIN
dist.zip

Binary file not shown.

View File

@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from "vue-router"
import { useLoginStore } from "@/store/authentication/index"
import cache from "@/utils/cache"
const routes = [
{ path: "/", redirect: "/home", meta: { requiresAuth: true } },
{
@ -77,14 +78,13 @@ const router = createRouter({
// // 全局前置守卫
router.beforeEach((to, from, next) => {
const loginStore = useLoginStore()
const token = cache.getItem("token")
// 如果目标路由需要登录且用户未登录
if (to.meta.requiresAuth && !loginStore.token) {
if (to.meta.requiresAuth && !token) {
next("/login") // 跳转到登录页
}
// 如果用户已登录但尝试访问登录页
else if (to.path === "/login" && loginStore.token) {
else if (to.path === "/login" && token) {
next("/home") // 跳转到主页
}
// 其他情况正常放行

View File

@ -1,5 +1,9 @@
import http from "@/utils/http"
export function login(userInfo) {
return http.post("/auth/login", userInfo)
return http.post("/auth/login", userInfo, { withCredentials: true })
}
export function refreshToken() {
return http.post("/auth/refresh", {}, { withCredentials: true })
}

View File

@ -69,10 +69,7 @@ export function getSocialCommunityList() {
}
// 社交紧密团体中社团内的社团内部节点 从用户组中点击,只显示这两个用户的关系
export function getSocialCommunityDetailFromUserGroup(
relationId,
time = "2024-05-16 16:56:04"
) {
export function getSocialCommunityDetailFromUserGroup(relationId, time = "2024-05-16 16:56:04") {
return http.get(`/linkPrediction/user_detail?relationId=${relationId}&time=${time}`)
}
// 社交紧密团体的社团内部节点
@ -110,3 +107,8 @@ export function getCharacterSocialCommunityDetailNodes(
return http.get(`/linkPrediction/social/community_detail?groupIds=${ids}&dateTime=${time}`)
}
}
//点击边获取两用户交互的次数
export function getInteractionCount(source, target) {
return http.get(`/linkPrediction/interactionCount?source=${source}&target=${target}`)
}

View File

@ -1,21 +1,34 @@
import { defineStore } from "pinia"
import { login } from "@/service/api/authentication"
import { login, refreshToken } from "@/service/api/authentication"
import cache from "@/utils/cache"
import router from "@/router"
import { ElMessage } from "element-plus"
const TOKEN_KEY = "token"
export const useLoginStore = defineStore("loginStore", {
state: () => ({
token: cache.getItem(TOKEN_KEY) ?? ""
token: cache.getItem("token") ?? ""
}),
actions: {
setToken(accessToken) {
this.token = accessToken
cache.setItem("token", accessToken)
},
async loginForAccessToken(userInfo) {
const res = await login(userInfo)
if (res.code != 200) return
this.token = res.data.accessToken
cache.setItem(TOKEN_KEY, res.data.accessToken)
this.setToken(res.data.accessToken)
ElMessage.success("登录成功!")
router.push("/navigation")
},
async loginForRefreshToken() {
const res = await refreshToken()
console.log(res)
if (res.code == 200) {
this.setToken(res.data.accessToken)
return true // 刷新成功
} else {
return false
}
}
}
})

View File

@ -13,7 +13,8 @@ import {
getSocialPostListByRelationId,
getCharacterSocialCommunityNodes,
getCharacterSocialCommunityStatistics,
getCharacterSocialCommunityDetailNodes
getCharacterSocialCommunityDetailNodes,
getInteractionCount
} from "@/service/api/linkPrediction"
import defaultAvatar from "@/assets/images/avatar/default.png"
@ -209,6 +210,11 @@ export const useCharacterInteractionStore = defineStore("characterInteraction",
...item,
count: res.data[item.key]
}))
},
async initInteractionCount(source, target) {
const res = await getInteractionCount(source, target)
if (res.code != 200) return
return res.data
}
},
persist: true // 开启持久化
@ -415,7 +421,7 @@ export const useSocialGroupsStore = defineStore("socialGroups", {
{ text: "立委", top: 57.5, left: 72.5, width: 49, height: 19, fontSize: 12, opacity: 0.6 },
{ text: "國會", top: 60.5, left: 265.5, width: 49, height: 19, fontSize: 12, opacity: 0.7 },
{ text: "两岸", top: 170.5, left: 287.5, width: 49, height: 19, fontSize: 12, opacity: 0.8 }
],
]
}),
actions: {
// 互动行为相似列表数据
@ -470,8 +476,8 @@ export const useSocialGroupsStore = defineStore("socialGroups", {
) {
customStatisticsObj.hiddenInteractionCount = 1
// 创建一个Set来获取不重复的ids
const uniqueIds = new Set(ids);
customStatisticsObj.groupCount = uniqueIds.size;
const uniqueIds = new Set(ids)
customStatisticsObj.groupCount = uniqueIds.size
}
this.statisticsDetailList = this.statisticsDetailList.map((item) => ({
@ -681,6 +687,12 @@ export const useCharacterHiddenStore = defineStore("characterHidden", {
this.communityDetailNodeList = res.data.userList
this.timeList = Array.from(new Set(res.data.timeList))
this.predictionUserIds = res.data.predictNodes
},
async initInteractionCount(source, target) {
const res = await getInteractionCount(source, target)
if (res.code != 200) return
return res.data
}
},
persist: true // 开启持久化

View File

@ -2,16 +2,16 @@ import axios from "axios"
import cache from "./cache"
import { ElMessage } from "element-plus"
import router from "@/router"
import { useLoginStore } from "@/store/authentication/index"
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, // 从环境变量获取基础URL
timeout: 10000 // 请求超时时间
timeout: 10000, // 请求超时时间
withCredentials: true
})
//白名单
const excludePath = new Set(["/login"])
// 请求拦截器
const excludePath = new Set(["/auth/login", "/auth/refresh"])
service.interceptors.request.use(
(config) => {
const token = cache.getItem("token")
@ -21,17 +21,14 @@ service.interceptors.request.use(
return config
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
async (response) => {
const res = response.data
// 根据业务状态码处理
const loginStore = useLoginStore()
if (res.code !== 200) {
ElMessage({
message: res.message || "Error",
@ -39,29 +36,32 @@ service.interceptors.response.use(
duration: 5 * 1000
})
// 特殊状态码处理
if (res.code === 401 || res.code === 403) {
// 若token过期或者token无效或者未找到token对页面中需要认证的接口都有效使得重新登录
import("@/store/authentication/index").then(({ useLoginStore }) => {
useLoginStore().token = ""
})
cache.removeItem("token")
router.push("/login")
try {
// 尝试刷新token
const refreshed = await loginStore.loginForRefreshToken()
if (refreshed) {
// 刷新成功更新请求头中的accessToken并重新发送请求
response.config.headers.Authorization = `Bearer ${loginStore.token}`
return service(response.config)
}
} catch (refreshError) {
ElMessage({
message: "会话已过期,请重新登录!",
type: "error",
duration: 5 * 1000
})
cache.removeItem("token")
router.push("/login")
return Promise.reject(new Error("Refresh token failed"))
}
}
return res
// return Promise.reject(new Error(res.message || "Error"))
return Promise.reject(new Error(res.message || "Error")) //500会抛出该错
} else {
return res
}
},
(error) => {
if (error.status == 401 || error.status == 403) {
cache.removeItem("token")
import("@/store/authentication/index").then(({ useLoginStore }) => {
useLoginStore().token = ""
})
router.push("/login")
}
ElMessage({
message: error.message,
type: "error",

View File

@ -53,10 +53,31 @@ const handleGoback = () => {
emit("click:goback", "CommunityNode")
}
const handleClickNode = () => {
chart.on("click", function (params) {
const handleClickNodeOrLink = () => {
chart.on("click", async function (params) {
if (params.dataType == "node" && predictionUserIds.value.includes(params.data.id)) {
//
emit("click:openDialog", params.data)
} else if (params.dataType == "edge") {
//线
const { source, target } = params.data //id
const linkIndex = params.dataIndex
const option = chart.getOption()
const currentInteractionCount = await props.interactionStore.initInteractionCount(
source,
target
)
option.series[0].links[linkIndex].edge = currentInteractionCount
chart.setOption(
{
series: [
{
links: option.series[0].links
}
]
},
false
)
}
})
}
@ -157,8 +178,7 @@ const initChart = async () => {
links.push({
source: parentId,
target: child.id,
edge: child.isHidden ? 1 : 0,
interactionTimes: child.interactionTime,
edge: 0,
lineStyle: {
width: child.isHidden ? 7 : edgeWidth(child.interactionTime),
color: child.isHidden ? props.interactionStore.predictionLineColor : "#37ACD7", // ==
@ -267,7 +287,7 @@ const initChart = async () => {
show: false,
position: "middle",
formatter: function (params) {
return `${params.data.interactionTimes}次互动`
return `${params.data.edge}次互动`
},
fontSize: 14
},
@ -381,7 +401,7 @@ const highLightUserNodes = (userIds) => {
onMounted(() => {
initChart()
highLightUserNodes()
handleClickNode()
handleClickNodeOrLink()
})
</script>

View File

@ -5,7 +5,7 @@
:model="ruleForm"
status-icon
:rules="rules"
label-width="auto"
:label-width="null"
class="demo-ruleForm"
@submit.native.prevent="submitForm(ruleFormRef)"
>
@ -71,7 +71,6 @@ const submitForm = (formEl) => {
if (valid) {
const userInfo = toRaw(ruleForm)
loginStore.loginForAccessToken(userInfo)
} else {
}
})
}
@ -84,6 +83,7 @@ const submitForm = (formEl) => {
border-radius: 7px;
background-color: #fff;
padding: 0 40px;
.demo-ruleForm {
margin-top: 100px;
height: 50%;

View File

@ -10,7 +10,7 @@
</template>
<script setup>
import LoginPanel from "./components/loginPanel.vue"
import LoginPanel from "@/views/Login/components/loginPanel.vue"
</script>
<style scoped lang="less">
@ -37,9 +37,10 @@ import LoginPanel from "./components/loginPanel.vue"
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
// 1350px
@media (max-width: 1350px) {
width: 650px; //
width: 750px; //
}
// 750px