/** * Mock 拦截器 - 拦截所有 API 请求并返回模拟数据 * 用于无后端情况下的前端界面展示 */ import { mockUsers, mockVideos, mockCategories, mockComments, mockFollowing, mockFollowers, mockFavorites, mockDanmaku, mockCaptcha, mockCurrentUser, mockMessages, mockNotifications, mockSettings, paginate } from './data' // Mock 存储状态 const mockStore = { isLoggedIn: true, currentUserId: 1, videos: [...mockVideos], comments: [...mockComments], userVideos: { 1: [...mockVideos.filter(v => v.userId === 1)], 2: [...mockVideos.filter(v => v.userId === 2)], 3: [...mockVideos.filter(v => v.userId === 3)] }, messages: [...mockMessages], notifications: [...mockNotifications], settings: { ...mockSettings } } // 延迟函数,模拟网络延迟 const delay = (ms: number = 100) => new Promise(resolve => setTimeout(resolve, ms)) // Mock API 处理器映射 const mockHandlers: Record Promise> = { // ==================== 认证相关 ==================== 'GET /auth/captcha': async () => { await delay() return mockCaptcha }, 'POST /auth/login': async () => { await delay(200) return { accessToken: 'mock-access-token-' + Date.now(), refreshToken: 'mock-refresh-token-' + Date.now(), tokenType: 'Bearer', expiresIn: 7200, userId: 1, username: 'testuser' } }, 'POST /auth/register': async () => { await delay(200) return '注册成功' }, 'POST /auth/logout': async () => { await delay() return null }, 'POST /auth/refresh': async () => { await delay() return { accessToken: 'mock-access-token-refreshed-' + Date.now(), refreshToken: 'mock-refresh-token-refreshed-' + Date.now(), tokenType: 'Bearer', expiresIn: 7200, userId: 1, username: 'testuser' } }, 'GET /auth/current': async () => { await delay() return mockCurrentUser }, 'GET /auth/username/:id': async (url: string) => { await delay() const id = parseInt(url.split('/').pop() || '0') const user = mockUsers.find(u => u.id === id) return user?.nickname || '未知用户' }, // ==================== 用户相关 ==================== 'GET /user/profile/current': async () => { await delay() return mockUsers.find(u => u.id === mockStore.currentUserId) || mockUsers[0] }, 'GET /user/profile/public/:id': async (url: string) => { await delay() const id = parseInt(url.split('/').pop() || '0') return mockUsers.find(u => u.id === id) || mockUsers[0] }, 'PUT /user/profile': async (_url: string, options: any) => { await delay(200) const user = mockUsers.find(u => u.id === mockStore.currentUserId) if (user && options.body) { Object.assign(user, options.body) } return null }, 'POST /user/profile/avatar': async () => { await delay(500) return 'https://picsum.photos/100/100?random=' + Date.now() }, 'POST /user/follow': async (_url: string) => { await delay(100) return null }, 'DELETE /user/follow': async (_url: string) => { await delay(100) return null }, 'GET /user/follow/check/:id': async (url: string) => { await delay() const id = parseInt(url.split('/').pop() || '0') return mockFollowing.some(f => f.id === id) }, 'GET /user/follow/following': async (_url: string) => { await delay() return mockFollowing }, 'GET /user/follow/followers': async (_url: string) => { await delay() return mockFollowers }, 'GET /user/message/list': async () => { await delay() return mockStore.messages .filter(item => item.senderId !== mockStore.currentUserId) .sort((a, b) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime()) }, 'GET /user/message/detail/:id': async (url: string) => { await delay() const targetId = parseInt(url.split('/').pop() || '0') return mockStore.messages .filter(item => (item.senderId === targetId && item.receiverId === mockStore.currentUserId) || (item.senderId === mockStore.currentUserId && item.receiverId === targetId) ) .sort((a, b) => new Date(a.createTime).getTime() - new Date(b.createTime).getTime()) }, 'POST /user/message': async (_url: string, options: any) => { await delay(120) const body = options.body || {} const newMessage = { id: mockStore.messages.length + 1, senderId: mockStore.currentUserId, senderNickname: mockUsers.find(u => u.id === mockStore.currentUserId)?.nickname || '用户', senderAvatar: mockUsers.find(u => u.id === mockStore.currentUserId)?.avatar || '', receiverId: body.receiverId, receiverNickname: mockUsers.find(u => u.id === body.receiverId)?.nickname || '用户', receiverAvatar: mockUsers.find(u => u.id === body.receiverId)?.avatar || '', content: body.content, isRead: 1, createTime: new Date().toISOString() } mockStore.messages.push(newMessage) return newMessage }, 'PUT /user/message/read/:id': async () => { await delay() return null }, 'GET /user/message/unread/count': async () => { await delay() return mockStore.messages.filter(item => item.receiverId === mockStore.currentUserId && item.isRead === 0).length }, 'GET /user/notification/list': async (_url: string, options: any) => { await delay() const { type } = options.params || {} if (!type) return mockStore.notifications return mockStore.notifications.filter(item => item.type === type) }, 'PUT /user/notification/read': async () => { await delay() mockStore.notifications.forEach(item => { item.isRead = 1 }) return null }, 'PUT /user/notification/read/:id': async (url: string) => { await delay() const id = parseInt(url.split('/').pop() || '0') const notification = mockStore.notifications.find(item => item.id === id) if (notification) notification.isRead = 1 return null }, 'GET /user/notification/unread/count': async () => { await delay() return mockStore.notifications.filter(item => item.isRead === 0).length }, 'GET /user/setting': async () => { await delay() return mockStore.settings }, 'PUT /user/setting': async (_url: string, options: any) => { await delay() Object.assign(mockStore.settings, options.body || {}) return null }, // ==================== 视频相关 ==================== 'GET /content/video/public/:id': async (url: string) => { await delay() const id = parseInt(url.split('/').pop() || '0') return mockVideos.find(v => v.id === id) || mockVideos[0] }, 'GET /content/video/detail': async (url: string) => { await delay() const id = parseInt(url.split('/').pop() || '0') return mockVideos.find(v => v.id === id) || mockVideos[0] }, 'GET /content/video/public/list': async (_url: string, options: any) => { await delay() const params = options.params || {} let videos = [...mockVideos] // 按分类筛选 if (params.categoryId) { videos = videos.filter(v => v.categoryId === params.categoryId) } // 按关键词搜索 if (params.keyword) { const keyword = params.keyword.toLowerCase() videos = videos.filter(v => v.title.toLowerCase().includes(keyword) || v.description.toLowerCase().includes(keyword) ) } return paginate(videos, params.pageNum || 1, params.pageSize || 20) }, 'GET /content/video/list': async (_url: string, options: any) => { await delay() const params = options.params || {} return paginate(mockVideos, params.pageNum || 1, params.pageSize || 20) }, 'GET /content/video/user/:id': async (url: string, options: any) => { await delay() const userId = parseInt(url.split('/').pop() || '0') const params = options.params || {} const videos = mockVideos.filter(v => v.userId === userId) return paginate(videos, params.pageNum || params.page || 1, params.pageSize || params.size || 20) }, 'POST /content/video': async (_url: string, options: any) => { await delay(300) const body = options.body || {} const newVideo = { id: mockVideos.length + 1, userId: mockStore.currentUserId, title: body.title || '新视频', description: body.description || '', coverUrl: body.coverUrl || 'https://picsum.photos/320/180?random=' + Date.now(), videoUrl: body.videoUrl || '', duration: body.duration || 0, width: body.width || 1920, height: body.height || 1080, format: body.format || 'mp4', size: body.size || 0, categoryId: body.categoryId || 1, status: 0, likeCount: 0, coinCount: 0, favoriteCount: 0, commentCount: 0, viewCount: 0, shareCount: 0, danmakuCount: 0, isRecommend: 0, isTop: 0, publishTime: new Date().toISOString(), createTime: new Date().toISOString(), updateTime: new Date().toISOString(), tagIds: body.tagIds || [], tagNames: [], categoryName: '科技', userNickname: mockUsers.find(u => u.id === mockStore.currentUserId)?.nickname || '用户', userAvatar: mockUsers.find(u => u.id === mockStore.currentUserId)?.avatar || '', isLiked: false, isFavorited: false, isCoined: false } mockVideos.unshift(newVideo) return newVideo }, 'POST /content/video/upload': async () => { await delay(1000) return { videoUrl: 'https://www.w3schools.com/html/mov_bbb.mp4', coverUrl: 'https://picsum.photos/320/180?random=' + Date.now() } }, 'POST /content/video/like': async () => { await delay(100) return null }, 'DELETE /content/video/like': async () => { await delay(100) return null }, 'POST /content/video/coin': async () => { await delay(100) return null }, 'POST /content/video/favorite': async () => { await delay(100) return null }, 'DELETE /content/video/favorite': async () => { await delay(100) return null }, 'POST /content/video/share': async () => { await delay(100) return null }, 'POST /content/video/play': async () => { await delay(50) return null }, // ==================== 分类相关 ==================== 'GET /content/category/tree': async () => { await delay() return mockCategories }, 'GET /content/category/list': async () => { await delay() return mockCategories }, // ==================== 评论相关 ==================== 'GET /comment/public/list': async (_url: string, options: any) => { await delay() const params = options.params || {} const comments = mockComments.filter(c => c.videoId === params.videoId) if (params.sortBy === 'hot') { comments.sort((a, b) => b.likeCount - a.likeCount) } else { comments.sort((a, b) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime()) } return paginate(comments, params.pageNum || 1, params.pageSize || 20) }, 'POST /comment': async (_url: string, options: any) => { await delay(200) const body = options.body || {} const newComment = { id: mockComments.length + 1, videoId: body.videoId, userId: mockStore.currentUserId, parentId: body.parentId || 0, rootId: body.rootId || 0, content: body.content, likeCount: 0, replyCount: 0, isTop: 0, createTime: new Date().toISOString(), userNickname: mockUsers.find(u => u.id === mockStore.currentUserId)?.nickname || '用户', userAvatar: mockUsers.find(u => u.id === mockStore.currentUserId)?.avatar || '', isLiked: false } mockComments.unshift(newComment) return newComment }, 'POST /comment/like': async () => { await delay(100) return null }, 'DELETE /comment/like': async () => { await delay(100) return null }, // ==================== 弹幕相关 ==================== 'GET /content/danmaku/all/:id': async (url: string) => { await delay() const videoId = parseInt(url.split('/').pop() || '0') return mockDanmaku.filter(d => d.videoId === videoId || videoId === 0) }, 'POST /content/danmaku': async (_url: string, _options: any) => { await delay(100) return null }, 'GET /content/danmaku/time': async (_url: string, options: any) => { await delay() const params = options.params || {} return mockDanmaku.filter(d => d.videoId === params.videoId) }, // ==================== 收藏相关 ==================== 'GET /content/favorite/list': async () => { await delay() return mockFavorites }, 'POST /content/favorite': async (_url: string, options: any) => { await delay(100) const body = options.body || {} const newFavorite = { id: mockFavorites.length + 1, userId: mockStore.currentUserId, name: body.name || '新收藏夹', description: body.description || '', coverUrl: '', visibility: body.visibility || 1, videoCount: 0, createTime: new Date().toISOString() } mockFavorites.push(newFavorite) return newFavorite }, 'GET /content/favorite': async (_url: string, options: any) => { await delay() const params = options.params || {} return paginate(mockVideos.slice(0, 5), params.pageNum || 1, params.pageSize || 20) }, 'GET /content/favorite/:id/videos': async (url: string, options: any) => { await delay() const favoriteId = Number(url.split('/').slice(-2, -1)[0] || '1') const params = options.params || {} const base = favoriteId === 1 ? mockVideos.slice(0, 4) : mockVideos.slice(4, 9) return paginate(base, params.pageNum || 1, params.pageSize || 20) }, // ==================== 推荐相关 ==================== 'GET /recommend/home': async (_url: string, options: any) => { await delay() const params = options.params || {} const videos = mockVideos.filter(v => v.isRecommend === 1) const page = params.page || 1 const size = params.size || 20 const start = (page - 1) * size return videos.slice(start, start + size) }, 'GET /recommend/hot': async (_url: string, options: any) => { await delay() const params = options.params || {} const videos = [...mockVideos].sort((a, b) => b.viewCount - a.viewCount) const size = params.size || 20 return videos.slice(0, size) }, 'GET /recommend/similar/:id': async (_url: string, options: any) => { await delay() const params = options.params || {} const size = params.size || params.limit || 10 return mockVideos.slice(0, size) }, // ==================== 搜索相关 ==================== 'GET /search/video': async (_url: string, options: any) => { await delay() const params = options.params || {} let videos = [...mockVideos] if (params.keyword) { const keyword = params.keyword.toLowerCase() videos = videos.filter(v => v.title.toLowerCase().includes(keyword) || v.description.toLowerCase().includes(keyword) ) } return paginate(videos, params.pageNum || 1, params.pageSize || 20) }, // ==================== 用户视频 ==================== 'GET /user/video': async (_url: string, options: any) => { await delay() const userId = mockStore.currentUserId const params = options.params || {} const videos = mockVideos.filter(v => v.userId === userId) return paginate(videos, params.pageNum || 1, params.pageSize || 20) } } // 匹配 Mock 处理器 function matchHandler(url: string, method: string): ((url: string, options: any) => Promise) | null { // 标准化 URL(移除查询参数和 /api 前缀) const normalizedUrl = url.replace(/^\/api/, '').split('?')[0] // 构建匹配模式 const patterns = Object.keys(mockHandlers) for (const pattern of patterns) { const [patternMethod, patternPath] = pattern.split(' ') // 检查方法是否匹配 if (patternMethod !== method.toUpperCase()) continue // 检查路径是否匹配 const patternParts = patternPath.split('/') const urlParts = normalizedUrl.split('/') if (patternParts.length !== urlParts.length) continue let matched = true for (let i = 0; i < patternParts.length; i++) { if (patternParts[i] !== urlParts[i] && !patternParts[i].startsWith(':')) { matched = false break } } if (matched) { return mockHandlers[pattern] } } return null } // Mock 请求处理函数 export async function mockRequest(url: string, options: any = {}): Promise { const method = options.method || 'GET' // 查找匹配的处理器 const handler = matchHandler(url, method) if (handler) { console.log(`[Mock] ${method} ${url}`) try { const result = await handler(url, options) return { code: 200, message: 'success', data: result } } catch (error) { console.error(`[Mock Error] ${method} ${url}`, error) return { code: 500, message: 'Mock 处理错误', data: null } } } // 未找到处理器,返回默认响应 console.warn(`[Mock] No handler for ${method} ${url}`) return { code: 404, message: '未找到对应的 Mock 处理器', data: null } } // 判断是否启用 Mock export function isMockEnabled(): boolean { return import.meta.env.VITE_MOCK === 'true' || import.meta.env.VITE_MOCK === true } // 导出 Mock 开关 export const mockEnabled = isMockEnabled()