修复WebSocket身份认证问题
- 添加WebSocketAuthInterceptor处理token认证 - 修改WebSocket连接逻辑,支持token传递 - 统一用户身份识别,确保登录用户使用USER类型 - 修复前端环境变量配置,统一WebSocket URL - 添加Token测试页面用于验证功能 - 更新聊天消息处理逻辑,正确识别用户身份 解决了登录用户发送消息时同时保存GUEST和USER两种类型数据的问题
This commit is contained in:
@@ -5,9 +5,9 @@ VITE_APP_TITLE=开心APP - 开发环境
|
||||
VITE_APP_DESCRIPTION=你的情绪陪伴使者
|
||||
|
||||
# API配置 - 直接访问backend-single
|
||||
VITE_API_BASE_URL=http://localhost:8080/api
|
||||
VITE_UPLOAD_URL=http://localhost:8080/api/upload
|
||||
VITE_WS_URL=http://localhost:8080/ws/chat
|
||||
VITE_API_BASE_URL=http://localhost:19089/api
|
||||
VITE_UPLOAD_URL=http://localhost:19089/api/upload
|
||||
VITE_WS_URL=http://localhost:19089/api/ws/chat
|
||||
|
||||
# WebSocket配置
|
||||
VITE_WS_RECONNECT_ATTEMPTS=5
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
VITE_APP_TITLE=开心APP
|
||||
VITE_APP_DESCRIPTION=你的情绪陪伴使者
|
||||
|
||||
# API配置 - 生产环境通过网关访问
|
||||
VITE_API_BASE_URL=http://47.111.10.27:19000/api
|
||||
VITE_UPLOAD_URL=http://47.111.10.27:19000/api/upload
|
||||
VITE_WS_URL=http://47.111.10.27:19000/ws/chat
|
||||
# API配置 - 生产环境直接访问backend-single
|
||||
VITE_API_BASE_URL=http://47.111.10.27:19089/api
|
||||
VITE_UPLOAD_URL=http://47.111.10.27:19089/api/upload
|
||||
VITE_WS_URL=http://47.111.10.27:19089/api/ws/chat
|
||||
|
||||
# WebSocket配置
|
||||
VITE_WS_RECONNECT_ATTEMPTS=10
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# WebSocket聊天功能完善总结
|
||||
|
||||
## 概述
|
||||
|
||||
根据后端WebSocket接口和聊天接口,对前端聊天页面功能进行了全面完善,提升了用户体验和系统稳定性。
|
||||
|
||||
## 完成的功能改进
|
||||
|
||||
### 1. ✅ 修复WebSocket连接配置
|
||||
- **问题**: 前端WebSocket URL配置需要与后端保持一致
|
||||
- **解决方案**:
|
||||
- 确认后端单体应用运行在8080端口
|
||||
- WebSocket端点为 `http://localhost:8080/ws/chat`
|
||||
- 前端配置已正确设置
|
||||
|
||||
### 2. ✅ 完善消息类型处理
|
||||
- **问题**: 前端消息类型定义与后端不完全匹配
|
||||
- **解决方案**:
|
||||
- 更新 `WebSocketMessage` 接口,与后端DTO保持一致
|
||||
- 更新 `ChatRequest` 接口,支持后端所需的所有字段
|
||||
- 添加详细的类型注释
|
||||
|
||||
### 3. ✅ 优化AI回复显示
|
||||
- **问题**: AI回复需要支持分段显示,模拟自然对话流
|
||||
- **解决方案**:
|
||||
- 实现 `splitAiReply()` 函数,支持 `\n` 和 `\n\n` 分割
|
||||
- 实现 `addAiReplyMessages()` 函数,支持延时分段显示
|
||||
- 每段消息间隔1秒显示,提升用户体验
|
||||
|
||||
### 4. ✅ 完善错误处理机制
|
||||
- **问题**: WebSocket连接错误处理不够友好
|
||||
- **解决方案**:
|
||||
- 增强WebSocket连接错误处理,支持不同错误代码的详细说明
|
||||
- 添加用户友好的错误提示信息
|
||||
- 在聊天界面显示错误信息,而不是仅在控制台输出
|
||||
- 改进消息发送失败的处理逻辑
|
||||
|
||||
### 5. ✅ 添加消息状态跟踪
|
||||
- **问题**: 缺少消息发送状态的可视化反馈
|
||||
- **解决方案**:
|
||||
- 扩展 `ChatMessage` 类型,添加 `status` 和 `error` 字段
|
||||
- 实现 `updateMessageStatus()` 函数,支持状态更新
|
||||
- 在UI中显示消息状态:发送中、已发送、已送达、已读、发送失败
|
||||
- 添加状态对应的样式和颜色区分
|
||||
|
||||
### 6. ✅ 完善会话管理
|
||||
- **问题**: WebSocket连接时会话ID设置和多会话切换需要优化
|
||||
- **解决方案**:
|
||||
- 在创建新会话时自动设置WebSocket会话ID
|
||||
- 在切换会话时更新WebSocket会话ID
|
||||
- 添加 `getConversationId()` 方法获取当前会话ID
|
||||
- 确保WebSocket连接状态与会话状态同步
|
||||
|
||||
## 技术实现细节
|
||||
|
||||
### WebSocket消息类型
|
||||
```typescript
|
||||
interface WebSocketMessage {
|
||||
messageId: string
|
||||
conversationId?: string
|
||||
type: 'TEXT' | 'TYPING' | 'SYSTEM' | 'ERROR' | 'HEARTBEAT' | 'CONNECTION' | 'AI_THINKING'
|
||||
content: string
|
||||
senderId: string
|
||||
senderType: 'USER' | 'GUEST' | 'AI' | 'SYSTEM'
|
||||
status: 'SENDING' | 'SENT' | 'DELIVERED' | 'READ' | 'FAILED'
|
||||
createTime: string
|
||||
data?: any
|
||||
}
|
||||
```
|
||||
|
||||
### 消息状态跟踪
|
||||
- **发送中**: 用户点击发送按钮后立即显示
|
||||
- **已发送**: WebSocket消息发送成功后更新
|
||||
- **已送达**: 数据库保存成功后更新
|
||||
- **已读**: 收到后端确认后更新(待后端支持)
|
||||
- **发送失败**: 发送或保存失败时显示
|
||||
|
||||
### AI回复分段显示
|
||||
```typescript
|
||||
const splitAiReply = (content: string): string[] => {
|
||||
const segments = content.split(/\n\n|\n/).filter(segment => segment.trim().length > 0)
|
||||
return segments
|
||||
}
|
||||
```
|
||||
|
||||
### 错误处理增强
|
||||
- WebSocket连接错误代码映射
|
||||
- 用户友好的错误信息显示
|
||||
- 自动重连机制优化
|
||||
|
||||
## 用户体验改进
|
||||
|
||||
1. **实时状态反馈**: 用户可以看到消息的发送状态
|
||||
2. **自然对话流**: AI回复分段显示,模拟真实对话
|
||||
3. **友好错误提示**: 连接问题时显示清晰的错误信息
|
||||
4. **会话管理**: 支持多会话切换,状态同步
|
||||
5. **连接状态指示**: 头部显示实时连接状态
|
||||
|
||||
## 测试工具
|
||||
|
||||
创建了 `WebSocketTester` 类用于测试WebSocket功能:
|
||||
- 连接测试
|
||||
- 消息发送测试
|
||||
- 断开连接测试
|
||||
- 详细的测试日志
|
||||
|
||||
使用方法:
|
||||
```javascript
|
||||
// 在浏览器控制台中
|
||||
await wsTest.runConnectionTest()
|
||||
await wsTest.testMessageSending()
|
||||
wsTest.testDisconnection()
|
||||
console.log(wsTest.getTestResults())
|
||||
```
|
||||
|
||||
## 后续建议
|
||||
|
||||
1. **消息已读状态**: 需要后端支持消息已读确认
|
||||
2. **离线消息**: 支持离线消息的缓存和同步
|
||||
3. **文件上传**: 扩展支持图片和文件消息
|
||||
4. **消息撤回**: 支持消息撤回功能
|
||||
5. **群聊支持**: 扩展支持多人聊天
|
||||
|
||||
## 配置文件
|
||||
|
||||
确保以下配置正确:
|
||||
- `.env.development`: WebSocket URL配置
|
||||
- `backend-single`: 端口8080,WebSocket端点 `/ws/chat`
|
||||
- 数据库连接配置正确
|
||||
|
||||
## 部署注意事项
|
||||
|
||||
1. 确保后端WebSocket服务正常运行
|
||||
2. 检查防火墙和代理配置
|
||||
3. 验证WebSocket连接的跨域设置
|
||||
4. 监控WebSocket连接的稳定性
|
||||
@@ -110,6 +110,15 @@ const routes: RouteRecordRaw[] = [
|
||||
requiresAuth: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/token-test',
|
||||
name: 'TokenTest',
|
||||
component: () => import('@/views/TokenTest.vue'),
|
||||
meta: {
|
||||
title: 'Token测试',
|
||||
requiresAuth: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
|
||||
@@ -15,20 +15,17 @@ import type {
|
||||
export const authService = {
|
||||
// 获取验证码
|
||||
async getCaptcha(): Promise<CaptchaResponse> {
|
||||
const response = await request.get('/auth/captcha')
|
||||
return response.data.data
|
||||
return await request.get('/auth/captcha')
|
||||
},
|
||||
|
||||
// 用户登录
|
||||
async login(data: LoginRequest): Promise<ApiResponse<LoginResponse>> {
|
||||
const response = await request.post('/auth/login', data)
|
||||
return response.data
|
||||
async login(data: LoginRequest): Promise<LoginResponse> {
|
||||
return await request.post('/auth/login', data)
|
||||
},
|
||||
|
||||
// 用户注册
|
||||
async register(data: RegisterRequest): Promise<ApiResponse<LoginResponse>> {
|
||||
const response = await request.post('/auth/register', data)
|
||||
return response.data
|
||||
async register(data: RegisterRequest): Promise<LoginResponse> {
|
||||
return await request.post('/auth/register', data)
|
||||
},
|
||||
|
||||
// 刷新token
|
||||
|
||||
@@ -2,39 +2,50 @@ import { request } from './api'
|
||||
import type { ChatMessage, ChatSession, PaginatedResponse } from '@/types'
|
||||
|
||||
export const chatApi = {
|
||||
// 发送消息
|
||||
sendMessage: (content: string, sessionId?: string): Promise<ChatMessage> =>
|
||||
request.post('/chat/message', { content, sessionId }),
|
||||
// 发送AI聊天消息(REST备用,主用WebSocket)
|
||||
sendAiMessage: (conversationId: string, message: string, userId: string): Promise<any> =>
|
||||
request.post('/ai/chat', { conversationId, message, userId }),
|
||||
|
||||
// 获取会话列表
|
||||
getSessions: (): Promise<ChatSession[]> =>
|
||||
request.get('/chat/sessions'),
|
||||
// 创建会话
|
||||
createSession: (userId: string, title: string): Promise<ChatSession> =>
|
||||
request.post('/conversation', { userId, title }),
|
||||
|
||||
// 创建新会话
|
||||
createSession: (title?: string): Promise<ChatSession> =>
|
||||
request.post('/chat/session', { title }),
|
||||
// 获取会话分页
|
||||
getSessions: (params: { page: number, size: number, userId?: string }): Promise<PaginatedResponse<ChatSession>> =>
|
||||
request.get('/conversation/page', { params }),
|
||||
|
||||
// 获取会话消息
|
||||
getSessionMessages: (sessionId: string, page = 1, size = 50): Promise<PaginatedResponse<ChatMessage>> =>
|
||||
request.get(`/chat/session/${sessionId}/messages`, { params: { page, size } }),
|
||||
// 获取用户所有会话
|
||||
getUserSessions: (userId: string): Promise<ChatSession[]> =>
|
||||
request.get(`/conversation/user/${userId}`),
|
||||
|
||||
// 删除会话
|
||||
deleteSession: (sessionId: string): Promise<void> =>
|
||||
request.delete(`/chat/session/${sessionId}`),
|
||||
deleteSession: (id: string): Promise<void> =>
|
||||
request.delete(`/conversation/${id}`),
|
||||
|
||||
// 更新会话标题
|
||||
updateSessionTitle: (sessionId: string, title: string): Promise<ChatSession> =>
|
||||
request.put(`/chat/session/${sessionId}`, { title }),
|
||||
updateSessionTitle: (id: string, title: string): Promise<ChatSession> =>
|
||||
request.put(`/conversation/${id}`, { title }),
|
||||
|
||||
// 搜索消息
|
||||
searchMessages: (keyword: string, sessionId?: string): Promise<ChatMessage[]> =>
|
||||
request.get('/chat/search', { params: { keyword, sessionId } }),
|
||||
// 获取会话消息分页
|
||||
getSessionMessages: (conversationId: string, params: { page: number, size: number }): Promise<PaginatedResponse<ChatMessage>> =>
|
||||
request.get(`/message/conversation/${conversationId}/page`, { params }),
|
||||
|
||||
// 获取聊天统计
|
||||
getChatStats: (): Promise<{
|
||||
totalSessions: number
|
||||
totalMessages: number
|
||||
todayMessages: number
|
||||
}> =>
|
||||
request.get('/chat/stats'),
|
||||
// 获取会话所有消息
|
||||
getAllSessionMessages: (conversationId: string): Promise<ChatMessage[]> =>
|
||||
request.get(`/message/conversation/${conversationId}`),
|
||||
|
||||
// 创建消息(保存到数据库)
|
||||
createMessage: (data: {
|
||||
conversationId: string,
|
||||
userId: string,
|
||||
content: string,
|
||||
contentType?: string,
|
||||
senderType?: string,
|
||||
senderId?: string
|
||||
}): Promise<ChatMessage> =>
|
||||
request.post('/message', data),
|
||||
|
||||
// 聊天统计
|
||||
getChatStats: (userId?: string, conversationId?: string): Promise<any> =>
|
||||
request.get('/ai/stats', { params: { userId, conversationId } }),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import SockJS from 'sockjs-client'
|
||||
import * as Stomp from 'stompjs'
|
||||
import type { ChatMessage } from '@/types'
|
||||
|
||||
// WebSocket消息类型
|
||||
// WebSocket消息类型 - 与后端保持一致
|
||||
export interface WebSocketMessage {
|
||||
messageId: string
|
||||
conversationId?: string
|
||||
@@ -15,7 +15,7 @@ export interface WebSocketMessage {
|
||||
data?: any
|
||||
}
|
||||
|
||||
// 聊天请求类型
|
||||
// 聊天请求类型 - 与后端ChatRequest保持一致
|
||||
export interface ChatRequest {
|
||||
content: string
|
||||
senderId: string
|
||||
@@ -82,11 +82,17 @@ export class WebSocketService {
|
||||
this.client.heartbeat.outgoing = 20000
|
||||
this.client.heartbeat.incoming = 20000
|
||||
|
||||
// 连接配置
|
||||
const connectHeaders = {
|
||||
// 连接配置 - 添加token支持
|
||||
const connectHeaders: any = {
|
||||
'X-User-Id': this.userId
|
||||
}
|
||||
|
||||
// 如果有token,添加到连接头中
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
connectHeaders['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
this.client.connect(
|
||||
connectHeaders,
|
||||
(frame) => {
|
||||
@@ -109,13 +115,37 @@ export class WebSocketService {
|
||||
(error) => {
|
||||
console.error('WebSocket连接失败:', error)
|
||||
this.setStatus('ERROR')
|
||||
this.callbacks.onError?.(error)
|
||||
|
||||
// 检查是否是网络错误
|
||||
if (error && error.type === 'close' && error.code === 1006) {
|
||||
console.log('WebSocket连接被异常关闭,尝试重连...')
|
||||
// 详细的错误处理
|
||||
let errorMessage = '连接失败'
|
||||
if (error) {
|
||||
if (error.type === 'close') {
|
||||
switch (error.code) {
|
||||
case 1006:
|
||||
errorMessage = '连接异常断开,正在重连...'
|
||||
break
|
||||
case 1000:
|
||||
errorMessage = '连接正常关闭'
|
||||
break
|
||||
case 1001:
|
||||
errorMessage = '服务器正在重启,请稍后重试'
|
||||
break
|
||||
case 1002:
|
||||
errorMessage = '协议错误'
|
||||
break
|
||||
case 1003:
|
||||
errorMessage = '数据格式错误'
|
||||
break
|
||||
default:
|
||||
errorMessage = `连接关闭 (代码: ${error.code})`
|
||||
}
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
}
|
||||
|
||||
this.callbacks.onError?.({ ...error, userMessage: errorMessage })
|
||||
|
||||
// 尝试重连
|
||||
this.scheduleReconnect()
|
||||
reject(error)
|
||||
@@ -150,13 +180,21 @@ export class WebSocketService {
|
||||
*/
|
||||
sendChatMessage(content: string, conversationId?: string): void {
|
||||
if (!this.client?.connected) {
|
||||
const error = new Error('WebSocket连接已断开,无法发送消息')
|
||||
console.error('WebSocket未连接')
|
||||
this.callbacks.onError?.({ userMessage: '连接已断开,请等待重连后再试', originalError: error })
|
||||
return
|
||||
}
|
||||
|
||||
if (!content.trim()) {
|
||||
const error = new Error('消息内容不能为空')
|
||||
this.callbacks.onError?.({ userMessage: '消息内容不能为空', originalError: error })
|
||||
return
|
||||
}
|
||||
|
||||
// 使用新的后端接口格式
|
||||
const chatRequest: ChatRequest = {
|
||||
content,
|
||||
content: content.trim(),
|
||||
senderId: this.userId!,
|
||||
senderType: this.userId?.startsWith('guest_') ? 'GUEST' : 'USER',
|
||||
messageType: 'TEXT',
|
||||
@@ -169,7 +207,10 @@ export class WebSocketService {
|
||||
console.log('发送聊天消息:', chatRequest)
|
||||
} catch (error) {
|
||||
console.error('发送消息失败:', error)
|
||||
this.callbacks.onError?.(error)
|
||||
this.callbacks.onError?.({
|
||||
userMessage: '消息发送失败,请重试',
|
||||
originalError: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +219,14 @@ export class WebSocketService {
|
||||
*/
|
||||
setConversationId(conversationId: string): void {
|
||||
this.conversationId = conversationId
|
||||
console.log('WebSocket会话ID已更新:', conversationId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话ID
|
||||
*/
|
||||
getConversationId(): string | null {
|
||||
return this.conversationId
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,7 +368,7 @@ export class WebSocketService {
|
||||
}
|
||||
|
||||
// 创建WebSocket服务实例
|
||||
const wsUrl = import.meta.env.VITE_WS_URL || 'http://localhost:19000/ws/chat'
|
||||
const wsUrl = import.meta.env.VITE_WS_URL || 'http://localhost:19089/ws/chat'
|
||||
export const webSocketService = new WebSocketService(wsUrl)
|
||||
|
||||
export default webSocketService
|
||||
|
||||
+127
-40
@@ -3,6 +3,7 @@ import { ref, watch } from 'vue'
|
||||
import type { ChatMessage, ChatSession } from '@/types'
|
||||
import webSocketService, { type WebSocketMessage, type ConnectionStatus } from '@/services/websocket'
|
||||
import { useUserStore } from './user'
|
||||
import { chatApi } from '@/services/chat'
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
const userStore = useUserStore()
|
||||
@@ -21,12 +22,25 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const newMessage: ChatMessage = {
|
||||
...message,
|
||||
id: Date.now().toString(),
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
status: message.type === 'user' ? 'sending' : 'sent'
|
||||
}
|
||||
messages.value.push(newMessage)
|
||||
return newMessage
|
||||
}
|
||||
|
||||
// 更新消息状态
|
||||
const updateMessageStatus = (messageId: string, status: ChatMessage['status'], error?: string) => {
|
||||
const message = messages.value.find(m => m.id === messageId)
|
||||
if (message) {
|
||||
message.status = status
|
||||
if (error) {
|
||||
message.error = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息:WebSocket推送+数据库保存
|
||||
const sendMessage = async (content: string) => {
|
||||
if (!wsConnected.value) {
|
||||
console.error('WebSocket未连接,无法发送消息')
|
||||
@@ -46,69 +60,110 @@ export const useChatStore = defineStore('chat', () => {
|
||||
})
|
||||
|
||||
try {
|
||||
// 通过WebSocket发送消息
|
||||
// WebSocket推送
|
||||
webSocketService.sendChatMessage(content, currentSession.value?.id)
|
||||
console.log('消息已通过WebSocket发送:', content)
|
||||
|
||||
// 更新消息状态为已发送
|
||||
updateMessageStatus(userMessage.id, 'sent')
|
||||
|
||||
// 数据库保存
|
||||
if (currentSession.value?.id && userStore.user?.id) {
|
||||
await chatApi.createMessage({
|
||||
conversationId: currentSession.value.id,
|
||||
userId: userStore.user.id,
|
||||
content,
|
||||
contentType: 'TEXT',
|
||||
senderType: 'USER',
|
||||
senderId: userStore.user.id
|
||||
})
|
||||
|
||||
// 更新消息状态为已送达
|
||||
updateMessageStatus(userMessage.id, 'delivered')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebSocket发送消息失败:', error)
|
||||
console.error('消息发送或保存失败:', error)
|
||||
|
||||
// 更新消息状态为失败
|
||||
updateMessageStatus(userMessage.id, 'failed', '发送失败')
|
||||
|
||||
addMessage({
|
||||
content: '抱歉,消息发送失败,请稍后重试。',
|
||||
type: 'ai',
|
||||
sessionId: currentSession.value?.id
|
||||
})
|
||||
}
|
||||
|
||||
return userMessage
|
||||
}
|
||||
|
||||
const createSession = (title?: string) => {
|
||||
const newSession: ChatSession = {
|
||||
id: Date.now().toString(),
|
||||
title: title || `对话 ${sessions.value.length + 1}`,
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
messageCount: 0
|
||||
// 创建会话:同步后端
|
||||
const createSession = async (title?: string) => {
|
||||
let newSession: ChatSession
|
||||
if (userStore.user?.id) {
|
||||
newSession = await chatApi.createSession(userStore.user.id, title || `对话${sessions.value.length + 1}`)
|
||||
} else {
|
||||
newSession = {
|
||||
id: Date.now().toString(),
|
||||
title: title || `对话${sessions.value.length + 1}`,
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
messageCount: 0
|
||||
}
|
||||
}
|
||||
sessions.value.unshift(newSession)
|
||||
currentSession.value = newSession
|
||||
messages.value = []
|
||||
|
||||
// 如果WebSocket已连接,设置新的会话ID
|
||||
if (wsConnected.value) {
|
||||
webSocketService.setConversationId(newSession.id)
|
||||
}
|
||||
|
||||
return newSession
|
||||
}
|
||||
|
||||
const switchSession = (sessionId: string) => {
|
||||
// 切换会话:加载消息
|
||||
const switchSession = async (sessionId: string) => {
|
||||
const session = sessions.value.find(s => s.id === sessionId)
|
||||
if (session) {
|
||||
currentSession.value = session
|
||||
// TODO: 加载该会话的消息
|
||||
loadSessionMessages(sessionId)
|
||||
await loadSessionMessages(sessionId)
|
||||
|
||||
// 如果WebSocket已连接,更新会话ID
|
||||
if (wsConnected.value) {
|
||||
webSocketService.setConversationId(sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载会话消息:从后端获取
|
||||
const loadSessionMessages = async (sessionId: string) => {
|
||||
try {
|
||||
// TODO: 从API加载消息
|
||||
// const response = await chatApi.getSessionMessages(sessionId)
|
||||
// messages.value = response.data
|
||||
|
||||
// 临时模拟数据
|
||||
messages.value = []
|
||||
const msgs = await chatApi.getAllSessionMessages(sessionId)
|
||||
messages.value = msgs
|
||||
} catch (error) {
|
||||
console.error('Failed to load session messages:', error)
|
||||
messages.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const deleteSession = (sessionId: string) => {
|
||||
const index = sessions.value.findIndex(s => s.id === sessionId)
|
||||
if (index > -1) {
|
||||
sessions.value.splice(index, 1)
|
||||
if (currentSession.value?.id === sessionId) {
|
||||
currentSession.value = sessions.value[0] || null
|
||||
if (currentSession.value) {
|
||||
loadSessionMessages(currentSession.value.id)
|
||||
} else {
|
||||
messages.value = []
|
||||
// 删除会话:同步后端
|
||||
const deleteSession = async (sessionId: string) => {
|
||||
try {
|
||||
await chatApi.deleteSession(sessionId)
|
||||
const index = sessions.value.findIndex(s => s.id === sessionId)
|
||||
if (index > -1) {
|
||||
sessions.value.splice(index, 1)
|
||||
if (currentSession.value?.id === sessionId) {
|
||||
currentSession.value = sessions.value[0] || null
|
||||
if (currentSession.value) {
|
||||
await loadSessionMessages(currentSession.value.id)
|
||||
} else {
|
||||
messages.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除会话失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +177,33 @@ export const useChatStore = defineStore('chat', () => {
|
||||
)
|
||||
}
|
||||
|
||||
// 分割AI回复为多条消息
|
||||
const splitAiReply = (content: string): string[] => {
|
||||
// 先按 \n\n 分割,再按 \n 分割
|
||||
const segments = content.split(/\n\n|\n/).filter(segment => segment.trim().length > 0)
|
||||
return segments
|
||||
}
|
||||
|
||||
// 添加AI回复消息(支持分段显示)
|
||||
const addAiReplyMessages = (content: string, delay: number = 1000) => {
|
||||
const segments = splitAiReply(content)
|
||||
|
||||
segments.forEach((segment, index) => {
|
||||
setTimeout(() => {
|
||||
addMessage({
|
||||
content: segment.trim(),
|
||||
type: 'ai',
|
||||
sessionId: currentSession.value?.id
|
||||
})
|
||||
|
||||
// 最后一条消息后停止输入状态
|
||||
if (index === segments.length - 1) {
|
||||
isTyping.value = false
|
||||
}
|
||||
}, index * delay)
|
||||
})
|
||||
}
|
||||
|
||||
// WebSocket消息处理
|
||||
const handleWebSocketMessage = (wsMessage: WebSocketMessage) => {
|
||||
console.log('处理WebSocket消息:', wsMessage)
|
||||
@@ -129,13 +211,8 @@ export const useChatStore = defineStore('chat', () => {
|
||||
switch (wsMessage.type) {
|
||||
case 'TEXT':
|
||||
if (wsMessage.senderType === 'AI') {
|
||||
// AI回复消息
|
||||
addMessage({
|
||||
content: wsMessage.content,
|
||||
type: 'ai',
|
||||
sessionId: currentSession.value?.id
|
||||
})
|
||||
isTyping.value = false
|
||||
// AI回复消息 - 支持分段显示
|
||||
addAiReplyMessages(wsMessage.content)
|
||||
}
|
||||
break
|
||||
|
||||
@@ -176,7 +253,8 @@ export const useChatStore = defineStore('chat', () => {
|
||||
// WebSocket连接管理
|
||||
const connectWebSocket = async () => {
|
||||
try {
|
||||
const userId = userStore.user?.id || undefined
|
||||
// 优先使用userInfo中的用户ID,如果没有则使用user中的ID
|
||||
const userId = userStore.userInfo?.id || userStore.user?.id || undefined
|
||||
|
||||
await webSocketService.connect(userId, {
|
||||
onMessage: handleWebSocketMessage,
|
||||
@@ -201,6 +279,15 @@ export const useChatStore = defineStore('chat', () => {
|
||||
wsConnected.value = false
|
||||
isConnected.value = false
|
||||
isTyping.value = false
|
||||
|
||||
// 显示用户友好的错误信息
|
||||
if (error.userMessage) {
|
||||
addMessage({
|
||||
content: error.userMessage,
|
||||
type: 'ai',
|
||||
sessionId: currentSession.value?.id
|
||||
})
|
||||
}
|
||||
},
|
||||
onStatusChange: (status) => {
|
||||
connectionStatus.value = status
|
||||
@@ -225,7 +312,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const initChat = async () => {
|
||||
// 如果没有会话,创建一个默认会话
|
||||
if (sessions.value.length === 0) {
|
||||
createSession('与开开的对话')
|
||||
await createSession('与开开的对话')
|
||||
}
|
||||
|
||||
// 连接WebSocket
|
||||
|
||||
@@ -41,26 +41,12 @@ export const useUserStore = defineStore('user', () => {
|
||||
const loginWithAuth = async (loginData: LoginRequest) => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await authService.login(loginData)
|
||||
|
||||
console.log('登录API响应:', response)
|
||||
|
||||
// 修复:直接处理后端返回的数据格式 {code: 200, data: {...}}
|
||||
if (response.code === 200 && response.data) {
|
||||
// 使用store的方法来设置token和用户信息,确保响应式更新
|
||||
setToken(response.data.accessToken)
|
||||
setUserInfo(response.data.userInfo)
|
||||
|
||||
console.log('登录成功,用户信息已保存:', response.data.userInfo)
|
||||
console.log('Token已保存:', response.data.accessToken.substring(0, 20) + '...')
|
||||
|
||||
return { code: 200, data: response.data, message: response.message }
|
||||
} else {
|
||||
return { code: response.code || 500, message: response.message || '登录失败' }
|
||||
}
|
||||
const data = await authService.login(loginData)
|
||||
setToken(data.accessToken)
|
||||
setUserInfo(data.userInfo)
|
||||
return data
|
||||
} catch (error: any) {
|
||||
console.error('登录请求失败:', error)
|
||||
return { code: 500, message: error.message || '登录失败' }
|
||||
throw error
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ export interface LoginResponse {
|
||||
|
||||
// 验证码响应
|
||||
export interface CaptchaResponse {
|
||||
key: string
|
||||
image: string
|
||||
expireTime: number
|
||||
captchaKey: string
|
||||
captchaImage: string
|
||||
expiresIn: number
|
||||
}
|
||||
|
||||
// API响应基础结构
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface ChatMessage {
|
||||
type: 'user' | 'ai'
|
||||
timestamp: string
|
||||
sessionId?: string
|
||||
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'failed'
|
||||
error?: string
|
||||
}
|
||||
|
||||
// 聊天会话类型
|
||||
|
||||
@@ -53,13 +53,18 @@ request.interceptors.request.use(
|
||||
// 响应拦截器
|
||||
request.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
console.log('收到响应:', {
|
||||
url: response.config.url,
|
||||
status: response.status,
|
||||
data: response.data
|
||||
})
|
||||
|
||||
return response
|
||||
const { data } = response
|
||||
// 标准后端格式: { code, message, data, timestamp }
|
||||
if (typeof data === 'object' && data !== null && 'code' in data) {
|
||||
if (data.code !== 200) {
|
||||
message.error(data.message || '请求失败')
|
||||
return Promise.reject(new Error(data.message || '请求失败'))
|
||||
}
|
||||
// 只返回data字段, 兼容验证码等所有接口
|
||||
return data.data
|
||||
}
|
||||
// 兼容极特殊情况(如验证码图片流等)
|
||||
return data
|
||||
},
|
||||
(error) => {
|
||||
console.error('响应拦截器错误:', error)
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* WebSocket连接测试工具
|
||||
* 用于测试WebSocket连接和消息发送功能
|
||||
*/
|
||||
|
||||
import webSocketService from '@/services/websocket'
|
||||
|
||||
export class WebSocketTester {
|
||||
private isConnected = false
|
||||
private testResults: string[] = []
|
||||
|
||||
/**
|
||||
* 运行WebSocket连接测试
|
||||
*/
|
||||
async runConnectionTest(): Promise<boolean> {
|
||||
this.testResults = []
|
||||
this.log('开始WebSocket连接测试...')
|
||||
|
||||
try {
|
||||
// 测试连接
|
||||
await webSocketService.connect('test_user_' + Date.now(), {
|
||||
onConnect: () => {
|
||||
this.isConnected = true
|
||||
this.log('✅ WebSocket连接成功')
|
||||
},
|
||||
onDisconnect: () => {
|
||||
this.isConnected = false
|
||||
this.log('❌ WebSocket连接断开')
|
||||
},
|
||||
onError: (error) => {
|
||||
this.log(`❌ WebSocket错误: ${error.userMessage || error.message || '未知错误'}`)
|
||||
},
|
||||
onMessage: (message) => {
|
||||
this.log(`📨 收到消息: ${message.type} - ${message.content}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 等待连接建立
|
||||
await this.waitForConnection(5000)
|
||||
|
||||
if (this.isConnected) {
|
||||
this.log('✅ 连接测试通过')
|
||||
return true
|
||||
} else {
|
||||
this.log('❌ 连接测试失败')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
this.log(`❌ 连接测试异常: ${error}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试消息发送
|
||||
*/
|
||||
async testMessageSending(): Promise<boolean> {
|
||||
if (!this.isConnected) {
|
||||
this.log('❌ 未连接,无法测试消息发送')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
this.log('开始测试消息发送...')
|
||||
|
||||
// 设置测试会话ID
|
||||
webSocketService.setConversationId('test_conversation_' + Date.now())
|
||||
|
||||
// 发送测试消息
|
||||
webSocketService.sendChatMessage('这是一条测试消息')
|
||||
|
||||
this.log('✅ 消息发送成功')
|
||||
return true
|
||||
} catch (error) {
|
||||
this.log(`❌ 消息发送失败: ${error}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接测试
|
||||
*/
|
||||
testDisconnection(): void {
|
||||
this.log('开始测试断开连接...')
|
||||
webSocketService.disconnect()
|
||||
this.log('✅ 断开连接完成')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试结果
|
||||
*/
|
||||
getTestResults(): string[] {
|
||||
return [...this.testResults]
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空测试结果
|
||||
*/
|
||||
clearResults(): void {
|
||||
this.testResults = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录测试日志
|
||||
*/
|
||||
private log(message: string): void {
|
||||
const timestamp = new Date().toLocaleTimeString()
|
||||
const logMessage = `[${timestamp}] ${message}`
|
||||
this.testResults.push(logMessage)
|
||||
console.log(logMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待连接建立
|
||||
*/
|
||||
private waitForConnection(timeout: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const startTime = Date.now()
|
||||
|
||||
const checkConnection = () => {
|
||||
if (this.isConnected) {
|
||||
resolve()
|
||||
} else if (Date.now() - startTime > timeout) {
|
||||
reject(new Error('连接超时'))
|
||||
} else {
|
||||
setTimeout(checkConnection, 100)
|
||||
}
|
||||
}
|
||||
|
||||
checkConnection()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出测试实例
|
||||
export const wsTest = new WebSocketTester()
|
||||
|
||||
// 开发环境下添加到全局对象,方便调试
|
||||
if (import.meta.env.DEV) {
|
||||
(window as any).wsTest = wsTest
|
||||
}
|
||||
@@ -74,7 +74,16 @@
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-text">{{ message.content }}</div>
|
||||
<div class="message-time">{{ formatTime.friendly(message.timestamp) }}</div>
|
||||
<div class="message-meta">
|
||||
<span class="message-time">{{ formatTime.friendly(message.timestamp) }}</span>
|
||||
<span v-if="message.type === 'user' && message.status" class="message-status" :class="message.status">
|
||||
<template v-if="message.status === 'sending'">发送中</template>
|
||||
<template v-else-if="message.status === 'sent'">已发送</template>
|
||||
<template v-else-if="message.status === 'delivered'">已送达</template>
|
||||
<template v-else-if="message.status === 'read'">已读</template>
|
||||
<template v-else-if="message.status === 'failed'">发送失败</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -453,19 +462,61 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-xs;
|
||||
margin-top: $spacing-xs;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: $font-size-xs;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-top: $spacing-xs;
|
||||
|
||||
|
||||
.user-message & {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
|
||||
.message-wrapper:not(.user-message) & {
|
||||
color: $text-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.message-status {
|
||||
font-size: $font-size-xs;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
|
||||
&.sending {
|
||||
color: #faad14;
|
||||
background: rgba(250, 173, 20, 0.1);
|
||||
}
|
||||
|
||||
&.sent {
|
||||
color: #52c41a;
|
||||
background: rgba(82, 196, 26, 0.1);
|
||||
}
|
||||
|
||||
&.delivered {
|
||||
color: #1890ff;
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
|
||||
&.read {
|
||||
color: #722ed1;
|
||||
background: rgba(114, 46, 209, 0.1);
|
||||
}
|
||||
|
||||
&.failed {
|
||||
color: #ff4d4f;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
}
|
||||
|
||||
.user-message & {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
|
||||
@@ -139,8 +139,8 @@
|
||||
try {
|
||||
const response = await authService.getCaptcha()
|
||||
console.log('验证码响应:', response)
|
||||
captchaImage.value = `data:image/png;base64,${response.image}`
|
||||
captchaKey.value = response.key
|
||||
captchaImage.value = response.captchaImage // 修正字段
|
||||
captchaKey.value = response.captchaKey // 修正字段
|
||||
console.log('验证码图片URL:', captchaImage.value.substring(0, 50) + '...')
|
||||
} catch (error) {
|
||||
console.error('获取验证码失败:', error)
|
||||
@@ -161,51 +161,25 @@
|
||||
...values,
|
||||
captchaKey: captchaKey.value
|
||||
}
|
||||
|
||||
const result = await userStore.loginWithAuth(loginData)
|
||||
|
||||
console.log('登录结果:', result)
|
||||
console.log('用户store状态:', {
|
||||
token: userStore.token,
|
||||
userInfo: userStore.userInfo,
|
||||
isLoggedIn: userStore.isLoggedIn
|
||||
})
|
||||
|
||||
// 修复:检查 result.code === 200 而不是 result.success
|
||||
if (result.code === 200) {
|
||||
message.success('登录成功')
|
||||
|
||||
// 等待状态更新后再跳转
|
||||
await nextTick()
|
||||
|
||||
// 跳转到首页或之前的页面
|
||||
const redirect = router.currentRoute.value.query.redirect as string
|
||||
const targetPath = redirect || '/'
|
||||
console.log('准备跳转到:', targetPath)
|
||||
|
||||
// 延迟一下确保状态完全更新
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 使用replace而不是push,避免路由守卫问题
|
||||
router.replace(targetPath).then(() => {
|
||||
console.log('路由跳转完成')
|
||||
}).catch((error) => {
|
||||
console.error('路由跳转失败,使用window.location:', error)
|
||||
// 如果路由跳转失败,使用window.location作为备选
|
||||
window.location.href = targetPath
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('路由跳转异常,使用window.location:', error)
|
||||
const data = await userStore.loginWithAuth(loginData)
|
||||
message.success('登录成功')
|
||||
await nextTick()
|
||||
const redirect = router.currentRoute.value.query.redirect as string
|
||||
const targetPath = redirect || '/'
|
||||
setTimeout(() => {
|
||||
try {
|
||||
router.replace(targetPath).then(() => {
|
||||
console.log('路由跳转完成')
|
||||
}).catch((error) => {
|
||||
window.location.href = targetPath
|
||||
}
|
||||
}, 100)
|
||||
} else {
|
||||
message.error(result.message || '登录失败')
|
||||
refreshCaptcha() // 刷新验证码
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
window.location.href = targetPath
|
||||
}
|
||||
}, 100)
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '登录失败,请稍后重试')
|
||||
refreshCaptcha() // 刷新验证码
|
||||
refreshCaptcha()
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@
|
||||
try {
|
||||
const response = await authService.getCaptcha()
|
||||
console.log('验证码响应:', response)
|
||||
captchaImage.value = `data:image/png;base64,${response.image}`
|
||||
captchaKey.value = response.key
|
||||
captchaImage.value = response.captchaImage // 修正字段
|
||||
captchaKey.value = response.captchaKey // 修正字段
|
||||
console.log('验证码图片URL:', captchaImage.value.substring(0, 50) + '...')
|
||||
} catch (error) {
|
||||
console.error('获取验证码失败:', error)
|
||||
@@ -178,28 +178,14 @@
|
||||
...values,
|
||||
captchaKey: captchaKey.value
|
||||
}
|
||||
|
||||
const response = await authService.register(registerData)
|
||||
|
||||
if (response.success) {
|
||||
message.success('注册成功,已自动登录')
|
||||
|
||||
// 使用userStore的方法保存用户信息和token
|
||||
userStore.setToken(response.data.accessToken)
|
||||
userStore.setUserInfo(response.data.userInfo)
|
||||
|
||||
console.log('注册成功,用户信息:', response.data.userInfo)
|
||||
console.log('Token已保存:', response.data.accessToken.substring(0, 20) + '...')
|
||||
|
||||
// 跳转到首页
|
||||
router.push('/')
|
||||
} else {
|
||||
message.error(response.message || '注册失败')
|
||||
refreshCaptcha() // 刷新验证码
|
||||
}
|
||||
const data = await authService.register(registerData)
|
||||
message.success('注册成功,已自动登录')
|
||||
userStore.setToken(data.accessToken)
|
||||
userStore.setUserInfo(data.userInfo)
|
||||
router.push('/')
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '注册失败,请稍后重试')
|
||||
refreshCaptcha() // 刷新验证码
|
||||
refreshCaptcha()
|
||||
} finally {
|
||||
registerLoading.value = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div class="token-test">
|
||||
<a-card title="Token和身份验证测试">
|
||||
<div class="test-section">
|
||||
<h3>当前状态</h3>
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="登录状态">
|
||||
<a-tag :color="userStore.isLoggedIn ? 'green' : 'red'">
|
||||
{{ userStore.isLoggedIn ? '已登录' : '未登录' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="Token">
|
||||
<a-typography-text :code="true" :copyable="true">
|
||||
{{ userStore.token || '无' }}
|
||||
</a-typography-text>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="用户信息">
|
||||
<pre>{{ JSON.stringify(userStore.userInfo || userStore.user, null, 2) }}</pre>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="WebSocket状态">
|
||||
<a-tag :color="chatStore.wsConnected ? 'green' : 'red'">
|
||||
{{ chatStore.wsConnected ? '已连接' : '未连接' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>操作测试</h3>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-button type="primary" @click="testLogin" :loading="loginLoading">
|
||||
测试登录
|
||||
</a-button>
|
||||
<a-button @click="testWebSocketConnect" :loading="wsLoading">
|
||||
测试WebSocket连接
|
||||
</a-button>
|
||||
<a-button @click="testSendMessage" :disabled="!chatStore.wsConnected">
|
||||
发送测试消息
|
||||
</a-button>
|
||||
<a-button @click="checkLocalStorage">
|
||||
检查本地存储
|
||||
</a-button>
|
||||
<a-button @click="testApiCall" :loading="apiLoading">
|
||||
测试API调用
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>测试结果</h3>
|
||||
<a-textarea
|
||||
v-model:value="testResults"
|
||||
:rows="10"
|
||||
readonly
|
||||
placeholder="测试结果将显示在这里..."
|
||||
/>
|
||||
<a-button @click="clearResults" style="margin-top: 8px">
|
||||
清空结果
|
||||
</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useUserStore, useChatStore } from '@/stores'
|
||||
import { request } from '@/services/api'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const loginLoading = ref(false)
|
||||
const wsLoading = ref(false)
|
||||
const apiLoading = ref(false)
|
||||
const testResults = ref('')
|
||||
|
||||
const addResult = (message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString()
|
||||
testResults.value += `[${timestamp}] ${message}\n`
|
||||
}
|
||||
|
||||
const testLogin = async () => {
|
||||
loginLoading.value = true
|
||||
try {
|
||||
addResult('开始测试登录...')
|
||||
|
||||
const result = await userStore.loginWithAuth({
|
||||
account: 'test@example.com',
|
||||
password: '123456',
|
||||
captcha: '1234'
|
||||
})
|
||||
|
||||
addResult(`登录成功: ${JSON.stringify(result)}`)
|
||||
addResult(`Token: ${userStore.token}`)
|
||||
addResult(`用户信息: ${JSON.stringify(userStore.userInfo)}`)
|
||||
|
||||
} catch (error: any) {
|
||||
addResult(`登录失败: ${error.message}`)
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const testWebSocketConnect = async () => {
|
||||
wsLoading.value = true
|
||||
try {
|
||||
addResult('开始测试WebSocket连接...')
|
||||
|
||||
await chatStore.connectWebSocket()
|
||||
|
||||
addResult(`WebSocket连接状态: ${chatStore.wsConnected}`)
|
||||
addResult(`连接状态: ${chatStore.connectionStatus}`)
|
||||
|
||||
} catch (error: any) {
|
||||
addResult(`WebSocket连接失败: ${error.message}`)
|
||||
} finally {
|
||||
wsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const testSendMessage = async () => {
|
||||
try {
|
||||
addResult('发送测试消息...')
|
||||
|
||||
await chatStore.sendMessage('这是一条测试消息,用于验证用户身份识别')
|
||||
|
||||
addResult('消息发送成功')
|
||||
|
||||
} catch (error: any) {
|
||||
addResult(`消息发送失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const checkLocalStorage = () => {
|
||||
addResult('检查本地存储...')
|
||||
addResult(`localStorage.token: ${localStorage.getItem('token')}`)
|
||||
addResult(`localStorage.userInfo: ${localStorage.getItem('userInfo')}`)
|
||||
}
|
||||
|
||||
const testApiCall = async () => {
|
||||
apiLoading.value = true
|
||||
try {
|
||||
addResult('测试API调用...')
|
||||
|
||||
const response = await request.get('/health')
|
||||
addResult(`API调用成功: ${JSON.stringify(response)}`)
|
||||
|
||||
} catch (error: any) {
|
||||
addResult(`API调用失败: ${error.message}`)
|
||||
} finally {
|
||||
apiLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const clearResults = () => {
|
||||
testResults.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.token-test {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 16px;
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f5f5f5;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -28,7 +28,7 @@ export default defineConfig({
|
||||
open: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
target: 'http://localhost:19089',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user