Sails项目结构中api包包含了后端的主要逻辑。其中包含了多个主要目录:
2.1 api/controllers/
此目录中的js文件包含了与models的交互逻辑与向客户端渲染视图。
2.1.1 总述
Actions是Sails应用中用来处理web请求的主要对象。
Actions和应用中的routes绑定,当用户请求一个URL时,被绑定的action会执行逻辑和响应。
2.1.2 actions样例
actions文件有两种形式:classic和actions2。
2.1.2.1 classic形式
传统的方法新建一个action就是像函数一样声明。当客户端请求一个绑定路径的action时,此方法会形成一个请求对象作为第一个参数,形成一个响应对象作为第二个参数。如下是一个例子,通过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});
}
2.1.2.2 actions2形式
与Sails helpers的工作方式类似,通过一种描述性定义来定义actions,它本质上是自文档化与自验证性。例子如下:
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 (inputs, exits) {
// 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: inputs.userId });
// If no user was found, respond "notFound" (like calling `res.notFound()`)
if (!user) { return exits.notFound(); }
// Display the welcome view.
return exits.success({name: user.name});
}
};
使用传统的res,req方法可以减少代码量,但actions2有以下好处:
1. 代码不会直接依赖res和req,使其可以更好的复用和抽象入helpers。
2. 可以迅速决定action需要的请求参数的名字和类型,而且在action运行之前可以自动验证。
3. 可以不用分析代码而看到运行action所有可能的结果。
在一个容器中,代码可以标准化,使之可以更容易复用和修改。因为可以首先声明action的参数,可以更少可能暴露底部用例和安全漏洞。
2.1.2.3 退出标志
在一个action,helper或脚本中,默认抛出任何东西都会引发错误返回。如果想要其他的返回,就必须抛出一些特殊的标志。比如:
return exits.hasConflictingCourses();
throw 'hasConflictingCourses';
throw { hasConflictingCourses: ['CS 301', 'M 402'] };
除了便于记忆,退出标志在循环等结构中特别有用,但是仍想以一种特殊的方式退出。
2.1.3 controllers
编写Sails应用最快的方法就是把各类操作都组织在controller文件中。其文件名必须以Controller结尾,包含一组操作。例如:
module.exports = {
login: function (req, res) { ... },
logout: function (req, res) { ... },
signup: function (req, res) { ... },
};
2.1.4 独立操作文件
对于大型应用,使用独立的操作文件会更好。这种形式与所有操作都在一个controller文件中相比,每个操作都在api/controller目录下有独立的文件。例如:
api/
controllers/
user/
login.js
logout.js
signup.js
每个js文件都有一个包含req,res的方法或一个actions定义。
这种模式有几种优势:
1. 更容易追踪应用中的动作,只要看文件目录即可,无须查看代码。
2. 每个操作文件都很小,比较容易维护,而controller文件可能随着应用复杂变大。
3. 针对操作文件的路由更直观。
4. 可以为高级操作设计目录路由,可以用一个api/controller/index.js来绑定应用的 / 路由。
2.1.5 保持精简
在MVC框架的传统设计中,Sails应用的controller通常很简单,因为可复用的代码都在helper中。这样做可以在应用变得复杂时使代码更容易维护。但是,过早的把代码都移动到helper会引发一些维护问题,影响效率。
建议在使用到重复代码三次及以上时,将其移动到helper中进行复用。
2.2 api/models/
此目录包含应用中的数据结构。
2.3 api/policies
此目录中包含了js文件本质上是express中间件为了对应用中controllers行为进行验证。