Swift Vapor4 运用async/await和结构化并发实现一个多图上传接口

704 阅读2分钟

需求:该接口采用 POST 请求,且支持多图上传。

那么第一步,我们需要配置 FileMiddleware, FileMiddleware允许从项目的 Public 文件夹向 client 提供资源。你可以在这里存放 css 或者位图图片等静态文件。

public func configure(_ app: Application) throws {

    /// 配置文件上传最大尺寸
    app.routes.defaultMaxBodySize = "10mb"
    
    /// 配置 FileMiddleware 中间件
    app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
    
    ....
}

然后我们配置一个上传路由,实现上传功能,我们将逻辑实现的代码放到 FileController.swift 中:

//routes.swift
func routes(_ app: Application) throws {

    //
    try app.register(collection: FileController())
    ...
}

FileController.swift 中:

import Fluent
import Vapor

struct FileController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let file = routes.grouped("file")
        file.post("upload", use: uploadImage)
    }
}

extension FileController {
    private func uploadImage(_ req: Request) async throws -> OutJson<[String]> {
        let inUpload = try req.content.decode(InUpload.self)
        let formatter = DateFormatter()
        formatter.dateFormat = "y-m-d-HH-MM-SS-"

        let result = try await withThrowingTaskGroup(of: String.self, returning: [String].self, body: { (group) async throws in 
            for file in inUpload.files {
                group.addTask { 
                    let prefix = formatter.string(from: .init())
                    let parentDir = "\(inUpload.dir)/"
                    let dir = req.application.directory.publicDirectory + parentDir
                    let fileName = prefix + file.filename
                    let path = dir + fileName
                    // 创建目录
                    try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: nil)
                    try await req.fileio.writeFile(file.data, at: path)
                    return parentDir + fileName
                }
            }
            var images = [String]()
            for try await img in group {
                images.append(img)
            }
            return images 
        })
        return OutJson(success: result)
    }

}

FileController 的主要逻辑就是:

  1. 获取到接口参数
  2. 遍历参数中的files: [File],拼接好参数,进行保存到指定位置中
  3. 让图片路径返回给请求端

对于接口参数的解析:

let inUpload = try req.content.decode(InUpload.self)

InUpload.swift 也非常简单:


import Vapor

struct InUpload: In {
    var files: [File]
    var type: Int // 表情 = 0,壁纸 = 1,图片 = 2, 文件=3

    // 根据 type 保存到不同的文件夹中
    var dir: String {
        if type == 0 {
            return "emotions"
        }
        else if type == 1 {
            return "wallpaper"
        }
        else if type == 2 {
            return "images"
        } 
        else if type == 3 {
            return "files"
        } else {
            return "other"
        }
    }
}

图片的拼接逻辑,根据type的不同,保存到不同的目录中。

在保存图片的时候,我们使用了 Swift 的结构化并发 withThrowingTaskGroup, 具体使用可以看上面的代码。

添加完成上面代码,我们可以通过 Postman 测试:

多图上传的时候,接收的 files 是个数组,我们可以将 Key 设置为 files[下标] 方式,Value 类型选择文件即可。

通过上面的简单说明,相信你也可以非常容易的实现文件上传接口了。

如果想阅读更多文章,不妨关注 OldBirds 微信公众号。