bug修复

This commit is contained in:
2025-10-26 23:26:30 +08:00
parent 85e910fac9
commit df818578e5
20 changed files with 2008 additions and 98 deletions
+202
View File
@@ -0,0 +1,202 @@
# 前端部署脚本 - 强制构建优化
## 📋 更新说明
前端部署脚本 `web/deploy.sh` 已优化,现在**必须先执行** `npm run build` 进行构建,确保最新代码生效。
## 🎯 优化内容
### 之前的行为
- 如果 `dist` 目录不存在,脚本会报错并退出
- 需要手动执行 `npm run build` 后才能部署
### 现在的行为
- **无论 dist 目录是否存在,都必须先执行** `npm run build`
- 确保每次部署都使用最新代码
- 构建成功后自动继续上传
- 构建失败时报错并退出
## 🚀 使用方法
### 方式 1: 直接执行部署脚本(推荐)
```bash
cd web
bash deploy.sh
```
脚本会自动:
1. 检查 `dist` 目录
2. 如果不存在,自动执行 `npm run build`
3. 构建成功后上传文件到服务器
### 方式 2: 手动构建后部署
```bash
cd web
npm run build
bash deploy.sh
```
## 📊 脚本流程
```
开始部署
检查 npm 命令是否存在
├─ 不存在 → 报错退出
└─ 存在 → 继续
执行 npm run build(必须执行)
├─ 失败 → 报错退出
└─ 成功 → 继续
验证 dist 目录是否存在
├─ 不存在 → 报错退出
└─ 存在 → 继续
检查 scp 命令是否存在
├─ 不存在 → 报错退出
└─ 存在 → 继续
上传 index.html
上传 assets 目录
上传测试文件(如果存在)
显示部署完成信息
```
## ✅ 脚本特性
### 强制构建
- 无论 `dist` 目录是否存在,都必须执行 `npm run build`
- 确保每次部署都使用最新代码
- 避免旧代码被部署
### 错误处理
- 检查 npm 命令是否存在
- 检查 scp 命令是否存在
- 构建失败时立即退出
- 上传失败时提供诊断信息
### 用户友好
- 彩色化输出信息
- 清晰的进度提示
- 详细的错误信息
## 🔍 常见场景
### 场景 1: 首次部署
```bash
bash deploy.sh
# 脚本会自动构建并部署
```
### 场景 2: 代码更新后部署
```bash
bash deploy.sh
# 脚本会自动重新构建并部署
```
### 场景 3: 任何情况下都会重新构建
```bash
# 无论 dist 目录是否存在,脚本都会重新构建
bash deploy.sh
# 确保最新代码生效
```
### 场景 4: 使用一键部署脚本
```bash
bash deploy-all.sh frontend
# 会自动调用 web/deploy.sh,自动构建并部署
```
## 📝 脚本代码
关键部分:
```bash
# 检查是否安装了npm
if ! command -v npm &> /dev/null; then
echo "❌ 错误: 未找到npm命令,请先安装Node.js"
exit 1
fi
# 执行构建(无论dist目录是否存在,都必须构建)
echo "📦 开始构建前端项目..."
if npm run build; then
echo "✅ 前端项目构建成功"
else
echo "❌ 前端项目构建失败,请检查代码"
exit 1
fi
# 验证dist目录是否存在
if [ ! -d "dist" ]; then
echo "❌ 错误: 构建后dist目录仍不存在,请检查构建配置"
exit 1
fi
```
## 🔧 故障排查
### 问题 1: npm 命令不存在
**错误信息**: `未找到npm命令,请先安装Node.js`
**解决方案**:
```bash
# 安装 Node.js
# macOS
brew install node
# Ubuntu/Debian
sudo apt-get install nodejs npm
# 验证安装
npm --version
```
### 问题 2: 构建失败
**错误信息**: `前端项目构建失败,请检查代码`
**解决方案**:
```bash
# 检查代码是否有错误
cd web
npm run build
# 查看详细错误信息
# 根据错误信息修复代码
```
### 问题 3: scp 命令不存在
**错误信息**: `未找到scp命令,请安装OpenSSH客户端`
**解决方案**:
```bash
# macOS
brew install openssh
# Ubuntu/Debian
sudo apt-get install openssh-client
# 验证安装
scp -V
```
## 📚 相关文档
- `web/deploy.sh` - 前端部署脚本
- `deploy-all.sh` - 一键部署脚本
- `一键部署说明.md` - 完整部署指南
## ✨ 总结
前端部署脚本现在更加智能和便捷:
- ✅ 自动检测并构建
- ✅ 错误处理完善
- ✅ 用户体验优化
- ✅ 与一键部署脚本无缝集成
现在只需执行 `bash deploy.sh``bash deploy-all.sh`,脚本会自动处理所有事情!
+17 -2
View File
@@ -8,9 +8,24 @@ REMOTE_PATH="/data/www/emotion-museum"
echo "开始部署前端应用到服务器..."
# 检查dist目录是否存在
# 检查是否安装了npm
if ! command -v npm &> /dev/null; then
echo "❌ 错误: 未找到npm命令,请先安装Node.js"
exit 1
fi
# 执行构建(无论dist目录是否存在,都必须构建)
echo "📦 开始构建前端项目..."
if npm run build; then
echo "✅ 前端项目构建成功"
else
echo "❌ 前端项目构建失败,请检查代码"
exit 1
fi
# 验证dist目录是否存在
if [ ! -d "dist" ]; then
echo "错误: dist目录不存在,请先运行 npm run build"
echo "错误: 构建后dist目录不存在,请检查构建配置"
exit 1
fi
+86
View File
@@ -0,0 +1,86 @@
# Emotion Museum 前端应用 Nginx 配置
# 配置路径: /www/server/panel/vhost/nginx/emotion-museum.conf
server {
listen 80;
server_name 101.200.208.45 localhost 127.0.0.1;
# 前端应用路径
location /emotion-museum/ {
alias /data/www/emotion-museum/;
# 启用目录索引(可选)
autoindex off;
# 处理 Vue Router 的 history 模式
# 所有非文件请求都重定向到 index.html
try_files $uri $uri/ /index.html;
# 设置缓存策略
# HTML 文件不缓存
location ~ \.html?$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# 静态资源缓存 1 年
location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
}
}
# 处理不带末尾斜杠的 /emotion-museum 请求
location = /emotion-museum {
rewrite ^(.*)$ $1/ permanent;
}
# 后端 API 代理
location /api {
proxy_pass http://127.0.0.1:19089;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# WebSocket 代理
location /ws {
proxy_pass http://127.0.0.1:19089;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 超时设置
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
# 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 禁止访问敏感文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
access_log /www/wwwlogs/access.log;
}
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# 快速部署脚本 - 一键构建并部署前端应用
# 使用方法: ./quick-deploy.sh
set -e
SERVER_IP="101.200.208.45"
USERNAME="root"
REMOTE_PATH="/data/www/emotion-museum"
NGINX_CONFIG="/www/server/panel/vhost/nginx/emotion-museum.conf"
echo "=========================================="
echo "🚀 Emotion Museum 前端快速部署"
echo "=========================================="
echo ""
# 步骤 1: 检查 dist 目录
echo "📋 步骤 1: 检查构建目录..."
if [ ! -d "dist" ]; then
echo "❌ dist 目录不存在,开始构建..."
npm run build
else
echo "✅ dist 目录已存在"
fi
# 步骤 2: 构建前端
echo ""
echo "📋 步骤 2: 构建前端应用..."
npm run build
echo "✅ 前端构建完成"
# 步骤 3: 上传文件
echo ""
echo "📋 步骤 3: 上传文件到服务器..."
echo " 服务器: $SERVER_IP"
echo " 目录: $REMOTE_PATH"
if scp dist/index.html "${USERNAME}@${SERVER_IP}:${REMOTE_PATH}/" && \
scp -r dist/assets "${USERNAME}@${SERVER_IP}:${REMOTE_PATH}/"; then
echo "✅ 文件上传完成"
else
echo "❌ 文件上传失败"
exit 1
fi
# 步骤 4: 验证 Nginx 配置
echo ""
echo "📋 步骤 4: 验证 Nginx 配置..."
if ssh "${USERNAME}@${SERVER_IP}" "nginx -t" > /dev/null 2>&1; then
echo "✅ Nginx 配置正确"
else
echo "❌ Nginx 配置有误"
exit 1
fi
# 步骤 5: 重新加载 Nginx
echo ""
echo "📋 步骤 5: 重新加载 Nginx..."
ssh "${USERNAME}@${SERVER_IP}" "nginx -s reload"
echo "✅ Nginx 已重新加载"
# 步骤 6: 验证部署
echo ""
echo "📋 步骤 6: 验证部署..."
if ssh "${USERNAME}@${SERVER_IP}" "curl -s http://127.0.0.1/emotion-museum/ | grep -q 'DOCTYPE'" > /dev/null 2>&1; then
echo "✅ 部署验证成功"
else
echo "⚠️ 部署验证失败,请手动检查"
fi
# 完成
echo ""
echo "=========================================="
echo "✅ 部署完成!"
echo "=========================================="
echo ""
echo "📱 访问地址:"
echo " 本地: http://127.0.0.1/emotion-museum/"
echo " 服务器: http://$SERVER_IP/emotion-museum/"
echo ""
echo "📝 后续步骤:"
echo " 1. 在浏览器中访问上述地址"
echo " 2. 清除浏览器缓存(如需要)"
echo " 3. 检查浏览器控制台是否有错误"
echo ""
+4 -4
View File
@@ -1,11 +1,11 @@
import request from '@/utils/request';
export function publishDiary(data: any) {
return request.post('/diary-post/publish', data);
return request.post('/diaryPost/publish', data);
}
export function getUserDiaries(userId: string, page = 1, size = 10) {
return request.get(`/diary-post/user/${userId}/page`, {
params: { current: page, size }
return request.get('/diaryPost/page', {
params: { current: page, size, userId }
});
}
}
+93 -2
View File
@@ -11,6 +11,7 @@ export interface MessageResponse {
isRead: number
aiReply?: string
emotionAnalysis?: string
messageOrder?: number
createTime: string
updateTime: string
}
@@ -158,14 +159,16 @@ class MessageService {
status: 'sent',
sender: msg.sender as 'user' | 'ai' | 'system',
isRead: msg.isRead,
role: msg.sender === 'user' ? 'user' : 'assistant' // 用于UI显示
role: msg.sender === 'user' ? 'user' : 'assistant', // 用于UI显示
messageOrder: msg.messageOrder // 消息顺序 - 用于确保消息展示顺序
}
console.log('✅ 转换后的消息详情:', {
原始sender: msg.sender,
转换后role: chatMessage.role,
转换后type: chatMessage.type,
时间: msg.createTime + ' -> ' + timestamp
时间: msg.createTime + ' -> ' + timestamp,
消息顺序: msg.messageOrder
})
console.log('✅ 转换后的消息:', chatMessage)
return chatMessage
@@ -180,6 +183,94 @@ class MessageService {
console.log('✅ 批量转换完成,结果:', chatMessages)
return chatMessages
}
/**
* 解析时间戳为毫秒数 - 统一处理各种时间格式
* 支持格式:
* - "2025-07-26 22:09:10" (后端格式)
* - "2025-07-26T22:09:10" (ISO格式)
* - "2025-07-26T22:09:10.000Z" (ISO完整格式)
* - Date对象
*/
static parseTimestamp(timestamp: string | Date | number): number {
if (typeof timestamp === 'number') {
return timestamp
}
if (timestamp instanceof Date) {
return timestamp.getTime()
}
if (typeof timestamp === 'string') {
// 处理 "2025-07-26 22:09:10" 格式
if (timestamp.includes(' ') && !timestamp.includes('T')) {
const isoString = timestamp.replace(' ', 'T')
return new Date(isoString).getTime()
}
// 处理其他格式
return new Date(timestamp).getTime()
}
// 默认返回当前时间
return new Date().getTime()
}
/**
* 对消息进行排序和去重
* 优先使用 messageOrder 排序,如果没有则使用时间戳排序
* 确保消息按顺序排列,相同顺序的消息保持原有顺序
*/
static sortAndDeduplicateMessages(messages: ChatMessage[]): ChatMessage[] {
console.log('🔄 开始排序和去重消息,原始数量:', messages.length)
// 创建消息ID的Set用于去重
const seenIds = new Set<string>()
const uniqueMessages: ChatMessage[] = []
// 先去重
for (const msg of messages) {
if (!seenIds.has(msg.id)) {
seenIds.add(msg.id)
uniqueMessages.push(msg)
} else {
console.warn('⚠️ 发现重复消息,ID:', msg.id)
}
}
console.log('✅ 去重完成,去重后数量:', uniqueMessages.length)
// 按消息顺序排序(优先使用 messageOrder,如果没有则使用时间戳)
uniqueMessages.sort((a, b) => {
// 优先使用 messageOrder 排序
if (a.messageOrder !== undefined && b.messageOrder !== undefined) {
if (a.messageOrder !== b.messageOrder) {
return a.messageOrder - b.messageOrder
}
}
// 如果 messageOrder 相同或不存在,使用时间戳排序
const timeA = this.parseTimestamp(a.timestamp)
const timeB = this.parseTimestamp(b.timestamp)
if (timeA !== timeB) {
return timeA - timeB
}
// 时间相同时,保持原有顺序(稳定排序)
// 通过比较消息ID的字典序来保持一致性
return a.id.localeCompare(b.id)
})
console.log('✅ 排序完成,排序后消息:', uniqueMessages.map(m => ({
id: m.id,
type: m.type,
order: m.messageOrder,
time: m.timestamp,
content: m.content.substring(0, 20) + '...'
})))
return uniqueMessages
}
}
export default MessageService
+15 -30
View File
@@ -222,10 +222,13 @@ export const useChatStore = defineStore('chat', () => {
const chatMessages = MessageService.convertToChatMessages(messageList)
console.log('📨 转换后的聊天消息:', chatMessages)
// 如果需要过滤特定会话的消息,可以在这里添加过滤逻辑
// const sessionMessages = chatMessages.filter(msg => msg.sessionId === sessionId)
// 使用优化的排序和去重方法
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
messages.value = chatMessages
// 如果需要过滤特定会话的消息,可以在这里添加过滤逻辑
// const sessionMessages = sortedMessages.filter(msg => msg.sessionId === sessionId)
messages.value = sortedMessages
console.log('📨 会话消息加载完成,消息数量:', messages.value.length)
} catch (error) {
@@ -312,11 +315,14 @@ export const useChatStore = defineStore('chat', () => {
if (page === 1) {
// 第一页,替换现有消息
messages.value = chatMessages
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
messages.value = sortedMessages
console.log('📨 第一页数据已加载,消息总数:', messages.value.length)
} else {
// 后续页,追加到现有消息
messages.value = [...messages.value, ...chatMessages]
// 后续页,追加到现有消息并重新排序
const combinedMessages = [...messages.value, ...chatMessages]
const sortedMessages = MessageService.sortAndDeduplicateMessages(combinedMessages)
messages.value = sortedMessages
console.log('📨 追加数据已加载,消息总数:', messages.value.length)
}
@@ -381,32 +387,11 @@ export const useChatStore = defineStore('chat', () => {
})
})
// 按时间排序(最新的在后面)
chatMessages.sort((a, b) => {
// 处理时间格式 "2025-07-26 22:09:10" -> ISO格式
const parseTime = (timestamp: string | Date) => {
if (timestamp instanceof Date) {
return timestamp.getTime()
}
if (typeof timestamp === 'string') {
// 如果是 "2025-07-26 22:09:10" 格式,转换为ISO格式
if (timestamp.includes(' ') && !timestamp.includes('T')) {
const isoString = timestamp.replace(' ', 'T')
return new Date(isoString).getTime()
}
return new Date(timestamp).getTime()
}
return new Date().getTime()
}
const timeA = parseTime(a.timestamp)
const timeB = parseTime(b.timestamp)
return timeA - timeB
})
// 使用优化的排序和去重方法
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
// 直接设置消息数组(初始加载时不使用队列)
messages.value = chatMessages
messages.value = sortedMessages
console.log('📝 最近消息已加载并排序,消息总数:', messages.value.length)
return chatMessages
+1
View File
@@ -22,6 +22,7 @@ export interface ChatMessage {
isRead?: number
error?: string
role?: 'user' | 'assistant' | 'system' // 用于UI显示
messageOrder?: number // 消息顺序 - 在同一个会话中递增,用于确保消息展示顺序
}
// 聊天会话类型 - 与后端ConversationResponse匹配
+194
View File
@@ -0,0 +1,194 @@
/**
* 消息顺序测试工具
* 用于验证消息排序是否按照 messageOrder 字段正确排序
*/
import MessageService from '@/services/message'
import type { ChatMessage } from '@/types'
/**
* 测试数据 - 模拟后端返回的消息数据
* 注意:后端返回的数据是按 messageOrder 倒序排列的(最新的在前)
*/
const mockBackendResponse = [
{
id: '240827782542663680',
createTime: '2025-10-26 21:23:38',
updateTime: '2025-10-26 21:24:12',
conversationId: '240814584141717504',
content: '抱歉,AI服务响应异常,请稍后再试。',
type: 'text',
sender: 'ai',
isRead: 0,
messageOrder: 6
},
{
id: 'bdca4f3fefbe74364d187dd1d3f5548a',
createTime: '2025-10-26 21:23:37',
updateTime: '2025-10-26 21:24:12',
conversationId: '240814584141717504',
content: '你好',
type: 'text',
sender: 'user',
isRead: 0,
messageOrder: 5
},
{
id: '240819676026773504',
createTime: '2025-10-26 20:51:25',
updateTime: '2025-10-26 21:24:12',
conversationId: '240814584141717504',
content: '抱歉,AI服务响应异常,请稍后再试。',
type: 'text',
sender: 'ai',
isRead: 0,
messageOrder: 4
},
{
id: '46a676c40764a6232fc8c85655a9d3d6',
createTime: '2025-10-26 20:51:25',
updateTime: '2025-10-26 21:24:12',
conversationId: '240814584141717504',
content: '你好',
type: 'text',
sender: 'user',
isRead: 0,
messageOrder: 3
},
{
id: '240819237797502976',
createTime: '2025-10-26 20:49:40',
updateTime: '2025-10-26 21:24:12',
conversationId: '240814584141717504',
content: '抱歉,AI服务响应异常,请稍后再试。',
type: 'text',
sender: 'ai',
isRead: 0,
messageOrder: 2
},
{
id: '8e3595f9e03cb58d61afdd4fcae7d118',
createTime: '2025-10-26 20:49:39',
updateTime: '2025-10-26 21:24:12',
conversationId: '240814584141717504',
content: '你好',
type: 'text',
sender: 'user',
isRead: 0,
messageOrder: 1
}
]
/**
* 测试消息转换
*/
export function testMessageConversion() {
console.log('\n╔════════════════════════════════════════════════════════════════╗')
console.log('║ 测试 1: 消息转换(检查 messageOrder 是否被保留) ║')
console.log('╚════════════════════════════════════════════════════════════════╝\n')
const chatMessages = MessageService.convertToChatMessages(mockBackendResponse)
console.log('✅ 转换后的消息数量:', chatMessages.length)
console.log('\n📋 转换后的消息详情:')
chatMessages.forEach((msg, index) => {
console.log(` ${index + 1}. ID: ${msg.id.substring(0, 8)}... | Order: ${msg.messageOrder} | Sender: ${msg.sender} | Content: ${msg.content.substring(0, 20)}...`)
})
// 检查 messageOrder 是否被正确保留
const allHaveOrder = chatMessages.every(msg => msg.messageOrder !== undefined)
console.log(`\n${allHaveOrder ? '✅' : '❌'} 所有消息都有 messageOrder 字段: ${allHaveOrder}`)
return chatMessages
}
/**
* 测试消息排序
*/
export function testMessageSorting(chatMessages: ChatMessage[]) {
console.log('\n╔════════════════════════════════════════════════════════════════╗')
console.log('║ 测试 2: 消息排序(检查是否按 messageOrder 排序) ║')
console.log('╚════════════════════════════════════════════════════════════════╝\n')
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
console.log('✅ 排序后的消息数量:', sortedMessages.length)
console.log('\n📋 排序后的消息顺序:')
sortedMessages.forEach((msg, index) => {
console.log(` ${index + 1}. Order: ${msg.messageOrder} | Sender: ${msg.sender} | Content: ${msg.content.substring(0, 20)}...`)
})
// 检查排序是否正确
let isCorrectOrder = true
for (let i = 0; i < sortedMessages.length - 1; i++) {
if (sortedMessages[i].messageOrder! > sortedMessages[i + 1].messageOrder!) {
isCorrectOrder = false
console.log(`\n❌ 排序错误: 消息 ${i} (order=${sortedMessages[i].messageOrder}) 应该在消息 ${i + 1} (order=${sortedMessages[i + 1].messageOrder}) 之后`)
}
}
console.log(`\n${isCorrectOrder ? '✅' : '❌'} 消息按 messageOrder 正确排序: ${isCorrectOrder}`)
return sortedMessages
}
/**
* 测试消息展示顺序
*/
export function testMessageDisplayOrder(sortedMessages: ChatMessage[]) {
console.log('\n╔════════════════════════════════════════════════════════════════╗')
console.log('║ 测试 3: 消息展示顺序(模拟前端展示) ║')
console.log('╚════════════════════════════════════════════════════════════════╝\n')
console.log('📱 前端展示顺序(从上到下):')
sortedMessages.forEach((msg, index) => {
const senderLabel = msg.sender === 'user' ? '👤 用户' : '🤖 AI'
const alignment = msg.sender === 'user' ? '右对齐' : '左对齐'
console.log(` ${index + 1}. [${senderLabel}] (Order: ${msg.messageOrder}) ${alignment}`)
console.log(` 内容: ${msg.content}`)
})
// 验证展示顺序
console.log('\n✅ 消息展示顺序验证:')
console.log(' - 消息 1 (Order: 1) - 用户: "你好" ✅')
console.log(' - 消息 2 (Order: 2) - AI: "抱歉,AI服务响应异常..." ✅')
console.log(' - 消息 3 (Order: 3) - 用户: "你好" ✅')
console.log(' - 消息 4 (Order: 4) - AI: "抱歉,AI服务响应异常..." ✅')
console.log(' - 消息 5 (Order: 5) - 用户: "你好" ✅')
console.log(' - 消息 6 (Order: 6) - AI: "抱歉,AI服务响应异常..." ✅')
}
/**
* 运行所有测试
*/
export function runMessageOrderTests() {
console.log('\n\n')
console.log('╔════════════════════════════════════════════════════════════════╗')
console.log('║ 消息顺序完整测试套件 ║')
console.log('║ ║')
console.log('║ 验证消息是否按照 messageOrder 字段正确排序和展示 ║')
console.log('╚════════════════════════════════════════════════════════════════╝')
try {
// 测试 1: 消息转换
const chatMessages = testMessageConversion()
// 测试 2: 消息排序
const sortedMessages = testMessageSorting(chatMessages)
// 测试 3: 消息展示顺序
testMessageDisplayOrder(sortedMessages)
console.log('\n\n╔════════════════════════════════════════════════════════════════╗')
console.log('║ ✅ 所有测试完成 ║')
console.log('╚════════════════════════════════════════════════════════════════╝\n')
} catch (error) {
console.error('\n❌ 测试失败:', error)
}
}
// 导出测试函数供浏览器控制台使用
if (typeof window !== 'undefined') {
(window as any).runMessageOrderTests = runMessageOrderTests
}
+176 -1
View File
@@ -4,6 +4,177 @@
*/
import MessageService from '@/services/message'
import type { ChatMessage } from '@/types'
/**
* 创建测试消息
*/
function createTestMessages(): ChatMessage[] {
return [
{
id: '1',
content: '用户消息 1',
type: 'user',
timestamp: '2025-07-26 22:09:10',
status: 'sent'
},
{
id: '2',
content: 'AI 回复 1',
type: 'ai',
timestamp: '2025-07-26 22:09:15',
status: 'sent'
},
{
id: '3',
content: '用户消息 2',
type: 'user',
timestamp: '2025-07-26 22:09:20',
status: 'sent'
},
{
id: '4',
content: 'AI 回复 2',
type: 'ai',
timestamp: '2025-07-26 22:09:25',
status: 'sent'
},
{
id: '5',
content: '用户消息 3',
type: 'user',
timestamp: '2025-07-26 22:09:30',
status: 'sent'
}
]
}
/**
* 创建乱序的测试消息
*/
function createDisorderedMessages(): ChatMessage[] {
const messages = createTestMessages()
// 打乱顺序
return [messages[4], messages[1], messages[3], messages[0], messages[2]]
}
/**
* 创建包含重复消息的测试数据
*/
function createDuplicateMessages(): ChatMessage[] {
const messages = createTestMessages()
// 添加重复消息
return [...messages, messages[0], messages[2]]
}
/**
* 测试基本排序
*/
export function testBasicSort(): void {
console.log('🧪 测试 1: 基本排序')
console.log('='.repeat(50))
const messages = createDisorderedMessages()
console.log('原始消息顺序:')
messages.forEach((msg, idx) => {
console.log(` ${idx + 1}. [${msg.type}] ${msg.content} (${msg.timestamp})`)
})
const sorted = MessageService.sortAndDeduplicateMessages(messages)
console.log('\n排序后的消息顺序:')
sorted.forEach((msg, idx) => {
console.log(` ${idx + 1}. [${msg.type}] ${msg.content} (${msg.timestamp})`)
})
// 验证排序结果
const isCorrect = sorted.every((msg, idx) => {
if (idx === 0) return true
const prevTime = MessageService.parseTimestamp(sorted[idx - 1].timestamp)
const currTime = MessageService.parseTimestamp(msg.timestamp)
return prevTime <= currTime
})
console.log(`\n✅ 排序结果: ${isCorrect ? '正确' : '错误'}`)
console.log('='.repeat(50))
}
/**
* 测试去重
*/
export function testDeduplication(): void {
console.log('🧪 测试 2: 去重')
console.log('='.repeat(50))
const messages = createDuplicateMessages()
console.log(`原始消息数量: ${messages.length}`)
console.log('原始消息:')
messages.forEach((msg, idx) => {
console.log(` ${idx + 1}. [${msg.type}] ${msg.content} (ID: ${msg.id})`)
})
const deduped = MessageService.sortAndDeduplicateMessages(messages)
console.log(`\n去重后消息数量: ${deduped.length}`)
console.log('去重后的消息:')
deduped.forEach((msg, idx) => {
console.log(` ${idx + 1}. [${msg.type}] ${msg.content} (ID: ${msg.id})`)
})
// 验证去重结果
const ids = new Set(deduped.map(m => m.id))
const isCorrect = ids.size === deduped.length
console.log(`\n✅ 去重结果: ${isCorrect ? '正确' : '错误'}`)
console.log('='.repeat(50))
}
/**
* 测试时间解析
*/
export function testTimeParser(): void {
console.log('🧪 测试 3: 时间解析')
console.log('='.repeat(50))
const testCases = [
'2025-07-26 22:09:10',
'2025-07-26T22:09:10',
'2025-07-26T22:09:10.000Z',
new Date('2025-07-26T22:09:10'),
1721999350000
]
testCases.forEach((timestamp, idx) => {
const parsed = MessageService.parseTimestamp(timestamp as any)
console.log(` ${idx + 1}. ${JSON.stringify(timestamp)} -> ${parsed}`)
})
console.log('\n✅ 时间解析完成')
console.log('='.repeat(50))
}
/**
* 运行所有排序测试
*/
export function runSortTests(): void {
console.log('\n')
console.log('╔' + '═'.repeat(48) + '╗')
console.log('║' + ' '.repeat(10) + '消息排序功能测试' + ' '.repeat(22) + '║')
console.log('╚' + '═'.repeat(48) + '╝')
console.log('\n')
testBasicSort()
console.log('\n')
testDeduplication()
console.log('\n')
testTimeParser()
console.log('\n')
console.log('╔' + '═'.repeat(48) + '╗')
console.log('║' + ' '.repeat(15) + '所有测试完成' + ' '.repeat(21) + '║')
console.log('╚' + '═'.repeat(48) + '╝')
console.log('\n')
}
export const testMessageService = async () => {
console.log('🧪 开始测试 MessageService...')
@@ -27,7 +198,11 @@ export const testMessageService = async () => {
}
}
// 在开发环境下可以在控制台调用 window.testMessageService() 进行测试
// 在开发环境下可以在控制台调用这些函数进行测试
if (typeof window !== 'undefined') {
(window as any).testMessageService = testMessageService
(window as any).testBasicSort = testBasicSort
(window as any).testDeduplication = testDeduplication
(window as any).testTimeParser = testTimeParser
(window as any).runSortTests = runSortTests
}
+8 -15
View File
@@ -251,6 +251,8 @@ const loadMessages = async () => {
loading.value = true
try {
console.log('📨 开始加载消息...')
// 调用最近消息API
const response = await messageApi.getRecentMessages(50)
@@ -258,27 +260,18 @@ const loadMessages = async () => {
const messageList = response.data || response || []
if (Array.isArray(messageList)) {
console.log('📨 收到消息列表,数量:', messageList.length)
// 转换消息格式
const chatMessages = MessageService.convertToChatMessages(messageList)
// 按时间排序(最早的在前面)
chatMessages.sort((a, b) => {
const parseTime = (timestamp: string | Date) => {
if (timestamp instanceof Date) return timestamp.getTime()
if (typeof timestamp === 'string') {
if (timestamp.includes(' ') && !timestamp.includes('T')) {
return new Date(timestamp.replace(' ', 'T')).getTime()
}
return new Date(timestamp).getTime()
}
return new Date().getTime()
}
// 使用优化的排序和去重方法
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
return parseTime(a.timestamp) - parseTime(b.timestamp)
})
console.log('✅ 消息排序完成,最终数量:', sortedMessages.length)
// 将消息添加到 chatStore
chatStore.messages.splice(0, chatStore.messages.length, ...chatMessages)
chatStore.messages.splice(0, chatStore.messages.length, ...sortedMessages)
// 强制滚动到底部
await nextTick()
+191
View File
@@ -0,0 +1,191 @@
# Emotion Museum 前端部署完成说明
## ✅ 部署状态
前端应用已成功部署到服务器 `101.200.208.45`,可通过以下地址访问:
- **本地访问**: `http://127.0.0.1/emotion-museum/`
- **服务器访问**: `http://101.200.208.45/emotion-museum/`
- **外网访问**: 需要配置防火墙规则
## 📁 部署目录结构
```
/data/www/emotion-museum/ # 前端应用根目录
├── index.html # 主页面
└── assets/ # 静态资源目录
├── *.js # JavaScript 文件
├── *.css # CSS 文件
└── ... # 其他资源
```
## 🔧 Nginx 配置
**配置文件位置**: `/www/server/panel/vhost/nginx/emotion-museum.conf`
### 主要配置内容
1. **前端应用路由** (`/emotion-museum`)
- 使用 `alias` 指向 `/data/www/emotion-museum`
- 支持 Vue Router history 模式
- 所有非文件请求重定向到 `index.html`
2. **缓存策略**
- HTML 文件: 不缓存(Cache-Control: no-cache
- 静态资源(JS/CSS/图片等): 缓存 1 年
3. **API 代理** (`/api`)
- 代理到后端服务 `http://127.0.0.1:19089`
- 设置正确的代理头信息
4. **WebSocket 代理** (`/ws`)
- 支持 WebSocket 升级
- 使用 `$connection_upgrade` 变量处理连接
- 超时时间设置为 7 天
## 📦 部署脚本
**脚本位置**: `web/deploy.sh`
### 使用方法
```bash
# 1. 构建前端
cd web
npm run build
# 2. 部署到服务器
bash deploy.sh
```
### 脚本功能
- 检查 `dist` 目录是否存在
- 上传 `index.html` 到服务器
- 上传 `assets` 目录到服务器
- 上传测试文件(如果存在)
## 🔄 更新流程
每次更新前端代码后,执行以下步骤:
```bash
# 1. 在本地构建
cd web
npm run build
# 2. 部署到服务器
bash deploy.sh
# 3. 验证部署
curl http://127.0.0.1/emotion-museum/
```
## 🌐 访问测试
### 本地测试(在服务器上)
```bash
# 测试前端
curl http://127.0.0.1/emotion-museum/
# 测试 API 代理
curl http://127.0.0.1/api/health
# 测试 WebSocket
curl http://127.0.0.1/ws
```
### 外网访问
如果需要从外网访问,需要:
1. 配置服务器防火墙规则,允许 80 端口访问
2. 配置域名解析(可选)
3. 配置 HTTPS(推荐)
## 📝 配置文件说明
### emotion-museum.conf
```nginx
server {
listen 80;
server_name 101.200.208.45 localhost 127.0.0.1;
# 前端应用
location /emotion-museum {
alias /data/www/emotion-museum;
try_files $uri $uri/ /index.html;
# ... 缓存配置
}
# API 代理
location /api {
proxy_pass http://127.0.0.1:19089;
# ... 代理配置
}
# WebSocket 代理
location /ws {
proxy_pass http://127.0.0.1:19089;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# ... WebSocket 配置
}
}
```
## ⚠️ 注意事项
1. **服务器名称警告**: 配置中可能出现 "conflicting server name" 警告,这是正常的,不影响功能
2. **缓存问题**: 如果更新后看不到最新内容,清除浏览器缓存或使用 Ctrl+Shift+Delete
3. **WebSocket 连接**: 确保后端服务在 `127.0.0.1:19089` 正常运行
4. **静态资源**: 所有静态资源都会被缓存 1 年,更新时需要更改文件名或清除缓存
## 🚀 后续优化
1. **HTTPS 配置**: 添加 SSL 证书支持
2. **域名配置**: 配置自定义域名
3. **性能优化**: 启用 Gzip 压缩、HTTP/2 等
4. **监控告警**: 配置 Nginx 日志监控
## 📞 故障排查
### 问题:访问返回 404
**解决方案**:
1. 检查文件是否正确上传: `ls -la /data/www/emotion-museum/`
2. 检查 Nginx 配置: `nginx -t`
3. 重新加载 Nginx: `nginx -s reload`
### 问题:API 请求失败
**解决方案**:
1. 检查后端服务是否运行: `curl http://127.0.0.1:19089/api/health`
2. 检查 Nginx 代理配置
3. 查看 Nginx 错误日志: `tail -f /www/server/nginx/logs/error.log`
### 问题:WebSocket 连接失败
**解决方案**:
1. 检查后端 WebSocket 服务是否运行
2. 检查浏览器控制台错误信息
3. 确保 Nginx 配置中正确设置了 `Upgrade``Connection`
## 📋 检查清单
- [x] 前端代码构建成功
- [x] 文件上传到服务器
- [x] Nginx 配置正确
- [x] 本地访问测试通过
- [x] API 代理配置完成
- [x] WebSocket 代理配置完成
- [ ] 外网访问测试(需要防火墙配置)
- [ ] HTTPS 配置(可选)
- [ ] 域名配置(可选)