基于Express4.0开发web
1.快速搭建
在全局下安装express1
npm install -g express
执行1
2
3
4express -v yourProject
或者
cd yourProject
express
就会快速在目录yourProject中搭建express,-v参数指定模板引擎,没写就默认为jade
2.express中间件
1 | var app = requrie('express')(); |
next()作用是交付控制权,前往下一个中间件
可以利用这一点做验证功能
3.MVC架构
1 | /app |
以上就是项目的架构,按照MVC架构,后台代码都放在app下,schemas存放mongodb的模式,可以看作是一张张表,models放生成模型的文件,模型与模式对接起来。public存放静态文件,/libs存放前端框架如bootstrap。把router路由模块抽离放在router目录。
bower模块用来安装项目依赖的前端框架。需要在全局安装bower1
npm install -g bower
安装bootstrap1
2bower init
bower install bootstrap
bower init 生成bower.json
然后安装bootstrap时,会根据.bowerrc文件1
2
3{
"directory": "public/libs"
}
把依赖的框架安装在public/libs下
4.项目管理工具grunt
gruntfile.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112module.exports = function(grunt) {
grunt.initConfig({
watch: {
jade: {
files: ['views/**'],
options: {
livereload: true
}
},
js: {
files: ['public/js/**', 'app/**/*.js'],
//tasks: ['jshint'],
options: {
livereload: true
}
},
uglify: {
files: ['public/**/*.js'],
tasks: ['jshint'],
options: {
livereload: true
}
},
styles: {
files: ['public/**/*.less'],
tasks: ['less'],
options: {
nospawn: true
}
}
},
nodemon: {
dev: {
script: 'bin/www', //入口文件
options: {
nodeArgs: ['--debug'], //开启开发模式
args: [],
ignoredFiles: ['README.md', 'node_modules/**', 'public/libs/**', '.vscode'],
watchedExtensions: ['js'],
watchedFolders: ['./'], //监听定义的文件夹,根目录
debug: true,
delayTime: 1000,
env: {
PORT: 3000
},
cwd: __dirname
}
}
},
jshint: {
options: {
jshintrc: '.jshintrc',
ignores: ['public/libs/**/*.js']
},
all: ['public/javascripts/*.js', 'test/**/*.js', 'app/**/*.js']
},
less: {
development: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: {
'public/build/index.css' : 'public/less/index.less'
}
}
},
uglify: {
development: {
files: {
'public/build/admin.min.js': 'public/javascripts/admin.js',
'public/build/detail.min.js': [
'public/javascripts/detail.js'
]
}
}
},
mochaTest: {
options: {
reporter: 'spec'
},
src: ['test/**/*.js']
},
concurrent: {
tasks: ['nodemon', 'watch', 'jshint', 'less', 'uglify'],
options: {
logConcurrentOutput: true
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch'); //监控定义好的静态文件变动
grunt.loadNpmTasks('grunt-nodemon'); //监控入口文件变动
grunt.loadNpmTasks('grunt-concurrent'); //优化慢任务的构建时间,比如sass,less,并发执行多个阻塞的任务,比如nodemon和watch
grunt.loadNpmTasks('grunt-mocha-test'); //测试框架
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.option('force', true); //避免程序因语法错误而终止执行
grunt.registerTask('default', ['concurrent']);
grunt.registerTask('test', ['mochaTest']);
}
前期开发只需要前面三个模块包即watch,nodemon,concurrent就行。
5.router模块
这里有两种方法
第一种是将路由分类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16app.js
//挂载路由表
app.use('/', index);
app.use('/admin', admin);
app.use('/user', user)
user.js
var express = require('express');
var User = require('../models/user');
var router = express.Router();
//用户注册
router.post('/signup', function(req, res) {
...
}
module.exports = router;
当url以/user开头时就会进入user.js文件。
第二种是路由全部写在一个文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19app.js
require('./config/routers')(app); //不能传入express.Router(),它只能作为中间件用,如上
router.js
var Movie = require('../app/controllers/movie');
var Index = require('../app/controllers/index')
module.exports = function(app) {
//Index
app.get('/', Index.index);
//Movie
app.get('/movie/:id', Movie.detail);
app.get('/admin/movie/list',User.signinRequired,User.adminRequired, Movie.list);
};
6.数据库模块mongose
1 | app.js |
模式schemas(表)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.Types.ObjectId;
var MovieSchema = new mongoose.Schema({ //定义模式
doctor: String,
title: String,
language: String,
country: String,
summary: String,
flash: String,
poster: String,
year: String,
pv: {
type: Number,
default: 0
},
category: {
type: ObjectId,
ref: 'Category'
},
meta: {
createAt: {
type: Date,
default: Date.now()
},
updateAt: {
type: Date,
default: Date.now()
}
}
})
//每次存取数据之前,都调用save方法
MovieSchema.pre('save', function(next){
if(this.isNew) {
this.meta.createAt = this.meta.updateAt = Date.now()
} else {
this.meta.updateAt = Date.now()
}
next() //将流程走下去
})
MovieSchema.statics = { //实例化以后就会具有以下方法,相当于构造函数的成员方法
fetch: function(cb) {
return this
.find({})
.sort('meta.updateAt')
.exec(cb)
},
fetchById: function(id, cb) {
return this
.findOne({_id: id})
.exec(cb)
}
}
module.exports = MovieSchema
模型models(模式跟模型对接)1
2
3
4
5
6var mongoose = require('mongoose')
var MovieSchema = require('../schemas/movie')
var Movie = mongoose.model('Movie', MovieSchema) //模式和模型对接
module.exports = Movie
值得注意的是,使用mongodb之前,需要在电脑上安装mongodb,并注册成一个服务,方便使用前开启,具体参考笔记:mongodb错误
7.将session存放在mongodb里
session是会话支持,作为客户端的id,跟服务器交互1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//使用mongodb来储存sesion信息
var cookieParser = require('cookie-parser');
var session = require("express-session");
var MongoStore = require('connect-mongo')(session);
//配置session
app.use(cookieParser());
app.use(session({
secret: 'imoocMovieWeb',
store: new MongoStore({
url: dbURL,
collection: 'sessions'
}),
resave: true,
saveUninitialized: true
}))
其中呢,cookieParser()中间件是session和bodyParser的依赖的中间件
bodyParser是解析request请求的中间价1
2
3配置
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true})); //extended为true则可以解析字符串和数组以外的数据
8.前后台传数据
可以用res.render方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15controllers/movie.js
//获取后列表页
exports.list = function(req, res, next) {
Movie.fetch(function(err, movies) {
if(err) {
console.log(err)
}
res.render('list', {
title: '后台列表页',
movies: movies
});
})
};
还可以用res.locals,可以传递方法和变量1
2
3
4
5
6
7
8
9
10
11app.js
//前后台传递数据
app.use(function(req, res, next) {
res.locals.moment = moment; //向前台模板发送处理时间的moment方法
var _user = req.session.user;
res.locals.user = _user; //传递用户转态
next();
})
8.各种用到的中间件
flash
1
2
3
4
5var flash = require('connect-flash') //存储变量的中间件,相当全局变量
app.use(flash())
使用
var error = req.flash('error')logger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var logger = require('morgan'); //处理日志模块
//日志文件
var fs = require('fs');
var accessLogfile = fs.createWriteStream('access.log', {flags:'a'});
var errorLogfile = fs.createWriteStream('error.log', {flags:'a'});
app.set('env', 'production');
app.use(logger({stream: accessLogfile})); //访问日志
//处理错误日志,处理错误的中间件一定要写在路由后面
if ('production' == app.get('env')) {
app.use(function(err, req, res, next){
var meta = '[' + new Date() + '] ' + req.url + '\n';
errorLogfile.write(meta + err.stack + '\n');
next();
});
}moment
1
2
3
4
5
6
7app.js
var moment = require('moment'); //处理时间格式
在中间件里
res.locals.moment = moment; //向前台模板发送处理时间的moment方法
jade模板文件
moment(item.meta.updateAt).format('MM/DD/YYYY') //item.meta.updateAt是后台传过来的时间数据new Date()bcrypt-nodejs 加盐模块
1
2
3
4
5
6
7
8
9
10
11
12var bcrypt = require('bcrypt-nodejs');
var SALL_WORK_FACTOR = 10; //数值越大,破解越复杂
bcrypt.genSalt(SALL_WORK_FACTOR, function(err, salt) {
if(err) return next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if(err) return next(err);
user.password = hash
next() //将流程走下去
})
})underscore (用到这模块的继承方法)
1
2var _ = require('underscore');
_movie = _.extend(movie, movieObj)
9.jade模板引擎
1 | layout.jade 公用的部份 |
引进用include
block content是主体内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24extends ../layout //-扩展layout
block content
.container
.row
each cat in categories
.panel.panel-default
.panel-heading
h3
a(href='/results?cat=#{cat._id}&p=0') #{cat.name}
.panel-body
if cat.movies && cat.movies.length > 0
each item in cat.movies
.col-md-2
.thumbnail
a(href="/movie/#{item._id}")
if item.poster.indexOf('http:') > -1
img(src="#{item.poster}", alt="#{item.title}")
else
img(src="/upload/#{item.poster}", alt="#{item.title}")
.caption
h4 #{item.title}
p: a.btn.btn-primary(href="/movie/#{item._id}", role="button") 观看预告片
script(src='/build/admin.min.js')
- request(发http请求)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var request = require('request');
//用法
request({url: url, json: true}, function (error, response, body) {
if (!error && response.statusCode === 200) {
var data = body;
var now = (new Date().getTime());
var expires_in = now + (data.expires_in - 20) * 1000;
data.expires_in = expires_in;
resolve(data);
//console.log(data);
} else {
reject('can not get ticket')
}
});