操作负责响应来自 Web 浏览器、移动应用程序或任何其他能够与服务器通信的系统的请求。它们通常充当您模型和视图之间的中间人,并协调项目的业务逻辑的大部分:您可以使用操作来提供网页、处理表单提交、管理第三方 API 请求以及介于两者之间的一切。
操作绑定到应用程序中的路由。当用户代理请求特定 URL 时,绑定到该路由的操作执行其中的业务逻辑并发送回响应。例如,应用程序中的GET /hello
路由可以绑定到类似的操作
async function (req, res) {
return res.send('Hi there!');
}
每当 Web 浏览器导航到应用程序服务器上的/hello
URL 时,页面都会显示消息:“你好!”。
操作定义在api/controllers/
文件夹和子文件夹中(稍后我们将详细介绍控制器)。为了使 Sails 识别文件是操作,文件名必须是kebab-cased(仅包含小写字母、数字和连字符)。在 Sails 中引用操作时(在大多数情况下,是在将其绑定到路由时),使用其相对于api/controllers
的路径,不带任何文件扩展名。例如,要将路由绑定到位于api/controllers/user/find.js
中的操作,您需要将 URL 指向user/find
。
默认情况下,Sails 只知道如何解释.js
文件,但您可以自定义应用程序以使用诸如CoffeeScript或TypeScript之类的内容。操作可以具有任何文件扩展名,但.md
(Markdown)和.txt
(文本)除外。
操作文件可以使用两种格式之一:actions2(推荐)或经典。
自 Sails v1.0 发布以来,我们建议您以更现代的“actions2”语法编写操作,该语法与 Sails 的助手的工作方式基本相同。通过以这种方式定义操作,它们本质上是自文档和自验证的。
使用 actions2 提供了几个优势
sails generate action
快速创建 actions2 文件exits
)都清晰可见,无需剖析代码req
和res
,这使得它更容易重用或抽象到助手中请注意,在使用 actions2 时,您可以将请求对象访问为
this.req
。
或者,您可以将env
与inputs
和exits
一起传递到函数中,以在不使用this.req
的情况下访问req
。
简而言之,您的代码将以一种更容易在以后重用和修改的方式进行标准化。并且由于您将在提前声明操作的参数,因此您不太可能暴露边缘情况和安全漏洞。
以下是 actions2 格式的示例
module.exports = {
friendlyName: 'Welcome user',
description: 'Look up the specified user and welcome them, or redirect to a signup page if no user was found.',
inputs: {
userId: {
description: 'The ID of the user to look up.',
// By declaring a numeric example, Sails will automatically respond with `res.badRequest`
// if the `userId` parameter is not a number.
type: 'number',
// By making the `userId` parameter required, Sails will automatically respond with
// `res.badRequest` if it's left out.
required: true
}
},
exits: {
success: {
responseType: 'view',
viewTemplatePath: 'pages/welcome'
},
notFound: {
description: 'No user with the specified ID was found in the database.',
responseType: 'notFound'
}
},
fn: async function ({userId}) {
// Look up the user whose ID was specified in the request.
// Note that we don't have to validate that `userId` is a number;
// the machine runner does this for us and returns `badRequest`
// if validation fails.
var user = await User.findOne({ id: userId });
// If no user was found, respond "notFound" (like calling `res.notFound()`)
if (!user) { throw 'notFound'; }
// Display a personalized welcome view.
return {
name: user.name
};
}
};
Sails 使用machine-as-action模块,从像上面示例中那样格式化的操作中自动创建路由处理函数。有关更多信息,请参阅machine-as-action 文档。
在操作、助手或脚本中,抛出任何内容都将默认触发error
退出。如果要触发任何其他退出,可以通过抛出“特殊退出信号”来做到这一点。这将是一个字符串(退出的名称),或一个以退出名称为键,输出数据为值的 对象。例如,而不是使用通常的语法
return exits.hasConflictingCourses();
您可以使用简写
throw 'hasConflictingCourses';
或者,要包含输出数据
throw { hasConflictingCourses: ['CS 301', 'M 402'] };
除了易于阅读的简写之外,退出信号在您位于for
循环、forEach
等内部,但仍希望通过特定退出退出时特别有用。
如果您正在使用现有的代码库或从 v0.12 升级的应用程序,您可能更习惯于经典操作格式。经典操作声明为具有req
和res
参数的函数。当客户端请求绑定到此类操作的路由时,该函数使用传入请求对象作为第一个参数(req
),并使用传出响应对象作为第二个参数(res
)运行。
以下是一个示例操作,它通过 ID 查找用户,然后要么显示“欢迎”视图,要么在找不到用户时重定向到注册页面
module.exports = async function welcomeUser (req, res) {
// Get the `userId` parameter from the request.
// This could have been set on the querystring, in
// the request body, or as part of the URL used to
// make the request.
var userId = req.param('userId');
// If no `userId` was specified, or it wasn't a number, return an error.
if (!_.isNumeric(userId)) {
return res.badRequest(new Error('No user ID specified!'));
}
// Look up the user whose ID was specified in the request.
var user = await User.findOne({ id: userId });
// If no user was found, redirect to signup.
if (!user) {
return res.redirect('/signup' );
}
// Display the welcome view, setting the view variable
// named "name" to the value of the user's name.
return res.view('welcome', {name: user.name});
}
您可以使用
sails generate action
并使用--no-actions2
快速创建经典操作。
对于更简单的项目和原型,开始编写 Sails 应用程序的最快方法通常是将操作组织到控制器文件中。控制器文件是一个PascalCased文件,其名称必须以Controller
结尾,包含操作的字典。例如,可以在api/controllers/UserController.js
文件中创建一个“用户控制器”,其中包含
module.exports = {
login: function (req, res) { ... },
logout: function (req, res) { ... },
signup: function (req, res) { ... },
};
您可以使用sails generate controller
快速创建控制器文件。
与操作文件一样,您可以自定义应用程序以使用诸如CoffeeScript或TypeScript之类的内容,尽管 Sails 默认情况下只知道如何解释.js
文件。控制器可以具有任何文件扩展名,但.md
(Markdown)和.txt
(文本)除外。
对于更大、更成熟的应用程序,独立操作可能比控制器文件更好。在这种方案中,每个操作都位于api/controllers
的适当子文件夹中的自己的文件中,而不是将多个操作放在一个文件中。例如,以下文件结构将等效于UserController.js
文件
api/
controllers/
user/
login.js
logout.js
signup.js
使用独立操作与控制器文件相比,具有几个优势
foo/bar/baz.js
与foo/BarController.baz
)api/controllers/index.js
文件,并将其自动绑定到应用程序的/
路由(而不是创建任意控制器文件来保存根操作)遵循大多数 MVC 框架的传统,成熟的 Sails 应用程序通常具有“瘦”控制器,也就是说,您的操作代码最终变得精简,因为可重用代码已移至助手中,或者有时甚至提取到单独的节点模块中。这种方法肯定可以使您的应用程序在复杂度不断增加的情况下更容易维护。
但与此同时,过早地将代码提取到可重用的助手中会导致浪费时间和效率的维护问题。正确的答案介于两者之间。
Sails 建议使用以下一般经验法则:**在您要使用相同代码段第三次之前,请勿将其提取到单独的助手中。**但是,与任何教条一样,请使用您的判断!如果所讨论的代码非常长或复杂,那么可能很早就将其提取到助手中会更合理。相反,如果您知道您正在构建的是一个快速的一次性原型,您可能只需要复制粘贴代码来节省时间。
无论您是出于热情还是利润而开发,归根结底,目标是尽可能充分地利用您作为工程师的时间。有时这意味着编写更多代码,而有时则意味着考虑项目的长期可维护性。如果您不确定在您当前的开发阶段中哪个目标更重要,您可能需要退后一步,仔细考虑一下(最好是与您的团队其他成员或在 Node.js/Sails 上构建应用程序的其他人员交谈)。