文件系统主要存放文件,实现于存放OSS文件和本地文件
本地文件系统
系统接口
系统接口主要包含get-oss-config和add-update-oss-config
ossconfig模型定义
ossconfig主要是这几个字段用于创建s3client或者本地客户端,这些字段就可以用于去请求api存放文件内容,文件系统的配置信息保存在本地file-system-dev.properties,配置文件文件地址指定也可以通过系统参数指定,根据项目的环境来获取不同的配置文件
private String accessKey;
private String secretKey;
private String region;
private String domain;
private String s3Url;
private String type;通过接口可以把配置信息保存到配置文件后续操作接口可以通过token来操作
认证系统
主要是获取jwt来操作文件上传,通过token来操作文件避免key和密钥暴露的文献
auth模型定义
@Data
public class OssCreateAuthInfoCommand {
private String accessKey;
private String secretKey;
private String clientIp;
private String type;
private List<String> permissions;
}系统通过key和value来查询系统的配置文件是否匹配,匹配上返回token给用户,用户返回的jwt携带上key后续解析这个key来判断具体操作那个客户端。
文件接口
文件接口主要分为增删查接口,单个文件直接上传、分片上传、文件的查找以及文件上传的签名url的返回,文件接口操作本地还是OSS都是根据key来的,在jwt过滤器中解析出来信息然后放到请求域中,具体的实现请查看代码
@PostMapping("/slice-upload-init")
public Mono<Response<SliceUploadInitVo>> sliceUploadInit(@RequestBody SliceUploadInitCommand command) {
return fileApplicationService.sliceUploadInit(command)
.map(ResponseUtil::success) // 包装成功响应
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage()))); // 错误处理
}
@PostMapping(value = "/upload-chunk")
public Mono<Response<ChunkUploadVo>> uploadChunk(
@RequestPart("fileKey") String fileKey,
@RequestPart("file") FilePart chunk,
@RequestPart("partNum") String partNum,
@RequestPart("uploadId") String uploadId,
@RequestPart(value = "bucketName", required = false) String bucketName,
ServerHttpRequest request) {
return chunk.content()
.collect(ByteArrayOutputStream::new, (out, buffer) -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
out.write(bytes, 0, bytes.length);
DataBufferUtils.release(buffer);
})
.flatMap(out -> {
byte[] chunkData = out.toByteArray();
MultiValueMap<String, String> queryParams = request.getQueryParams();
String key = queryParams.getFirst("key");
if(key == null) {
key = fileKey;
}
ChunkUploadCommand chunkUploadCommand = ChunkUploadCommand.builder()
.fileKey(key)
.filePart(chunkData)
.uploadId(uploadId)
.partNum(Integer.parseInt(partNum))
.bucketName(bucketName)
.build();
return fileApplicationService.chunkUpload(chunkUploadCommand);
})
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage()))); // 错误处理
}
@PostMapping("/slice-assemble-file")
public Mono<Response<SliceAssembleFileVo>> sliceAssembleFile(@RequestBody SliceAssembleFileCommand command) {
return fileApplicationService.sliceAssembleFile(command)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage())));
}
@PostMapping("/download-file")
public Mono<Response<DownloadFileVo>> downloadFile(@RequestBody DownloadFileCommand command) {
return fileApplicationService.downloadFile(command)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage())));
}
@PostMapping( "/upload-file")
public Mono<Response<UploadFileVo>> uploadFile(@RequestPart("fileKey") String fileKey,
@RequestPart("file") FilePart file,
@RequestPart(value = "dir", required = false) String dir,
@RequestPart(value = "domain", required = false) String domain,
@RequestPart("bucketName") String bucketName,
ServerHttpRequest request) {
Flux<DataBuffer> content = file.content();
HttpHeaders headers = request.getHeaders();
String contentMd5 = headers.getFirst("Content-MD5");
String contentType = headers.getFirst("Content-Type");
String contentLength = headers.getFirst("Content-Length");
UploadFileCommand chunkUploadCommand = UploadFileCommand.builder()
.key(fileKey)
.dir(dir)
.md5(contentMd5)
.domain(domain)
.contentType(contentType)
.size(contentLength != null ? Long.parseLong(contentLength) : 0)
.dataBufferFlux(content)
.bucketName(bucketName)
.build();
return fileApplicationService.uploadFile(chunkUploadCommand)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage()))); // 错误处理
}
@PostMapping("/delete-file")
public Mono<Response<DeleteFileResultVo>> deleteFile(@RequestBody DeleteFileCommand deleteFileCommand) {
return fileApplicationService.deleteFile(deleteFileCommand)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage()))); // 错误处理
}
@PostMapping("/find-file")
public Mono<Response<PageResultVo<FileInfoVo>>> deleteFile(@RequestBody FileInfoQueryCommand fileInfoQueryCommand) {
return fileApplicationService.findFile(fileInfoQueryCommand)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage()))); // 错误处理
}
@PostMapping("/signature-url")
public Mono<Response<SignatureUrlVo>> signatureUrl(@RequestBody CreateSignatureUrlCommand createSignatureUrlCommand) {
return fileApplicationService.signatureUrl(createSignatureUrlCommand)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage()))); // 错误处理
}
@PostMapping("/upload-byte-file")
public Mono<Response<UploadFileVo>> uploadByteFile(@RequestBody UploadByteFIleCommand command) {
return fileApplicationService.uploadFileByBase64(command)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage())));
}文件操作对外接口
文件对外的接口这部分接口主要包含get接口和put接口,get接口用于用户请求资源,put接口用于用户上传资源,在大文件上传的直接使用put接口上传二进制流大于使用form表单上传的效率,在200MB的清空下有着接口8-10倍的效率。
@RestController
public class FileApi {
@Autowired
FileApplicationService fileApplicationService;
@GetMapping(value = "/**")
public Mono<Void> getFileResource(ServerHttpRequest request,
ServerHttpResponse response) {
return fileApplicationService.getFileDataBuffer(request, response);
}
}package top.csmcool.filesystem.interfaces.facade;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import top.csmcool.common.model.Response;
import top.csmcool.common.utils.EncryptionUtil;
import top.csmcool.common.utils.ResponseUtil;
import top.csmcool.filesystem.application.dto.command.ChunkUploadCommand;
import top.csmcool.filesystem.application.dto.command.UploadFileCommand;
import top.csmcool.filesystem.application.dto.command.UploadSignFileCommand;
import top.csmcool.filesystem.application.dto.vo.UploadFileVo;
import top.csmcool.filesystem.application.dto.vo.WriteOssFileVo;
import top.csmcool.filesystem.application.service.FileApplicationService;
import java.io.ByteArrayOutputStream;
@RestController
@RequiredArgsConstructor
@RequestMapping("/{bucket}") // 桶名作为路径变量
public class PutFileApi {
private final FileApplicationService fileApplicationService;
@PutMapping("/{*path}")
public Mono<Response<WriteOssFileVo>> uploadFileDirect(@PathVariable String bucket,
@PathVariable String path, // 这里获取到的path会包含开头的斜杠
ServerWebExchange exchange) {
// 1. 验证桶名
if (StringUtils.isBlank(bucket) || StringUtils.isBlank(path)) {
return Mono.just(ResponseUtil.failure("Invalid bucket or file name"));
}
// 2. 组装参数
// 获取上传的文件
String objectKey = path.startsWith("/") ? path.substring(1) : path;
Flux<DataBuffer> body = exchange.getRequest().getBody();
// 2. 获取请求头和内容MD5
HttpHeaders headers = exchange.getRequest().getHeaders();
String contentMd5 = headers.getFirst("Content-MD5");
String contentType = headers.getFirst("Content-Type");
String contentLength = headers.getFirst("Content-Length");
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
String uploadId = queryParams.getFirst("uploadId");
String partNumber = queryParams.getFirst("partNumber");
UploadSignFileCommand chunkUploadCommand = UploadSignFileCommand.builder()
.key(objectKey)
.md5(contentMd5)
.contentType(contentType)
.size(contentLength != null ? Long.parseLong(contentLength) : 0)
.dataBufferFlux(body)
.bucketName(bucket)
.uploadId(uploadId)
.number(partNumber != null ? Integer.parseInt(partNumber) : 1)
.build();
return fileApplicationService.uploadSignFile(chunkUploadCommand)
.map(ResponseUtil::success)
.onErrorResume(e -> Mono.just(ResponseUtil.failure(e.getMessage()))); // 错误处理
}
}