第31章:高级Fluent
¶
在本书的前几节中,你学到了如何使用Fluent
来对数据库进行查询。你还学习了如何对模型进行CRUD
操作。在这一章中,你将了解到Fluent
的一些更高级的功能。你将看到如何用enum
保存模型,并使用Fluent
的软删除和时间戳功能。你还将学习如何使用原始SQL
和连接,以及如何"急于加载"关系。
开始学习¶
本章的入门项目是基于第21
章末尾的TIL
应用程序。你可以使用该项目中的代码,也可以使用本章书中的启动项目。这个项目依赖于一个在本地运行的PostgreSQL
数据库。
清理现有数据库¶
如果你在前面的章节中一直跟随,你需要删除现有的数据库。本章包含了模型的改变,需要恢复你的数据库或删除它。在终端,键入:
docker rm -f postgres
如果名为postgres
的Docker
容器正在运行,这将停止它并删除它。
创建一个新的数据库¶
在Docker
中创建一个新的数据库供TIL
应用程序使用。在终端,键入:
docker run --name postgres \
-e POSTGRES_DB=vapor_database \
-e POSTGRES_USER=vapor_username \
-e POSTGRES_PASSWORD=vapor_password \
-p 5432:5432 -d postgres
以下是这样做的:
- 运行一个名为
postgres
的新容器。 - 通过环境变量指定数据库的名称、用户名和密码。
- 允许应用程序连接到
PostgreSQL
服务器的默认端口:5432
. - 在后台作为一个守护程序运行服务器。
- 为这个容器使用名为
postgres
的Docker
镜像。如果你的机器上没有这个镜像,Docker
会自动下载它。
关于如何在项目中配置数据库的更多信息,见第6章,"配置数据库"。
软删除¶
在第7章,"CRUD
数据库操作"中,你学会了如何从数据库中删除模型。然而,虽然你可能希望模型对用户来说是被删除的,但你可能不希望实际删除它们。你也可以有法律或公司的要求,强制保留数据。Fluent
提供了软删除功能,允许你这样做。在Xcode
中打开TIL
应用程序,进入User.swift
。寻找:
var acronyms: [Acronym]
并在下面添加以下定义:
@Timestamp(key: "deleted_at", on: .delete)
var deletedAt: Date?
这为Fluent
增加了一个新的属性,用于存储你对模型进行软删除的日期。你用@Timestamp
来注解这个属性。当你调用delete(on:)
时,Fluent
会检查这个属性的封装器。如果.delete
动作的属性存在,Fluent
会在该属性上设置当前日期并保存更新的模型。否则,它将从数据库中删除该模型。这就是在Fluent中实现软删除所需要的全部内容!
接下来,打开CreateUser.swift
。在prepare(on:)
中,在.unique(on: "username")
前添加:
.field("deleted_at", .datetime)
这就为迁移添加了一个字段,这样Fluent
就为新属性创建了正确的列。
打开UsersController.swift
,创建一个路由来使用新功能。在loginHandler(_:)
下面,添加以下内容:
func deleteHandler(_ req: Request)
-> EventLoopFuture<HTTPStatus> {
User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound)).flatMap { user in
user.delete(on: req.db).transform(to: .noContent)
}
}
这将删除作为参数传递的用户,并返回一个204 No Content
的响应。最后,你需要注册这个路由。在boot(routes:)
的末尾添加以下内容:
tokenAuthGroup.delete(":userID", use: deleteHandler)
这就把对/api/users/<user_ID>
的DELETE
请求路由到deleteHandler(_:)
。建立并运行Vapor
应用程序。在RESTed
中,使用预先定义的admin
用户,用正确的HTTP基本认证凭证向http://localhost:8080/api/users/login
发送请求,以获得一个令牌。请参阅第18章,"API
认证,第一部分",以了解如何做到这一点的复习情况。
接下来,创建一个新的请求,并进行如下配置:
- URL: http://localhost:8080/api/users
- method: POST
- Parameter encoding: JSON-encoded
- header: Authorization: Bearer
添加三个带有名称和值的参数:
- username: a username of your choice
- name: a name of your choice
- password: a password of your choice
点击Send Request
。这将在应用程序中创建一个用户:
接下来,发送一个删除新用户的请求。配置该请求如下:
- URL: http://localhost:8080/api/users/
- method: DELETE
- header: Authorization: Bearer
点击Send Request
。你应该看到一个204 No Content
的响应,表明你成功地对用户进行了软删除。最后,配置一个请求来获取所有的用户:
- URL: http://localhost:8080/api/users/
- method: GET
点击Send Request
。你会注意到,尽管你只是软删除了该用户,但它并没有出现在所有用户的列表中:
恢复用户¶
尽管现在应用程序允许你软删除用户,但你可能想在未来的某一天恢复它们。首先,在UsersController.swift
的顶部import Vapor
下面添加以下内容:
import Fluent
这样你就可以使用Fluent
的过滤函数。接下来,在deleteHandler(_:)
下面创建一个新的路由处理程序来恢复一个用户:
func restoreHandler(_ req: Request)
throws -> EventLoopFuture<HTTPStatus> {
// 1
let userID =
try req.parameters.require("userID", as: UUID.self)
// 2
return User.query(on: req.db)
.withDeleted()
.filter(\.$id == userID)
.first()
.unwrap(or: Abort(.notFound))
.flatMap { user in
// 3
user.restore(on: req.db).transform(to: .ok)
}
}
以下是发生的情况:
- 从请求的参数中获取用户的
ID
作为UUID
。 - 执行一个查询,找到具有该
ID
的用户。withDeleted()
告诉Fluent
包括软删除的模型。 - 对用户调用
restore(on:)
来恢复该用户。将响应转换为200 OK
。
最后,注册路由处理程序。在boot(route:)
的末尾添加以下内容:
tokenAuthGroup.post(":userID", "restore", use: restoreHandler)
这将一个POST
请求映射到/api/users/**<**UID>/restore
到restoreHandler(_:)
。建立并运行应用程序,打开RESTed
。配置一个请求,如下所示,使用你上面删除的用户的UUID
:
- URL: http://localhost:8080/api/users/
/restore - method: POST
- header: Authorization: Bearer
点击Send Request
。你会收到一个200 OK
的响应,表明你已经恢复了用户。
如果你不再有用户的UUID
,你可以在终端中使用以下魔法来检索它:
docker exec -it postgres psql -U vapor_username vapor_database
select id from "users" where username = ’<your username>’;
\q
配置一个最终请求,如下所示:
- URL: http://localhost:8080/api/users/
- method: GET
点击Send Request
。被恢复的用户现在出现在用户列表中:
强制删除¶
现在你可以软删除和恢复用户,你可能想增加正确删除用户的能力。你可以使用强制删除来实现这个目的。回到Xcode
中,仍然在UsersController.swift
中,创建一个新的路由来完成这个任务。在restoreHandler(_:)
下面添加以下内容:
func forceDeleteHandler(_ req: Request)
-> EventLoopFuture<HTTPStatus> {
User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
user.delete(force: true, on: req.db)
.transform(to: .noContent)
}
}
你的代码与deleteHandler(_:)
相似。然而,这一次你在模型上调用delete(force:on:)
。将force
设置为true
可以绕过软删除,将模型从数据库中删除。
最后,注册路由处理程序。在boot(routes:)
的末尾添加以下内容:
tokenAuthGroup.delete(
":userID",
"force",
use: forceDeleteHandler)
这将一个DELETE
请求路由到/api/users/<user_ID>/force
到forceDeleteHandler(_:)
。构建并运行应用程序,回到RESTed
。配置一个新的请求,如下所示:
- URL: http://localhost:8080/api/users/
/force - method: DELETE
- header: Authorization: Bearer
点击Send Request
,你会收到一个204 No Content
的回应。配置一个最后的请求如下:
- URL: http://localhost:8080/api/users/
/restore - method: POST
- header: Authorization: Bearer
点击Send Request
。你会收到一个404 Not Found
的错误,因为模型在要恢复的数据库中已不存在:
时间戳¶
Fluent
内置了对模型的创建时间和更新时间的时间戳功能。事实上,你在上面使用了一个来实现软删除功能。如果你配置了这些,Fluent
会自动设置和更新这些时间。要启用这一点,在Xcode
中打开Acronym.swift
。在var categories: [Category]
为日期添加两个新属性:
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
@Timestamp(key: "updated_at", on: .update)
var updatedAt: Date?
就像软删除一样,Fluent
在创建和更新模型时寻找这些时间戳。如果它们存在,Fluent
就会设置这些日期。现在,打开CreateAcronym.swift
。在prepare(on:)
中,在.create()
之前添加以下内容:
.field("created_at", .datetime)
.field("updated_at", .datetime)
这样就把这两个新字段添加到了迁移中,这样Fluent
就在数据库中创建了这些列。这就是所需的全部内容! 创建一个新的路由处理程序来使用该功能。
打开AcronymsController.swift
,在removeCategoriesHandler(_:)
下面添加以下内容:
func getMostRecentAcronyms(_ req: Request)
-> EventLoopFuture<[Acronym]> {
Acronym.query(on: req.db)
.sort(\.$updatedAt, .descending)
.all()
}
这条路由返回所有缩写,按updatedAt
排序。该排序使用降序,以确保最新的出现在前面。关于如何使用sort(_:)
的更多信息,请参阅第7章,"CRUD
数据库操作"。当你创建模型时,Fluent
会设置createdAt
。当你创建模型时,Fluent
也会设置updatedAt
,并在你更新模型时设置。在boot(routes:)
下面的acronymsRoutes.get(":acronymID", "categories", use: getCategoriesHandler)
中注册这个路由,内容如下:
acronymsRoutes.get("mostRecent", use: getMostRecentAcronyms)
这将一个GET
请求路由到/api/acronyms/mostRecent
到getMostRecentAcronyms(_:)
。在你运行应用程序之前,你必须更新或重置数据库,为Acronym
添加新字段。为了节省时间,本章将重置Docker
数据库。要使用迁移来改变表,请参阅第27章,"数据库/API
的版本和迁移"。在终端,运行以下命令:
docker rm -f postgres
docker run --name postgres \
-e POSTGRES_DB=vapor_database \
-e POSTGRES_USER=vapor_username \
-e POSTGRES_PASSWORD=vapor_password \
-p 5432:5432 -d postgres
这些命令在Docker
中停止、删除和重新创建PostgreSQL
数据库,如本章开头所述。最后,构建并运行应用程序,打开RESTed
。创建一些缩写,记住你需要先登录,如第18章"API
认证,第1部分"中所述。
Tips
你可能会发现使用Web
界面来添加缩略语更简单,在你的浏览器中访问http://localhost:8080。
接下来,在RESTed
中配置一个新的请求,如下所示:
- URL: http://localhost:8080/api/acronyms/
- method: PUT
- header: Authorization: Bearer
这就更新了创建的第一个缩写。添加两个有名称和值的参数:
- short:与原首字母缩写相同的短语,例如:OMG
- long:缩写的最新含义,例如:Oh My Gosh
点击Send Request
来更新缩写。最后,在RESTed中配置一个新的请求,如下:
- URL: http://localhost:8080/api/acronyms/mostRecent
- method: GET
点击Send Request
,获得所有缩略语的列表,按最近更新排序。你会看到第一个缩略语出现在列表的首位,因为你最后一次更新了它:
枚举¶
对数据库列的一个常见要求是将值限制在一个预定义的集合中。FluentPostgreSQL
和FluentMySQL
都支持enum
的功能。为了证明这一点,你将为用户添加一个类型来定义基本的用户访问级别。在Xcode
中,在Sources/App/Models
创建一个名为UserType.swift
的新文件。打开这个新文件并添加以下内容:
import Foundation
// 1
enum UserType: String, Codable {
// 2
case admin
case standard
case restricted
}
以下是新代码的作用:
- 创建一个新的
String
枚举类型,UserType
,符合Codable
。该类型必须是一个String
枚举,以符合Codable
。 - 定义三种类型的用户访问,在
Vapor
应用程序中使用。
接下来,打开User.swift
,在下面添加一个新属性var deletedAt: Date?
来存储用户的类型:
@Enum(key: "userType")
var userType: UserType
这为User
增加了一个新的属性。你用@Enum
来注解这个属性。这是一种特殊的Field
属性包装器,用于存储本地数据库的enum
。改变初始化器以支持新属性:
init(
id: UUID? = nil,
name: String,
username: String,
password: String,
userType: UserType = .standard
) {
self.name = name
self.username = username
self.password = password
self.userType = userType
}
这样就把新创建的用户类型默认为标准用户。最后,在CreateAdminUser.swift
中,将let user = User(...)
改为如下:
let user = User(
name: "Admin",
username: "admin",
password: passwordHash,
userType: .admin)
这使得管理员用户成为admin
类型。然后,打开CreateUser.swift
。将prepare(on:)
的主体替换为以下内容:
// 1
database.enum("userType")
// 2
.case("admin")
.case("standard")
.case("restricted")
// 3
.create()
.flatMap { userType in
database.schema("users")
.id()
.field("name", .string, .required)
.field("username", .string, .required)
.field("password", .string, .required)
.field("deleted_at", .datetime)
// 4
.field("userType", userType, .required)
.unique(on: "username")
.create()
}
以下是新代码的作用:
- 使用
enum(_:)
设置一个数据库枚举。这类似于使用schema(_:_)
来设置一个表。 - 为你的枚举定义不同的情况。
- 调用
create()
来在数据库中创建枚举。使用flatMap(_:)
等待创建完成。flatMap(_:)
的闭包接收创建的枚举类型。 - 使用枚举类型在
users
表中为新属性定义一个新字段。
接下来,打开UsersController.swift
来使用这个新属性。将deleteHandler(_:)
的函数签名替换为以下内容:
func deleteHandler(_ req: Request)
throws -> EventLoopFuture<HTTPStatus> {
这允许你在函数体中抛出错误。接下来,将deleteHandler(_:)
的主体替换为以下内容:
// 1
let requestUser = try req.auth.require(User.self)
// 2
guard requestUser.userType == .admin else {
throw Abort(.forbidden)
}
// 3
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
user.delete(on: req.db)
.transform(to: .noContent)
}
所做的改变是:
- 从请求中获取认证的用户。
- 确保认证的用户是一个管理员。这确保只有管理员可以删除其他用户。否则,抛出一个
403 Forbidden
响应。 - 像以前一样,删除请求参数中指定的用户。
使用前面的命令重置数据库,然后建立并运行应用程序。打开RESTed
,以管理员用户身份登录,获得一个令牌。配置一个新的请求,如下所示:
- URL: http://localhost:8080/api/users
- method: POST
- Parameter encoding: JSON-encoded
- header: Authorization: Bearer
添加四个带有名称和值的参数:
- username: a username of your choice
- name: a name of your choice
- password: a password of your choice
- userType: standard
点击Send Request
来创建用户。改变数值以创建另一个要删除的用户,并点击Send Request
。记下第二个用户的ID
。以你创建的第一个用户的身份登录,并按如下方式配置另一个请求:
- URL: http://localhost:8080/api/users/
- method: DELETE
- Parameter encoding: JSON-encoded
- header: Authorization: Bearer
点击Send Request
,你会收到一个403 Forbidden
的回应:
改变Authorization
标题,使用来自管理用户的令牌,并再次点击Send Request
。这次请求成功了,你会收到一个204 No Content
的响应:
Note
为了更加完整,你应该对forceDeleteHandler(_:)
和restoreHandler(_:)
做同样的修改。这是给读者留下的一个练习。
生命周期钩子¶
Fluent
允许你使用模型中间件来钩住模型生命周期的各个环节。这些中间件的工作方式与其他中间件类似,允许你在不同事件前后执行代码。关于中间件的更多信息,请参见第29章,"中间件"。Fluent
允许你为以下事件添加中间件。
create
:当Fluent
创建一个模型时被调用。update
:当Fluent
更新一个模型时被调用。delete
:当Fluent
删除一个模型时被调用。softDelete
:当Fluent
软删除一个模型时被调用。restore
:当Fluent
恢复一个模型时被调用。
这些钩子允许你为你的模型添加额外的检查,填充或删除字段或添加额外的步骤,如日志信息。为了证明这一点,在Sources/App/Models
中创建一个新文件,名为UserMiddleware.swift
。打开新文件并添加以下内容:
import Fluent
import Vapor
// 1
struct UserMiddleware: ModelMiddleware {
// 2
func create(
model: User,
on db: Database,
next: AnyModelResponder) -> EventLoopFuture<Void> {
// 3
User.query(on: db)
.filter(\.$username == model.username)
.count()
.flatMap { count in
// 4
guard count == 0 else {
let error =
Abort(
.badRequest,
reason: "Username already exists")
return db.eventLoop.future(error: error)
}
// 5
return next.create(model, on: db).map {
// 6
let errorMessage: Logger.Message =
"Created user with username \(model.username)"
db.logger.debug(errorMessage)
}
}
}
}
以下是新代码的作用:
- 创建一个符合
ModelMiddleware
的新类型。 - 实现
create(model:on:next:)
,在创建用户之前进行额外检查。 - 查询数据库以获得具有新用户的用户名的用户数量。
- 确保没有具有该用户名的用户,否则返回一个失败的未来,并给出
AbortError
和原因。这将向客户端返回一个比数据库约束违反信息更好的错误信息。返回一个失败的未来将取消保存。你仍然应该使用数据库约束来断言一个用户名是唯一的,以防两个用户在同一时间试图用相同的用户名注册。 - 将下一个响应者链起来,以允许其他中间件运行。
- 保存完成后,向控制台记录一条信息。你可以在
Fluent
保存模型后在此运行额外的代码。
使用ModelMiddleware
来验证唯一的用户名是很有用的,因为你只需要在一个地方做这件事。TIL
应用程序包含两个创建用户的地方 - API
和网站。通过使用ModelMiddleware
,你不需要重复的逻辑来确保用户名是唯一的。
最后,打开configure.swift
来注册这个中间件。在app.migrations.add(CreateAdminUser())
下面添加以下内容:
app.databases.middleware.use(UserMiddleware(), on: .psql)
这将UserMiddleware
注册到psql
,以确保每当你创建一个User
时它都会运行。
构建并运行应用程序,如果你还没有一个令牌的话,请登录以获得一个令牌。在RESTed
中,配置一个新的请求,如下所示:
- URL: http://localhost:8080/api/users
- method: POST
- Parameter encoding: JSON-encoded
- header: Authorization: Bearer
添加四个带有名称和值的参数:
- username: admin
- name: Admin
- password: password
- userType: admin
点击Send Request
,你会看到返回的错误信息,因为管理员的用户名已经存在:
急于加载和嵌套模型¶
如果你遵循一个严格的REST API
,你应该在一个单独的请求中检索一个模型的子代。然而,这并不总是理想的,你可能希望能够发送一个单一的请求来获取所有模型及其所有子模型。例如,在TIL
应用程序中,你可能想要一个返回所有类别及其所有缩写的路由。你甚至可能想要返回所有类别及其所有用户的首字母缩写。这通常被称为N+1问题,Fluent
通过急于加载的方式使其变得简单。打开CategoriesController.swift
,在文件的底部添加以下内容:
struct AcronymWithUser: Content {
let id: UUID?
let short: String
let long: String
let user: User.Public
}
struct CategoryWithAcronyms: Content {
let id: UUID?
let name: String
let acronyms: [AcronymWithUser]
}
这定义了两个新的类型,在返回所有类别及其首字母缩写和首字母缩写的用户时使用。在getAcronymsHandler(_:)
下面,添加代码来执行查询:
func getAllCategoriesWithAcronymsAndUsers(_ req: Request)
-> EventLoopFuture<[CategoryWithAcronyms]> {
// 1
Category.query(on: req.db)
// 2
.with(\.$acronyms) { acronyms in
// 3
acronyms.with(\.$user)
// 4
}.all().map { categories in
// 5
categories.map { category in
// 6
let categoryAcronyms = category.acronyms.map {
AcronymWithUser(
id: $0.id,
short: $0.short,
long: $0.long,
user: $0.user.convertToPublic())
}
// 7
return CategoryWithAcronyms(
id: category.id,
name: category.name,
acronyms: categoryAcronyms)
}
}
}
以下是新的路径处理程序的作用:
- 对
Category
进行查询,以获得所有的类别。 - 使用
with(_:)
急于加载类别的首字母缩写。with(_:)
接受一个关键路径来急于加载关系 - 在这个例子中,$acronyms
。 with(_:)
也接受一个可选的闭包,允许你嵌套急切的负载。这允许你同时在Acronym
上急于加载$user
。Fluent
会计算出它需要为你执行的查询。- 使用
all()
来完成查询并获得所有的结果。 - 循环浏览所有返回的类别,将它们转换为
CategoryWithAcronyms
。 - 将所有类别的首字母转换成
AcronymWithUser
。当你急于加载一个模型的关系时,你可以直接访问该属性。你不需要像前几章那样通过属性包装器。请注意。如果你在没有急于加载关系的情况下这样做,你会得到一个致命的错误。 - 返回转换为
CategoryWithAcronyms
的类别。
最后,在boot(routes:)
下面注册路由categoriesRoute.get(":categoryID", "acronyms", use: getAcronymsHandler)
:
categoriesRoute.get(
"acronyms",
use: getAllCategoriesWithAcronymsAndUsers)
向/api/categories/acronyms
的GET
请求路由到getAllCategoriesWithAcronymsAndUsers(_:)
。建立并运行应用程序,并创建一些用户和首字母缩写和类别。在RESTed
中,配置一个新的请求,如下:
- URL: http://localhost:8080/api/categories/acronyms
- method: GET
点击Send Request
,你会看到所有的类别都有它们的首字母缩写,首字母缩写也有它们的用户:
连接¶
有时,你想在检索信息时查询其他表。例如,你可能想获得创建最近的缩写的用户。你可以通过急于加载和Swift
来完成这个任务。你可以通过获取所有的用户并急于加载他们的首字母缩写来做到这一点。然后,你可以按照创建日期对缩写进行排序,得到最新的缩写并返回其用户。然而,这意味着将所有的用户和他们的首字母缩写加载到内存中,即使你不需要它们,这也是低效的。连接允许你通过指定共同值将一个表的列与另一个表的列结合起来。例如,你可以用用户的ID
将缩写表和用户表结合起来。然后你可以在不同的表中进行排序,甚至过滤。
打开UsersController.swift
,在forceDeleteHandler(_:)
下面添加一个路由处理程序,以获得最近创建缩略语的用户:
func getUserWithMostRecentAcronym(_ req: Request)
-> EventLoopFuture<User.Public> {
// 1
User.query(on: req.db)
// 2
.join(Acronym.self, on: \Acronym.$user.$id == \User.$id)
// 3
.sort(Acronym.self, \Acronym.$createdAt, .descending)
// 4
.first()
.unwrap(or: Abort(.internalServerError))
.convertToPublic()
}
以下是新代码的作用:
- 对
User
进行查询。 - 通过连接用户的
ID
和缩写的用户的$id
值,将User
与Acronym
连接起来。 - 对
Acronym
进行排序,并对createdAt
属性进行排序,以获得最新的首字母缩写。你可以使用排序和过滤器与连接。 - 返回第一个用户,如果不存在则返回一个内部服务器错误。数据库应该总是包含至少一个具有管理员身份的用户。注意这只是返回
User
模型,而不是缩略语。
在boot(routes:)
下注册路由usersRoute.get(":userID", "acronyms", use: getAcronymsHandler)
:
usersRoute.get(
"mostRecentAcronym",
use: getUserWithMostRecentAcronym)
这就把对/api/users/mostRecentAcronym
的GET
请求路由到getUserWithMostRecentAcronym(_:)
。建立并运行应用程序,并启动RESTed
。配置一个新的请求,如下所示:
- URL: http://localhost:8080/api/users/mostRecentAcronym
- method: GET
点击Send Request
,你会看到创建最近的缩写的用户:
原始SQL
¶
虽然Fluent
提供的工具可以让你建立很多不同的行为,但有一些高级的功能它并没有提供。Fluent
不支持查询不同的模式或聚合函数。在一个复杂的应用程序中,你可能会发现有些情况下Fluent
并不能提供你需要的功能。在这种情况下,你可以使用原始的SQL
查询来直接与数据库交互。这使得你可以执行数据库支持的任何类型的查询。
在AcronymsController.swift
中,在文件顶部import Fluent
下面添加以下内容:
import SQLKit
这使你可以看到原始查询的必要方法。接下来,在getMostRecentAcronyms(_:)
下面,添加:
func getAllAcronymsRaw(_ req: Request)
throws -> EventLoopFuture<[Acronym]> {
// 1
guard let sql = req.db as? SQLDatabase else {
throw Abort(.internalServerError)
}
// 2
return sql.raw("SELECT * FROM acronyms")
// 3
.all(decoding: Acronym.self)
}
以下是代码的作用:
- 将
Request
上的数据库转换为SQLDatabase
,允许你执行原始查询。如果转换失败,返回500 Internal Server Error
。 - 使用
raw(_:)
在数据库上创建一个原始查询。
Note
你必须小心谨慎,对查询中的任何输入进行消毒,以避免注入攻击。raw(_:)
在必要时支持参数绑定。
- 获取所有的结果并将行解码为
Acronym
。尽管这使用了一个原始查询,你仍然使用Codable
来转换数据库中的数据,从而提供了类型安全。
在boot(routes:)
下面的acronymsRoutes.get("mostRecent", use: getMostRecentAcronyms)
中注册新路线,内容如下:
acronymsRoutes.get("raw", use: getAllAcronymsRaw)
这就把一个GET
请求路由到/api/acronyms/raw
的getAllAcronymsRaw(_:)
。建立并运行你的应用程序,并前往RESTed
。配置一个最终请求,如下所示:
- URL: http://localhost:8080/api/acronyms/raw
- method: GET
点击Send Request
,你会看到所有的缩略语返回:
接下来去哪?¶
在本章中,你学到了如何使用Fluent
提供的一些高级功能来执行复杂的查询。你还看到了如果Fluent
不能满足你的需要,如何发送原始SQL查询。
有了高级功能的知识,你现在应该可以用Vapor
和Fluent
来构建任何东西了!