文件系统主要存放文件,实现于存放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()))); // 错误处理
    }

}