0 概述
可用2种方法运行keystone:yo生成器、scratch setup(直接手撸)
--------------------------------------------------------------------------------
1.1 yo生成器
1.1.0安装
首先安装了yo
$ npm install -g yo
启动yo的keystone生成器来生成一个新项目
$ npm install -g generator-keystone
yo了keystone以后,keystone.js里面生成以下默认配置:pug,LESS(CSS),要换就在init option里改(见最后)。其他option设置看configuration
-------------------------------
var keystone = require('keystone');
keystone.init({
'name':'My Project',
'less':'public',
'static':'public',
'views':'My templates/views',
'views engine':'pug',
'auto update':true,
'mongo':'mongodb://localhost/my-project',
'session':true,
'auth':true,
'user model':'User',
'cookies secret':'secure string goes here',
});
keystone.import('models');
keystone.set('set','require('./routes'));
keystone.start();
---------------------------------------------------
不用生成器要设置些啥:
首先,我们要准备工作环境和工具安装:
首先安装nodejs和mongoDB
创建一个路径然后npm init
并创建默认package.json
然后npm install --save keystone
这时你应该有一个node_modules的路径。keystone也添加到package.json
1.2初始设置(init setup)
安装和设置一个keystone.js文件来部署你的应用。步骤分解成以下几步:
1.2.1
创建一个新文件keystone.js,准备认证keystone
1.2.2
keystone.js文件是keystone的登陆launch文件,它定义了基本认证选项、初始化keystone并1.2.1.3启动了应用的服务器
1.2.3
keystone.js中至少得:
var keystone = require('keystone');
#require一下 keystone然后init它初始值
keystone.init({
'cookies secret':'secure string goes here',
});
keystone.start();
---------------------------------------------------
2.1 模型(Model)
带你过一遍数据模型配置(data model setup),要设置其他的数据模型具体看文档 Database Configuration.
2.2.1
models文件夹先准备好,往里面放user.js
🌰实例:models/user.js这个脚本初始化了user模型,user不需要导出任何东西,但是模型需要用keystone注册一下,需要进行认证(authentication)和对话管理(session management)
--------------------------------------------------------------------------
var keystone = require('keystone');
var Types = keystone.field.Types;
var User = new keystone.List('User');
#此构造函数将创建一个keystone.list用户模型。这个列表还没有任何属性,所以不会非常有用。让我们在代码下面添加一些数据字段:
User.add({
displayName: { type: String },
password: { type: keystone.Field.Types.Password },
email: { type: keystone.Field.Types.Email, unique: true },
});
这会向模型添加三个字段:显示名称、密码字段和电子邮件字段。让我们把这些结构分解一下。
2.2.1 displayName: { type: String },
这演示了最基本的字段定义。每个字段都需要定义一个类型属性:displayname使用javascript的默认字符串类型。
2.2.2 password:{ type:Types.Password, initial:true },
密码字段使用的是特定于Keystone的字段类型。这将向数据添加一个已定义的形状,并添加一些额外的UI和数据层验证。Keystone的密码字段类型在保存时自动加密。此外,Keystone管理用户界面不会显示密码字段的内容,并且需要输入两次密码才能更改密码。这为我们提供了大量的密码安全保护。
2.2.3 email: { type: keystone.Field.Types.Email, unique: true },
电子邮件字段与密码类似,因为它使用的是另一种特定于Keystone的字段类型。Keystone的电子邮件类型验证字段条目看起来像有效的电子邮件地址。此外,我们还传递了第二个选项unique:true,它强制字段在数据库中是唯一的。帐户的电子邮件地址不能重复。
如果您想了解Keystone提供的所有字段类型,可以在字段API文档中找到完整的选项列表。此外,对于所有字段都可用的unique等选项,您可以阅读有关字段选项API的更多信息。当您创建自己的数据模型时,了解可用的字段类型和选项将非常有用。
2.2.4
还有三个步骤可以让您的用户模型正常工作。第一个是注册模型,这样Keystone知道在其模型列表中包含用户。
为此,请在user.js文件的底部添加以下行:
User.register();
canAccessKeystone
接下来,由于此用户模型将用于登录到管理用户界面,因此需要添加属性canaccesskeystone。我们将有一个允许所有用户访问Keystone的用户模型,但您可能希望为自己的应用程序实现更细粒度的控制。
在User.register();之上添加:
User.schema.virtual('canAccessKeystone').get(function () {
return true;
});
或在User.add里添加:
canAccessKeystone:{ type:Boolean, initial:true
...}
2.3.1 在进行认证(authentication)和对话管理(session management)注意:
⚠️user model模型的名字一定要keystone可以找到的用户(User)如果模型名字不一样,要确保选项(option)设置是正确的
⚠️如果你想要你的应用支持对话管理,要将session设置成true。加载session会有点过载,不需要就off 。
⚠️keystone自带登陆登出的页面,要使他们能工作,将auth设置为true。你也可以在application view 文件夹中放入登陆登出的页面。对话session坚持使用加密cookie储存用户ID,确保cookie secret是long 随机字符串。
⚠️user 模型需要有个canAccessKeystone特性 ,它的作用是告知用户是否可以进入keystone的管理员(admin)界面UI,这个特性可以变成虚拟方法(virtual method)或者存储的布尔值,注意,如果你选择虚拟方法(virtual method),不能直接在mongoDB中设置,要设置看Mongoose virtuals,模式相关的看schema documentation.
显示defaultcolumns默认列:设置用户模型的最后一部分是定义要在管理用户界面中显示的默认列。
在user.register上面添加此行:
user.defaultcolumns='id,displayname,email';
2.3.2 添加更新脚本(updates)
在启动应用程序之前还有一件事要做。您需要在数据库中有一个初始用户。您可以通过一个更新脚本来实现这一点,该脚本将在启动时运行。
创建一个名为updates的新目录,并在其中创建一个0.0.1-first-user.js文件。
将以下代码添加到0.0.1-first-user.js中:
exports.create = {
User: [
{
displayName: 'user1',
email: 'user@keystonejs.com',
password: 'admin',
},
],
};
这样将当keystone启动时,创建一个具有这些详细信息的用户(尽管在保存之前密码将被加密)。
注意:您可能最终会将更新脚本提交到项目中,因此不应包括敏感信息。在更新脚本中添加的任何密码都应该在之后手动更改。
3.1 登陆管理用户UI看看
现在你准备好了!您应该能够运行node keystone.js来启动应用程序。如果您将您最喜欢的Web浏览器指向http://localhost:3000/keystone,那么您现在应该看到一个登录页面。使用您刚添加的电子邮件地址和密码登录,玩一玩。
4.1 事件模型
下面是一个更有趣的模型。添加此项,然后重新启动Keystone应用程序。
models/Event.js
var keystone = require('keystone');
var Types = keystone.Field.Types;
var Event = new keystone.List('Event');
Event.add({
name: { type: String, required: true, initial: true },
description: { type: Types.Html, wysiwyg: true },
cost: { type: Number, default: 0, size: 'small' },
startTime: { type: Types.Datetime, required: true, initial: true, index: true },
endTime: { type: Types.Datetime, required: true, initial: true, index: true },
location: { type: Types.Location, initial: true },
published: { type: Boolean },
publishDate: { type: Types.Date, index: true },
});
Event.schema.virtual('canAccessKeystone').get(function () {
return true;
});
Event.schema.pre('save', function (next) {
let event = this;
if (event.isModified('published') && event.published) {
this.publishDate = Date.now();
}
return next();
});
Event.defaultColumns = 'name, description';
Event.register();
--------------------------------------------------------------------------
5.1 路由(route)
路由可以将客户端请求(从Web浏览器或API调用)定向到应用程序中相应处理函数。
您将创建一个使用pug视图引擎呈现的基本网页。以pug为例,但是对于其他视图引擎,路由和渲染的原理是相同的。
从第1部分:初始设置开始,您应该有以下文件:
| MyProject
|--node_modules/
|--package.json
|--keystone.js
keystone应该已经安装,并且您的keystone.js文件至少应该包括以下内容:
var keystone = require('keystone');
keystone.init({
'cookie secret': 'secure string goes here',
});
keystone.start();
如果您完成了第2部分:数据模型设置,那么keystone.js将具有额外的数据库配置选项。在本教程部分中设置的基本路由和网页不需要数据库配置选项,但您需要为后续教程部分保留这些详细信息(第4部分:从表单添加数据)。
5.1.2 添加新的页面视图
5.1.3 修改keystone.js文件
如第1部分所述,keystone.init定义了keystone启动的初始选项。为了呈现应用程序视图,我们将向keystone.init添加两个新属性,然后添加一条将导入路由的线。
要添加的两个属性是:
views视图:相对于keystone.js的文件夹位置,用于加载视图文件。我们将使用“模板/视图”。
view engine视图引擎:用于呈现视图文件的Keystone模板引擎。
keystone.init现在至少应具有以下属性:
keystone.init({
'cookie secret': 'secure string goes here',
views: 'templates/views',
'view engine': 'pug',
});
Keystone将查找与View Engine同名的已安装npm包,因此必须安装pug才能使其正常工作。
$ npm install --save pug
最后,添加一行,告诉Keystone您计划在哪里定义路由:
keystone.set('routes', require('./routes'));
5.1.4 添加路由
接下来,我们需要添加routes路由文件。我们将使用Keystone推荐的文件和目录布局来构建它们,但是如果您愿意,您可以使用不同的结构。
首先,我们要添加一个routes文件夹,并在其中创建一个index.js文件。在routes文件夹中,添加一个views文件夹,然后给出它自己的index.js。
在此之后,您应该有一个如下的文件夹结构:
| MyProject
|--node_modules/
|--routes
| |--index.js
| |--views
| | |--index.js
|--keystone.js
这种结构的原因是最好将各个路由保存在它们自己的文件中,并使用单个中心文件来收集它们。
routes/index.js
让我们从中央路由文件routes/index.js开始。
这个文件将导出一个函数,接受为我们构建的Express应用程序keystone,并添加我们的单独路线。
最基本的形式是:
function routeFunction(req, res) {
res.render('index');
}
module.exports = function (app) {
app.get('/', routeFunction);
};```For each route we want, we add a new path and route function. In the example above, the path `'/'` (the default homepage) will be handled by `routeFunction()`.What we are going to add will be slightly more complicated:
对于我们想要的每个路由,我们都添加了一个新的路径和路由函数。在上面的示例中,路径`'/'`(默认主页)将由'routeFunction()`'处理。
我们将要添加的内容会稍微复杂一些:
```javascript
var keystone = require('keystone');
var importRoutes = keystone.importer(__dirname);
var routes = {
views: importRoutes('./views'),
};
exports = module.exports = function (app) {
app.get('/', routes.views.index)
};
Keystone导入程序为我们提供了一个功能,允许我们将文件夹及其内容减少到具有相同嵌套的对象中。
然后使用要导入的目录调用importRoutes,并将其附加到routes.views中的对象。最后,我们现在可以提供routes.views.index作为app.get函数调用的第二个参数。
对于单个路由来说,这有点重,但是可以很容易地添加新的路由,而不需要在进行过程中对每个文件都有一系列的要求。我们现在需要在routes/views/index.js中运行一些内容。
routes/views/index.js
这是我们的第一个endpoint文件,也是建立新路由的许多常见模式的一个重要点。
让我们直接跳到这个代码:
module.exports = function (req, res) {
res.send('Hello you learner, you');
};
这是使用 Express 路由,您可以在 Express 文档中了解更多信息Basic routing.
在本例中,我们希望呈现第一个视图。为此,我们希望文件读取:
module.exports = function (req, res) {
res.render('index');
};
5.1.5 添加视图
呈现视图的最后一步是视图 views 文件本身,其中包括模板化的文本内容。
首先,您需要一个顶层的新模板文件夹,以及其中的视图 views 文件夹。在此视图 views 文件夹中创建文件index.pug。在此之后,您的文件结构应该看起来像:
| MyProject
|--node_modules/
|--routes
| |--index.js
| |--views
| | |--index.js
|--templates
| |--views
| | |--index.pug
|--keystone.js
注意, routes/views & templates/views相互镜像。在添加更多路由时遵循此模式是一个好主意,这样它们之间的关系很容易确定。
index.pug文件只需要一点内容:
doctype html
html(lang="en")
head
title= "Words on a page"
body
h1 Welcome to Keystone!
#container.col
p.
Hope you're enjoying learning about KeystoneJS. We're close to some very dynamic cool things.
现在,如果您使用节点keystone启动您的keystone应用程序,您应该能够访问主页并看到它的呈现!
下一步:从表单添加数据,这将引导您通过设置API端点来保存数据。
📒keystone的 importer 和express的中间件的好处:
写路由(routes)和视图(views)的时候,把所有的绑定设置都写在一个文件里,然后把通用逻辑写在另一个文件。然后每个绑定了控制器的路由,控制器都放在自己的文件里。并且可用和template相似的方法在view里进行渲染。
📒路由和中间件(middleware)
routes/index.js这个脚本帮你导入你的路由控制器,并且按步骤进行控制器索引,绑定他们到URL上:
++++++++++
#加载keystone,middleware.js文件(如下),并为当前目录创建导入程序
var keystone = require('keystone');
var middleware = require('./middleware');
var importRoutes = keystone.importr(_dirname);
//绑定通用中间件
#初始化我们的基本错误处理程序
#初始化视图模板的公共局部变量
#在呈现视图模板之前从会话中检索闪存消息
keystone.pre('routes', middleware.initErrorHandlers);
keystone.pre('routes', middleware.initLocals);
keystone.pre('render', middleware.flashMessages);
#告诉Keystone如何处理404和500个错误
//处理404错误
keystone.pre('404', function(req, res, next){
res.notfound();
});
//处理其他错误
keystone.set('500', function(err, req, res, next){
var title, message;
err = err.stack;
}
res.err(err, title, message);
});
#使用导入程序importRoutes加载/routes/views目录中的所有路由控制器
//加载路由
var routes = {
views : importRoutes('./views')
}
#导出绑定索引路由控制器以获取根URL上的请求的方法
//绑定路由
exports = module.exports = function(app){
app.get('/', routes.views.index);
}
=这个方法的app参数来自我们的express应用程序,所以你可以在express中做任何绑定路由的事情,你可以在这里做。
=应使用根控制器下的app.get、app.post或app.all添加添加添加到应用程序中的其他路由控制器。
++++++++++
#📒通用路由中间件(common route middlerware)
将你通用的中间件放在独立的routes/middleware.js文件夹里,这样如果你的中间件文件太大的话,可以让你的route的index.js文件干净整洁
在通用module里在projects/lib文件夹里将重要的功能进行重构
routes/middleware.js
这里有你的应用的路由的通用中间件:
var _ = require('lodash');
/**初始化标准本地视图,包括任何应该在路由控制器执行之前被初始化的东西**/
exports.initLocals = function(req, res, next){
var locals = res.locals;
locals.user = req.user;
//添加自己的本地变量
next();
}
/**初始化错误处理器函数到‘res’**/
exports.initErrorHandlers = function(req, res, next){
res.err = function(err, title, message){
err:err,
errorTitle:title,
errorMsg: message
});
}
res.notfound= function(title, message){
res.status(404).render('errors/404',{
errorTitle:title,
errorMsg: message
});
}
next();
}
/**初始化错误处理器函数到‘res’**/
exports.flashMessages= function(req, res, next){
var flashMessages= {
info:req.flash('info'),
success:req.flash('success'),
warning:req.flash('warning'),
error:req.flash('error')
};
res.locals.messages = _.some(flashMessages, function(msgs){return msgs.length})? flashMessages:false;
next();
}
📒中间件的几个功能
keystone的中间件接受这么几个argument:
req : an Express request object
res : an Express response object
next : amiddleware结束运行时唤醒的下一个方法
flash message接受这么几个argument:
通过对话(session)给你的访问者传输flashing message。默认四类:info,success,warning,error
你可以通过更新中间件或pug template来支持其他类型
要在路由控制器里用上flash message,这么做:
req.flash('info','some infomations!');
message用的是 session这样它们可以在重定向中活下来并且只展示一次。
还有一些类似 Update Handler的keystone功能可以自动生成flash message
Part 4: Adding data from a form(使用事件模型)
设置:
var keystone = require('keystone');
var Types = keystone.Field.Types;
var Event = new keystone.List('Event');
Event.add({
name: { type: String, required: true, initial: true },
description: { type: Types.Html, wysiwyg: true },
cost: { type: Number, default: 0, size: 'small' },
startTime: { type: Types.Datetime, required: true, initial: true, index: true },
endTime: { type: Types.Datetime, required: true, initial: true, index: true },
location: { type: Types.Location, required: false, initial: true },
published: { type: Boolean },
publishDate: { type: Types.Date, index: true },
});
Event.schema.virtual('canAccessKeystone').get(function () {
return true;
});
Event.schema.pre('save', function (next) {
let event = this;
if (event.isModified('published') && event.published) {
this.publishDate = Date.now();
}
return next();
});
Event.defaultColumns = 'displayName, email';
Event.register();
创建AddEvent视图
首先,我们要创建一对新文件来组成路由和视图。这些应该是routes/views/addevent和templates/views/addevent.pug。
由于这不是pug教程,下面是我们之前为addevent.pug模板准备的页面:
doctype html
html(lang="en")
head
title= "Add Event"
body
if enquirySubmitted
h3 Your Event has been added to the database
else
.container
.row: .col-sm-8.col-md-6
form(method='post' action="/api/event")
input(type='hidden', name='action')
.form-group
label Event Name
input(type='text', name='name')
.form-group
label Start Time
input(type='datetime-local', name='startTime')
.form-group
label End Time
input(type='datetime-local', name='endTime')
.form-group
label Description
textarea(name='description', placeholder='event description...' rows=4)
.form-actions
button(type='submit').btn.btn-primary Send
接下来,我们要添加一个非常简单的路由。routes/views/addevent.js的内容应该是:
module.exports = function (req, res) {
res.render('addEvent');
};
然后,我们想把这个新的路由添加到routes/index.js 中,并为我们的KestStin应用程序添加一个新的路径。
要添加的新路由是:
app.get('/add-event', routes.views.addEvent)
现在 /routes/index.js 包含了:
exports = module.exports = function (app) {
app.get('/', routes.views.index)
app.get('/add-event', routes.views.addEvent)
};
通过这个路由配置,您应该能够启动 Keystone,转到新路由,并填写Event 表格。下一步是接收和处理数据。
创建event/post.js endpoint
我们现在将添加一个post请求的处理程序和一个可以将事件信息保存回数据库的请求处理程序。
由于此端点不是视图,因此我们必须修改Routes对象。我们将创建一个名为api的导入路由的新文件夹。
首先,我们需要向routes对象添加一个属性来读取api文件夹。Routes对象最终应如下所示:
var routes = {
views: importRoutes('./views'),
api: importRoutes('./api'),
};
其次,我们将向应用程序添加一个新的路由。由于这不是GET请求,我们需要通过将动词更改为POST来让应用程序知道:
app.post(“/api/event”,routes.api.event.post);
接下来,我们可以创建路由/api/event/post route。API文件夹只包含另一个文件夹是正确的。这个结构帮助我们获得上面非常可读的路由定义。
我们像以前一样,以快速方式开始路由:
module.exports = function (req, res) {
}
不过,这次我们要做一些更复杂的事情。首先我们要读取表单数据,然后将这些数据保存到数据库中。
读取表单数据
表单数据位于传入的请求对象上——更具体地说是作为req.body。因为我们构建了req.body,只包含我们将要使用的字段,所以我们将能够直接传递它。但是,为了确保没有错误,最好检查预期值。
我们可以在函数中将这些变量赋给它们自己的变量,从而得到:
module.exports = function (req, res) {
if (!req.body.name || !req.body.startTime || !req.body.endTime) {
return res.sendError('incomplete data set');
}
};
将事件添加到数据库
一旦表单数据已被验证,我们就可以移动到如何将数据保存回数据库。
在返回路由功能之前,我们需要一些软件包:
var keystone = require('keystone');
var Event = keystone.list('Event');
通过这些设置,我们可以开始查看如何保存数据。
首先,我们可以创建一个新的事件项,包括传递初始值:
var new event=new event.model(req.body);
此代码将从schema中返回具有事件 Event 属性的对象,但该对象尚未保存到数据库中。您可以使用newevent.save()来实现mongoose的save()方法,但是keystone提供了一个updateItem()函数来运行keystone的验证器(可以包括附加的验证)。如果项目不存在,则updateItem()将创建该项目。
我们可以这样称呼它:
event.updateItem(newEvent)
注意:updateItem()还有许多其他的重要功能,可以帮助您确保数据完整性,我们建议您有时间阅读完整的文档。
这给我们留下了一个文件,看起来像:
var keystone = require('keystone');
var Event = keystone.list('Event');
module.exports = function (req, res) {
if (!req.body.name || !req.body.startTime || !req.body.endTime) {
return res.send({ status: 'incomplete data set' });
}
var newEvent = new Event.model();
Event.updateItem(newEvent, req.body);
};
别让我吊着
这里的一个缺陷是,当我们成功地添加了一个事件时,我们从未对等待中的网站做出响应来告诉它。我们将在updateItem的回调中执行此操作。我们还可以添加更多的错误检查。
Event.updateItem(newEvent, req.body, function (error) {
res.locals.enquirySubmitted = true;
if (error) res.locals.saveError = true;
res.render('addEvent');
});
做得好!您已经完成了Keystone入门的四步指南,现在您有了一个基本的Keystone应用程序,它包含一个用于表单处理的API,可以将数据添加到数据库中。有关列表选项和Keystone支持的字段类型的详细信息,请浏览数据库配置文档。
---------------------------------------------------------------
1.2.4.从表格中添加数据:
连接1.2.3路由配置和1.2.2数据库配置=创造一个post请求endpoint来提交数据
要设置4个:数据模型、认证和对话管理、路由和视图、模版template、public asset(静态数据), Application Update(动态数据,可以写入数据库)
---------------------------(跳过)------------------------------------
模板(pug……)
Public Assets
Application Updates
------------------------------------------------------------------------
Guides:解释一些特例
---------------------------------------------------
Documentation:数据库配置和初始化选项等基本配置
---------------------------------------------------
API References: