平时在写博客的时候,一般用的Typora,文章和图片都会存在本地,到了转到线上博客的时候,图片还要一个个上传。Typora本身自带图片上传的功能,配合PicGo使用,不过自己想多折腾一下,这时就想建个文件服务器,迁移文章的时候也方便了,也让我那吃灰的服务器有了用途。😄

使用到的技术栈

  • MinIO 分布式文件存储系统 ,当然也可以选择直接存到本地
  • SpringBoot
  • Powershell

后端准备

 搭建完MinIO后,打开管理界面:

image-20210715150650325

后端接口直接使用minio-client完成文件操作即可。

文件上传代码如下,其中省去对数据库的操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String upload(MultipartFile multipartFile) throws IOException {
        String originalFilename = multipartFile.getOriginalFilename();
        if (originalFilename == null) throw new FileUploadException();
        InputStream originalFileInputStream = multipartFile.getInputStream();
        String year = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy"));
        String decodeFileName = URLDecoder.decode(originalFilename, StandardCharsets.UTF_8);
        if (decodeFileName.contains("\\")) { //windows
            decodeFileName = decodeFileName.substring(decodeFileName.lastIndexOf("\\") + 1);
        } else if (decodeFileName.contains("/")) { //linux
            decodeFileName = decodeFileName.substring(decodeFileName.lastIndexOf("/") + 1);
        }
        String bucketName = bucketName + year;
        String uuid = UUID.randomUUID().toString().replace("-", "");
		    String finalName = uuid + decodeFileName.substring(decodeFileName.lastIndexOf("."));
        boolean createBucketFlag = minIOService.createBucket(bucketName);
        if (createBucketFlag) {
            minIOService.upload(bucketName, finalName, originalFileInputStream);
           } else {
             throw new FileUploadException();
           }
        return this.serverProtocol + "://" + this.serverDomain + ("dev".equals(profilesActive) ? ":" + this.serverPort : "") + "/file/preview/" + uuid;
    }

文件上传完成后需要返回相应的预览链接,预览代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public void filePreview(String uuid, HttpServletResponse response) throws IOException {
        ZFile zFile = getOneCache(ZFile::getFileUuid, uuid);
        if (zFile == null) {
            HttpResponseUtil.renderJson(Result.notFound(Object.class), response);
            return;
        }
        String fileName = zFile.getFileName();
        if (fileName == null) {
            HttpResponseUtil.renderJson(Result.notFound(Object.class), response);
            return;
        }
        String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
        String finalName = uuid + fileSuffix;
        GetObjectResponse objectResponse = minIOService.getFile(zFile.getFileBucket(), finalName);
        response.setContentType(ContentType.get(fileSuffix));
        IoUtil.copy(objectResponse, response.getOutputStream());
    }

配合PowerShell脚本

 使用Typora的custom command line,完成调用ps脚本来上传,脚本内容如下:

 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

$uri = "http://127.0.0.1:8080/file/upload"

function post { 
  param (
    [string]$filePath
  )
$fileBytes = [System.IO.File]::ReadAllBytes($filePath);
$fileEnc = [System.Text.Encoding]::GetEncoding('ISO-8859-1').GetString($fileBytes);
$boundary = [System.Guid]::NewGuid().ToString(); 
$LF = "`r`n";
#$encodedFilePath = [System.Web.HttpUtility]::UrlEncode($filePath) 
$encodedFilePath =  [uri]::EscapeDataString($filePath)
$bodyLines = ( 
    "--$boundary", 
    "Content-Disposition: form-data; name=`"file`"; filename=`"$encodedFilePath`"",
    "Content-Type: application/octet-stream$LF",
    $fileEnc
 "--$boundary--$LF" 
) -join $LF

Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines
}

for ($i = 2; $i -lt $args.Count; $i++) {
  post($args[$i])
}

调用脚本内容如下:

1
powershell C:\Users\Gary\OneDrive\Documents\uploadFile.ps1  ${filename}

完成以上配置即可。