前端笔记之NodeJS(四)MongoDB数据库&Mongoose&自制接口&MVC架构思想|实战
一、MongoDB数据库
1.1 NoSQL简介
随着互联网web2.0网站的兴起,传统的SQL数据库(关系数据库)在应付web2.0网站,特别是超大规模和高并发的SNS(social network system,人人网)类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
传统的数据库MySQL、SQL Server、Oracle、Access都是SQL结构型数据库。
这些结构型数据库的特点,就是“表”有明确的“字段(列)”的概念,而且字段是固定的。如果要增加字段,所有已经存在的条目都要改变。
SQL的优点就是查询可以很复杂,比如检索所有“语文成绩大于数学成绩且英语成绩在班级前三名的女生”。
但是在现在,你会发现很多时候检索可以很简单,不需要那么复杂。比如话费详单,没有任何需求去检索:通话时长在5分钟以上且是夜间主动拨打的。
结构型数据库的缺点:如果今后需要增加字段,之前的所有的条目,也要一起增加。比如你的条目已经有2万条,此时如果突然要增加一个语文成绩字段,此时20000条已经存在的条目都要进行字段的改变,此时特别耗费时间。也就是说灵活性不够!
结构型数据库的优点:查找快、有主键的概念、从键、主从查找、映射等等的高级数据库的概念。
SQL在这个时代:优点被缩小了,缺点被放大。
如果只需要保存,并不需要精确查找,NoSQL数据库非常合适。
中国联通的上网记录,每个人都会有一堆数据,这个数据就是:只要存,基本上不需要查找。并且经常结构会经常变。
所以MySQL等等结构型数据,他们的优点(查找快、主从查找高级功能)我们越来越不用了,缺点(结构难以变化)我们需求越来越大。所以人类开始反思SQL型数据库了。
所以诞生叫做NoSQL,非关系型数据库。数据没有行、列的概念,使用k-v对儿或者JSON进行存储。优缺点正好和SQL型反过来了,适合结构变化,不适合精确查找。刚好适应了时代“只要存储,不要查找”。
NoSQL有一个昵称,叫做Not Only SQL,不仅仅是数据库。实际上是玩笑话,功能比SQL弱太多了。
介绍两种工作中最常用的数据库:MongoDB(文档型数据库)、Redis(K-V对型数据库),他们都是NoSQL。
文档型数据库(比如MongoDB)优点:数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构。
非关系型数据库现在已经广泛的应用于:社交平台、APP通信记录、银行流水、电信通话详单等等。任何只需要保存,但是不需要精确查找的使用场景,都可以用非关系型数据库。
NoSQL非关系型数据库,每一个条目的字段是不固定的。以MongoDB为例子:
{"name":"小明","age":12,"sex":"男"} {"name":"小红","age":13,"sex":"女"} {"name":"小刚","age":14,"sex":"男", "chengji":99} {"name":"小黑","age":14,"sex":"男", "gongzi":5000}
1.2 MongoDB数据库安装
工作的时候是严格前后端分开的,我们学习前端不需要学习数据库的知识,但是为了模拟一个项目,更重要的是,让你了解后台和前端的交互模式,所以必须学习数据库,要有全栈思维。
MongoDB是一种NoSQL(No Only SQL,非关系型数据库)数据库。
MongoDB并不是什么软件,而是一堆CLI(命令行程序)。
MongoDB是绿色的,也就是说不需要安装,解压缩就能用。
下载地址:https://www.mongodb.com/download-center
解压缩到你电脑盘符(不要出现中文):C:\Program Files
解压缩之后的画风是这样的:
bin文件夹中提供的exe文件,不要双击运行,而是应该在cmd中运行,它们都是CLI程序:
此时要将这个bin文件夹设置为系统的环境变量,就能保证CMD在任何盘符下可以运行他们。
| 计算机右键点击属性 |
| 如图更改环境变量 |
| 将bin文件夹的路径复制进去。 |
| 打开CMD,在任意盘符下输入 mongo -version 就能查看版本, |
| 如果环境变量没有设置对,会报错。 |
数据库的使用需要你安装windows补丁:KB2731284。
如果补丁安装不上,查看一下方法:
https://jingyan.baidu.com/article/fd8044fa3c47e15031137acd.html
1.3数据库的开机
在c盘创建一个文件夹叫database,打开cmd输入:
mongod --dbpath c:\database
mongod 表示开机命令,数据库会被打开,--dbpath表示选择一个数据库的位置。
不要关闭这个CMD,关闭了数据库也就关闭了,此时请打开一个新的CMD窗口做其他操作:
mongo
进入MongoDB的REPL环境
1.4数据库的使用
先开机,然后新建一个CMD仓库,用mongo命令管理数据,cmd中输入mongo回车
下面的操作试着看,在mongo的REPL环境中是 > ,而不是C:\Users\admin>的CMD环境:
创建(切换)数据库:
use student
插入数据:
db.banji0902.insert({"name":"小明", "age":12})
查询所有:
db.banji0902.find()
精确查询:
db.banji0902.find({"name":"小明"})
1.5 MongoDB层次结构
MongoDB中从大到小:Database > collections > documents > records
来自: https://docs.mongodb.com/manual/introduction/
3.5.1 records条目和documents 文档
record(条目)
在MongoDB中记录是一个文件,这是一个数据结构由字段和值对。MongoDB文档类似于JSON对象。字段的值可以包括其他文档、数组和文档数组。
documents中的一个document也称为一个record条目,条目类似JSON结构,是k-v对的,值可以是其他对象、数组、对象数组。
1.5.2 collections 集合(表)
MongoDB使用collections集合存储文档(records),集合就相当于SQL中的“表格”。但是不像表格一样,集合不需要文档拥有相同的结构(schema)。
下面是一个集合,里面有4个文档,文档中有3个条目(name、age、sex)
{"name":"小明","age":12,"sex":"男"} {"name":"小红","age":14,"sex":"女"} {"name":"小黑","age":14,"sex":"男"} {"name":"小篮","age":13,"sex":"男"}
文档存储在集合(表)中,集合又存储在数据库中。
1.5.3 database数据库
数据库中可以有很多个集合(表)。
1.6常见的CMD命令
控制mongodb一般都是使用CMD命令,但也有可视化数据库软件。
命令有两种:
CMD命令
REPL命令(用mongo就可以进入此环境,在小尖角号“>”提示符下输入)
先学习CMD中能运行哪些命令?
mongod表示开机,mongo表示进入数据库的REPL环境、mongoimport导入数据、mongoexport导出数据。
先开机,今后一切操作必须先开机:
mongod --dbpath c:\database
首先要准备好外部的数据,写几个文档,必须是.txt文件,而不是.json文件
导入数据,新建一个CMD窗口,输入:
mongoimport -d student -c banji0902 ./模拟数据.txt
删除旧数据,导入新数据
mongoimport -d student -c banji0902 ./模拟数据.txt --drop
导出指定的数据库和集合(表)中的数据:
mongoexport -d student -c banji0902 -o c:\haha.txt
-d 表示指定的数据库 -c 表示指定的表名 -o 表示导出路径 --drop 表示清空之前的数据
1.7常见的REPL命令
在开机之后,另一个CMD窗口,输入mongo按回车进入数据库的REPL环境,即可操作数据库。
切换(创建)某一个数据库:
use student
查询当前有哪些数据库:
show dbs
查看当前数据库有哪些集合(表)
show collections
删除数据库:
db.dropDatabase()
在表中插入数据:
db.banji0902.insert({"name":"小明", "age":12})
查询表中所有的数据:
db.banji0902.find()
精确查询:
db.banji0902.find({"name":"小明"})
且查找:查找年龄是18,并且是男的
db.banji0902.find({"sex":"男", "age":18})
或($or)查找:查找年龄是18岁或“男”
db.banji0902.find({$or:[{"sex":"男"}, {"age":38}]})
大于($gt):
db.banji0902.find({"age":{$gt:18}})
大于等于($gte)、小于($lt)、小于等于($lte)
查找男生,并且年龄介于18到25之间:
db.banji0902.find({"sex":"男", "age":{$gte:18, $lte:25}})
查询中可以用正则表达式:
db.banji0902.find({"name":/小/g})
查询喜欢斗地主的人:
db.banji0902.find({"hobby":"斗地主"})
修改($set):
db.banji0902.update({"name":"小1"}, {$set:{"sex":"不男不女"}})
删除某条数据:
db.banji0902.remove({"name":"小1"})
1.8 MongoDB和Nodejs连接
官方API:http://mongodb.github.io/node-mongodb-native/2.2/
增删改查(CRUD)操作:http://mongodb.github.io/node-mongodb-native/2.2/tutorials/crud/
安装mongodb的依赖,并且安装指定的版本号,不要安装最新版:
npm install mongodb@2.2.28 --save
注意:mongodb版本的API是不一样的,所以要看官方文档:
http://mongodb.github.io/node-mongodb-native/3.0/
var MongoClient = require("mongodb").MongoClient; //引包 //数据库地址 var dbUrl = 'mongodb://127.0.0.1:27017/student'; //连接数据库 MongoClient.connect(dbUrl, function(err,db){ if(err){ console.log("数据库连接失败"); return; } console.log("数据库连接成功!"); db.close(); })
查询数据:
var MongoClient = require("mongodb").MongoClient; //引包 //数据库地址 var dbUrl = 'mongodb://127.0.0.1:27017/student'; //连接数据库 MongoClient.connect(dbUrl, function(err,db){ if(err){ console.log("数据库连接失败"); return; } console.log("数据库连接成功!"); //查询当前数据库中banji0902表的数据 db.collection("banji0902").find({"age":{$gt:18}}).toArray(function(err,data){ console.log(data); db.close(); }) })
传统的连接形式限制一般不用了,因为:
l 回调函数难看。
l 不方便MVC编程
l 原生API复杂
二、Mongoose数据库
2.1新增数据
node.js的优雅mongodb对象建模。
安装依赖:
npm install --save mongoose@4
写一个Hello World。套路:引包、创建schema、创建模型、实例化模型、调用save()方法。
var mongoose = require("mongoose"); //引包 //连接数据库,数据库叫iqd,如果数据库不存在会自动创建 mongoose.connect("mongodb://127.0.0.1:27017/iqd", {useMongoClient:true}); //创建一个数据库结构(Schema) var studentSchema = { name:String, age:Number, sex:String } //创建模型(会返回一个类),student是表名称(集合) //注意:在nodejs中创建的表面叫“student”,但是到了数据库中会自动加上s,变成“students” var Student = mongoose.model('student', studentSchema); //new一个实例 var xiaoming = new Student({"name":"小明","age":12,"sex":"男"}); //保存数据 xiaoming.save();
运行node app.js
iqd数据自动创建,并且创建了一个叫student的表(数据库会自动加s),所以变成students
开机、REPL、控制台各一个CMD。
mongoose就是这样的哲学:让开发者感觉不到在操作数据,仅仅通过new save等类、实例的方法,就能管理数据库,除了new和svae()能创建数据,用Student.insert({})也能创建数据库。
2.2查找
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
//查询数据库的方法1: Student.find({"age":{$gt:18}}, function(err,data){ console.log(data) }) //查询数据库的方法2: Student.find({"age":{$gt:18}}).exec(function(err,data){ console.log(data) })
2.3删除
删除方法很灵活,可以用类打点调用Student的remove方法
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
//删除的方法1 Student.remove({"name":"小黑"}, function(err,data){ console.log(data) }) //删除的方法2: Student.remove({"name":"小黑"}).exec(function(err,data){ console.log(data) })
也可以调用实例的remove()方法删除
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
Student.find({"name":"小6"}, function(err,data){ var xiao6 = data[0]; xiao6.remove(); //删除 })
2.4修改
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
Student.find({"name":"小3"}, function(err,data){ var xiao3 = data[0]; xiao3.sex = "不男不女"; //赋值修改 xiao3.save(); })
如果不用mongoose,用原生
db.collections("students").update({"name":"小3"}, {$set:{"sex":"不男不女"}})
2.5封装静态方法
刚刚发现可以用类名(构造函数)打点调用很多方法
Student.find()
Student.remove()
那么如何自己封装一些方法呢?并且能用类打点调用,mongoose提供自定义封装类的方法(静态方法),步骤:
l 创建schema的时候,必须用new mongoose.Schema()创建
l 在schema的实例上,用statics.***绑定静态方法
var mongoose = require('mongoose'); //引包 //连接数据库,数据库叫iqd,如果数据库不存在会自动创建 mongoose.connect('mongodb://localhost/iqd',{useMongoClient:true}); //创建一个数据库结构(Schema) var studentSchema = new mongoose.Schema({ name: String, age : Number, sex : String }) //封装自定义的静态方法 studentSchema.statics.check = function(name,callback){ //this表示Student类 this.find({"name":name}, function(err,data){ var exsit = data.length > 0; //将结果通过参数传递给回调函数 callback(exsit) }) } var Student = mongoose.model('student', studentSchema); //验证某个人是否存在 Student.check("小明", function(exsit){ console.log(exsit) })
2.6封装动态方法
实例打点调用的都叫动态方法:
var mongoose = require('mongoose'); //引包 //连接数据库,数据库叫iqd,如果数据库不存在会自动创建 mongoose.connect('mongodb://localhost/iqd',{useMongoClient:true}); //创建一个数据库结构(Schema) var studentSchema = new mongoose.Schema({ name: String, age : Number, sex : String, }) //封装自定义的动态方法 studentSchema.methods.bianxing = function(name,callback){ //this表示实例 this.sex = this.sex == "男" ? "女" : "男"; } var Student = mongoose.model('student', studentSchema); //使用动态方法 Student.find({"name":"小明"}, function(err,data){ var ren = data[0]; //实例 ren.bianxing(); ren.save(); console.log(data) })
Nodejs + MongoDB技术栈在工作中很少用到,因为有后台哥哥姐姐,学习Nodejs、MongoDB的HTTP服务器开发的目的:
理解前端后端如何工作、配合、数据如何传递、数据库的增删改查是什么情况。
为了写一个JSON接口,给前端调用
三、制作一套增删改查的接口
做一套RESTful风格的路由接口,创建模拟数据:
{"id":1001,"name":"小明","age":12,"sex":"男","job":"前端"} {"id":1002,"name":"小红","age":15,"sex":"女","job":"设计"} {"id":1003,"name":"小刚","age":12,"sex":"男","job":"老师"} {"id":1004,"name":"小强","age":13,"sex":"男","job":"后端"} {"id":1005,"name":"小绿","age":18,"sex":"男","job":"老师"} {"id":1006,"name":"小青","age":18,"sex":"女","job":"前端"} {"id":1007,"name":"小黑","age":18,"sex":"女","job":"产品"} {"id":1008,"name":"小花","age":18,"sex":"女","job":"设计"}
它们都是用户,所以导入到数据库时,名字叫users的表中,注意表名最后一个字母必须是s。
创建models文件夹,文件夹中创建User.js,创建数据结构
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
var mongoose = require('mongoose'); //引包 //默认暴露 module.exports = mongoose.model('user', { id:Number, name: String, age : Number, sex : String, job : String });
app.js查询接口
var mongoose = require('mongoose'); //引包 var User = require('./models/User.js'); var express = require('express'); //引包 var app = express(); //连接数据库,数据库叫gziqd,如果数据库不存在会自动创建 mongoose.Promise = global.Promise; mongoose.connect('mongodb://localhost/gziqd',{useMongoClient:true}); //查询数据库全部的用户 app.get("/users", function(req,res){ User.find().exec(function(err,data){ res.json({"data": data}) }) }); //查询某一个id的用户 app.get("/users/:id", function(req,res){ var id = req.params.id; //指定id查询 User.find({"id": id}).exec(function(err,data){ res.json(data[0]) }) }); app.listen(3000)
发出POST请求创建数据
index.html前端页面:
<html> <head> <title>Document</title> </head> <body> <!-- 增加 --> <p>ID :<input type="text" id="idTxt" /></p> <p>姓名:<input type="text" id="name" /></p> <p>工作:<input type="text" id="job" /></p> <button id="btn1">创建用户</button> <hr> <!-- 删除 --> <p> 删除的人的id :<input type="text" id="delId"/> <button id="btn2">删除</button> </p> <hr> <!-- 修改 --> <p> 改名的人的id :<input type="text" id="changeId"/><br> 要改为什么名 :<input type="text" id="changeToName"/> <button id="btn3">修改</button> </p> <hr> <!-- 查询 --> <p> 查询id :<input type="text" id="checkId"/> <button id="btn4">查询</button> </p> </body> <script type="text/javascript" src="js/jquery-2.2.4.min.js"></script> <script type="text/javascript"> //发出POST请求,创建用户,提交给数据库(服务器) $("#btn1").click(function(){ $.post("/users",{ id : $("#idTxt").val(), name: $("#name").val(), job : $("#job").val() }, function(data){ if(data == "ok"){ alert("创建成功!"); } }) }); </script> </html>
后端拦截post增加数据的请求,在数据库插入数据
app.post("/users", function(req,res){ var form = new formidable.IncomingForm(); form.parse(req, function(err, data){ //插入数据的方法:User.create() User.create(data, function(){ res.send("ok") }) }); });
发出delete删除请求:
请求的类型一共有26种,下面要用到delete请求、patch请求。
$("#btn2").click(function(){ $.ajax({ url : "/users/" + $("#delId").val(), //要删除的id type : "delete", success: function(data){ if(data == "ok"){ alert("成功"); } } }); });
后端拦截delete删除数据的请求,在数据库删除指定id的用户
app.delete("/users/:id", function (req, res){ var id = req.params.id; //删除数据的语法:User.remove(); User.remove({ "id": id } , function (err, data){ res.send("ok"); }); });
发出patch修改请求:
$("#btn3").click(function(){ $.ajax({ url : "/users/" + $("#changeId").val() , type : "patch", data : { "name": $("#changeToName").val() }, success : function(data){ if(data == "ok"){ alert("成功"); } } }); });
后端拦截patch修改数据的请求,在数据库修改指定id的用户
app.patch("/users/:id", function (req, res) { var id = req.params.id; var form = new formidable.IncomingForm(); form.parse(req, function (err, jsonobj) { var name = jsonobj.name; //得到前端发来的name User.update({"id": id }, {"$set": {"name":name} }, function (err, results) { res.send("ok"); }); }); });
完整的app.js,实现了很多接口。
下面红色代码是express的路由,绿色代码是formidable得到数据,蓝色代码是关于数据库的。
var formidable = require('formidable'); var mongoose = require('mongoose'); var User = require('./models/Users.js'); //引入数据结构 var express = require('express'); var app = express(); //静态化www文件夹 app.use(express.static("www")); //连接数据库,数据库叫做gziqd。如数据库不存在会自动创建。 mongoose.Promise = global.Promise; mongoose.connect('mongodb://localhost/gziqd', {useMongoClient:true}); //查询数据库全部的用户 app.get("/users" ,function(req,res){ User.find().exec(function(err, data){ res.json({"data" : data}) }) }); //查询某一个id的用户 app.get("/users/:id" ,function(req,res){ var id = req.params.id; // 指定id查询 User.find({"id" : id}).exec(function(err, data){ res.json(data[0]) }) }); // 后端接收请求,插入数据,创建用户 app.post("/users" ,function(req,res){ var form = new formidable.IncomingForm(); form.parse(req, function(err, data){ //插入数据的语法:User.create(); User.create(data, function(){ res.send("ok"); }) }); }); //删除请求 app.delete("/users/:id" ,function(req,res){ var id = req.params.id; //删除数据的语法:User.remove(); User.remove({"id":id}, function(){ res.send("ok"); }) }); //修改请求 app.patch("/users/:id" ,function(req,res){ var id = req.params.id; var form = new formidable.IncomingForm(); form.parse(req, function(err, data){ var name = data.name; //得到前端提交的name数据 //修改数据的语法:User.update(); User.update({"id" : id}, {$set:{"name":name}}, function(err, data){ res.send("ok"); }) }); }); app.listen(3000);
RESTful风格的路由:
功能 | URL | 请求类型 |
增加 | http://127.0.0.1/users | POST请求 |
删除 | http://127.0.0.1/users/1001 | DELETE请求 |
修改 | http://127.0.0.1/users/1001 | PATCH请求 |
查询 | http://127.0.0.1/users http://127.0.0.1/users/1001 | GET请求 |
一个URL地址可以干多个工作,靠HTTP请求类型区分开。
RESTful是个英语单词的缩写,语义化的、美观的URL路由形式。
在2015年RESTful流行之前的路由是这样的:
功能 | URL | 请求类型 |
增加 | http://127.0.0.1/adduser.php | POST请求 |
删除 | http://127.0.0.1/deluser.php?id=1001 | GET请求 |
修改 | http://127.0.0.1/changeuser.php?id=1001&name=新名字 | GET请求 |
查询 | http://127.0.0.1/users.php http://127.0.0.1/users.php?id=1001 | GET请求 |
缺点:
① 每个业务用到了不同的php文件,不方便MVC编程。
② 暴露技术细节,人家轻松知道你的处理页面和传参数的情况。
总结一下mongoose中的写法:
功能 | 写法 |
增加 | User.create({}) |
删除 | User.remove({"id" : id}) |
修改 | User.update({"id":id}, {"$set" : {"name" : 前端发来的新名字}}) |
查找 | User.find() User.find({"id":id}) |
四、MVC的架构思想
4.1 MVC理论知识
Models 模型
| Views 视图
| Controllers 控制器(包工头)
|
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据、进行计算等内容。最脏最累的活是models在完成。
在MVC中M表示可插拔的、可自己调试的单独的逻辑单元,model。
model层的内容仅对自己的函数负责,不需要对整个项目业务逻辑负责,它自己能够单元测试(unit test)。
View(视图)是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
Controller(控制器)是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送命令请求。从模型得到数据之后,要渲染视图。
控制器会向多个模型发送请求,请求他们的数据,交给视图去呈递。
MVC 分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。
4.2 MVC小项目-项目介绍
做一个“带有机器学习能力的因数计算器”。
项目的逻辑:
首页上面点击查询按钮的时候,由前端js调用window.location进行跳转页面到/cha/9954路由页面:
Nodejs不擅长计算,所以我们考虑一个事:机器学习。
把用户查询过的数字,存储起来,比如用户查询过9954,此时就在data文件夹中创建9954.txt的文件。此时将因数数组存放于此。当有其他用户查询9954的时候,不需要计算,而是直接呈递结果!这样一来,用的人越多,机器知道的答案也越多,从而越来越快。因为Nodejs找一个文件远比计算一个什么快多了,I/O操作是Nodejs的强项。
逻辑:
4.3 MVC小项目-模型层编写
创建一个models模型车文件夹,新建2个文件:
match.js 里面提供计算因数的函数
file.js 里面提供文件的操作函数(读取文件的数组、将数组写入文件、判断文件是否存在)
也就是说:“模型层员工”就两个:文件操作函数、数学计算函数。
模型层只对自己的函数负责,不需要看见全局业务。
创建data文件夹,负责存储所有已经计算过的答案:
fs.stat()函数可以检查一个文件是否存在,存在返回JSON,不存在返回undefined
fs.stat(path, callback)
在file.js文件,封装一个isExist()函数,这个函数接收数字、回调函数,通过回调函数返回true/false。
表示文件是否存在,true/false返回值最终需要提供给控制器使用。
var fs = require("fs"); //判断一个文件是否存在,接收一个数字参数,比如886,可以判断886.txt是否存在 function isExist(n,callback){ //异步函数不允许return,所以只能提供回调函数传递参数 fs.stat("./data/"+ n +".txt",function(err,stats){ //文件存在stats会返回json,不存在返回undefined var result = stats === undefined ? false : true; callback(result); }); }; //单元测试,检查886.txt是否存在 isExist(886,function(result){ console.log(result); }); //向外暴露函数 exports.isExist = isExist;
写一个函数,单元测试一个函数。
var fs = require("fs"); //判断一个文件是否存在,接收一个数字参数,比如886,可以判断886.txt是否存在 function isExist(n, callback){ ... } //向外暴露 exports.isExist = isExist; // 单元测试。检查886.txt是否存在 // isExist(886,function(result){ // console.log(result) // }); //将数组写入到某个文件中 function writeArray(n, arr, callback){ fs.writeFile("./data/"+ n +".txt", JSON.stringify(arr), function(err){ if(err){ callback(false) }else{ callback(true) } }) } //向外暴露 exports.writeArray = writeArray; // 单元测试,写入数据 // writeArray(8888,[2,2,2],function(result){ // console.log(result) // }); //读取某个文件中的数组 function readArray(n, callback){ fs.readFile("./data/" + n + ".txt", function(err,data){ if(err){ //如果文件不存在 callback(false) }else{ //如果文件存在,返回读取到的数组结果 var arr = JSON.parse(data) callback(arr) } }) } //单元测试 // readArray(888, function(arr){ // console.log(arr) // }) //向外暴露 exports.readArray = readArray; math.js模块,里面就提供了一个函数: //计算因数函数,返回一个数组 function calcFactor(n){ var arr = []; for (var i = 1; i <= n; i++) { if(n % i == 0){ arr.push(i) }; } return arr; } console.log(calcFactor(48)); exports.calcFactor = calcFactor;
4.4 MVC小项目-控制器和视图层
控制器就是中间件,我们将路由的回调函数单独放在外部的js文件中,就是控制器,当项目大的时候可以分出很多。
var express = require("express"); var app = express(); var mainCtrl = require("./controllers/mainCtrl.js"); //设置模板引擎 app.set("view engine", "ejs"); //路由清单,把中间件的回调函数放在外部文件 app.get("/", mainCtrl.showIndex); //显示首页 // app.get("cha/:num", mainCtrl.showCha); //显示结果的页面 app.listen(3000);
向外暴露中间件:
exports.showIndex = function(req,res){ res.render("index.ejs") } exports.showCha = function(req,res){ }
index.html视图
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <style type="text/css"> body{background: #ccc;} .wrap{ background: #fff; width: 900px;height: 400px; border-radius:10px; padding:10px;margin:20px auto; text-align:center; } input{ width: 60%;font-size:22px;border-radius:5px; border:1px solid #eee;padding:0 5px; } button{width: 60px;height: 30px;background: skyblue;border:none;} </style> </head> <body> <div class="wrap"> <h1>因数计算器</h1> <div class="box"> <p>请输入查询的数字</p> <p> <input type="text" autofocus id="numTxt"> <button id="btn">查询</button> </p> </div> </div> </body> <script type="text/javascript"> var btn = document.getElementById("btn"); var numTxt = document.getElementById("numTxt"); btn.onclick = function(){ var num = Number(numTxt.value); //得到数字 if(isNaN(num)){ //isNaN 如果输入的不是数字返回true alert("请输入正确的数字"); return; } //点击查询跳转到查询页面 window.location = "/cha/" + num; } </script> </html>
可以跳转页面了。
核心就是去路由/cha/:num,控制器在调用所有“小兵”,用小兵暴露的API来组合上层业务:
cha.ejs视图:
<style type="text/css"> .wrap{... min-height:400px;} </style> <body> <div class="wrap"> <h1>因数计算器 - 结果</h1> <div class="box"> <p>你查询的数字是:<%= num %>。它的因数有<%= results.length %>个,分别是:</p> <p>本次查询耗时:<%= time %>毫秒</p> <ul> <% for(var i = 0 ; i < results.length; i++){ %> <li><%= results[i] %></li> <% } %> </ul> </div> </div> </body>
mainCtrl.js控制器:
var file = require("../models/file.js"); var match = require("../models/match.js"); //显示结果页面 exports.showCha = function(req,res){ //得到查询的数字 var num = req.params.num; //渲染查询页面,先用假数据测试 res.render("cha" ,{ num : num, results : [1,2,3,4,5], }); } var file = require("../models/file.js"); var match = require("../models/match.js"); exports.showCha = function(req,res){ //得到查询的数字 var num = req.params.num; var time = new Date(); //检查文件是否存在 file.isExist(num , function(exist){ if(exist){//这个数字(文件)已经存在 //直接读取这个文件: file.readArray(num , function(arr){ res.render("cha" ,{ //字典 num : num, results : arr, time : new Date() - time }); }); }else{//如果文件不存在! var arr = math.calcFactor(num);//计算! //向文件写入数组 file.writeArray(num , arr , function(){ res.render("cha" ,{ num : num, results : arr, time : new Date() - time }); }); } }); }