Как загрузить файлы для модели с помощью пары 3 для приложения iOS - PullRequest
3 голосов
/ 25 июня 2019

Я хочу сделать приложение для iOS, используя Vapor 3 в качестве моего бэкэнда. Модель, которую я создаю для представления моего объекта, содержит некоторые свойства, которые будут такими файлами, как файлы .png и .plist. У меня возникают проблемы с пониманием того, как использовать multipart для захвата этих файлов и отправки их на конечную точку моей модели всякий раз, когда я выполняю запрос POST.

Я также запутался в том, какой тип данных я должен установить, чтобы эти свойства файла были в моем классе Model. В составных документах (https://docs.vapor.codes/3.0/multipart/overview/#content) в разделе «Содержимое») говорится, что они создают Struct, и они устанавливают для своего изображения тип данных, но также говорят, что вы можете сделать его типом «Файл». видел примеры, когда они делают его типа String.

Я надеялся, что кто-то сможет уточнить, что именно я должен установить для типов данных этих свойств, и как я могу загрузить эти файлы в мой Controllers / ModelController, где я делаю сохранение и вызываю .post () в моей загрузке (маршрутизатор: Маршрутизатор) функция

Я уже просмотрел многокомпонентную паровую документацию и прочитал эти посты stackoverflow, но все еще не понимаю, что мне следует делать, когда я пытаюсь использовать метод post: - Vapor загружать несколько файлов одновременно - Как обрабатывать многочастный запрос с помощью Vapor 3 - Загрузка изображений в Vapor 3 с использованием PostgreSQL

Это мой модельный класс:

import Vapor
import FluentMySQL

final class AppObject: Codable {
  var id: Int?
  var plistFile: String // file
  var imageFile: String // file
  var notes: String
  var name: String

  init(ipaFile: String, plistFile: String, imageFile: String, notes: String, name: String) {
    self.ipaFile = ipaFile
    self.plistFile = plistFile
    self.imageFile = imageFile
    self.notes = notes
    self.name = name
  }
}

extension AppObject: MySQLModel {}
extension AppObject: Content {}
extension AppObject: Migration {}
extension AppObject: Parameter {}

Это мой контроллер для вышеуказанной модели:

import Vapor
import Fluent

struct AppObjectsController: RouteCollection {


    func boot(router: Router) throws {
        let appObjectsRoute = router.grouped("api", "apps")
        appObjectsRoute.get(use: getAllHandler)
        appObjectsRoute.post(AppObject.self, use: createHandler)
    }

    func getAllHandler(_ req: Request) throws -> Future<[AppObject]> {
        return AppObject.query(on: req).all()
    }

    // what else should I be doing here in order to upload actual files?
    func createHandler(_ req: Request, appobject: AppObject) throws -> Future<AppObject> {
         return appobject.save(on: req)
    }
}

некоторые примеры, которые я видел, имеют дело с загрузкой для веб-приложения, и они возвращают Future , но, поскольку я работаю с приложением для iOS, я не знаю, должен ли я возвращать HTTPResponseStatus или мою модель объект.

Пожалуйста, помогите, я старался изо всех сил сказать это хорошо, я новичок в Vapor

1 Ответ

2 голосов
/ 25 июня 2019

Серверный

Модель

final class AppObject: Codable {
    var id: Int?
    var ipaFile: String // relative path to file in Public dir
    var plistFile: String // relative path to file in Public dir
    var imageFile: String // relative path to file in Public dir
    var notes: String
    var name: String

    init(ipaFile: String, plistFile: String, imageFile: String, notes: String, name: String) {
        self.ipaFile = ipaFile
        self.plistFile = plistFile
        self.imageFile = imageFile
        self.notes = notes
        self.name = name
    }
}

extension AppObject: MySQLModel {}
extension AppObject: Content {}
extension AppObject: Migration {}
extension AppObject: Parameter {}

Контроллер

struct AppObjectsController: RouteCollection {
    func boot(router: Router) throws {
        let appObjectsRoute = router.grouped("api", "apps")
        appObjectsRoute.get(use: getAllHandler)
        appObjectsRoute.post(PostData.self, use: createHandler)
    }

    func getAllHandler(_ req: Request) throws -> Future<[AppObject]> {
        return AppObject.query(on: req).all()
    }
}

extension AppObjectsController {
    struct PostData: Content {
        let ipaFile, plistFile, imageFile: File
        let name, notes: String
    }

    func createHandler(_ req: Request, payload: PostData) throws -> Future<AppObject> {
        let ipaFile = ServerFile(ext: "ipa", folder: .ipa)
        let plistFile = ServerFile(ext: "plist", folder: .plist)
        let imageFile = ServerFile(ext: "jpg", folder: .image)
        let appObject = AppObject(ipaFile: ipaFile.relativePath, plistFile: plistFile.relativePath, imageFile: imageFile.relativePath, notes: payload.notes, name: payload.name)
        /// we have to wrap it in transaction
        /// to rollback object creating
        /// in case if file saving fails
        return req.transaction(on: .mysql) { conn in
            return appObject.create(on: conn).map { appObject in
                try ipaFile.save(with: payload.ipaFile.data)
                try plistFile.save(with: payload.plistFile.data)
                try imageFile.save(with: payload.imageFile.data)
            }
        }
    }
}

Структура ServerFile

struct ServerFile {
    enum Folder: String {
        case ipa = "ipa"
        case plist = "plists"
        case image = "images"
        case root = ""
    }

    let file, ext: String
    let folder: Folder

    init (file: String? = UUID().uuidString, ext: String, folder: Folder? = .root) {
        self.file = file
        self.ext = ext
        self.folder = folder
    }

    var relativePath: String {
        guard folder != .root else { return fileWithExt }
        return folder.rawValue + "/" + fileWithExt
    }

    var fileWithExt: String { return file + "." + ext }

    func save(with data: Data) throws {
        /// Get path to project's dir
        let workDir = DirectoryConfig.detect().workDir
        /// Build path to Public folder
        let publicDir = workDir.appending("Public")
        /// Build path to file folder
        let fileFolder = publicDir + "/" + folder.rawValue
        /// Create file folder if needed
        var isDir : ObjCBool = true
        if !FileManager.default.fileExists(atPath: fileFolder, isDirectory: &isDir) {
            try FileManager.default.createDirectory(atPath: fileFolder, withIntermediateDirectories: true)
        }
        let filePath = publicDir + "/" + relativePath
        /// Save data into file
        try data.write(to: URL(fileURLWithPath: filePath))
    }
}

IOS

Заявить AppObject модель

struct AppObject: Codable {
    var id: Int
    var ipaFile, plistFile, imageFile: String
    var name, notes: String
}

С библиотекой CodyFire многокомпонентные запросы действительно просты

Объявить вашу конечную точку

import CodyFire

struct AppController: EndpointController {
    static var server: ServerURL? = nil
    static var endpoint: String = "apps"
}

/// Usually separate file like App+Create.swift
extension AppController {
    struct CreateAppRequest: MultipartPayload {
        var ipaFile, plistFile, imageFile: Attachment
        var name, note: String
        public init (ipaFile: Attachment, plistFile: Attachment, imageFile: Attachment, name: String, note: String) {
            self.ipaFile = ipaFile
            self.plistFile = plistFile
            self.imageFile = imageFile
            self.name = name
            self.note = note
        }
    }

    static func create(_ payload: CreateAppRequest) -> APIRequest<AppObject> {
        return request(payload: payload).method(.post)
    }
}

Затем в каком-либо контроллере представления попытайтесь создать приложение на сервере

/// Replace _ with file data
let ipaFile = Attachment(data: _, fileName: "", mimeType: "ipa")
let plistFile = Attachment(data: _, fileName: "", mimeType: "plist")
let imageFile = Attachment(data: _, fileName: "", mimeType: .jpg)

let payload = AppController.CreateAppRequest(ipaFile: ipaFile, 
                                             plistFile: plistFile,
                                             imageFile: imageFile,
                                             name: "something", 
                                             note: "something")

AppController.create(payload).onRequestStarted {
    /// it calls only if request started properly
    /// start showing loading bar
}.onError { error in
    let alert = UIAlertController(title: nil, message: error.description, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .cancel))
    self.present(alert, animated: true)
}.onProgress { progress in
    /// show progress
}.onSuccess { appObject in
    /// show success
    /// here you received just created `appObject`
}

И это все, это просто работает:)

Следующий пример получения списка AppObject s

/// Separate file like App+List.swift
extension AppController {
    static func list() -> APIRequest<[AppObject]> {
        return request()
    }
}

тогда где-то в поле зрения контроллера

AppController.list().onSuccess { appObjects in
    /// `appObjects` is `[AppObject]`
}

Надеюсь, это поможет.

...