Files
happy-life-star/web-new/前端技术方案Augment.md
T

30 KiB
Raw Blame History

情绪博物馆Web端技术方案

技术选型说明

WebSocket通信方案选择

本方案选择 STOMP + 原生WebSocket 而非 Socket.io 的原因:

  1. 后端集成优势: 项目后端使用Spring BootSTOMP是Spring WebSocket的原生支持协议
  2. 标准化协议: STOMP是标准的消息传递协议,不依赖特定实现
  3. 消息队列支持: 天然支持点对点和发布订阅模式,适合聊天和通知场景
  4. 轻量级: 相比Socket.io更轻量,减少前端包体积
  5. Token认证支持: 原生WebSocket支持在握手时传递自定义请求头进行Token认证

技术栈版本策略

  • 稳定性优先: 选择经过验证的稳定版本
  • 生态兼容: 确保各组件间良好兼容
  • 长期支持: 优先选择有LTS支持的版本
  • 性能考虑: 新版本的性能优化和bug修复

1. 核心技术栈(推荐版本)

1.1 前端框架

  • Vue.js: 3.4.21 (最新稳定版)
  • TypeScript: 5.4.2 (最新稳定版)
  • Vite: 5.1.6 (最新稳定版,更好的构建性能)

1.2 UI框架与样式

  • Element Plus: 2.6.1 (最新稳定版,更好的Vue3支持)
  • Tailwind CSS: 3.4.1 (最新稳定版)
  • @tailwindcss/forms: 0.5.7 (表单样式增强)
  • @tailwindcss/typography: 0.5.10 (文本排版增强)

1.3 状态管理与路由

  • Pinia: 2.1.7 (保持现有版本,稳定可靠)
  • Vue Router: 4.3.0 (最新稳定版)
  • @pinia/nuxt: 0.5.1 (如果需要SSR支持)

1.4 HTTP客户端与实时通信

  • Axios: 1.6.8 (最新稳定版)
  • @stomp/stompjs: 7.1.1 (WebSocket通信,与Spring Boot后端集成,支持Token认证)

1.5 数据可视化

  • ECharts: 5.5.0 (最新稳定版)
  • vue-echarts: 6.7.3 (Vue3专用ECharts组件)
  • @antv/g2: 5.1.15 (备选图表库,更现代化)

1.6 工具库

  • Day.js: 1.11.10 (最新稳定版)
  • Lodash-es: 4.17.21 (ES模块版本)
  • Zod: 3.22.4 (数据验证)
  • VueUse: 10.9.0 (Vue组合式API工具集)

1.7 开发工具

  • @vitejs/plugin-vue: 5.0.4
  • @vue/tsconfig: 0.5.1
  • vue-tsc: 2.0.6
  • unplugin-auto-import: 0.17.5 (自动导入)
  • unplugin-vue-components: 0.26.0 (组件自动导入)

2. 新增推荐技术栈

2.1 表单处理

  • @vuelidate/core: 2.0.3 (表单验证)
  • @vuelidate/validators: 2.0.4
  • vue-hooks-form: 0.8.6 (表单状态管理)

2.2 动画与交互

  • @vueuse/motion: 2.0.0 (动画库)
  • vue-toastification: 2.0.0-rc.5 (通知组件)
  • nprogress: 0.2.0 (页面加载进度条)

2.3 文件处理

  • vue-upload-component: 3.1.4 (文件上传)
  • cropperjs: 1.6.1 (图片裁剪)
  • file-saver: 2.0.5 (文件下载)

2.4 富文本编辑

  • @tiptap/vue-3: 2.2.4 (现代富文本编辑器)
  • @tiptap/starter-kit: 2.2.4
  • @tiptap/extension-image: 2.2.4

2.5 PWA支持

  • vite-plugin-pwa: 0.19.2 (PWA支持)
  • workbox-window: 7.0.0 (Service Worker管理)

3. 开发工具与代码质量

3.1 代码规范

  • ESLint: 8.57.0
  • @vue/eslint-config-typescript: 12.0.0
  • @vue/eslint-config-prettier: 9.0.0
  • Prettier: 3.2.5
  • lint-staged: 15.2.2
  • husky: 9.0.11

3.2 测试框架

  • Vitest: 1.4.0 (单元测试)
  • @vue/test-utils: 2.4.5 (Vue组件测试)
  • jsdom: 24.0.0 (DOM环境模拟)
  • Cypress: 13.7.1 (E2E测试)

3.3 构建优化

  • rollup-plugin-visualizer: 5.12.0 (构建分析)
  • vite-plugin-compression: 0.5.1 (Gzip压缩)
  • vite-plugin-mock: 3.0.1 (Mock数据)

4. 项目结构设计

src/
├── api/                    # API接口定义
│   ├── auth.ts            # 认证相关接口
│   ├── chat.ts            # 聊天相关接口
│   ├── diary.ts           # 日记相关接口
│   └── user.ts            # 用户相关接口
├── assets/                # 静态资源
│   ├── images/            # 图片资源
│   ├── icons/             # 图标资源
│   └── styles/            # 全局样式
├── components/            # 公共组件
│   ├── common/            # 通用组件
│   ├── forms/             # 表单组件
│   ├── charts/            # 图表组件
│   └── layout/            # 布局组件
├── composables/           # 组合式API
│   ├── useAuth.ts         # 认证逻辑
│   ├── useChat.ts         # 聊天逻辑
│   ├── useWebSocket.ts    # WebSocket逻辑
│   └── useApi.ts          # API调用逻辑
├── config/                # 配置文件
│   ├── env.ts             # 环境配置
│   ├── constants.ts       # 常量定义
│   └── routes.ts          # 路由配置
├── layouts/               # 页面布局
│   ├── DefaultLayout.vue  # 默认布局
│   ├── AuthLayout.vue     # 认证布局
│   └── ChatLayout.vue     # 聊天布局
├── pages/                 # 页面组件
│   ├── auth/              # 认证页面
│   ├── chat/              # 聊天页面
│   ├── diary/             # 日记页面
│   └── dashboard/         # 仪表盘页面
├── stores/                # 状态管理
│   ├── auth.ts            # 认证状态
│   ├── chat.ts            # 聊天状态
│   ├── user.ts            # 用户状态
│   └── app.ts             # 应用状态
├── types/                 # 类型定义
│   ├── api.ts             # API类型
│   ├── user.ts            # 用户类型
│   ├── chat.ts            # 聊天类型
│   └── global.d.ts        # 全局类型
├── utils/                 # 工具函数
│   ├── request.ts         # HTTP请求工具
│   ├── websocket.ts       # WebSocket工具
│   ├── storage.ts         # 存储工具
│   ├── validation.ts      # 验证工具
│   └── format.ts          # 格式化工具
└── views/                 # 页面视图
    ├── Home.vue           # 首页
    ├── Login.vue          # 登录页
    ├── Chat.vue           # 聊天页
    └── Dashboard.vue      # 仪表盘

5. 核心功能实现方案

5.1 认证系统

  • JWT Token管理: 使用Pinia存储,自动刷新机制
  • 路由守卫: 基于Vue Router的权限控制
  • 第三方登录: 支持微信、QQ、GitHub等
  • 验证码: 图形验证码 + 短信验证码

5.2 实时通信

  • STOMP协议: 基于@stomp/stompjs,与Spring Boot WebSocket集成
  • 原生WebSocket: 支持Token认证,无需降级方案
  • 连接管理: 自动重连、心跳检测、连接状态监控
  • 消息队列: 支持点对点和发布订阅模式
  • Token认证: 在WebSocket握手时传递Authorization头部
  • 消息类型: 文本、图片、表情、文件、系统通知
  • 离线处理: 离线消息缓存和同步机制

5.3 数据可视化

  • 情绪趋势图: 基于ECharts的时间序列图
  • 情绪雷达图: 多维度情绪分析
  • 成长轨迹: 交互式时间轴
  • 数据导出: 支持PDF、Excel导出

5.4 响应式设计

  • 移动端适配: 基于Tailwind CSS的响应式布局
  • 触摸手势: 支持滑动、缩放等手势操作
  • PWA支持: 离线缓存、桌面安装
  • 性能优化: 虚拟滚动、懒加载

6. 环境配置优化

6.1 多环境配置

// config/env.ts
export const envConfigs = {
  local: {
    name: '本地环境',
    apiBaseUrl: 'http://localhost:19089/api',
    wsBaseUrl: 'ws://localhost:19089',
    uploadUrl: 'http://localhost:19089/api/upload',
    debug: true,
    mock: false
  },
  dev: {
    name: '开发环境',
    apiBaseUrl: 'https://dev-api.emotion-museum.com/api',
    wsBaseUrl: 'wss://dev-api.emotion-museum.com',
    uploadUrl: 'https://dev-api.emotion-museum.com/api/upload',
    debug: true,
    mock: false
  },
  test: {
    name: '测试环境',
    apiBaseUrl: 'https://test-api.emotion-museum.com/api',
    wsBaseUrl: 'wss://test-api.emotion-museum.com',
    uploadUrl: 'https://test-api.emotion-museum.com/api/upload',
    debug: false,
    mock: false
  },
  prod: {
    name: '生产环境',
    apiBaseUrl: 'https://api.emotion-museum.com/api',
    wsBaseUrl: 'wss://api.emotion-museum.com',
    uploadUrl: 'https://api.emotion-museum.com/api/upload',
    debug: false,
    mock: false
  }
}

6.2 构建优化配置

// vite.config.ts
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
      dts: true
    }),
    Components({
      resolvers: [ElementPlusResolver()],
      dts: true
    }),
    VitePWA({
      registerType: 'autoUpdate',
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg}']
      }
    })
  ],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          elementPlus: ['element-plus'],
          echarts: ['echarts'],
          utils: ['axios', 'dayjs', 'lodash-es']
        }
      }
    }
  }
})

7. 部署方案

7.1 Docker部署

# Dockerfile
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

7.2 CI/CD配置

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run build
      - run: npm run test
      - name: Deploy to server
        run: |
          # 部署脚本

7.3 Nginx配置

# nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # 处理Vue Router的history模式
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # API代理
    location /api/ {
        proxy_pass http://backend:19089/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # WebSocket代理
    location /ws/ {
        proxy_pass http://backend:19089/ws/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

8. 性能优化策略

8.1 代码分割

  • 路由级别懒加载: 使用动态import()
  • 组件级别动态导入: 按需加载大型组件
  • 第三方库按需加载: Tree-shaking优化
  • 图片懒加载: Intersection Observer API

8.2 缓存策略

  • HTTP缓存配置: 静态资源长期缓存
  • Service Worker缓存: 离线访问支持
  • 本地存储优化: IndexedDB存储大量数据
  • CDN加速: 静态资源CDN分发

8.3 监控与分析

  • Sentry: 7.108.0 (错误监控)
  • Google Analytics: gtag (用户行为分析)
  • Web Vitals: 3.5.2 (性能指标监控)
  • Bundle Analyzer: 构建分析工具

8.4 性能指标

  • 首屏加载时间: < 2秒
  • 交互响应时间: < 100ms
  • 代码分割: 单个chunk < 250KB
  • 图片优化: WebP格式,响应式图片

9. 安全考虑

9.1 前端安全

  • XSS防护: 内容安全策略(CSP)
  • CSRF防护: Token验证
  • 敏感信息加密: 本地存储加密
  • 依赖安全: 定期更新依赖包

9.2 API安全

  • Token自动刷新: JWT令牌管理
  • 请求签名验证: HMAC签名
  • 接口限流: 防止恶意请求
  • 数据加密传输: HTTPS强制

9.3 内容安全策略

<!-- CSP配置 -->
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' 'unsafe-inline' 'unsafe-eval';
               style-src 'self' 'unsafe-inline';
               img-src 'self' data: https:;
               connect-src 'self' wss: https:;">

10. 开发规范

10.1 代码规范

  • 命名规范:
    • 组件: PascalCase (UserProfile.vue)
    • 文件: kebab-case (user-profile.ts)
    • 变量: camelCase (userName)
    • 常量: UPPER_SNAKE_CASE (API_BASE_URL)

10.2 Git提交规范

feat: 新功能
fix: 修复bug
docs: 文档更新
style: 代码格式调整
refactor: 代码重构
test: 测试相关
chore: 构建工具或辅助工具的变动

10.3 组件开发规范

<template>
  <!-- 模板内容 -->
</template>

<script setup lang="ts">
// 导入
import { ref, computed, onMounted } from 'vue'
import type { User } from '@/types/user'

// Props定义
interface Props {
  user: User
  readonly?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  readonly: false
})

// Emits定义
interface Emits {
  update: [user: User]
  delete: [id: string]
}

const emit = defineEmits<Emits>()

// 响应式数据
const loading = ref(false)

// 计算属性
const displayName = computed(() => {
  return props.user.nickname || props.user.username
})

// 生命周期
onMounted(() => {
  // 初始化逻辑
})
</script>

<style scoped>
/* 组件样式 */
</style>

10.4 API接口规范

// api/user.ts
import type { User, UserProfile, UpdateUserRequest } from '@/types/user'
import { request } from '@/utils/request'

export const userApi = {
  // 获取用户信息
  getProfile(): Promise<UserProfile> {
    return request.get('/user/profile')
  },

  // 更新用户信息
  updateProfile(data: UpdateUserRequest): Promise<User> {
    return request.put('/user/profile', data)
  },

  // 上传头像
  uploadAvatar(file: File): Promise<{ url: string }> {
    const formData = new FormData()
    formData.append('avatar', file)
    return request.post('/user/avatar/upload', formData, {
      headers: { 'Content-Type': 'multipart/form-data' }
    })
  }
}

10.5 WebSocket实现方案

为什么选择STOMP而不是Socket.io

  1. 后端集成: 项目后端使用Spring BootSTOMP是Spring WebSocket的标准协议
  2. 消息队列: STOMP天然支持消息队列模式,适合聊天和通知场景
  3. 标准化: STOMP是标准协议,不依赖特定实现
  4. 轻量级: 相比Socket.io更轻量,减少包体积

WebSocket工具类实现

// utils/websocket.ts
import { Client } from '@stomp/stompjs'
import { envConfig } from '@/config/env'

export class WebSocketService {
  private client: Client
  private connected = false
  private reconnectAttempts = 0
  private maxReconnectAttempts = 5
  private currentToken = ''

  constructor() {
    this.client = new Client({
      // 使用原生WebSocket,支持Token认证
      brokerURL: `${envConfig.wsBaseUrl}/ws`,

      // 心跳检测
      heartbeatIncoming: 4000,
      heartbeatOutgoing: 4000,

      // 重连配置
      reconnectDelay: 5000,

      // 调试模式
      debug: envConfig.debug ? console.log : undefined,

      onConnect: () => {
        this.connected = true
        this.reconnectAttempts = 0
        console.log('WebSocket连接成功')
      },

      onDisconnect: () => {
        this.connected = false
        console.log('WebSocket连接断开')
      },

      onStompError: (frame) => {
        console.error('STOMP错误:', frame)
        this.handleReconnect()
      },

      // WebSocket连接前的配置
      beforeConnect: () => {
        // 在WebSocket握手时添加Token
        if (this.currentToken) {
          this.client.configure({
            connectHeaders: {
              Authorization: `Bearer ${this.currentToken}`,
              'X-Requested-With': 'XMLHttpRequest'
            }
          })
        }
      }
    })
  }

  // 连接WebSocket
  connect(token: string) {
    this.currentToken = token
    this.client.configure({
      connectHeaders: {
        Authorization: `Bearer ${token}`,
        'X-Requested-With': 'XMLHttpRequest'
      }
    })
    this.client.activate()
  }

  // 更新Token(用于Token刷新场景)
  updateToken(newToken: string) {
    this.currentToken = newToken
    if (this.connected) {
      // 断开当前连接
      this.disconnect()
      // 使用新Token重新连接
      setTimeout(() => {
        this.connect(newToken)
      }, 1000)
    }
  }

  // 断开连接
  disconnect() {
    this.client.deactivate()
  }

  // 订阅消息
  subscribe(destination: string, callback: (message: any) => void) {
    if (!this.connected) {
      console.warn('WebSocket未连接')
      return
    }

    return this.client.subscribe(destination, (message) => {
      try {
        const data = JSON.parse(message.body)
        callback(data)
      } catch (error) {
        console.error('消息解析失败:', error)
      }
    })
  }

  // 发送消息
  send(destination: string, body: any) {
    if (!this.connected) {
      console.warn('WebSocket未连接,消息将被缓存')
      // 这里可以实现消息缓存逻辑
      return
    }

    this.client.publish({
      destination,
      body: JSON.stringify(body)
    })
  }

  // 处理重连
  private handleReconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++
      setTimeout(() => {
        console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
        this.client.activate()
      }, 5000 * this.reconnectAttempts)
    }
  }
}

聊天功能使用示例

// composables/useChat.ts
import { ref, onMounted, onUnmounted } from 'vue'
import { WebSocketService } from '@/utils/websocket'
import { useAuthStore } from '@/stores/auth'

export function useChat() {
  const authStore = useAuthStore()
  const wsService = new WebSocketService()
  const messages = ref<any[]>([])
  const isConnected = ref(false)

  // 连接WebSocket
  const connect = () => {
    if (authStore.token) {
      wsService.connect(authStore.token)

      // 订阅个人消息
      wsService.subscribe(`/user/${authStore.user.id}/queue/messages`, (message) => {
        messages.value.push(message)
      })

      // 订阅聊天室消息
      wsService.subscribe('/topic/chat', (message) => {
        messages.value.push(message)
      })

      isConnected.value = true
    }
  }

  // 发送消息
  const sendMessage = (content: string, type: 'text' | 'image' = 'text') => {
    const message = {
      content,
      type,
      timestamp: Date.now(),
      userId: authStore.user.id
    }

    wsService.send('/app/chat.send', message)
  }

  // 组件挂载时连接
  onMounted(() => {
    connect()
  })

  // 组件卸载时断开连接
  onUnmounted(() => {
    wsService.disconnect()
  })

  return {
    messages,
    isConnected,
    sendMessage,
    connect
  }
}

10.6 WebSocket Token认证详解

Token传递方式

原生WebSocket + STOMP协议支持Token认证:

  1. WebSocket握手时传递 (推荐)
// 在WebSocket连接建立时传递Token
brokerURL: `${envConfig.wsBaseUrl}/ws`,
connectHeaders: {
  Authorization: `Bearer ${token}`,
  'X-Requested-With': 'XMLHttpRequest'
}
  1. URL参数传递 (备选方案)
// 如果后端不支持连接头部,可以通过URL传递
brokerURL: `${envConfig.wsBaseUrl}/ws?token=${token}`

为什么移除SockJS

  • SockJS不支持在WebSocket握手时传递自定义请求头
  • 无法在连接建立时进行Token认证
  • 只能通过URL参数传递Token,安全性较低
  • 原生WebSocket完全支持Token认证
  • 现代浏览器WebSocket支持度已经很好

WebSocket方案对比

特性 原生WebSocket + STOMP SockJS + STOMP Socket.io
Token认证 支持请求头传递 不支持请求头 支持
浏览器兼容 现代浏览器完全支持 兼容性最好 兼容性好
包体积 最小 ⚠️ 中等 最大
Spring Boot集成 原生支持 原生支持 需要额外配置
标准化 标准协议 标准协议 私有协议
安全性 握手时认证 ⚠️ URL参数认证 握手时认证

最终选择:原生WebSocket + STOMP

  • 🎯 安全性优先: 支持在握手时进行Token认证
  • 🎯 性能最优: 无额外协议层,直接使用WebSocket
  • 🎯 标准化: 基于标准STOMP协议
  • 🎯 轻量级: 最小的包体积
  • 🎯 兼容性: 现代浏览器支持度已经足够

Token刷新处理

// stores/auth.ts - 认证状态管理
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { WebSocketService } from '@/utils/websocket'

export const useAuthStore = defineStore('auth', () => {
  const token = ref<string>('')
  const user = ref<any>(null)
  const wsService = ref<WebSocketService | null>(null)

  // 监听Token变化,自动更新WebSocket连接
  watch(token, (newToken, oldToken) => {
    if (newToken && newToken !== oldToken && wsService.value) {
      wsService.value.updateToken(newToken)
    }
  })

  // 登录
  const login = async (credentials: LoginRequest) => {
    try {
      const response = await authApi.login(credentials)
      token.value = response.token
      user.value = response.user

      // 登录成功后建立WebSocket连接
      if (!wsService.value) {
        wsService.value = new WebSocketService()
      }
      wsService.value.connect(token.value)

      return response
    } catch (error) {
      throw error
    }
  }

  // 登出
  const logout = () => {
    // 断开WebSocket连接
    if (wsService.value) {
      wsService.value.disconnect()
      wsService.value = null
    }

    token.value = ''
    user.value = null
  }

  // Token刷新
  const refreshToken = async () => {
    try {
      const response = await authApi.refreshToken()
      token.value = response.token
      // watch会自动处理WebSocket重连
      return response
    } catch (error) {
      // Token刷新失败,执行登出
      logout()
      throw error
    }
  }

  return {
    token,
    user,
    wsService,
    login,
    logout,
    refreshToken
  }
})

后端WebSocket安全配置

对应的Spring Boot后端配置示例:

// WebSocketConfig.java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/queue");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*");
                // 移除.withSockJS(),使用原生WebSocket支持Token认证
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new AuthChannelInterceptor());
    }
}

// AuthChannelInterceptor.java - Token验证拦截器
@Component
public class AuthChannelInterceptor implements ChannelInterceptor {

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);

        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String token = accessor.getFirstNativeHeader("Authorization");
            if (token != null && token.startsWith("Bearer ")) {
                String jwt = token.substring(7);
                // 验证JWT Token
                if (jwtTokenProvider.validateToken(jwt)) {
                    String userId = jwtTokenProvider.getUserIdFromToken(jwt);
                    accessor.setUser(new StompPrincipal(userId));
                } else {
                    throw new IllegalArgumentException("Invalid token");
                }
            } else {
                throw new IllegalArgumentException("Missing token");
            }
        }

        return message;
    }
}

错误处理和重连机制

// 增强的WebSocket错误处理
export class WebSocketService {
  private tokenExpiredCallback?: () => void

  constructor(onTokenExpired?: () => void) {
    this.tokenExpiredCallback = onTokenExpired
    // ... 其他初始化代码
  }

  private handleStompError(frame: any) {
    console.error('STOMP错误:', frame)

    // 检查是否是Token相关错误
    if (frame.headers && frame.headers.message) {
      const errorMessage = frame.headers.message.toLowerCase()

      if (errorMessage.includes('unauthorized') ||
          errorMessage.includes('invalid token') ||
          errorMessage.includes('token expired')) {

        console.warn('Token认证失败,触发重新登录')
        this.tokenExpiredCallback?.()
        return
      }
    }

    // 其他错误进行重连
    this.handleReconnect()
  }
}

// 在应用中使用
const authStore = useAuthStore()
const wsService = new WebSocketService(() => {
  // Token过期回调
  authStore.logout()
  router.push('/login')
})

安全最佳实践

  1. Token验证: 每次WebSocket连接都验证Token有效性
  2. 权限控制: 基于用户角色限制订阅和发送权限
  3. 连接限制: 限制单用户的并发连接数
  4. 消息加密: 敏感消息内容加密传输
  5. 审计日志: 记录WebSocket连接和消息日志

这个方案完全支持Token认证,并且提供了完整的Token生命周期管理,包括刷新、过期处理和安全重连机制。

11. 测试策略

11.1 单元测试

// tests/components/UserProfile.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UserProfile from '@/components/UserProfile.vue'

describe('UserProfile', () => {
  it('renders user information correctly', () => {
    const user = {
      id: '1',
      username: 'testuser',
      nickname: 'Test User',
      avatar: 'https://example.com/avatar.jpg'
    }

    const wrapper = mount(UserProfile, {
      props: { user }
    })

    expect(wrapper.text()).toContain('Test User')
    expect(wrapper.find('img').attributes('src')).toBe(user.avatar)
  })
})

11.2 E2E测试

// cypress/e2e/auth.cy.ts
describe('Authentication', () => {
  it('should login successfully', () => {
    cy.visit('/login')
    cy.get('[data-cy=username]').type('testuser')
    cy.get('[data-cy=password]').type('password123')
    cy.get('[data-cy=login-btn]').click()

    cy.url().should('include', '/dashboard')
    cy.get('[data-cy=user-menu]').should('be.visible')
  })
})

11.3 测试配置

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./tests/setup.ts']
  },
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

12. 国际化支持

12.1 i18n配置

  • vue-i18n: 9.10.2 (国际化支持)
  • @intlify/unplugin-vue-i18n: 4.0.0 (构建时优化)
// i18n/index.ts
import { createI18n } from 'vue-i18n'
import zh from './locales/zh.json'
import en from './locales/en.json'

export const i18n = createI18n({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  messages: {
    zh,
    en
  }
})

12.2 语言文件结构

// i18n/locales/zh.json
{
  "common": {
    "confirm": "确认",
    "cancel": "取消",
    "save": "保存",
    "delete": "删除"
  },
  "auth": {
    "login": "登录",
    "register": "注册",
    "logout": "退出登录"
  },
  "chat": {
    "sendMessage": "发送消息",
    "typing": "正在输入...",
    "offline": "离线"
  }
}

13. 移动端适配

13.1 响应式断点

/* tailwind.config.js */
module.exports = {
  theme: {
    screens: {
      'xs': '475px',
      'sm': '640px',
      'md': '768px',
      'lg': '1024px',
      'xl': '1280px',
      '2xl': '1536px',
    }
  }
}

13.2 移动端优化

  • 触摸优化: 44px最小触摸目标
  • 手势支持: 滑动、缩放、长按
  • 性能优化: 虚拟滚动、图片懒加载
  • 离线支持: Service Worker缓存

13.3 PWA配置

// vite.config.ts PWA配置
VitePWA({
  registerType: 'autoUpdate',
  includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
  manifest: {
    name: '情绪博物馆',
    short_name: '情绪博物馆',
    description: '记录情绪,分享心情的温暖空间',
    theme_color: '#4A90E2',
    background_color: '#ffffff',
    display: 'standalone',
    icons: [
      {
        src: 'pwa-192x192.png',
        sizes: '192x192',
        type: 'image/png'
      },
      {
        src: 'pwa-512x512.png',
        sizes: '512x512',
        type: 'image/png'
      }
    ]
  }
})

14. 总结

本技术方案基于现代化的Vue3生态系统,采用TypeScript提供类型安全,使用Vite构建工具提升开发体验。方案涵盖了:

14.1 技术优势

  • 现代化技术栈: Vue3 + TypeScript + Vite
  • 完整的工具链: 从开发到部署的全流程支持
  • 性能优化: 代码分割、懒加载、缓存策略
  • 开发体验: 热更新、自动导入、类型检查

14.2 可扩展性

  • 模块化设计: 清晰的项目结构和职责分离
  • 组件化开发: 可复用的UI组件库
  • 插件系统: 支持功能扩展和第三方集成
  • 国际化支持: 多语言适配能力

14.3 维护性

  • 代码规范: ESLint + Prettier统一代码风格
  • 测试覆盖: 单元测试 + E2E测试
  • 文档完善: 组件文档和API文档
  • 版本管理: 语义化版本控制

14.4 部署方案

  • 多环境支持: local/dev/test/prod环境配置
  • 容器化部署: Docker + Nginx部署方案
  • CI/CD流程: 自动化构建和部署
  • 监控告警: 错误监控和性能分析

这个技术方案能够很好地支持情绪博物馆Web端的所有功能需求,同时具备良好的可维护性和扩展性,为项目的长期发展奠定坚实基础。