title: Express框架
date: 2022-01-16 09:51:18
tags:
Express框架
express是一个基于内置核心http模块的一个第三方的包,专注于web服务器的构建。
//在项目文件夹express路径cmd之后,
//初始化项目
yarn init -y
//安装express
yarn add express
使用express搭建一个简单的服务器
app.js
//1.引入express
const express = require("express");
//2.创建app对象(项目对象),它就是项目最大的这个对象
const app = express();
//4.处理请求,第一个参数是请求路径,第二个参数是针对这个路径的处理函数
app.get("/", (req, res) => {
//这个函数有两个形参
//第一个形参req是请求对象(跟请求相关的数据需要用它)
//第二个形参res是响应对象(跟响应相关的工作需要用它)
//响应一个字符串给浏览器
res.send("hello express 框架!");
})
//3.监听是否有请求
app.listen(3000, () => { //3000是端口号
//这里的代码会在服务器启动的时候执行一次
console.log("Express web server is listening at 3000 port!");
})
处理get请求
const express = require("express");
const fs = require("fs");
const path = require("path");
const app = express();
app.get("/", (req, res) => {
console.log("首页的内容");
res.send("首页");
})
app.get("/register", (req, res) => {
//获取文件路径
let filePath = path.join(__dirname, "views", "register.html"); //登录页面的路径
//读取文件内容
let content = fs.readFileSync(filePath, "utf-8");
res.send(content);
})
app.listen(3000, () => {
console.log("Express web server is listening at 3000 port!");
})
获取get请求的参数
const express = require("express");
const app = express();
app.get("/index", (req, res) => {
//请求地址假如为/index?name=express&age=11,也可以匹配到,自动搜索?前的内容
//req.query是获取到的字符串参数,是一个对象,{name: 'express', age: '11'}
console.log(req.query.name);
res.send("index首页");
})
app.get("/list", (req, res) => {
//地址为/list?curPage=1&perPage=10&catagoryId=2
res.send(req.query.curPage);
})
app.listen(3000, () => {
console.log("Express web server is listening at 3000 port!");
})
处理post请求
//客户端
<form method="POST" action="/register">
//不添加enctype="application/x-www-form-urlencoded"也行,默认已经有了
//作用是将表单里的数据编程username=express&age=11这种形式
...
</form>
//服务端
const express = require("express");
const app = express();
app.get("/index", (req, res) => {
console.log(req.query.name);
res.send("index首页");
})
app.get("/list", (req, res) => {
res.send(req.query.curPage);
})
//get请求是在页面地址栏进行请求,post是在表单提交的时候进行请求
app.post("/register", (req, res) => {
//可以在这个接口中处理post请求
res.send("post ok");
})
app.listen(3000, () => {
console.log("Express web server is listening at 3000 port!");
})
获取post请求参数
使用第三方的包body-parser更加简便专业地处理请求参数
需要在项目目录下安装body-parser(在安装express的时候已经安装了,node_mudules文件夹下可以看到)
yarn add body-parser 或者 npm install body-parser
//客户端
<form method="POST" action="/register">
//不添加enctype="application/x-www-form-urlencoded"也行,默认已经有了
//作用是将表单里的数据编程username=express&age=11这种形式
...
</form>
//服务端
const express = require("express");
const fs = require("fs");
const path = require("path");
//1.引入body-parser
const bodyParser = require("body-parser");
const app = express();
//2.把bodyParser添加到项目中,把bodyParser注册到app下(使它能在app项目中使用)
app.use(bodyParser.urlencoded({extended: false})); //false接收的值为字符串或者数组,true则为任意类型
app.use(bodyParser.json()); //将来获取之后解析为json格式
app.get("/", (req, res) => {
console.log("首页的内容");
res.send("首页");
})
app.get("/register", (req, res) => {
let filePath = path.join(__dirname, "views", "register.html"); //登录页面的路径
let content = fs.readFileSync(filePath, "utf-8");
res.send(content);
})
app.post("/register", (req, res) => {
//可以在这个接口中处理post请求,一般post提交过来后,后端需要获取提交过来的参数
//获取用户填写的数据,也叫请求参数
//3.获取参数,用req.body来获取,是一个对象
let {username, email, password, repwd} = req.body; //解构
console.log(username, email, password, repwd); //在服务端的小黑框里展示
//业务逻辑
//校验参数是不是符合规则,判空
//查询数据库,看看用户名是否被注册等等业务逻辑
res.send("post ok");
})
app.listen(3000, () => {
console.log("Express web server is listening at 3000 port!");
})
重定向
const express = require("express");
const fs = require("fs");
const path = require("path");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.get("/", (req, res) => {
res.send("首页");
})
app.get("/register", (req, res) => {
let filePath = path.join(__dirname, "views", "register.html");
let content = fs.readFileSync(filePath, "utf-8");
res.send(content);
})
//登录页面,表单post提交之后跳转到该页面
app.get("/login", (req, res) => {
let filePath = path.join(__dirname, "views", "login.html");
let content = fs.readFileSync(filePath, "utf-8");
res.send(content);
})
app.post("/register", (req, res) => {
let {username, email, password, repwd} = req.body;
console.log(username, email, password, repwd);
//不想返回这个"post ok",表单注册完提交之后跳转到login.html页面
//res.send("post ok");
//重定向
//res.writeHead(302); //修改响应头中的状态码
//res.writeHead(302, {name: "nodejs"}); //添加一个响应头的键值对
//没有上面的两行代码,重定向后在开发者工具中的状态码也为302
//上面的代码不注释的话会报错,Cannot set headers after they are sent to the Client
//意思是已经返回给浏览器了还去设置响应头
res.redirect("/login");
//本来是在这里运动代码,现在调到app.get("/login", (req, res) => {...})这个login接口里执行代码了
})
app.listen(3000, () => {
console.log("Express web server is listening at 3000 port!");
})
all方法合并相同的路径请求
//如果请求路径相同,可以用all方法合并//将下面两个合并app.get("/register", (req, res) => { let filePath = path.join(__dirname, "views", "register.html"); let content = fs.readFileSync(filePath, "utf-8"); res.send(content);})app.post("/register", (req, res) => { let {username, email, password, repwd} = req.body; console.log(username, email, password, repwd); res.redirect("/login"); })//all方法app.all("/register", (req, res) => { //先获取请求方式req.method,根据请求方式执行对应的代码处理 if(req.method === "GET") { let filePath = path.join(__dirname, "views", "register.html"); let content = fs.readFileSync(filePath, "utf-8"); res.send(content); } else if(req.method === "POST") { let {username, email, password, repwd} = req.body; console.log(username, email, password, repwd); res.redirect("/login"); }})
获取静态资源的方式
const express = require("express");const app = express();//一个项目一般都会有一个静态资源文件夹//现在图片放在public文件夹下的image文件夹下//设置在public文件夹下查找资源(以public为根去找静态资源),地址栏的/就是根的意思,//http://127.0.0.1:3000/images/01.jpg,根下的images文件夹下的jpg图片app.use(express.static("public"));//在html文件中<img src="/images/01.jpg" alt="" />地址这样写就可以获取到图片了//在静态资源的路径下加一个前缀,却不想在public文件夹和images文件夹中再新建一个文件夹,可以设置第一个参数//app.use("/static", express.static("public"));//此时的地址为http://127.0.0.1:3000/static/images/01.jpgapp.get("/", (req, res) => { res.send("hello express");})app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
art-template模板引擎的使用
使用前需要安装art-tamplate和express-art-template
yarn add art-template 或者 npm install art-templateyarn add express-art-template 或者 npm install express-art-template
在views目录下新建index.html
读取这个文件,但是不使用之前的读取路径的方法,使用art-template
const express = require("express");const path = require("path");const app = express();//下面的四句代码是模板引擎的初始化工作//引入express-art-template,使用对应引擎,原版本第一个参数是art,因为我们这里需要使用index.html,所以直接就换了,第四句代码中也是如此app.engine('html', require('express-art-template'));//项目环境的设置//生产环境(线上)production//开发环境developmentapp.set('view options', { debug: process.env.NODE_ENV !== 'production'});//设置在哪一个目录下查找模板文件(html文件)app.set('views', path.join(__dirname, 'views')); //当前路径的views文件夹下//和第一句一样需要设置模板的后缀名为htmlapp.set('view engine', 'html');app.get("/", (req, res) => { //这里就不需要引入之前的那种路径读取文件的方式了,直接这么写 //res.render是模板引擎 res.render("index"); //返回index页面给浏览器,注意这里不需要有.html后缀名,上面已经设置了})app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
art-template模板引擎传递数据
const express = require("express");const path = require("path");const app = express();app.engine('html', require('express-art-template'));app.set('view options', { debug: process.env.NODE_ENV !== 'production'});app.set('views', path.join(__dirname, 'views'));app.set('view engine', 'html');app.get("/", (req, res) => { //我们可以在接口("/"这种路径)中查询数据库,得到数据之后传递给模板展示在页面上 //现在假如给了一些数据 let data = { num1: 10, num2: 5 user: { name: zhangsan, age: 11 }, books: ["haha", "lala"] } res.render("index", data); //返回index页面给浏览器并把data传递到模板中,在index.html中就可以使用这些数据了,例如<p>{{ num1 }}</P>,就可以在页面上直接显示10了,这里用到了插值表达式,这个插值表达式的模板不是vue,而是art-template,之前装的art-template就是解析页面中例如插值表达式这些模板的,express-art-template则是在express中使用的目的 //在页面中还有很多语法,例如{{ num1 > num2 ? num1 : num2 }} //{{ user.age }},{{ user["age"] }} //数组的话需要把数组的每一项都放在li标签中,用each //<ul> // {{each books}} //用each,books为数组名 // <li>{{ $index }} {{ $value }}</li> //$index是下标,$value是每一项的值 // {{/each}} //还需要一个结束符 //</ul> //如果num1小于num2就展示下面这个p标签,否则就不展示,用if,节点移除了,找不到了,和vue的v-if是一样的 //{{if num1 > num2}} // <p>num1是小于num2的</p> //{{/if}} //如果把上面改为num1 < num2发现在编辑器里颜色不对,不是语法问题能正常编译,知识编辑器不支持这种语法格式 //还有很多语法})app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
art-template模板过滤器的使用
const express = require("express");//1.引入art-templateconst template = require("art-template");const path = require("path");const app = express();app.engine('html', require('express-art-template'));app.set('view options', { debug: process.env.NODE_ENV !== 'production'});app.set('views', path.join(__dirname, 'views'));app.set('view engine', 'html');//2.书写过滤器函数//template.defaults.imports.过滤器名称 = function(value) {// return 返回出过滤后的数据;//}template.defaults.imports.timestamp = function(value) { //value是|前面的数据,这里也就是num1 return value * 1000; //返回出过滤后的数据}//3.在模板中使用过滤器函数{{ 数据 | 过滤器名称 }}app.get("/", (req, res) => { let data = { num1: 10, } res.render("index", data); //在html文件的过滤器形式是{{ num1 | timestamp}} //注意在html文件中不要再注释里面写{{}},因为即使是注释里{{}},编译器也会认为这里需要解析})app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
在项目中使用路由
入口文件app.js应保持整洁,需要的接口应该放在routes路由文件夹下,文件夹下有passport.js(根验证相关的代码放在这里处理)。没有了app对象,就用express.router()
passport.js
const express = require("express");const fs = require("fs");const path = require("path");const router = express.router();router.get("/register", (req, res) => { let filePath = path.join(__dirname, "../views", "register.html"); //地址变了 let content = fs.readFileSync(filePath, "utf-8"); res.send(content);})router.get("/login", (req, res) => { let filePath = path.join(__dirname, "../views", "login.html"); //地址变了 let content = fs.readFileSync(filePath, "utf-8"); res.send(content);})router.post("/register", (req, res) => { let {username, email, password, repwd} = req.body; console.log(username, email, password, repwd); res.redirect("/login"); })//导出module.exports = router;
index.js
const express = require("express");const router = express.router();router.get("/", (req, res) => { console.log("首页的内容"); res.send("首页");})module.exports = router;
app.js
const express = require("express");const bodyParser = require("body-parser");//引入passport里的路由const passportRouter = require("./routes/passport");const indexRouter = require("./routes/index")const app = express();app.use(bodyParser.urlencoded({extended: false})); app.use(bodyParser.json());//把路由注册到app下app.use(passportRouter);app.use(indexRouter);app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
处理请求之前的钩子函数
const express = require("express");const bodyParser = require("body-parser");const passportRouter = require("./routes/passport");const app = express();app.use(bodyParser.urlencoded({extended: false})); app.use(bodyParser.json());//这种钩子函数的作用是,网站有些功能需要登陆之后才能使用,比如收藏、关注等,可以在这里校验是否登录,//没有登录的话必须组织继续往下执行//这种函数党政一个校验是否登录的工具,也不会放在入口文档中function checkLogin(req, res, next) { console.log("执行passportRouter的路由接口函数之前先执行这句代码"); //这里假设true是没有登录,则return直接跳出该函数,不会执行next()了 //if(true) { // res.send("登录校验没有通过"); // return; //} next(); //跳转的作用。去执行app.use后面的这个函数里面的代码,在这里也就是passportRouter函数}//第一个参数要是checkLoginapp.use(checkLogin, passportRouter); //在执行passport.js里面的函数之前就会执行checkLogin这个函数的代码app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
创建一个utils文件夹,专门放置工具函数
utils文件夹下创建index.js文件
function checkLogin(req, res, next) { console.log("执行passportRouter的路由接口函数之前先执行这句代码"); next();}//其他函数//...//因为还有其他函数,所以以对象的形式导出module.exports = { checkLogin}
app.js
const express = require("express");const bodyParser = require("body-parser");const passportRouter = require("./routes/passport");const utils = require("./utils"); //如果导入的是index.js文件,路径简写到utils就可以const app = express();app.use(bodyParser.urlencoded({extended: false})); app.use(bodyParser.json());app.use(utils.checkLogin, passportRouter);app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
pathinfo/pathname参数的获取
const express = require("express");const path = require("path");const app = express();app.engine('html', require('express-art-template'));app.set('view options', { debug: process.env.NODE_ENV !== 'production'});app.set('views', path.join(__dirname, 'views'));app.set('view engine', 'html');app.get("/list", (req, res) => { res.render("list");})app.get("/detail/:id", (req, res) => { //不同的地址的内容不一样 //:id表示动态地获取detail/后面的内容,这个参数叫做pathinfo/pathname参数 //通过req.params获取参数对象,加入点击页面新闻标题1,则拿到{id: '1'},req.params.id可以获取1 //再根据这个id去查询数据库,获取这篇文章的内容,传到模板里面去 res.send("detail详情页" + req.params.id);})//假如地址是这样的<li><a href="/detail/1/music">新闻标题1</a></li>//可以这样app.get("/detail/:id/:type", (req, res) => {...}),//相当于req.params这个对象多了一个键值对{id: '1', type: 'music'}app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
list.html
<ul> <li><a href="/detail/1">新闻标题1</a></li> <li><a href="/detail/2">新闻标题2</a></li> <li><a href="/detail/3">新闻标题3</a></li> <li><a href="/detail/4">新闻标题4</a></li></ul>
art-template模板继承
const express = require("express");const path = require("path");const app = express();app.engine('html', require('express-art-template'));app.set('view options', { debug: process.env.NODE_ENV !== 'production'});app.set('views', path.join(__dirname, 'views'));app.set('view engine', 'html');app.get("/child", (req, res) => { res.render("child");})app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
父模板也叫base模板
base.html
<!--整个网站中不同页面的公共的部分--><body> <h1>父模板</h1> <p>父模板内容</p> <p>底部</p></body>
child.html
{{extend './base.html'}} //模板继承于parent.html,地址栏输入localhost:3000/child之后,parent.html的内容都会出现
肯定子页面和父页面有的内容是不同的,这样操作parent.html
<body> <h1>父模板</h1> <!--继承中与父模板内容不一样的地方,需要加上block,contentBlock是这个块的名字,自己定义的--> {{block 'contentBlock'}} <p>父模板内容</p> {{/block}} <p>底部</p></body>
child.html
{{extend './base.html'}}{{block 'contentBlock'}} <!--这里是自己自模板的内容,contentBlock要与父模板替换的部分一致--> <div>子模板独有的内容</div>{{/block}}
状态保持技术cookie和session
因为http是一种无状态协议,浏览器请求服务器是无状态的。
无状态:指一次用户请求时,浏览器、、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因:浏览器与服务器是使用socket套接字(tcp是基于socket的)进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的socket连接,而且服务器也会在处理页面完毕之后销毁页面对象。
有时需要保持用户的浏览状态,比如用户是否登陆过,浏览过哪些商品等,实现状态保持主要由两种方式:
1.在客户端存储信息使用cookie 2.在服务端存储信息使用session
cookie
特点:
1.cookie由服务器设置的,通过响应头携带键值对形式的cookie信息,并自动保存在浏览器端的一小段文字信息
2.cookie是以键和值的形式进行存储
3.浏览器在访问一个网站的服务器时,会自动在请求头中把和本网站相关的所有cookie发送给服务器
4.cookie是基于域名安全的(CSRF)
5.cookie有过期时间,默认关闭浏览器之后过期
设置和获取cookie信息
在项目路径下安装cookie-parser
yarn add cookie-parser
const express = require("express");//引入const cookieParser = require("cookie-parser");const app = express();//注册app.use(cookieParser);//设置cookie信息app.get("/set_cookie", (req, res) => { res.cookie("name", "node", {maxAge: 60 * 60 * 2}); //参数分别为键、值、可选参数(这里是保存时间) res.cookie("age", 11); res.send("设置了cookie信息");})//获取cookie信息app.get("/get_cookie", (req, res) => { let name = req.cookies.name; let age = req.cookies["age"]; res.send(`获取到的cookie信息为:${name},${age}`);})app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
session
特点:
1.session数据保存在服务器端
2.session是以键值对的形式进行存储
3.session依赖于cookie,每个session信息对应的客户端的标识保存在cookie中
浏览器第一次向服务器发送请求,服务器设置session并保存在服务端,会有一个空间对session数据的标识(英文和数字构成,没有可读性)进行存储;服务器向浏览器返回页面信息,在cookie中保存刚刚session数据的标识而不是真正的数据(session数据并不会完完整整地暴露在浏览器上;session是依赖于cookie的),保存在浏览器上的不是session数据而是session数据的标识;浏览器下一次请求携带的cookie信息中有session数据的标识,服务端拿到session的标识,就知道浏览器的用户的身份,再去做相应的处理
设置和获取session信息
在项目路径下安装cookie-session
yarn add cookie-session
const express = require("express");//引入const cookieParser = require("cookie-session");const app = express();//注册//调用cookieParser,需要传入一些参数,相当于app关于cookie-session的设置app.use(cookieParser({ name: "my_session", keys: ["AHFAKFEJLFAEEFHA"]; //这里面的内容随便写,会有算法对其加密,作用是区别于算法加密后的内容 //虽然可以由很多session的标识保存在cookie中,但是cookie中只有一个键值对,就是这里很多session的过期时间是统一设置的 maxAge: 1000 * 60 * 60 * 24; //一天}));//设置cookie信息app.get("/set_cookie", (req, res) => { req.session["name"] = "nodejs_session"; //注意session这里是req,cookie那里是res,有差异的原因是cookie-session底层交给了req对象,所以这里用req req.session["age"] = 12; res.send("设置了cookie信息");})//获取cookie信息app.get("/get_cookie", (req, res) => { let name = req.session["name"]; let age = req.session["age"]; res.send(`获取到的cookie信息为:${name},${age}`);})app.listen(3000, () => { console.log("Express web server is listening at 3000 port!");})
数据库
例如mysql、redis(内存读取,比硬盘读取更快,但关机后内存会被清空掉,当然redis也可以做持久化存储)
数据库(是一个软件)特点:
1.持久化存储
2.读写速度极高
3.保证数据的有效性(对保存的数据格式是有要求的)
4.对程序支持性非常好,容易扩展
数据库管理系统(Database Management System,DBMS)是为管理数据库而设计的软件系统,包括三大部分构成(它们之间的关系是数据库客户端通过SQL语句告诉服务端客户端想要做什么,服务器和数据文件一般都在一台机器上,直接可以读写数据文件):
1.数据库文件集合:主要是一系列的数据文件,作用是存储数据
2.数据库服务端:主要是对数据文件以及文件中的数据进行管理
3.数据库客户端:主要负责和服务端通信,向服务端传输数据或者从服务端获取数据
**数据库分类:**关系型数据库(表与表之间的数据有关联,例如mysql)和非关系型数据库(例如Redis,是以键值对的形式存在的,没有表)
关系型数据库中核心元素:
数据行(记录,每一行成为记录)
数据列(字段)
数据表(数据行的集合)
数据库(数据表的集合)
常用的数据类型
整数:int,bit(0 1)
小数:decimal(5,2),整数字位共占5位,小数部分占两位;
字符串:char(3),3位字符串,少于三位补空格,varchar(常用)不会去补空格,text表示存储大文本,当字符大于4000时推荐使用,比如技术博客。
日期时间:date,time,datetime
枚举类型(enum)
附:SQL语句不区分大小写;对于图片、音频、视频等文件不存储在数据库中,而是上传到某个服务器上,然后在表中存储这个文件的保存路径。
数据约束
本质上是对数据在数据类型限定的基础上添加的额外的要求,保证数据的完整性和有效性
常见约束如下:
1.主键primary key:物理上存储的书序。MySql建议所有表的主键字段都叫id,类型为int unsigned(无符号)
2.非空not null:此字段不能为空值
3.唯一unique:此字段不允许重复
4.默认default:当不填写字段的时候会使用默认值,如果填写了以填写为主
5.外键foreign key:对关系字段进行约束,当为关系字段填写值时,会到关联的表中查询此值是否存在,如果存在则填写成功,如果不存在则填写失败并抛出异常(很少用,会影响查询速度)
mysql数据库的操作
--------------数据库操作*链接数据库mysql -uroot -p-u是值用户名username,用户名是root,-p是指密码password,可以在后面直接输入,也可以回车后输入密码退出数据库quit/exit/\qsql语句最后需要有分号;结尾显示数据库版本versionselect version();查看当前使用的数据库select database();*查看所有数据库show databases;*创建数据库create database 数据库名 charset=utf8;查看创建数据库的语句(了解)show create database 数据库名;查看创建表的语句也可以用这个show create table 数据表名;*使用数据库use 数据库名;删除数据库(谨慎使用)drop database 数据库名;运行批次任务文件(直接引入.sql文件,里面有各种操作的语句,直接将其导入数据库)source 文件地址(文件在电脑上的地址,注意路径不要有汉字)----------------数据表操作查看当前数据库所有表show tables;创建表(int unsigned无符号正形;auto_increment表示自动增长跟主键在一起;not null表示不能为空;primary key主键;default默认值;)create table 数据表名(字段 类型 约束[, 字段 类型 约束]);create table test(name varchar(30) not null, age int unsigned);创建classes表(有id、name两个字段)create table classes(id int unsigned primary key auto_increment, name varchar(30) not null);创建students表(有id、name、age、high(decimal)、gender(enum)、cls_id这些字段)create table students(id int unsigned primary key auto_increment, name varchar(30) not null, age int unsigned, high decimal(3,2), gender enum("男","女","保密","中性") default "保密", cls_id int unsigned);查看表结构(查看表的字段信息)desc 数据表名;desc test;修改表-添加字段mascot(吉祥物)alter table 表名 add 列名 类型;给classes表添加mascot字段alter table classes add mascot varchar(50);修改表-修改字段:不重新命名alter table 表名 modefy 列名 类型及约束;alter table classes modify mascot varchar(100);注意:只修改约束不修改类型,sql语句中也要写上类型修改表-修改字段:重新命名alter table 表名 change 原名 新名 类型及约束;alter table classes change mascot jxw int unsigned;修改表-删除字段alter table 表名 drop 列名;alter table classes drop jxw;删除表drop table 表名;drop table test;删除数据库(谨慎使用)drop database 数据库名;---------------增删改查(curd)增向表内插入数据insert into 表名(字段1,字段2) values(值1,值2)主键字段可以用0 null default来占位insert into classes(id, name) values(3, "1d22");因为id是int unsigned格式的,且有自增auto_incrementinsert into classes(id, name) values(0, "1");的id就会是之前最大的id增一,也就是id为4,和不写一样insert into classes(name) values("ss");此时id为5把多个记录一起添加insert into students(name) values("赵柳"),("马八");改update 表名 set 列1=值1,列2=值2...where 条件;全部修改(慎用)update students set age = 16;全部记录的age字段修改为16按条件修改update students set age = 20 where id = 3;按条件修改多个值update students set age = 15, high = 1.83 where id = 2;查select后的是将要展示出来的字段的信息查看表中数据查看表中所有记录数据select * from 表名;select * from classes;按条件查询查询id为1的学生的所有信息select * from students where id = 1;查询指定列select 列1,列2,.. from 表名 where 条件;如果为where 1什么都不影响,表示查全部select * from students where 1;等价于select * from students;select name, age from students;select name, age from students where id = 2;可以使用as为列或表指定别名(临时的别名,字段名是不会变的)select 字段[as 别名], 字段[as 别名] from 数据表;select name as "姓名", age as "年龄" from students;字段的顺序select age, name from students;先按age排序,相同的按照name排序select 表名.字段,... from 表名;select students.name,students.age from students;可以通过as给表起别名select 别名.字段,... from 表名 as 别名;select s.name, s.age from students as s;消除重复行(查性别,用select gender from studnets;的话会把每个记录的性别都查出来)select distinct 字段 from 表名;select distinct gender from students;select gender from students group by gender;使用分组的方式也可以,分组在下面----条件查询:比较运算符(< <= > >= = !=、<>不等号)查询年纪大于等于18岁的学生信息select * from students where age >= 18;逻辑运算符(条件1 and 条件2; between ... and)select * from students where age > 18 and age < 28;select * from students where age between 18 and 28;包含18和28,和下面的语句是一样的select * from students where age >= 18 and age <= 28;18岁以上的女性的学生信息select * from students where age > 18 and gender = "女";因为性别这个字段值是枚举型,枚举的数据类型可以用数字来表示,gender enum("男","女","保密","中性"),可以通过索引获取,注意是索引1 2 3不是下标0 1 2 3,所以上面的语句等价于select * from students where age > 18 and gender = 2;or18岁以上或者身高搞过180(包含)以上的学生信息select * from students where age > 18 or height >= 180;not不在18岁以上的女性 这个范围内的信息select * from students where not (age > 18 and gender = 2);----模糊查询like %替换任意个字符,0 1 2 3;_替换一个字符查询姓名中以"小"开始的名字的学生信息select * from students where name like "小%";查询姓名中有"小"开始的名字的学生信息select * from students where name like "%小%";查询名字是两个字的学生信息select * from students where name like "__";查询至少有两个字的名字的学生信息select * from students where name like "__%";----范围查询in,in(1, 3, 8)表示非连续的确切的值查询年龄为18或者34的学生信息select * from students where age in (18, 34);不是区间,是确切的值,等同于下面的语句select * from students where age = 18 or age = 34;not in不在非连续的范围之内年龄不是18或者34岁的学生信息select * from students where age not in (18, 34);between ... and ...表示一个连续的范围内查询年龄在18到34岁的学生信息,包含18和34岁select * from students where age between 18 and 34;not between ... and ...表示不在一个连续的范围内查询年龄不在18到34之间的学生信息select * from students where age not between 18 and 34;----空判断判空is null查询身高为空的学生信息select * from students where height is null;判非空is not nullselect * from students where height is not null;删一般企业都会进行软删除/逻辑删除,没有真正删除掉用户的宝贵的资料,而是用一个字段表示是否删除,其实还在数据库中物理删除,真的在数据库中删除了,不可逆的操作delete from 表名 where 条件delete from students;删除了整个表delete from students where id = 4;逻辑删除用一个字段来表示这条记录是否已经不能再使用了给students表添加一个is_delete字段bit类型(bit类型在展示时和int不一样,0就啥也没有,1的话就是一个框框)alter table 表名 add 字段 类型 default 默认值;alter table students add is_delete bit default 0;update 表名 set is_delete=1 where 条件;update students set is_delete=1 where id=1;--------------------排序order by 单个字段/多个字段asc为升序,默认为升序;desc为降序查询年龄在18到34之间的男性信息,按照年龄从小到大排序select * from students where gender = 1 and age between 18 and 34 order by age asc;查询年龄在18到34岁之间的女性信息,身高从高到低排序select * from students where gender = 2 and age between 18 and 34 order by height desc;查询年龄在18到34岁之间的女性信息,身高从高到低排序,如果身高相同的话按照年龄从小到大排序select * from students where gender = 2 and age between 18 and 34 order by height desc, age asc;查询年龄在18到34岁之间的女性信息,身高从高到低排序,如果身高相同的话按照年龄从小到大排序,如果年龄也相同按照id从小到大排序select * from students where gender = 2 and age between 18 and 34 order by height desc, age asc, id desc;--------------------聚合函数总数count查询一共多少人select count(*) from students;假如这样写select count(gender) from students;得到的值不是4而是所有记录的个数,重复的也算查询男性有多少人select count(*) from students where gender = 1;最大值max查询最大的年龄select max(age) from students;查询女性的最高身高select max(height) from students where gender = 2;最小值min求和sum计算所有人的年龄总和select sum(age) from students;平均值avg计算平均年龄select avg(age) from students;或者select sum(age)/count(*) from students;四舍五入round(123.23, 1)保留一位小数计算所有人的平均年龄,保留两位小数select round(avg(age), 2) from students;聚合语句avg不会计算字段值为null的记录注意:聚合函数计算的时候不会把null计算进去select round(sum(age)/count(*), 2) from students;这个语句可能和上一个语句的结果不一样,因为存在age为null的话,这个语句就会因count(*)除数变大-------------分组(要求中的字眼大概是每种、每类这样的)select 分组的字段(分完组后每组要展示的字段) from 表名 by 分组字段(怎么分的组);按照性别分组,查询所有的性别select gender from students group by gender;计算每种性别中的人数select gender, count(*) from students group by gender;group_concat(...)查询同种性别中的姓名select gender, group_concat(name) from students group by gender;多个人名之间逗号隔开查询每组性别的平均年龄select gender, avg(age) from students group by gender;having(注意having和group by连用,having后通常跟着聚合函数)分组条件having,查询条件where,聚合函数如果作为条件,只能和having配合,不能和where配合查询平均年龄超过30岁的姓名、性别和平均年龄select group_concat(name), gender,avg(age) from students group by gender having avg(age) > 30;查询每种性别中的人数多于2个的信息select name, gender, count(*) from students group by gender having count(*) > 2;----------------分页limit start(就是索引),count;注意:limit放在最后面limit(要显示第几页-1) * 每页分多少个, 每页分多少个;限制查询出来的数据个数查询前五个数据select * from students limit 5;每页分2个,显示第一页(查询前2个数据)select * from students limit 2;简写形式,必须从头开始显示的才能简写select * from students limit 0, 2;通用写法每页分2个,显示第二页select * from students limit 2, 2;每页分2个,显示第三页select * from students limit 4, 2;每页分2个,显示第六页的信息,按照年龄从小到大排序先排序还是先分页,这需要符合实际需求,实际上都是排序完之后分页显示的,所以limit放在最后select * from students order by age asc limit 10, 2;有三种用到条件的地方select * from students whereselect 分组 from students group by 分组字段 having 条件查询slect * from 表A inner join 表B on 连接的条件------------------连接查询(表与表之间的连接,为了更好地查出有效数据)select * from 表A inner join 表B on 连接的条件;查询有能够对应班级的学生以及班级信息(将表students和表classes中id相同的挑选出来);select * from students inner join classes;没有添加on条件时,classes表有几条数据,students的记录就会多几倍,完全补充一遍,但这样肯定是不行的,多了select * from students inner join classes on students.cls_id=classes.id;将id值相等的挑选出来按照要求显示姓名、班级select students.name, classes.name from students inner join classes on students.cls_id =classes.id;给数据表其名字select s.name, c.name from students as s inner join classes as c on s.cls_id = c.id;select s.name as "姓名", c.name as "所在班级" from students as s inner join classes as c on s.cls_id = c.id;查询 能够对应班级的学生以及班级信息,显示students表的所有信息和classes表的班级名称信息select s.*, c.name from students as s inner join classes as c on s.cls_id = c.id;在以上的查询基础上,将班级名称放在第一列select c.name, s.* from students as s inner join classes as c on s.cls_id = c.id;查询 能够对应班级的学生以及班级信息,按照班级名称进行排序select c.name, s.* from students as s inner join classes as c on s.cls_id = c.id order by c.name;在以上查询基础上,当同一班级时,按照学生的id进行从小到大排序select c.name, s.* from students as s inner join classes as c on s.cls_id = c.id order by c.name, s.id;----------------子查询(一个查询的结果作为另外一个查询的一部分,会把前者的查询成为子查询)查询出高于平均身高的信息select * from students where height > avg(height);这样写是错的,因为聚合函数只能用在having,不能用在whereselect * from students where height > (select avg(height) from students);查询学生的班级号能够对应的学生名字select students.name from students where cls_id in (select id from classes);
在后台服务器程序获取数据库数据
先安装mysql
yarn add mysql
有一个db.js文件,在项目中需要引入这个文件(放在node_modules文件夹同级的文件夹db下),才能对数据库进行查询
var mysql = require("mysql");//连接池var pool = mysql.createPool({ host: "localhost", user: "root", password: "", database: "qianduan_test"})//数据库连接配置,这有这里不一样需要改一下//这里的方法都是mysql这个模块里面的function query(sql, callback) { //参数一是sql语句,参数二是回调函数 pool.getConnection(function(err, connection) { connection.query(sql, function(err, rows) { callback(err, rows) connection.release() }) })}//对数据库进行增删改查操作的基础exports.query = query
app.js
const express = require("express");const db = require("./db/db");const app = express();app.get("/get_dta", (req, res) => { //查询数据库信息 db.query("select * from students", (err, data) => { console.log(data); //在黑窗口中显示 res.send(data); //在页面中展示数据库中查询到的数据,是个数组,数组内部是很多对象,每个对象就是一个记录 }) //res.send("成功显示");})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
orm(Object-Relation Mapping)
主要实现模型对象到关系数据库数据的映射,比如把数据库表中每条记录映射为一个模型对象,就是把一条记录当做一个对象,字段映射为属性,编程的时候就不用写"select * from students"这样的sql语句了,而只需要找到对应的object对象找到它内部的东西就可以了,相当于面向对象的思路来写
优点:
只需要面向对象编程,不需要面向数据库编写代码。
对数据库的操作都转化成对象属性和方法的操作。
不用编写各种数据库的sql语句,不同数据库的sql语法不完全一致。
实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异,不用关注用的是mysql、oracle…等。
通过简单的配置就能轻松更换数据库,而不需要修改代码。
开发效率高、维护方便、兼容性好
缺点:
相比较直接使用SQL语句操作数据库,有性能损失。
根据对象的操作转换为SQL语句,根据查询的结果转化为对象,在映射过程中有性能损失。
有局限性,ORM中没有提供查询功能需要会写sql语句。
把nodejs-orm文件夹放到db文件夹下,其中有index.js文件,这里引入了mysql模块,也有数据库连接的配置和其他设置
查
app.js
const express = require("express");const db = require("./db/nodejs-orm"); //也可以require("./db/nodejs-orm/index.js")const app = express();app.get("/get_dta", (req, res) => { //查询数据库信息 //创建模型:需要操作哪一个数据库表 let Students = db.model("students"); //查询所有信息 //Students.find((err, data) => { //查询所有 // res.send(data); //}) //第一个参数是数组,查询指定的字段名,数组中每一个元素就是字段名 //Students.find(["name", "age"], (err, data) => { //只有关于name和age的信息 // res.send(data); //}) //第一个参数是字符串,相当于where后面所写的条件 Students.find("age > 18 and age < 34", (err, data) => { res.send(data); }) //第一个参数是字符串,相当于where后面所写的条件,注意字符串引号的嵌套 //Students.find("name = '小月月'", (err, data) => { // res.send(data); //}) //分页查询,第一个参数为对象且Students.limit //number:第几页 //count:每页条数 //where:可选的,值是字符串 Students.limit({number: 2, count: 2}, (err, data) => { //每页分2个,显示第二页 //和mysql语句的数字一样,select * from students limit 2, 2; res.send(data); }) //Students.limit({where: "age > 18", number: 2, count: 2}, (err, data) => { // res.send(data); //}) //res.send("orm使用");})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
增
const express = require("express");const db = require("./db/nodejs-orm"); //也可以require("./db/nodejs-orm/index.js")const app = express();app.get("/get_dta", (req, res) => { let Students = db.model("students"); //添加单条数据 Students.insert({name: "赵云", age: 20}, (err, data) => { res.send(data); //返回的是一个对象,其中有一条属性insertId: 15 }) //添加多条数据 Students.insert([{name: "马超"}, {name: "关羽", age = 31}], (err, data) => { res.send("添加成功!"); }) })app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
删(物理删除,是真正的删除了,如果是逻辑删除,用下面的修改方式)
const express = require("express");const db = require("./db/nodejs-orm"); //也可以require("./db/nodejs-orm/index.js")const app = express();app.get("/get_dta", (req, res) => { let Students = db.model("students"); //删除单条数据 Students.delete("id = 21", (err, result) => { console.log(result); }) //清空表格 Students.delete((err, result) => { console.log(result); })})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
改
const express = require("express");const db = require("./db/nodejs-orm"); //也可以require("./db/nodejs-orm/index.js")const app = express();app.get("/get_dta", (req, res) => { let Students = db.model("students"); //修改数据 //只传一个对象,表示把所有纪律都修改 Students.update({age: 30}, (err, result) => { //全部的记录都会修改age为30 console.log(result); }) //回调函数前面有两个参数,第一个参数是条件(where后面的语句),第二个参数是对象,要修改的字段和值 Students.update("id = 1", {age: 18}, (err, result) => { console.log(result); })})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
自定义执行sql语句
上面的情况如果满足不了需求,直接使用自定义执行sql语句的方式
const express = require("express");const db = require("./db/nodejs-orm"); //也可以require("./db/nodejs-orm/index.js")const app = express();app.get("/get_dta", (req, res) => { let Students = db.model("students"); //自定义执行sql语句 Students.sql('select * from students', (err, results) => { res.send(data); })})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
一个接口往往不知查询一次数据库,就像用户登录时,需要查询数据库中是否注册,如果没注册,提示注册,如果注册了再进一步查询数据库,查看账号和密码是否一致,这时就要出现回调函数嵌套回调函数的情形了,可以通过async和await实现
const express = require("express");const db = require("./db/nodejs-orm"); //也可以require("./db/nodejs-orm/index.js")const app = express();app.get("/get_dta", (req, res) => { (async function() { //自执行的话可以写成匿名函数 let Students = db.model("students"); //查询成功result接收data //查询失败result接收err let result = await new Promise((resolve, reject) => { Students.find("age < 20", (err, data) => { if(err) reject(err); resolve(data); }) }) res.send(result); })(); //自执行函数})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
内部捕获异常
const express = require("express");const db = require("./db/nodejs-orm"); //也可以require("./db/nodejs-orm/index.js")const app = express();app.get("/get_dta", (req, res) => { (async function() { //自执行的话可以写成匿名函数 let Students = db.model("students"); let result; //不能写在下面try块级作用域里面,写在里面只在作用域里面获取 try{ result = await new Promise((resolve, reject) => { Students.find("age < 20", (err, data) => { if(err) reject(err); resolve(data); }) }) } catch(err) { console.log(err); //出错后服务端小黑框能捕获异常 res.send({errMsg: "数据库查询异常"}); //前端页面也有异常显示 return; //这里需要直接返回,因为不返回,下面的res.send还会执行 } res.send(result); })(); //自执行函数})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
倘若多次调用数据库,此时封装更加便捷
在db文件夹下创建handleDB.js文件
handleDB.js
const db = require("./db/nodejs-orm");async function handleDB(res, tableName, methodName, errMsg, n1, n2) { let Model = db.model(tableName); let result; try{ result = await new Promise((resolve, reject) => { if(!n1) { //n1,n2一个参数都没传 //因为methodName是字符串,所以Model.find应该用第二种调用格式Model["find"] Model[methodName]((err, data) => { if(err) reject(err); resolve(data); }) return; } //程序能够到这,说明是有n1的情况 if(!n2) { //没有传递n2 Model[methodName](n1, (err, data) => { if(err) reject(err); resolve(data); }) return; } //程序能够到这,说明n1和n2都传了 Model[methodName](n1, (err, data) => { if(err) reject(err); resolve(data); }) }) } catch(err) { console.log(err); res.send({errMsg: errMsg}); return; } result result;}module.exports = handleDB;//这里如果是module.exports = { handleDB }的形式,那么在app.js中引入handleDB并使用的话就要使用handleDB.handleDB了,相当于引入的是一个对象,使用的是对象的方法。
app.js
const express = require("express");const handleDB = require("./db/handleDB");const app = express();app.get("/get_dta", (req, res) => { (async function() { //要操作哪一个表,怎么操作(增删改查的方法名) let result = await handleDB(res, "students", "find", "数据库查询出错", "age < 20"); res.send(result); })();})app.listen(3000, () => { console.log("服务器已经启动,端口为3000")})
**注意CSRF以及token防护 **
跨域的本质:跨域的本质是服务器不能处理别的网站的js代码,别的网站:协议、域名、端口不同
用户向网站A请求时,网站A会发一个csrf_token存在cookie中,下次用户再次进入网站A时,会携带cookie并在请求头中设置一个属性X-CSRFTOKEN:csrf_token(根据cookie设置的),网站A验证cookie中的token和请求头中的token值进行对比,如果一样就是合法用户。不良网站如果通过用户访问网站A,是没办法在他的网站发出的请求的请求头中添加token的,就没办法通过网站A的验证通过,不良网站可以在ajax进行设置,但就会出现跨域的问题,所以token验证是完全能防护的。
app.js
//cookie-parser模块需要引入并注册app.all('/transfer', (req, res) => { let username = req.session["username"]; if(!username) { res.redirect("/"); } if(req.method == "GET") { //1.生成csrf_token,设置保存在cookie中 let csrf_token = getRandomString(48); //48位的token值 res.cookie("csrf_token", csrf_token); //设置token res.render('temp_transfer'); }else if(req.method == "POST") { //3.获取cookie中的token值和请求头中x-csrftoken属性的token值 //注意:这里req.headers['x-csrftoken']的'x-csrftoken'是小写的才能获取到,仅管设置的时候是'X-CSRFToken' if(req.cookies["csrf_token"] === req.headers['x-csrftoken']) { console.log("CSRF验证通过"); } else { console.log("CSRF验证不通过"); res.send("CSRF验证不通过"); return; } let {to_account, money} = req.body; }})2.在ajax中设置请求头中x-csrftoken属性的token值$.ajax({ ... headers: {'X-CSRFToken' : getCookie('csrf_token')}, ...}) //只要是post提交的都应该写一份这样的防护代码,比如登录界面//一个项目多出都会用到这样的代码,所以将其封装起来,并因其执行顺序把判断GET和POST之后的代码放在钩子函数里面(回顾之前钩子函数部分),其他部分就和没有防护时一模一样
生成图片验证码
先安装验证码获取的模块,在项目目录下
yarn add svg-captcha
使用
const Captcha = require("svg-captcha")let captchaObj = new Captcha(); //Captcha()是设置的一个类let captcha = captchaObj.getCode();captcha.text //图片验证码文本captcha.data //图片验证码图片内容信息res.setHeader('Content-Type', 'image/svg+xml'); //(设置图片的时候必须设置响应头)配合img标签的src属性请求来展示验证码图片的时候,需要设置响应头
Base64
常见的传输8bit字节代码的编码方式之一,在参数传输过程中,使用全英文没有问题,一旦涉及到中文就会出现乱码,还有网络传输的字符并不全是可打印的字符,比如二进制文件、图片等。Base64就是解决此问题的,基于64个可打印的字符来表示二进制的数据的一种方法。它并不是加密处理
Encode编码、Decode解码
一个字符占8位,每三个字符24位一起算,先转为各自的ASCII码,再转为二进制,从头开始每六位一起算,依次根据Base表得出对应的字符就完事了
加密
加密算法分为不可逆算法和可逆算法,可逆算法分为对称加密和非对称加密,对称加密使用的是同一个密钥,非对称加密是加密和解密使用不同的密钥
明文指的是原文,未加密前的数据;密文为加密后的数据
单项散列函数
特点:
加密是单向的,只能加密不能解密;加密得到的密文长度总是定长的;明文相同,密文一定相同,明文不同,密文一定不同;性能好,效率高
常见算法:MD5、SHA-256
应用场景:密码存储;文件完整性验证
对称加密
特点:使用密钥加密也可以解密;与非对称加密相比,要求双方获取相同的密钥是对称加密的主要缺点之一,但是速度快
应用场景:用户的敏感信息,比如身份证号码、手机号码等加密存储,同样显示时也需要反向解密出来。
非对称加密
客户端向服务端请求公钥,服务端生成公钥并给客户端,同时服务端这边有公钥对应的私钥;客户端利用公钥加密数据,将数据和公钥传给服务端;服务端用私钥解密密文
特点:
加密时使用公钥,解密时使用私钥;公钥是公开的,私钥是私有的;相对于对称加密性能差,但比起安全,因为公钥可以被伪造
应用场景:
在HTTPS协议中用于对称加密的私钥传输
md5的使用
安装md5包
yarn add md5
const md5 = require("md5");console.log(md5("hello"));
双重md5加盐加密的处理方式
let ret = md5(md5("hello") + "%dfafakfdh%"); //第一次md5后加上一段字符串(盐),再进行md5
Restful风格接口
RESTful中REST,即Representational State Transfer,表现层状态转化,前后端分离遵守这种风格,一般这种借口返回的是json数据
客户端用到的手段,只能是HTTP协议,在协议里,四中操作方式:GET用来获取资源,POST用来新建资源(也可以更新资源),PUT用来更新资源,DELETE用来删除资源
特点:
每一个URI代表一种资源;
客户端和服务器之间传递这种资源的某种表现层;
客户端通过四个HTTP动词,对服务器端资源进行操作,实现“表现层状态转化”
接口举例:
GET /users 获取用户列表
GET /users/1 获取id为1的用户
POST /users/4 插入id为4的用户
RESTful针对动态资源,路径为资源名称,路径不包含后缀名(动态资源不写.html)
Json Web Token(JWT)
一个轻巧的规范,允许使用JWT在两个组织之间传递安全可靠的信息
JWT由三部分组成:adhfajlfl.ahfdalsah.cnadfa,第一部分是Header(base64编码,第三部分签名的加密类型是什么,比如HS256,type:‘JWT’),第二部分是Payload载荷(核心,base64编码用户数据),第三部分是Signature签名(验证签名,验签)(加密结果,HS256(base64(header) + ‘.’+base64(payload).盐)),这个盐是服务器端的),都是加密后的结果
客户端向服务端请求jwt_token,服务端生成jwt_token并返回给客户端,客户端需要收藏请求时,携带jwt_token发给服务端,服务端验证token是不是有效的(签名验证,验证base64(header) + ‘.’+base64(payload).盐是否相等),再进行操作
安装jsonwebtoken
yarn add jsonwebtoken
生成token值
const jwt = require("jsonwebtoken");//生成jwt_token,xxx.xxxx.xxxconst token = jwt.sign({id: 1, username: "zhangsan"}, salt, {expiresIn: 60 * 60 *2});//用户数据,盐,expiresIn为过期时间,单位为秒
postman软件
用来测试接口的,后端没有前端提供的页面怎么测试,就用它
头像上传接口multer
//安装yarn add multer//引入和使用const multer = require("multer");const upload = multer({ dest: '设置上传图片的保存路径'}); //点击file插入图片后,图片保存在这个路径//接受图片提交的接口router.post("接口路径", upload.single('avatar'), (req, res) => { //avatar为file表单的那么属性 req.file; //提交之后通过req.file获取本次提交的信息对象 ... ... ...})
七牛云
企业级的对象存储平台
跨域
跨域是指浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
同源策略限制的行为:
cookie无法读取;
DOM和js对象无法获取;
Ajax请求发送不出去
为什么需要用到跨域?
自身业务是出现很多端(前后端分离开发);和第三方合作
如何处理跨域带来的ajax问题?(解决跨域问题)
1.jsonp
2.设置代理服务器
3.后端设置响应头
JSONP
处理使用ajax代码发起请求外,页面某些标签也会自动发起请求。我们可以利用script标签的src属性来发起请求,因为只是使用script标签,并没有使用js脚本,就不涉及跨域问题。
jsonp就是前段利用script在页面不刷新的情况下和服务器进行交互的一种技术。拿jsonp格式的数据去填充一个函数,json with padding a funciton 简称jsonp
使用ajax方式会出现跨域,解决方法:
普通形式
app.js
app.get("/get_data", (req, res) => { let data = { name: "nodejs", age: 12 } res.send("callback({name: 'nodejs', age: 11})");})
index.html
<script> ... function callback(data) { console.log(data); $(".sp1").html(data.name); }</script><script src="http://localhost:3000/get_data></script>
express模块使用jsonp
app.js
app.get("/get_data", (req, res) => { let data = { name: "nodejs", age: 12 } res.jsonp(data); //使用jsonp的解决跨域,是express封装的,可以公开的接口,和res.send(...)一样的形式})
index.html
<script> ... function callback(data) { console.log(data); $(".sp1").html(data.name); }</script><script src="http://localhost:3000/get_data?callback=callback"></script>//第一个callback是固定的写法,说明要用jsonp解决跨域了;第二个callback是要执行的函数的名称
后端设置响应头解决跨域问题
app.js
app.get("/get_data", (req, res) => { let data = { name: "nodejs", age: 12 } res.setHeader("Access-Control-Allow-Origin", "*"); //*表示任意的源头,也就是任意的服务器 res.send(data);})
使用CORS包来解决跨域
安装cors包
yarn add cors
app.js
const express = require("express");const cors = require("cors");const app = express;app.use(cors()); //原理就是这种方式res.setHeader("Access-Control-Allow-Origin", "*");app.get("/get_data", (req, res) => { let data = { name: "nodejs", age: 12 } res.send(data);})
洋葱执行原理
koa、router等都适用
const express = require("express");const router = express.Router();const app = express();router.get("/get_data", (req, res) => { console.log("get_data"); //2 res.send("get_data"); next();})function func1(req, res, next) { console.log("func1"); //1 next();}function func2(req, res, next) { console.log("func2"); //3}app.use(func1, router, func2);app.listen(3008, () => { console.log("服务器已经启动");})
洋葱
const express = require("express");const router = express.Router();const app = express();router.get("/get_data", (req, res) => { console.log("get_data"); // 2,洋葱的次外层 res.send("get_data"); //是洋葱的核心 next(); console.log(1111); //3,因为有res.send,从洋葱的核心返回到次外层})function func1(req, res, next) { console.log("func1"); //1,相当于洋葱的最外层 next(); //有next,去找router里的 console.log(2222); //4,从次外层再返回到最外层}function func2(req, res, next) { console.log("func2"); //5,洋葱之外的}app.use(func1, router, func2);app.listen(3008, () => { console.log("服务器已经启动");})
Koa
Koa是现在最流行的基于Node.js平台的web开发框架。koa不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写web应用变得得心应手。比express小得多,需要什么模块再去自己安装。
项目初始化以及安装koa
yarn init -yyarn add koa
app.js
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => { //ctx是context上下文,就是之前的req,res
console.log(ctx.url); //下面三句是一样的效果
console.log(ctx.req.url);
console.log(ctx.reqest.method);
ctx.body = 'Hello World'; //页面就会展示Hello World
});
app.listen(3000, () => {
console.log("server is running at port 3000!");
})
koa路由中间件
安装koa-router
yarn add koa-router
使用
const Router = require('koa-router');
let router = new Router();
router.get("/", function(ctx) {
ctx.body = "hello";
});
app.use(router.routes());
完整的
const Koa = require('koa');
const Router = require('koa-router');
const handleDB = require('./db/handleDB');
const app = new Koa();
const router = new Router();
router.get("/", async ctx => {
let ret = await handleDB(ctx.res, "info_user", "find", "数据库查询出错");
ctx.body = ret;
});
app.use(router.routes());
app.listen(3000, () => {
console.log("server is running at port 3000!")
})