Node.js学习笔记【二】
模块化
在nodejs中,应用由模块组成,nodejs中采用commonJS模块规范。
模块分类
- 系统模块:http、querystring、url等
- 自定义模块
- 模块管理
系统模块
可以去node.js手册上查看 node 本身提供的模块
例如:
Crypto 加密
Events 事件
FileSystem 文件系统
Net 网络操作
OS操作系统信息
Path 处理文件路径
Stream 流操作
Timers 定时器
(可用于定时清理服务器垃圾等)
ZLIB 压缩
自定义模块
- 模块组成
- npm
- 发布模块
模块化基本组成部分
require
引入模块('.js'文件后缀可选)
require
有自己的引入规则:
- 如果有'./',从
当前目录
找 - 如果没有'./',先从
系统模块
再从node_modules
找
module
module身上也有一个exports,和下面的exports其实是同个东西。可以批量对外输出信息。exports
单独一个个对外输出信息,需要加给exports上
npm
npm
:NodeJS Package Manager(NodeJS包管理器)
它有以下作用:
- 提供统一下载途径
npm install xxx
(卸载为npm uninstall xxx
) - 自动下载依赖
使用npm install
安装npm
后,会生成一个node_modules
文件夹,它不仅用来存放下载别人的模块,还可以存放自己的模块,这样就可以在require
引入组件的模块时,可以不用加./
发布模块
发布自己的模块需要有自己的
npm
账号,然后使用命令npm login
登录账号使用
npm init
初始化包的信息:设置包名name
,版本version
,描述description
,包的主文件/入口地址entry point
,包的测试命令test command
,填写git仓库地址git repository
,包的标签关键字keywords
,作者author
,包遵循的开源协议license
初始化完毕后会生成一个
package.json
(描述包的文件)使用
npm publish
上传发布,使用npm install xxx
下载自己成功发布的包当修改自己包的代码时,不能直接使用
npm publish
上传发布,需要在package.json
修改版本号再使用npm publish
上传发布或者使用npm update xxx
按照package.json
中标注的版本号进行更新使用
npm unpublish
/npm --force unpublish
下架自己上传的包
express框架
Express
是一个简洁而灵活的 node.js Web应用框架
, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。
安装
进入目录cmd
下,使用npm install express
命令安装express框架
安装express框架
后,就可以引入使用它啦
const express = require('express');
//1.创建服务
var server = express();
//2.处理请求
// 用户访问a.html时
server.use('/a.html',function(req,res){
// res.send与之前的res.write差不多
res.send('abc');
res.end()
});
// 用户访问b.html时
server.use('/b.html',function(req,res){
res.send('123');
res.end()
});
//3.监听
server.listen(8080);
实际上,这里的res
和req
是express框架封装的,非nodeJs原生的,它们保留了原生的功能,还添加了其他功能。
例如:res.send()
:res.send()
胃口比res.write()
好,不仅能"吃"字符串、二进制数据,也可以"吃"数组、JSON等其他数据,是一个增强版的res.write()
总而言之,express
保留了原生的功能,添加了一些方法(例如,.send
),增强原有的功能。
express接收请求方法
express
中有三种接收用户请求的方法:
.get('/',function(req,res){})
:负责接收用户的get
请求.post('/',function(req,res){})
:负责接收用户的post
请求.use('/',function(req,res){})
:接收所有类型的请求,通吃。
使用.get
和.post
方法
const express = require('express');
var server = express()
server.get('/',function(req,res){
console.log("有GET");
});
server.post('/',function(req,res){
console.log("有POST");
});
server.listen(8080);
表单提交get请求
<form action="http://localhost:8080" method="post">
用户:<input type="text" name="user"/>
<input type="submit" value="提交">
</form>
表单提交post请求
express读取文件
在express
里有许多中间件
,其中express-static
可以帮助我们处理静态文件。
使用npm install express-static
命令
使用express-static
读取静态文件
const express = require('express');
const expressStatic = require('express-static')
var server = express();
server.listen(8080);
//在use里使用express-static读取www目录下的静态文件
server.use(expressStatic('./www'))
express创建接口
假设定义一个这样的登录接口:
/login?user=xxx&pass=xxx
=> {ok:true/false,msg:"原因"}
使用express
创建接口,其中用users模拟用户数据
//用户数据 var users={ 'blue':'123456', 'pink':'654321', 'purple':'987654' }; server.get('/login',function(req,res){ // express提供了req.query查询前端发送过来的参数 var user = req.query['user']; var pass = req.query['pass'];
if(users[user]==null){
res.send({ok:false,msg:'此用户不存在'});
}else{
if(users[user]!=pass){
res.send({ok:false,msg:'密码错了'});
}else{
res.send({ok:true,msg:'成功'})
}
}
})
检测接口:一切正常
完善前台
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="ajax.js" charset="utf-8"></script> <script> window.onload = function(){ var oUser = document.getElementById('user'); var oPass = document.getElementById('pass'); var oBtn = document.getElementById('btn1');
oBtn.onclick = function(){ ajax({ url:'/login', data:{user:oUser.value,pass:oPass.value}, success:function(str){ var json = eval('('+str+')'); if(json.ok){ alert('登录成功'); }else{ alert('失败:'+json.msg); } }, error:function(){ alert('通信失败'); } }) } }
</script>
</head>
<body>
用户:<input type="text" id="user"/><br>
密码:<input type="password" id="pass"><br>
<input type="button" value="登录" id="btn1"/>
</body>
</html>
运行服务器,前台提交数据给后台
接下来我们将表单get请求换成post请求。
因为解析post数据比get数据略微麻烦,这里需要引入新的中间件body-parser
(使用npm install body-parser
命令安装)
注意:body-parser
只能解析数据不能解析文件,中间件multer
可以解析上传的文件。
const express = require('express'); const bodyParser = require('body-parser');
var server = express();
server.listen(8080);
server.use(bodyParser({}));
server.use('/',function(req,res){
console.log(req.body);//POST
})
这里的bodyParser.urlencoded({})
其实有两个参数:
server.use(bodyParser.urlencoded({
extended,//扩展模式,值为true则为开启扩展模式
limit//限制接收多大数据,默认为100K
}))
GET数据与POST数据处理区别
GET——无需中间件,
req.query
POST——需要
body-parser
分成两步:①加工:server.use(bodyParser.urlencoded({}));
②server.use(function(){
req.body
});
链式操作
使用链式操作是规定这个操作流程有一个步骤,即需要先做什么,然后做什么。依次下去形成一个流水线
。
简而言之:请求接口相同,同时又有next()
操作,这样就是express链式操作
我们可以利用链式操作仿写body-parser解析POST数据的过程。
const express = require('express'); const querystring = require('querystring'); var server = express(); server.listen(8080); //没有指明路径,意味着针对所有路径。 server.use(function(req,res,next){ var str = ''; //收集数据 req.on('data',function(data){ str+=data; }); req.on('end',function(){ // 使用原生的querystring将数据解析成JSON格式返回给body req.body = querystring.parse(str);
next();//数据都获取完了再进行下一步
})
})
server.use('/',function(req,res){
console.log(req.body);
})
这样我们仿写的body-parser
中间件就完成啦(^▽^),我们可以将它封装起来。
创建libs->my-body-parser.js
const querystring = require('querystring');
module.exports=function(req,res,next){
var str = '';
req.on('data',function(data){
str+=data;
});
req.on('end',function(){
req.body = querystring.parse(str);
next();
})
}
引入使用包装好的中间件
const express = require('express'); const bodyParser2 = require('./libs/my-body-parser');
var server = express();
server.listen(8080);server.use(bodyParser2);
server.use('/',function(req,res){
console.log(req.body);
})
效果同body-parser
一样
cookie与session
http有个不足:无状态。也就是两次请求之间,服务器是无法识别是否是同个人,这样每次用户刷新页面就得重新登录一次,所以cookie
就应运而生。
cookie
在浏览器保存一些数据,每次请求都会带过来。这样的话服务器可以根据cookie
判断登录状态。
缺陷:不安全(用户可修改)、存储空间有限(4k)
cookie
保存在客户端,是不安全的。所以session
应运而生。
session
: session
是不能独立存在的,是基于cookie实现。它保存数据,保存在服务端。
优点:安全、存储空间依据服务器空间
原理:客户端请求服务端,先带一个空的cookie传到服务器,然后服务端对这个cookie
赋值一个session
的ID
并传回客户端;客户端向服务端发起下一个请求时,就会带上这个cookie
。cookie
中会有一个session
的ID
,服务器利用session
的ID
找到session
文件读取或写入。
隐患:session劫持
读取与发送cookie
- 读取——
cookie-parser
- 发送——
res.cookie()
const express = require('express'); const cookieParser = require('cookie-parser'); var server = express();
//cookie
server.use(cookieParser());server.use('/',function(req,res){
//发送——res.cookie可选参数path,maxAge
//path:指定在该路径下可以读这个cookie
//maxAge:设置过期时间(有效期),单位毫秒
res.cookie('user','purple',{path:'/aaa',maxAge:30243600*1000});
//读取cookie
console.log(req.cookies);
});
server.listen(8080);
值得注意的是,cookie
是可以往上访问的,所以在根目录下可以读取在aaa目录下的cookie
cookie
的安全性比较差,所以可以实现给它加签名。
const express = require('express'); const cookieParser = require('cookie-parser');
var server = express();
//cookie,把签名传给它解析
server.use(cookieParser('hasd2jk2jke'));server.use('/',function(req,res){
//设置签名
req.secret='hasd2jk2jke';
//有了密钥还要将参数签名signed设置为true才能让cookie数据签名
res.cookie('user','purple',{signed:true});
console.log("带签名的cookie",req.signedCookies);
console.log("无签名的cookie",req.cookies);
})
server.listen(8080);
总结
- cookie空间非常小——省着用,要精打细算,能不签名就不签名
- 安全性非常差——要校验cookie是否被篡改过
- 发送cookie——res.cookie(名字,值,{path:'/',maxAge:毫秒,signed});
- 读取cookie——使用到中间件cookieParser,
server.use(cookieParser('密钥'))
req.cookies
未签名版,req.signedCookies
带签名版- 删除cookie:
res.clearCookie(cookie名);
- cookie加密:
cookie-encrypter
,cookie加密意义不大。
写入与读取session
在cookie
的基础上,我们可以使用中间件cookie-session
往cookie写入或者读取session
。
const express = require('express'); const cookieParser = require('cookie-parser'); const cookieSession = require('cookie-session'); var server = express();
//cookie
server.use(cookieParser());
server.use(cookieSession({
name:'sess',
//session名
//使用session时,需要加keys--密钥,keys为数组,会依次循环使用keys中的密钥对session加密
//keys密钥数组越长,越安全
keys : ['aaa','bbb','ccc'],
//设置有效期(单位毫秒)
maxAge: 136001000//有效期1小时
}));server.use('/',function(req,res){
//第一次访问时
if(req.session['count']==null){
req.session['count']=1;
}else{
req.session['count']++;
}
//session在request上
console.log(req.session['count']);
res.send('ok');
});
server.listen(8080);
模板引擎
为了便于维护,且使后端逻辑能够比较好的融入前端的HTML代码中,同时便于维护,很多第三方开发者就开发出了各种Nodejs模板引擎,其中比较常用的就是Jade
模板引擎和Ejs
模板引擎。
jade:破坏式的、侵入式;强依赖
——使用了jade
就不能跟普通的html和CSS共存
ejs:温和、非侵入式、弱依赖
conslidate
帮助express适配各种模板引擎。通过conslidate
,各种模板引擎可以得到整合,可以对express提供统一的接口。
Jade和Ejs使用
Jade
使用npm install jade
命令安装jade
使用jade.renderFile
方法读取文件,渲染文件里面的内容,输出解析后的html字符串。
1.jade
html
head
style
script
body
div
ul
li
li
li
p
简单用法:
jade
根据缩进,规定层级属性
放在()里面,逗号分隔
<script src="a.js"></script>
=>
script(src="a.js")
内容
接在属性
后面空一格的位置
<a href="http://www.baidu.com/">百度</a>
=>
a(href="http://www.baidu.com/") 百度
行内CSS样式
可以用①普通属性写法②用json
div(style="width:200px;height:200px;background:red")
div(style= {width: '200px', height: '200px', background: 'red'})
class内部标签CSS样式
可以用①普通属性写法②数组
div(class="aaa left-warp active")
div(class= ['aaa', 'left-warp', 'active'])
- 竖线
|
可以使内容
在body或script等标签中原样输出
<body>abcd</body>
=>
<body>
|abcd
</body>
.
可以使下一级内容都是原样输出
<head>div<head>
=>
head.
div
- 可以使用
include
引入代码文件
script
include a.js
更多关于jade
知识可以看官方网站
Ejs
使用npm install ejs
命令安装ejs
使用ejs.renderFile
方法读取文件,渲染文件里面的内容,输出解析后的html字符串
1.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
我的名字:<%= name %>
</body>
</html>
注意这里的<%= name %>
变量name
来自ejs.js中
简单用法:
- <%=
变量名
%>可以输出变量,变量来自ejs.js中。 - <% for(){}%> 可以使用for循环语句
- <%= %>转义输出;<%- %>不转义输出
<%=
var str="<div></div>";
%>
<%- str %>
- <% include
文件名路径
%>可以输出文件内容
更多关于ejs知识可以看官方网站
解析post文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form action="http://localhost:8080" method="post" enctype="multipart/form-data">
文件:<input type="file" name="f1"/><br>
<input type="submit" value="上传">
</form>
</body>
</html>
前面学过body-parser可以解析post数据,但是它不能解析post上传的文件,我们需要用到新的中间件multer
解析post文件
const express = require('express'); const multer = require('multer'); //dest参数可以设置上传文件保存的地址 var objMulter = multer({dest:'./www/upload/'}); var server = express();
//指定一个文件
// server.use(objMulter.single('f1'))
// 接收任何文件
server.use(objMulter.any());server.post('/',function(req,res){
console.log(req.files);
});
server.listen(8080);
可以看到文件上传后被保存在指定目录下,同时原始文件名被修改成随机序列(防止重名
)以及扩展名消失了,但是我们想保留文件的扩展名,需要求助path
获取原始扩展名然后使用fs
重命名
path
解析出来的路径有这么几个属性:
- base 文件名(包含扩展名)
- ext 扩展名
- dir 文件路径
- name 文件名(不包含扩展名)
我们就可以使用ext
获取其文件扩展名
fs
中有一个函数fs.rename(旧文件名,新文件名,回调函数)
,可以使用它把刚才保存的上传文件重命名修改其文件扩展名。
const express = require('express'); const multer = require('multer'); const fs = require('fs'); const pathLib = require('path'); //dest参数可以设置上传文件保存的地址 var objMulter = multer({dest:'./www/upload/'}); var server = express();
//指定一个文件
// server.use(objMulter.single('f1'))
// 接收任何文件
server.use(objMulter.any());server.post('/',function(req,res){
console.log(req.files);
//新文件名
var newName = req.files[0].path+pathLib.parse(req.files[0].originalname).ext;
fs.rename(req.files[0].path,newName,function(err){
if(err)
res.send('上传失败')
else
res.send('ok')
})
//1.获取原始文件扩展名
//2.重命名临时文件
});
server.listen(8080);
可以看到成功上传文件并保留了其文件扩展名
route——路由
route
:route是express里的一个重要部分,它可以把不同的目录对应到不同的模块
使用步骤:
- 创建router:
var router = express.Router();
- 把router添加到server:
server.use('/user',router);
- router内部处理
const express = require('express'); var server = express(); //目录1::/user/ //创建user相关的路由 var routeUser = express.Router(); //记得把创建好的路由告诉server server.use('/user',routeUser); routeUser.get('/1.html',function(req,res){ res.send('user1'); }) routeUser.get('/2.html',function(req,res){ res.send('user2'); })
//目录2:/atricle/
//创建article相关的路由
var articleRouter=express.Router();
server.use('/article', articleRouter);
articleRouter.get('/10001.html', function (req, res){ //http://xxxx.com/article/10001.html
res.send('asdfasdfasdf');
});
server.listen(8080);
MySQL
在MySQL里,有两种单位:
- 库:文件夹—用来管理,本身没法存数据
- 表:文件—存数据
使用mysql
建表,例如user_table表
建表完成之后,可以进行mysql客户端的操作。node.js不支持mysql,需要使用第三方库,npm install mysql
下载mysql模块(client).
const mysql = require('mysql');
//1.链接
//createConnection(哪台服务器,用户名,密码,库)
var db=mysql.createConnection({
host:'localhost',
user:'root',
password:'123456',
database:'20210726'
})
//2.查询
//query(sql语言,回调函数)
db.query("",(err,data)=>{
if(err)
console.log('出错了',err);
else
console.log('成功了',data);
//可以把数组或JSON转换成适于传输的字符串
console.log(JSON.stringify(data));
});
连接池(Pool)
保持某个数目的连接数,连接的时候选择能用的连接,避免重复连接
//createPool
const db = mysql.createPool({
host:'localhost',
port:3306,
user:'root',
password:'123456',
database:'20210726'
});
SQL语句
SQL 标准写法
- 关键字大写
- 库、表、字段需要加上”
- 分号结尾
SQL四大操作语句
删 DELETE
DELETE FROM 表 WHERE 条件
增 INSERT
INSERT INTO 表(字段列表)VALUES(值列表)
改 UPDATE
UPDATE 表 SET 字段=值,字段=值,……WHERE 条件
查 SELECT
SELECT * FROM 表 WHERE 条件
子句
WHERE条件
WHERE age<=10
WHERE age>=10 AND score<60
WHERE age>15 OR score>80
ORDER 排序
ASC
——升序(从小到大)
DESC
——降序(从大到小)
ORDER BY age ASC/DESC
//先按价格升序,再按销量降序
ORDER BY price ASC,sales DESC
GROUP 聚类、合并相同
//按班级分组,将class相同的合并,统计班级人数 SELECT class,COUNT(class) FROM student GROUP BY class
//计算各班平均分
SELECT class,AGE(score) FROM student
GROUP BY class
//计算各班最高分,最低分
SELECT class,MAX(score),MIN(score) FROM student
GROUP BY class
//统计每个人消费总价,按总价升序排列
SELECT name,SUM(price) FROM sales_table GROUP BY name ORDER BY SUM(price) ASC
GROUP
子句使用时可以配合COUNT
、MIN
、MAX
、AVG
配合使用
LIMIT限制输出
应用:分页。
分页的方式
- 所有数据一次性传给前端;不适合数据量大的情况。
- 每次后台只给一页数据给前端;
LIMIT 10; 前10条
LIMIT 5,8; 从5开始,要8条
注意:字句之间有顺序:
WHERE
, GROUP BY
, ORDER BY
, LIMIT
(筛选→合并→排序→限制)
SELECT class,COUNT(class) FROM student
WHERE score>60
GROUP BY COUNT(class) DESC
LIMIT 2;