如何为 iOS 应用程序使用 vapor 3 上传模型文件
How to upload files for a model using vapor 3 for an iOS app
我想制作一个 iOS 应用程序,使用 Vapor 3 作为我的后端。我创建的用于表示我的对象的模型包含一些属性,这些属性将是 .png 和 .plist 文件等文件。每当我发出 POST 请求时,我无法理解如何使用 multipart 来获取这些文件并将它们发送到我的模型端点。
我也对我应该将这些文件属性设置在我的模型中的数据类型感到困惑 class。在 "Content" 部分下的多部分文档 (https://docs.vapor.codes/3.0/multipart/overview/#content) 中,他们说要创建一个 Struct 并将其图像设置为 Data 类型,但也说您可以将其设置为 File 类型。我还看到了将其设为 String 类型的示例。
我希望有人能澄清我应该将这些属性的数据类型设置为什么,以及如何将这些文件上传到我的 Controllers/ModelController 中,我在那里进行保存和调用。post () 在我的 boot(router: Router) 函数中
我已经查看了 Multipart vapor 文档并通读了这些 Whosebug posts 但仍然不明白当我尝试使用 post 方法时我应该做什么:
-
-
-
这是我的模型class:
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)
}
}
我见过的一些示例涉及为网络应用程序上传,它们 return 一个 Future< View > 但由于我正在做一个 iOS 应用程序,我不知道如果我应该 returning 一个 HTTPResponseStatus 或我的模型对象。
请帮忙,我尽力表达好,我是 Vapor 的新手
Server-side
型号
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)
}
}
}
}
服务器文件结构
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]`
}
希望对您有所帮助。
我想制作一个 iOS 应用程序,使用 Vapor 3 作为我的后端。我创建的用于表示我的对象的模型包含一些属性,这些属性将是 .png 和 .plist 文件等文件。每当我发出 POST 请求时,我无法理解如何使用 multipart 来获取这些文件并将它们发送到我的模型端点。
我也对我应该将这些文件属性设置在我的模型中的数据类型感到困惑 class。在 "Content" 部分下的多部分文档 (https://docs.vapor.codes/3.0/multipart/overview/#content) 中,他们说要创建一个 Struct 并将其图像设置为 Data 类型,但也说您可以将其设置为 File 类型。我还看到了将其设为 String 类型的示例。
我希望有人能澄清我应该将这些属性的数据类型设置为什么,以及如何将这些文件上传到我的 Controllers/ModelController 中,我在那里进行保存和调用。post () 在我的 boot(router: Router) 函数中
我已经查看了 Multipart vapor 文档并通读了这些 Whosebug posts 但仍然不明白当我尝试使用 post 方法时我应该做什么:
-
这是我的模型class:
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)
}
}
我见过的一些示例涉及为网络应用程序上传,它们 return 一个 Future< View > 但由于我正在做一个 iOS 应用程序,我不知道如果我应该 returning 一个 HTTPResponseStatus 或我的模型对象。
请帮忙,我尽力表达好,我是 Vapor 的新手
Server-side
型号
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)
}
}
}
}
服务器文件结构
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]`
}
希望对您有所帮助。