├── .gitignore ├── pom.xml └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .idea 3 | *.iml 4 | .tags 5 | .tags_sorted_by_file 6 | .pyc 7 | __pycache__ 8 | .cache 9 | .settings 10 | .classpath 11 | .project 12 | .DS_Store 13 | *.pid 14 | *Help 15 | target 16 | log.* 17 | log.home_IS_UNDEFINED 18 | doc/api_doc 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | ch.liubai.upload 6 | liubai-upload-component 7 | pom 8 | 1.0.1 9 | 10 | liubai-upload-core 11 | liubai-upload-spring-boot-starter 12 | 13 | 14 | 15 | 8 16 | 8 17 | UTF-8 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Liubai Upload - 大文件断点续传组件 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/ch.liubai.upload/liubai-upload-spring-boot-starter.svg)](https://search.maven.org/artifact/ch.liubai.upload/liubai-upload-spring-boot-starter) 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 5 | [![Java Version](https://img.shields.io/badge/Java-8+-green.svg)](https://www.oracle.com/java/) 6 | [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.3.12-brightgreen.svg)](https://spring.io/projects/spring-boot) 7 | 8 | Liubai Upload 是一个高性能、易集成的大文件断点续传组件,专为 Spring Boot 应用设计。支持文件完整性校验、多种存储方式,提供开箱即用的上传解决方案。 9 | 10 | ## ✨ 核心特性 11 | 12 | - 🚀 **断点续传**:支持网络中断后从断点继续上传,避免重复传输 13 | - 🔒 **文件完整性校验**:基于 SHA256 算法确保文件传输完整性 14 | - 💾 **多种存储方式**:支持本地文件存储和 MySQL 数据库存储 15 | - 🔧 **零配置启动**:Spring Boot Starter 自动配置,开箱即用 16 | - 📊 **智能去重**:相同文件自动识别,避免重复存储 17 | - 🎯 **高性能**:分块上传,支持大文件高效传输 18 | - 🛡️ **安全可靠**:完整的错误处理和数据一致性保障 19 | 20 | ## 🏗️ 项目架构 21 | 22 | ### 模块结构 23 | ``` 24 | liubai-upload-component/ 25 | ├── liubai-upload-core/ # 核心功能模块 26 | │ └── src/main/java/ch/liubai/upload/ 27 | │ ├── controller/ # REST API 控制器 28 | │ │ └── FileController.java # 文件上传控制器 29 | │ ├── service/ # 业务逻辑层 30 | │ │ ├── FileService.java # 文件服务接口 31 | │ │ └── impl/FileServiceImpl.java # 文件服务实现 32 | │ ├── entity/ # 实体类 33 | │ │ ├── FileMetadata.java # 文件元数据实体 34 | │ │ ├── FileUploadPreprocessResponse.java # 预处理响应实体 35 | │ │ └── ReturnVO.java # 统一返回对象 36 | │ ├── enums/ # 枚举类 37 | │ │ ├── ErrorType.java # 错误类型枚举 38 | │ │ └── UploadErrorCodeEnum.java # 上传错误码枚举 39 | │ ├── metadata/ # 元数据存储 40 | │ │ ├── FileMetadataStorage.java # 存储接口 41 | │ │ ├── FileMetadataProperties.java # 配置属性 42 | │ │ ├── LocalFileMetadataStorage.java # 本地文件存储实现 43 | │ │ └── DatabaseFileMetadataStorage.java # 数据库存储实现 44 | │ └── util/ # 工具类 45 | │ └── UploadFileUtil.java # 文件上传工具类 46 | ├── liubai-upload-spring-boot-starter/ # Spring Boot 自动配置 47 | │ └── src/main/java/ch/liubai/upload/ 48 | │ ├── FileMetadataStorageFactory.java # 存储工厂自动配置 49 | │ └── FileMetadataWrapper.java # 配置属性包装类 50 | └── resources/ # 资源文件 51 | ├── html/index.html # 前端示例页面 52 | ├── js/app.js # 前端JavaScript实现 53 | └── sql/file_metadata.sql # 数据库表结构 54 | ``` 55 | 56 | ### 核心组件 57 | 58 | - **FileController**:提供文件预处理和上传的 REST API 59 | - **FileService**:核心业务逻辑,处理文件上传和完整性校验 60 | - **FileMetadataStorage**:文件元数据存储抽象,支持本地和数据库存储 61 | - **UploadFileUtil**:文件操作工具类,提供 SHA256 计算、文件移动等功能 62 | 63 | ## 🚀 快速开始 64 | 65 | ### 1. 添加依赖 66 | 67 | 在您的 `pom.xml` 中添加依赖: 68 | 69 | ```xml 70 | 71 | ch.liubai.upload 72 | liubai-upload-spring-boot-starter 73 | 1.0.1 74 | 75 | ``` 76 | 77 | ### 2. 配置参数 78 | 79 | 在 `application.yml` 中添加配置: 80 | 81 | ```yaml 82 | file: 83 | metadata: 84 | # 存储类型:local(本地) 或 mysql(数据库) 85 | # 不配置时自动检测:有 DataSource 则使用 mysql,否则使用 local 86 | storageType: mysql 87 | 88 | # 临时文件存储路径(必填) 89 | tempDir: /tmp/liubai-upload/temp 90 | 91 | # 上传完成文件存储路径(必填) 92 | uploadDir: /tmp/liubai-upload/files 93 | 94 | # 元数据存储路径(local 模式使用) 95 | # 不配置时默认使用 ~/.file_metadata 96 | metadataDir: /tmp/liubai-upload/metadata 97 | ``` 98 | 99 | ### 3. 启动应用 100 | 101 | 启动 Spring Boot 应用,组件会自动注册以下接口: 102 | 103 | - `GET /file/preprocess` - 文件预处理接口 104 | - `POST /file/upload` - 文件上传接口 105 | 106 | ## 📖 API 文档 107 | 108 | ### 文件预处理接口 109 | 110 | **请求** 111 | ```http 112 | GET /file/preprocess?sha256={文件SHA256}&totalBytes={文件大小} 113 | ``` 114 | 115 | **参数** 116 | - `sha256`:文件的 SHA256 哈希值(必填) 117 | - `totalBytes`:文件总字节数(必填) 118 | 119 | **响应** 120 | ```json 121 | { 122 | "code": 20000, 123 | "message": "success", 124 | "data": { 125 | "uploadedBytes": 1048576, // 已上传字节数 126 | "currentSha256": "abc123..." // 当前已上传部分的 SHA256 127 | } 128 | } 129 | ``` 130 | 131 | ### 文件上传接口 132 | 133 | **请求** 134 | ```http 135 | POST /file/upload 136 | Content-Type: multipart/form-data 137 | 138 | sha256: 文件SHA256值 139 | file: 文件数据 140 | startByte: 起始字节位置 141 | totalBytes: 文件总大小 142 | ``` 143 | 144 | **响应** 145 | ```json 146 | { 147 | "code": 20000, 148 | "message": "文件上传成功", 149 | "data": "success" 150 | } 151 | ``` 152 | 153 | ## 💻 前端集成示例 154 | 155 | ### HTML 页面 156 | ```html 157 | 158 | 159 | 160 | 文件上传 161 | 162 | 163 | 164 | 165 | 166 |
167 | 168 | 169 |
170 | 上传进度: {{ Math.round(progress * 100) }}% 171 |
172 |
173 | 174 | 175 | ``` 176 | 177 | ### JavaScript 实现 178 | ```javascript 179 | new Vue({ 180 | el: '#app', 181 | data: { 182 | file: null, 183 | uploading: false, 184 | progress: 0 185 | }, 186 | methods: { 187 | handleFileChange(event) { 188 | this.file = event.target.files[0]; 189 | }, 190 | 191 | async startUpload() { 192 | if (!this.file) return; 193 | 194 | this.uploading = true; 195 | try { 196 | // 1. 计算文件 SHA256 197 | const sha256 = await this.calculateSHA256(this.file); 198 | 199 | // 2. 预处理请求 200 | const preprocessRes = await axios.get('/file/preprocess', { 201 | params: { 202 | sha256: sha256, 203 | totalBytes: this.file.size 204 | } 205 | }); 206 | 207 | const { uploadedBytes, currentSha256 } = preprocessRes.data.data; 208 | 209 | // 3. 检查是否需要上传 210 | if (uploadedBytes === this.file.size && currentSha256 === sha256) { 211 | alert('文件已存在'); 212 | return; 213 | } 214 | 215 | // 4. 断点续传 216 | const startByte = uploadedBytes || 0; 217 | const fileSlice = this.file.slice(startByte); 218 | 219 | const formData = new FormData(); 220 | formData.append('file', fileSlice); 221 | formData.append('sha256', sha256); 222 | formData.append('startByte', startByte); 223 | formData.append('totalBytes', this.file.size); 224 | 225 | // 5. 上传文件 226 | await axios.post('/file/upload', formData, { 227 | headers: { 'Content-Type': 'multipart/form-data' }, 228 | onUploadProgress: (progressEvent) => { 229 | this.progress = (startByte + progressEvent.loaded) / this.file.size; 230 | } 231 | }); 232 | 233 | alert('上传成功'); 234 | } catch (error) { 235 | console.error('上传失败:', error); 236 | alert('上传失败'); 237 | } finally { 238 | this.uploading = false; 239 | } 240 | }, 241 | 242 | calculateSHA256(file) { 243 | return new Promise((resolve, reject) => { 244 | const reader = new FileReader(); 245 | const sha256 = CryptoJS.algo.SHA256.create(); 246 | 247 | reader.onload = (e) => { 248 | const wordArray = CryptoJS.lib.WordArray.create(e.target.result); 249 | sha256.update(wordArray); 250 | resolve(sha256.finalize().toString()); 251 | }; 252 | 253 | reader.onerror = reject; 254 | reader.readAsArrayBuffer(file); 255 | }); 256 | } 257 | } 258 | }); 259 | ``` 260 | 261 | ## ⚙️ 高级配置 262 | 263 | ### 数据库模式 264 | 265 | 使用 MySQL 存储时,组件会自动创建 `file_metadata` 表: 266 | 267 | ```sql 268 | CREATE TABLE file_metadata ( 269 | id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', 270 | file_name VARCHAR(255) NOT NULL COMMENT '文件名', 271 | sha256 VARCHAR(64) NOT NULL COMMENT '文件SHA256', 272 | file_size BIGINT COMMENT '文件大小(字节)', 273 | create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 274 | update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 275 | UNIQUE KEY sha256_UNIQUE_IDX (sha256) COMMENT 'SHA256唯一索引' 276 | ) COMMENT '文件元数据表'; 277 | ``` 278 | 279 | ### 自定义存储实现 280 | 281 | 实现 `FileMetadataStorage` 接口来自定义存储方式: 282 | 283 | ```java 284 | @Component 285 | public class CustomFileMetadataStorage implements FileMetadataStorage { 286 | 287 | @Override 288 | public void addFileMetadata(FileMetadata metadata) throws Exception { 289 | // 自定义存储逻辑 290 | } 291 | 292 | @Override 293 | public FileMetadata getFileMetadata(String sha256) throws Exception { 294 | // 自定义查询逻辑 295 | return null; 296 | } 297 | } 298 | ``` 299 | 300 | ## 🔧 技术栈 301 | 302 | - **Java 8+**:核心开发语言 303 | - **Spring Boot 2.3.12**:应用框架 304 | - **Maven**:项目构建工具 305 | - **MySQL**:可选的元数据存储 306 | - **Vue.js + Axios**:前端示例实现 307 | - **CryptoJS**:前端 SHA256 计算 308 | 309 | ## 📝 更新日志 310 | 311 | ### v1.0.1 (2024-01-22) 312 | - ✨ 新增 Spring Boot Starter 自动配置 313 | - 🐛 修复文件完整性校验问题 314 | - 📈 优化大文件上传性能 315 | - 🔧 改进错误处理机制 316 | 317 | ## 🤝 贡献指南 318 | 319 | 欢迎提交 Issue 和 Pull Request! 320 | 321 | 1. Fork 本仓库 322 | 2. 创建特性分支:`git checkout -b feature/amazing-feature` 323 | 3. 提交更改:`git commit -m 'Add amazing feature'` 324 | 4. 推送分支:`git push origin feature/amazing-feature` 325 | 5. 提交 Pull Request 326 | 327 | ## 📄 许可证 328 | 329 | 本项目基于 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 许可证开源。 330 | 331 | ## 👨‍💻 作者 332 | 333 | - **pfr** - *项目维护者* - [刘白](mailto:1044586526@qq.com) 334 | 335 | ## 🔗 相关链接 336 | 337 | - [GitHub 仓库](https://github.com/1044586526/liubai-upload) 338 | - [问题反馈](https://github.com/1044586526/liubai-upload/issues) 339 | 340 | --- 341 | 342 | ⭐ 如果这个项目对您有帮助,请给我们一个 Star! 343 | --------------------------------------------------------------------------------