├── .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 | [](https://search.maven.org/artifact/ch.liubai.upload/liubai-upload-spring-boot-starter)
4 | [](https://opensource.org/licenses/Apache-2.0)
5 | [](https://www.oracle.com/java/)
6 | [](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 |
--------------------------------------------------------------------------------