Xiajijian
2026-03-10 a222a89580ad46ad31924bdfba4f0e05ecefde16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
package com.ard.inspection.service.impl;
 
import com.ard.common.core.domain.AjaxResult;
import com.ard.common.utils.uuid.IdUtils;
import com.ard.inspection.domain.ArdPointCloud;
import com.ard.inspection.domain.UploadFileEntity;
import com.ard.inspection.service.IArdPointCloudService;
import com.ard.inspection.service.IBigFileUploadService;
import com.ard.inspection.utils.ZipFileUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
 
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;
import java.util.stream.Collectors;
 
import static com.ard.inspection.utils.ZipFileUtils.formatFileSize;
 
/**
 * @author xia
 */
@Service
@Slf4j
public class BigFileUploadServiceImpl implements IBigFileUploadService {
 
    public static final String CLOUD_FILE_PATH = "https://192.168.1.3:3001/tileset/";
 
    @Autowired
    private IArdPointCloudService ardPointCloudService;
 
    @Resource
    private ZipFileUtils zipFileUtils;
 
    @Override
    public Map<String, Object> checkChunk(String fileMd5,String chunkPath) {
        Map<String, Object> result = new HashMap<>();
        try {
            File chunkFolder = new File(chunkPath + File.separator + fileMd5);
            if (!chunkFolder.exists()) {
                result.put("exception", "分片不存在!!!");
                return result;
            }
 
            File[] chunks = chunkFolder.listFiles();
            if (chunks == null || chunks.length == 0) {
                result.put("exception", "无分片!!!");
                return result;
            }
 
            List<Integer> uploadedChunks = Arrays.stream(chunks)
                    .map(file -> Integer.parseInt(file.getName().split("\\.")[0]))
                    .collect(Collectors.toList());
            result.put("message", uploadedChunks);
            return result;
        } catch (Exception e) {
            log.error("检查分片失败", e);
            result.put("exception", "检查分片失败!!!");
            return result;
        }
    }
 
    @Override
    public Map<String, Object> uploadChunk(MultipartFile file,
                              Integer chunkNumber,
                              String fileMd5,
                              String chunkPath) {
        Map<String, Object> result = new HashMap<>();
        try {
            // 验证参数
            if (file.isEmpty()) {
                result.put("exception", "文件不能为空!!!");
                return result;
            }
 
            // 创建分片目录
            String chunkDirPath = chunkPath + File.separator + fileMd5;
            File chunkDir = new File(chunkDirPath);
            if (!chunkDir.exists()) {
                chunkDir.mkdirs();
            }
 
            // 保存分片
            String chunkFileName = chunkNumber + ".part";
            File chunkFile = new File(chunkDir, chunkFileName);
            file.transferTo(chunkFile);
 
            result.put("message", "分片上传成功!!!");
            return result;
        } catch (IOException e) {
            log.error("分片上传失败", e);
            result.put("exception", "分片上传失败!!!");
            return result;
        }
    }
 
    @Override
    public Map<String, Object> uploadChunk(MultipartFile file,UploadFileEntity uploadFileEntity,String chunkPath) {
        Map<String, Object> result = new HashMap<>();
        try {
            // 验证参数
            if (file.isEmpty()) {
                result.put("exception", "文件不能为空!!!");
                return result;
            }
 
            // 创建分片目录
            String chunkDirPath = chunkPath + File.separator + uploadFileEntity.getFileMd5();
            File chunkDir = new File(chunkDirPath);
            if (!chunkDir.exists()) {
                chunkDir.mkdirs();
            }
 
            // 保存分片
            String chunkFileName = uploadFileEntity.getChunkNumber() + ".part";
            File chunkFile = new File(chunkDir, chunkFileName);
            file.transferTo(chunkFile);
 
            result.put("message", "分片上传成功!!!");
            return result;
        } catch (IOException e) {
            log.error("分片上传失败", e);
            result.put("exception", "分片上传失败!!!");
            return result;
        }
    }
 
    @Override
    public Map<String, Object> mergeChunks(String md5,String fileName,String uploadPath) {
        Map<String, Object> result = new HashMap<>();
        // 临时目录
        String chunkDir = uploadPath + File.separator + md5;
        File dir = new File(chunkDir);
        if (!dir.exists()) {
            result.put("exception", "切片目录不存在");
            return result;
        }
        // 获取所有切片文件
        File[] chunks = dir.listFiles();
        if (chunks == null || chunks.length == 0) {
            result.put("exception", "没有切片文件");
            return result;
        }
        // 按照切片索引排序
        Arrays.sort(chunks, (a, b) -> {
            int aIndex = Integer.parseInt(a.getName().split("\\.")[0]);
            int bIndex = Integer.parseInt(b.getName().split("\\.")[0]);
            return aIndex - bIndex;
        });
 
        // 合并后的文件,这里使用原始文件名,为了避免重名,可以加上时间戳或使用UUID
        // 这里根据业务需求,可以保存到数据库,记录文件路径等信息
        String ext = fileName.substring(fileName.lastIndexOf("."));
        String newFileName = UUID.randomUUID().toString().replace("-", "") + ext; // 生成新文件名
        File mergeFile = new File(uploadPath, newFileName);
 
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(mergeFile, "rw")) {
            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区
            for (File chunk : chunks) {
                try (RandomAccessFile chunkFile = new RandomAccessFile(chunk, "r")) {
                    int len;
                    while ((len = chunkFile.read(buffer)) != -1) {
                        randomAccessFile.write(buffer, 0, len);
                    }
                }
                // 删除切片文件
                chunk.delete();
            }
            // 删除临时目录
            dir.delete();
        } catch (IOException e) {
            log.error("合并文件失败", e);
            result.put("exception", "合并文件失败");
            return result;
        }
 
        // 这里可以保存文件信息到数据库,例如文件路径、文件名、md5等
        // fileService.saveFileInfo(...);
//        ArdPointCloud ardPointCloud = new ArdPointCloud();
//        ardPointCloud.setFileName(newFileName);
//        ardPointCloud.setFileSize(mergeFile.length());
//        ardPointCloud.setAssociatedState("0");
//        ardPointCloudService.insertArdPointCloud(ardPointCloud);
 
        result.put("message", "文件合并成功");
        return result;
    }
 
    /**
     * 大文件上传并解压接口
     * @param file 上传的ZIP文件
     * @param overwrite 是否覆盖已存在文件(默认false)
     * @param uploadPath 上传文件存储路径
     * @param chunkPath 临时切片存储路径
     * @param zipPath 解压文件存储路径
     * @return 上传结果
     */
    @Override
    public AjaxResult uploadBigZipFile(MultipartFile file, boolean overwrite,
                                       String uploadPath,
                                       String chunkPath,
                                       String zipPath) {
        // 1. 校验文件是否为空
        if (file.isEmpty()) {
            return AjaxResult.error("上传文件不能为空");
        }
 
        // 2. 校验文件格式
        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || !originalFilename.endsWith(".zip")) {
            return AjaxResult.error("仅支持ZIP格式文件上传");
        }
 
        // 2.1 校验文件名是否重复
        if (!overwrite) {
            if (ardPointCloudService.selectArdPointCloudByFileName(originalFilename) != null) {
                return AjaxResult.error("文件已存在");
            }
        }
 
        // 3. 创建临时目录
        File tempDir = new File(uploadPath);
        if (!tempDir.exists()) {
            boolean mkdirs = tempDir.mkdirs();
            if (!mkdirs) {
                return AjaxResult.error("创建临时上传目录失败");
            }
        }
 
        // 4. 保存上传的ZIP文件到临时目录(避免重名)
        String tempFileName = UUID.randomUUID() + ".zip";
        File tempFile = new File(chunkPath + File.separator + tempFileName);
        try {
            file.transferTo(tempFile);
            log.info("临时文件保存成功:{},文件大小:{}MB", tempFile.getAbsolutePath(),
                    file.getSize() / 1024 / 1024);
        } catch (IOException e) {
            log.error("保存临时文件失败", e);
            return AjaxResult.error("文件上传失败:" + e.getMessage());
        }
 
        // 5. 解压文件
        try {
            zipFileUtils.unzip(tempFile.getAbsolutePath(), overwrite);
            // 6. 删除临时文件(可选,若需要保留压缩包可注释)
            boolean delete = tempFile.delete();
            if (delete) {
                log.info("临时文件已删除:{}", tempFile.getAbsolutePath());
            }
            ArdPointCloud ardPointCloud = new ArdPointCloud();
            ardPointCloud.setFileName(originalFilename);
            ardPointCloud.setFileSize(formatFileSize(file.getSize()));
            int dotIndex = originalFilename.lastIndexOf('.');
            String folderName = (dotIndex > 0) ? originalFilename.substring(0, dotIndex) : originalFilename;
            String zipP = CLOUD_FILE_PATH + folderName + "/tileset.json";
            ardPointCloud.setFilePath(zipP);
            ardPointCloud.setFolderName(folderName);
            return AjaxResult.success("文件上传并解压成功", ardPointCloud);
        } catch (IOException e) {
            log.error("解压文件失败", e);
            // 失败时删除临时文件
            if (tempFile.exists()) {
                tempFile.delete();
            }
            return AjaxResult.error("文件解压失败:" + e.getMessage());
        }
    }
 
}