sharp-fileupload客户端Restful(四)

客户端api可以用来上传、下载、浏览文件等操作,由 DocumentService 完成服务操作。DocumentService 主要工作:

  • 管理数据库维护元数据
  • FileStore 来完成存储
  • 依赖 ImageService 完成图片类型的浏览

准备工作

创建表

CREATE TABLE `sys_document` (
  `id` bigint(20) NOT NULL,
  `name` varchar(255) NOT NULL,
  `extension` varchar(16) DEFAULT NULL,
  `content_type` varchar(16) DEFAULT NULL,
  `size` int(11) DEFAULT NULL,
  `group_name` varchar(255) NOT NULL,
  `path` varchar(255) NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

添加依赖

<dependency>
    <groupId>com.rick.db</groupId>
    <artifactId>sharp-database</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

配置数据库

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/fastdfs?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2b8
    username: root
    password: jkxyx205

接口测试

批量上传

  • 请求
POST /documents/upload HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

Content-Disposition: form-data; name="file"; filename="/Users/rick/Desktop/StockSnap_STDZONQNPW.jpg
Content-Disposition: form-data; name="file"; filename="/Users/rick/Desktop/java技能树.jpg


------WebKitFormBoundary7MA4YWxkTrZu0gW--
  • 后端处理
@PostMapping("/upload")
public Result<List<?>> fileUpload(MultipartHttpServletRequest multipartRequest) throws IOException {
    return ResultUtils.success(documentService.store(FileMetaUtils.parse(multipartRequest, UPLOAD_NAME), "upload"));
}

@PostMapping("/upload2")
public Result<List<?>> fileUpload2(@RequestParam(UPLOAD_NAME) List<MultipartFile> fileList) throws IOException {
    return ResultUtils.success(documentService.store(FileMetaUtils.parse(fileList), "upload"));
}

@PostMapping("/upload3")
public Result<List<?>> fileUpload3(@RequestBody List<FileMeta> fileMetaList) throws IOException {
    return ResultUtils.success(documentService.store(fileMetaList, "upload"));
}
  • 响应
{
    "success": true,
    "code": 0,
    "msg": "OK",
    "data": [
        {
            "name": "StockSnap_STDZONQNPW",
            "extension": "jpg",
            "contentType": "image/jpeg",
            "size": 30079171,
            "groupName": "upload",
            "path": "475304840076365824.jpg",
            "url": "http://localhost:7892/upload/475304840076365824.jpg",
            "id": 475304840302858240,
            "createdAt": "2021-10-01T04:24:42.474Z",
            "fullName": "StockSnap_STDZONQNPW.jpg",
            "fullPath": "upload/475304840076365824.jpg"
        },
        {
            "name": "java技能树",
            "extension": "jpg",
            "contentType": "image/jpeg",
            "size": 337707,
            "groupName": "upload",
            "path": "475304840239943680.jpg",
            "url": "http://localhost:7892/upload/475304840239943680.jpg",
            "id": 475304840332218368,
            "createdAt": "2021-10-01T04:24:42.481Z",
            "fullName": "java技能树.jpg",
            "fullPath": "upload/475304840239943680.jpg"
        }
    ]
}

查看信息

  • 请求
GET /documents/475304840302858240 HTTP/1.1
  • 后端处理
/**
 * 文件详情
 * @return
 */
@GetMapping(value = "/{id}")
public Result<Document> documentInfo(@PathVariable Long id) {
    return ResultUtils.success(documentService.findById(id));
}
  • 响应
{
    "success": true,
    "code": 0,
    "msg": "OK",
    "data": {
        "name": "StockSnap_STDZONQNPW",
        "extension": "jpg",
        "contentType": "image/jpeg",
        "size": 30079171,
        "groupName": "upload",
        "path": "475304840076365824.jpg",
        "url": "http://localhost:7892/upload/475304840076365824.jpg",
        "id": 475304840302858240,
        "createdAt": "2021-10-01T04:24:42Z",
        "fullName": "StockSnap_STDZONQNPW.jpg",
        "fullPath": "upload/475304840076365824.jpg"
    }
}

下载

  • 请求
# 单文件下载

http://localhost:8080/documents/download?id=475308098194935808

# 批量下载

http://localhost:8080/documents/download?id=475308098194935808&id=475308098169769984

  • 后端处理
@GetMapping(value = "/download/{id}")
public void download(HttpServletRequest request, HttpServletResponse response, @PathVariable long id) throws IOException {
    documentService.download(request, response , id);
}

@GetMapping(value = "/download")
public void download(HttpServletRequest request, HttpServletResponse response, @RequestParam(name = "id") long[] ids) throws IOException {
    documentService.download(request, response, ids);
}

浏览

  • 请求
# 客户端跳转

http://localhost:8080/documents/preview/475308098194935808

# 图片进行处理

http://localhost:8080/documents/preview2/475308098194935808?rw=9&rh=5

# office预览必须带上文件扩展名

https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fa923-112-87-216-2.ngrok.io%2Fdocuments%2Fpreview2%2F477893013692383232/小程序部署准备工作.docx


https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fa923-112-87-216-2.ngrok.io%2Fdocuments%2Fpreview2%2F477897073204035588/演示.pptx


https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fa923-112-87-216-2.ngrok.io%2Fdocuments%2Fpreview2%2F477897073204035587/需求.xlsx

Note: the needs to be URL encoded, and the document must be publicly accessible on the internet.

微软接口文档

  • 后端处理
 /**
 * 预览文件: 通过nginx,这种访问速度会更快些
 * http://localhost:8080/documents/preview/475029213054144512
 * @return
 * @throws IOException
 */
@GetMapping(value = "/preview/{id:\\d+}")
public void view(HttpServletResponse response, @PathVariable Long id) throws IOException {
    response.sendRedirect(documentService.getURL(id));
}

/**
 * 预览普通文件,将文件写到流中
 * http://localhost:8080/documents/preview2/475029213054144512
 * 预览office,必须有后缀docx/xlsx/pptx,否则会File not found
 * https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fa923-112-87-216-2.ngrok.io%2Fdocuments%2Fpreview2%2F477896371325009920/hello.docx
 * @return
 * @throws IOException
 */
@GetMapping(value = {"/preview2/{id:\\d+}", "/preview2/{id:\\d+}/{fileName}.{extension:(?i)docx|xlsx|pptx}"})
public void view2(HttpServletRequest request, HttpServletResponse response, @PathVariable Long id, ImageParam imageParam) throws IOException {
    Document document = documentService.findById(id);
    documentService.preview(id, imageParam, HttpServletResponseUtils.getOutputStreamAsView(request, response, document.getFullName()));
    }

重命名

  • 请求
PUT /documents/475029213070921728/rename?name=hello world HTTP/1.1```
  • 后端处理
@PutMapping("/{id}/rename")
public Result rename(@PathVariable Long id,  String name) {
    documentService.rename(id, name);
    return ResultUtils.success();
}

删除

  • 请求
DELETE /documents/475010776193994752 HTTP/1.1
  • 响应
{
    "success": true,
    "code": 0,
    "msg": "OK"
}
  • 后端处理
    @DeleteMapping(value = "/{id}")
    public Result deleteDocument(@PathVariable Long id) {
        documentService.delete(id);
        return ResultUtils.success();
    }
  • 响应
{
    "success": true,
    "code": 0,
    "msg": "OK"
}

sharp-fileupload图片处理ImageService(三)

创建名字图片

方法

/**
 * 创建名字图片
 * @param text 张三
 * @param groupName 存储
 * @param storeName 存储的文件名,可能是用户的id
 * @return
 * @throws IOException
 */
public String createImage(String text, String groupName, String storeName) throws IOException

测试

String url = imageService.createImage("张三", "header");
String url = imageService.createImage("张三", "header", "Rick");

输出

http://localhost:7892/header/475290263729115136.png

http://localhost:7892/header/Rick.png

http://xhope.top/wp-content/uploads/2021/10/475098507762896896.png

图片处理

public FileMeta cropPic(FileMeta fileMeta, ImageParam imageParam) throws IOException;

测试

@Test
@Order(1)
public void testCropPic() throws IOException {
    File file = new File("/Users/rick/jkxyx205/tmp/fileupload/demo/1.jpg");
    FileMeta fileMeta = FileMetaUtils.parse(file);

    // 裁剪9:5
    ImageParam imageParam = new  ImageParam();
    imageParam.setRw(9);
    imageParam.setRh(5);

    FileMeta cropPicFileMeta = imageService.cropPic(fileMeta, imageParam);
    // 将裁剪的存储到磁盘
    List<? extends FileMeta> crop = fileStore.storeFileMeta(Arrays.asList(cropPicFileMeta), "crop");
    System.out.println(crop.get(0).getUrl());
}

图片浏览

依赖图片裁剪,通过Response的输出流实现图片浏览的功能

public void write(FileMeta fileMeta, ImageParam imageParam, OutputStream os) throws IOException

测试

/**
 * http://localhost:8080/images/475036437923139584?p=0&rw=9&rh=5&r=30&w=500
 * 原图按9:5裁剪,旋转30度,宽度500像素
 * 图片查看
 * @param request
 * @param response
 * @param imageParam
 */
@GetMapping("/{id}")
public void preview(HttpServletRequest request, HttpServletResponse response, @PathVariable("id") Long id, ImageParam imageParam) throws IOException {
    Document document = documentService.findById(id);
    imageService.write(document, imageParam, HttpServletResponseUtils.getOutputStreamAsView(request, response, document.getFullName()));
}

sharp-fileupload文件存储FileStore(二)

FileStore 依赖 inputStreamStore,同时实现了inputStreamStore接口。主要是处理文件文件上传。

方法

  • 处理web端文件请求
public List<? extends FileMeta> upload(List<MultipartFile> multipartFileList, String groupName);
  • 处理本地文件
public List<? extends FileMeta> storeFiles(List<File> fileList, String groupName)
  • 处理byte[]的文件
    FileMeta#data是一个字节数组
public List<? extends FileMeta> storeFileMeta(List<? extends FileMeta> fileMetaList, String groupName)

FileMeta.java

@Getter
@Setter
public class FileMeta {

    private String name;

    private String extension;

    private String contentType;

    private Long size;

    private String groupName;

    private String path;

    @Transient
    @JsonIgnore
    private byte[] data;

    @Transient
    private String url;

    /**
     * 获取完整名称
     * @return
     */
    public String getFullName() {
        if (StringUtils.isBlank(this.getExtension())) {
            return this.getName();
        }

        return this.getName() + (StringUtils.isEmpty(this.getExtension()) ? "" : "." + this.getExtension());
    }

    public String getFullPath() {
        return this.getGroupName() + "/" + getPath();
    }

    /**
     * 设置完整名称
     * @param fullName
     */
    public void setFullName(String fullName) {
        String fileName = com.rick.common.util.StringUtils.stripFilenameExtension(fullName);
        String fileExt = com.rick.common.util.StringUtils.getFilenameExtension(fullName);
        setName(fileName);
        setExtension(fileExt);
    }
}

测试

@Test
public void testStore() throws IOException {
    File file = new File("/Users/rick/jkxyx205/tmp/fileupload/demo/1.jpg");
    FileMeta fileMeta = FileMetaUtils.parse(file);
    fileStore.storeFileMeta(Lists.newArrayList(fileMeta), "upload").get(0);
}

sharp-fileupload流存储InputStreamStore(一)

简介

sharp-fileupload 可以上传文件,对图片的处理(裁剪、选装、缩放)。提供了Restfull接口上传,访问文档。底层存储功能支持:

  • 本地存储
  • 阿里云OSS
  • FastDFS

添加依赖

<dependency>
    <groupId>com.rick.fileupload</groupId>
    <artifactId>sharp-fileupload</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

配置存储策略

本地存储

默认就是本地存储。

application.xml

fileupload:
  tmp: /Users/rick/jkxyx205/tmp/fastdfs/tmp # 下载的临时目录
  local:
    server-url: http://localhost:7892/ # 映射到tmp目录
    root-path: /Users/rick/jkxyx205/tmp/fileupload

文件存储的路径是 root-path。在本地开启一个静态服务器指向存储地址。访问url是 server-url

OSS

创建 Bucketname为「sharp-fileupload」
application.xml

fileupload:
  tmp: /Users/rick/jkxyx205/tmp/fastdfs/tmp # 下载的临时目录
  oss:
    endpoint: oss-cn-beijing.aliyuncs.com
    accessKeyId: xxx
    accessKeySecret: xxx
    bucketName: sharp-fileupload

添加依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>

配置类

@Bean
public InputStreamStore ossInputStreamStore(OSSProperties ossProperties) {
    OSS ossClient = new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
    return new OSSInputStreamStore(ossClient, ossProperties);
}

FastDFS

fdfs_client.properties

fastdfs.tracker_servers=192.168.0.117:22122
fastdfs.http_tracker_http_port=8080

添加依赖

<dependency>
    <groupId>org.csource</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27-SNAPSHOT</version>
</dependency>

配置类

@Bean
public InputStreamStore fastDFSInputStreamStore() throws IOException, MyException {
    return new FastDFSInputStreamStore("fdfs_client.properties");
}

测试InputStreamStore

接口信息:

public interface InputStreamStore {

    StoreResponse store(String groupName, String extension, InputStream is) throws IOException;

    /**
     *
     * @param groupName
     * @param storeName 磁盘存储的文件名
     * @param extension 扩展名
     * @param is
     * @return
     * @throws IOException
     */
    StoreResponse store(String groupName, String storeName, String extension, InputStream is) throws IOException;

    /**
     * 删除文件
     * @param groupName
     * @param path
     * @return
     */
    void delete(String groupName, String path) throws IOException;

    /**
     * 获取访问地址
     * @param groupName
     * @param path
     * @return
     */
    String getURL(String groupName, String path);

    /**
     * 获取文件流
     * @param groupName
     * @param path
     * @return
     * @throws IOException
     */
    InputStream getInputStream(String groupName, String path) throws IOException;

     /**
     * 获取字节数据
     * @param groupName
     * @param path
     * @return
     * @throws IOException
     */
    byte[] getByteArray(String groupName, String path) throws IOException;
}

测试代码:

@Autowired
private InputStreamStore inputStreamStore;

private static String path;

@Test
@Order(1)
public void testPropertyStore() throws IOException {
    StoreResponse response = inputStreamStore.store("group", "jpeg",
            new FileInputStream("/Users/rick/jkxyx205/tmp/fileupload/demo/1.jpg"));
    System.out.println(response.getGroupName());
    System.out.println(response.getPath());
    System.out.println(response.getFullPath());
    System.out.println(response.getUrl());
    path = response.getPath();
}

@Test
@Order(2)
public void getURL() {
    String url = inputStreamStore.getURL("group", path);
    System.out.println(url);
}

@Test
@Order(3)
public void getInputStream() throws IOException {
    InputStream is = inputStreamStore.getInputStream("group", path);
    FileUtils.copyInputStreamToFile(is, new File("/Users/rick/jkxyx205/tmp/fileupload/download/1.png"));
    is.close();
}

@Test
@Order(Order.DEFAULT)
public void testPropertyDelete() throws IOException {
    inputStreamStore.delete("group", path);
}