Node.js学习笔记【二】

模块化

在nodejs中,应用由模块组成,nodejs中采用commonJS模块规范。

模块分类

  1. 系统模块:http、querystring、url等
  2. 自定义模块
  3. 模块管理

系统模块

可以去node.js手册上查看 node 本身提供的模块

例如:

Crypto 加密

Events 事件

FileSystem 文件系统

Net 网络操作

OS操作系统信息

Path 处理文件路径

Stream 流操作

Timers 定时器(可用于定时清理服务器垃圾等)

ZLIB 压缩

自定义模块

  1. 模块组成
  2. npm
  3. 发布模块

模块化基本组成部分

  • require引入模块('.js'文件后缀可选)

require有自己的引入规则:

  1. 如果有'./',从当前目录
  2. 如果没有'./',先从系统模块再从node_modules
  • module module身上也有一个exports,和下面的exports其实是同个东西。可以批量对外输出信息。
  • exports单独一个个对外输出信息,需要加给exports上

npm

npm:NodeJS Package Manager(NodeJS包管理器)

它有以下作用:

  1. 提供统一下载途径 npm install xxx(卸载为npm uninstall xxx)
  2. 自动下载依赖

使用npm install安装npm后,会生成一个node_modules文件夹,它不仅用来存放下载别人的模块,还可以存放自己的模块,这样就可以在require引入组件的模块时,可以不用加./

发布模块

  1. 发布自己的模块需要有自己的npm账号,然后使用命令npm login登录账号

  2. 使用npm init初始化包的信息:设置包名name,版本version,描述description,包的主文件/入口地址entry point,包的测试命令test command,填写git仓库地址git repository,包的标签关键字keywords,作者author,包遵循的开源协议license

  3. 初始化完毕后会生成一个package.json(描述包的文件)

  4. 使用npm publish上传发布,使用npm install xxx下载自己成功发布的包

  5. 当修改自己包的代码时,不能直接使用npm publish上传发布,需要在package.json修改版本号再使用npm publish上传发布或者使用npm update xxx按照package.json中标注的版本号进行更新

  6. 使用npm unpublish/npm --force unpublish下架自己上传的包

     

express框架

Express是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。

安装

进入目录cmd下,使用npm install express命令安装express框架

image-20210723143557174

image-20210723143627049

安装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);

image-20210723145016659

image-20210723145033091

image-20210723145045319

实际上,这里的resreq是express框架封装的,非nodeJs原生的,它们保留了原生的功能,还添加了其他功能。

例如:res.send()res.send()胃口比res.write()好,不仅能"吃"字符串、二进制数据,也可以"吃"数组、JSON等其他数据,是一个增强版的res.write()

总而言之,express保留了原生的功能,添加了一些方法(例如,.send),增强原有的功能。

express接收请求方法

express中有三种接收用户请求的方法:

  1. .get('/',function(req,res){}):负责接收用户的get请求
  2. .post('/',function(req,res){}):负责接收用户的post请求
  3. .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>

image-20210723153709517

image-20210723153743029

表单提交post请求

image-20210723153925969

express读取文件

express里有许多中间件,其中express-static可以帮助我们处理静态文件。

使用npm install express-static命令

image-20210723161759741

使用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'))

image-20210723162532258

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:'成功'})
}
}
})

检测接口:一切正常

image-20210723164815039

完善前台

<!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:&#39;/login&#39;,
      data:{user:oUser.value,pass:oPass.value},
      success:function(str){
        var json = eval(&#39;(&#39;+str+&#39;)&#39;);
        if(json.ok){
          alert(&#39;登录成功&#39;);
        }else{
          alert(&#39;失败:&#39;+json.msg);
        }
      },
      error:function(){
        alert(&#39;通信失败&#39;);
      }
    })
  }
}

</script>
</head>
<body>
用户:<input type="text" id="user"/><br>
密码:<input type="password" id="pass"><br>
<input type="button" value="登录" id="btn1"/>
</body>
</html>

运行服务器,前台提交数据给后台

image-20210723170951057

image-20210723171034852

image-20210723171048155

接下来我们将表单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
})

image-20210724134302892

这里的bodyParser.urlencoded({})其实有两个参数:

server.use(bodyParser.urlencoded({

extended,//扩展模式,值为true则为开启扩展模式

limit//限制接收多大数据,默认为100K

}))

GET数据与POST数据处理区别

  1. GET——无需中间件,req.query

  2. POST——需要body-parser分成两步:

    ①加工:server.use(bodyParser.urlencoded({}));

    ②server.use(function(){req.body});

链式操作

使用链式操作是规定这个操作流程有一个步骤,即需要先做什么,然后做什么。依次下去形成一个流水线

简而言之:请求接口相同,同时又有next()操作,这样就是express链式操作

image-20210724135404931

image-20210724135333483

我们可以利用链式操作仿写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);
})

image-20210724142143925

这样我们仿写的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一样

image-20210724144908431

cookie与session

http有个不足:无状态。也就是两次请求之间,服务器是无法识别是否是同个人,这样每次用户刷新页面就得重新登录一次,所以cookie就应运而生。


cookie在浏览器保存一些数据,每次请求都会带过来。这样的话服务器可以根据cookie判断登录状态。

缺陷:不安全(用户可修改)、存储空间有限(4k)


cookie保存在客户端,是不安全的。所以session应运而生。

session: session是不能独立存在的,是基于cookie实现。它保存数据,保存在服务端。

优点:安全、存储空间依据服务器空间

原理:客户端请求服务端,先带一个空的cookie传到服务器,然后服务端对这个cookie赋值一个sessionID并传回客户端;客户端向服务端发起下一个请求时,就会带上这个cookiecookie中会有一个sessionID,服务器利用sessionID找到session文件读取或写入。

image-20210724164241009

隐患:session劫持

读取与发送cookie


  1. 读取——cookie-parser
  2. 发送——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);

总结

  1. cookie空间非常小——省着用,要精打细算,能不签名就不签名
  2. 安全性非常差——要校验cookie是否被篡改过
  3. 发送cookie——res.cookie(名字,值,{path:'/',maxAge:毫秒,signed});
  4. 读取cookie——使用到中间件cookieParser,server.use(cookieParser('密钥'))
  5. req.cookies 未签名版,req.signedCookies带签名版
  6. 删除cookie:res.clearCookie(cookie名);
  7. 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字符串。

image-20210724211848803

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字符串

image-20210724213210712

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);

image-20210725094707845

image-20210725094735714

可以看到文件上传后被保存在指定目录下,同时原始文件名被修改成随机序列(防止重名)以及扩展名消失了,但是我们想保留文件的扩展名,需要求助path获取原始扩展名然后使用fs重命名

path解析出来的路径有这么几个属性:

  1. base 文件名(包含扩展名)
  2. ext 扩展名
  3. dir 文件路径
  4. 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);

可以看到成功上传文件并保留了其文件扩展名

image-20210725100747288

route——路由

routeroute是express里的一个重要部分,它可以把不同的目录对应到不同的模块

使用步骤:

  1. 创建router:var router = express.Router();
  2. 把router添加到server:server.use('/user',router);
  3. 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);

image-20210725222729261

MySQL

在MySQL里,有两种单位:

  1. 库:文件夹—用来管理,本身没法存数据
  2. 表:文件—存数据

使用mysql建表,例如user_table表

image-20210726155244182

建表完成之后,可以进行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 标准写法

  1. 关键字大写
  2. 库、表、字段需要加上”
  3. 分号结尾

SQL四大操作语句

  1. 删 DELETE

    DELETE FROM 表 WHERE 条件

  2. 增 INSERT

    INSERT INTO 表(字段列表)VALUES(值列表)

  3. 改 UPDATE

    UPDATE 表 SET 字段=值,字段=值,……WHERE 条件

  4. 查 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子句使用时可以配合COUNTMINMAXAVG配合使用

LIMIT限制输出

应用:分页。

分页的方式

  1. 所有数据一次性传给前端;不适合数据量大的情况。
  2. 每次后台只给一页数据给前端;
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;

 

posted @ 2021-07-27 22:25  小风车吱呀转  阅读(87)  评论(0编辑  收藏  举报