一、nodejs特点
单线程:
好处:减少了内存的开销,操作系统的内存换页
坏处:一个用户造成了线程的奔溃,整个服务都奔溃了
非阻塞I/O:一个线程永远在执行计算操作,这个线程的CPU核心利用率是100%
事件驱动:底层的代码中,近半数都用于事件队列,回调函数的队列中
二、安装
1、https://nodejs.org/en/下载安装
2、配置环境变量
3、查看版本号:node -v
4、运行:node 文件名
三、基本语法
访问任何一个文件都需要静态出来 ,文件名字和和访问路径的名字没有任何关系
var http = require("http");===>require表示引包,引包就是引用自己的一个特殊功能
var fs=require("fs")===>读取目录文件
var server = http.createServer(function(req,res){===>createServer:创建一个服务;req表示请求,request; res表示响应,response
if(req.url=="/fang"){
fs.readFile("./test.html",function(err,data){
res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});===>设置HTTP头部,状态码是200,文件类型是html,字符集是utf8
res.end("哈哈哈哈,我买了一个iPhone" + (1+2+3) + "s")
})
}else if(req.url=="/2.jpg"){===>请求一个图片,需要把图片静态出来
fs.readFile("./2.jpg",function(err,data){
res.writeHead(200,{"Content-type":"image/jpg"});
res.end(data)
})
}else{
res.writeHead(404,{"Content-type":"text/html;charset=UTF-8"});
res.end("没有这个页面")
}
});
server.listen(3000,"127.0.0.1");===>运行服务器,监听3000端口(端口号可以任改)
四、http
设置mime类型:res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"})
输出请求地址:res.url(所有的路由设计都是通过req.url来实现的)
识别url用到的模块:url模块,querystring模块
url模块(提交参数true 就相当于是querystring):
用户输入地址(不包括问号后边的内容):url.parse(req.url).pathname;
问号后边的内容:url.parse(req.url).query;==>url.parse(req.url,true).query; 输出的是对象
URL 的完整的小写的主机部分(包括端口号):url.parse(req.url).host
完整的url:url.parse(req.url).href
完整的主机部分:url.parse(req.url).host
获取端口号:url.parse(req.url).port
把url对象装换成url字符串:url.format()
五、fs
异步IO
fs.rename(oldname,newname,callback):修改文件名字
fs.readFile:读取文件
fs.readFile("./test/1.txt",function(){err,data}{
if(err){throw err}
res.end(data);
})
fs.stat:检测文件状态
fs.stat("./album/bbb",function(err,stats){
stats.isDirectory()==>判断是不是文件夹
})
fs.stat返回的是一个对象实例,提供了isFile(判断文件),isDirectoory(判断文件夹),isBlockDevice等方法,size,ctime,mtime等属性
fs.readdir:读取文件夹中所有的文件
fs.mkdir:异步创建文件夹
fs.rmdir:删除文件夹
不正确
fs.readdir("./album",function(err,files){
console.log(files)==>files是个数组,表示./album这个文件夹中的所有东西,包括文件、文件夹
for(var i=0; i<files.length; i++){
var thefilename=files[i];
fs.stat("./album/"+thefilename,function(err,stats){
if(stats.isDirectory()){
wenjianjia.push(thefilename)
}
})
}
})
正确写法把异步变成同步(递归思想)
fs.readdir("./album",function(err,files){
var wenjianjia=[];
(function iterator(i){
if(i==files.length)return;
var fileName=files[i];
fs.stat('./album/'+fileName,function(err,stats){
if(stats.isDirectory()){
wenjianjia.push(fileName)
}
iterator(i+1)
})
})(0)
})
六、路由
根据不同的请求路径返回不同的数据
七、path(mime类型)
返回拓展名:path.extname(p)
function getMime(extname){
swich(extname){
case ".html":
case ".jpg":return "text/html";
break;
return "image/jpg";break;
}
}
八、模块
模块引用是从当前模块出发,但是用fs读取模块的时候是从更目录查找 就找不到,解决方法 使用个目录__dirname
顶级作用域
js文件中,可以用exports暴露很多东西,使用者只需要require一次,相当于增加了顶层变量,所有的函数,变量都要从这个顶层变量走
js文件和js文件之间有两种合作的模式
1)某一个js文件中,提供了一个函数,共别人使用,只需要暴露函数就行了:exports.msg=msg
2)某一个js文件,描述了一个类。module.exports=People
注:在require中没有写'./',不是相对路径,例如:“main.js”,是一个特殊的路径,默认是从node_modules;如果引用‘./’和‘.js’都不写,默认是引用文件夹
每个模块文件中,推荐都写一个package.json文件,这个文件的名字不能改,node将自动读取里边的配置,有一个main项,设置入口文件:
{
name:'bar',
"version":'1.0.1',
"main":'app.js'
}
注:package.json文件,要放到模块文件夹的根目录去
九、npm
http://www.npmjs.cim
如果要配置一个模块
下载模块:npm install 包命
可以用package.json管理依赖
1)生成package.json:npm install
“dependencies”:{依赖的包名}
2)生成package.json以后,就创建了依赖,当把模块删除的时候,就可以在本地下载所需模块
npm.init
十、http请求
处理post请求的两种方法(这两种方法包含了两个方法:data,end):on、addListener
解析前台发来的数据,解析字符串中的键值对:querystring (查询字符串)
上传图片和视频:formidable
返回当前目录的上一级:path.resolve(__dirname,'..')
处理地址栏参数,一般用于get请求:url
enctype="multipart/form-data"
修改文件的名字fs.rename("./1.txt","./2.txt")
post请求:
接收post请求
if(req.url=="/dopost"&&req.method.tolowerCase()=="post"){
var alldata='';
req.addListener('data',function(chunk){==>chunk输出的格式Buffer
alldata+=chunk
console.log(chunk)
})
req.addListener('end',function(){
console.log(alldata.toString());
var dataString=alldata.toString()
res.end("success");
})
}
十一、express
npm install express
中间件
获取参数的四种方法:
req.body:不是nodejs里边提供,需要载入body-parser中间件才可以使用(此方法通常解析post请求的数据)
使用:
var express=require('express'); var bodyParser=require('body-parser'); var app=express(); app.use(bodyParser.urlencoded({extended:true})); app.post('/teacher',function(req,res){ res.end(req.body.name); }); app.listen(3000,'127.0.0.1');
req.query:nodejs中默认提供,不需要载入中间件(此方法用来解析get请求的数据)
req.params:nodejs默认提供,不需要载入其他中间件(包含路由参数,在URL的路径部分)
req.param():被弃用
express的基本雏形:
var express=require('express');
var app=express();
app.set('view engine','ejs');
app.get('/',function(req,res){
res.render('haha',{
news:['aa','bb','']
})
})
'#'提交是提交给自己
取得 GET Request 的 Query Strings:
GET /test?name=fred&tel=0926xxx572
app.get('/test', function(req, res) {
console.log(req.query.name);
console.log(req.query.tel);
});
如果是表单且是用 POST method:
<form action='/test' method='post'>
<input type='text' name='name' value='fred'>
<input type='text' name='tel' value='0926xxx572'>
<input type='submit' value='Submit'>
</form>
app.post('/test', function(req, res) {
console.log(req.query.id);
console.log(req.body.name);
console.log(req.body.tel);
});
十二、路由
当用get访问一个网址
app.get('网址',function(){
})
当用post访问一个网址
app.post('网址',funtion(){
})
如果想处理这个网址的任何method的请求,
app.all('网址',function(){
})
注:这里的地址不分大小写,所有的参数‘?’都已经被忽略
正则表达式可以被使用。正则表达式中,未知部分用圆括号分组,然后可用req.params[0]、[1]得到req.params类数组对象
app.get(/^\/student\/([\d]{10})$/,function(req,res){
res.send("学生信息,学号"+req.params[0]);
})
冒号是更推荐的写法
app.get('/student/:id',function(req,res){
var id=req.params["id"];
var reg=/^[\d]{6}$/;
if(reg.test(id)){
res.send(id)
}else{
res.send("请检查格式")
}
})
表单可以自己提交到自己
app.get("/",function(req,res){
res.render("form")
})
app.post("/",function(){
//将数据提交到数据库
res.send("成功")
})
restful路由设计:同一个网址,不同的访问方式,执行不同的命令 ==>适合app开发使用
请求:/studednt
get:读取学生信息
add:添加学生信息
delete:删除学生信息
十二、中间件
如果get、post回调函数中,没有next参数,那么就匹配上第一个路由,就不会再往下匹配了
如果想往下匹配,需要写next()
app.get('/',function(req,res){
console.log(1);
next();
})
app.get('/',function(req,res){
console.log(2)
})
路由get、post这些东西,就是中间件,中间件讲究顺序,匹配上第一个以后,就不会再往后匹配,next函数才能继续
app.use()也是一个中间件,与get和post不同的是,他的网址不是精确匹配的,而是能够有小文件夹扩展的
注:当use不写路径的时候,实际上就相当于'/',就是所有网址都可以访问,
app.use('/admin',function(req,res){
res.write(req.orginalUrl) ==>/admin/aa/bb/cc/dd
res.write(req.baseUrl); ==>/admin
res.write(req.path); ==>/aa/bb/cc/dd
res.end("你好")
})
静态服务 (第三方服务):app.use(express.static('./public'))======>app.use('/jintai',express.static('./public')) 这样写静态文件路径从'jintai'出发
app.use()就给我们增加了一些特定功能的便利场所
实际上app.use()的东西,基本上都是第三方得到的
render和send不同点
1、大多数情况下,渲染内容有res.render(),将会根据views中的模板文件进行渲染,如果不想使用views文件夹,想自己设置文件夹的名字,那么app.set('view engine','ejs')
2、如果想写一个快速测试页,当然可以使用res.send(),这个函数将根据内容,自动帮我们设置了Content-Type头部和200状态码,send()只能用一次,和end一样,可以设置mime类型
3、如果想使用不同的状态码:res.status(404).send('sorry ,we can not defined')
4、如果想使用不行的Content-Type:res.set('Content-Type','text/html')
get和post请求
get请求的参数在url中,在原生的node中,需要使用url模块来识别参数字符串,在express中,不需要使用url模块了,可以直接使用req.query对象
post请求在express中不能直接获取,必须使用body-parser模块,使用后,将可以用req.body得到参数,但是如果表单中含有文件,那么还需要使用formadable模块
十三、MVC
文件目录:
controller:控制器 路由
models:模型
views:视图
uploads:图片存放地址
public:静态文件目录
node_modules:依赖文件
app.js:入口文件
设置入口文件:在package.json中{'main':'router.js'}
错误
res.render('index',{
'name':studentgetById('123').name
})
正确
student.studentgetByXuehao('123',function(detail){
res.render('index',{
'name':detail.name
})
})
异步和同步读取文件夹
异步读取文件(递归思想)
fs.readdir('./uploads/',function(err, files){
(function iterator(i){
if(i==files.length){
callback(allAlbum);
return;
}
fs.stat('./uploads/'+files[i],function(err, stats){
console.log(stats)
if(stats.isDirectory()){
allAlbum.push(files[i])
}
iterator(i+1)
})
})(0)
});
同步步读取文件
fs.readdir('./uploads/',function(err, files){
(function iterator(i){
if(i==files.length){
callback(allAlbum);
return;
}
fs.stat('./uploads/'+files[i],function(err, stats){
console.log(stats)
if(stats.isDirectory()){
allAlbum.push(files[i])
}
iterator(i+1)
})
})(0)
});
返回当前目录的上一级:path.resolve(__dirname,'..')
十四、NoSQL
非结构型数据库,没有行、列的概念,用json来存储数据。
集合就相当于“表”,文档就相当于“行”
文档就是json,上下文语境中,也是javascript范畴,所以我们的数据库也是js范畴的东西
NoSQL不是银弹,没有资格挑战老牌数据库,还是特定情况下,是适合的
十五、mongoDB
win7系统需要在安装补丁,KB2731284
装好文件夹:C:\Program Files\MongoDB
设置环境变量:C:\Program Files\MongoDB\Server\3.2\bin
开机:mongod --dbpath 数据库文档所在的文件夹
使用数据库:mongo 链接数据库
导入数据:mongoimport
列出所有数据库:show dbs
使用某个数据库:use 数据库名字
如果use一个不存在的数据库,就是新建数据库
查看当前所在数据库:db
插入数据(student就是所谓的集合,集合中存放着许多json):db.student.insert({"name":"xiaoming","age":12})
列出当前集合:show collections
列出集合中的json:db.student.find();
精确查找:db.student.find({'name':'xiaoming'});
多个条件匹配:db.student.find({'name':'xiaoming','age':12});
大于50的集合:db.student.find({'score.yuwen':{$gt:50}});
或关系查找:db.student.find($or:[{"age":9},{"age":11}]);
查找完之后大点调用sort,表示什降排序,如果前边的一样按后边的来排:db.student.find().sort({'borough':1,'adress':1})
向集合中插入一条数据:db.student.insert({"name":"xiaozhang","age":11});
删除数据库,删除当前数据库:db.dropDatabase();
导入数据:mongoimport --db test(数据库的名字) --collection restaurants(向往哪个集合中导入) --drop(把集合清空) --file dataset.json(导入的文件
改变数据(只能改变一个数据):db.student.update({'name':'小明'},{$set:{'age':16}});
改变多个数据:db.student.update({},{$set:{'age':16}},{multi:true});
完整替换(不写$set):db.student.update({'name':'小明'},{'age':16});
删除数据:sb.student.remove({'age':18})
只删除一个数据:sb.student.remove({'age':18},{justOne:true})
查看下一页:it
查找几条数据:db.student.find().limit(4);
略过几条数据:db.student.find().limit(4).skip(4);
显示数据库的数据状态:db.student.stats();
显示数据条数:db.student.find().count();
查看检索的过程:db.student.find().explain();
链接数据库:
mongoClient.connect(url,function(err,db){
代码执行;
db.close()
})
DAO封装:设计模式把底层的访问逻辑和业务逻辑分开
数据库链接:
function _connectDB(callback){
mongoClient.connect(url,function(err,db){
callback(err,db)
})
}
增删改查
插入一条数据:
exports.insertOne=function (collectionName,json,callback){
_connectDB(function(err,db){
db.collection(collectionName).insertOne(json,function(err,result){
callback(err,result);
db.close();
})
})
}
查找数据:
exports.find=function(collectionName,json,callback){
_connectDB(function(err,db){
db.collection(collectionName).find(json).toArray(function(err,docs){
callback(err,docs)
})
})
}
通过查找数据,可以对数据进行分页。有两种做法
1)错误的做法:就是将所有的result都读取数组,然后进行数据操作,进行分页
2)正确的做饭:就是在数据库中,只读取这么多内容
exports.find=function(collectionName,json,args,callback){
var limit=args.pageAmount;
var skipNumber=args.page*args.pageAmount;
_connectDB(function(err,db){
db.collection(collectionName).find(json).limit(limit).skip(skipNumber).toArray(function(err,docs){
callback(err,docs)
})
})
}
删除数据:
expots.delete=function(collectionName,json,callback){
_connectDB(function(err,db){
db.collection(collectionName).deletMany(json,function(err,result){
callback(err,result)
})
})
}
修改数据:
exports.update=function(collectionName,json1,json2,callback){
_connectDB(function(err,db){
db.collection(collectionName).updateMany(json,json2,function(err,result){
})callback(err,result)
})
}
索引:
建立索引:db.student.createIndex({'name':1});
索引不能相同:db.members.createIndex({'user_id':1},{unique:true})
好处:这样,今后通过name寻找student文档的熟读会非常快,
缺点:插入每条数据变慢了,效率低了
node建立索引:
_connectDB(function(err,db){
if(err){
console.log(err);
returen;
}
db.collection('student').createIndex({"cuisine":1},null,function(err,result){
console.log(result);
})
})
十六、session
cookie是在res中设置,req中读取的,第一次访问没有cookie。
cookie的存储大小有限,kv对儿。对用户课件,用户可以禁用、清除cookie、可以被篡改
cookie用来制作记录用户的一些信息,必须购买历史,猜你喜欢
http是无状态的协议,所以两次的访问,服务器不能认识到是同一个客户端的访问,就要用cookie的巧妙的解决这个问题
session不是一个天生就有的技术,而是依赖cookie,当浏览器禁用cookie的时候,或者用户清楚了cookie,登陆也消失,session比cookie不一样,session下发的是乱码,并且服务器自己缓存一些东西,下次浏览器的请求带着乱码上来,此时与缓存进行比较,看看是谁。
所以,一个乱码,可以对应无限大的数据,
任何语言中session的使用,是“机理透明”的,他是帮你设置cookie的,但是足够方便,让你感觉不到这事与cookie有关
nodejs中的session:
设置cookie失效时间:
var hour=360000;
req.session.cookie.express=new Date(Bate.now()+hour);
req.session.cookie.maxAge=hour;
var session=require("express-session");
app.use(session({
secrel:'keyboard cat',
resave:false,
saveUninitalized:true,
cookie:{secure:true}
}))
app.get("/",function(req,res){
if(req.session.login){
req.send('欢迎你'+req.session.username)
}else{
res.send("没有成功登陆")
}
})
app.get("/login",function(req,res){
req.session.login=true;//设置这个session
req.session.username="考拉"
res.send("你已经成功登陆")
})
十七、MD5
MD5加密是函数型加密。就是每次加密的结果一定相同,没有随机位
特点:
不管加密的文字,多长多短,永远都是32位英语字母、数组混合
哪怕只改一个字,密文都会大改
var crypto=require('crypto');
var shasum=crypto.createHash('sha1');
function md5(mingma){
var md5=crypto.createHash('md5');
var password=md5.update(mingma).digest('base64');
return password;
}
十八、图片裁剪(GraphiMagick)
下载:http://www.graphicsmagick.org/
设置全局的环境变量
http://elf8848.iteye.com/blog/382528
注意:后面三个7.0版本安装时必须勾选"Install legacy utilities(e.g. convert)"选项,否则依然会报错,而ImageMagick-6.2.7-6-Q16-windows-dll.exe默认安装即可
另外,ImageMagick-6.9.2-8-Q16-x64-static.exe安装时没有"Install legacy utilities(e.g. convert)"选项,安装完后会报错。
大家可以试试其他版本
gm convert danny.jpg haha.jpg 格式转换
gm mogrify -output-directory 文件夹-resize 320*200 *.jpg 裁剪图片
nodejs使用gm,需要安装gm包 https://www.npmjs.com/package/gm
var fs=require('fs');
var gm=require('gm');
nodejs缩略图制作
图片缩放:
gm('./danny.jpg')
.resize(50,50,"!")//“!”强制变成50*50
.write('./danny2.jpg',function(err){
if(err){
console.log(err)
}
})
图片裁剪:
gm('/path.to/my/img.jpg')
.flip()
.magnify()
.rotate('green',45)
.blur(7,3)
.crop(300,300,150,130)//裁剪的坐标 x,y,w,h
.edge(3)
.write('path/to/crazy.jpg',function(err){
if(!err)console.log('crazytown has arrived')
})
十九、Mongoose
mongoose 4.11.0以后使用:mongoose.connect("mongodb://localhost/mongoose",{useMongoClient: true});
module.exports:直接将函数暴露
exports:将行数名暴露
是一个将javascript对象与数据库产生关系的一个框架,操作对象,就是操作数据可了:对象产品了,同时也持久化了
这个思路是java三大框架SSH中的Hibernate框架的思路。彻底改变了人们使用数据库的方式。
//引包,并不需要引用mongodb这个包
var mongoose=require('mongoose');
mongoose.Promise = global.Promise;//解决报错 2.4以上
//链接数据库,haha是数据库名字
mongoose.connect('mongodb://localhost/haha');
//创建了一个模型
var Cat=mongoose.model('Cat',{name:String});
//实例化一只猫
var kitty=new Cat({name:'Zildjian'});
//保存到数据库
kitty.save(function(err){
console.loh('保存')
})
mvc
//mongoose数据库链接
var mongoose=require('mongoose')
var db=mongoose.createConnection('mongodb://127.0.0.1:2017/haha')
//
db.once('open',function(callback){
console.log('数据库成功链接')
})
//Schema结构
var mongooseSchema=new mongoose.Schema({
username:{type:String,default:'匿名用户'},
title:{type:String},
content:{type:String},
time:{type:Date,default:Date.now},
age:{type:Number}
})
//基于shemale创建的
var student=db.model('Student',mongooseSchema)
//向外暴露
module.exports=mongooseSchema;
//app.js实例化(2种方法)
var xiaoming=new mongooseSchema({'name':'小明','age':12,'sex':'男'})
xiaoming.save(function(){
console.log('存储成功')
})
Student.create({'name':'小红','age':12,'sex':'女'},function(err,result){
console.log('创建成功')
})
//创建静态方法
mongooseSchema.statics.zhaoren=function(name,callback){
return this.model('student').find({name:name},callback)
}
//创建修改的静态方法
mongooseSchema.statics.xiugai=function(conditions,update,options,callback){
this.model('Student').update(conditions,update,options,callback)
}
//创建对象方法
mongooseSchema.methods.findSimilarTypes=function(callback){
this.model('Student').find({type:this.type},callback)
}
methods和statics的区别
区别就是一个给Model添加方法(statics),一个给实例添加方法(methods);调用的方式不一样,statics直接model调用,methods实例直接调用
二十、socket
http是没有状态的,服务器只会响应来自客户端的请求,但是他与客户端之间不具备持连接
长轮询:客户端没个很短的时间就会向服务器发送请求,查看是否有新的消息,轮询的速度快,浪费性能
长连接:客户端只请求一次,服务器会将连接持续,不会返回结果,服务器有了新数据,就将数据发回来,又有了数据就将数据发回来,而一直保持挂起状态。这种做法也造成了大量的性能浪费。
websocket协议能够让浏览器和服务器全双工通信,互相的,服务器也能主动通知客户端了。
websocket的原理非常简单,利用http请求产生握手,http头部中含有websocket协议的请求,所以握手之后,二者转用TCP协议进行交流(QQ的协议)
websocket协议,需要浏览器的支持和服务器的支持:
浏览器:Chrome4、火狐4、ie10、Safari5
服务器:Node0、Apach7.0.2、Nginx1.3
Node.js上需要写一些程序,来处理TCP请求
Socket.IO
后注、命令行
dir查看完整目录
可以让其他的用户访问此程序:server.listen(8080,"本机的ip")
处理favicon.ico: if(req.url=="/favicon.ico")return;