diff --git a/backend/IM_API/Controllers/AuthController.cs b/backend/IM_API/Controllers/AuthController.cs index 7e15bcc..612e647 100644 --- a/backend/IM_API/Controllers/AuthController.cs +++ b/backend/IM_API/Controllers/AuthController.cs @@ -53,6 +53,7 @@ namespace IM_API.Controllers return Ok(res); } [HttpPost] + [ProducesResponseType(typeof(BaseResponse),StatusCodes.Status200OK)] public async Task Refresh(RefreshDto dto) { (bool ok,int userId) = await _refreshTokenService.ValidateRefreshTokenAsync(dto.refreshToken); diff --git a/backend/IM_API/Services/RedisRefreshTokenService.cs b/backend/IM_API/Services/RedisRefreshTokenService.cs index 260041d..60b3a81 100644 --- a/backend/IM_API/Services/RedisRefreshTokenService.cs +++ b/backend/IM_API/Services/RedisRefreshTokenService.cs @@ -53,8 +53,8 @@ namespace IM_API.Services if (json.IsNullOrEmpty) return (false,-1); try { - var doc = JsonConvert.DeserializeObject(json); - var userId = doc.GetProperty("UserId").GetInt32(); + using var doc = JsonDocument.Parse(json.ToString()); + var userId = doc.RootElement.GetProperty("UserId").GetInt32(); return (true,userId); } catch diff --git a/frontend/web/src/services/api.js b/frontend/web/src/services/api.js index e0040ca..3243dcd 100644 --- a/frontend/web/src/services/api.js +++ b/frontend/web/src/services/api.js @@ -2,10 +2,15 @@ import axios from 'axios' import { useMessage } from '@/components/messages/useAlert'; import router from '@/router'; import { useAuthStore } from '@/stores/auth'; +import { authService } from './auth'; const message = useMessage(); const authStore = useAuthStore(); +let waitqueue = []; +let isRefreshing = false; +const authURL = ['/auth/login', '/auth/register', '/auth/refresh']; + const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api', // 从环境变量中读取基础 URL timeout: 10000, @@ -31,16 +36,50 @@ api.interceptors.response.use( response => { return response.data; }, - err => { - if (err.response) { - switch (err.response.status) { + async err => { + const { config, response } = err; + if (response) { + switch (response.status) { case 401: + if (authURL.some(x => config.url.includes(x))) { + authStore.logout(); + message.error('未登录,请登录后操作。'); + router.push('/auth/login') + break; + } + if (config._retry) { + break; + } + + config._retry = true; + // 已经在刷新 → 排队 + if (isRefreshing) { + return new Promise(resolve => { + waitqueue.push(token => { + config.headers.Authorization = `Bearer ${token}` + resolve(api(config)) + }) + }) + } + + isRefreshing = true; + const refreshToken = authStore.refreshToken; + console.log(authStore) + if (refreshToken != null && refreshToken != '') { + const res = await authService.refresh(refreshToken) + authStore.setLoginInfo(res.data.token, res.data.refreshToken, res.data.userInfo) + waitqueue.forEach(cb => cb(authStore.token)); + waitqueue = []; + config.headers.Authorization = `Bearer ${authStore.token}` + return api(config) + } + authStore.logout(); message.error('未登录,请登录后操作。'); router.push('/auth/login') break; case 400: - if (err.response.data && err.response.data.code == 1003) { - message.error(err.response.data.message); + if (response.data && response.data.code == 1003) { + message.error(response.data.message); break; } default: diff --git a/frontend/web/src/services/auth.js b/frontend/web/src/services/auth.js index e3f62e5..8654008 100644 --- a/frontend/web/src/services/auth.js +++ b/frontend/web/src/services/auth.js @@ -13,5 +13,11 @@ export const authService = { * @param {*} data * @returns */ - register: (data) => request.post('/auth/register', data) + register: (data) => request.post('/auth/register', data), + /** + * 刷新用户凭证 + * @param {*} data + * @returns + */ + refresh: (refreshToken) => request.post('/auth/refresh', { refreshToken }) } \ No newline at end of file diff --git a/frontend/web/src/stores/auth.js b/frontend/web/src/stores/auth.js index 2193a06..edde2ab 100644 --- a/frontend/web/src/stores/auth.js +++ b/frontend/web/src/stores/auth.js @@ -4,10 +4,10 @@ import { defineStore } from 'pinia' export const useAuthStore = defineStore('auth', () => { const token = ref(localStorage.getItem('user_token') || ''); const refreshToken = ref(localStorage.getItem('refresh_token') || ''); - const userInfo = ref(localStorage.getItem('user_info') || ''); + const userInfo = ref(JSON.parse(localStorage.getItem('user_info')) || {}); //判断是否已登录 - const isLoggedIn = computed(() => !!token.value); + const isLoggedIn = computed(() => !!refreshToken.value); /** * 登录成功保存状态 @@ -15,12 +15,13 @@ export const useAuthStore = defineStore('auth', () => { * @param {*} user 用户信息 */ function setLoginInfo(newToken, newRefreshToken, user) { + console.log(`设置凭证:\ntoken:${newToken}\nrefreshToken:${newRefreshToken}`) token.value = newToken; refreshToken.value = newRefreshToken userInfo.value = user; localStorage.setItem('user_token', newToken); - localStorage.setItem('refresh_token', refreshToken) - localStorage.setItem('user_info', user) + localStorage.setItem('refresh_token', newRefreshToken) + localStorage.setItem('user_info', JSON.stringify(user)) } /** @@ -29,10 +30,11 @@ export const useAuthStore = defineStore('auth', () => { function logout() { token.value = ''; userInfo.value = null; + refreshToken.value = '' localStorage.removeItem('user_token'); localStorage.removeItem('refresh_token') localStorage.removeItem('user_info') } - return { token, userInfo, isLoggedIn, setLoginInfo, logout }; + return { token, refreshToken, userInfo, isLoggedIn, setLoginInfo, logout }; }) diff --git a/frontend/web/src/views/contact/ContactList.vue b/frontend/web/src/views/contact/ContactList.vue index 0e7f2df..e9ebd99 100644 --- a/frontend/web/src/views/contact/ContactList.vue +++ b/frontend/web/src/views/contact/ContactList.vue @@ -30,9 +30,9 @@ class="list-item" :class="{active: activeContactId === c.id}" @click="activeContactId = c.id"> - +
-
{{ c.name }}
+
{{ c.remarkName }}
@@ -43,21 +43,21 @@

- {{ currentContact.name }} - - {{ currentContact.gender === 'm' ? '♂' : '♀' }} + {{ currentContact.remarkName }} + + {{ '♂' }}

-

微信号:{{ currentContact.wxid }}

-

地区:{{ currentContact.region }}

+

账号:{{ currentContact.userInfo.username }}

+

地区:{{ '未知' }}

- +
- 备注名 - {{ currentContact.alias || '未设置' }} + 昵称 + {{ currentContact.userInfo.nickName }}
个性签名 @@ -65,7 +65,7 @@
来源 - 通过搜索微信号添加 + 通过搜索账号添加
@@ -91,20 +91,24 @@ import { friendService } from '@/services/friend' const searchQuery = ref('') const activeContactId = ref(null) -const contacts1 = ref([]); +const contacts = ref([]); -// 模拟联系人数据 -const contacts = ref([ - { id: 1, name: '南浔', wxid: 'nan_xun_99', region: '浙江 杭州', avatar: 'https://i.pravatar.cc/40?1', gender: 'f', signature: '山有木兮木有枝', alias: '南酱' }, - { id: 102, name: '老张', wxid: 'zhang_boss', region: '广东 深圳', avatar: 'https://i.pravatar.cc/40?10', gender: 'm', signature: '搞钱要紧', alias: '张总' }, - { id: 103, name: 'UI小王', wxid: 'wang_design', region: '上海 黄浦', avatar: 'https://i.pravatar.cc/40?5', gender: 'f', signature: '不改了,真的不改了', alias: '' }, - { id: 104, name: '测试组长', wxid: 'test_pro', region: '北京', avatar: 'https://i.pravatar.cc/40?8', gender: 'm', signature: 'Bug 哪里跑', alias: '铁面人' } -]) const filteredContacts = computed(() => { - return contacts.value.filter(c => - c.name.includes(searchQuery.value) || c.wxid.includes(searchQuery.value) - ) + const searchKey = searchQuery.value.toString().trim(); + if(!searchKey){ + return contacts.value; + } + + + return contacts.value.filter(c => { + if (!c) return false + + const remark = c.remarkName || '' + const username = c.userInfo.username || '' + + return remark.includes(searchKey) || username.includes(searchKey) + }) }) const currentContact = computed(() => { @@ -121,15 +125,14 @@ function handleGoToChat() { const loadContactList = async (page = 1,limit = 100) => { const res = await friendService.getFriendList(page,limit); - contacts1.value = res.data; + contacts.value = res.data; + console.log(contacts.value) } onMounted(async () => { await loadContactList(); - const contacts = await friendService.getFriendList(); - console.log(contacts) })