├── book ├── img │ ├── 1.1.1.png │ ├── 1.1.2.png │ ├── 1.1.3.png │ ├── 1.1.4.png │ ├── 1.1.5.png │ ├── 1.2.1.png │ ├── 1.2.2.png │ ├── 1.2.3.png │ ├── 1.2.4.png │ ├── 1.2.5.png │ ├── 2.2.1.png │ ├── 2.6.1.png │ ├── 3.1.1.png │ ├── 3.1.2.png │ ├── 3.3.1.png │ ├── 3.4.1.png │ ├── 3.4.2.png │ ├── 4.2.1.png │ ├── 4.2.2.png │ ├── 4.5.1.jpg │ ├── 4.5.1.png │ ├── 4.5.2.png │ ├── 4.5.3.png │ ├── 4.5.4.png │ ├── 4.5.5.png │ ├── 4.5.6.png │ ├── 4.5.7.png │ ├── 4.5.8.png │ ├── 4.5.9.png │ ├── 4.7.1.png │ ├── 4.8.1.png │ ├── 4.8.2.png │ ├── 4.8.3.png │ ├── 4.14.1.png │ ├── 4.14.2.png │ ├── 4.14.3.png │ ├── 4.15.1.png │ ├── 4.15.2.png │ ├── 4.15.3.png │ ├── 4.5.10.png │ ├── 4.5.11.png │ ├── 4.5.12.png │ └── 4.5.13.png ├── 4.1 开发环境.md ├── 4.5 页面设计.md ├── 2.5 package.json.md ├── 2.3 Promise.md ├── 2.4 环境变量.md ├── 4.2 准备工作.md ├── 3.1 初始化一个 Express 项目.md ├── 4.3 配置文件.md ├── 1.2 MongoDB 的安装与使用.md ├── 2.2 exports 和 module.exports.md ├── 2.1 require.md ├── 1.1 Node.js 的安装与使用.md ├── 4.6 连接数据库.md ├── 2.6 npm 使用注意事项.md ├── 3.2 路由.md ├── 4.11 日志.md ├── 3.4 Express 浅析.md ├── 3.3 模板引擎.md ├── 4.8 登出与登录.md ├── 4.13 部署.md ├── 4.12 测试.md ├── 4.4 功能设计.md ├── 4.7 注册.md └── 4.9 添加商品.md ├── public └── images │ ├── xmsz-1.jpg │ ├── xmsz-3.jpg │ ├── xmsz-4.jpg │ └── xmsz-5.jpg ├── views ├── error.ejs ├── addcommodity.html ├── home.html ├── login.html ├── register.html └── cart.html ├── coverage ├── lcov-report │ ├── sort-arrow-sprite.png │ ├── prettify.css │ ├── mysession │ │ ├── routes │ │ │ ├── index.js.html │ │ │ ├── logout.js.html │ │ │ ├── login.js.html │ │ │ ├── index.html │ │ │ ├── home.js.html │ │ │ └── register.js.html │ │ ├── config │ │ │ ├── default.js.html │ │ │ ├── multerUtil.js.html │ │ │ └── index.html │ │ ├── index.html │ │ ├── middlewares │ │ │ ├── testController.js.html │ │ │ ├── index.html │ │ │ └── check.js.html │ │ └── common │ │ │ ├── dbHelper.js.html │ │ │ ├── models.js.html │ │ │ └── index.html │ ├── sorter.js │ ├── base.css │ └── index.html └── lcov.info ├── routes ├── index.js ├── logout.js ├── login.js ├── home.js ├── register.js └── cart.js ├── config ├── default.js └── multerUtil.js ├── common ├── dbHelper.js └── models.js ├── middlewares ├── testController.js └── check.js ├── package.json ├── app.js └── test └── register.js /book/img/1.1.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.1.1.png -------------------------------------------------------------------------------- /book/img/1.1.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.1.2.png -------------------------------------------------------------------------------- /book/img/1.1.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.1.3.png -------------------------------------------------------------------------------- /book/img/1.1.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.1.4.png -------------------------------------------------------------------------------- /book/img/1.1.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.1.5.png -------------------------------------------------------------------------------- /book/img/1.2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.2.1.png -------------------------------------------------------------------------------- /book/img/1.2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.2.2.png -------------------------------------------------------------------------------- /book/img/1.2.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.2.3.png -------------------------------------------------------------------------------- /book/img/1.2.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.2.4.png -------------------------------------------------------------------------------- /book/img/1.2.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/1.2.5.png -------------------------------------------------------------------------------- /book/img/2.2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/2.2.1.png -------------------------------------------------------------------------------- /book/img/2.6.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/2.6.1.png -------------------------------------------------------------------------------- /book/img/3.1.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/3.1.1.png -------------------------------------------------------------------------------- /book/img/3.1.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/3.1.2.png -------------------------------------------------------------------------------- /book/img/3.3.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/3.3.1.png -------------------------------------------------------------------------------- /book/img/3.4.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/3.4.1.png -------------------------------------------------------------------------------- /book/img/3.4.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/3.4.2.png -------------------------------------------------------------------------------- /book/img/4.2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.2.1.png -------------------------------------------------------------------------------- /book/img/4.2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.2.2.png -------------------------------------------------------------------------------- /book/img/4.5.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.1.jpg -------------------------------------------------------------------------------- /book/img/4.5.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.1.png -------------------------------------------------------------------------------- /book/img/4.5.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.2.png -------------------------------------------------------------------------------- /book/img/4.5.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.3.png -------------------------------------------------------------------------------- /book/img/4.5.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.4.png -------------------------------------------------------------------------------- /book/img/4.5.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.5.png -------------------------------------------------------------------------------- /book/img/4.5.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.6.png -------------------------------------------------------------------------------- /book/img/4.5.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.7.png -------------------------------------------------------------------------------- /book/img/4.5.8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.8.png -------------------------------------------------------------------------------- /book/img/4.5.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.9.png -------------------------------------------------------------------------------- /book/img/4.7.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.7.1.png -------------------------------------------------------------------------------- /book/img/4.8.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.8.1.png -------------------------------------------------------------------------------- /book/img/4.8.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.8.2.png -------------------------------------------------------------------------------- /book/img/4.8.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.8.3.png -------------------------------------------------------------------------------- /book/img/4.14.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.14.1.png -------------------------------------------------------------------------------- /book/img/4.14.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.14.2.png -------------------------------------------------------------------------------- /book/img/4.14.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.14.3.png -------------------------------------------------------------------------------- /book/img/4.15.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.15.1.png -------------------------------------------------------------------------------- /book/img/4.15.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.15.2.png -------------------------------------------------------------------------------- /book/img/4.15.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.15.3.png -------------------------------------------------------------------------------- /book/img/4.5.10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.10.png -------------------------------------------------------------------------------- /book/img/4.5.11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.11.png -------------------------------------------------------------------------------- /book/img/4.5.12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.12.png -------------------------------------------------------------------------------- /book/img/4.5.13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/book/img/4.5.13.png -------------------------------------------------------------------------------- /public/images/xmsz-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/public/images/xmsz-1.jpg -------------------------------------------------------------------------------- /public/images/xmsz-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/public/images/xmsz-3.jpg -------------------------------------------------------------------------------- /public/images/xmsz-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/public/images/xmsz-4.jpg -------------------------------------------------------------------------------- /public/images/xmsz-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/public/images/xmsz-5.jpg -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18820227745/shop-demo-node/HEAD/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app){ 2 | require('./login')(app); 3 | require('./home')(app); 4 | require('./logout')(app); 5 | require('./register')(app); 6 | require('./cart')(app); 7 | } -------------------------------------------------------------------------------- /config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 3000, 3 | session: { 4 | secret: 'e-commerce', 5 | key: 'e-commerce', 6 | maxAge: 1000 * 60 * 30 7 | }, 8 | mongodb: 'mongodb://localhost:27017/e-commerce' 9 | }; 10 | -------------------------------------------------------------------------------- /routes/logout.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( app ) { 2 | //退出登录 3 | app.get('/logout', function(req, res){ 4 | req.session.user = null; 5 | req.session.error = null; 6 | res.redirect('/home'); 7 | }); 8 | } -------------------------------------------------------------------------------- /book/4.1 开发环境.md: -------------------------------------------------------------------------------- 1 | 从本章开始,正式学习如何使用 Express + MongoDB 搭建一个简单的小电商网站。 2 | 3 | ### Node.js: `6.11.0` 4 | 5 | ### MongoDB: `3.4.2` 6 | 7 | ### Express: `3.4.8` 8 | 9 | 上一节:[3.4 Express 浅析](https://github.com/18820227745/shop-demo-node/blob/master/book/3.4%20Express%20%E6%B5%85%E6%9E%90.md) 10 | 11 | 下一节:[4.2 准备工作](https://github.com/18820227745/shop-demo-node/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md) 12 | -------------------------------------------------------------------------------- /common/dbHelper.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'), 2 | Schema = mongoose.Schema, 3 | models = require('./models'); 4 | 5 | for (var m in models) { 6 | mongoose.model(m, new Schema(models[m])); 7 | } 8 | module.exports = { 9 | getModel: function (type) { 10 | return _getModel(type); 11 | } 12 | }; 13 | 14 | var _getModel = function (type) { 15 | return mongoose.model(type); 16 | }; 17 | 18 | 19 | -------------------------------------------------------------------------------- /middlewares/testController.js: -------------------------------------------------------------------------------- 1 | var muilter = require('../config/multerUtil'); 2 | //multer有single()中的名称必须是表单上传字段的name名称。 3 | var upload = muilter.single('imgSrc'); 4 | exports.dataInput = function (req, res) { 5 | upload(req, res, function (err) { 6 | //添加错误处理 7 | if (err) { 8 | return console.log(err); 9 | } 10 | //文件信息在req.file或者req.files中显示。 11 | console.log(req); 12 | }); 13 | } -------------------------------------------------------------------------------- /middlewares/check.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | checkLogin: function checkLogin(req, res, next) { 3 | if (!req.session.user) { 4 | req.session.error = "请先登录"; 5 | res.redirect("/login"); 6 | } 7 | next(); 8 | }, 9 | 10 | checkNotLogin: function checkNotLogin(req, res, next) { 11 | if (req.session.user) { 12 | req.session.error = "已登录"; 13 | return res.redirect('back');//返回之前的页面 14 | } 15 | next(); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /common/models.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | user:{ 3 | name: {type: String}, 4 | password: {type: String} 5 | }, 6 | commodity: { 7 | name: String, 8 | price: Number, 9 | imgSrc: String 10 | }, 11 | cart: { 12 | uId: {type: String}, 13 | cId: {type: String}, 14 | cName: { type: String}, 15 | cPrice: { type: String}, 16 | cImaSrc: { type: String}, 17 | cQuantity: { type: Number}, 18 | cStatus: { type: Boolean,default: false} 19 | } 20 | } -------------------------------------------------------------------------------- /config/multerUtil.js: -------------------------------------------------------------------------------- 1 | var multer = require('multer'); 2 | var storage = multer.diskStorage({ 3 | //设置上传后文件路径,uploads文件夹会自动创建。 4 | destination: function (req, file, cb) { 5 | cb(null, '../public/images') 6 | }, 7 | //给上传文件重命名,获取添加后缀名 8 | filename: function (req, file, cb) { 9 | var fileFormat = (file.originalname).split("."); 10 | cb(null, file.fieldname + '-' + Date.now() + "." + fileFormat[fileFormat.length - 1]); 11 | } 12 | }); 13 | //添加配置文件到muler对象。 14 | var upload = multer({ 15 | storage: storage 16 | }); 17 | 18 | module.exports = upload; -------------------------------------------------------------------------------- /book/4.5 页面设计.md: -------------------------------------------------------------------------------- 1 | 我们使用 jQuery + Semantic-UI 实现前端页面的设计,最终效果图如下: 2 | 3 | **注册页** 4 | 5 | ![](./img/4.5.1.png) 6 | 7 | **登录页** 8 | 9 | ![](./img/4.5.2.png) 10 | 11 | **未登录时的主页(或用户页)** 12 | 13 | ![](./img/4.5.3.png) 14 | 15 | **登录后的主页(或用户页)** 16 | 17 | ![](./img/4.5.4.png) 18 | 19 | **添加商品页** 20 | 21 | ![](./img/4.5.5.png) 22 | 23 | **购物车页** 24 | 25 | ![](./img/4.5.6.png) 26 | 27 | 28 | 29 | 30 | 上一节:[4.4 功能设计](https://github.com/18820227745/shop-demo-node/blob/master/book/4.4%20%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1.md) 31 | 32 | 下一节:[4.6 连接数据库](https://github.com/18820227745/shop-demo-node/blob/master/book/4.6%20%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93.md) 33 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e-commerce", 3 | "version": "0.0.2", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js", 7 | "test": "node --harmony ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha" 8 | }, 9 | "dependencies": { 10 | "body-parser": "~1.10.0", 11 | "config-lite": "^2.0.0", 12 | "connect-multiparty": "^2.0.0", 13 | "ejs": "~0.8.5", 14 | "express": "~3.4.8", 15 | "express-formidable": "^1.0.0", 16 | "express-session": "~1.9.3", 17 | "express-winston": "^2.4.0", 18 | "mongoose": "~3.8.1", 19 | "multer": "^1.3.0", 20 | "path": "~0.4.9", 21 | "sha1": "^1.1.1", 22 | "winston": "^2.3.1" 23 | }, 24 | "devDependencies": { 25 | "istanbul": "^0.4.5", 26 | "mocha": "^3.4.2", 27 | "supertest": "^3.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /routes/login.js: -------------------------------------------------------------------------------- 1 | var sha1 = require('sha1'); 2 | module.exports = function(app){ 3 | //登录页面 4 | app.get('/login',function(req,res){ 5 | res.render('login'); 6 | }); 7 | //登录 8 | app.post('/login',function(req,res){ 9 | var User = global.dbHelper.getModel('user'), 10 | uname = req.body.uname; 11 | //查询数据库 12 | User.findOne({name: uname}, function(error,doc){ 13 | if(error){ 14 | res.send(500); 15 | console.log(error); 16 | }else if(!doc){ 17 | req.session.error = "用户名不存在!"; 18 | res.send(404); 19 | }else{ 20 | if(req.body.upwd !== doc.password ){ 21 | req.session.error = "密码错误!"; 22 | res.send(404); 23 | }else{ 24 | req.session.user = doc; 25 | res.send(200); 26 | } 27 | } 28 | }); 29 | }); 30 | } -------------------------------------------------------------------------------- /book/2.5 package.json.md: -------------------------------------------------------------------------------- 1 | package.json 对于 Node.js 应用来说是一个不可或缺的文件,它存储了该 Node.js 应用的名字、版本、描述、作者、入口文件、脚本、版权等等信息。npm 官网有 package.json 每个字段的详细介绍:[https://docs.npmjs.com/files/package.json](https://docs.npmjs.com/files/package.json)。 2 | 3 | ## 2.5.1 semver 4 | 5 | 语义化版本(semver)即 dependencies、devDependencies 和 peerDependencies 里的如:`"co": "^4.6.0"`。 6 | 7 | semver 格式:`主版本号.次版本号.修订号`。版本号递增规则如下: 8 | 9 | - `主版本号`:做了不兼容的 API 修改 10 | - `次版本号`:做了向下兼容的功能性新增 11 | - `修订号`:做了向下兼容的 bug 修正 12 | 13 | 更多阅读: 14 | 15 | 1. http://semver.org/lang/zh-CN/ 16 | 2. http://taobaofed.org/blog/2016/08/04/instructions-of-semver/ 17 | 18 | 作为 Node.js 的开发者,我们在发布 npm 模块的时候一定要遵守语义化版本的命名规则,即:有 breaking change 发大版本,有新增的功能发小版本,有小的 bug 修复或优化则发修订版本。 19 | 20 | 上一节:[2.4 环境变量](https://github.com/18820227745/shop-demo-node/blob/master/book/2.4%20%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F.md) 21 | 22 | 下一节:[2.6 npm 使用注意事项](https://github.com/18820227745/shop-demo-node/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) -------------------------------------------------------------------------------- /book/2.3 Promise.md: -------------------------------------------------------------------------------- 1 | 网上已经有许多关于 Promise 的资料了,这里不在赘述。以下 4 个链接供读者学习: 2 | 3 | 1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (基础) 4 | 2. http://liubin.org/promises-book/ (开源 Promise 迷你书) 5 | 3. http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/ (进阶) 6 | 4. https://promisesaplus.com/ (官方定义规范) 7 | 8 | Promise 用于异步流程控制,生成器与 yield 也能实现流程控制(基于 co),但不在本教程讲解范围内,读者可参考我的另一部教程 [N-club](https://github.com/nswbmw/N-club)。async/await 结合 Promise 也可以实现流程控制,有兴趣请查阅 [《ECMAScript6入门》](http://es6.ruanyifeng.com/#docs/async#async函数)。 9 | 10 | ### 深入 Promise 11 | 12 | - [深入 Promise(一)——Promise 实现详解](https://zhuanlan.zhihu.com/p/25178630) 13 | - [深入 Promise(二)——进击的 Promise](https://zhuanlan.zhihu.com/p/25198178) 14 | - [深入 Promise(三)——命名 Promise](https://zhuanlan.zhihu.com/p/25199781) 15 | 16 | 上一节:[2.2 exports 和 module.exports](https://github.com/nswbmw/N-blog/blob/master/book/2.2%20exports%20%E5%92%8C%20module.exports.md) 17 | 18 | 下一节:[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) 19 | -------------------------------------------------------------------------------- /book/2.4 环境变量.md: -------------------------------------------------------------------------------- 1 | 环境变量不属于 Node.js 的知识范畴,只不过我们在开发 Node.js 应用时经常与环境变量打交道,所以这里简单介绍下。 2 | 3 | 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。在 Mac 和 Linux 的终端直接输入 env,会列出当前的环境变量,如:USER=xxx。简单来讲,环境变量就是传递参数给运行程序的。 4 | 5 | 在 Node.js 中,我们经常这么用: 6 | 7 | ``` 8 | NODE_ENV=test node app 9 | ``` 10 | 11 | 通过以上命令启动程序,指定当前环境变量 `NODE_ENV` 的值为 test,那么在 app.js 中可通过 `process.env` 来获取环境变量: 12 | 13 | ``` 14 | console.log(process.env.NODE_ENV) //test 15 | ``` 16 | 17 | 另一个常见的例子是使用 [debug](https://www.npmjs.com/package/debug) 模块时: 18 | 19 | ``` 20 | DEBUG=* node app 21 | ``` 22 | 23 | Windows 用户需要首先设置环境变量,然后再执行程序: 24 | 25 | ``` 26 | set DEBUG=* 27 | set NODE_ENV=test 28 | node app 29 | ``` 30 | 31 | 或者使用 [cross-env](https://www.npmjs.com/package/cross-env): 32 | 33 | ``` 34 | npm i cross-env -g 35 | ``` 36 | 37 | 使用方式: 38 | 39 | ``` 40 | cross-env NODE_ENV=test node app 41 | ``` 42 | 43 | 上一节:[2.3 Promise](https://github.com/18820227745/shop-demo-node/blob/master/book/2.3%20Promise.md) 44 | 45 | 下一节:[2.5 packge.json](https://github.com/18820227745/shop-demo-node/blob/master/book/2.5%20package.json.md) -------------------------------------------------------------------------------- /book/4.2 准备工作.md: -------------------------------------------------------------------------------- 1 | ## 4.2.1 目录结构 2 | 3 | 我们停止 supervisor 并删除 e-commerce 目录从头来过。重新创建 e-commerce,运行 `npm init`,如下: 4 | 5 | ![](./img/4.2.1.png) 6 | 7 | 在 myblog 目录下创建以下目录及空文件(package.json 除外): 8 | 9 | ![](./img/4.2.2.png) 10 | 11 | 对应文件及文件夹的用处: 12 | 13 | 1. `book`: 项目的说明文档 14 | 2. `common`: 数据库模板,操作数据库接口等 15 | 2. `public`: 存放静态文件,如样式、图片等 16 | 3. `routes`: 存放路由文件 17 | 4. `views`: 存放模板文件 18 | 5. `app.js`: 程序主文件 19 | 6. `package.json`: 存储项目名、描述、作者、依赖等等信息 20 | 21 | 22 | > 小提示:不知读者发现了没有,我们遵循了 MVC(模型(model)-视图(view)-控制器(controller/route)) 的开发模式。 23 | 24 | ## 4.2.2 安装依赖模块 25 | 26 | 运行以下命令安装所需模块: 27 | 28 | ``` 29 | npm i express express-session body-parser multer mongoose config-lite ejs sha1 --save 30 | ``` 31 | 32 | 对应模块的用处: 33 | 34 | 1. `express`: web 框架 35 | 2. `express-session`: session 中间件 36 | 3. `body-parser`: 解析客户端请求的body 37 | 4. `multer`: 文件上传中间件 38 | 5. `mongoose`: mongdb数据库 39 | 6. `ejs`: 模板 40 | 7. `config-lite`: 读取配置文件 41 | 8. `sha1`: 加密模块 42 | 43 | 44 | 后面会详细讲解这些模块的用处。 45 | 46 | 上一节:[4.1 开发环境](https://github.com/18820227745/shop-demo-node/blob/master/book/4.1%20%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83.md) 47 | 48 | 下一节:[4.3 配置文件](https://github.com/18820227745/shop-demo-node/blob/master/book/4.3%20%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6.md) 49 | -------------------------------------------------------------------------------- /book/3.1 初始化一个 Express 项目.md: -------------------------------------------------------------------------------- 1 | 首先,我们新建一个目录 myblog,在该目录下运行 `npm init` 生成一个 package.json,如下所示: 2 | 3 | ![](./img/3.1.1.png) 4 | 5 | > 注意:括号里的是默认值,如果使用默认值则直接回车即可,否则输入自定义内容后回车。 6 | 7 | 然后安装 express 并写入 package.json: 8 | 9 | ``` 10 | npm i express@4.14.0 --save 11 | ``` 12 | 13 | 新建 index.js,添加如下代码: 14 | 15 | ``` 16 | var express = require('express'); 17 | var app = express(); 18 | 19 | app.get('/', function(req, res) { 20 | res.send('hello, express'); 21 | }); 22 | 23 | app.listen(3000); 24 | ``` 25 | 26 | 以上代码的意思是:生成一个 express 实例 app,挂载了一个根路由控制器,然后监听 3000 端口并启动程序。运行 `node index`,打开浏览器访问 `localhost:3000` 时,页面应显示 hello, express。 27 | 28 | 这是最简单的一个使用 express 的例子,后面会介绍路由及模板的使用。 29 | 30 | ## 3.1.1 supervisor 31 | 32 | 在开发过程中,每次修改代码保存后,我们都需要手动重启程序,才能查看改动的效果。使用 [supervisor](https://www.npmjs.com/package/supervisor) 可以解决这个繁琐的问题,全局安装 supervisor: 33 | 34 | ``` 35 | npm install -g supervisor 36 | ``` 37 | 38 | 运行 `supervisor --harmony index` 启动程序,如下所示: 39 | 40 | ![](./img/3.1.2.png) 41 | 42 | supervisor 会监听当前目录下 node 和 js 后缀的文件,当这些文件发生改动时,supervisor 会自动重启程序。 43 | 44 | 上一节:[2.6 npm 使用注意事项](https://github.com/18820227745/shop-demo-node/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) 45 | 46 | 下一节:[3.2 路由](https://github.com/18820227745/shop-demo-node/blob/master/book/3.2%20%E8%B7%AF%E7%94%B1.md) -------------------------------------------------------------------------------- /routes/home.js: -------------------------------------------------------------------------------- 1 | var testController=require('../middlewares/testController'); 2 | //将检查登录封装成中间件 3 | var multipart = require('connect-multiparty'); 4 | var multipartMiddleware = multipart({ uploadDir: '../public/' }); 5 | var checkLogin = require('../middlewares/check').checkLogin; 6 | module.exports = function (app){ 7 | //home页面为登录时可以直接浏览 8 | app.get('/home',function(req,res){ 9 | var Commodity = global.dbHelper.getModel("commodity"); 10 | var login = { 11 | href:'/login', 12 | message: '登 录' 13 | }; 14 | if (req.session.user) { 15 | login.href='/logout'; 16 | login.message='退 出'; 17 | } 18 | Commodity.find({},function(error,docs){ 19 | res.render('home',{Commoditys:docs,login:login}); 20 | }); 21 | }); 22 | //添加商品页 23 | app.get('/addcommodity',checkLogin,function(req,res){ 24 | //render可以带变量,渲染页面 25 | res.render("addcommodity"); 26 | }); 27 | //添加商品 28 | app.post('/addcommodity',checkLogin,multipartMiddleware, function(req,res){ 29 | var Commodity = global.dbHelper.getModel("commodity"); 30 | Commodity.create({ 31 | name: req.body.name, 32 | price: req.body.price, 33 | price: req.body.price, 34 | imgSrc: req.body.imgSrc 35 | },function(error,doc){ 36 | if(doc){ 37 | res.send(200); 38 | }else{ 39 | res.send(404); 40 | } 41 | }) 42 | }); 43 | } -------------------------------------------------------------------------------- /book/4.3 配置文件.md: -------------------------------------------------------------------------------- 1 | 不管是小项目还是大项目,将配置与代码分离是一个非常好的做法。我们通常将配置写到一个配置文件里,如 config.js 或 config.json ,并放到项目的根目录下。但通常我们都会有许多环境,如本地开发环境、测试环境和线上环境等,不同的环境的配置不同,我们不可能每次部署时都要去修改引用 config.test.js 或者 config.production.js。config-lite 模块正是你需要的。 2 | 3 | ## 4.3.1 config-lite 4 | 5 | [config-lite](https://www.npmjs.com/package/config-lite) 是一个轻量的读取配置文件的模块。config-lite 会根据环境变量(`NODE_ENV`)的不同从当前执行进程目录下的 config 目录加载不同的配置文件。如果不设置 `NODE_ENV`,则读取默认的 default 配置文件,如果设置了 `NODE_ENV`,则会合并指定的配置文件和 default 配置文件作为配置,config-lite 支持 .js、.json、.node、.yml、.yaml 后缀的文件。 6 | 7 | 如果程序以 `NODE_ENV=test node app` 启动,则 config-lite 会依次降级查找 `config/test.js`、`config/test.json`、`config/test.node`、`config/test.yml`、`config/test.yaml` 并合并 default 配置; 如果程序以 `NODE_ENV=production node app` 启动,则 config-lite 会依次降级查找 `config/production.js`、`config/production.json`、`config/production.node`、`config/production.yml`、`config/production.yaml` 并合并 default 配置。 8 | 9 | 在 myblog 下新建 config 目录,在该目录下新建 default.js,添加如下代码: 10 | 11 | **config/default.js** 12 | 13 | ``` 14 | module.exports = { 15 | port: 3000, 16 | session: { 17 | secret: 'e-commerce', 18 | key: 'e-commerce', 19 | maxAge: 1000 * 60 * 30 20 | }, 21 | mongodb: 'mongodb://localhost:27017/e-commerce' 22 | }; 23 | ``` 24 | 25 | 配置释义: 26 | 27 | 1. `port`: 程序启动要监听的端口号 28 | 2. `session`: express-session 的配置信息,后面介绍 29 | 3. `mongodb`: mongodb 的地址,`e-commerce` 为 db 名 30 | 31 | 上一节:[4.2 准备工作](https://github.com/18820227745/shop-demo-node/blob/master/book/4.2%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.md) 32 | 33 | 下一节:[4.4 功能设计](https://github.com/18820227745/shop-demo-node/blob/master/book/4.4%20%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1.md) -------------------------------------------------------------------------------- /book/1.2 MongoDB 的安装与使用.md: -------------------------------------------------------------------------------- 1 | ## 1.2.1 安装与启动 MongoDB 2 | 3 | - Windows 用户向导:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/ 4 | - Linux 用户向导:https://docs.mongodb.com/manual/administration/install-on-linux/ 5 | - Mac 用户向导:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/ 6 | 7 | ## 1.2.2 Robomongo 和 Mongochef 8 | 9 | #### Robomongo 10 | 11 | [Robomongo](https://robomongo.org/) 是一个基于 Shell 的跨平台开源 MongoDB 可视化管理工具,支持 Windows、Linux 和 Mac,嵌入了 JavaScript 引擎和 MongoDB mongo,只要你会使用 mongo shell,你就会使用 Robomongo,它还提了供语法高亮、自动补全、差别视图等。 12 | 13 | [Robomongo 下载地址](https://robomongo.org/download) 14 | 15 | 下载并安装成功后点击左上角的 `Create` 创建一个连接,给该连接起个名字如: `localhost`,使用默认地址(localhost)和端口(27017)即可,点击 `Save` 保存。 16 | 17 | ![](./img/1.2.1.png) 18 | 19 | 20 | 双击 `localhost` 连接到 MongoDB 并进入交互界面,尝试插入一条数据并查询出来,如下所示: 21 | 22 | ![](./img/1.2.2.png) 23 | 24 | 25 | #### MongoChef 26 | 27 | [MongoChef](http://3t.io/mongochef/) 是另一款强大的 MongoDB 可视化管理工具,支持 Windows、Linux 和 Mac。 28 | 29 | [MongoChef 下载地址](http://3t.io/mongochef/#mongochef-download-compare),我们选择左侧的非商业用途的免费版下载。 30 | 31 | ![](./img/1.2.3.png) 32 | 33 | 安装成功后跟 Robomongo 一样,也需要创建一个新的连接的配置,成功后双击进入到 MongoChef 主页面,如下所示: 34 | 35 | ![](./img/1.2.4.png) 36 | 37 | 还可以使用 shell 模式: 38 | 39 | ![](./img/1.2.5.png) 40 | 41 | > 小提示: MongoChef 相较于 Robomongo 更强大一些,但 Robomongo 比较轻量也能满足大部分的常规需求,所以哪一个适合自己还需读者自行尝试。 42 | 43 | 上一节:[1.1 Node.js 的安装与使用](https://github.com/18820227745/shop-demo-node/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) 44 | 45 | 下一节:[2.1 require](https://github.com/18820227745/shop-demo-node/blob/master/book/2.1%20require.md) -------------------------------------------------------------------------------- /book/2.2 exports 和 module.exports.md: -------------------------------------------------------------------------------- 1 | require 用来加载代码,而 exports 和 module.exports 则用来导出代码。 2 | 3 | 很多新手可能会迷惑于 exports 和 module.exports 的区别,为了更好的理解 exports 和 module.exports 的关系,我们先来巩固下 js 的基础。示例: 4 | 5 | **test.js** 6 | 7 | ``` 8 | var a = {name: 1}; 9 | var b = a; 10 | 11 | console.log(a); 12 | console.log(b); 13 | 14 | b.name = 2; 15 | console.log(a); 16 | console.log(b); 17 | 18 | var b = {name: 3}; 19 | console.log(a); 20 | console.log(b); 21 | ``` 22 | 23 | 运行 test.js 结果为: 24 | 25 | ``` 26 | { name: 1 } 27 | { name: 1 } 28 | { name: 2 } 29 | { name: 2 } 30 | { name: 2 } 31 | { name: 3 } 32 | ``` 33 | 34 | **解释**:a 是一个对象,b 是对 a 的引用,即 a 和 b 指向同一块内存,所以前两个输出一样。当对 b 作修改时,即 a 和 b 指向同一块内存地址的内容发生了改变,所以 a 也会体现出来,所以第三四个输出一样。当 b 被覆盖时,b 指向了一块新的内存,a 还是指向原来的内存,所以最后两个输出不一样。 35 | 36 | 明白了上述例子后,我们只需知道三点就知道 exports 和 module.exports 的区别了: 37 | 38 | 1. module.exports 初始值为一个空对象 {} 39 | 2. exports 是指向的 module.exports 的引用 40 | 3. require() 返回的是 module.exports 而不是 exports 41 | 42 | Node.js 官方文档的截图证实了我们的观点: 43 | 44 | ![](./img/2.2.1.png) 45 | 46 | #### exports = module.exports = {...} 47 | 48 | 我们经常看到这样的写法: 49 | 50 | ``` 51 | exports = module.exports = {...} 52 | ``` 53 | 54 | 上面的代码等价于: 55 | 56 | ``` 57 | module.exports = {...} 58 | exports = module.exports 59 | ``` 60 | 61 | 原理很简单:module.exports 指向新的对象时,exports 断开了与 module.exports 的引用,那么通过 exports = module.exports 让 exports 重新指向 module.exports。 62 | 63 | > 小提示:ES6 的 import 和 export 不在本文的讲解范围,有兴趣的读者可以去学习阮一峰老师的[《ECMAScript6入门》](http://es6.ruanyifeng.com/)。 64 | 65 | 上一节:[2.1 require](https://github.com/18820227745/shop-demo-node/blob/master/book/2.1%20require.md) 66 | 67 | 下一节:[2.3 Promise](https://github.com/18820227745/shop-demo-node/blob/master/book/2.3%20Promise.md) -------------------------------------------------------------------------------- /book/2.1 require.md: -------------------------------------------------------------------------------- 1 | require 用来加载一个文件的代码,关于 require 的机制这里不展开讲解,请仔细阅读 [官方文档](https://nodejs.org/api/modules.html)。 2 | 3 | 简单概括以下几点: 4 | 5 | - require 可加载 .js、.json 和 .node 后缀的文件 6 | - require 的过程是同步的,所以这样是错误的: 7 | ``` 8 | setTimeout(() => { 9 | module.exports = { a: 'hello' }; 10 | }, 0); 11 | ``` 12 | require 这个文件得到的是空对象 `{}` 13 | 14 | - require 目录的机制是: 15 | - 如果目录下有 package.json 并指定了 main 字段,则用之 16 | - 如果不存在 package.json,则依次尝试加载目录下的 index.js 和 index.node 17 | - require 过的文件会加载到缓存,所以多次 require 同一个文件(模块)不会重复加载 18 | - 判断是否是程序的入口文件有两种方式: 19 | - require.main === module(推荐) 20 | - module.parent === null 21 | 22 | 23 | #### 循环引用 24 | 25 | 循环引用(或循环依赖)简单点来说就是 a 文件 require 了 b 文件,然后 b 文件又反过来 require 了 a 文件。我们用 a->b 代表 b require 了 a。 26 | 27 | 简单的情况: 28 | 29 | ``` 30 | a->b 31 | b->a 32 | ``` 33 | 34 | 复杂点的情况: 35 | 36 | ``` 37 | a->b 38 | b->c 39 | c->a 40 | ``` 41 | 42 | 循环引用并不会报错,导致的结果是 require 的结果是空对象 `{}`,原因是 b require 了 a,a 又去 require 了 b,此时 b 还没初始化好,所以只能拿到初始值 `{}`。当产生循环引用时一般有两种方法解决: 43 | 44 | 1. 通过分离共用的代码到另一个文件解决,如上面简单的情况,可拆出共用的代码到 c 中,如下: 45 | 46 | ``` 47 | c->a 48 | c->b 49 | ``` 50 | 51 | 2. 不在最外层 require,在用到的地方 require,通常在函数的内部 52 | 53 | 总的来说,循环依赖的陷阱并不大容易出现,但一旦出现了,对于新手来说还真不好定位。它的存在给我们提了个醒,要时刻注意你项目的依赖关系不要过于复杂,哪天你发现一个你明明已经 exports 了的方法报 `undefined is not a function`,我们就该提醒一下自己:哦,也许是它来了。 54 | 55 | 官方示例: [https://nodejs.org/api/modules.html#modules_cycles](https://nodejs.org/api/modules.html#modules_cycles) 56 | 57 | 上一节:[1.2 MongoDB 的安装与使用](https://github.com/18820227745/shop-demo-node/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) 58 | 59 | 下一节:[2.2 exports 和 module.exports](https://github.com/18820227745/shop-demo-node/blob/master/book/2.2%20exports%20%E5%92%8C%20module.exports.md) 60 | -------------------------------------------------------------------------------- /routes/register.js: -------------------------------------------------------------------------------- 1 | var sha1 = require('sha1'); 2 | module.exports = function (app) { 3 | app.get('/register', function (req, res) { 4 | res.render("register"); 5 | }); 6 | //解析表单,并写入数据库 7 | app.post("/register", function (req, res) { 8 | var User = global.dbHelper.getModel('user'); 9 | var uname = req.body.username; 10 | // 校验参数 11 | try { 12 | if (!(uname.length >= 1 && uname.length <= 10)) { 13 | throw new Error('名字请限制在 1-10 个字符'); 14 | } 15 | if (req.body.password !== req.body.confirmpwd) { 16 | throw new Error('两次输入密码不一致'); 17 | } 18 | } catch (e) { 19 | // 注册失败 20 | console.log("e.message_----------",e.message); 21 | req.session.error = e.message; 22 | return res.redirect('/register'); 23 | } 24 | User.findOne({ name: uname }, function (error, doc) { 25 | if (error) { 26 | res.send(500); 27 | req.session.error = '网络异常错误!'; 28 | console.log(error); 29 | } else if (doc) { 30 | req.session.error = "用户已存在!"; 31 | res.send(500); 32 | } else { 33 | User.create({ 34 | name: uname, 35 | password: req.body.upwd 36 | }, function (error, doc) { 37 | if (error) { 38 | res.send(500); 39 | console.log(error); 40 | } else { 41 | req.session.error = "用户创建成功!"; 42 | res.redirect("login"); 43 | res.send(200); 44 | } 45 | }); 46 | } 47 | }); 48 | }); 49 | } -------------------------------------------------------------------------------- /book/1.1 Node.js 的安装与使用.md: -------------------------------------------------------------------------------- 1 | ## 1.1.1 安装 Node.js 2 | 3 | 有三种方式安装 Node.js:一是通过安装包安装,二是通过源码编译安装,三是在 Linux 下可以通过 yum|apt-get 安装,在 Mac 下可以通过 [Homebrew](http://brew.sh/) 安装。对于 Windows 和 Mac 用户,推荐使用安装包安装,Linux 用户推荐使用源码编译安装。 4 | 5 | #### Windows 和 Mac 安装: 6 | 7 | **第一步:** 8 | 9 | 打开 [Node.js 官网](https://nodejs.org/en/),可以看到以下两个下载选项: 10 | 11 | ![](./img/1.1.1.png) 12 | 13 | 左边的是 LTS 版,用过 ubuntu 的同学可能比较熟悉,即长期支持版本,大多数人用这个就可以了。右边是最新版,支持最新的语言特性(比如对 ES6 的支持更全面),想尝试新特性的开发者可以安装这个版本。我们选择左边的 v6.11.1 LTS 点击下载。 14 | 15 | > 小提示:从 [http://node.green](http://node.green) 上可以看到 Node.js 各个版本对 ES6 的支持情况。 16 | 17 | **第二步:** 18 | 19 | 安装 Node.js,这个没什么好说的,一直点击 `继续` 即可。 20 | 21 | ![](./img/1.1.2.png) 22 | 23 | **第三步:** 24 | 25 | 提示安装成功后,打开终端输入以下命令,可以看到 node 和 npm 都已经安装好了: 26 | 27 | ![](./img/1.1.3.png) 28 | 29 | #### Linux 安装: 30 | 31 | Linux 用户可通过源码编译安装: 32 | 33 | ``` 34 | curl -O https://nodejs.org/dist/v6.9.1/node-v6.9.1.tar.gz 35 | tar -xzvf node-v6.9.1.tar.gz 36 | cd node-v6.9.1 37 | ./configure 38 | make 39 | make install 40 | ``` 41 | 42 | > 注意: 如果编译过程报错,可能是缺少某些依赖包。因为报错内容不尽相同,请读者自行求助搜索引擎或 [stackoverflow](http://stackoverflow.com/)。 43 | 44 | ## 1.1.2 n 和 nvm 45 | 46 | 通常我们使用稳定的 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/) 讲得特别详细,这里不再赘述。 47 | 48 | ## 1.1.3 nrm 49 | 50 | [nrm](https://github.com/Pana/nrm) 是一个管理 npm 源的工具。用过 ruby 和 gem 的同学会比较熟悉,通常我们会把 gem 源切到国内的淘宝镜像,这样在安装和更新一些包的时候比较快。nrm 同理,用来切换官方 npm 源和国内的 npm 源(如: [cnpm](http://cnpmjs.org/)),当然也可以用来切换官方 npm 源和公司私有 npm 源。 51 | 52 | 全局安装 nrm: 53 | 54 | ``` 55 | npm i nrm -g 56 | ``` 57 | 58 | 查看当前 nrm 内置的几个 npm 源的地址: 59 | 60 | ![](./img/1.1.4.png) 61 | 62 | 切换到 cnpm: 63 | 64 | ![](./img/1.1.5.png) 65 | 66 | 下一节:[1.2 MongoDB 的安装与使用](https://github.com/18820227745/shop-demo-node/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) -------------------------------------------------------------------------------- /views/addcommodity.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 添加商品 6 | 7 | 8 | 9 | 10 | 13 | 34 | 35 | 36 | 37 |
38 |

添加商品

39 | 返回商品页 40 |
41 | 42 | 43 |
44 | 45 | 46 |
47 | 48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /book/4.6 连接数据库.md: -------------------------------------------------------------------------------- 1 | 我们使用 [Mongoose](https://github.com/Automattic/mongoose) 这个模块操作 mongodb 进行增删改查。 2 | 3 | **app.js** 4 | 5 | 使用全局变量实现数据库操作 6 | ``` 7 | global.dbHelper = require('./common/dbHelper'); 8 | global.db = mongoose.connect(config.mongodb); 9 | 10 | ``` 11 | 12 | **./common/model.js** 13 | 14 | 数据库集合设计:`user`(用户信息)、`commodity`(商品信息)、`cart`(购物车信息) 15 | ``` 16 | module.exports = { 17 | user:{ 18 | name: {type: String,required: true}, 19 | password: {type: String, required: true} 20 | }, 21 | commodity: { 22 | name: String, 23 | price: Number, 24 | imgSrc: String 25 | }, 26 | cart: { 27 | uId: {type: String}, 28 | cId: {type: String}, 29 | cName: { type: String}, 30 | cPrice: { type: String}, 31 | cImaSrc: { type: String}, 32 | cQuantity: { type: Number}, 33 | cStatus: { type: Boolean,default: false} 34 | } 35 | } 36 | 37 | ``` 38 | 39 | ## 4.6.1 为什么使用 Mongoose 40 | 41 | 42 | #### Mongoose: 43 | 44 | **优点:** 45 | 46 | 1. 封装了数据库的操作,给人的感觉是同步的,其实内部是异步的。如 mongoose 与 MongoDB 建立连接: 47 | ``` 48 | var mongoose = require('mongoose'); 49 | mongoose.connect('mongodb://localhost/test'); 50 | var BlogModel = mongoose.model('Blog', { title: String, content: String }); 51 | BlogModel.find() 52 | ``` 53 | 2. 支持 Promise。这个也无需多说,Promise 是未来趋势,可结合 co 使用,也可结合 async/await 使用。 54 | 3. 支持文档校验。如上所述。 55 | 56 | **缺点:** 57 | 58 | 1. 功能多,复杂。Mongoose 很强大,有很多功能是比较鸡肋甚至可以去掉的,如果使用反而会影响代码的可读性。比如虚拟属性以及 schema 上定义 statics 方法和 instance 方法,可以把这些定义成外部方法,用到的时候调用即可。 59 | 2. 较弱的 plugin 系统。如:`schema.pre('save', function(next) {})` 和 `schema.post('find', function(next) {})`,只支持异步 `next()`,灵活性大打折扣。 60 | 3. 其他:对新手来说难以理解的 Schema、Model、Entry 之间的关系;容易混淆的 toJSON 和 toObject,以及有带有虚拟属性的情况;用和不用 exec 的情况以及直接用 then 的情况;返回的结果是 Mongoose 包装后的对象,在此对象上修改结果却无效等等。 61 | 62 | 63 | 64 | ### 扩展阅读 65 | 66 | 1. [从零开始写一个 Node.js 的 MongoDB 驱动库](https://zhuanlan.zhihu.com/p/24308524) 67 | 68 | 2. [为什么使用 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) 69 | 70 | 71 | 上一节:[4.5 页面设计](https://github.com/18820227745/shop-demo-node/blob/master/book/4.5%20%E9%A1%B5%E9%9D%A2%E8%AE%BE%E8%AE%A1.md) 72 | 73 | 下一节:[4.7 注册](https://github.com/18820227745/shop-demo-node/blob/master/book/4.7%20%E6%B3%A8%E5%86%8C.md) 74 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var app = express(); 3 | var path = require("path"); 4 | var winston = require('winston'); 5 | var expressWinston = require('express-winston'); 6 | var mongoose = require("mongoose"); 7 | var config = require('config-lite')(__dirname); 8 | var pkg = require('./package'); 9 | var bodyParser = require("body-parser"); 10 | var multer = require("multer"); 11 | var session = require("express-session"); 12 | const formidable = require('express-formidable'); 13 | 14 | global.dbHelper = require('./common/dbHelper'); 15 | 16 | global.db = mongoose.connect(config.mongodb); 17 | 18 | app.use(session({ 19 | secret: config.session.secret, 20 | key: config.session.key, 21 | cookie: { 22 | maxAge: config.session.maxAge 23 | } 24 | })); 25 | 26 | 27 | // 设定views变量,意为视图存放的目录 28 | app.set("views", path.join(__dirname, 'views')); 29 | 30 | 31 | // 设定view engine变量, 意为网页模板引擎 32 | //app.set("view engine",'ejs');//ejs:可以直接嵌入变量<%= title %>,include其他页面模块 33 | app.set("view engine", 'html'); 34 | app.engine('.html', require('ejs').__express); 35 | 36 | // 设施bodyParser模块,是项目中可以直接引用req.body.XXXX 37 | app.use(bodyParser.json()); 38 | app.use(bodyParser.urlencoded({ extended: true })); 39 | //app.use(multer()); 40 | 41 | 42 | 43 | // 设定静态文件目录,比如本地文件 44 | app.use(express.static(path.join(__dirname, 'public'))); 45 | 46 | //全局中间件,每个路由处理都会先执行这段代码 47 | app.use(function (req, res, next) { 48 | res.locals.user = req.session.user; 49 | var err = req.session.error; 50 | res.locals.message = ''; 51 | if (err) res.locals.message = '
' + err + '
'; 52 | next(); 53 | }); 54 | 55 | // app.use(expressWinston.logger({ 56 | // transports: [ 57 | // new (winston.transports.Console)({ 58 | // json: true, 59 | // colorize: true 60 | // }), 61 | // new winston.transports.File({ 62 | // filename: 'logs/success.log' 63 | // }) 64 | // ] 65 | // })); 66 | // 路由 67 | require('./routes')(app); 68 | // 错误请求的日志 69 | // app.use(expressWinston.errorLogger({ 70 | // transports: [ 71 | // new winston.transports.Console({ 72 | // json: true, 73 | // colorize: true 74 | // }), 75 | // new winston.transports.File({ 76 | // filename: 'logs/error.log' 77 | // }) 78 | // ] 79 | // })); 80 | 81 | 82 | app.get('/', function (req, res) { 83 | res.redirect("/home"); 84 | }); 85 | 86 | if (module.parent) { 87 | module.exports = app; 88 | } else { 89 | // 监听端口,启动程序 90 | app.listen(config.port, function () { 91 | console.log(`${pkg.name} listening on port ${config.port}`); 92 | }); 93 | } 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /views/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 主页 5 | 6 | 7 | 8 | 9 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | 商品展示页 35 |
36 | 添加商品 37 | 购物车 38 | ><%=login.message%> 39 | 40 |
41 |
42 |
43 |
44 |
    45 | <%for(var i in Commoditys){ if(!Commoditys[i].name)continue;%> 46 |
  • 47 |
    48 | 49 |
    50 |
    51 | <%=Commoditys[i].name%> 52 | ¥<%=Commoditys[i].price%> 53 |
    54 |
    55 | 加入购物车 56 |
    57 |
  • 58 | <%}%> 59 |
60 |
61 |
62 |
63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /book/2.6 npm 使用注意事项.md: -------------------------------------------------------------------------------- 1 | ## 2.6.1 npm init 2 | 3 | 使用 `npm init` 初始化一个空项目是一个好的习惯,即使你对 package.json 及其他属性非常熟悉,`npm init` 也是你开始写新的 Node.js 应用或模块的一个快捷的办法。`npm init` 有智能的默认选项,比如从根目录名称推断模块名称,通过 `~/.npmrc` 读取你的信息,用你的 Git 设置来确定 repository 等等。 4 | 5 | ## 2.6.2 npm install 6 | 7 | `npm install` 是我们最常用的 npm 命令之一,因此我们需要好好了解下这个命令。终端输入 `npm install -h` 查看使用方式: 8 | 9 | ![](./img/2.6.1.png) 10 | 11 | 可以看出:我们通过 `npm install` 可以安装 npm 上发布的某个版本、某个tag、某个版本区间的模块,甚至可以安装本地目录、压缩包和 git/github 的库作为依赖。 12 | 13 | > 小提示: `npm i` 是 `npm install` 的简写,建议使用 `npm i`。 14 | 15 | 直接使用 `npm i` 安装的模块是不会写入 package.json 的 dependencies (或 devDependencies),需要额外加个参数: 16 | 17 | 1. `npm i express --save`/`npm i express -S` (安装 express,同时将 `"express": "^4.14.0"` 写入 dependencies ) 18 | 2. `npm i express --save-dev`/`npm i express -D` (安装 express,同时将 `"express": "^4.14.0"` 写入 devDependencies ) 19 | 3. `npm i express --save --save-exact` (安装 express,同时将 `"express": "4.14.0"` 写入 dependencies ) 20 | 21 | 第三种方式将固定版本号写入 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)。 22 | 23 | > 后面会介绍更安全的 `npm shrinkwrap` 的用法。 24 | 25 | 运行以下命令: 26 | 27 | ``` 28 | npm config set save-exact true 29 | ``` 30 | 31 | 这样每次 `npm i xxx --save` 的时候会锁定依赖的版本号,相当于加了 `--save-exact` 参数。 32 | 33 | > 小提示:`npm config set` 命令将配置写到了 ~/.npmrc 文件,运行 `npm config list` 查看。 34 | 35 | ## 2.6.3 npm scripts 36 | 37 | npm 提供了灵活而强大的 scripts 功能,见 [官方文档](https://docs.npmjs.com/misc/scripts)。 38 | 39 | npm 的 scripts 有一些内置的缩写命令,如常用的: 40 | 41 | - `npm start` 等价于 `npm run start` 42 | - `npm test` 等价于 `npm run test` 43 | 44 | ## 2.6.4 npm shrinkwrap 45 | 46 | 前面说过要锁定依赖的版本,但这并不能完全防止意外情况的发生,因为锁定的只是最外一层的依赖,而里层依赖的模块的 package.json 有可能写的是 `"mongoose": "*"`。为了彻底锁定依赖的版本,让你的应用在任何机器上安装的都是同样版本的模块(不管嵌套多少层),通过运行 `npm shrinkwrap`,会在当前目录下产生一个 `npm-shrinkwrap.json`,里面包含了通过 node_modules 计算出的模块的依赖树及版本。上面的截图也显示:只要目录下有 npm-shrinkwrap.json 则运行 `npm install` 的时候会优先使用 npm-shrinkwrap.json 进行安装,没有则使用 package.json 进行安装。 47 | 48 | 更多阅读: 49 | 50 | 1. https://docs.npmjs.com/cli/shrinkwrap 51 | 2. http://tech.meituan.com/npm-shrinkwrap.html 52 | 53 | > 注意: 如果 node_modules 下存在某个模块(如直接通过 `npm install xxx` 安装的)而 package.json 中没有,运行 `npm shrinkwrap` 则会报错。另外,`npm shrinkwrap` 只会生成 dependencies 的依赖,不会生成 devDependencies 的。 54 | 55 | 上一节:[2.5 packge.json](https://github.com/nswbmw/N-blog/blob/master/book/2.5%20package.json.md) 56 | 57 | 下一节:[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) -------------------------------------------------------------------------------- /book/3.2 路由.md: -------------------------------------------------------------------------------- 1 | 前面我们只是挂载了根路径的路由控制器,现在修改 index.js 如下: 2 | 3 | ``` 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | app.get('/', function(req, res) { 8 | res.send('hello, express'); 9 | }); 10 | 11 | app.get('/users/:name', function(req, res) { 12 | res.send('hello, ' + req.params.name); 13 | }); 14 | 15 | app.listen(3000); 16 | ``` 17 | 18 | 以上代码的意思是:当访问根路径时,依然返回 hello, express,当访问如 `localhost:3000/users/nswbmw` 路径时,返回 hello, nswbmw。路径中 `:name` 起了占位符的作用,这个占位符的名字是 name,可以通过 `req.params.name` 取到实际的值。 19 | 20 | > 小提示:express 使用了 [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) 模块实现的路由匹配。 21 | 22 | 不难看出:req 包含了请求来的相关信息,res 则用来返回该请求的响应,更多请查阅 [express 官方文档](http://expressjs.com/en/4x/api.html)。下面介绍几个常用的 req 的属性: 23 | 24 | - `req.query`: 解析后的 url 中的 querystring,如 `?name=haha`,req.query 的值为 `{name: 'haha'}` 25 | - `req.params`: 解析 url 中的占位符,如 `/:name`,访问 /haha,req.params 的值为 `{name: 'haha'}` 26 | - `req.body`: 解析后请求体,需使用相关的模块,如 [body-parser](https://www.npmjs.com/package/body-parser),请求体为 `{"name": "haha"}`,则 req.body 为 `{name: 'haha'}` 27 | 28 | ## 3.2.1 express.Router 29 | 30 | 上面只是很简单的路由使用的例子(将所有路由控制函数都放到了 index.js),但在实际开发中通常有几十甚至上百的路由,都写在 index.js 既臃肿又不好维护,这时可以使用 express.Router 实现更优雅的路由解决方案。在 myblog 目录下创建空文件夹 routes,在 routes 目录下创建 index.js 和 users.js。最后代码如下: 31 | 32 | **index.js** 33 | 34 | ``` 35 | var express = require('express'); 36 | var app = express(); 37 | var indexRouter = require('./routes/index'); 38 | var userRouter = require('./routes/users'); 39 | 40 | app.use('/', indexRouter); 41 | app.use('/users', userRouter); 42 | 43 | app.listen(3000); 44 | ``` 45 | 46 | **routes/index.js** 47 | 48 | ``` 49 | var express = require('express'); 50 | var router = express.Router(); 51 | 52 | router.get('/', function(req, res) { 53 | res.send('hello, express'); 54 | }); 55 | 56 | module.exports = router; 57 | ``` 58 | 59 | **routes/users.js** 60 | 61 | ``` 62 | var express = require('express'); 63 | var router = express.Router(); 64 | 65 | router.get('/:name', function(req, res) { 66 | res.send('hello, ' + req.params.name); 67 | }); 68 | 69 | module.exports = router; 70 | ``` 71 | 72 | 以上代码的意思是:我们将 `/` 和 `/users/:name` 的路由分别放到了 routes/index.js 和 routes/users.js 中,每个路由文件通过生成一个 express.Router 实例 router 并导出,通过 `app.use` 挂载到不同的路径。这两种代码实现了相同的功能,但在实际开发中推荐使用 express.Router 将不同的路由分离到不同的路由文件中。 73 | 74 | 更多 express.Router 的用法见 [express 官方文档](http://expressjs.com/en/4x/api.html#router)。 75 | 76 | 上一节:[3.1 初始化一个 Express 项目](https://github.com/18820227745/shop-demo-node/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) 77 | 78 | 下一节:[3.3 模板引擎](https://github.com/18820227745/shop-demo-node/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md) -------------------------------------------------------------------------------- /book/4.11 日志.md: -------------------------------------------------------------------------------- 1 | 现在我们来实现日志功能,日志分为正常请求的日志和错误请求的日志,这两种日志都打印到终端并写入文件。 2 | 3 | ## 4.11.1 winston 和 express-winston 4 | 5 | 我们使用 [winston](https://www.npmjs.com/package/winston) 和 [express-winston](https://www.npmjs.com/package/express-winston) 记录日志。新建 logs 目录存放日志文件,修改 app.js,在: 6 | 7 | **index.js** 8 | 9 | 下引入所需模块: 10 | 11 | ``` 12 | var winston = require('winston'); 13 | var expressWinston = require('express-winston'); 14 | ``` 15 | 16 | 将: 17 | 18 | ``` 19 | // 路由 20 | require('./routes')(app); 21 | ``` 22 | 23 | 修改为: 24 | 25 | ``` 26 | // 正常请求的日志 27 | app.use(expressWinston.logger({ 28 | transports: [ 29 | new (winston.transports.Console)({ 30 | json: true, 31 | colorize: true 32 | }), 33 | new winston.transports.File({ 34 | filename: 'logs/success.log' 35 | }) 36 | ] 37 | })); 38 | // 路由 39 | require('./routes')(app); 40 | // 错误请求的日志 41 | app.use(expressWinston.errorLogger({ 42 | transports: [ 43 | new winston.transports.Console({ 44 | json: true, 45 | colorize: true 46 | }), 47 | new winston.transports.File({ 48 | filename: 'logs/error.log' 49 | }) 50 | ] 51 | })); 52 | ``` 53 | 54 | 可以看出:我们将正常请求的日志打印到终端并写入了 `logs/success.log`,将错误请求的日志打印到终端并写入了 `logs/error.log`。需要注意的是:记录正常请求日志的中间件要放到 `require('./routes')(app);` 之前,记录错误请求日志的中间件要放到 `require('./routes')(app);` 之后。 55 | 56 | ## 4.11.2 .gitignore 57 | 58 | 如果我们想把项目托管到 git 服务器上(如: [GitHub](https://github.com)),而不想把线上配置、本地调试的 logs 以及 node_modules 添加到 git 的版本控制中,这个时候就需要 .gitignore 文件了,git 会读取 .gitignore 并忽略这些文件。在 myblog 下新建 .gitignore 文件,添加如下配置: 59 | 60 | **.gitignore** 61 | 62 | ``` 63 | config/* 64 | !config/default.* 65 | npm-debug.log 66 | node_modules 67 | coverage 68 | ``` 69 | 70 | 需要注意的是,通过设置: 71 | 72 | ``` 73 | config/* 74 | !config/default.* 75 | ``` 76 | 77 | 这样只有 config/default.js 会加入 git 的版本控制,而 config 目录下的其他配置文件则会被忽略,因为把线上配置加入到 git 是一个不太安全的行为,通常你需要去线上环境手动创建 config/production.js,然后添加一些线上的配置(如:mongodb url)即可覆盖相应的 default 配置。 78 | 79 | > 注意:后面讲到部署到 Heroku 时,因为无法登录到 Heroku 主机,所以可以把以下两行删掉,将 config/production.js 也加入 git 中。 80 | > 81 | > ``` 82 | > config/* 83 | > !config/default.* 84 | > ``` 85 | 86 | 然后在 public/img 目录下创建 .gitignore: 87 | 88 | ``` 89 | # Ignore everything in this directory 90 | * 91 | # Except this file 92 | !.gitignore 93 | ``` 94 | 95 | 这样 git 会忽略 public/img 目录下所有上传的头像,而不忽略 public/img 目录。同理,在 logs 目录下创建 .gitignore 忽略日志文件: 96 | 97 | ``` 98 | # Ignore everything in this directory 99 | * 100 | # Except this file 101 | !.gitignore 102 | ``` 103 | 104 | 上一节:[4.10 购物车](https://github.com/18820227745/shop-demo-node/blob/master/book/4.10%20%E8%B4%AD%E7%89%A9%E8%BD%A6.md) 105 | 106 | 下一节:[4.12 测试](https://github.com/18820227745/shop-demo-node/blob/master/book/4.12%20%E6%B5%8B%E8%AF%95.md) -------------------------------------------------------------------------------- /views/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 用户登录 5 | 6 | 7 | 8 | 9 | 22 | 43 | 44 | 45 |
46 | <%- message %> 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 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/routes/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\routes\index.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/routes/ index.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 6/6 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 6/6 41 |
42 |
43 |
44 |
45 |

46 | 
65 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 54 | 55 | 56 | 57 | 58 |  
module.exports = function(app){
59 |   require('./login')(app);
60 |   require('./home')(app);
61 |   require('./logout')(app);
62 |   require('./register')(app);
63 |   require('./cart')(app);
64 | }
66 |
67 |
68 | 72 | 73 | 74 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /test/register.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var assert = require('assert'); 3 | var request = require('supertest'); 4 | var sha1 = require('sha1'); 5 | var app = require('../app'); 6 | var User = global.dbHelper.getModel('user'); 7 | 8 | var testName1 = 'testName2'; 9 | var testName2 = 'nswbmw2'; 10 | describe('login', function () { 11 | describe('POST /register', function () { 12 | var agent = request.agent(app);//persist cookie when redirect 13 | beforeEach(function (done) { 14 | // 创建一个用户 15 | User.create({ 16 | name: testName1, 17 | password: "123456" 18 | }, function (error, doc) { 19 | if (error) { 20 | done; 21 | console.log(error); 22 | } else { 23 | done(); 24 | console.log("Successfully created user"); 25 | } 26 | }); 27 | 28 | }); 29 | 30 | afterEach(function (done) { 31 | // 删除测试用户 32 | User.remove({ name: { $in: [testName1, testName2] } }, function (error) { 33 | if (error) { 34 | done; 35 | console.log(error); 36 | } else { 37 | done(); 38 | console.log("Delete success! "); 39 | } 40 | }); 41 | }); 42 | 43 | // 名户名错误的情况 44 | it('wrong name', function (done) { 45 | agent 46 | .post('/register') 47 | .type('form') 48 | .field({ username: '666', password: '666', confirmpwd: '666' }) 49 | .redirects() 50 | .end(function (err, res) { 51 | if (err) return done(err); 52 | assert(res.text.match(/名字请限制在 1-10 个字符/)); 53 | done(); 54 | }); 55 | }); 56 | 57 | // 其余的参数测试自行补充 58 | // 用户名被占用的情况 59 | // it('duplicate name', function (done) { 60 | // agent 61 | // .post('/register') 62 | // .type('form') 63 | // .field({"username":testName1,password:'123456',confirmpwd:'123456'}) 64 | // .redirects() 65 | // .end(function (err, res) { 66 | // if (err) return done(err); 67 | // assert(res.text.match(/用户已存在/)); 68 | // done(); 69 | // }); 70 | // }); 71 | 72 | // 注册成功的情况 73 | // it('success', function (done) { 74 | // agent 75 | // .post('/signup') 76 | // .type('form') 77 | // .field({"username":testName2,password:'123456',confirmpwd:'123456'}) 78 | // .redirects() 79 | // .end(function (err, res) { 80 | // if (err) return done(err); 81 | 82 | // assert(res.text.match(/用户创建成功/)); 83 | // done(); 84 | // }); 85 | // }); 86 | }); 87 | }); -------------------------------------------------------------------------------- /book/3.4 Express 浅析.md: -------------------------------------------------------------------------------- 1 | 前面我们讲解了 express 中路由和模板引擎 ejs 的用法,但 express 的精髓并不在此,在于中间件的设计理念。 2 | 3 | ## 3.4.1 中间件与 next 4 | 5 | express 中的中间件(middleware)就是用来处理请求的,当一个中间件处理完,可以通过调用 `next()` 传递给下一个中间件,如果没有调用 `next()`,则请求不会往下传递,如内置的 `res.render` 其实就是渲染完 html 直接返回给客户端,没有调用 `next()`,从而没有传递给下一个中间件。看个小例子,修改 index.js 如下: 6 | 7 | **index.js** 8 | 9 | ``` 10 | var express = require('express'); 11 | var app = express(); 12 | 13 | app.use(function(req, res, next) { 14 | console.log('1'); 15 | next(); 16 | }); 17 | 18 | app.use(function(req, res, next) { 19 | console.log('2'); 20 | res.status(200).end(); 21 | }); 22 | 23 | app.listen(3000); 24 | ``` 25 | 26 | 此时访问 `localhost:3000`,终端会输出: 27 | 28 | ``` 29 | 1 30 | 2 31 | ``` 32 | 33 | 通过 `app.use` 加载中间件,在中间件中通过 next 将请求传递到下一个中间件,next 可接受一个参数接收错误信息,如果使用了 `next(error)`,则会返回错误而不会传递到下一个中间件,修改 index.js 如下: 34 | 35 | **index.js** 36 | 37 | ``` 38 | var express = require('express'); 39 | var app = express(); 40 | 41 | app.use(function(req, res, next) { 42 | console.log('1'); 43 | next(new Error('haha')); 44 | }); 45 | 46 | app.use(function(req, res, next) { 47 | console.log('2'); 48 | res.status(200).end(); 49 | }); 50 | 51 | app.listen(3000); 52 | ``` 53 | 54 | 此时访问 `localhost:3000`,终端会输出错误信息: 55 | 56 | ![](./img/3.4.1.png) 57 | 58 | 浏览器会显示: 59 | 60 | ![](./img/3.4.2.png) 61 | 62 | > 小提示:`app.use` 有非常灵活的使用方式,详情见 [官方文档](http://expressjs.com/en/4x/api.html#app.use)。 63 | 64 | express 有成百上千的第三方中间件,在开发过程中我们首先应该去 npm 上寻找是否有类似实现的中间件,尽量避免造轮子,节省开发时间。下面给出几个常用的搜索 npm 模块的网站: 65 | 66 | 1. [http://npmjs.com](http://npmjs.com)(npm 官网) 67 | 2. [http://node-modules.com](http://node-modules.com) 68 | 3. [https://npms.io](https://npms.io) 69 | 4. [https://nodejsmodules.org](https://nodejsmodules.org) 70 | 71 | > 小提示:express@4 之前的版本基于 connect 这个模块实现的中间件的架构,express@4 及以上的版本则移除了对 connect 的依赖自己实现了,理论上基于 connect 的中间件(通常以 `connect-` 开头,如 `connect-mongo`)仍可结合 express 使用。 72 | 73 | > 注意:中间件的加载顺序很重要!比如:通常把日志中间件放到比较靠前的位置,后面将会介绍的 `connect-flash` 中间件是基于 session 的,所以需要在 `express-session` 后加载。 74 | 75 | ## 3.4.2 错误处理 76 | 77 | 上面的例子中,应用程序为我们自动返回了错误栈信息(express 内置了一个默认的错误处理器),假如我们想手动控制返回的错误内容,则需要加载一个自定义错误处理的中间件,修改 index.js 如下: 78 | 79 | **index.js** 80 | 81 | ``` 82 | var express = require('express'); 83 | var app = express(); 84 | 85 | app.use(function(req, res, next) { 86 | console.log('1'); 87 | next(new Error('haha')); 88 | }); 89 | 90 | app.use(function(req, res, next) { 91 | console.log('2'); 92 | res.status(200).end(); 93 | }); 94 | 95 | //错误处理 96 | app.use(function(err, req, res, next) { 97 | console.error(err.stack); 98 | res.status(500).send('Something broke!'); 99 | }); 100 | 101 | app.listen(3000); 102 | ``` 103 | 104 | 此时访问 `localhost:3000`,浏览器会显示 `Something broke!`。 105 | 106 | > 小提示:关于 express 的错误处理,详情见 [官方文档](http://expressjs.com/en/guide/error-handling.html)。 107 | 108 | 上一节:[3.3 模板引擎](https://github.com/18820227745/shop-demo-node/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md) 109 | 110 | 下一节:[4.1 开发环境](https://github.com/18820227745/shop-demo-node/blob/master/book/4.1%20%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83.md) -------------------------------------------------------------------------------- /views/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 用户注册 6 | 7 | 8 | 9 | 10 | 11 | 23 | 44 | 45 | 46 |
47 | <%- message %> 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 | -------------------------------------------------------------------------------- /routes/cart.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function ( app ) { 3 | //查看购物车商品 4 | app.get('/cart', function(req, res) { 5 | var Cart = global.dbHelper.getModel('cart'); 6 | //不适用中间件检查登录 7 | if(!req.session.user){ 8 | req.session.error = "用户已过期,请重新登录:" 9 | res.redirect('/login'); 10 | }else{ 11 | //查找对应用户,且商品状态为未付款状态 12 | Cart.find({"uId":req.session.user._id,"cStatus":false}, function (error, docs) { 13 | res.render('cart',{carts:docs}); 14 | }); 15 | } 16 | }); 17 | //添加购物车商品 18 | app.get("/addToCart/:id", function(req, res) { 19 | //req.params.id 获取商品ID号 20 | if(!req.session.user){ 21 | req.session.error = "用户已过期,请重新登录:" 22 | res.redirect('/login'); 23 | }else{ 24 | var Commodity = global.dbHelper.getModel('commodity'), 25 | Cart = global.dbHelper.getModel('cart'); 26 | Cart.findOne({"uId":req.session.user._id, "cId":req.params.id},function(error,doc){ 27 | //商品已存在 +1 28 | if(doc){ 29 | Cart.update({"uId":req.session.user._id, "cId":req.params.id},{$set : { cQuantity : doc.cQuantity + 1 }},function(error,doc){ 30 | //成功返回1 失败返回0 31 | if(doc > 0){ 32 | res.redirect('/home'); 33 | } 34 | }); 35 | //商品未存在,添加 36 | }else{ 37 | Commodity.findOne({"_id": req.params.id}, function (error, doc) { 38 | if (doc) { 39 | Cart.create({ 40 | uId: req.session.user._id, 41 | cId: req.params.id, 42 | cName: doc.name, 43 | cPrice: doc.price, 44 | cImgSrc: doc.imgSrc, 45 | cQuantity : 1 46 | },function(error,doc){ 47 | if(doc){ 48 | res.redirect('/home'); 49 | } 50 | }); 51 | } else { 52 | 53 | } 54 | }); 55 | } 56 | }); 57 | } 58 | }); 59 | 60 | //删除购物车商品 61 | app.get("/delFromCart/:id", function(req, res) { 62 | //req.params.id 获取商品ID号 63 | var Cart = global.dbHelper.getModel('cart'); 64 | Cart.remove({"_id":req.params.id},function(error,doc){ 65 | //成功返回1 失败返回0 66 | if(doc > 0){ 67 | res.redirect('/cart'); 68 | } 69 | }); 70 | }); 71 | 72 | //购物车结算 73 | app.post("/cart/clearing",function(req,res){ 74 | var Cart = global.dbHelper.getModel('cart'); 75 | Cart.update({"_id":req.body.cid},{$set : { cQuantity : req.body.cnum,cStatus:true }},function(error,doc){ 76 | //更新成功返回1 失败返回0 77 | if(doc > 0){ 78 | res.send(200); 79 | } 80 | }); 81 | }); 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/config/default.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\config\default.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/config/ default.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 1/1 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 1/1 41 |
42 |
43 |
44 |
45 |

46 | 
74 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 |   57 |   58 |   59 |   60 |   61 |   62 |   63 |   64 |  
module.exports = {
65 |   port: 3000,
66 |   session: {
67 |     secret: 'e-commerce',
68 |     key: 'e-commerce',
69 |     maxAge: 1000 * 60 * 30
70 |   },
71 |   mongodb: 'mongodb://localhost:27017/e-commerce'
72 | };
73 |  
75 |
76 |
77 | 81 | 82 | 83 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/routes/logout.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\routes\logout.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/routes/ logout.js 20 |

21 |
22 |
23 | 40% 24 | Statements 25 | 2/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 50% 34 | Functions 35 | 1/2 36 |
37 |
38 | 40% 39 | Lines 40 | 2/5 41 |
42 |
43 |
44 |
45 |

46 | 
68 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 |   55 | 56 |   57 |   58 |   59 |   60 |  
module.exports = function ( app ) {
61 |     //退出登录
62 |     app.get('/logout', function(req, res){
63 |         req.session.user = null;
64 |         req.session.error = null;
65 |         res.redirect('/home');
66 |     });
67 | }
69 |
70 |
71 | 75 | 76 | 77 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files mysession/ 20 |

21 |
22 |
23 | 91.43% 24 | Statements 25 | 32/35 26 |
27 |
28 | 75% 29 | Branches 30 | 3/4 31 |
32 |
33 | 33.33% 34 | Functions 35 | 1/3 36 |
37 |
38 | 91.18% 39 | Lines 40 | 31/34 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 |
FileStatementsBranchesFunctionsLines
app.js
91.43%32/3575%3/433.33%1/391.18%31/34
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /book/3.3 模板引擎.md: -------------------------------------------------------------------------------- 1 | 模板引擎(Template Engine)是一个将页面模板和数据结合起来生成 html 的工具。上例中,我们只是返回纯文本给浏览器,现在我们修改代码返回一个 html 页面给浏览器。 2 | 3 | ## 3.3.1 ejs 4 | 5 | 模板引擎有很多,[ejs](https://www.npmjs.com/package/ejs) 是其中一种,因为它使用起来十分简单,而且与 express 集成良好,所以我们使用 ejs。安装 ejs: 6 | 7 | ``` 8 | npm i ejs --save 9 | ``` 10 | 11 | 修改 index.js 如下: 12 | 13 | **index.js** 14 | 15 | ``` 16 | var path = require('path'); 17 | var express = require('express'); 18 | var app = express(); 19 | var indexRouter = require('./routes/index'); 20 | var userRouter = require('./routes/users'); 21 | 22 | app.set('views', path.join(__dirname, 'views'));// 设置存放模板文件的目录 23 | app.set('view engine', 'ejs');// 设置模板引擎为 ejs 24 | 25 | app.use('/', indexRouter); 26 | app.use('/users', userRouter); 27 | 28 | app.listen(3000); 29 | ``` 30 | 31 | 通过 `app.set` 设置模板引擎为 ejs 和存放模板的目录。在 myblog 下新建 views 文件夹,在 views 下新建 users.ejs,添加如下代码: 32 | 33 | **views/users.ejs** 34 | 35 | ``` 36 | 37 | 38 | 39 | 42 | 43 | 44 |

<%= name.toUpperCase() %>

45 |

hello, <%= name %>

46 | 47 | 48 | ``` 49 | 50 | 修改 routes/users.js 如下: 51 | 52 | **routes/users.js** 53 | 54 | ``` 55 | var express = require('express'); 56 | var router = express.Router(); 57 | 58 | router.get('/:name', function(req, res) { 59 | res.render('users', { 60 | name: req.params.name 61 | }); 62 | }); 63 | 64 | module.exports = router; 65 | ``` 66 | 67 | 通过调用 `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`,如下图所示: 68 | 69 | ![](./img/3.3.1.png) 70 | 71 | 上面代码可以看到,我们在模板 `<%= name.toUpperCase() %>` 中使用了 JavaScript 的语法 `.toUpperCase()` 将名字转化为大写,那这个 `<%= xxx %>` 是什么东西呢?ejs 有 3 种常用标签: 72 | 73 | 1. `<% code %>`:运行 JavaScript 代码,不输出 74 | 2. `<%= code %>`:显示转义后的 HTML内容 75 | 3. `<%- code %>`:显示原始 HTML 内容 76 | 77 | > 注意:`<%= code %>` 和 `<%- code %>` 都可以是 JavaScript 表达式生成的字符串,当变量 code 为普通字符串时,两者没有区别。当 code 比如为 `

hello

` 这种字符串时,`<%= code %>` 会原样输出 `

hello

`,而 `<%- code %>` 则会显示 H1 大的 hello 字符串。 78 | 79 | 下面的例子解释了 `<% code %>` 的用法: 80 | 81 | **Data** 82 | 83 | ``` 84 | supplies: ['mop', 'broom', 'duster'] 85 | ``` 86 | 87 | **Template** 88 | 89 | ``` 90 | 95 | ``` 96 | 97 | **Result** 98 | 99 | ``` 100 | 105 | ``` 106 | 107 | 更多 ejs 的标签请看 [官方文档](https://www.npmjs.com/package/ejs#tags)。 108 | 109 | ## 3.3.2 includes 110 | 111 | 我们使用模板引擎通常不是一个页面对应一个模板,这样就失去了模板的优势,而是把模板拆成可复用的模板片段组合使用,如在 views 下新建 header.ejs 和 footer.ejs,并修改 users.ejs: 112 | 113 | **views/header.ejs** 114 | 115 | ``` 116 | 117 | 118 | 119 | 122 | 123 | 124 | ``` 125 | 126 | **views/footer.ejs** 127 | 128 | ``` 129 | 130 | 131 | ``` 132 | 133 | **views/users.ejs** 134 | 135 | ``` 136 | <%- include('header') %> 137 |

<%= name.toUpperCase() %>

138 |

hello, <%= name %>

139 | <%- include('footer') %> 140 | ``` 141 | 142 | 我们将原来的 users.ejs 拆成出了 header.ejs 和 footer.ejs,并在 users.ejs 通过 ejs 内置的 include 方法引入,从而实现了跟以前一个模板文件相同的功能。 143 | 144 | > 小提示:拆分模板组件通常有两个好处: 145 | > 146 | > 1. 模板可复用,减少重复代码 147 | > 2. 主模板结构清晰 148 | 149 | > 注意:要用 `<%- include('header') %>` 而不是 `<%= include('header') %>` 150 | 151 | 上一节:[3.2 路由](https://github.com/nswbmw/N-blog/blob/master/book/3.2%20%E8%B7%AF%E7%94%B1.md) 152 | 153 | 下一节:[3.4 Express 浅析](https://github.com/18820227745/shop-demo-node/blob/master/book/3.4%20Express%20%E6%B5%85%E6%9E%90.md) -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/middlewares/testController.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\middlewares\testController.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/middlewares/ testController.js 20 |

21 |
22 |
23 | 42.86% 24 | Statements 25 | 3/7 26 |
27 |
28 | 0% 29 | Branches 30 | 0/2 31 |
32 |
33 | 0% 34 | Functions 35 | 0/2 36 |
37 |
38 | 42.86% 39 | Lines 40 | 3/7 41 |
42 |
43 |
44 |
45 |

 46 | 
 83 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 |   60 | 61 | 62 |   63 |   64 |   65 |   66 |   67 |   68 |   69 |   70 |  
var muilter = require('../config/multerUtil');
 71 | //multer有single()中的名称必须是表单上传字段的name名称。
 72 | var upload = muilter.single('imgSrc');
 73 | exports.dataInput = function (req, res) {
 74 |     upload(req, res, function (err) {
 75 |         //添加错误处理
 76 |         if (err) {
 77 |             return console.log(err);
 78 |         }
 79 |         //文件信息在req.file或者req.files中显示。
 80 |         console.log(req);
 81 |     });
 82 | }
84 |
85 |
86 | 90 | 91 | 92 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/common/dbHelper.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\common\dbHelper.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/common/ dbHelper.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 7/7 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 7/7 41 |
42 |
43 |
44 |
45 |

 46 | 
101 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 |   66 |   67 |   68 | 69 | 70 |   71 | 72 |   73 | 74 |   75 |   76 |   77 | 78 | 79 |   80 |   81 |   82 |  
var mongoose = require('mongoose'),
 83 |     Schema = mongoose.Schema,
 84 |     models = require('./models');
 85 |  
 86 | for (var m in models) {
 87 |     mongoose.model(m, new Schema(models[m]));
 88 | }
 89 | module.exports = {
 90 |     getModel: function (type) {
 91 |         return _getModel(type);
 92 |     }
 93 | };
 94 |  
 95 | var _getModel = function (type) {
 96 |     return mongoose.model(type);
 97 | };
 98 |  
 99 |  
100 |  
102 |
103 |
104 | 108 | 109 | 110 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/common/models.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\common\models.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/common/ models.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 1/1 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 1/1 41 |
42 |
43 |
44 |
45 |

 46 | 
104 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 |   67 |   68 |   69 |   70 |   71 |   72 |   73 |   74 |   75 |   76 |   77 |   78 |   79 |   80 |   81 |   82 |   83 |   84 |  
module.exports = {
 85 |     user:{
 86 |         name: {type: String},
 87 |         password: {type: String}
 88 |     },
 89 |     commodity: {
 90 |         name: String,
 91 |         price: Number,
 92 |         imgSrc: String
 93 |     },
 94 |     cart: {
 95 |         uId: {type: String},
 96 |         cId: {type: String},
 97 |         cName: { type: String},
 98 |         cPrice: { type: String},
 99 |         cImaSrc: { type: String},
100 |         cQuantity: { type: Number},
101 |         cStatus: { type: Boolean,default: false} 
102 |     }
103 | }
105 |
106 |
107 | 111 | 112 | 113 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/config/multerUtil.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\config\multerUtil.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/config/ multerUtil.js 20 |

21 |
22 |
23 | 57.14% 24 | Statements 25 | 4/7 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 0% 34 | Functions 35 | 0/2 36 |
37 |
38 | 57.14% 39 | Lines 40 | 4/7 41 |
42 |
43 |
44 |
45 |

 46 | 
 98 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 65 |   66 |   67 |   68 |   69 |   70 |   71 |   72 |   73 |   74 |   75 |   76 | 77 |   78 |   79 |   80 |
var multer = require('multer');
 81 | var storage = multer.diskStorage({
 82 |     //设置上传后文件路径,uploads文件夹会自动创建。
 83 |     destination: function (req, file, cb) {
 84 |         cb(null, '../public/images')
 85 |     },
 86 |     //给上传文件重命名,获取添加后缀名
 87 |     filename: function (req, file, cb) {
 88 |         var fileFormat = (file.originalname).split(".");
 89 |         cb(null, file.fieldname + '-' + Date.now() + "." + fileFormat[fileFormat.length - 1]);
 90 |     }
 91 | });
 92 | //添加配置文件到muler对象。
 93 | var upload = multer({
 94 |     storage: storage
 95 | });
 96 |  
 97 | module.exports = upload;
99 |
100 |
101 | 105 | 106 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/middlewares/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\middlewares\ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files mysession/middlewares/ 20 |

21 |
22 |
23 | 25% 24 | Statements 25 | 4/16 26 |
27 |
28 | 0% 29 | Branches 30 | 0/6 31 |
32 |
33 | 0% 34 | Functions 35 | 0/4 36 |
37 |
38 | 25% 39 | Lines 40 | 4/16 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 |
FileStatementsBranchesFunctionsLines
check.js
11.11%1/90%0/40%0/211.11%1/9
testController.js
42.86%3/70%0/20%0/242.86%3/7
89 |
90 |
91 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/middlewares/check.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\middlewares\check.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/middlewares/ check.js 20 |

21 |
22 |
23 | 11.11% 24 | Statements 25 | 1/9 26 |
27 |
28 | 0% 29 | Branches 30 | 0/4 31 |
32 |
33 | 0% 34 | Functions 35 | 0/2 36 |
37 |
38 | 11.11% 39 | Lines 40 | 1/9 41 |
42 |
43 |
44 |
45 |

 46 | 
 98 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 |   65 |   66 |   67 |   68 |   69 |   70 |   71 |   72 |   73 |   74 |   75 |   76 |   77 |   78 |   79 |   80 |  
module.exports = {
 81 |     checkLogin: function checkLogin(req, res, next) {
 82 |         if (!req.session.user) {
 83 |             req.session.error = "请先登录";
 84 |             res.redirect("/login");
 85 |         }
 86 |         next();
 87 |     },
 88 |  
 89 |     checkNotLogin: function checkNotLogin(req, res, next) {
 90 |         if (req.session.user) {
 91 |             req.session.error = "已登录";
 92 |             return res.redirect('back');//返回之前的页面
 93 |         }
 94 |         next();
 95 |     }
 96 | };
 97 |  
99 |
100 |
101 | 105 | 106 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/common/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\common\ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files mysession/common/ 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 8/8 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 8/8 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 |
FileStatementsBranchesFunctionsLines
dbHelper.js
100%7/7100%0/0100%2/2100%7/7
models.js
100%1/1100%0/0100%0/0100%1/1
89 |
90 |
91 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/config/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\config\ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files mysession/config/ 20 |

21 |
22 |
23 | 62.5% 24 | Statements 25 | 5/8 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 0% 34 | Functions 35 | 0/2 36 |
37 |
38 | 62.5% 39 | Lines 40 | 5/8 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 |
FileStatementsBranchesFunctionsLines
default.js
100%1/1100%0/0100%0/0100%1/1
multerUtil.js
57.14%4/7100%0/00%0/257.14%4/7
89 |
90 |
91 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /book/4.8 登出与登录.md: -------------------------------------------------------------------------------- 1 | ## 4.8.1 登出 2 | 3 | 现在我们来完成登出的功能。修改 routes/signout.js 如下: 4 | 5 | **routes/logout.js** 6 | 7 | ``` 8 | module.exports = function ( app ) { 9 | //退出登录 10 | app.get('/logout', function(req, res){ 11 | req.session.user = null; 12 | req.session.error = null; 13 | res.redirect('/home'); 14 | }); 15 | } 16 | ``` 17 | 18 | 此时点击右上角的 `退 出`,成功后如下图所示: 19 | 20 | ![](./img/4.8.1.png) 21 | 22 | ## 4.8.2 登录页 23 | 24 | 现在我们来完成登录页。修改 routes/signin.js 相应代码如下: 25 | 26 | **routes/login.js** 27 | 28 | ``` 29 | //登录页面 30 | app.get('/login',function(req,res){ 31 | res.render('login'); 32 | }); 33 | ``` 34 | 35 | 新建 views/login.html,添加如下代码: 36 | 37 | **views/login.html** 38 | 39 | ``` 40 | 41 | 42 | 43 | 用户登录 44 | 45 | 46 | 47 | 48 | 61 | 82 | 83 | 84 |
85 | <%- message %> 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 | 112 | ``` 113 | 114 | 现在访问 `localhost:3000/login` 试试吧。 115 | 116 | ## 4.8.3 登录 117 | 118 | 现在我们来完成登录的功能。修改 修改 routes/login.js 如下: 119 | 120 | **routes/login.js** 121 | 122 | ``` 123 | var sha1 = require('sha1'); 124 | module.exports = function(app){ 125 | //登录页面 126 | app.get('/login',function(req,res){ 127 | res.render('login'); 128 | }); 129 | //登录 130 | app.post('/login',function(req,res){ 131 | var User = global.dbHelper.getModel('user'), 132 | uname = req.body.uname; 133 | //查询数据库 134 | User.findOne({name: uname}, function(error,doc){ 135 | if(error){ 136 | res.send(500); 137 | console.log(error); 138 | }else if(!doc){ 139 | req.session.error = "用户名不存在!"; 140 | res.send(404); 141 | }else{ 142 | if(sha1(req.body.upwd) !== doc.password ){ 143 | req.session.error = "密码错误!"; 144 | res.send(404); 145 | }else{ 146 | req.session.user = doc; 147 | res.send(200); 148 | } 149 | } 150 | }); 151 | }); 152 | } 153 | ``` 154 | 155 | 现在访问 `localhost:3000/login`,用刚才注册的账号登录,如下图所示: 156 | 157 | ![](./img/4.8.3.png) 158 | 159 | 上一节:[4.7 注册](https://github.com/18820227745/shop-demo-node/blob/master/book/4.7%20%E6%B3%A8%E5%86%8C.md) 160 | 161 | 下一节:[4.9 添加商品](https://github.com/18820227745/shop-demo-node/blob/master/book/4.9%20%E6%B7%BB%E5%8A%A0%E5%95%86%E5%93%81.md) -------------------------------------------------------------------------------- /book/4.13 部署.md: -------------------------------------------------------------------------------- 1 | ## 4.13.1 申请 MLab 2 | 3 | [MLab](https://mlab.com) (前身是 MongoLab) 是一个 mongodb 云数据库提供商,我们可以选择 500MB 空间的免费套餐用来测试。注册成功后,点击右上角的 `Create New` 创建一个数据库(如: myblog),成功后点击进入到该数据库详情页,注意页面中有一行黄色的警告: 4 | 5 | ``` 6 | A database user is required to connect to this database. To create one now, visit the 'Users' tab and click the 'Add database user' button. 7 | ``` 8 | 9 | 每个数据库至少需要一个 user,所以我们点击 Users 下的 `Add database user` 创建一个用户。 10 | 11 | > 注意:不要选中 `Make read-only`,因为我们有写数据库的操作。 12 | 13 | 最后分配给我们的类似下面的 mongodb url: 14 | 15 | ``` 16 | mongodb://:@ds139327.mlab.com:39327/myblog 17 | ``` 18 | 19 | 如我创建的用户名和密码都为 myblog 的用户,新建 config/production.js,添加如下代码: 20 | 21 | **config/production.js** 22 | 23 | ``` 24 | module.exports = { 25 | mongodb: 'mongodb://myblog:myblog@ds139327.mlab.com:39327/myblog' 26 | }; 27 | ``` 28 | 29 | 停止程序,然后以 production 配置启动程序: 30 | 31 | ``` 32 | NODE_ENV=production supervisor --harmony index 33 | ``` 34 | 35 | > 注意:Windows 用户全局安装 [cross-env](https://www.npmjs.com/package/cross-env),使用: 36 | > ``` 37 | > cross-env NODE_ENV=production supervisor --harmony index 38 | > ``` 39 | 40 | ## 4.13.2 pm2 41 | 42 | 当我们的博客要部署到线上服务器时,不能单纯的靠 `node index` 或者 `supervisor index` 来启动了,因为我们断掉 SSH 连接后服务就终止了,这时我们就需要像 [pm2](https://www.npmjs.com/package/pm2) 或者 [forever](https://www.npmjs.com/package/forever) 这样的进程管理器了。pm2 是 Node.js 下的生产环境进程管理工具,就是我们常说的进程守护工具,可以用来在生产环境中进行自动重启、日志记录、错误预警等等。以 pm2 为例,全局安装 pm2: 43 | 44 | ``` 45 | npm install pm2 -g 46 | ``` 47 | 48 | 修改 package.json,添加 start 的命令: 49 | 50 | **package.json** 51 | 52 | ``` 53 | "scripts": { 54 | "test": "node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha", 55 | "start": "NODE_ENV=production pm2 start index.js --node-args='--harmony' --name 'myblog'" 56 | } 57 | ``` 58 | 59 | 然后运行 `npm start` 通过 pm2 启动程序,如下图所示 : 60 | 61 | ![](./img/4.15.1.png) 62 | 63 | pm2 常用命令: 64 | 65 | 1. `pm2 start/stop`: 启动/停止程序 66 | 2. `pm2 reload/restart [id|name]`: 重启程序 67 | 3. `pm2 logs [id|name]`: 查看日志 68 | 4. `pm2 l/list`: 列出程序列表 69 | 70 | 更多命令请使用 `pm2 -h` 查看。 71 | 72 | ## 4.13.2 部署到 Heroku 73 | 74 | [Heroku](https://www.heroku.com) 是一个支持多种编程语言的云服务平台,Heroku 也提供免费的基础套餐供开发者测试使用。现在,我们将论坛部署到 Heroku。 75 | 76 | > 注意:新版 heroku 会有填写信用卡的步骤,如果没有请跳过本节。 77 | 78 | 首先,需要到 [https://toolbelt.heroku.com/](https://toolbelt.heroku.com/) 下载安装 Heroku 的命令行工具包 toolbelt。然后登录(如果没有账号,请注册)到 Heroku 的 Dashboard,点击右上角 New -> Create New App 创建一个应用。创建成功后运行: 79 | 80 | ``` 81 | $ heroku login 82 | ``` 83 | 84 | 填写正确的 email 和 password 验证通过后,本地会产生一个 SSH public key。在部署到 Heroku 之前,我们需要对代码进行简单的修改。如下: 85 | 86 | 1.删掉 .gitignore 中: 87 | ``` 88 | config/* 89 | !config/default.* 90 | ``` 91 | 因为我们无法登录到 Heroku 主机创建 production 配置文件,所以这里将 production 配置也上传到 Heroku。 92 | 93 | 2.打开 index.js,将 `app.listen` 修改为: 94 | ``` 95 | const port = process.env.PORT || config.port; 96 | app.listen(port, function () { 97 | console.log(`${pkg.name} listening on port ${port}`); 98 | }); 99 | ``` 100 | 因为 Heroku 会动态分配端口,所以不能用配置文件里写死的端口。 101 | 102 | 3.修改 package.json,在 scripts 添加: 103 | 104 | ``` 105 | "heroku": "NODE_ENV=production node --harmony index" 106 | ``` 107 | 108 | 在根目录下新建 Procfile 文件,添加如下内容: 109 | ``` 110 | web: npm run heroku 111 | ``` 112 | Procfile 文件告诉 Heroku 该使用什么命令启动一个 web 服务。更多见:[https://devcenter.heroku.com/articles/getting-started-with-nodejs](https://devcenter.heroku.com/articles/getting-started-with-nodejs)。 113 | 114 | 然后输入以下命令: 115 | 116 | ``` 117 | $ git init 118 | $ heroku git:remote -a 你的应用名称 119 | $ git add . 120 | $ git commit -am "first blood" 121 | $ git push heroku master 122 | ``` 123 | 124 | 稍后,我们的论坛就部署成功了。使用: 125 | 126 | ``` 127 | heroku open 128 | ``` 129 | 130 | 打开应用主页。如果出现 "Application error",使用: 131 | 132 | ``` 133 | heroku logs 134 | ``` 135 | 查看日志,调试完后 push 到 heroku重新部署。 136 | 137 | ## 4.13.3 部署到 UCloud 138 | 139 | [UCloud](https://www.ucloud.cn/) 是国内的一家云计算服务商,接下来我们尝试将博客搭在 UCloud 上。 140 | 141 | #### 创建主机 142 | 143 | 1. 注册 UCloud 144 | 2. 点击左侧的 `云主机`,然后点击 `创建主机`,统统选择最低配置 145 | 3. 右侧付费方式选择 `按时`(每小时),点击 `立即购买` 146 | 4. 在支付确认页面,点击 `确认支付` 147 | 148 | 购买成功后回到主机管理列表,如下所示: 149 | 150 | ![](./img/4.13.2.png) 151 | 152 | > 注意:下面所有的 ip 都替换为你自己的外网 ip。 153 | 154 | #### 环境搭建与部署 155 | 156 | 修改 config/production.js,将 port 修改为 80 端口: 157 | 158 | **config/production.js** 159 | 160 | ``` 161 | module.exports = { 162 | port: 80, 163 | mongodb: 'mongodb://myblog:myblog@ds139327.mlab.com:39327/myblog' 164 | }; 165 | ``` 166 | 167 | 登录主机,用刚才设置的密码: 168 | 169 | ``` 170 | ssh root@106.75.47.229 171 | ``` 172 | 173 | 因为是 CentOS 系统,所以我选择使用 yum 安装,而不是下载源码编译安装: 174 | 175 | ``` 176 | yum install git #安装git 177 | yum install nodejs #安装 Node.js 178 | yum install npm #安装 npm 179 | 180 | npm i npm -g #升级 npm 181 | npm i pm2 -g #安装 pm2 182 | npm i n -g #安装 n 183 | n v6.9.1 #安装 v6.9.1 版本的 Node.js 184 | n use 6.9.1 #使用 v6.9.1 版本的 Node.js 185 | node -v 186 | ``` 187 | > 注意:如果 `node -v` 显示的不是 6.9.1,则断开 ssh,重新登录主机再试试。 188 | 189 | 此时应该在 `/root` 目录下,运行以下命令: 190 | ``` 191 | git clone https://github.com/nswbmw/N-blog.git myblog #或在本机 myblog 目录下运行 rsync -av --exclude="node_modules" ./ root@106.75.47.229:/root/myblog 192 | cd myblog 193 | npm i 194 | npm start 195 | pm2 logs 196 | ``` 197 | > 注意:如果不想用 git 的形式将代码拉到云主机上,可以用 rsync 将本地的代码同步到你的 UCloud 主机上,如上所示。 198 | 199 | 最后,访问你的公网 ip 地址试试吧,如下所示: 200 | 201 | ![](./img/4.15.3.png) 202 | 203 | > 小提示:绑定域名不在本节讲解范围,读者可自行尝试。 204 | 205 | > 小提示:因为我们选择的按时付费套餐,测试完成后,可在主机管理页面选择关闭主机,节约费用。 206 | 207 | 上一节:[4.12 测试](https://github.com/18820227745/shop-demo-node/blob/master/book/4.12%20%E6%B5%8B%E8%AF%95.md) 208 | -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /book/4.12 测试.md: -------------------------------------------------------------------------------- 1 | ## 4.12.1 mocha 和 supertest 2 | 3 | [mocha](https://www.npmjs.com/package/mocha) 和 [suptertest](https://www.npmjs.com/package/supertest) 是常用的测试组合,通常用来测试 restful 的 api 接口,这里我们也可以用来测试我们的博客应用。在 myblog 下新建 test 文件夹存放测试文件,以注册为例讲解 mocha 和 supertest 的用法。首先安装所需模块: 4 | 5 | ``` 6 | npm i mocha supertest --save-dev 7 | ``` 8 | >提示:产品模式用dependencies(--save),开发模式用devDependencies(--save-dev) 9 | 10 | 11 | 修改 package.json,将: 12 | 13 | **package.json** 14 | 15 | ``` 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | } 19 | ``` 20 | 21 | 修改为: 22 | 23 | ``` 24 | "scripts": { 25 | "test": "mocha --harmony test" 26 | } 27 | ``` 28 | 29 | 指定执行 test 目录的测试。修改 app.js,将: 30 | 31 | **index.js** 32 | 33 | ``` 34 | // 监听端口,启动程序 35 | app.listen(config.port, function () { 36 | console.log(`${pkg.name} listening on port ${config.port}`); 37 | }); 38 | ``` 39 | 40 | 修改为: 41 | 42 | ``` 43 | if (module.parent) { 44 | module.exports = app; 45 | } else { 46 | // 监听端口,启动程序 47 | app.listen(config.port, function () { 48 | console.log(`${pkg.name} listening on port ${config.port}`); 49 | }); 50 | } 51 | ``` 52 | 53 | 这样做可以实现:直接启动 app.js 则会监听端口启动程序,如果 app.js 被 require 了,则导出 app,通常用于测试。 54 | 55 | 新建 test/register.js,添加如下测试代码: 56 | 57 | **test/register.js** 58 | 59 | ``` 60 | var path = require('path'); 61 | var assert = require('assert'); 62 | var request = require('supertest'); 63 | var sha1 = require('sha1'); 64 | var app = require('../app'); 65 | var User = global.dbHelper.getModel('user'); 66 | 67 | var testName1 = 'testName2'; 68 | var testName2 = 'nswbmw2'; 69 | describe('login', function () { 70 | describe('POST /register', function () { 71 | var agent = request.agent(app);//persist cookie when redirect 72 | beforeEach(function (done) { 73 | // 创建一个用户 74 | User.create({ 75 | name: testName1, 76 | password: "123456" 77 | }, function (error, doc) { 78 | if (error) { 79 | done; 80 | console.log(error); 81 | } else { 82 | done(); 83 | console.log("Successfully created user"); 84 | } 85 | }); 86 | 87 | }); 88 | 89 | afterEach(function (done) { 90 | // 删除测试用户 91 | User.remove({ name: { $in: [testName1, testName2] } }, function (error) { 92 | if (error) { 93 | done; 94 | console.log(error); 95 | } else { 96 | done(); 97 | console.log("Delete success! "); 98 | } 99 | }); 100 | }); 101 | 102 | // 名户名错误的情况 103 | it('wrong name', function (done) { 104 | agent 105 | .post('/register') 106 | .type('form') 107 | .field({ username: '666', password: '666', confirmpwd: '666' }) 108 | .redirects() 109 | .end(function (err, res) { 110 | if (err) return done(err); 111 | assert(res.text.match(/名字请限制在 1-10 个字符/)); 112 | done(); 113 | }); 114 | }); 115 | 116 | // 其余的参数测试自行补充 117 | // 用户名被占用的情况 118 | // it('duplicate name', function (done) { 119 | // agent 120 | // .post('/register') 121 | // .type('form') 122 | // .field({"username":testName1,password:'123456',confirmpwd:'123456'}) 123 | // .redirects() 124 | // .end(function (err, res) { 125 | // if (err) return done(err); 126 | // assert(res.text.match(/用户已存在/)); 127 | // done(); 128 | // }); 129 | // }); 130 | 131 | // 注册成功的情况 132 | // it('success', function (done) { 133 | // agent 134 | // .post('/signup') 135 | // .type('form') 136 | // .field({"username":testName2,password:'123456',confirmpwd:'123456'}) 137 | // .redirects() 138 | // .end(function (err, res) { 139 | // if (err) return done(err); 140 | 141 | // assert(res.text.match(/用户创建成功/)); 142 | // done(); 143 | // }); 144 | // }); 145 | }); 146 | }); 147 | ``` 148 | 149 | 运行 `npm test` 看看效果吧,其余的测试请读者自行完成。 150 | 151 | ## 4.12.2 测试覆盖率 152 | 153 | 我们写测试肯定想覆盖所有的情况(包括各种出错的情况及正确时的情况),但光靠想需要写哪些测试是不行的,总也会有疏漏,最简单的办法就是可以直观的看出测试是否覆盖了所有的代码,这就是测试覆盖率,即被测试覆盖到的代码行数占总代码行数的比例。 154 | 155 | > 注意:即使测试覆盖率达到 100% 也不能说明你的测试覆盖了所有的情况,只能说明基本覆盖了所有的情况。 156 | 157 | [istanbul](https://www.npmjs.com/package/istanbul) 是一个常用的生成测试覆盖率的库,它会将测试的结果报告生成 html 页面,并放到项目根目录的 coverage 目录下。首先安装 istanbul: 158 | 159 | ``` 160 | npm i istanbul --save-dev 161 | ``` 162 | 163 | 配置 istanbul 很简单,将 package.json 中: 164 | 165 | **package.json** 166 | 167 | ``` 168 | "scripts": { 169 | "test": "mocha --harmony test" 170 | } 171 | ``` 172 | 173 | 修改为: 174 | 175 | ``` 176 | "scripts": { 177 | "test": "node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha" 178 | } 179 | ``` 180 | 181 | > 注意:如果 Windows 下报错,尝试修改为: 182 | > ``` 183 | > "scripts": { 184 | > "test": "node --harmony ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha" 185 | > } 186 | > ``` 187 | > 见 [#201](https://github.com/nswbmw/N-blog/issues/201). 188 | 189 | 即可将 mocha 和 istanbul 结合使用,终端会打印: 190 | 191 | ![](./img/4.14.1.png) 192 | 193 | 打开 coverage/Icov-report/index.html,如下所示: 194 | 195 | ![](./img/4.14.2.png) 196 | 197 | 可以点进去查看某个代码文件具体的覆盖率,如下所示: 198 | 199 | ![](./img/4.14.3.png) 200 | 201 | 红色的行表示测试没有覆盖到,因为我们只写了 name 和 gender 的测试。 202 | 203 | 上一节:[4.11 日志](https://github.com/18820227745/shop-demo-node/blob/master/book/4.11%20%E6%97%A5%E5%BF%97.md) 204 | 205 | 下一节:[4.13 部署](https://github.com/18820227745/shop-demo-node/blob/master/book/4.13%20%E9%83%A8%E7%BD%B2.md) 206 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* dark red */ 156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 157 | .low .chart { border:1px solid #C21F39 } 158 | /* medium red */ 159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 160 | /* light red */ 161 | .low, .cline-no { background:#FCE1E5 } 162 | /* light green */ 163 | .high, .cline-yes { background:rgb(230,245,208) } 164 | /* medium green */ 165 | .cstat-yes { background:rgb(161,215,106) } 166 | /* dark green */ 167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 168 | .high .chart { border:1px solid rgb(77,146,33) } 169 | /* dark yellow (gold) */ 170 | .medium .chart { border:1px solid #f9cd0b; } 171 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 172 | /* light yellow */ 173 | .medium { background: #fff4c2; } 174 | /* light gray */ 175 | span.cline-neutral { background: #eaeaea; } 176 | 177 | .cbranch-no { background: yellow !important; color: #111; } 178 | 179 | .cstat-skip { background: #ddd; color: #111; } 180 | .fstat-skip { background: #ddd; color: #111 !important; } 181 | .cbranch-skip { background: #ddd !important; color: #111; } 182 | 183 | 184 | .cover-fill, .cover-empty { 185 | display:inline-block; 186 | height: 12px; 187 | } 188 | .chart { 189 | line-height: 0; 190 | } 191 | .cover-empty { 192 | background: white; 193 | } 194 | .cover-full { 195 | border-right: none !important; 196 | } 197 | pre.prettyprint { 198 | border: none !important; 199 | padding: 0 !important; 200 | margin: 0 !important; 201 | } 202 | .com { color: #999 !important; } 203 | .ignore-none { color: #999; font-weight: normal; } 204 | 205 | .wrapper { 206 | min-height: 100%; 207 | height: auto !important; 208 | height: 100%; 209 | margin: 0 auto -48px; 210 | } 211 | .footer, .push { 212 | height: 48px; 213 | } 214 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/routes/login.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\routes\login.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/routes/ login.js 20 |

21 |
22 |
23 | 22.22% 24 | Statements 25 | 4/18 26 |
27 |
28 | 0% 29 | Branches 30 | 0/6 31 |
32 |
33 | 25% 34 | Functions 35 | 1/4 36 |
37 |
38 | 22.22% 39 | Lines 40 | 4/18 41 |
42 |
43 |
44 |
45 |

 46 | 
134 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 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 |  
var sha1 = require('sha1');
105 | module.exports = function(app){
106 |     //登录页面
107 |     app.get('/login',function(req,res){
108 |         res.render('login');
109 |     });
110 |     //登录
111 |     app.post('/login',function(req,res){
112 |         var User = global.dbHelper.getModel('user'),
113 |             uname = req.body.uname;
114 |         //查询数据库
115 |         User.findOne({name: uname}, function(error,doc){
116 |             if(error){
117 |                 res.send(500);
118 |                 console.log(error);
119 |             }else if(!doc){
120 |                 req.session.error = "用户名不存在!";
121 |                 res.send(404);
122 |             }else{
123 |                 if(req.body.upwd !== doc.password ){
124 |                     req.session.error = "密码错误!";
125 |                     res.send(404);
126 |                 }else{
127 |                     req.session.user = doc;
128 |                     res.send(200);
129 |                 }
130 |             }
131 |         });
132 |     });
133 | }
135 |
136 |
137 | 141 | 142 | 143 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /book/4.4 功能设计.md: -------------------------------------------------------------------------------- 1 | ## 4.4.1 功能与路由设计 2 | 3 | 在开发项目之前,我们首先需要明确电商网站要实现哪些功能。由于本教程面向初学者,所以只实现了电商最基本的功能,其余的功能读者可自行实现。 4 | 5 | 功能及路由设计如下: 6 | require('./login')(app); 7 | require('./home')(app); 8 | require('./logout')(app); 9 | require('./register')(app); 10 | require('./cart')(app); 11 | 1. 注册 12 | 1. 注册页:`GET /register` 13 | 2. 注册(包含上传头像):`POST /register` 14 | 2. 登录 15 | 1. 登录页:`GET /login` 16 | 2. 登录:`POST /login` 17 | 3. 登出:`GET /logout` 18 | 4. 商品 19 | 1. 查看商品页:`GET /home` 20 | 2. 添加商品页:`GET /addcommodity` 21 | 3. 添加商品:`POST /addcommodity` 22 | 5. 购物车 23 | 1. 购物车商品页:`GET /cart` 24 | 2. 添加购物车商品页:`GET /addToCart/:id` 25 | 3. 删除购物车商品: `GET /removeToCart/:id` 26 | 4. 购物车结算:`GET /cart/clearing` 27 | 28 | 由于我们博客页面是后端渲染的,所以只通过简单的 `(GET)` 和 `
(jQuery)` 与后端进行交互,如果使用其他前端框架(如 angular、vue、react 等等)可通过 Ajax 与后端交互,则 api 的设计应尽量遵循 restful 风格。 29 | 30 | #### restful 31 | 32 | restful 是一种 api 的设计风格,提出了一组 api 的设计原则和约束条件。 33 | 34 | 如上面删除购物车商品的路由设计: 35 | 36 | ``` 37 | GET /removeToCart/:id 38 | ``` 39 | 40 | restful 风格的设计: 41 | 42 | ``` 43 | DELETE /removeToCart/:id 44 | ``` 45 | 46 | 可以看出,restful 风格的 api 更直观且优雅。 47 | 48 | 更多阅读: 49 | 50 | 1. http://www.ruanyifeng.com/blog/2011/09/restful 51 | 2. http://www.ruanyifeng.com/blog/2014/05/restful_api.html 52 | 3. http://developer.51cto.com/art/200908/141825.htm 53 | 4. http://blog.jobbole.com/41233/ 54 | 55 | ## 4.4.2 会话 56 | 57 | 由于 HTTP 协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是会话(Session)。关于 Session 的讲解网上有许多资料,这里不再赘述。参考: 58 | 59 | 1. http://justsee.iteye.com/blog/1570652 60 | 2. https://www.zhihu.com/question/19786827 61 | 62 | #### cookie 与 session 的区别 63 | 64 | 1. cookie 存储在浏览器(有大小限制),session 存储在服务端(没有大小限制) 65 | 2. 通常 session 的实现是基于 cookie 的,即 session id 存储于 cookie 中 66 | 67 | 我们通过引入 express-session 中间件实现对会话的支持: 68 | 69 | ``` 70 | app.use(session(options)) 71 | ``` 72 | 73 | session 中间件会在 req 上添加 session 对象,即 req.session 初始值为 `{}`,当我们登录后设置 `req.session.user = 用户信息`,返回浏览器的头信息中会带上 `set-cookie` 将 session id 写到浏览器 cookie 中,那么该用户下次请求时,通过带上来的 cookie 中的 session id 我们就可以查找到该用户,并将用户信息保存到 `req.session.user`。 74 | 75 | ## 4.4.3 页面通知 76 | 77 | 我们还需要这样一个功能:当我们操作成功时需要显示一个成功的通知,如登录成功跳转到主页;当我们操作失败时需要显示一个失败的通知,如注册时用户名被占用了,需要显示一个 `用户名已存在!` 的通知。 78 | 79 | ## 4.4.4 权限控制 80 | 81 | 电商购物网站,我们没有登录的话只能浏览商品,登陆后才能查看购物车、购买商品,,这就是权限控制。我们也来给博客添加权限控制,如何实现页面的权限控制呢?我们可以把用户状态的检查封装成一个中间件,在每个需要权限控制的路由加载该中间件,即可实现页面的权限控制。在 myblog 下新建 middlewares 文件夹,在该目录下新建 check.js,添加如下代码: 82 | 83 | 84 | **middlewares/check.js** 85 | 86 | ``` 87 | module.exports = { 88 | checkLogin: function checkLogin(req, res, next) { 89 | if (!req.session.user) { 90 | req.session.error = "请先登录"; 91 | res.redirect("/login"); 92 | } 93 | next(); 94 | }, 95 | checkNotLogin: function checkNotLogin(req, res, next) { 96 | if (req.session.user) { 97 | req.session.error = "已登录"; 98 | return res.redirect('back');//返回之前的页面 99 | } 100 | next(); 101 | } 102 | }; 103 | ``` 104 | 105 | 可以看出: 106 | 107 | 1. `checkLogin`: 当用户信息(`req.session.user`)不存在,即认为用户没有登录,则跳转到登录页,同时显示 `请先登录` 的通知,用于需要用户登录才能操作的页面及接口 108 | 2. `checkNotLogin`: 当用户信息(`req.session.user`)存在,即认为用户已经登录,则跳转到之前的页面,同时显示 `已登录` 的通知,如登录、注册页面及登录、注册的接口 109 | 110 | ## 4.4.5 接口介绍 111 | 112 | 最终我们创建以下路由文件: 113 | 114 | **routes/index.js** 115 | 116 | ``` 117 | module.exports = function(app){ 118 | require('./login')(app); 119 | require('./home')(app); 120 | require('./logout')(app); 121 | require('./register')(app); 122 | require('./cart')(app); 123 | } 124 | ``` 125 | 126 | **routes/cart.js** 127 | 128 | ``` 129 | //查看购物车商品 130 | app.get('/cart', function(req, res) 131 | //添加购物车商品 132 | app.get("/addToCart/:id", function(req, res) 133 | //删除购物车商品 134 | app.get("/delFromCart/:id", function(req, res) 135 | //购物车结算 136 | app.post("/cart/clearing",function(req,res) 137 | 138 | ``` 139 | 140 | **routes/home.js** 141 | 142 | ``` 143 | //home页面为登录时可以直接浏览 144 | app.get('/home',function(req,res) 145 | //添加商品页 146 | app.get('/addcommodity',checkLogin,function(req,res) 147 | //添加商品 148 | app.post('/addcommodity',checkLogin, function(req,res) 149 | 150 | ``` 151 | 152 | **routes/login.js** 153 | 154 | ``` 155 | //登录页面 156 | app.get('/login',function(req,res) 157 | //登录 158 | app.post('/login',function(req,res) 159 | 160 | ``` 161 | 162 | **routes/logout.js** 163 | 164 | ``` 165 | //退出登录 166 | app.get('/logout', function(req, res) 167 | 168 | ``` 169 | 170 | 最后, 修改 index.js 如下: 171 | 172 | **app.js** 173 | 174 | ``` 175 | var express = require("express"); 176 | var app = express(); 177 | var path = require("path"); 178 | var mongoose = require("mongoose"); 179 | var config = require('config-lite')(__dirname); 180 | var bodyParser = require("body-parser"); 181 | var multer = require("multer"); 182 | var session = require("express-session"); 183 | 184 | global.dbHelper = require('./common/dbHelper'); 185 | 186 | global.db = mongoose.connect(config.mongodb); 187 | 188 | app.use(session({ 189 | secret: config.session.secret, 190 | key: config.session.key, 191 | cookie: { 192 | maxAge: config.session.maxAge 193 | } 194 | })); 195 | 196 | // 设定views变量,意为视图存放的目录 197 | app.set("views", path.join(__dirname, 'views')); 198 | 199 | 200 | // 设定view engine变量, 意为网页模板引擎 201 | //app.set("view engine",'ejs');//ejs:可以直接嵌入变量<%= title %> 202 | app.set("view engine", 'html'); 203 | app.engine('.html', require('ejs').__express); 204 | 205 | // 设施bodyParser模块,是项目中可以直接引用req.body.XXXX 206 | app.use(bodyParser.json()); 207 | app.use(bodyParser.urlencoded({ extended: true })); 208 | app.use(multer()); 209 | 210 | // 设定静态文件目录,比如本地文件 211 | app.use(express.static(path.join(__dirname, 'public'))); 212 | 213 | //全局中间件,每个路由处理都会先执行这段代码 214 | app.use(function (req, res, next) { 215 | res.locals.user = req.session.user; 216 | var err = req.session.error; 217 | res.locals.message = ''; 218 | if (err) res.locals.message = '
' + err + '
'; 219 | next(); 220 | }); 221 | 222 | require('./routes')(app); 223 | 224 | app.get('/', function (req, res) { 225 | res.render("login"); 226 | }); 227 | 228 | app.listen(config.port); 229 | 230 | 231 | ``` 232 | 233 | > 注意:中间件的加载顺序很重要。如上面设置静态文件目录的中间件应该放到 routes(app) 之前加载,这样静态文件的请求就不会落到业务逻辑的路由里。 234 | 235 | 运行 `node app.js` 启动博客,访问以下地址查看效果: 236 | 237 | 1. http://localhost:3000/login 238 | 2. http://localhost:3000/home 239 | 3. http://localhost:3000/cart 240 | 4. http://localhost:3000/register 241 | 242 | 上一节:[4.3 配置文件](https://github.com/18820227745/shop-demo-node/blob/master/book/4.3%20%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6.md) 243 | 244 | 下一节:[4.5 页面设计](https://github.com/18820227745/shop-demo-node/blob/master/book/4.5%20%E9%A1%B5%E9%9D%A2%E8%AE%BE%E8%AE%A1.md) 245 | -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | / 20 |

21 |
22 |
23 | 48.35% 24 | Statements 25 | 88/182 26 |
27 |
28 | 8.33% 29 | Branches 30 | 4/48 31 |
32 |
33 | 26.83% 34 | Functions 35 | 11/41 36 |
37 |
38 | 48.07% 39 | Lines 40 | 87/181 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 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
FileStatementsBranchesFunctionsLines
mysession/
91.43%32/3575%3/433.33%1/391.18%31/34
mysession/common/
100%8/8100%0/0100%2/2100%8/8
mysession/config/
62.5%5/8100%0/00%0/262.5%5/8
mysession/middlewares/
25%4/160%0/60%0/425%4/16
mysession/routes/
33.91%39/1152.63%1/3826.67%8/3033.91%39/115
128 |
129 |
130 |
134 | 135 | 136 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /book/4.7 注册.md: -------------------------------------------------------------------------------- 1 | ## 4.7.1 用户模型设计 2 | 3 | 我们只存储用户的名称、密码(加密后的),对应修改 `common/models.js`,添加如下代码: 4 | 5 | **common/models.js** 6 | 7 | 定义schema: 8 | ``` 9 | module.exports = { 10 | user:{ 11 | name: {type: String,required: true}, 12 | password: {type: String, required: true} 13 | }, 14 | commodity: { 15 | name: String, 16 | price: Number, 17 | imgSrc: String 18 | }, 19 | cart: { 20 | uId: {type: String}, 21 | cId: {type: String}, 22 | cName: { type: String}, 23 | cPrice: { type: String}, 24 | cImaSrc: { type: String}, 25 | cQuantity: { type: Number}, 26 | cStatus: { type: Boolean,default: false} 27 | } 28 | } 29 | ``` 30 | 31 | **common/dbHelper.js** 32 | 33 | 生成相应的model,并导出: 34 | ``` 35 | var mongoose = require('mongoose'), 36 | Schema = mongoose.Schema, 37 | models = require('./models'); 38 | 39 | for (var m in models) { 40 | mongoose.model(m, new Schema(models[m])); 41 | } 42 | module.exports = { 43 | getModel: function (type) { 44 | return _getModel(type); 45 | } 46 | }; 47 | 48 | var _getModel = function (type) { 49 | return mongoose.model(type); 50 | }; 51 | 52 | ``` 53 | 54 | 55 | > 小提示:关于 mongoose 的 schema 的用法,请查阅 [Mongoose快速入门](http://cnodejs.org/topic/595d9ad5a4de5625080fe118#595e532210d696af07768a6c)。 56 | 57 | 58 | 59 | ## 4.7.2 注册页 60 | 61 | 首先,我们来完成注册。新建 views/register.html,添加如下代码: 62 | 63 | **views/register.html** 64 | 65 | ``` 66 | 67 | 68 | 69 | 70 | 用户注册 71 | 72 | 73 | 74 | 75 | 76 | 88 | 109 | 110 | 111 |
112 | <%- message %> 113 |
114 |
115 |
116 |
117 |
用户注册
118 |
119 |
120 | 121 |
122 | 123 |
124 |
125 | 126 |
127 |
128 | 129 |
130 |
131 | 132 | 登 陆 133 |
134 | 135 |
136 |
137 |
138 |
139 |
140 | 141 | 142 | ``` 143 | 144 | > 注意:form 表单的提交时通过register()封装ajax请求。 145 | 146 | 修改 routes/register.js 中获取注册页的路由如下: 147 | 148 | **routes/signup.js** 149 | 150 | ``` 151 | var sha1 = require('sha1'); 152 | module.exports = function (app) { 153 | app.get('/register', function (req, res) { 154 | res.render("register"); 155 | }); 156 | //解析表单,并写入数据库 157 | app.post("/register", function (req, res) { 158 | var User = global.dbHelper.getModel('user'); 159 | var uname = req.body.uname; 160 | // 校验参数 161 | try { 162 | if (!(uname.length >= 1 && uname.length <= 10)) { 163 | throw new Error('名字请限制在 1-10 个字符'); 164 | } 165 | if (req.body.ucpwd !== req.body.upwd) { 166 | throw new Error('两次输入密码不一致'); 167 | } 168 | } catch (e) { 169 | // 注册失败 170 | req.session.error = e.message; 171 | return res.redirect('/register'); 172 | } 173 | User.findOne({ name: uname }, function (error, doc) { 174 | if (error) { 175 | res.send(500); 176 | req.session.error = '网络异常错误!'; 177 | console.log(error); 178 | } else if (doc) { 179 | req.session.error = "用户已存在!"; 180 | res.send(500); 181 | } else { 182 | User.create({ 183 | name: uname, 184 | password: sha1(req.body.upwd) 185 | }, function (error, doc) { 186 | if (error) { 187 | res.send(500); 188 | console.log(error); 189 | } else { 190 | req.session.error = "用户创建成功!"; 191 | res.send(200); 192 | } 193 | }); 194 | } 195 | }); 196 | }); 197 | } 198 | ``` 199 | 200 | 现在访问 `localhost:3000/register` 看看效果吧。 201 | 202 | 203 | 上一节:[4.6 连接数据库](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) 204 | 205 | 下一节:[4.8 登出与登录](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) -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:F:\session\mysession\app.js 3 | FN:47,(anonymous_1) 4 | FN:82,(anonymous_2) 5 | FN:90,(anonymous_3) 6 | FNF:3 7 | FNH:1 8 | FNDA:2,(anonymous_1) 9 | FNDA:0,(anonymous_2) 10 | FNDA:0,(anonymous_3) 11 | DA:1,1 12 | DA:2,1 13 | DA:3,1 14 | DA:4,1 15 | DA:5,1 16 | DA:6,1 17 | DA:7,1 18 | DA:8,1 19 | DA:9,1 20 | DA:10,1 21 | DA:11,1 22 | DA:12,1 23 | DA:14,1 24 | DA:16,1 25 | DA:18,1 26 | DA:28,1 27 | DA:33,1 28 | DA:34,1 29 | DA:37,1 30 | DA:38,1 31 | DA:44,1 32 | DA:47,1 33 | DA:48,2 34 | DA:49,2 35 | DA:50,2 36 | DA:51,2 37 | DA:52,2 38 | DA:67,1 39 | DA:82,1 40 | DA:83,0 41 | DA:86,1 42 | DA:87,1 43 | DA:90,0 44 | DA:91,0 45 | LF:34 46 | LH:31 47 | BRDA:51,1,0,1 48 | BRDA:51,1,1,1 49 | BRDA:86,2,0,1 50 | BRDA:86,2,1,0 51 | BRF:4 52 | BRH:3 53 | end_of_record 54 | TN: 55 | SF:F:\session\mysession\config\default.js 56 | FNF:0 57 | FNH:0 58 | DA:1,1 59 | LF:1 60 | LH:1 61 | BRF:0 62 | BRH:0 63 | end_of_record 64 | TN: 65 | SF:F:\session\mysession\common\dbHelper.js 66 | FN:9,(anonymous_1) 67 | FN:14,(anonymous_2) 68 | FNF:2 69 | FNH:2 70 | FNDA:2,(anonymous_1) 71 | FNDA:2,(anonymous_2) 72 | DA:1,1 73 | DA:5,1 74 | DA:6,3 75 | DA:8,1 76 | DA:10,2 77 | DA:14,1 78 | DA:15,2 79 | LF:7 80 | LH:7 81 | BRF:0 82 | BRH:0 83 | end_of_record 84 | TN: 85 | SF:F:\session\mysession\common\models.js 86 | FNF:0 87 | FNH:0 88 | DA:1,1 89 | LF:1 90 | LH:1 91 | BRF:0 92 | BRH:0 93 | end_of_record 94 | TN: 95 | SF:F:\session\mysession\routes\index.js 96 | FN:1,(anonymous_1) 97 | FNF:1 98 | FNH:1 99 | FNDA:1,(anonymous_1) 100 | DA:1,1 101 | DA:2,1 102 | DA:3,1 103 | DA:4,1 104 | DA:5,1 105 | DA:6,1 106 | LF:6 107 | LH:6 108 | BRF:0 109 | BRH:0 110 | end_of_record 111 | TN: 112 | SF:F:\session\mysession\routes\login.js 113 | FN:2,(anonymous_1) 114 | FN:4,(anonymous_2) 115 | FN:8,(anonymous_3) 116 | FN:12,(anonymous_4) 117 | FNF:4 118 | FNH:1 119 | FNDA:1,(anonymous_1) 120 | FNDA:0,(anonymous_2) 121 | FNDA:0,(anonymous_3) 122 | FNDA:0,(anonymous_4) 123 | DA:1,1 124 | DA:2,1 125 | DA:4,1 126 | DA:5,0 127 | DA:8,1 128 | DA:9,0 129 | DA:12,0 130 | DA:13,0 131 | DA:14,0 132 | DA:15,0 133 | DA:16,0 134 | DA:17,0 135 | DA:18,0 136 | DA:20,0 137 | DA:21,0 138 | DA:22,0 139 | DA:24,0 140 | DA:25,0 141 | LF:18 142 | LH:4 143 | BRDA:13,1,0,0 144 | BRDA:13,1,1,0 145 | BRDA:16,2,0,0 146 | BRDA:16,2,1,0 147 | BRDA:20,3,0,0 148 | BRDA:20,3,1,0 149 | BRF:6 150 | BRH:0 151 | end_of_record 152 | TN: 153 | SF:F:\session\mysession\routes\home.js 154 | FN:6,(anonymous_1) 155 | FN:8,(anonymous_2) 156 | FN:18,(anonymous_3) 157 | FN:23,(anonymous_4) 158 | FN:28,(anonymous_5) 159 | FN:35,(anonymous_6) 160 | FNF:6 161 | FNH:1 162 | FNDA:1,(anonymous_1) 163 | FNDA:0,(anonymous_2) 164 | FNDA:0,(anonymous_3) 165 | FNDA:0,(anonymous_4) 166 | FNDA:0,(anonymous_5) 167 | FNDA:0,(anonymous_6) 168 | DA:1,1 169 | DA:3,1 170 | DA:4,1 171 | DA:5,1 172 | DA:6,1 173 | DA:8,1 174 | DA:9,0 175 | DA:10,0 176 | DA:14,0 177 | DA:15,0 178 | DA:16,0 179 | DA:18,0 180 | DA:19,0 181 | DA:23,1 182 | DA:25,0 183 | DA:28,1 184 | DA:29,0 185 | DA:30,0 186 | DA:36,0 187 | DA:37,0 188 | DA:39,0 189 | LF:21 190 | LH:8 191 | BRDA:14,1,0,0 192 | BRDA:14,1,1,0 193 | BRDA:36,2,0,0 194 | BRDA:36,2,1,0 195 | BRF:4 196 | BRH:0 197 | end_of_record 198 | TN: 199 | SF:F:\session\mysession\middlewares\testController.js 200 | FN:4,(anonymous_1) 201 | FN:5,(anonymous_2) 202 | FNF:2 203 | FNH:0 204 | FNDA:0,(anonymous_1) 205 | FNDA:0,(anonymous_2) 206 | DA:1,1 207 | DA:3,1 208 | DA:4,1 209 | DA:5,0 210 | DA:7,0 211 | DA:8,0 212 | DA:11,0 213 | LF:7 214 | LH:3 215 | BRDA:7,1,0,0 216 | BRDA:7,1,1,0 217 | BRF:2 218 | BRH:0 219 | end_of_record 220 | TN: 221 | SF:F:\session\mysession\config\multerUtil.js 222 | FN:4,(anonymous_1) 223 | FN:8,(anonymous_2) 224 | FNF:2 225 | FNH:0 226 | FNDA:0,(anonymous_1) 227 | FNDA:0,(anonymous_2) 228 | DA:1,1 229 | DA:2,1 230 | DA:5,0 231 | DA:9,0 232 | DA:10,0 233 | DA:14,1 234 | DA:18,1 235 | LF:7 236 | LH:4 237 | BRF:0 238 | BRH:0 239 | end_of_record 240 | TN: 241 | SF:F:\session\mysession\middlewares\check.js 242 | FN:2,checkLogin 243 | FN:10,checkNotLogin 244 | FNF:2 245 | FNH:0 246 | FNDA:0,checkLogin 247 | FNDA:0,checkNotLogin 248 | DA:1,1 249 | DA:3,0 250 | DA:4,0 251 | DA:5,0 252 | DA:7,0 253 | DA:11,0 254 | DA:12,0 255 | DA:13,0 256 | DA:15,0 257 | LF:9 258 | LH:1 259 | BRDA:3,1,0,0 260 | BRDA:3,1,1,0 261 | BRDA:11,2,0,0 262 | BRDA:11,2,1,0 263 | BRF:4 264 | BRH:0 265 | end_of_record 266 | TN: 267 | SF:F:\session\mysession\routes\logout.js 268 | FN:1,(anonymous_1) 269 | FN:3,(anonymous_2) 270 | FNF:2 271 | FNH:1 272 | FNDA:1,(anonymous_1) 273 | FNDA:0,(anonymous_2) 274 | DA:1,1 275 | DA:3,1 276 | DA:4,0 277 | DA:5,0 278 | DA:6,0 279 | LF:5 280 | LH:2 281 | BRF:0 282 | BRH:0 283 | end_of_record 284 | TN: 285 | SF:F:\session\mysession\routes\register.js 286 | FN:2,(anonymous_1) 287 | FN:3,(anonymous_2) 288 | FN:7,(anonymous_3) 289 | FN:27,(anonymous_4) 290 | FN:39,(anonymous_5) 291 | FNF:5 292 | FNH:3 293 | FNDA:1,(anonymous_1) 294 | FNDA:1,(anonymous_2) 295 | FNDA:1,(anonymous_3) 296 | FNDA:0,(anonymous_4) 297 | FNDA:0,(anonymous_5) 298 | DA:1,1 299 | DA:2,1 300 | DA:3,1 301 | DA:4,1 302 | DA:7,1 303 | DA:8,1 304 | DA:9,1 305 | DA:10,1 306 | DA:11,1 307 | DA:14,1 308 | DA:15,1 309 | DA:16,0 310 | DA:18,0 311 | DA:19,0 312 | DA:23,1 313 | DA:24,1 314 | DA:25,1 315 | DA:27,0 316 | DA:28,0 317 | DA:29,0 318 | DA:30,0 319 | DA:31,0 320 | DA:32,0 321 | DA:33,0 322 | DA:34,0 323 | DA:36,0 324 | DA:40,0 325 | DA:41,0 326 | DA:42,0 327 | DA:44,0 328 | DA:45,0 329 | DA:46,0 330 | LF:32 331 | LH:14 332 | BRDA:15,1,0,0 333 | BRDA:15,1,1,0 334 | BRDA:15,2,0,1 335 | BRDA:15,2,1,0 336 | BRDA:18,3,0,0 337 | BRDA:18,3,1,0 338 | BRDA:28,4,0,0 339 | BRDA:28,4,1,0 340 | BRDA:32,5,0,0 341 | BRDA:32,5,1,0 342 | BRDA:40,6,0,0 343 | BRDA:40,6,1,0 344 | BRF:12 345 | BRH:1 346 | end_of_record 347 | TN: 348 | SF:F:\session\mysession\routes\cart.js 349 | FN:2,(anonymous_1) 350 | FN:4,(anonymous_2) 351 | FN:12,(anonymous_3) 352 | FN:18,(anonymous_4) 353 | FN:26,(anonymous_5) 354 | FN:29,(anonymous_6) 355 | FN:37,(anonymous_7) 356 | FN:46,(anonymous_8) 357 | FN:61,(anonymous_9) 358 | FN:64,(anonymous_10) 359 | FN:73,(anonymous_11) 360 | FN:75,(anonymous_12) 361 | FNF:12 362 | FNH:1 363 | FNDA:1,(anonymous_1) 364 | FNDA:0,(anonymous_2) 365 | FNDA:0,(anonymous_3) 366 | FNDA:0,(anonymous_4) 367 | FNDA:0,(anonymous_5) 368 | FNDA:0,(anonymous_6) 369 | FNDA:0,(anonymous_7) 370 | FNDA:0,(anonymous_8) 371 | FNDA:0,(anonymous_9) 372 | FNDA:0,(anonymous_10) 373 | FNDA:0,(anonymous_11) 374 | FNDA:0,(anonymous_12) 375 | DA:2,1 376 | DA:4,1 377 | DA:5,0 378 | DA:7,0 379 | DA:8,0 380 | DA:9,0 381 | DA:12,0 382 | DA:13,0 383 | DA:18,1 384 | DA:20,0 385 | DA:21,0 386 | DA:22,0 387 | DA:24,0 388 | DA:26,0 389 | DA:28,0 390 | DA:29,0 391 | DA:31,0 392 | DA:32,0 393 | DA:37,0 394 | DA:38,0 395 | DA:39,0 396 | DA:47,0 397 | DA:48,0 398 | DA:61,1 399 | DA:63,0 400 | DA:64,0 401 | DA:66,0 402 | DA:67,0 403 | DA:73,1 404 | DA:74,0 405 | DA:75,0 406 | DA:77,0 407 | DA:78,0 408 | LF:33 409 | LH:5 410 | BRDA:7,1,0,0 411 | BRDA:7,1,1,0 412 | BRDA:20,2,0,0 413 | BRDA:20,2,1,0 414 | BRDA:28,3,0,0 415 | BRDA:28,3,1,0 416 | BRDA:31,4,0,0 417 | BRDA:31,4,1,0 418 | BRDA:38,5,0,0 419 | BRDA:38,5,1,0 420 | BRDA:47,6,0,0 421 | BRDA:47,6,1,0 422 | BRDA:66,7,0,0 423 | BRDA:66,7,1,0 424 | BRDA:77,8,0,0 425 | BRDA:77,8,1,0 426 | BRF:16 427 | BRH:0 428 | end_of_record 429 | -------------------------------------------------------------------------------- /book/4.9 添加商品.md: -------------------------------------------------------------------------------- 1 | ## 4.9.1 商品模型设计 2 | 3 | 我们只存储商品的id、商品名称、商品价格、商品图片这几个字段,对应修改 common/models.js,添加如下代码: 4 | 5 | **common/models.js** 6 | 7 | ``` 8 | module.exports = { 9 | user:{ 10 | name: {type: String,required: true}, 11 | password: {type: String, required: true} 12 | }, 13 | commodity: { 14 | name: String, 15 | price: Number, 16 | imgSrc: String 17 | }, 18 | cart: { 19 | uId: {type: String}, 20 | cId: {type: String}, 21 | cName: { type: String}, 22 | cPrice: { type: String}, 23 | cImaSrc: { type: String}, 24 | cQuantity: { type: Number}, 25 | cStatus: { type: Boolean,default: false} 26 | } 27 | } 28 | ``` 29 | 30 | ## 4.9.2 浏览商品 31 | 32 | 无论用户登录还是未登录都能浏览所有的商品信息,现在我们来实现商品浏览功能。首先创建商品浏览页,新建views/home.html,添加如下代码 33 | 34 | 35 | **views/home.html** 36 | 37 | ``` 38 | 39 | 40 | 41 | 主页 42 | 43 | 44 | 45 | 46 | 65 | 66 | 67 |
68 |
69 |
70 |
71 | 商品展示页 72 |
73 | 添加商品 74 | 购物车 75 | ><%=login.message%> 76 | 77 |
78 |
79 |
80 |
81 |
    82 | <%for(var i in Commoditys){ if(!Commoditys[i].name)continue;%> 83 |
  • 84 |
    85 | 86 |
    87 |
    88 | <%=Commoditys[i].name%> 89 | ¥<%=Commoditys[i].price%> 90 |
    91 |
    92 | 加入购物车 93 |
    94 |
  • 95 | <%}%> 96 |
97 |
98 |
99 |
100 | 101 | 102 | ``` 103 | 104 | 105 | 修改 routes/home.js,添加获取商品信息代码: 106 | 107 | **routes/home.js** 108 | 109 | ``` 110 | //home页面为登录时可以直接浏览 111 | app.get('/home',function(req,res){ 112 | var Commodity = global.dbHelper.getModel("commodity"); 113 | var login = { 114 | href:'/login', 115 | message: '登 录' 116 | }; 117 | if (req.session.user) { 118 | login.href='/logout'; 119 | login.message='退 出'; 120 | } 121 | Commodity.find({},function(error,docs){ 122 | res.render('home',{Commoditys:docs,login:login}); 123 | }); 124 | }); 125 | ``` 126 | 127 | 现在访问 `localhost:3000/home` 查看一下现有的所有商品吧。 128 | 129 | ## 4.9.3 添加商品 130 | 131 | 用户登录后可以点击添加商品按钮进入添加商品页面。现在我们来实现添加商品的功能。 132 | 133 | 首先新建添加商品页,新建view/asscommodity.html 如下: 134 | 135 | **view/asscommodity.html** 136 | 137 | ``` 138 | 139 | 140 | 141 | 添加商品 142 | 143 | 144 | 145 | 146 | 148 | 167 | 168 | 169 |
170 |

添加商品

171 | 返回商品页 172 |
173 | 174 | 175 | 176 |
177 |
178 | 179 | 180 | ``` 181 | 182 | 修改 routes/home.js,添加如下代码: 183 | 184 | **routes/home.js** 185 | 186 | ``` 187 | //添加商品页 188 | app.get('/addcommodity',checkLogin,function(req,res){ 189 | //render可以带变量,渲染页面 190 | res.render("addcommodity"); 191 | }); 192 | //添加商品 193 | app.post('/addcommodity',checkLogin, function(req,res){ 194 | var Commodity = global.dbHelper.getModel("commodity"); 195 | Commodity.create({ 196 | name: req.body.name, 197 | price: req.body.price, 198 | imgSrc: req.body.imgSrc 199 | },function(error,doc){ 200 | if(doc){ 201 | res.send(200); 202 | }else{ 203 | res.send(404); 204 | } 205 | }) 206 | }); 207 | ``` 208 | 209 | 210 | 现在可以尝试登陆后,添加商品返回主页面查看一下所有的商品。 211 | 212 | 上一节:[4.8 登出与登录](https://github.com/18820227745/shop-demo-node/blob/master/book/4.8%20%E7%99%BB%E5%87%BA%E4%B8%8E%E7%99%BB%E5%BD%95.md) 213 | 214 | 下一节:[4.10 购物车](https://github.com/18820227745/shop-demo-node/blob/master/book/4.10%20%E8%B4%AD%E7%89%A9%E8%BD%A6.md) 215 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/routes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\routes\ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files mysession/routes/ 20 |

21 |
22 |
23 | 33.91% 24 | Statements 25 | 39/115 26 |
27 |
28 | 2.63% 29 | Branches 30 | 1/38 31 |
32 |
33 | 26.67% 34 | Functions 35 | 8/30 36 |
37 |
38 | 33.91% 39 | Lines 40 | 39/115 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 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
FileStatementsBranchesFunctionsLines
cart.js
15.15%5/330%0/168.33%1/1215.15%5/33
home.js
38.1%8/210%0/416.67%1/638.1%8/21
index.js
100%6/6100%0/0100%1/1100%6/6
login.js
22.22%4/180%0/625%1/422.22%4/18
logout.js
40%2/5100%0/050%1/240%2/5
register.js
43.75%14/328.33%1/1260%3/543.75%14/32
141 |
142 |
143 | 147 | 148 | 149 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /views/cart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 购物车 5 | 6 | 7 | 8 | 9 | 83 | 127 | 128 | 129 |
130 |
131 |
132 |
133 | 商品页 134 | 退 出 135 |
136 |

购物车

137 |
138 |
139 |
140 |
141 |
142 | 143 | 全选 144 | 145 | 商品 146 | 价格 147 | 数量 148 | 操作 149 |
150 |
151 |
152 |
    153 | <%for(var i in carts){ if(!carts[i].cId)continue%> 154 |
  • 155 |
    156 | 158 |
    159 |
    160 | 161 | 162 | 163 |
    164 |
    165 | <%=carts[i].cName%> 166 |
    167 |
    168 | <%=carts[i].cPrice%> 169 |
    170 |
    171 | + 172 | 173 | - 174 |
    175 |
    176 | 删除 177 |
    178 |
  • 179 | <%}%> 180 |
181 |
182 |
183 | 总计:¥0.00 184 | 185 |
186 |
187 |
188 |
189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/routes/home.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\routes\home.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/routes/ home.js 20 |

21 |
22 |
23 | 38.1% 24 | Statements 25 | 8/21 26 |
27 |
28 | 0% 29 | Branches 30 | 0/4 31 |
32 |
33 | 16.67% 34 | Functions 35 | 1/6 36 |
37 |
38 | 38.1% 39 | Lines 40 | 8/21 41 |
42 |
43 |
44 |
45 |

 46 | 
173 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40 86 | 41 87 | 42 88 | 43 89 |   90 | 91 | 92 | 93 | 94 |   95 | 96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 |   109 |   110 | 111 |   112 |   113 |   114 |   115 | 116 |   117 |   118 |   119 |   120 |   121 |   122 |   123 |   124 |   125 |   126 |   127 |   128 |   129 |   130 |  
 var testController=require('../middlewares/testController');
131 | //将检查登录封装成中间件
132 | var multipart = require('connect-multiparty');
133 | var multipartMiddleware = multipart({ uploadDir: '../public/' });
134 | var checkLogin = require('../middlewares/check').checkLogin;
135 | module.exports = function (app){
136 |     //home页面为登录时可以直接浏览
137 |     app.get('/home',function(req,res){
138 |             var Commodity = global.dbHelper.getModel("commodity");
139 |             var login = {
140 |                 href:'/login',
141 |                 message: '登 录'
142 |             };
143 |             if (req.session.user) {
144 |                 login.href='/logout';
145 |                 login.message='退 出';
146 |             }
147 |             Commodity.find({},function(error,docs){
148 |                 res.render('home',{Commoditys:docs,login:login});
149 |             });
150 |     });
151 |     //添加商品页
152 |     app.get('/addcommodity',checkLogin,function(req,res){
153 |         //render可以带变量,渲染页面
154 |         res.render("addcommodity");
155 |     });
156 |     //添加商品
157 |     app.post('/addcommodity',checkLogin,multipartMiddleware, function(req,res){
158 |         var Commodity = global.dbHelper.getModel("commodity");
159 |         Commodity.create({
160 |             name: req.body.name,
161 |             price: req.body.price,
162 |             price: req.body.price,
163 |             imgSrc: req.body.imgSrc
164 |         },function(error,doc){
165 |             if(doc){
166 |                 res.send(200);
167 |             }else{
168 |                 res.send(404);
169 |             }
170 |         })
171 |     });
172 | }
174 |
175 |
176 | 180 | 181 | 182 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /coverage/lcov-report/mysession/routes/register.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for mysession\routes\register.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / mysession/routes/ register.js 20 |

21 |
22 |
23 | 43.75% 24 | Statements 25 | 14/32 26 |
27 |
28 | 8.33% 29 | Branches 30 | 1/12 31 |
32 |
33 | 60% 34 | Functions 35 | 3/5 36 |
37 |
38 | 43.75% 39 | Lines 40 | 14/32 41 |
42 |
43 |
44 |
45 |

 46 | 
200 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40 86 | 41 87 | 42 88 | 43 89 | 44 90 | 45 91 | 46 92 | 47 93 | 48 94 | 49 95 | 50 96 | 51 97 | 52 98 | 99 | 100 | 101 |   102 |   103 | 104 | 105 | 106 | 107 | 108 |   109 |   110 | 111 | 112 |   113 |   114 |   115 |   116 |   117 |   118 |   119 | 120 | 121 | 122 |   123 |   124 |   125 |   126 |   127 |   128 |   129 |   130 |   131 |   132 |   133 |   134 |   135 |   136 |   137 |   138 |   139 |   140 |   141 |   142 |   143 |   144 |   145 |   146 |   147 |   148 |  
var sha1 = require('sha1');
149 | module.exports = function (app) {
150 |     app.get('/register', function (req, res) {
151 |         res.render("register");
152 |     });
153 |     //解析表单,并写入数据库
154 |     app.post("/register", function (req, res) {
155 |         var User = global.dbHelper.getModel('user');
156 |         var uname = req.body.username;
157 |         console.log("2222222222222222222222: ",req);
158 |         console.log("11111111111111",req.body);
159 |  
160 |         // 校验参数
161 |         try {
162 |             if (!(uname.length >= 1 && uname.length <= 10)) {
163 |                 throw new Error('名字请限制在 1-10 个字符');
164 |             }
165 |             if (req.body.password !== req.body.confirmpwd) {
166 |                 throw new Error('两次输入密码不一致');
167 |             }
168 |         } catch (e) {
169 |             // 注册失败
170 |             console.log("e.message_----------",e.message);
171 |             req.session.error = e.message;
172 |             return res.redirect('/register');
173 |         }
174 |         User.findOne({ name: uname }, function (error, doc) {
175 |             if (error) {
176 |                 res.send(500);
177 |                 req.session.error = '网络异常错误!';
178 |                 console.log(error);
179 |             } else if (doc) {
180 |                 req.session.error = "用户已存在!";
181 |                 res.send(500);
182 |             } else {
183 |                 User.create({
184 |                     name: uname,
185 |                     password: req.body.upwd
186 |                 }, function (error, doc) {
187 |                     if (error) {
188 |                         res.send(500);
189 |                         console.log(error);
190 |                     } else {
191 |                         req.session.error = "用户创建成功!";
192 |                         res.redirect("login");
193 |                         res.send(200);
194 |                     }
195 |                 });
196 |             }
197 |         });
198 |     });
199 | }
201 |
202 |
203 | 207 | 208 | 209 | 216 | 217 | 218 | 219 | --------------------------------------------------------------------------------