此程序基于[慕课网](http://www.imooc.com/learn/75)Scott老师的Node.js+MongoDB建站攻略(第一期)视频教程编写,当前所有模块程序均为最新版本,截止2017年4月,代码中包含详细的注释,非常适合初学者。
源码下载地址 :https://github.com/itPoet/i_movie
项目目录结构:
首先schemas下的movie.js
var mongoose = require('mongoose');
var movieSchema = new mongoose.Schema({
title: String,
doctor: String,
language: String,
country: String,
summary: String,
flash: String,
poster: String,
year: Number,
// meta 更新或录入数据的时间记录
meta: {
createAt: {
type: Date,
default: Date.now()
},
updateAt: {
type: Date,
default: Date.now()
},
}
});
// movieSchema.pre 表示每次存储数据之前都先调用这个方法
movieSchema.pre('save', function (next) {
if (this.isNew) {
this.meta.createAt = this.meta.updateAt = Date.now();
} else {
this.meta.updateAt = Date.now();
}
next();
});
// movieSchema 模式的静态方法
movieSchema.statics = {
fetch: function (cb) {
return this
.find({})
.sort('meta.updateAt')
.exec(cb)
},
findById: function (id, cb) {
return this
.findOne({_id: id})
.exec(cb)
}
}
// 导出movieSchema模式
module.exports = movieSchema;
models下movie.js
var mongoose = require('mongoose');
var movieSchema = require('../schemas/movie.js'); //引入'../schemas/movie.js'导出的模式模块
// 编译生成movie模型
var movie = mongoose.model('movie', movieSchema);
// 将movie模型[构造函数]导出
module.exports = movie;
关于mongoose:
https://cnodejs.org/topic/504b4924e2b84515770103dd
最主要的逻辑,程序入口 app.js:
var express = require('express'); // 加载express模块
var app = express(); // 启动Web服务器
var port = process.env.PORT || 3000; // 设置端口号:3000
app.listen(port); // 监听 port[3000]端口
console.log('website start on port' + port);
var path = require('path');
// 引入path模块的作用:因为页面样式的路径放在了bower_components,告诉express,请求页面里所过来的请求中,如果有请求样式或脚本,都让他们去bower_components中去查找
var mongoose = require('mongoose'); // 加载mongoose模块
mongoose.connect('mongodb://localhost:27017/imovie'); // 连接mongodb本地数据库imovie
console.log('MongoDB connection success!');
/* mongoose 简要知识点补充
* mongoose模块构建在mongodb之上,提供了Schema[模式]、Model[模型]和Document[文档]对象,用起来更为方便。
* Schema对象定义文档的结构(类似表结构),可以定义字段和类型、唯一性、索引和验证。
* Model对象表示集合中的所有文档。
* Document对象作为集合中的单个文档的表示。
* mongoose还有Query和Aggregate对象,Query实现查询,Aggregate实现聚合。
* */
app.locals.moment = require('moment'); // 载入moment模块,格式化日期
var serveStatic = require('serve-static'); // 静态文件处理
app.use(serveStatic('public')); // 路径:public
var bodyParser = require('body-parser');
// 因为后台录入页有提交表单的步骤,故加载此模块方法(bodyParser模块来做文件解析),将表单里的数据进行格式化
app.use(bodyParser.urlencoded({extended: true}));
var _underscore = require('underscore'); // _.extend用新对象里的字段替换老的字段
app.set('views', './views/pages'); // 设置视图默认的文件路径
app.set('view engine', 'jade'); // 设置视图引擎:jade
var movie = require('./models/movie.js'); // 载入mongoose编译后的模型movie
// 编写主要页面路由
// index page 首页
app.get('/', function (req, res) {
movie.fetch(function (err, movies) {
if (err) {
console.log(err);
}
res.render('index', { // 渲染index 首页
title: '电影首页',
movies: movies
});
});
});
// detail page 详情页
app.get('/movie/:id', function (req, res) {
var id = req.params.id;
movie.findById(id, function (err, movie) {
res.render('detail', {
title: '电影:' + movie.title,
movie: movie
});
});
});
// admin page 后台录入页
app.get('/admin/movie', function (req, res) {
res.render('admin', {
title: '后台录入页',
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: '后台更新页',
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 = null;
if (id !== 'undefined') { // 已经存在的电影数据
movie.findById(id, function (err, movie) {
if (err) {
console.log(err);
}
_movie = _underscore.extend(movie, movieObj); // 用新对象里的字段替换老的字段
_movie.save(function (err, movie) {
if (err) {
console.log(err);
}
res.redirect('/movie/' + movie._id);
});
});
} else { // 新加的电影
_movie = new movie({
doctor: movieObj.doctor,
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: '电影列表',
movies: movies
});
});
});
// list delete movie data 列表页删除电影
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});
}
});
}
});
4个界面,使用了jade模板:
admin.jade:
extends ../layout
block content
.container
.row
form.form-horizontal(method="post", action="/admin/movie/new")
input(type="hidden", name="movie[_id]", value="#{movie._id}")
//- 隐藏表单域,用来更新电影数据,存储电影ID
.form-group
label.col-sm-2.control-label(for="inputTitle") 电影名称
.col-sm-10
input#inputTitle.form-control(type="text", name="movie[title]", value="#{movie.title}")
.form-group
label.col-sm-2.control-label(for="inputDoctor") 电影导演
.col-sm-10
input#inputDoctor.form-control(type="text", name="movie[doctor]", value="#{movie.doctor}")
.form-group
label.col-sm-2.control-label(for="inputCountry") 国家
.col-sm-10
input#inputCountry.form-control(type="text", name="movie[country]", value="#{movie.country}")
.form-group
label.col-sm-2.control-label(for="inputLanguage") 语种
.col-sm-10
input#inputLanguage.form-control(type="text", name="movie[language]", value="#{movie.language}")
.form-group
label.col-sm-2.control-label(for="inputPoster") 海报地址
.col-sm-10
input#inputPoster.form-control(type="text", name="movie[poster]", value="#{movie.poster}")
.form-group
label.col-sm-2.control-label(for="inputFlash") 片源地址
.col-sm-10
input#inputFlash.form-control(type="text", name="movie[flash]", value="#{movie.flash}")
.form-group
label.col-sm-2.control-label(for="inputYear") 上映年代
.col-sm-10
input#inputYear.form-control(type="text", name="movie[year]", value="#{movie.year}")
.form-group
label.col-sm-2.control-label(for="inputSummary") 电影简介
.col-sm-10
textarea#inputSummary.form-control(type="text", name="movie[summary]", value="#{movie.summary}")
.form-group
.col-sm-offset-2.col-sm-10
button.btn.btn-default(type="submit") 录入
detail.jade:
extends ../layout
block content
.container
.row
.col-md-7
embed(src="#{movie.flash}", allowFullScreen="true", quality="high", width="700", height="450", align="middle", type="application/x-shockwave-flash")
.col-md-5
dl.dl-horizontal
dt 电影名称
dd= movie.title
dt 导演
dd= movie.doctor
dt 国家
dd= movie.country
dt 语言
dd= movie.language
dt 上映年份
dd= movie.year
dt 简介
dd= movie.summary
index.jade:
extends ../layout
block content
.container
.row
each item in movies
.col-md-2
.thumbnail
a(href="/movie/#{item._id}")
img(src="#{item.poster}", alt="#{item.title}")
.caption
h3 #{item.title}
p: a.btn.btn-primary(href="/movie/#{item._id}", role="button") 观看预告片
list.jade:
extends ../layout
block content
.container
.row
table.table.table-hover.table-bordered
thead
tr
th 电影名称
th 导演
th 国家
th 上映年份
th 录入时间
th 查看
th 更新
th 删除
tbody
each item in movies
tr(class="item-id-#{item._id}")
td #{item.title}
td #{item.doctor}
td #{item.country}
td #{item.year}
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}") 删除
script(src="/js/admin.js")
运行效果图: