# 数据传输助手 - 开发文档 ## 拆分文档目录(01/02/03…) - [01 - 整体架构与核心概念](docs/01-整体架构与核心概念.md) - [02 - 房间管理(创建 / 加入 / 退出)](docs/02-房间管理(创建-加入-退出).md) - [03 - WebSocket连接管理(连接 / 订阅 / 心跳 / 重连)](docs/03-WebSocket连接管理(连接-订阅-心跳-重连).md) - [04 - 消息协议与消息分发(统一Message模型)](docs/04-消息协议与消息分发(统一Message模型).md) - [05 - 文本传输(含大文本分片)](docs/05-文本传输(含大文本分片).md) - [06 - 文件传输(拖拽 / 分片 / 进度 / 下载)](docs/06-文件传输(拖拽-分片-进度-下载).md) - [07 - 图片预览(Base64内联显示)](docs/07-图片预览(Base64内联显示).md) - [08 - 剪贴板集成(读取 / 粘贴 / 降级)](docs/08-剪贴板集成(读取-粘贴-降级).md) - [09 - 在线用户列表(房间成员与系统消息)](docs/09-在线用户列表(房间成员与系统消息).md) - [10 - 历史记录(本地存储 / 清空 / 导出)](docs/10-历史记录(本地存储-清空-导出).md) - [11 - 数据库设计(可选:持久化与审计)](docs/11-数据库设计(可选:持久化与审计).md) - [12 - 部署与环境配置(后端 / 前端 / Nginx)](docs/12-部署与环境配置(后端-前端-Nginx).md) - [13 - 安全与风控(基础措施与可选增强)](docs/13-安全与风控(基础措施与可选增强).md) - [14 - 开发计划与里程碑(Phase 1~4)](docs/14-开发计划与里程碑(Phase1-4).md) ## 一、项目概述 ### 1.1 项目背景 在VNC远程桌面、内网环境或安全隔离场景中,由于剪贴板共享被禁用或系统限制,用户无法直接进行复制粘贴操作。本工具提供一个基于浏览器的轻量级数据传输方案,通过WebSocket实现多端实时数据同步。 ### 1.2 应用场景 - VNC/远程桌面环境的数据传输 - 内外网隔离环境下的文件/文本交换 - 临时性的跨设备数据共享 - 无法使用U盘或即时通讯工具的场景 ### 1.3 技术栈 | 层级 | 技术 | 版本建议 | |------|------|----------| | 后端 | Spring Boot | 2.7.x / 3.x | | 后端 | Java | 11 / 17 | | 实时通信 | WebSocket (STOMP) | - | | 前端 | Vue | 3.x | | UI组件库 | Element Plus | 2.x | | 构建工具 | Vite | 4.x | --- ## 二、系统架构 ### 2.1 架构图 ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 发送端浏览器 │◄───►│ WebSocket │◄───►│ 接收端浏览器 │ │ (Vue + Element)│ │ 服务器(Java) │ │ (Vue + Element)│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ └───────────────────────┴───────────────────────┘ 同一房间号(room) ``` ### 2.2 核心概念 - **房间(Room)**:数据传输的隔离空间,通过6位数字房间号标识 - **会话(Session)**:WebSocket连接实例 - **消息(Message)**:传输的数据单元,支持文本、文件元数据、二进制分片 --- ## 三、功能设计 ### 3.1 功能模块 ``` ├── 房间管理 │ ├── 创建房间(自动生成6位房间号) │ ├── 加入房间(输入房间号) │ └── 退出房间 │ ├── 数据传输 │ ├── 文本传输(支持大文本,自动分片) │ ├── 文件传输(支持拖拽上传、进度显示) │ ├── 图片预览(Base64内联显示) │ └── 剪贴板读取(读取系统剪贴板内容) │ ├── 连接管理 │ ├── 在线用户列表 │ ├── 连接状态指示 │ └── 心跳保活 │ └── 历史记录 ├── 消息历史(本地存储) ├── 清空记录 └── 导出记录 ``` ### 3.2 页面布局 ``` ┌─────────────────────────────────────────────────────────────┐ │ 数据传输助手 [连接状态] │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌───────────────────────────────────────┐ │ │ │ 房间信息 │ │ 消息展示区 │ │ │ │ ───────── │ │ ┌─────────────────────────────────┐ │ │ │ │ 房间号: │ │ │ [系统] xxx 加入了房间 │ │ │ │ │ 123456 │ │ │ [我] 这是一段文本消息... │ │ │ │ │ │ │ │ [对方] 收到,这是回复... │ │ │ │ │ ───────── │ │ │ [文件] 文档.pdf (2.5MB) [下载] │ │ │ │ │ 在线用户 │ │ └─────────────────────────────────┘ │ │ │ │ ● 用户A │ │ │ │ │ │ ○ 用户B │ └───────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ ───────── │ │ [粘贴/拖拽区域] │ │ │ │ 快捷操作 │ │ 支持: 文本粘贴、文件拖拽、剪贴板读取 │ │ │ │ [清空] │ └───────────────────────────────────────┘ │ │ │ [导出] │ ┌────────────────────┐ ┌──────────────┐ │ │ │ └─────────────┘ │ 输入框... │ │ 发送 [▶] │ │ │ │ └────────────────────┘ └──────────────┘ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 四、数据库设计 ### 4.1 实体设计(可选,支持消息持久化) ```sql -- 房间表 CREATE TABLE room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_code VARCHAR(6) UNIQUE NOT NULL COMMENT '房间号', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP COMMENT '过期时间', is_active TINYINT DEFAULT 1 ); -- 消息记录表(可选,用于审计或消息回放) CREATE TABLE message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_code VARCHAR(6) NOT NULL, sender_id VARCHAR(64) NOT NULL, sender_name VARCHAR(32), msg_type TINYINT COMMENT '1-文本 2-文件 3-图片', content TEXT COMMENT '文本内容或文件元数据JSON', file_size BIGINT COMMENT '文件大小', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_room_time (room_code, created_at) ); ``` --- ## 五、接口设计 ### 5.1 WebSocket 端点 | 端点 | 说明 | |------|------| | `/ws/data-transfer` | WebSocket连接入口 | | `/topic/room/{roomCode}` | 房间广播频道(订阅) | | `/app/room/{roomCode}/join` | 加入房间(发送) | | `/app/room/{roomCode}/leave` | 离开房间(发送) | | `/app/room/{roomCode}/message` | 发送消息(发送) | | `/app/room/{roomCode}/file/chunk` | 发送文件分片(发送) | ### 5.2 消息协议 #### 基础消息格式 ```json { "type": "TEXT | FILE | IMAGE | SYSTEM | CHUNK", "senderId": "uuid", "senderName": "用户昵称", "timestamp": 1706345600000, "payload": {} } ``` #### 文本消息 ```json { "type": "TEXT", "senderId": "uuid", "senderName": "用户A", "timestamp": 1706345600000, "payload": { "content": "要传输的文本内容", "isChunk": false, "chunkIndex": 0, "totalChunks": 1 } } ``` #### 文件元数据消息 ```json { "type": "FILE", "senderId": "uuid", "senderName": "用户A", "timestamp": 1706345600000, "payload": { "fileId": "uuid", "fileName": "document.pdf", "fileSize": 2621440, "mimeType": "application/pdf", "totalChunks": 10 } } ``` #### 文件分片消息 ```json { "type": "CHUNK", "senderId": "uuid", "payload": { "fileId": "uuid", "chunkIndex": 0, "data": "base64EncodedChunkData" } } ``` #### 系统消息 ```json { "type": "SYSTEM", "payload": { "event": "USER_JOIN | USER_LEAVE | ERROR", "message": "xxx 加入了房间", "userList": [{"id": "uuid", "name": "用户A"}] } } ``` --- ## 六、后端实现 ### 6.1 项目结构 ``` data-transfer-server/ ├── src/main/java/com/example/datatransfer/ │ ├── config/ │ │ ├── WebSocketConfig.java # WebSocket配置 │ │ └── CorsConfig.java # 跨域配置 │ ├── controller/ │ │ └── WebSocketController.java # WebSocket消息处理器 │ ├── service/ │ │ ├── RoomService.java # 房间管理 │ │ ├── MessageService.java # 消息处理 │ │ └── FileTransferService.java # 文件传输管理 │ ├── model/ │ │ ├── Message.java # 消息实体 │ │ ├── Room.java # 房间实体 │ │ └── FileChunk.java # 文件分片 │ ├── handler/ │ │ └── CustomHandshakeHandler.java # 握手处理器 │ └── DataTransferApplication.java ├── src/main/resources/ │ └── application.yml └── pom.xml ``` ### 6.2 核心代码示例 #### WebSocketConfig.java ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/data-transfer") .setAllowedOriginPatterns("*") .withSockJS(); } } ``` #### WebSocketController.java ```java @Controller public class WebSocketController { @Autowired private SimpMessagingTemplate messagingTemplate; @Autowired private RoomService roomService; // 加入房间 @MessageMapping("/room/{roomCode}/join") public void joinRoom(@DestinationVariable String roomCode, @Payload JoinRequest request, SimpMessageHeaderAccessor headerAccessor) { String sessionId = headerAccessor.getSessionId(); roomService.joinRoom(roomCode, sessionId, request.getUserName()); // 广播用户加入消息 Message systemMsg = Message.builder() .type(MessageType.SYSTEM) .payload(Map.of( "event", "USER_JOIN", "message", request.getUserName() + " 加入了房间", "userList", roomService.getUserList(roomCode) )) .build(); messagingTemplate.convertAndSend("/topic/room/" + roomCode, systemMsg); } // 发送文本消息 @MessageMapping("/room/{roomCode}/message") public void sendMessage(@DestinationVariable String roomCode, @Payload Message message) { message.setTimestamp(System.currentTimeMillis()); messagingTemplate.convertAndSend("/topic/room/" + roomCode, message); } // 发送文件分片 @MessageMapping("/room/{roomCode}/file/chunk") public void sendFileChunk(@DestinationVariable String roomCode, @Payload FileChunk chunk) { messagingTemplate.convertAndSend( "/topic/room/" + roomCode + "/file/" + chunk.getFileId(), chunk ); } } ``` #### RoomService.java ```java @Service public class RoomService { // 内存存储,生产环境可改用Redis private ConcurrentHashMap rooms = new ConcurrentHashMap<>(); public String createRoom() { String roomCode = generateRoomCode(); Room room = new Room(roomCode); rooms.put(roomCode, room); return roomCode; } public void joinRoom(String roomCode, String sessionId, String userName) { Room room = rooms.computeIfAbsent(roomCode, k -> new Room(k)); room.addUser(sessionId, userName); } public void leaveRoom(String roomCode, String sessionId) { Room room = rooms.get(roomCode); if (room != null) { room.removeUser(sessionId); if (room.isEmpty()) { rooms.remove(roomCode); } } } private String generateRoomCode() { // 生成6位数字房间号 Random random = new Random(); return String.format("%06d", random.nextInt(1000000)); } } ``` --- ## 七、前端实现 ### 7.1 项目结构 ``` data-transfer-web/ ├── public/ ├── src/ │ ├── api/ │ │ └── websocket.js # WebSocket封装 │ ├── components/ │ │ ├── RoomPanel.vue # 房间信息面板 │ │ ├── MessageList.vue # 消息列表 │ │ ├── MessageInput.vue # 消息输入 │ │ ├── FileDropZone.vue # 文件拖拽区域 │ │ └── UserList.vue # 在线用户列表 │ ├── views/ │ │ ├── HomeView.vue # 首页(创建/加入房间) │ │ └── RoomView.vue # 房间页面 │ ├── stores/ │ │ └── room.js # Pinia状态管理 │ ├── utils/ │ │ ├── fileChunker.js # 文件分片工具 │ │ └── clipboard.js # 剪贴板工具 │ ├── App.vue │ └── main.js ├── index.html ├── package.json └── vite.config.js ``` ### 7.2 核心代码示例 #### WebSocket封装 (websocket.js) ```javascript import SockJS from 'sockjs-client' import { Client } from '@stomp/stompjs' class WebSocketService { constructor() { this.client = null this.subscriptions = new Map() } connect(url, onConnect, onError) { this.client = new Client({ webSocketFactory: () => new SockJS(url), reconnectDelay: 5000, heartbeatIncoming: 4000, heartbeatOutgoing: 4000, }) this.client.onConnect = onConnect this.client.onStompError = onError this.client.activate() } subscribe(destination, callback) { if (this.client && this.client.connected) { const subscription = this.client.subscribe(destination, (message) => { callback(JSON.parse(message.body)) }) this.subscriptions.set(destination, subscription) } } send(destination, body) { if (this.client && this.client.connected) { this.client.publish({ destination, body: JSON.stringify(body) }) } } disconnect() { this.subscriptions.forEach(sub => sub.unsubscribe()) this.subscriptions.clear() if (this.client) { this.client.deactivate() } } } export default new WebSocketService() ``` #### 房间页面 (RoomView.vue) ```vue ``` #### 文件拖拽组件 (FileDropZone.vue) ```vue ``` --- ## 八、部署配置 ### 8.1 后端部署 (application.yml) ```yaml server: port: 8080 spring: websocket: message-buffer-size: 8192 # 如需消息持久化,配置数据库 datasource: url: jdbc:mysql://localhost:3306/data_transfer username: root password: xxx # 文件传输配置 transfer: chunk-size: 65536 # 64KB分片 max-file-size: 104857600 # 100MB room-expire-hours: 24 # 房间过期时间 ``` ### 8.2 前端部署 (vite.config.js) ```javascript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { proxy: { '/ws': { target: 'http://localhost:8080', ws: true, changeOrigin: true }, '/api': { target: 'http://localhost:8080', changeOrigin: true } } }, build: { outDir: 'dist', assetsDir: 'assets' } }) ``` ### 8.3 Nginx配置(生产环境) ```nginx server { listen 80; server_name your-domain.com; location / { root /path/to/data-transfer-web/dist; index index.html; try_files $uri $uri/ /index.html; } location /ws { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /api { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` --- ## 九、安全考虑 ### 9.1 安全措施 | 风险 | 解决方案 | |------|----------| | 房间号暴力破解 | 6位数字+过期机制,限制尝试频率 | | 大文件攻击 | 限制单文件100MB,限制房间总传输量 | | XSS攻击 | 消息内容HTML转义,使用textContent | | 数据泄露 | 房间自动过期,服务端不持久化敏感内容 | | WebSocket劫持 | 握手时验证Origin,使用WSS加密 | ### 9.2 可选增强 - 添加房间密码 - 端到端加密(WebCrypto API) - IP白名单限制 - 操作日志审计 --- ## 十、开发计划 ### 10.1 里程碑 | 阶段 | 时间 | 交付物 | |------|------|--------| | Phase 1 | 3天 | 基础WebSocket连接、文本传输、房间管理 | | Phase 2 | 3天 | 文件传输(分片)、进度显示、拖拽上传 | | Phase 3 | 2天 | 剪贴板集成、图片预览、历史记录 | | Phase 4 | 2天 | UI优化、响应式适配、测试修复 | ### 10.2 依赖清单 **后端 (pom.xml)** ```xml org.springframework.boot spring-boot-starter-websocket org.webjars sockjs-client 1.5.1 org.webjars stomp-websocket 2.3.4 ``` **前端 (package.json)** ```json { "dependencies": { "vue": "^3.3.4", "vue-router": "^4.2.4", "pinia": "^2.1.6", "element-plus": "^2.3.14", "@stomp/stompjs": "^7.0.0", "sockjs-client": "^1.6.1" }, "devDependencies": { "vite": "^4.4.9", "@vitejs/plugin-vue": "^4.3.4" } } ``` --- ## 十一、附录 ### 11.1 参考文档 - [Spring WebSocket官方文档](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket) - [STOMP协议规范](https://stomp.github.io/) - [Element Plus文档](https://element-plus.org/) - [Vue 3文档](https://vuejs.org/) ### 11.2 常见问题 **Q: 文件传输大小限制?** A: 默认100MB,可通过配置调整。超大文件建议分片后逐片发送。 **Q: 支持多文件同时传输吗?** A: 支持,每个文件有独立fileId,可并行传输。 **Q: 断线重连如何处理?** STOMP客户端内置重连机制,重连后需重新加入房间。 **Q: 内网环境如何部署?** A: 打包后部署到内网服务器,确保客户端能访问WebSocket端口。 --- **文档版本**: v1.0 **编写日期**: 2026-01-27 **作者**: AI Assistant