@RestController
@RequestMapping("/upload")
public class FileUploadController {
@Autowired
private FileUploadService fileUploadService;
// 接收分片
@PostMapping("/chunk")
public ResponseEntity<?> uploadChunk(@RequestParam("file") MultipartFile file,
@RequestParam("fileHash") String fileHash,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("totalChunks") int totalChunks) {
fileUploadService.saveChunk(file, fileHash, chunkIndex, totalChunks);
return ResponseEntity.ok("Chunk uploaded");
}
// 查詢已上傳分片(支持斷點續傳)
@GetMapping("/check")
public ResponseEntity<List<Integer>> checkUploadedChunks(@RequestParam("fileHash") String fileHash) {
List<Integer> uploadedChunks = fileUploadService.getUploadedChunks(fileHash);
return ResponseEntity.ok(uploadedChunks);
}
// 合併分片
@PostMapping("/merge")
public ResponseEntity<?> mergeChunks(@RequestParam("fileHash") String fileHash,
@RequestParam("fileName") String fileName) {
fileUploadService.mergeChunks(fileHash, fileName);
return ResponseEntity.ok("File merged");
}
// 斷點下載(支援 Range)
@GetMapping("/download")
public void download(@RequestParam String fileName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
fileUploadService.download(fileName, req, resp);
}
@Service
public class FileUploadService {
private static final Logger log = LoggerFactory.getLogger(FileUploadService.class);
private final SimpMessagingTemplate messagingTemplate;
private final NasFileInfoMapper fileInfoMapper;
private final NasDownloadLogMapper nasDownloadLogMapper;
private static final String HOSTNAME = "10.32.5.80"; // NAS服务器地址
private static final String SHARE_NAME = "AnyshareBAK"; // 共享文件夹名称
private static final String USERNAME = "anyshare"; // 用户名
private static final String PASSWORD = "anybakup$*5.46";
private static final String NAS_UPLOAD_PATH = "/upload"; // NAS 目标路径,可根据需要修改
private static final String BASE_PATH = "/data/uploads/temp"; // 本地临时目录
private final SMBClient client;
private Connection connection;
private static Session session;
public FileUploadService(SimpMessagingTemplate messagingTemplate,NasFileInfoMapper fileInfoMapper,NasDownloadLogMapper nasDownloadLogMapper) {
this.messagingTemplate = messagingTemplate;
this.fileInfoMapper = fileInfoMapper;
this.nasDownloadLogMapper = nasDownloadLogMapper;
this.client = new SMBClient(SmbConfig.builder()
.withMultiProtocolNegotiate(true)
.build());
}
private void init() throws IOException {
if (connection == null || !connection.isConnected()) {
connection = client.connect(HOSTNAME);
session = connection.authenticate(new AuthenticationContext(USERNAME, PASSWORD.toCharArray(), null));
}
}
public void saveChunk(MultipartFile chunk, String fileHash, int chunkIndex, int totalChunks) {
File dir = new File(BASE_PATH, fileHash);
if (!dir.exists()) dir.mkdirs();
File chunkFile = new File(dir, chunkIndex + ".part");
try (InputStream in = chunk.getInputStream(); OutputStream out = new FileOutputStream(chunkFile)) {
in.transferTo(out);
// ✅ 上傳完成一個分片後推送進度
notifyProgress(fileHash, getUploadedChunks(fileHash).size(), totalChunks);
} catch (IOException e) {
throw new RuntimeException("Failed to save chunk", e);
}
}
public List<Integer> getUploadedChunks(String fileHash) {
File dir = new File(BASE_PATH, fileHash);
if (!dir.exists()) return List.of();
return Arrays.stream(Objects.requireNonNull(dir.listFiles()))
.map(file -> Integer.parseInt(file.getName().replace(".part", "")))
.collect(Collectors.toList());
}
public void mergeChunks(String fileHash, String fileName) {
File dir = new File(BASE_PATH, fileHash);
File mergedFile = new File(BASE_PATH + "/complete", fileName);
mergedFile.getParentFile().mkdirs();
try (OutputStream out = new FileOutputStream(mergedFile)) {
List<File> parts = Arrays.stream(Objects.requireNonNull(dir.listFiles()))
.sorted(Comparator.comparingInt(f -> Integer.parseInt(f.getName().replace(".part", ""))))
.collect(Collectors.toList());
for (File part : parts) {
try (InputStream in = new FileInputStream(part)) {
in.transferTo(out);
}
}
log.info("File merged successfully: {}", mergedFile.getAbsolutePath());
// 上传合并后的文件到 NAS
try (InputStream inputStream = new FileInputStream(mergedFile)) {
uploadToNas(inputStream, NAS_UPLOAD_PATH + "/" + fileName);
log.info("Upload to NAS complete: {}", fileName);
}
// 清理临时文件
for (File part : parts) part.delete();
dir.delete();
mergedFile.delete(); // 可选:也清理合并文件
// 合并和上传成功后保存信息到数据库
NasFileInfo info = new NasFileInfo();
info.setFileHash(fileHash);
info.setFileName(fileName);
info.setFileSize(mergedFile.length());
info.setUploadTime(LocalDateTime.now());
info.setUsername(SecurityUtils.getUsername());
info.setDeptId(SecurityUtils.getDeptId());
info.setNasPath(NAS_UPLOAD_PATH + "/" + fileName);
fileInfoMapper.insert(info);
} catch (IOException e) {
throw new RuntimeException("Merge or upload failed", e);
}
}
private void uploadToNas(InputStream inputStream, String nasPath) throws IOException {
}
private void createDirectoryIfNotExists(DiskShare share, String path) {
String[] folders = path.split("/");
String currentPath = "";
for (String folder : folders) {
if (folder.isEmpty()) continue;
currentPath += "/" + folder;
if (!share.folderExists(currentPath)) {
share.mkdir(currentPath);
}
}
}
public void download(String fileName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
init(); // 初始化 SMB 连接
try (DiskShare share = (DiskShare) session.connectShare(SHARE_NAME)) {
String nasPath = NAS_UPLOAD_PATH + "/" + fileName;
if (!share.fileExists(nasPath)) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//获取到目标文件夹
com.hierynomus.smbj.share.File remoteFile;
try {
remoteFile = share.openFile(nasPath, EnumSet.of(AccessMask.GENERIC_WRITE), null, SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OVERWRITE_IF, null);
} catch (Exception e) {
log.error("Error opening remote file for writing: {}", nasPath, e);
throw new IOException("Failed to open file for writing: " + nasPath);
}
long fileLength = remoteFile.getFileInformation().getStandardInformation().getEndOfFile();
String range = req.getHeader("Range");
long start = 0, end = fileLength - 1;
if (range != null && range.startsWith("bytes=")) {
String[] parts = range.replace("bytes=", "").split("-");
start = Long.parseLong(parts[0]);
if (parts.length > 1 && !parts[1].isEmpty()) end = Long.parseLong(parts[1]);
resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
} else {
resp.setStatus(HttpServletResponse.SC_OK);
}
long contentLength = end - start + 1;
resp.setHeader("Content-Type", "application/octet-stream");
resp.setHeader("Content-Length", String.valueOf(contentLength));
resp.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
resp.setHeader("Accept-Ranges", "bytes");
try (InputStream smbIn = remoteFile.getInputStream(); ServletOutputStream out = resp.getOutputStream()) {
smbIn.skip(start); // 跳过开始位置
byte[] buffer = new byte[8192];
long remaining = contentLength;
int len;
while ((len = smbIn.read(buffer, 0, (int) Math.min(buffer.length, remaining))) > 0) {
out.write(buffer, 0, len);
remaining -= len;
}
out.flush();
}
NasFileInfo fileInfo = fileInfoMapper.selectOne(
new LambdaQueryWrapper<NasFileInfo>().eq(NasFileInfo::getFileName, fileName)
);
if (fileInfo != null) {
NasDownloadLog log = new NasDownloadLog();
log.setFileId(fileInfo.getId());
log.setFileName(fileName);
log.setUsername(SecurityUtils.getUsername()); // 自行實作獲取使用者名稱
log.setIpAddress(req.getRemoteAddr());
log.setUserAgent(req.getHeader("User-Agent"));
log.setDownloadTime(LocalDateTime.now());
nasDownloadLogMapper.insert(log);
}
}
}
// 講進度丟該websocket
public void notifyProgress(String fileHash, int currentChunk, int total) {
messagingTemplate.convertAndSend("/progress/" + fileHash,
Map.of("current", currentChunk, "total", total));
}
这是一个关于使用断点续传到nas的文件上传与下载功能,请阅读分析以上代码,帮我补齐uploadToNas方法的具体实现
最新发布