Repository: nswbmw/N-blog Branch: master Commit: c842ec496fd9 Files: 64 Total size: 128.6 KB Directory structure: gitextract_ihiav8sp/ ├── .editorconfig ├── .eslintrc.json ├── .github/ │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── README.md ├── book/ │ ├── 1.1 Node.js 的安装与使用.md │ ├── 1.2 MongoDB 的安装与使用.md │ ├── 2.1 require.md │ ├── 2.2 exports 和 module.exports.md │ ├── 2.3 Promise.md │ ├── 2.4 环境变量.md │ ├── 2.5 package.json.md │ ├── 2.6 npm 使用注意事项.md │ ├── 3.1 初始化一个 Express 项目.md │ ├── 3.2 路由.md │ ├── 3.3 模板引擎.md │ ├── 3.4 Express 浅析.md │ ├── 4.1 开发环境.md │ ├── 4.10 留言.md │ ├── 4.11 404 页面.md │ ├── 4.12 错误页面.md │ ├── 4.13 日志.md │ ├── 4.14 测试.md │ ├── 4.15 部署.md │ ├── 4.2 准备工作.md │ ├── 4.3 配置文件.md │ ├── 4.4 功能设计.md │ ├── 4.5 页面设计.md │ ├── 4.6 连接数据库.md │ ├── 4.7 注册.md │ ├── 4.8 登出与登录.md │ └── 4.9 文章.md ├── config/ │ └── default.js ├── index.js ├── lib/ │ └── mongo.js ├── logs/ │ └── .gitignore ├── middlewares/ │ └── check.js ├── models/ │ ├── comments.js │ ├── posts.js │ └── users.js ├── package.json ├── public/ │ ├── css/ │ │ └── style.css │ └── img/ │ └── .gitignore ├── routes/ │ ├── comments.js │ ├── index.js │ ├── posts.js │ ├── signin.js │ ├── signout.js │ └── signup.js ├── test/ │ └── signup.js └── views/ ├── 404.ejs ├── components/ │ ├── comments.ejs │ ├── nav-setting.ejs │ ├── nav.ejs │ ├── notification.ejs │ └── post-content.ejs ├── create.ejs ├── edit.ejs ├── footer.ejs ├── header.ejs ├── post.ejs ├── posts.ejs ├── signin.ejs └── signup.ejs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true tab_width = 2 [*.md] trim_trailing_whitespace = false [Makefile] indent_style = tab ================================================ FILE: .eslintrc.json ================================================ { "extends": "standard", "globals": { "describe": true, "beforeEach": true, "afterEach": true, "after": true, "it": true } } ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ 提问方式: 1. 请先从错误栈自己定位问题,尝试亲自解决问题 2. 解决不了再去历史 isuue 里查看是否有相似的问题 3. 最后,提交新的 issue,并将错误代码提交到你的 GitHub,我抽空会帮你调试 不好的提问方式: 1. 只有标题,没有描述 2. 描述不清楚 ================================================ FILE: .gitignore ================================================ config/* !config/default.* npm-debug.log node_modules coverage ================================================ FILE: README.md ================================================ ## N-blog 使用 Express + MongoDB 搭建多人博客 ## 开发环境 - Node.js: `8.9.1` - MongoDB: `3.4.10` - Express: `4.16.2` ## 目录 - 开发环境搭建 - [Node.js 的安装与使用](https://github.com/nswbmw/N-blog/blob/master/book/1.1%20Node.js%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md) - [安装 Node.js](https://github.com/nswbmw/N-blog/blob/master/book/1.1%20Node.js%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md#111-安装-nodejs) - [n 和 nvm](https://github.com/nswbmw/N-blog/blob/master/book/1.1%20Node.js%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md#112-n-和-nvm) - [nrm](https://github.com/nswbmw/N-blog/blob/master/book/1.1%20Node.js%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md#113-nrm) - [MongoDB 的安装与使用](https://github.com/nswbmw/N-blog/blob/master/book/1.2%20MongoDB%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md) - [安装与启动 MongoDB](https://github.com/nswbmw/N-blog/blob/master/book/1.2%20MongoDB%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md#121-安装与启动-mongodb) - [Robomongo 和 MongoChef](https://github.com/nswbmw/N-blog/blob/master/book/1.2%20MongoDB%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md#122-robomongo-和-mongochef) - Node.js 知识点讲解 - [require](https://github.com/nswbmw/N-blog/blob/master/book/2.1%20require.md) - [exports 和 module.exports](https://github.com/nswbmw/N-blog/blob/master/book/2.2%20exports%20%E5%92%8C%20module.exports.md) - [Promise](https://github.com/nswbmw/N-blog/blob/master/book/2.3%20Promise.md) - [环境变量](https://github.com/nswbmw/N-blog/blob/master/book/2.4%20%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F.md) - [packge.json](https://github.com/nswbmw/N-blog/blob/master/book/2.5%20package.json.md) - [semver](https://github.com/nswbmw/N-blog/blob/master/book/2.5%20package.json.md#251-semver) - [npm 使用注意事项](https://github.com/nswbmw/N-blog/blob/master/book/2.6%20npm%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md) - [npm init](https://github.com/nswbmw/N-blog/blob/master/book/2.6%20npm%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md#261-npm-init) - [npm install](https://github.com/nswbmw/N-blog/blob/master/book/2.6%20npm%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md#262-npm-install) - [npm scripts](https://github.com/nswbmw/N-blog/blob/master/book/2.6%20npm%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md#263-npm-scripts) - [npm shrinkwrap ](https://github.com/nswbmw/N-blog/blob/master/book/2.6%20npm%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md#264-npm-shrinkwrap) - Hello, Express - [初始化一个 Express 项目](https://github.com/nswbmw/N-blog/blob/master/book/3.1%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%20Express%20%E9%A1%B9%E7%9B%AE.md) - [supervisor](https://github.com/nswbmw/N-blog/blob/master/book/3.1%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%20Express%20%E9%A1%B9%E7%9B%AE.md#311-supervisor) - [路由](https://github.com/nswbmw/N-blog/blob/master/book/3.2%20%E8%B7%AF%E7%94%B1.md) - [express.Router](https://github.com/nswbmw/N-blog/blob/master/book/3.2%20%E8%B7%AF%E7%94%B1.md#321-expressrouter) - [模板引擎](https://github.com/nswbmw/N-blog/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md) - [ejs](https://github.com/nswbmw/N-blog/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md#331-ejs) - [includes](https://github.com/nswbmw/N-blog/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md#332-includes) - [Express 浅析](https://github.com/nswbmw/N-blog/blob/master/book/3.4%20Express%20%E6%B5%85%E6%9E%90.md) - [中间件与 next](https://github.com/nswbmw/N-blog/blob/master/book/3.4%20Express%20%E6%B5%85%E6%9E%90.md#341-中间件与-next) - [错误处理](https://github.com/nswbmw/N-blog/blob/master/book/3.4%20Express%20%E6%B5%85%E6%9E%90.md#342-错误处理) - 一个简单的博客 - [开发环境](https://github.com/nswbmw/N-blog/blob/master/book/4.1%20%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83.md) - [准备工作](https://github.com/nswbmw/N-blog/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md) - [目录结构](https://github.com/nswbmw/N-blog/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md#421-目录结构) - [安装依赖模块](https://github.com/nswbmw/N-blog/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md#422-安装依赖模块) - [ESLint](https://github.com/nswbmw/N-blog/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md#423-eslint) - [EditorConfig](https://github.com/nswbmw/N-blog/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md#424-editorconfig) - [配置文件](https://github.com/nswbmw/N-blog/blob/master/book/4.3%20%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6.md) - [config-lite](https://github.com/nswbmw/N-blog/blob/master/book/4.3%20%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6.md#431-config-lite) - [功能设计](https://github.com/nswbmw/N-blog/blob/master/book/4.4%20%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1.md) - [功能与路由设计](https://github.com/nswbmw/N-blog/blob/master/book/4.4%20%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1.md#441-功能与路由设计) - [会话](https://github.com/nswbmw/N-blog/blob/master/book/4.4%20%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1.md#442-会话) - [页面通知](https://github.com/nswbmw/N-blog/blob/master/book/4.4%20%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1.md#443-页面通知) - [权限控制](https://github.com/nswbmw/N-blog/blob/master/book/4.4%20%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1.md#444-权限控制) - [页面设计](https://github.com/nswbmw/N-blog/blob/master/book/4.5%20%E9%A1%B5%E9%9D%A2%E8%AE%BE%E8%AE%A1.md) - [组件](https://github.com/nswbmw/N-blog/blob/master/book/4.5%20%E9%A1%B5%E9%9D%A2%E8%AE%BE%E8%AE%A1.md#451-组件) - [app.locals 和 res.locals](https://github.com/nswbmw/N-blog/blob/master/book/4.5%20%E9%A1%B5%E9%9D%A2%E8%AE%BE%E8%AE%A1.md#452-applocals-和-reslocals) - [连接数据库](https://github.com/nswbmw/N-blog/blob/master/book/4.6%20%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93.md) - [为什么使用 Mongolass](https://github.com/nswbmw/N-blog/blob/master/book/4.6%20%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93.md#461-为什么使用-mongolass) - [注册](https://github.com/nswbmw/N-blog/blob/master/book/4.7%20%E6%B3%A8%E5%86%8C.md) - [用户模型设计](https://github.com/nswbmw/N-blog/blob/master/book/4.7%20%E6%B3%A8%E5%86%8C.md#471-用户模型设计) - [注册页](https://github.com/nswbmw/N-blog/blob/master/book/4.7%20%E6%B3%A8%E5%86%8C.md#472-注册页) - [注册与文件上传](https://github.com/nswbmw/N-blog/blob/master/book/4.7%20%E6%B3%A8%E5%86%8C.md#473-注册与文件上传) - [登出与登录](https://github.com/nswbmw/N-blog/blob/master/book/4.8%20%E7%99%BB%E5%87%BA%E4%B8%8E%E7%99%BB%E5%BD%95.md) - [登出](https://github.com/nswbmw/N-blog/blob/master/book/4.8%20%E7%99%BB%E5%87%BA%E4%B8%8E%E7%99%BB%E5%BD%95.md#481-登出) - [登录页](https://github.com/nswbmw/N-blog/blob/master/book/4.8%20%E7%99%BB%E5%87%BA%E4%B8%8E%E7%99%BB%E5%BD%95.md#482-登录页) - [登录](https://github.com/nswbmw/N-blog/blob/master/book/4.8%20%E7%99%BB%E5%87%BA%E4%B8%8E%E7%99%BB%E5%BD%95.md#483-登录) - [文章](https://github.com/nswbmw/N-blog/blob/master/book/4.9%20%E6%96%87%E7%AB%A0.md) - [文章模型设计](https://github.com/nswbmw/N-blog/blob/master/book/4.9%20%E6%96%87%E7%AB%A0.md#491-文章模型设计) - [发表文章](https://github.com/nswbmw/N-blog/blob/master/book/4.9%20%E6%96%87%E7%AB%A0.md#492-发表文章) - [主页与文章页](https://github.com/nswbmw/N-blog/blob/master/book/4.9%20%E6%96%87%E7%AB%A0.md#493-主页与文章页) - [编辑与删除文章](https://github.com/nswbmw/N-blog/blob/master/book/4.9%20%E6%96%87%E7%AB%A0.md#494-编辑与删除文章) - [留言](https://github.com/nswbmw/N-blog/blob/master/book/4.10%20%E7%95%99%E8%A8%80.md) - [留言模型设计](https://github.com/nswbmw/N-blog/blob/master/book/4.10%20%E7%95%99%E8%A8%80.md#4101-留言模型设计) - [显示留言](https://github.com/nswbmw/N-blog/blob/master/book/4.10%20%E7%95%99%E8%A8%80.md#4102-显示留言) - [发表与删除留言](https://github.com/nswbmw/N-blog/blob/master/book/4.10%20%E7%95%99%E8%A8%80.md#4103-发表与删除留言) - [404页面](https://github.com/nswbmw/N-blog/blob/master/book/4.11%20404%20%E9%A1%B5%E9%9D%A2.md) - [错误页面](https://github.com/nswbmw/N-blog/blob/master/book/4.12%20%E9%94%99%E8%AF%AF%E9%A1%B5%E9%9D%A2.md) - [日志](https://github.com/nswbmw/N-blog/blob/master/book/4.13%20%E6%97%A5%E5%BF%97.md) - [winston 和 express-winston](https://github.com/nswbmw/N-blog/blob/master/book/4.13%20%E6%97%A5%E5%BF%97.md#4131-winston-和-express-winston) - [.gitignore](https://github.com/nswbmw/N-blog/blob/master/book/4.13%20%E6%97%A5%E5%BF%97.md#4132-gitignore) - [测试](https://github.com/nswbmw/N-blog/blob/master/book/4.14%20%E6%B5%8B%E8%AF%95.md) - [mocha 和 supertest](https://github.com/nswbmw/N-blog/blob/master/book/4.14%20%E6%B5%8B%E8%AF%95.md#4141-mocha-和-supertest) - [测试覆盖率](https://github.com/nswbmw/N-blog/blob/master/book/4.14%20%E6%B5%8B%E8%AF%95.md#4142-测试覆盖率) - [部署](https://github.com/nswbmw/N-blog/blob/master/book/4.15%20%E9%83%A8%E7%BD%B2.md) - [申请 MLab](https://github.com/nswbmw/N-blog/blob/master/book/4.15%20%E9%83%A8%E7%BD%B2.md#4151-申请-mlab) - [pm2](https://github.com/nswbmw/N-blog/blob/master/book/4.15%20%E9%83%A8%E7%BD%B2.md#4152-pm2) - [部署到 Heroku](https://github.com/nswbmw/N-blog/blob/master/book/4.15%20%E9%83%A8%E7%BD%B2.md#4152-部署到-heroku) - [部署到 UCloud](https://github.com/nswbmw/N-blog/blob/master/book/4.15%20%E9%83%A8%E7%BD%B2.md#4153-部署到-ucloud) - [部署到阿里云](https://github.com/nswbmw/N-blog/blob/master/book/4.15%20%E9%83%A8%E7%BD%B2.md#4154-部署到阿里云) - 扩展训练 - 添加分页功能 - 添加二级评论功能 - 添加标签(tag)功能 ## 捐赠 您的捐赠,是我持续开源的动力。 支付宝 | 微信 ------|------  |  ================================================ FILE: book/1.1 Node.js 的安装与使用.md ================================================ ## 1.1.1 安装 Node.js 有三种方式安装 Node.js:一是通过安装包安装,二是通过源码编译安装,三是在 Linux 下可以通过 yum|apt-get 安装,在 Mac 下可以通过 [Homebrew](http://brew.sh/) 安装。对于 Windows 和 Mac 用户,推荐使用安装包安装,Linux 用户推荐使用源码编译安装。 #### Windows 和 Mac 安装: **第一步:** 打开 [Node.js 官网](https://nodejs.org/en/),可以看到以下两个下载选项:  左边的是 LTS 版,用过 ubuntu 的同学可能比较熟悉,即长期支持版本,大多数人用这个就可以了。右边是最新版,支持最新的语言特性(比如对 ES6 的支持更全面),想尝试新特性的开发者可以安装这个版本。我们选择左边的 v6.9.1 LTS 点击下载。 > 小提示:从 [http://node.green](http://node.green) 上可以看到 Node.js 各个版本对 ES6 的支持情况。 **第二步:** 安装 Node.js,这个没什么好说的,一直点击 `继续` 即可。  **第三步:** 提示安装成功后,打开终端输入以下命令,可以看到 node 和 npm 都已经安装好了:  #### Linux 安装: Linux 用户可通过源码编译安装: ```sh curl -O https://nodejs.org/dist/v6.9.1/node-v6.9.1.tar.gz tar -xzvf node-v6.9.1.tar.gz cd node-v6.9.1 ./configure make make install ``` > 注意: 如果编译过程报错,可能是缺少某些依赖包。因为报错内容不尽相同,请读者自行求助搜索引擎或 [stackoverflow](http://stackoverflow.com/)。 ## 1.1.2 n 和 nvm 通常我们使用稳定的 LTS 版本的 Node.js 即可,但有的情况下我们又想尝试一下新的特性,我们总不能来回安装不同版本的 Node.js 吧,这个时候我们就需要 [n](https://github.com/tj/n) 或者 [nvm](https://github.com/creationix/nvm) 了。n 和 nvm 是两个常用的 Node.js 版本管理工具,关于 n 和 nvm 的使用以及区别,[这篇文章](http://taobaofed.org/blog/2015/11/17/nvm-or-n/) 讲得特别详细,这里不再赘述。 ## 1.1.3 nrm [nrm](https://github.com/Pana/nrm) 是一个管理 npm 源的工具。用过 ruby 和 gem 的同学会比较熟悉,通常我们会把 gem 源切到国内的淘宝镜像,这样在安装和更新一些包的时候比较快。nrm 同理,用来切换官方 npm 源和国内的 npm 源(如: [cnpm](http://cnpmjs.org/)),当然也可以用来切换官方 npm 源和公司私有 npm 源。 全局安装 nrm: ```sh npm i nrm -g ``` 查看当前 nrm 内置的几个 npm 源的地址:  切换到 cnpm:  下一节:[1.2 MongoDB 的安装与使用](https://github.com/nswbmw/N-blog/blob/master/book/1.2%20MongoDB%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md) ================================================ FILE: book/1.2 MongoDB 的安装与使用.md ================================================ ## 1.2.1 安装与启动 MongoDB - Windows 用户向导:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/ - Linux 用户向导:https://docs.mongodb.com/manual/administration/install-on-linux/ - Mac 用户向导:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/ ## 1.2.2 Robomongo 和 Mongochef #### Robomongo [Robomongo](https://robomongo.org/) 是一个基于 Shell 的跨平台开源 MongoDB 可视化管理工具,支持 Windows、Linux 和 Mac,嵌入了 JavaScript 引擎和 MongoDB mongo,只要你会使用 mongo shell,你就会使用 Robomongo,它还提供了语法高亮、自动补全、差别视图等。 [Robomongo 下载地址](https://robomongo.org/download) 下载并安装成功后点击左上角的 `Create` 创建一个连接,给该连接起个名字如: `localhost`,使用默认地址(localhost)和端口(27017)即可,点击 `Save` 保存。  双击 `localhost` 连接到 MongoDB 并进入交互界面,尝试插入一条数据并查询出来,如下所示:  #### MongoChef [MongoChef](http://3t.io/mongochef/) 是另一款强大的 MongoDB 可视化管理工具,支持 Windows、Linux 和 Mac。 [MongoChef 下载地址](http://3t.io/mongochef/#mongochef-download-compare),我们选择左侧的非商业用途的免费版下载。  安装成功后跟 Robomongo 一样,也需要创建一个新的连接的配置,成功后双击进入到 MongoChef 主页面,如下所示:  还可以使用 shell 模式:  > 小提示: MongoChef 相较于 Robomongo 更强大一些,但 Robomongo 比较轻量也能满足大部分的常规需求,所以哪一个适合自己还需读者自行尝试。 上一节:[1.1 Node.js 的安装与使用](https://github.com/nswbmw/N-blog/blob/master/book/1.1%20Node.js%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md) 下一节:[2.1 require](https://github.com/nswbmw/N-blog/blob/master/book/2.1%20require.md) ================================================ FILE: book/2.1 require.md ================================================ require 用来加载一个文件的代码,关于 require 的机制这里不展开讲解,请仔细阅读 [官方文档](https://nodejs.org/api/modules.html)。 简单概括以下几点: - require 可加载 .js、.json 和 .node 后缀的文件 - require 的过程是同步的,所以这样是错误的: ```sh setTimeout(() => { module.exports = { a: 'hello' } }, 0) ``` require 这个文件得到的是空对象 `{}` - require 目录的机制是: - 如果目录下有 package.json 并指定了 main 字段,则用之 - 如果不存在 package.json,则依次尝试加载目录下的 index.js 和 index.node - require 过的文件会加载到缓存,所以多次 require 同一个文件(模块)不会重复加载 - 判断是否是程序的入口文件有两种方式: - require.main === module(推荐) - module.parent === null #### 循环引用 循环引用(或循环依赖)简单点来说就是 a 文件 require 了 b 文件,然后 b 文件又反过来 require 了 a 文件。我们用 a->b 代表 b require 了 a。 简单的情况: ``` a->b b->a ``` 复杂点的情况: ``` a->b b->c c->a ``` 循环引用并不会报错,导致的结果是 require 的结果是空对象 `{}`,原因是 b require 了 a,a 又去 require 了 b,此时 b 还没初始化好,所以只能拿到初始值 `{}`。当产生循环引用时一般有两种方法解决: 1. 通过分离共用的代码到另一个文件解决,如上面简单的情况,可拆出共用的代码到 c 中,如下: ``` c->a c->b ``` 2. 不在最外层 require,在用到的地方 require,通常在函数的内部 总的来说,循环依赖的陷阱并不大容易出现,但一旦出现了,对于新手来说还真不好定位。它的存在给我们提了个醒,要时刻注意你项目的依赖关系不要过于复杂,哪天你发现一个你明明已经 exports 了的方法报 `undefined is not a function`,我们就该提醒一下自己:哦,也许是它来了。 官方示例: [https://nodejs.org/api/modules.html#modules_cycles](https://nodejs.org/api/modules.html#modules_cycles) 上一节:[1.2 MongoDB 的安装与使用](https://github.com/nswbmw/N-blog/blob/master/book/1.2%20MongoDB%20%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8.md) 下一节:[2.2 exports 和 module.exports](https://github.com/nswbmw/N-blog/blob/master/book/2.2%20exports%20%E5%92%8C%20module.exports.md) ================================================ FILE: book/2.2 exports 和 module.exports.md ================================================ require 用来加载代码,而 exports 和 module.exports 则用来导出代码。 很多新手可能会迷惑于 exports 和 module.exports 的区别,为了更好的理解 exports 和 module.exports 的关系,我们先来巩固下 js 的基础。示例: **test.js** ```js var a = {name: 1} var b = a console.log(a) console.log(b) b.name = 2 console.log(a) console.log(b) var b = {name: 3} console.log(a) console.log(b) ``` 运行 test.js 结果为: ``` { name: 1 } { name: 1 } { name: 2 } { name: 2 } { name: 2 } { name: 3 } ``` **解释**:a 是一个对象,b 是对 a 的引用,即 a 和 b 指向同一块内存,所以前两个输出一样。当对 b 作修改时,即 a 和 b 指向同一块内存地址的内容发生了改变,所以 a 也会体现出来,所以第三四个输出一样。当 b 被覆盖时,b 指向了一块新的内存,a 还是指向原来的内存,所以最后两个输出不一样。 明白了上述例子后,我们只需知道三点就知道 exports 和 module.exports 的区别了: 1. module.exports 初始值为一个空对象 {} 2. exports 是指向的 module.exports 的引用 3. require() 返回的是 module.exports 而不是 exports Node.js 官方文档的截图证实了我们的观点:  #### exports = module.exports = {...} 我们经常看到这样的写法: ```js exports = module.exports = {...} ``` 上面的代码等价于: ```js module.exports = {...} exports = module.exports ``` 原理很简单:module.exports 指向新的对象时,exports 断开了与 module.exports 的引用,那么通过 exports = module.exports 让 exports 重新指向 module.exports。 > 小提示:ES6 的 import 和 export 不在本文的讲解范围,有兴趣的读者可以去学习阮一峰老师的[《ECMAScript6入门》](http://es6.ruanyifeng.com/)。 上一节:[2.1 require](https://github.com/nswbmw/N-blog/blob/master/book/2.1%20require.md) 下一节:[2.3 Promise](https://github.com/nswbmw/N-blog/blob/master/book/2.3%20Promise.md) ================================================ FILE: book/2.3 Promise.md ================================================ 网上已经有许多关于 Promise 的资料了,这里不在赘述。以下 4 个链接供读者学习: 1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (基础) 2. http://liubin.org/promises-book/ (开源 Promise 迷你书) 3. http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/ (进阶) 4. https://promisesaplus.com/ (官方定义规范) Promise 用于异步流程控制,生成器与 yield 也能实现流程控制(基于 co),但不在本教程讲解范围内,读者可参考我的另一部教程 [N-club](https://github.com/nswbmw/N-club)。async/await 结合 Promise 也可以实现流程控制,有兴趣请查阅 [《ECMAScript6入门》](http://es6.ruanyifeng.com/#docs/async#async函数)。 ### 深入 Promise - [Promise 必知必会(十道题)](https://zhuanlan.zhihu.com/p/30797777) - [深入 Promise(一)——Promise 实现详解](https://zhuanlan.zhihu.com/p/25178630) - [深入 Promise(二)——进击的 Promise](https://zhuanlan.zhihu.com/p/25198178) - [深入 Promise(三)——命名 Promise](https://zhuanlan.zhihu.com/p/25199781) 上一节:[2.2 exports 和 module.exports](https://github.com/nswbmw/N-blog/blob/master/book/2.2%20exports%20%E5%92%8C%20module.exports.md) 下一节:[2.4 环境变量](https://github.com/nswbmw/N-blog/blob/master/book/2.4%20%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F.md) ================================================ FILE: book/2.4 环境变量.md ================================================ 环境变量不属于 Node.js 的知识范畴,只不过我们在开发 Node.js 应用时经常与环境变量打交道,所以这里简单介绍下。 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。在 Mac 和 Linux 的终端直接输入 env,会列出当前的环境变量,如:USER=xxx。简单来讲,环境变量就是传递参数给运行程序的。 在 Node.js 中,我们经常这么用: ```sh NODE_ENV=test node app ``` 通过以上命令启动程序,指定当前环境变量 `NODE_ENV` 的值为 test,那么在 app.js 中可通过 `process.env` 来获取环境变量: ``` console.log(process.env.NODE_ENV) //test ``` 另一个常见的例子是使用 [debug](https://www.npmjs.com/package/debug) 模块时: ```sh DEBUG=* node app ``` Windows 用户需要首先设置环境变量,然后再执行程序: ```sh set DEBUG=* set NODE_ENV=test node app ``` 或者使用 [cross-env](https://www.npmjs.com/package/cross-env): ```sh npm i cross-env -g ``` 使用方式: ```sh cross-env NODE_ENV=test node app ``` 上一节:[2.3 Promise](https://github.com/nswbmw/N-blog/blob/master/book/2.3%20Promise.md) 下一节:[2.5 packge.json](https://github.com/nswbmw/N-blog/blob/master/book/2.5%20package.json.md) ================================================ FILE: book/2.5 package.json.md ================================================ package.json 对于 Node.js 应用来说是一个不可或缺的文件,它存储了该 Node.js 应用的名字、版本、描述、作者、入口文件、脚本、版权等等信息。npm 官网有 package.json 每个字段的详细介绍:[https://docs.npmjs.com/files/package.json](https://docs.npmjs.com/files/package.json)。 ## 2.5.1 semver 语义化版本(semver)即 dependencies、devDependencies 和 peerDependencies 里的如:`"co": "^4.6.0"`。 semver 格式:`主版本号.次版本号.修订号`。版本号递增规则如下: - `主版本号`:做了不兼容的 API 修改 - `次版本号`:做了向下兼容的功能性新增 - `修订号`:做了向下兼容的 bug 修正 更多阅读: 1. http://semver.org/lang/zh-CN/ 2. http://taobaofed.org/blog/2016/08/04/instructions-of-semver/ 作为 Node.js 的开发者,我们在发布 npm 模块的时候一定要遵守语义化版本的命名规则,即:有 breaking change 发大版本,有新增的功能发小版本,有小的 bug 修复或优化则发修订版本。 上一节:[2.4 环境变量](https://github.com/nswbmw/N-blog/blob/master/book/2.4%20%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F.md) 下一节:[2.6 npm 使用注意事项](https://github.com/nswbmw/N-blog/blob/master/book/2.6%20npm%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md) ================================================ FILE: book/2.6 npm 使用注意事项.md ================================================ ## 2.6.1 npm init 使用 `npm init` 初始化一个空项目是一个好的习惯,即使你对 package.json 及其他属性非常熟悉,`npm init` 也是你开始写新的 Node.js 应用或模块的一个快捷的办法。`npm init` 有智能的默认选项,比如从根目录名称推断模块名称,通过 `~/.npmrc` 读取你的信息,用你的 Git 设置来确定 repository 等等。 ## 2.6.2 npm install `npm install` 是我们最常用的 npm 命令之一,因此我们需要好好了解下这个命令。终端输入 `npm install -h` 查看使用方式:  可以看出:我们通过 `npm install` 可以安装 npm 上发布的某个版本、某个tag、某个版本区间的模块,甚至可以安装本地目录、压缩包和 git/github 的库作为依赖。 > 小提示: `npm i` 是 `npm install` 的简写,建议使用 `npm i`。 直接使用 `npm i` 安装的模块是不会写入 package.json 的 dependencies (或 devDependencies),需要额外加个参数: 1. `npm i express --save`/`npm i express -S` (安装 express,同时将 `"express": "^4.14.0"` 写入 dependencies ) 2. `npm i express --save-dev`/`npm i express -D` (安装 express,同时将 `"express": "^4.14.0"` 写入 devDependencies ) 3. `npm i express --save --save-exact` (安装 express,同时将 `"express": "4.14.0"` 写入 dependencies ) 第三种方式将固定版本号写入 dependencies,建议线上的 Node.js 应用都采取这种锁定版本号的方式,因为你不可能保证第三方模块下个小版本是没有验证 bug 的,即使是很流行的模块。拿 Mongoose 来说,Mongoose 4.1.4 引入了一个 bug 导致调用一个文档 entry 的 remove 会删除整个集合的文档,见:[https://github.com/Automattic/mongoose/blob/master/History.md#415--2015-09-01](https://github.com/Automattic/mongoose/blob/master/History.md#415--2015-09-01)。 > 后面会介绍更安全的 `npm shrinkwrap` 的用法。 运行以下命令: ```sh npm config set save-exact true ``` 这样每次 `npm i xxx --save` 的时候会锁定依赖的版本号,相当于加了 `--save-exact` 参数。 > 小提示:`npm config set` 命令将配置写到了 ~/.npmrc 文件,运行 `npm config list` 查看。 ## 2.6.3 npm scripts npm 提供了灵活而强大的 scripts 功能,见 [官方文档](https://docs.npmjs.com/misc/scripts)。 npm 的 scripts 有一些内置的缩写命令,如常用的: - `npm start` 等价于 `npm run start` - `npm test` 等价于 `npm run test` ## 2.6.4 npm shrinkwrap 前面说过要锁定依赖的版本,但这并不能完全防止意外情况的发生,因为锁定的只是最外一层的依赖,而里层依赖的模块的 package.json 有可能写的是 `"mongoose": "*"`。为了彻底锁定依赖的版本,让你的应用在任何机器上安装的都是同样版本的模块(不管嵌套多少层),通过运行 `npm shrinkwrap`,会在当前目录下产生一个 `npm-shrinkwrap.json`,里面包含了通过 node_modules 计算出的模块的依赖树及版本。上面的截图也显示:只要目录下有 npm-shrinkwrap.json 则运行 `npm install` 的时候会优先使用 npm-shrinkwrap.json 进行安装,没有则使用 package.json 进行安装。 更多阅读: 1. https://docs.npmjs.com/cli/shrinkwrap 2. http://tech.meituan.com/npm-shrinkwrap.html > 注意: 如果 node_modules 下存在某个模块(如直接通过 `npm install xxx` 安装的)而 package.json 中没有,运行 `npm shrinkwrap` 则会报错。另外,`npm shrinkwrap` 只会生成 dependencies 的依赖,不会生成 devDependencies 的。 上一节:[2.5 packge.json](https://github.com/nswbmw/N-blog/blob/master/book/2.5%20package.json.md) 下一节:[3.1 初始化一个 Express 项目](https://github.com/nswbmw/N-blog/blob/master/book/3.1%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%20Express%20%E9%A1%B9%E7%9B%AE.md) ================================================ FILE: book/3.1 初始化一个 Express 项目.md ================================================ 首先,我们新建一个目录 myblog,在该目录下运行 `npm init` 生成一个 package.json,如下所示:  > 注意:括号里的是默认值,如果使用默认值则直接回车即可,否则输入自定义内容后回车。 然后安装 express 并写入 package.json: ```sh npm i express@4.14.0 --save ``` 新建 index.js,添加如下代码: ```js const express = require('express') const app = express() app.get('/', function (req, res) { res.send('hello, express') }) app.listen(3000) ``` 以上代码的意思是:生成一个 express 实例 app,挂载了一个根路由控制器,然后监听 3000 端口并启动程序。运行 `node index`,打开浏览器访问 `localhost:3000` 时,页面应显示 hello, express。 这是最简单的一个使用 express 的例子,后面会介绍路由及模板的使用。 ## 3.1.1 supervisor 在开发过程中,每次修改代码保存后,我们都需要手动重启程序,才能查看改动的效果。使用 [supervisor](https://www.npmjs.com/package/supervisor) 可以解决这个繁琐的问题,全局安装 supervisor: ```sh npm i -g supervisor ``` 运行 `supervisor index` 启动程序,如下所示:  supervisor 会监听当前目录下 node 和 js 后缀的文件,当这些文件发生改动时,supervisor 会自动重启程序。 上一节:[2.6 npm 使用注意事项](https://github.com/nswbmw/N-blog/blob/master/book/2.6%20npm%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md) 下一节:[3.2 路由](https://github.com/nswbmw/N-blog/blob/master/book/3.2%20%E8%B7%AF%E7%94%B1.md) ================================================ FILE: book/3.2 路由.md ================================================ 前面我们只是挂载了根路径的路由控制器,现在修改 index.js 如下: ```js const express = require('express') const app = express() app.get('/', function (req, res) { res.send('hello, express') }) app.get('/users/:name', function (req, res) { res.send('hello, ' + req.params.name) }) app.listen(3000) ``` 以上代码的意思是:当访问根路径时,依然返回 hello, express,当访问如 `localhost:3000/users/nswbmw` 路径时,返回 hello, nswbmw。路径中 `:name` 起了占位符的作用,这个占位符的名字是 name,可以通过 `req.params.name` 取到实际的值。 > 小提示:express 使用了 [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) 模块实现的路由匹配。 不难看出:req 包含了请求来的相关信息,res 则用来返回该请求的响应,更多请查阅 [express 官方文档](http://expressjs.com/en/4x/api.html)。下面介绍几个常用的 req 的属性: - `req.query`: 解析后的 url 中的 querystring,如 `?name=haha`,req.query 的值为 `{name: 'haha'}` - `req.params`: 解析 url 中的占位符,如 `/:name`,访问 /haha,req.params 的值为 `{name: 'haha'}` - `req.body`: 解析后请求体,需使用相关的模块,如 [body-parser](https://www.npmjs.com/package/body-parser),请求体为 `{"name": "haha"}`,则 req.body 为 `{name: 'haha'}` ## 3.2.1 express.Router 上面只是很简单的路由使用的例子(将所有路由控制函数都放到了 index.js),但在实际开发中通常有几十甚至上百的路由,都写在 index.js 既臃肿又不好维护,这时可以使用 express.Router 实现更优雅的路由解决方案。在 myblog 目录下创建空文件夹 routes,在 routes 目录下创建 index.js 和 users.js。最后代码如下: **index.js** ```js const express = require('express') const app = express() const indexRouter = require('./routes/index') const userRouter = require('./routes/users') app.use('/', indexRouter) app.use('/users', userRouter) app.listen(3000) ``` **routes/index.js** ```js const express = require('express') const router = express.Router() router.get('/', function (req, res) { res.send('hello, express') }) module.exports = router ``` **routes/users.js** ```js const express = require('express') const router = express.Router() router.get('/:name', function (req, res) { res.send('hello, ' + req.params.name) }) module.exports = router ``` 以上代码的意思是:我们将 `/` 和 `/users/:name` 的路由分别放到了 routes/index.js 和 routes/users.js 中,每个路由文件通过生成一个 express.Router 实例 router 并导出,通过 `app.use` 挂载到不同的路径。这两种代码实现了相同的功能,但在实际开发中推荐使用 express.Router 将不同的路由分离到不同的路由文件中。 更多 express.Router 的用法见 [express 官方文档](http://expressjs.com/en/4x/api.html#router)。 上一节:[3.1 初始化一个 Express 项目](https://github.com/nswbmw/N-blog/blob/master/book/3.1%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%20Express%20%E9%A1%B9%E7%9B%AE.md) 下一节:[3.3 模板引擎](https://github.com/nswbmw/N-blog/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md) ================================================ FILE: book/3.3 模板引擎.md ================================================ 模板引擎(Template Engine)是一个将页面模板和数据结合起来生成 html 的工具。上例中,我们只是返回纯文本给浏览器,现在我们修改代码返回一个 html 页面给浏览器。 ## 3.3.1 ejs 模板引擎有很多,[ejs](https://www.npmjs.com/package/ejs) 是其中一种,因为它使用起来十分简单,而且与 express 集成良好,所以我们使用 ejs。安装 ejs: ```sh npm i ejs --save ``` 修改 index.js 如下: **index.js** ```js const path = require('path') const express = require('express') const app = express() const indexRouter = require('./routes/index') const userRouter = require('./routes/users') app.set('views', path.join(__dirname, 'views'))// 设置存放模板文件的目录 app.set('view engine', 'ejs')// 设置模板引擎为 ejs app.use('/', indexRouter) app.use('/users', userRouter) app.listen(3000) ``` 通过 `app.set` 设置模板引擎为 ejs 和存放模板的目录。在 myblog 下新建 views 文件夹,在 views 下新建 users.ejs,添加如下代码: **views/users.ejs** ```html
hello, <%= name %>
``` 修改 routes/users.js 如下: **routes/users.js** ```js const express = require('express') const router = express.Router() router.get('/:name', function (req, res) { res.render('users', { name: req.params.name }) }) module.exports = router ``` 通过调用 `res.render` 函数渲染 ejs 模板,res.render 第一个参数是模板的名字,这里是 users 则会匹配 views/users.ejs,第二个参数是传给模板的数据,这里传入 name,则在 ejs 模板中可使用 name。`res.render` 的作用就是将模板和数据结合生成 html,同时设置响应头中的 `Content-Type: text/html`,告诉浏览器我返回的是 html,不是纯文本,要按 html 展示。现在我们访问 `localhost:3000/users/haha`,如下图所示:  上面代码可以看到,我们在模板 `<%= name.toUpperCase() %>` 中使用了 JavaScript 的语法 `.toUpperCase()` 将名字转化为大写,那这个 `<%= xxx %>` 是什么东西呢?ejs 有 3 种常用标签: 1. `<% code %>`:运行 JavaScript 代码,不输出 2. `<%= code %>`:显示转义后的 HTML内容 3. `<%- code %>`:显示原始 HTML 内容 > 注意:`<%= code %>` 和 `<%- code %>` 都可以是 JavaScript 表达式生成的字符串,当变量 code 为普通字符串时,两者没有区别。当 code 比如为 `hello, <%= name %>
<%- include('footer') %> ``` 我们将原来的 users.ejs 拆成出了 header.ejs 和 footer.ejs,并在 users.ejs 通过 ejs 内置的 include 方法引入,从而实现了跟以前一个模板文件相同的功能。 > 小提示:拆分模板组件通常有两个好处: > > 1. 模板可复用,减少重复代码 > 2. 主模板结构清晰 > 注意:要用 `<%- include('header') %>` 而不是 `<%= include('header') %>` 上一节:[3.2 路由](https://github.com/nswbmw/N-blog/blob/master/book/3.2%20%E8%B7%AF%E7%94%B1.md) 下一节:[3.4 Express 浅析](https://github.com/nswbmw/N-blog/blob/master/book/3.4%20Express%20%E6%B5%85%E6%9E%90.md) ================================================ FILE: book/3.4 Express 浅析.md ================================================ 前面我们讲解了 express 中路由和模板引擎 ejs 的用法,但 express 的精髓并不在此,在于中间件的设计理念。 ## 3.4.1 中间件与 next express 中的中间件(middleware)就是用来处理请求的,当一个中间件处理完,可以通过调用 `next()` 传递给下一个中间件,如果没有调用 `next()`,则请求不会往下传递,如内置的 `res.render` 其实就是渲染完 html 直接返回给客户端,没有调用 `next()`,从而没有传递给下一个中间件。看个小例子,修改 index.js 如下: **index.js** ```js const express = require('express') const app = express() app.use(function (req, res, next) { console.log('1') next() }) app.use(function (req, res, next) { console.log('2') res.status(200).end() }) app.listen(3000) ``` 此时访问 `localhost:3000`,终端会输出: ``` 1 2 ``` 通过 `app.use` 加载中间件,在中间件中通过 next 将请求传递到下一个中间件,next 可接受一个参数接收错误信息,如果使用了 `next(error)`,则会返回错误而不会传递到下一个中间件,修改 index.js 如下: **index.js** ```js const express = require('express') const app = express() app.use(function (req, res, next) { console.log('1') next(new Error('haha')) }) app.use(function (req, res, next) { console.log('2') res.status(200).end() }) app.listen(3000) ``` 此时访问 `localhost:3000`,终端会输出错误信息:  浏览器会显示:  > 小提示:`app.use` 有非常灵活的使用方式,详情见 [官方文档](http://expressjs.com/en/4x/api.html#app.use)。 express 有成百上千的第三方中间件,在开发过程中我们首先应该去 npm 上寻找是否有类似实现的中间件,尽量避免造轮子,节省开发时间。下面给出几个常用的搜索 npm 模块的网站: 1. [http://npmjs.com](http://npmjs.com)(npm 官网) 2. [http://node-modules.com](http://node-modules.com) 3. [https://npms.io](https://npms.io) 4. [https://nodejsmodules.org](https://nodejsmodules.org) > 小提示:express@4 之前的版本基于 connect 这个模块实现的中间件的架构,express@4 及以上的版本则移除了对 connect 的依赖自己实现了,理论上基于 connect 的中间件(通常以 `connect-` 开头,如 `connect-mongo`)仍可结合 express 使用。 > 注意:中间件的加载顺序很重要!比如:通常把日志中间件放到比较靠前的位置,后面将会介绍的 `connect-flash` 中间件是基于 session 的,所以需要在 `express-session` 后加载。 ## 3.4.2 错误处理 上面的例子中,应用程序为我们自动返回了错误栈信息(express 内置了一个默认的错误处理器),假如我们想手动控制返回的错误内容,则需要加载一个自定义错误处理的中间件,修改 index.js 如下: **index.js** ```js const express = require('express') const app = express() app.use(function (req, res, next) { console.log('1') next(new Error('haha')) }) app.use(function (req, res, next) { console.log('2') res.status(200).end() }) //错误处理 app.use(function (err, req, res, next) { console.error(err.stack) res.status(500).send('Something broke!') }) app.listen(3000) ``` 此时访问 `localhost:3000`,浏览器会显示 `Something broke!`。 > 小提示:关于 express 的错误处理,详情见 [官方文档](http://expressjs.com/en/guide/error-handling.html)。 上一节:[3.3 模板引擎](https://github.com/nswbmw/N-blog/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md) 下一节:[4.1 开发环境](https://github.com/nswbmw/N-blog/blob/master/book/4.1%20%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83.md) ================================================ FILE: book/4.1 开发环境.md ================================================ 从本章开始,正式学习如何使用 Express + MongoDB 搭建一个博客。 #### Node.js: `8.9.1` #### MongoDB: `3.4.10` #### Express: `4.16.2` 上一节:[3.4 Express 浅析](https://github.com/nswbmw/N-blog/blob/master/book/3.4%20Express%20%E6%B5%85%E6%9E%90.md) 下一节:[4.2 准备工作](https://github.com/nswbmw/N-blog/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md) ================================================ FILE: book/4.10 留言.md ================================================ ## 4.10.1 留言模型设计 我们只需要留言的作者 id、留言内容和关联的文章 id 这几个字段,修改 lib/mongo.js,添加如下代码: **lib/mongo.js** ```js exports.Comment = mongolass.model('Comment', { author: { type: Mongolass.Types.ObjectId, required: true }, content: { type: 'string', required: true }, postId: { type: Mongolass.Types.ObjectId, required: true } }) exports.Comment.index({ postId: 1, _id: 1 }).exec()// 通过文章 id 获取该文章下所有留言,按留言创建时间升序 ``` ## 4.10.2 显示留言 在实现留言功能之前,我们先让文章页可以显示留言列表。首先创建留言的模板,新建 views/components/comments.ejs,添加如下代码: **views/components/comments.ejs** ```ejs
留言
<% comments.forEach(function (comment) { %>