一、EGG框架简介和安装
1.egg.js
- egg.js是约定(按照一套统一的约定进行应用开发 )优先于配置的一个Node.js web 框架
- 基于koa为底层 由阿里nodejs团队封装的企业级Web应用解决方案
- 核心设计理念:约束和规范化团队开发、帮助开发团队和开发人员降低开发和维护成本
- 为企业级框架和应用而生 可以开发企业级的应用
- 可以通过egg更平滑地学习后端的相关知识
2.egg安装
- 创建一个项目文件夹
- 在此目录下cmd打开命令行窗口
- 输入快速初始化指令 npm init egg --type=simple ; npm i
- 启动项目 npm run dev
注:下载过程中可能因为种种原因导致丢包,重新安装或npm i即可
3.约定规则
app/router.js:用于配置URL路由规则
app/controller/:用于解析用户的输入,处理后返回相应的结果
app/service/: 用于编写业务逻辑层
app/public/: 用于放置静态资源
config/config.default.js: 用于编写配置文件
config/plugin.js 用于配置需要加载的插件
二、骨架认知
1.静态资源 public
- public文件夹内部的所有文件或文件夹都将被托管起来
2.路由Router
- 主要用来描述请求 URL 和具体承担执行动作的 Controller 的对应关系
- 即用户访问不同的路径时应该有不同的Controller去响应不同的内容
1)注册路由:
- 当用户访问的pathname为/test时 后端就会执行controller文件夹中的home.js文件夹中的test函数
router.get('/test', controller.home.test);
2)注册的路由名与前端静态资源文件名冲突时优先访问静态资源:
- 优先读取public的静态资源 没有就去看路由有没有注册过 也没有注册过就返回404
- 网络请求时 后端的处理顺序:静态文件>路由匹配(按照顺序匹配)
示例:
3)相同的路由名仅匹配第一个函数, 不同的路由名可以调相同的函数
- 相同的路由名从上至下仅匹配一次函数 不会继续匹配 即执行test1函数不会执行test2函数
4)星号路由 * 代表所有的网址都能匹配
- 一般放在最后 否则在其之后所有的文件都会调用这个函数 不会调用原本对应的函数
3.控制器Controller
- 负责解析用户的输入 处理后返回相应的结果
- 可以自定义要导出模块的类继承Controller
- 类里面的每一个方法都可以作为一个 Controller 在 Router 中引用到
- 可以从 app.controller 根据文件名和方法名定位到这个类
- 项目中的 Controller 类继承于 egg.Controller
- 所有的Controller 文件都必须放在 app/controller目录下
- 支持多级目录,访问时可以通过目录名级联访问
this的属性:
this.ctx: 当前请求的上下文 Context 对象的实例,通过它可以拿到框架封装好的处理当前请求的各种便捷属性和方法
this.ctx.body="": body指的是数据包的响应体 相当于res.end 只执行一次 后断开网络连接
this.app: 当前应用 Application 对象的实例,通过它可以拿到框架提供的全局对象和方法
this.service:应用定义的 Service,通过它可以访问到抽象出的业务层==> this.ctx.service
this.config:应用运行时的配置项
this.logger:logger 对象,上面有四个方法(debug,info,warn,error),分别代表打印四个不同级别的日志,使用方法和效果与 context logger 中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置
三、跨域
1.CORS配置
- egg-cors 插件实现cors跨域请求
1)下载
npm i egg-cors
2)开启并配置插件
![](https://i-blog.csdnimg.cn/blog_migrate/636fcfe1ccae507eea9b7b76a9f95918.png)
默认origin只支持一个具体的域名或*表示全部,如果想支持具体的多个指定域名需进行如下设置:
config.cors = {
origin:function(ctx) { //设置允许来自指定域名请求
console.log(ctx);
const whiteList = ['http://www.baidu.com','http://www.hqyj.com']; //白名单列表
let url = ctx.request.header.origin;
if(whiteList.includes(url)){
return url;
}
return 'http://localhost' //默认允许本地请求可跨域
},
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
};
本来本地服务器是不能请求到7001服务器的数据的 但是因为配置了cors插件 允许所有的服务器访问 解决了跨域问题
2.JSONP配置
- 如果前端的参数中有cb=fn参数(jsonp接口参数),将会返回JSONP格式的数据,否则返回JSON格式的数据 设置如下:
通过返回的函数名判断是否存在函数,若存在就执行函数
3.代理配置
egg中的网络请求技术:
this.ctx.curl(url, option)
option常用配置:
method:'GET/POST'
data:{name:"karen"}//会自动字符串化
返回一个promise对象
示例:(仅以百度为示例)
index.html自己请求百度网址的数据时会跨域 报错
解决办法:
四、GET请求前后端
- 发送的数据会显示在数据包中 参数拼接到url中 不安全 但速度快
- egg后端接收前端GET发送的参数通过this.ctx.request.query获取
1.ajax发送GET请求
- GET请求将参数传给后端
- 后端返回的数据由前端的xhr对象接收 程序员用js语言来使用返回的数据
示例:
get.html
<h1>ajax发送GET请求</h1>
<input type="text" id="search">
<button onclick="fn()">搜索</button>
<script>
function fn() {
var searchvalue=document.querySelector("#search").value
var xhr = new XMLHttpRequest()
var url=`http://192.168.43.17:7001/get/?keyword=${searchvalue}`
xhr.open("GET",url)
xhr.send()
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText)
}
}
}
</script>
router.js
router.get('/get', controller.home.get);
home.js
async get() {
//keyword为egg后端接收到的前端GET请求发送的参数
var keyword = this.ctx.request.query
console.log(keyword)
this.ctx.body = {
info: "get接口的数据"
}
}
请求结果:
2.axios发送GET请求
- ajax技术封装出来的 底层就是ajax 参数可以写在对象中{params} 也可以像ajax直接拼在url中
- 后端返回的数据由前端的xhr对象接收 程序员用js语言来使用返回的数据
示例:
get.html
<h1>axios发送GET请求</h1>
<input type="text" id="search">
<button onclick="fn()">搜索</button>
<script>
function fn() {
var searchvalue = document.querySelector("#search").value
var url = `http://192.168.43.17:7001/get`
axios(url, {params: {keyword: searchvalue}})
.then(res=>console.log(res))
/*参数querystring直接拼在网址中
axios("协议://ip:port/pathname?参数querystring")
.then(res=>console.log(res))
*/
}
</script>
router.js
router.get('/get', controller.home.get);
home.js
async get() {
//keyword为egg后端接收到的前端GET请求发送的参数
var keyword = this.ctx.request.query
console.log(keyword)
this.ctx.body = {
info: "get接口的数据--axios"
}
}
请求结果:
3.浏览器的地址栏发送GET请求
- 只能发送GET请求
- 接收的数据浏览器会直接读取渲染(图片、文本等) 读取/解析失败如压缩包会下载
4.a-href发送GET请求
- 只能发送GET请求
- 点击事件触发了默认事件才会发送GET请求(必须满足两个事件的条件)
- 即先触发a标签的点击事件 再触发href的网络请求事件
- 发送网络请求给href的网址,后端返回的数据,会直接读取渲染(target决定是否在新窗口渲染),读取(解析)失败如压缩包会下载
示例:
<a href="http://192.168.43.17:7001/get?name=嘻嘻嘻">a标签做网络请求</a>
请求结果:
5.img-src发送GET请求
- 只能发送GET请求
- 返回的数据渲染成图片 如果是非图片编码就会“碎裂” 地址正确一定能请求到编码 只关心能否渲染
6.link-href发送GET请求
- 只能发送GET请求 返回的数据按照加载的数据的对应功能使用
7.form发送GET请求
- 唯一能传输文件的网络请求技术
- 可以发送get/post/delete等请求 给action属性对应的url发送请求
- 用户点击了(表单内部)提交按钮或者触发表单的提交事件event.submit()
- GET请求会把form表单中的数据全部解析为url的querystring发送给后端
- 返回的数据 同a标签 渲染/下载
示例:
<form action="http://192.168.43.17:7001/get" method="get" target="_blank">
//表单的input标签的name属性的值就是querystring的key值
<input type="text" name="a">
<input type="text" name="b">
<button type="submit">提交</button>
</form>
请求结果:
五、POST请求前后端
- 不会把用户的隐私数据信息直接拼到url中发给后端
- 用暗文发送 可以发送大量的数据给后端 响应数据相对较慢 但是较为安全
- POST请求时 会有安全验证问题 需要关闭安全验证
- egg后端接收POST字段和文件通过 this.ctx.request.body | this.ctx.request.files
1.ajax发送POST请求
示例:
post.html
<h1>ajax发送POST请求</h1>
账号:<input type="text" id="id"><br>
密码:<input type="text" id="pwd">
<button onclick="fn()">登录</button>
<script>
function fn(){
var id=document.querySelector("#id").value
var pwd=document.querySelector("#pwd").value
var xhr = new XMLHttpRequest()
var url="http://192.168.43.17:7001/post"
xhr.open("POST",url,true)
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded")
xhr.send(`id=${id}&pwd=${pwd}`)
//send函数接收字符串querystring
//如果是“POST”就会把这个请求的数据放在“请求数据包”-HTTPRequestMessage的请求体中的
//如果是“GET” 不会报错 但是不会把这个数据拼接到url中发送
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText)
}
}
}
router.js
router.post('/post', controller.home.post);
home.js
async post() {
//前端POST发送给egg的参数字段
let obj= this.ctx.request.body
console.log(obj)
this.ctx.body = {
info: "post接口的数据",
res:obj
}
}
请求结果:
2.axios发送POST请求
1)axios.post(url,{参数字段}).then()
示例:
<h1>axios发送POST请求</h1>
账号:<input type="text" id="id"><br>
密码:<input type="text" id="pwd">
<button onclick="fn()">登录</button>
<script>
function fn(){
var id=document.querySelector("#id").value
var pwd=document.querySelector("#pwd").value
var url="http://192.168.43.17:7001/post"
axios.post(url,{id:id,pwd:pwd}).then(res=>console.log(res))
}
</script>
请求结果:
![](https://i-blog.csdnimg.cn/blog_migrate/84a12afe8c6482584df94b52b23a30f3.png)
2)axios.post(url,参数字段/文件).then()
1.上传文件
示例:
postfile.html
<h1>POST发送文件</h1>
账号:<input type="text" id="id"><br><br>
密码:<input type="text" id="pwd"><br><br>
选择头像:<input type="file" id="f1" multiple><br><br>
<button onclick="fn()">登录</button>
<script>
//给后端发送“大量”数据 文件 处理成表单数据
function fn() {
var id=document.querySelector("#id").value
var pwd=document.querySelector("#pwd").value
var f1=document.querySelector("#f1")
var url="http://192.168.43.17:7001/postfile"
var fdata = new FormData()
fdata.append("id", id)
fdata.append("pwd", pwd)
fdata.append("img", f1.files[0])
console.log(f1.files[0])
axios.post(url,fdata).then(res=>console.log(res))
}
</script>
router.js
router.post('/postfile', controller.home.postfile);
home.js
async postfile() {
//前端POST发送给egg的参数字段
let ziduan= this.ctx.request.body
//前端POST发送给egg的参数文件
let f=this.ctx.request.files //必须启用file接收文件
console.log(ziduan,f)
this.ctx.body = {
info: "postfile接口的数据"
}
}
请求结果:
2.移动文件
- 用户上传的文件会保存在服务器的计算的临时路径 需要移动到项目目录中(在同一个磁盘下)
示例:
home.js
//引入模块
const fs=require("fs")
const path=require("path")
async postfile() {
//前端POST发送给egg的参数字段
let ziduan= this.ctx.request.body
//前端POST发送给egg的参数文件
let f=this.ctx.request.files //必须启用file接收文件
console.log(ziduan,f)
if(f[0]){
let oldpath=f[0].filepath
let fname=path.basename(oldpath)
let newpath=__dirname+"/../public/upload"+fname //将文件移动到upload中
fs.renameSync(oldpath,newpath)
}
this.ctx.body = {
info: "postfile接口的数据"
}
}
六、前端注册实例
1.前端表单验证--减轻服务器的负载--DOM操作写提示
-邮箱验证:绑定change事件 判断value是否符合邮箱正则
-密码验证:密码要符合格式(自己定制)两次密码一致
-昵称验证:昵称要符合格式(自己定制)
-身份证的验证:图片的格式和大小清晰度-预览
2.点击提交按钮时 获取用户交互的信息
3.把数据post发送给后端
4.等待后端返回数据
5.操作数据
register.html
<style>
.main {
width: 600px;
height: 800px;
margin: 0 auto;
text-align: center;
border: 1px black solid;
}
.idbox {
width: 200px;
height: 100px;
background-image: url("img/rose.jpg");
background-size: 200px 100px;
margin: auto;
}
#idimg {
width: 200px;
height: 100px;
opacity: 0;
}
.txbox {
width: 100px;
height: 100px;
background-image: url("img/rose1.jpg");
background-size: 200px 100px;
margin: auto;
}
#tximg {
width: 100px;
height: 100px;
opacity: 0;
}
</style>
<div class="main">
<h1>用户注册</h1>
<hr>
昵称: <input type="text" id="nc"><br><br>
邮箱: <input type="text" id="email"><br><br>
密码: <input type="password" id="pwd1"><br><br>
确认密码: <input type="password" id="pwd2"><br><br>
上传身份证: <div class="idbox"> <input type="file" id="idimg"></div>
<span style="color: red;visibility: hidden;" id="span1">/* 只能是png、jpeg、jpg格式的图片 */</span><br>
<span style="color: red;visibility: hidden;" id="span2">/* 小于了15kb或大于了1M */</span><br>
选择头像:<div class="txbox"> <input type="file" id="tximg"></div>
<br>
<button onclick="fn()">提交</button>
</div>
<script>
var arr = new Array(6).fill(true)
//邮箱验证
var email = document.querySelector("#email")
email.addEventListener("change", () => {
var reg = /^[a-zA-Z0-9]+([-_.][A-Za-zd]+)*@([a-zA-Z0-9]+[-.])+[A-Za-zd]{2,5}$/
if (reg.test(email.value)) {
email.style.border = "3px solid green"
arr[0] = true
} else {
email.style.border = "3px solid red"
arr[0] = false
}
})
//密码验证
var pwd1 = document.querySelector("#pwd1")
var pwd2 = document.querySelector("#pwd2")
pwd1.addEventListener("change", () => {
/*1.密码必须由字母、数字组成,区分大小写
2.密码长度为8-18位
*/
var reg = /(?=.*[a-zA-Z])(?=.*[0-9])[A-Za-z0-9]{8,18}$/
if (reg.test(pwd1.value)) {
pwd1.style.border = "3px solid green"
arr[1] = true
} else {
pwd1.style.border = "3px solid red"
arr[1] = false
}
})
pwd2.addEventListener("change", () => {
/*1.密码必须由字母、数字组成,区分大小写
2.密码长度为8-18位
*/
var reg = /(?=.*[a-zA-Z])(?=.*[0-9])[A-Za-z0-9]{8,18}$/
if (reg.test(pwd2.value) && (pwd2.value == pwd1.value)) {
pwd1.style.border = "3px solid green"
arr[2] = true
} else {
pwd2.style.border = "3px solid red"
arr[2] = false
}
})
//昵称验证
var nc = document.querySelector("#nc")
nc.addEventListener("change", () => {
var reg = /(^[\u4E00-\u9FA5·]{2,16}$)|^[a-zA-Z][a-zA-Z\s]{0,20}[a-zA-Z]$/
if (reg.test(nc.value)) {
nc.style.border = "3px solid green"
arr[3] = true
} else {
nc.style.border = "3px solid red"
arr[3] = false
}
})
//身份证验证
var idimg = document.querySelector("#idimg")
idimg.addEventListener("change", () => {
console.log(idimg.files)
//预览
var url1 = window.URL.createObjectURL(idimg.files[0])
console.log(url1)
var idbox = document.querySelector(".idbox")
idbox.style.backgroundImage = `url(${url1})`
//验证格式和大小
var arr = ["image/png", "image/jpeg", "image/jpg"]
if (arr.includes(idimg.files[0].type)) {
span1.style.visibility = "hidden"
arr[4] = true
} else {
span1.style.visibility = ""
arr[4] = false
}
if (15 * 1024 > idimg.files[0].size || idimg.files[0].size > 1 * 1024 * 1024) {
span2.style.visibility = ""
arr[4] = false
} else {
span2.style.visibility = "hidden"
arr[4] = true
}
})
//头像验证
var tximg = document.querySelector("#tximg")
tximg.addEventListener("change", () => {
var txbox = document.querySelector(".txbox")
var url = window.URL.createObjectURL(tximg.files[0])
txbox.style.backgroundImage = `url(${url})`
})
async function fn() {
if (arr.includes(false)) {
} else {
//所有的验证都通过了才会向后端发送数据
var fdata = new FormData()
var nc = document.querySelector("#nc")
var email = document.querySelector("#email")
var pwd = document.querySelector("#pwd")
var idimg = document.querySelector("#idimg")
var tximg = document.querySelector("#tximg")
fdata.append("昵称",nc.value)
fdata.append("邮箱",email.value)
fdata.append("密码",pwd1.value)
fdata.append("身份证",idimg.files[0])
fdata.append("头像",tximg.files[0])
var re = await axios.post("/register", fdata)
console.log(re)
}
}
</script>
register.js
async register() {
let ziduan=this.ctx.request.body
let f1=this.ctx.request.files
console.log(ziduan,f1)
this.ctx.body="注册"
}
router.js
router.post('/register', controller.register.register);
请求结果: