带有蒸气2的服务器端Swift(第1部分)

在2015年Swift开源之前,我曾尝试用Node.js编写一个简单的RESTful API服务器。 但是,由于我对Javascript语法和后端开发不熟悉,所以没有足够的时间来深入研究。 如今,有几种不同的服务器端Swift框架,例如Vapor,Perfect和Kitura。 作为iOS开发人员,这是拓展后端开发视野的好机会。

根据其GitHub页面,Vapor是Swift最常用的Web框架。 它为您的下一个网站,API或云项目提供了精美而易用的基础。 更重要的是,raywenderlich.com网站上有一系列免费教程。 这些教程为我提供了如何使用Vapor构建API服务器的基本概念,即使它们是在先前版本中完成的。 谢谢雷!

在本文中,我将演示一个简单的RESTful API服务器,该服务器将处理三种模型: LessonTeacherStudent 。 基本上,我们需要以下端点:

  1. /{models} :通过GET请求获取所有模型对象,或者通过带有JSON正文的POST请求创建新的模型对象。 (只需将{models}替换为课程,教师和学生。)
  2. /{models}/id :通过GET请求获取特定的模型对象,通过DELETE请求删除一个模型对象,或通过带有JSON正文的PATCH请求更新现有模型对象。

在我们开始编码之前,有几种必要的配置。 首先,按照Vapor网站上的说明正确安装Vapor,然后使用vapor new your_project_name --template=api创建一个新项目。 其次,在该项目中,我选择使用PostgreSQL作为数据库服务器,因此有必要在Macbook上安装PostgreSQL,并将蒸气的PostgreSQL提供程序添加到我的项目依赖项中。 启动PostgreSQL服务器后,按如下所示打开并修改Package.swift文件。

 let package = Package( // ... dependencies: [ .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), .Package(url: "https://github.com/vapor-community/postgresql-provider.git", majorVersion: 2) ], // ... ) 

然后,在目录Config/secrets下创建一个名为postgresql.json的新文件,并将以下代码片段添加到postgresql.json文件中。

 { "hostname": your_host_address, "user": your_username, "password": your_password, "database": your_database_name, "port": 5432 } 

请记住用以下命令替换Config/fluent.json的驱动程序。

 "driver": "postgresql" 

最后一步是在Config+Setup.swift文件中添加PostgreSQL提供程序。

  import PostgreSQLProvider extension Config { // ... private func setupProviders() throws { try addProvider(FluentProvider.Provider.self) try addProvider(PostgreSQLProvider.Provider.self) } // ... } extension Config { // ... private func setupProviders() throws { try addProvider(FluentProvider.Provider.self) try addProvider(PostgreSQLProvider.Provider.self) } // ... } 

完成所有配置后,使用vapor buildvapor xcode获取必要的依赖关系并生成一个新的Xcode项目。

首先让我们处理模型类。 打开我们在上一步中生成的Xcode项目,然后将Lesson.swiftTeacher.swiftStudent.swift文件添加到Models组中。 在将新文件添加到我们的项目时,请确保选择目标为App。

此外,请按照Vapor的文档正确定义我们的模型。 因为它们的实现非常相似,所以我只显示Lesson.swift如下。

  import PostgreSQLProvider final class Lesson: Model { let storage = Storage() // This is for Storable protocol final class Lesson: Model { let storage = Storage() // This is for Storable protocol  final class Lesson: Model { let storage = Storage() // This is for Storable protocol  var title: String  // Use these keys instead of magic strings static let idKey = "id" static let titleKey = "title"  // Use these keys instead of magic strings static let idKey = "id" static let titleKey = "title"   // Use these keys instead of magic strings static let idKey = "id" static let titleKey = "title"  init(title: String) { self.title = title }  init(title: String) { self.title = title }   init(title: String) { self.title = title }  init(row: Row) throws { self.title = try row.get(Lesson.titleKey) }  init(row: Row) throws { self.title = try row.get(Lesson.titleKey) }   init(row: Row) throws { self.title = try row.get(Lesson.titleKey) }  func makeRow() throws -> Row { var row = Row() try row.set(Lesson.titleKey, title) return row } }  func makeRow() throws -> Row { var row = Row() try row.set(Lesson.titleKey, title) return row } }   func makeRow() throws -> Row { var row = Row() try row.set(Lesson.titleKey, title) return row } } // For database prepare and revert extension Lesson: Preparation { static func prepare(_ database: Database) throws { try database.create(self) { (user) in user.id() user.string(Lesson.titleKey) } } // For database prepare and revert extension Lesson: Preparation { static func prepare(_ database: Database) throws { try database.create(self) { (user) in user.id() user.string(Lesson.titleKey) } }  // For database prepare and revert extension Lesson: Preparation { static func prepare(_ database: Database) throws { try database.create(self) { (user) in user.id() user.string(Lesson.titleKey) } }  static func revert(_ database: Database) throws { try database.delete(self) } }  static func revert(_ database: Database) throws { try database.delete(self) } }   static func revert(_ database: Database) throws { try database.delete(self) } } // Convenience of generate model from JSON extension Lesson: JSONConvertible { convenience init(json: JSON) throws { self.init(title: try json.get(Lesson.titleKey)) } // Convenience of generate model from JSON extension Lesson: JSONConvertible { convenience init(json: JSON) throws { self.init(title: try json.get(Lesson.titleKey)) }  // Convenience of generate model from JSON extension Lesson: JSONConvertible { convenience init(json: JSON) throws { self.init(title: try json.get(Lesson.titleKey)) }  func makeJSON() throws -> JSON { var json = JSON() try json.set(Lesson.idKey, id?.string) try json.set(Lesson.titleKey, title) return json } }  func makeJSON() throws -> JSON { var json = JSON() try json.set(Lesson.idKey, id?.string) try json.set(Lesson.titleKey, title) return json } }   func makeJSON() throws -> JSON { var json = JSON() try json.set(Lesson.idKey, id?.string) try json.set(Lesson.titleKey, title) return json } } // Convenience of returning response extension Lesson: ResponseRepresentable {} // Convenience of returning response extension Lesson: ResponseRepresentable {} 

创建模型后,切换到Config+Setup.swift文件并添加准备。

 private func setupPreparations() throws { preparations.append(Lesson.self) preparations.append(Teacher.self) preparations.append(Student.self) } 

接下来,也将与我们的模型相对应的控制器添加到Controllers组中。 在每个控制器内部,我们可以利用Vapor的ResourceRepresentable协议的优势来处理模型的CRUD。 再次,为简单起见,我仅显示以下内容的LessonController.swift实现。

  import PostgreSQLProvider final class LessonController { fileprivate func getAll(request: Request) throws -> ResponseRepresentable { return try Lesson.all().makeJSON() } final class LessonController { fileprivate func getAll(request: Request) throws -> ResponseRepresentable { return try Lesson.all().makeJSON() }  final class LessonController { fileprivate func getAll(request: Request) throws -> ResponseRepresentable { return try Lesson.all().makeJSON() }  fileprivate func getOne(request: Request, lesson: Lesson) throws -> ResponseRepresentable { return lesson }  fileprivate func getOne(request: Request, lesson: Lesson) throws -> ResponseRepresentable { return lesson }   fileprivate func getOne(request: Request, lesson: Lesson) throws -> ResponseRepresentable { return lesson }  fileprivate func create(request: Request) throws -> ResponseRepresentable { let lesson = try request.lesson() try lesson.save() return lesson }  fileprivate func create(request: Request) throws -> ResponseRepresentable { let lesson = try request.lesson() try lesson.save() return lesson }   fileprivate func create(request: Request) throws -> ResponseRepresentable { let lesson = try request.lesson() try lesson.save() return lesson }  fileprivate func update(request: Request, lesson: Lesson) throws -> ResponseRepresentable { let newLesson = try request.lesson() lesson.title = newLesson.title try lesson.save() return lesson }  fileprivate func update(request: Request, lesson: Lesson) throws -> ResponseRepresentable { let newLesson = try request.lesson() lesson.title = newLesson.title try lesson.save() return lesson }   fileprivate func update(request: Request, lesson: Lesson) throws -> ResponseRepresentable { let newLesson = try request.lesson() lesson.title = newLesson.title try lesson.save() return lesson }  fileprivate func delete(request: Request, lesson: Lesson) throws -> ResponseRepresentable { try lesson.delete() return lesson } }  fileprivate func delete(request: Request, lesson: Lesson) throws -> ResponseRepresentable { try lesson.delete() return lesson } }   fileprivate func delete(request: Request, lesson: Lesson) throws -> ResponseRepresentable { try lesson.delete() return lesson } } // Notice the difference between Item and Muliple extension LessonController: ResourceRepresentable { func makeResource() -> Resource { return Resource( index: getAll, store: create, show: getOne, update: update, destroy: delete ) } } // Notice the difference between Item and Muliple extension LessonController: ResourceRepresentable { func makeResource() -> Resource { return Resource( index: getAll, store: create, show: getOne, update: update, destroy: delete ) } }  // Notice the difference between Item and Muliple extension LessonController: ResourceRepresentable { func makeResource() -> Resource { return Resource( index: getAll, store: create, show: getOne, update: update, destroy: delete ) } } // Convenience of retrieving Lesson object extension Request { fileprivate func lesson() throws -> Lesson { guard let json = json else { throw Abort.badRequest } // Convenience of retrieving Lesson object extension Request { fileprivate func lesson() throws -> Lesson { guard let json = json else { throw Abort.badRequest }  // Convenience of retrieving Lesson object extension Request { fileprivate func lesson() throws -> Lesson { guard let json = json else { throw Abort.badRequest }  return try Lesson(json: json) } }  return try Lesson(json: json) } } 

最后,通过Router.swift添加到Router.swift文件中,连接我们的控制器和Droplet对象。

 func setupRoutes() throws { let lessonController = LessonController() resource("lessons", lessonController)  func setupRoutes() throws { let lessonController = LessonController() resource("lessons", lessonController)  let teacherController = TeacherController() resource("teachers", teacherController)  let teacherController = TeacherController() resource("teachers", teacherController)   let teacherController = TeacherController() resource("teachers", teacherController)  let studentController = StudentController() resource("students", studentController) }  let teacherController = TeacherController() resource("teachers", teacherController)  let studentController = StudentController() resource("students", studentController) }  let studentController = StudentController() resource("students", studentController) } 

至此,我们实现了一个简单的RESTful API服务器,并且可以使用Postman测试模型CRUD。 在本系列的第2部分中,我将演示如何在两个模型之间实现同级(多对多)关系。