RandomAccessFile类包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile可以自由的移动记录指针,即可以向前移动,也可以向后移动。
1.Java实体类
@Data
@Accessors(chain = true)
public class Chunk implements Serializable {
/**
* 当前文件块,从1开始
*/
private Integer chunkNumber;
/**
* 分块大小
*/
private Long chunkSize;
/**
* 总大小
*/
// private Long totalSize;
/**
* 文件名
*/
private String filename;
/**
* 相对路径
*/
// private String relativePath;
/**
* 总块数
*/
private Integer totalChunks;
/**
* 二进制文件
*/
private MultipartFile file;
}
2. controller接收请求
@PostMapping("/upload")
public Response uploadPost(@RequestParam Integer chunkNumber,
String filename,
@RequestParam Integer totalChunks,
@RequestParam MultipartFile file,
@RequestParam String path,
@RequestParam Long chunkSize,
HttpServletResponse response) {
Chunk chunk = new Chunk()
.setChunkNumber(chunkNumber)
.setFile(file)
.setFilename(filename)
.setTotalChunks(totalChunks)
.setChunkSize(chunkSize);
log.info("文件上传:chunk:{},path:{}", chunk, file.getSize());
log.info("文件块{}大小:{}",chunk.getFilename(), file.getSize());
return fileUploadPost(chunk, path, response);
}
3. fileUploadPost方法:
/**
* 文件上传(断点续传)
*/
public Response fileUploadPost(Chunk chunk, String path, HttpServletResponse response) {
/**
* 根据响应码认为成功或失败的:
* 200 文件上传完成
* 201 文加块上传成功
*/
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, chunk.getFilename());
//第一个块,则新建文件
if (chunk.getChunkNumber() == 1 && !file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
log.error("创建文件时异常:{}",e);
return ResponseHelper.createResponse(500, "canceled", "canceled");
}
}
try (
//将块文件写入文件中
InputStream fis = chunk.getFile().getInputStream();
RandomAccessFile raf = new RandomAccessFile(file, "rw")
) {
int len = -1;
byte[] buffer = new byte[1024];
// 指针移动到当前块开始写的位置,chunk.getChunkNumber()是指当前是第几块,减一后乘
//以每个块的大小 得到前面块的偏移量,即当前块的起始位置
raf.seek((chunk.getChunkNumber() - 1) * chunk.getChunkSize());
//把当前块的内容写入
while ((len = fis.read(buffer)) != -1) {
raf.write(buffer, 0, len);
}
} catch (IOException e) {
log.error("文件断点上传异常:{}", e);
if (chunk.getChunkNumber() == 1) {
file.delete();
}
return ResponseHelper.createResponse(507, "retry", "retry");
}
//当前块的序号等于总的块数时,说明上传完成。返回over,否则就是还没上传完
if (chunk.getChunkNumber().equals(chunk.getTotalChunks())) {
return ResponseHelper.createResponse(200, "over", "over");
} else {
return ResponseHelper.createResponse(201, "ok", "ok");
}
}
4.Response 和 ResponseHelper
package shanghai.zombie.hugry;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
public class Response<T> {
private int code;
@JsonInclude(Include.NON_NULL)
@JsonProperty("message")
private String message = null;
@JsonProperty("data")
private T data = null;
public Response() {
}
public int getCode() {
return this.code;
}
public String getMessage() {
return this.message;
}
public T getData() {
return this.data;
}
public void setCode(final int code) {
this.code = code;
}
public void setMessage(final String message) {
this.message = message;
}
public void setData(final T data) {
this.data = data;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Response)) {
return false;
} else {
Response<?> other = (Response)o;
if (!other.canEqual(this)) {
return false;
} else if (this.getCode() != other.getCode()) {
return false;
} else {
Object this$message = this.getMessage();
Object other$message = other.getMessage();
if (this$message == null) {
if (other$message != null) {
return false;
}
} else if (!this$message.equals(other$message)) {
return false;
}
Object this$data = this.getData();
Object other$data = other.getData();
if (this$data == null) {
if (other$data != null) {
return false;
}
} else if (!this$data.equals(other$data)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Response;
}
public int hashCode() {
int PRIME = true;
int result = 1;
int result = result * 59 + this.getCode();
Object $message = this.getMessage();
result = result * 59 + ($message == null ? 43 : $message.hashCode());
Object $data = this.getData();
result = result * 59 + ($data == null ? 43 : $data.hashCode());
return result;
}
public String toString() {
return "Response(code=" + this.getCode() + ", message=" + this.getMessage() + ", data=" + this.getData() + ")";
}
}
public class ResponseHelper {
public ResponseHelper() {
}
public static <T> Response<T> createResponse(int code, String description, T payload) {
Response<T> response = createSuccessResponse(ResultCode.SUCCESS.getCode());
response.setCode(code);
response.setMessage(description);
response.setData(payload);
return response;
}
}
使用postman测试时,header要加Content-type: multipart/form-data,body中才可以选择file