Grant
npm install grunt grant-cli -g
全局安装grant及grant命令行工具,
npm install grunt-contrib-watch --save-dev //文件修改重新执行注册的任务
npm install grunt-nodemon --save-dev //监听app.js修改
npm install grunt-concurrent --save-dev //慢任务如sass
建立grunt.js文件
module.exports = function(grunt) {
grunt.initConfig({
watch: {
jade: {
files: ['views/**'],
options: {
livereload: true
}
},
js: {
files: ['public/js/**', 'models/**/*.js', 'schemas/**/*.js'],
//tasks: ['jshint'],
options: {
livereload: true
}
}
},
nodemon: {
dev: {
options: {
file: 'app.js',
args: [],
ignoredFiles: ['README.md', 'node_modules/**', '.DS_Store'],
watchedExtensions: ['js'],
watchedFolders: ['app','config'],
debug: true,
delayTime: 1,
env: {
PORT: 3000
},
cwd: __dirname
}
}
},
concurrent: {
tasks: ['nodemon', 'watch'],
options: {
logConcurrentOutput: true
}
}
})
grunt.loadNpmTasks('grunt-contrib-watch')
grunt.loadNpmTasks('grunt-nodemon')
grunt.loadNpmTasks('grunt-concurrent')
grunt.option('force', true) //避免一些错误影响后续执行
grunt.registerTask('default', ['concurrent']) //默认任务
}
根目录下执行grunt
命令启动
用户登陆注册
1)用户模型及密码处理
安装bcrypt进行密码加盐处理 npm install bcrypt --save-dev
,创建schema user.js
var mongoose = require('mongoose')
var bcrypt = require('bcrypt')
var SALT_WORK_FACTOR = 10
var UserSchema = new mongoose.Schema({
name: {
unique: true,
type: String
},
password: String,
meta: {
createAt: {
type: Date,
default: Date.now()
},
updateAt: {
type: Date,
default: Date.now()
}
}
})
UserSchema.pre('save', function(next) {
var user = this
if (this.isNew) {
this.meta.createAt = this.meta.updateAt = Date.now()
}
else {
this.meta.updateAt = Date.now()
}
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err)
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err)
user.password = hash
next()
})
})
})
UserSchema.statics = {
fetch: function(cb) {
return this
.find({})
.sort('meta.updateAt')
.exec(cb)
},
findById: function(id, cb) {
return this
.findOne({_id: id})
.exec(cb)
}
}
module.exports = UserSchema
2)登录注册前端视图
修改header.jade,添加navbar,添加注册与登陆两个按钮,分别为其添加模态视图
.container
.row
.page-header
h1= title
small 重度科幻迷
.navbar.navbar-default.navbar-fixed-bottom
.container
.navbar-header
a.navbar-brand(href="/") 重度科幻迷
if user
p.navbar-text.navbar-right
span 欢迎您,#{user.name}
span |
a.navbar-link(href="/logout") 登出
else
p.navbar-text.navbar-right
a.navbar-link(href="#", data-toggle="modal", data-target="#signupModal") 注册
span |
a.navbar-link(href="#", data-toggle="modal", data-target="#signinModal") 登录
#signupModal.modal.fade
.modal-dialog
.modal-content
form(method="POST", action="/user/signup")
.modal-header 注册
.modal-body
.form-group
label(for="signupName") 用户名
input#signupName.form-control(name="user[name]", type="text")
.form-group
label(for="signupPassword") 密码
input#signupPassword.form-control(name="user[password]", type="text")
.modal-footer
button.btn.btn-default(type="button", data-dismiss="modal") 关闭
button.btn.btn-success(type="submit") 提交
#signinModal.modal.fade
.modal-dialog
.modal-content
form(method="POST", action="/user/signin")
.modal-header 登录
.modal-body
.form-group
label(for="signinName") 用户名
input#signinName.form-control(name="user[name]", type="text")
.form-group
label(for="signinPassword") 密码
input#signinPassword.form-control(name="user[password]", type="text")
.modal-footer
button.btn.btn-default(type="button", data-dismiss="modal") 关闭
button.btn.btn-success(type="submit") 提交
3)注册用户后台存储
添加Model user.js
var mongoose = require('mongoose')
var UserSchema = require('../schemas/user')
var User = mongoose.model('User', UserSchema)
module.exports = User
在app.js中引入user model,并添加路由
var User = require('./models/user')
......
app.post('/user/signup',function(req,res){
var _user = req.body.user
var user = new User(_user)
user.save(function(err,user){
if (err) {
console.log(err)
}
res.redirect('/')
})
})
添加用户列表页
extends ../layout
block content
.container
.row
table.table.table-hover.table-bordered
thead
tr
th 名字
th 时间
th 查看
th 修改
th 删除
tbody
each item in users
tr(class="item-id-#{item._id}")
td #{item.name}
td #{moment(item.meta.updateAt).format('MM/DD/YYYY')}
td: a(target="_blank", href="../movie/#{item._id}") 查看
td: a(target="_blank", href="../admin/update/#{item._id}") 修改
td
button.btn.btn-danger.del(type="button", data-id="#{item._id}") 删除
在app.js中添加路由
//user list page
app.get('/admin/userlist', function(req, res) {
Movie.fetch(function(err,users){
if (err) {
console.log(err)
}
res.render('userlist', {
title: 'imooc 用户列表页',
users: users
})
})
})
登陆时判断用户是否存在,更改app.js signup路由
app.post('/user/signup',function(req,res){
var _user = req.body.user
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (user) {
return res.redirect('/')
} else {
var user = new User(_user)
user.save(function(err,user){
if (err) {
console.log(err)
}
res.redirect('/admin/userlist')
})
}
})
})
4)实现登陆逻辑
添加signin路由
app.post('/user/signin',function(req,res){
var _user = req.body.user
var name = _user.name
var password = _user.password
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (!user) {
return res.redirect('/')
}
user.comparePassword(password, function(err, isMatch){
if (err) {
console.log(err)
}
if (isMatch){
console.log('Password is match')
return res.redirect('/')
} else {
console.log('Password not match')
}
})
})
})
在user schema中添加comparePassword方法
UserSchema.methods = {
comparePassword: function(_password, cb) {
bcrypt.compare(_password, this.password, function(err, isMatch) {
if (err) return cb(err)
cb(null, isMatch)
})
}
}
5)保持用户状态
在app.js中安装并引用cookie-session,index中打印当前user,在/user/signin的post中,如果密码匹配,存储user到session
var cookieSession = require('cookie-session')
app.use(cookieSession({
secret: 'imooc'
}))
......
//index page
app.get('/', function(req, res) {
console.log('user in session:')
console.log(req.session.user)
......
if (isMatch){
console.log('Password is match')
req.session.user = user
return res.redirect('/')
}
6)利用MongoDB做会话的持久化
安装 express-session connect-mongo
var session = require('express-session')
var MongoStore = require('connect-mongo')(session);
app.use(session({
secret: 'imooc',
store: new MongoStore({
url:'mongodb://localhost/imooc',
collection: 'sessions'
})
}))
7)注销功能实现
header.jade中修改,判断user显示
if user
p.navbar-text.navbar-right
span 欢迎您,#{user.name}
span |
a.navbar-link(href="/logout") 登出
else
p.navbar-text.navbar-right
a.navbar-link(href="#", data-toggle="modal", data-target="#signupModal") 注册
span |
a.navbar-link(href="#", data-toggle="modal", data-target="#signinModal") 登录
添加登出路由,在index路由中,赋予locals user值
console.log('user in session:')
console.log(req.session.user)
var _user = req.session.user
if(_user) {
app.locals.user = _user
}
Movie.fetch(function(err,movies){
......
app.get('/logout',function(req,res){
delete req.session.user
delete app.locals.user
res.redirect('/')
})
8)会话持久逻辑预处理
将index路由中的赋值移到预处理逻辑中
app.use(function(req,res,next){
var _user = req.session.user
if(_user) {
app.locals.user = _user
}
return next()
})
9)调整目录,独立路由
添加config/routes.js,将路由相关代码移到该文件
var _ = require('underscore')
var Movie = require('../models/movie')
var User = require('../models/user')
module.exports = function(app){
app.use(function(req,res,next){
var _user = req.session.user
if(_user) {
app.locals.user = _user
}
return next()
})
//index page
app.get('/', function(req, res) {
console.log('user in session:')
console.log(req.session.user)
Movie.fetch(function(err,movies){
if (err) {
console.log(err)
}
res.render('index', {
title: 'imooc 首页',
movies: movies
})
})
})
app.post('/user/signup',function(req,res){
var _user = req.body.user
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (user) {
return res.redirect('/')
} else {
var user = new User(_user)
user.save(function(err,user){
if (err) {
console.log(err)
}
res.redirect('/admin/userlist')
})
}
})
})
app.post('/user/signin',function(req,res){
var _user = req.body.user
var name = _user.name
var password = _user.password
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (!user) {
return res.redirect('/')
}
user.comparePassword(password, function(err, isMatch){
if (err) {
console.log(err)
}
if (isMatch){
console.log('Password is match')
req.session.user = user
return res.redirect('/')
} else {
console.log('Password not match')
}
})
})
})
app.get('/logout',function(req,res){
delete req.session.user
delete app.locals.user
res.redirect('/')
})
//user list page
app.get('/admin/userlist', function(req, res) {
User.fetch(function(err,users){
if (err) {
console.log(err)
}
res.render('userlist', {
title: 'imooc 用户列表页',
users: users
})
})
})
//detail page
app.get('/movie/:id', function(req, res) {
var id = req.params.id
Movie.findById(id,function(err,movie){
res.render('detail', {
title: 'imooc 详情页',
movie: movie
})
})
})
//admin page
app.get('/admin/movie', function(req, res) {
res.render('admin', {
title: 'imooc 后台录入页',
movie: {
title: '',
doctor: '',
country: '',
year: '',
poster: '',
flash: '',
summary: '',
language: ''
}
})
})
//admin update movie
app.get('/admin/update/:id',function(req,res){
var id = req.params.id
if (id) {
Movie.findById(id,function(err,movie){
res.render('admin',{
title: 'Imooc 后台更新页',
movie: movie
})
})
}
})
//admin post movie
app.post('/admin/movie/new', function(req,res){
var id = req.body.movie._id
var movieObj = req.body.movie
var _movie
if (id !=='undefined') {
Movie.findById(id, function(err,movie){
if (err){
console.log(err)
}
_movie = _.extend(movie, movieObj)
_movie.save(function(err,movie) {
if (err){
console.log(err)
}
res.redirect('/movie/'+movie._id)
})
})
} else {
_movie = new Movie({
dector: movieObj.dector,
title: movieObj.title,
country: movieObj.country,
language: movieObj.language,
year: movieObj.year,
poster: movieObj.poster,
summary: movieObj.summary,
flash: movieObj.flash,
})
_movie.save(function(err,movie) {
if (err){
console.log(err)
}
res.redirect('/movie/'+movie._id)
})
}
})
//list page
app.get('/admin/list', function(req, res) {
Movie.fetch(function(err,movies){
if (err) {
console.log(err)
}
res.render('list', {
title: 'imooc 列表页',
movies: movies
})
})
})
//list delete movie
app.delete('/admin/list',function(req,res){
var id = req.query.id
if (id) {
Movie.remove({_id: id},function(err,movie){
if (err) {
console.log(err)
} else {
res.json({success: 1})
}
})
}
})
}
app.js中引入routes文件
app.use(session({
secret: 'imooc',
store: new MongoStore({
url:'mongodb://localhost/imooc',
collection: 'sessions'
})
}))
require('./config/routes')(app)
10)配置入口文件
安装morgan,
var logger = require('morgan')
if('development' === app.get('env')){
app.set('showStackError', true)
app.use(logger(':method :url :status'))
app.locals.pretty = true
mongoose.set('debug',true)
}
11)调整目录,分离mvc
新建app文件夹,将models/schemas/views文件夹移到该文件夹,添加controllers文件夹,将routes中代码移到新建的文件中
index.js
var Movie = require('../models/movie')
exports.index = function(req, res) {
console.log('user in session:')
console.log(req.session.user)
Movie.fetch(function(err,movies){
if (err) {
console.log(err)
}
res.render('index', {
title: 'imooc 首页',
movies: movies
})
})
}
user.js
var User = require('../models/user')
exports.signup = function(req,res){
var _user = req.body.user
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (user) {
return res.redirect('/')
} else {
var user = new User(_user)
user.save(function(err,user){
if (err) {
console.log(err)
}
res.redirect('/admin/userlist')
})
}
})
}
exports.signin = function(req,res){
var _user = req.body.user
var name = _user.name
var password = _user.password
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (!user) {
return res.redirect('/')
}
user.comparePassword(password, function(err, isMatch){
if (err) {
console.log(err)
}
if (isMatch){
console.log('Password is match')
req.session.user = user
return res.redirect('/')
} else {
console.log('Password not match')
}
})
})
}
exports.logout = function(req,res){
delete req.session.user
// delete app.locals.user
res.redirect('/')
}
//user list page
exports.list = function(req, res) {
User.fetch(function(err,users){
if (err) {
console.log(err)
}
res.render('userlist', {
title: 'imooc 用户列表页',
users: users
})
})
}
movie.js
var _ = require('underscore')
var Movie = require('../models/movie')
//detail page
exports.detail = function(req, res) {
var id = req.params.id
Movie.findById(id,function(err,movie){
res.render('detail', {
title: 'imooc 详情页',
movie: movie
})
})
}
//admin page
exports.new = function(req, res) {
res.render('admin', {
title: 'imooc 后台录入页',
movie: {
title: '',
doctor: '',
country: '',
year: '',
poster: '',
flash: '',
summary: '',
language: ''
}
})
}
//admin update movie
exports.update = function(req,res){
var id = req.params.id
if (id) {
Movie.findById(id,function(err,movie){
res.render('admin',{
title: 'Imooc 后台更新页',
movie: movie
})
})
}
}
//admin post movie
exports.save = function(req,res){
var id = req.body.movie._id
var movieObj = req.body.movie
var _movie
if (id !=='undefined') {
Movie.findById(id, function(err,movie){
if (err){
console.log(err)
}
_movie = _.extend(movie, movieObj)
_movie.save(function(err,movie) {
if (err){
console.log(err)
}
res.redirect('/movie/'+movie._id)
})
})
} else {
_movie = new Movie({
dector: movieObj.dector,
title: movieObj.title,
country: movieObj.country,
language: movieObj.language,
year: movieObj.year,
poster: movieObj.poster,
summary: movieObj.summary,
flash: movieObj.flash,
})
_movie.save(function(err,movie) {
if (err){
console.log(err)
}
res.redirect('/movie/'+movie._id)
})
}
}
//list page
exports.list = function(req, res) {
Movie.fetch(function(err,movies){
if (err) {
console.log(err)
}
res.render('list', {
title: 'imooc 列表页',
movies: movies
})
})
}
//list delete movie
exports.del = function(req,res){
var id = req.query.id
if (id) {
Movie.remove({_id: id},function(err,movie){
if (err) {
console.log(err)
} else {
res.json({success: 1})
}
})
}
}
routes.js修改为如下
var Index = require('../app/controllers/index')
var User = require('../app/controllers/user')
var Movie = require('../app/controllers/movie')
module.exports = function(app){
app.use(function(req,res,next){
var _user = req.session.user
// if(_user) {
app.locals.user = _user
// }
next()
})
//index page
app.get('/', Index.index)
//user
app.post('/user/signup',User.signup)
app.post('/user/signin',User.signin)
app.get('/logout',User.logout)
app.get('/admin/userlist', User.list)
//movie
app.get('/movie/:id', Movie.detail)
app.get('/admin/movie', Movie.new)
app.get('/admin/update/:id',Movie.update)
app.post('/admin/movie/new', Movie.save)
app.get('/admin/list', Movie.list)
app.delete('/admin/list',Movie.del)
}
修改app.js文件中路径app.set('views', './app/views/pages')
12)添加注册登陆跳转页面
添加signin与signup jade文件
extends ../layout
block content
.container
.row
.col-md-5
form(method="POST", action="/user/signin")
.modal-body
.form-group
label(for="signinName") 用户名
input#signinName.form-control(name="user[name]", type="text")
.form-group
label(for="signinPassword") 密码
input#signinPassword.form-control(name="user[password]", type="text")
.modal-footer
button.btn.btn-default(type="button", data-dismiss="modal") 关闭
button.btn.btn-success(type="submit") 提交
添加路由
app.get('/signin', User.showSignin)
app.get('/signup', User.showSignup)
添加controller方法,修改signup signin重定向地址
// signup
exports.showSignup = function(req, res) {
res.render('signup', {
title: '注册页面'
})
}
exports.showSignin = function(req, res) {
res.render('signin', {
title: '登录页面'
})
}
exports.signup = function(req,res){
var _user = req.body.user
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (user) {
return res.redirect('/signin')
} else {
var user = new User(_user)
user.save(function(err,user){
if (err) {
console.log(err)
}
res.redirect('/')
})
}
})
}
exports.signin = function(req,res){
var _user = req.body.user
var name = _user.name
var password = _user.password
User.findOne({name:_user.name}, function(err,user){
if (err) {
console.log(err)
}
if (!user) {
return res.redirect('/signup')
}
user.comparePassword(password, function(err, isMatch){
if (err) {
console.log(err)
}
if (isMatch){
console.log('Password is match')
req.session.user = user
return res.redirect('/')
} else {
return res.redirect('/signin')
console.log('Password not match')
}
})
})
}
13)用户权限管理
在user schema中添加role字段
password: String,
// 0: nomal user
// 1: verified user
// 2: professonal user
// >10: admin
// >50: super admin
role: {
type: Number,
default: 0
},
在routes.js文件中,访问userlist添加参数 app.get('/admin/userlist', User.signinRequired, User.adminRequired, User.list)
,在user controller中添加方法,如果没有登陆则调到signin,如果没有权限则调到signin
// midware for user
exports.signinRequired = function(req, res, next) {
var user = req.session.user
if (!user) {
return res.redirect('/signin')
}
next()
}
exports.adminRequired = function(req, res, next) {
var user = req.session.user
if (user.role <= 10) {
return res.redirect('/signin')
}
next()
}
给其他路由也加上admin权限管理
//index page
app.get('/', Index.index)
//user
app.post('/user/signup',User.signup)
app.post('/user/signin',User.signin)
app.get('/signin', User.showSignin)
app.get('/signup', User.showSignup)
app.get('/logout',User.logout)
app.get('/admin/userlist', User.signinRequired, User.adminRequired, User.list)
//movie
app.get('/movie/:id', Movie.detail)
app.get('/admin/movie', User.signinRequired, User.adminRequired, Movie.new)
app.get('/admin/update/:id',User.signinRequired, User.adminRequired, Movie.update)
app.post('/admin/movie/new',User.signinRequired, User.adminRequired, Movie.save)
app.get('/admin/list',User.signinRequired, User.adminRequired, Movie.list)
app.delete('/admin/list',User.signinRequired, User.adminRequired, Movie.del)