├── .bowerrc ├── .gitignore ├── README.md ├── app.js ├── bower.json ├── models └── movie.js ├── package.json ├── public └── js │ └── admin.js ├── schemas └── movie.js └── views ├── includes ├── head.jade ├── head.pug ├── header.jade └── header.pug ├── layout.jade └── pages ├── admin.jade ├── detail.jade ├── index.jade └── list.jade /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/libs" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public/libs 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 慕课网 - Node.js+MongoDB建站攻略(一期)参考源码 2 | 3 | > *提示:本项目不再维护,慎用!* 4 | 5 | 此程序基于[慕课网](http://www.imooc.com/learn/75)Scott老师的Node.js+MongoDB建站攻略(第一期)视频教程编写,当前所有模块程序均为最新版本,截止2017年4月,代码中包含详细的注释,非常适合初学者。 6 | 7 | #### Demo使用流程 8 | ##### 1、 **本地环境安装`Node.js`** 9 | * 1.1、 具体的安装方法,建议多看几篇博客文章,熟悉后再去尝试。如果不考虑对Node.js进行版本管理(_适用于对**Node.js**有一定了解的同学_),安装时可以一路**next**。 10 | * 1.2、 Node.js中文网:[nodejs.cn](http://nodejs.cn/) 11 | * 1.3、 Node.js中文网提供的下载页:[传送门](http://nodejs.cn/download/),选择操作系统对应的版本下载。 12 | * 1.4、 检测Node.js是否安装成功,命令窗口[cmd]`$ node -v`。若出现具体的版本号,表示安装成功。 13 | ![Node.js是否安装成功检测](http://ojzaff7fe.bkt.clouddn.com/nodejs%E7%89%88%E6%9C%AC%E6%A3%80%E6%B5%8B.jpg) 14 | 15 | ##### 2、 **安装`MongoDB`** 16 | * 2.1、 MongoDB中文网:[mongodb.org.cn](http://www.mongodb.org.cn/) 17 | * 2.2、 MongoDB下载链接:[传送门](https://www.mongodb.com/download-center#atlas) 18 | * 2.3、 MongoDB中文网教程(包含安装):[传送门](http://www.mongodb.org.cn/tutorial/) 19 | ![MongoDB中文网教程](http://ojzaff7fe.bkt.clouddn.com/mongodb%E6%95%99%E7%A8%8B%EF%BC%88%E5%8C%85%E5%90%AB%E5%AE%89%E8%A3%85%EF%BC%89.jpg) 20 | * 2.4、 检测MongoDB是否安装成功,命令窗口[cmd]`$ mongo -version`。若出现具体的版本号,表示安装成功。 21 | ![MongoDB安装成功输出信息](http://ojzaff7fe.bkt.clouddn.com/mongodb%E5%AE%89%E8%A3%85%E6%88%90%E5%8A%9F%E8%BE%93%E5%87%BA%E4%BF%A1%E6%81%AF.jpg) 22 | * 2.5、 MongoDB安装成功后,将安装路径下的`bin`目录,例如博主的是:`"C:\Develop\MongoDB\Server\3.4\bin"`添加到系统环境变量,这样便可以直接在命令窗口[cmd]直接执行bin文件里面的命令。 23 | ![bin文件目录](http://ojzaff7fe.bkt.clouddn.com/mongo-bin-%E7%9B%AE%E5%BD%95.jpg) 24 | * 2.6、 例如:Windows 10 环境变量添加流程。 25 | ![环境变量添加流程](http://ojzaff7fe.bkt.clouddn.com/%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E6%B7%BB%E5%8A%A0%E6%B5%81%E7%A8%8B.jpg) 26 | * 2.7、 设置存储路径,建议在C盘下创建`C:/data/db`目录文件夹,这是MongoDB数据库默认的数据存储路径,但需要手动创建。 27 | 28 | ##### 3、 启动`MongoDB`服务:`mongod` 29 | * 3.1、 如果本地存在`C:/data/db`文件夹,命令窗口[cmd]`$ mongod`,便可开启MongoDB服务,启动后**请勿关闭窗口**; 30 | ![mongod开启服务](http://ojzaff7fe.bkt.clouddn.com/mongod%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1.jpg) 31 | * 3.2、 同时,再新开一个命令窗口[cmd]`$ mongo`,就可以用命令来管理数据库,例如:数据的**增删改查**; 32 | ![mongo](http://ojzaff7fe.bkt.clouddn.com/mongo%E6%89%A7%E8%A1%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E5%91%BD%E4%BB%A4.jpg) 33 | 34 | ##### 4、 安装`bower`依赖:`$ bower install` 35 | * 4.1、 在Demo项目文件夹下,按住`shift`键的同时按下鼠标右键,选择在此处打开命令窗口,执行命令:`$ bower install`; 36 | ![mongo](http://ojzaff7fe.bkt.clouddn.com/%E9%BC%A0%E6%A0%87%E5%8F%B3%E9%94%AE%E6%89%93%E5%BC%80powershell.jpg) 37 | 38 | ##### 5、 安装`npm`依赖:`$ npm install` 39 | * 5.1、 在Demo项目文件夹下,按住`shift`键的同时按下鼠标右键,选择在此处打开命令窗口,执行命令:`$ npm install`; 40 | 41 | ##### 6、 启动项目入口文件:`$ node app.js` 42 | * 6.1、 在Demo项目文件夹下,按住`shift`键的同时按下鼠标右键,选择在此处打开命令窗口,执行命令:`$ node app`; 43 | 44 | ##### 7、 浏览器查看效果 45 | * 7.1、 `http://localhost:3000`查看首页效果。 46 | * 7.2、 `http://localhost:3000/admin/list`列表页 47 | * 7.3、 `http://localhost:3000/admin/movie`后台录入页 48 | 49 | **至此,本案例源码使用流程介绍完毕。在此过程遇到问题的同学,请前往[评论区](http://itpoet.cn/2017/12/19/build-movie-website-based-on-nodejs-and-mongodb/)留言。** 50 | 51 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); // 加载express模块 2 | var app = express(); // 启动Web服务器 3 | 4 | var port = process.env.PORT || 3000; // 设置端口号:3000 5 | app.listen(port); // 监听 port[3000]端口 6 | console.log('i_movie start on port' + port); 7 | 8 | var path = require('path'); 9 | // 引入path模块的作用:因为页面样式的路径放在了bower_components,告诉express,请求页面里所过来的请求中,如果有请求样式或脚本,都让他们去bower_components中去查找 10 | 11 | var mongoose = require('mongoose'); // 加载mongoose模块 12 | mongoose.connect('mongodb://localhost:27017/imovie'); // 连接mongodb本地数据库imovie 13 | console.log('MongoDB connection success!'); 14 | /* mongoose 简要知识点补充 15 | * mongoose模块构建在mongodb之上,提供了Schema[模式]、Model[模型]和Document[文档]对象,用起来更为方便。 16 | * Schema对象定义文档的结构(类似表结构),可以定义字段和类型、唯一性、索引和验证。 17 | * Model对象表示集合中的所有文档。 18 | * Document对象作为集合中的单个文档的表示。 19 | * mongoose还有Query和Aggregate对象,Query实现查询,Aggregate实现聚合。 20 | * */ 21 | 22 | app.locals.moment = require('moment'); // 载入moment模块,格式化日期 23 | 24 | var serveStatic = require('serve-static'); // 静态文件处理 25 | app.use(serveStatic('public')); // 路径:public 26 | 27 | var bodyParser = require('body-parser'); 28 | // 因为后台录入页有提交表单的步骤,故加载此模块方法(bodyParser模块来做文件解析),将表单里的数据进行格式化 29 | app.use(bodyParser.urlencoded({extended: true})); 30 | 31 | var _underscore = require('underscore'); // _.extend用新对象里的字段替换老的字段 32 | 33 | app.set('views', './views/pages'); // 设置视图默认的文件路径 34 | app.set('view engine', 'jade'); // 设置视图引擎:jade 35 | 36 | var movie = require('./models/movie.js'); // 载入mongoose编译后的模型movie 37 | 38 | // 编写主要页面路由 39 | // index page 首页 40 | app.get('/', function (req, res) { 41 | movie.fetch(function (err, movies) { 42 | if (err) { 43 | console.log(err); 44 | } 45 | res.render('index', { // 渲染index 首页 46 | title: 'i_movie 首页', 47 | movies: movies 48 | }); 49 | }); 50 | }); 51 | 52 | // detail page 详情页 53 | app.get('/movie/:id', function (req, res) { 54 | var id = req.params.id; 55 | movie.findById(id, function (err, movie) { 56 | res.render('detail', { 57 | title: 'i_movie' + movie.title, 58 | movie: movie 59 | }); 60 | }); 61 | }); 62 | 63 | // admin page 后台录入页 64 | app.get('/admin/movie', function (req, res) { 65 | res.render('admin', { 66 | title: 'i_movie 后台录入页', 67 | movie: { 68 | title: '', 69 | doctor: '', 70 | country: '', 71 | year: '', 72 | poster: '', 73 | flash: '', 74 | summary: '', 75 | language: '' 76 | } 77 | }); 78 | }); 79 | 80 | // admin update movie 后台更新页 81 | app.get('/admin/update/:id', function (req, res) { 82 | var id = req.params.id; 83 | if (id) { 84 | movie.findById(id, function (err, movie) { 85 | res.render('admin', { 86 | title: 'imovie 后台更新页', 87 | movie: movie 88 | }); 89 | }); 90 | } 91 | }); 92 | 93 | // admin post movie 后台录入提交 94 | app.post('/admin/movie/new', function (req, res) { 95 | var id = req.body.movie._id; 96 | var movieObj = req.body.movie; 97 | var _movie = null; 98 | if (id !== 'undefined') { // 已经存在的电影数据 99 | movie.findById(id, function (err, movie) { 100 | if (err) { 101 | console.log(err); 102 | } 103 | _movie = _underscore.extend(movie, movieObj); // 用新对象里的字段替换老的字段 104 | _movie.save(function (err, movie) { 105 | if (err) { 106 | console.log(err); 107 | } 108 | res.redirect('/movie/' + movie._id); 109 | }); 110 | }); 111 | } else { // 新加的电影 112 | _movie = new movie({ 113 | doctor: movieObj.doctor, 114 | title: movieObj.title, 115 | country: movieObj.country, 116 | language: movieObj.language, 117 | year: movieObj.year, 118 | poster: movieObj.poster, 119 | summary: movieObj.summary, 120 | flash: movieObj.flash 121 | }); 122 | _movie.save(function (err, movie) { 123 | if (err) { 124 | console.log(err); 125 | } 126 | res.redirect('/movie/' + movie._id); 127 | }); 128 | } 129 | }); 130 | 131 | // list page 列表页 132 | app.get('/admin/list', function (req, res) { 133 | movie.fetch(function (err, movies) { 134 | if (err) { 135 | console.log(err); 136 | } 137 | res.render('list', { 138 | title: 'i_movie 列表页', 139 | movies: movies 140 | }); 141 | }); 142 | }); 143 | 144 | // list delete movie data 列表页删除电影 145 | app.delete('/admin/list', function (req, res) { 146 | var id = req.query.id; 147 | if (id) { 148 | movie.remove({_id: id}, function (err, movie) { 149 | if (err) { 150 | console.log(err); 151 | } else { 152 | res.json({success: 1}); 153 | } 154 | }); 155 | } 156 | }); 157 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i_movie", 3 | "authors": [ 4 | "itpoet " 5 | ], 6 | "description": "itPoet imooc movie", 7 | "main": "app.js", 8 | "keywords": [ 9 | "Node.js", 10 | "MongoDB" 11 | ], 12 | "license": "MIT", 13 | "homepage": "", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "public/libs", 19 | "test", 20 | "tests" 21 | ], 22 | "dependencies": { 23 | "bootstrap": "^3.3.7" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /models/movie.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var movieSchema = require('../schemas/movie.js'); //引入'../schemas/movie.js'导出的模式模块 3 | 4 | // 编译生成movie模型 5 | var movie = mongoose.model('movie', movieSchema); 6 | 7 | // 将movie模型[构造函数]导出 8 | module.exports = movie; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i_movie", 3 | "version": "1.0.0", 4 | "description": "itPoet imooc movie", 5 | "main": "app.js", 6 | "dependencies": { 7 | "body-parser": "^1.17.1", 8 | "express": "^4.15.2", 9 | "jade": "^1.11.0", 10 | "moment": "^2.18.1", 11 | "mongoose": "^4.9.3", 12 | "pug": "^2.0.0-beta11", 13 | "serve-static": "^1.12.1", 14 | "underscore": "^1.8.3" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "keywords": [ 21 | "Node.js", 22 | "MongoDB" 23 | ], 24 | "author": "itPoet", 25 | "license": "ISC" 26 | } 27 | -------------------------------------------------------------------------------- /public/js/admin.js: -------------------------------------------------------------------------------- 1 | // 处理删除电影数据的逻辑 2 | $(function () { 3 | $('.del').click(function (e) { 4 | var target = $(e.target); 5 | var id = target.data('id'); 6 | var tr = $('.item-id-' + id); 7 | 8 | $.ajax({ 9 | type: 'DELETE', // 异步请求类型:删除 10 | url: '/admin/list?id=' + id, 11 | }) 12 | .done(function (results) { 13 | if (results.success === 1) { 14 | if (tr.length > 0) { 15 | tr.remove(); 16 | } 17 | } 18 | }); 19 | }); 20 | }); -------------------------------------------------------------------------------- /schemas/movie.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | var movieSchema = new mongoose.Schema({ 4 | title: String, 5 | doctor: String, 6 | language: String, 7 | country: String, 8 | summary: String, 9 | flash: String, 10 | poster: String, 11 | year: Number, 12 | // meta 更新或录入数据的时间记录 13 | meta: { 14 | createAt: { 15 | type: Date, 16 | default: Date.now() 17 | }, 18 | updateAt: { 19 | type: Date, 20 | default: Date.now() 21 | }, 22 | } 23 | }); 24 | 25 | // movieSchema.pre 表示每次存储数据之前都先调用这个方法 26 | movieSchema.pre('save', function (next) { 27 | if (this.isNew) { 28 | this.meta.createAt = this.meta.updateAt = Date.now(); 29 | } else { 30 | this.meta.updateAt = Date.now(); 31 | } 32 | next(); 33 | }); 34 | 35 | // movieSchema 模式的静态方法 36 | movieSchema.statics = { 37 | fetch: function (cb) { 38 | return this 39 | .find({}) 40 | .sort('meta.updateAt') 41 | .exec(cb) 42 | }, 43 | findById: function (id, cb) { 44 | return this 45 | .findOne({_id: id}) 46 | .exec(cb) 47 | } 48 | } 49 | 50 | // 导出movieSchema模式 51 | module.exports = movieSchema; 52 | 53 | -------------------------------------------------------------------------------- /views/includes/head.jade: -------------------------------------------------------------------------------- 1 | link(href="/libs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet") 2 | script(src="/libs/jquery/dist/jquery.min.js") 3 | script(src="/libs/bootstrap/dist/js/bootstrap.min.js") -------------------------------------------------------------------------------- /views/includes/head.pug: -------------------------------------------------------------------------------- 1 | link(href="/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet") 2 | script(src="/jquery/dist/jquery.min.js") 3 | script(src="/bootstrap/dist/js/bootstrap.min.js") -------------------------------------------------------------------------------- /views/includes/header.jade: -------------------------------------------------------------------------------- 1 | .container 2 | .row 3 | .page-header.clearfix 4 | h1= title 5 | .col-md-4 6 | .small 重度科幻迷~~~ 7 | .col-md-8 8 | form(method="GET",action="/results") 9 | .input-group.col-sm-4.pull-right 10 | input.form-control(type='text',name='q') 11 | span.input-group-btn 12 | button.btn.btn-default(type="submit") 搜索 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /views/includes/header.pug: -------------------------------------------------------------------------------- 1 | .container 2 | .row 3 | .page-header.clearfix 4 | h1= title 5 | .col-md-4 6 | .small 重度科幻迷~~~ 7 | .col-md-8 8 | form(method="GET",action="/results") 9 | .input-group.col-sm-4.pull-right 10 | input.form-control(type='text',name='q') 11 | span.input-group-btn 12 | button.btn.btn-default(type="submit") 搜索 13 | .navbar.navbar-default.navbar-fixed-bottom 14 | .container 15 | .navbar-header 16 | a.navbar-brand(href="/") 重度科幻迷 17 | if user 18 | p.navbar-text.navbar-right 19 | span 欢迎您,#{user.name} 20 | span  |  21 | a.navbar-link(href="/logout") 登出 22 | else 23 | p.navbar-text.navbar-right 24 | a.navbar-link(href="/signup") 注册 25 | span  |  26 | a.navbar-link(href="/signin") 登录 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | meta(charset="utf-8") 5 | title #{title} 6 | include ./includes/head.jade 7 | body 8 | include ./includes/header.jade 9 | block content -------------------------------------------------------------------------------- /views/pages/admin.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | .container 5 | .row 6 | form.form-horizontal(method="post", action="/admin/movie/new") 7 | input(type="hidden", name="movie[_id]", value="#{movie._id}") 8 | //- 隐藏表单域,用来更新电影数据,存储电影ID 9 | .form-group 10 | label.col-sm-2.control-label(for="inputTitle") 电影名称 11 | .col-sm-10 12 | input#inputTitle.form-control(type="text", name="movie[title]", value="#{movie.title}") 13 | .form-group 14 | label.col-sm-2.control-label(for="inputDoctor") 电影导演 15 | .col-sm-10 16 | input#inputDoctor.form-control(type="text", name="movie[doctor]", value="#{movie.doctor}") 17 | .form-group 18 | label.col-sm-2.control-label(for="inputCountry") 国家 19 | .col-sm-10 20 | input#inputCountry.form-control(type="text", name="movie[country]", value="#{movie.country}") 21 | .form-group 22 | label.col-sm-2.control-label(for="inputLanguage") 语种 23 | .col-sm-10 24 | input#inputLanguage.form-control(type="text", name="movie[language]", value="#{movie.language}") 25 | .form-group 26 | label.col-sm-2.control-label(for="inputPoster") 海报地址 27 | .col-sm-10 28 | input#inputPoster.form-control(type="text", name="movie[poster]", value="#{movie.poster}") 29 | .form-group 30 | label.col-sm-2.control-label(for="inputFlash") 片源地址 31 | .col-sm-10 32 | input#inputFlash.form-control(type="text", name="movie[flash]", value="#{movie.flash}") 33 | .form-group 34 | label.col-sm-2.control-label(for="inputYear") 上映年代 35 | .col-sm-10 36 | input#inputYear.form-control(type="text", name="movie[year]", value="#{movie.year}") 37 | .form-group 38 | label.col-sm-2.control-label(for="inputSummary") 电影简介 39 | .col-sm-10 40 | textarea#inputSummary.form-control(type="text", name="movie[summary]", value="#{movie.summary}") 41 | .form-group 42 | .col-sm-offset-2.col-sm-10 43 | button.btn.btn-default(type="submit") 录入 -------------------------------------------------------------------------------- /views/pages/detail.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | .container 5 | .row 6 | .col-md-7 7 | embed(src="#{movie.flash}", allowFullScreen="true", quality="high", width="700", height="450", align="middle", type="application/x-shockwave-flash") 8 | .col-md-5 9 | dl.dl-horizontal 10 | dt 电影名称 11 | dd= movie.title 12 | dt 导演 13 | dd= movie.doctor 14 | dt 国家 15 | dd= movie.country 16 | dt 语言 17 | dd= movie.language 18 | dt 上映年份 19 | dd= movie.year 20 | dt 简介 21 | dd= movie.summary 22 | -------------------------------------------------------------------------------- /views/pages/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | block content 3 | .container 4 | .row 5 | each item in movies 6 | .col-md-2 7 | .thumbnail 8 | a(href="/movie/#{item._id}") 9 | img(src="#{item.poster}", alt="#{item.title}") 10 | .caption 11 | h3 #{item.title} 12 | p: a.btn.btn-primary(href="/movie/#{item._id}", role="button") 观看预告片 -------------------------------------------------------------------------------- /views/pages/list.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | block content 3 | .container 4 | .row 5 | table.table.table-hover.table-bordered 6 | thead 7 | tr 8 | th 电影名称 9 | th 导演 10 | th 国家 11 | th 上映年份 12 | th 录入时间 13 | th 查看 14 | th 更新 15 | th 删除 16 | tbody 17 | each item in movies 18 | tr(class="item-id-#{item._id}") 19 | td #{item.title} 20 | td #{item.doctor} 21 | td #{item.country} 22 | td #{item.year} 23 | td #{moment(item.meta.updateAt).format('MM/DD/YYYY')} 24 | td: a(target="_blank", href="../movie/#{item._id}") 查看 25 | td: a(target="_blank", href="../admin/update/#{item._id}") 修改 26 | td 27 | button.btn.btn-danger.del(type="button", data-id="#{item._id}") 删除 28 | script(src="/js/admin.js") --------------------------------------------------------------------------------