前后端交互(二)
- ajax封装
- 相册空间实例
- ajax封装
- 数据库存储
- 上存图片处理
- 跨域请求的三种方法
ajax封装
给ajax函数传递一个对象,对象里面有基本的配置:
-
get/post 方法
-
路径 url
-
接收回调的结果 success(){}
ajax({ method:'get', url:'/getData', success(data){ console.log('data',data) } });ajax 函数:
function noop() {} function ajax(options) { //合并配置 options = { ...{ method:'get', url:'', success:noop }, ...options } console.log(111) //创建ajax对象 let xhr = new XMLHttpRequest(); //请求回调 xhr.onload = function () { options.success(xhr.responseText); } //打开请求配置 xhr.open(options.method,options.url,true); //发送请求 xhr.send(); } -
query数据处理
如果我们有query的数据要处理 http://localhost:9527/?a=1&b=2
这种我们要怎么请求给后端。
首先我们,应该在接口预定义个query的空对象,然后我们应该定义一处理query对象的函数
我们理想是把 {a:1,b:2} 这样的一个对象变成a=1&b=2。
//fn queryParse(obj) function queryParse(obj) { let arr = []; for (let item in obj){ arr.push(`${item}=${obj[item]}`) } return arr.join('&'); }url 处理
let queryData = queryParse({a:1,b:2}) if(queryData){ options.url += '?' + queryData; }
-
body发送数据处理
post
body发送的数据区别于url的query,它是有很多格式json 等。我们不肯像 query数据处理简单用函数拼凑。我们可以是使用内置的js的formData对象。
利用formData对象,遍历传入对象的里面的值,利用append方法加入到formData对象。这样向服务端发送formData数据
function bodyParse(obj) { let fd = new FormData(); for (let item in obj){ fd.append(item,obj[item]); } return fd } //-------------------------------// //处理body数据 let bodyData = null; if (options.data){ bodyData = bodyParse(options.data); } //发送请求 xhr.send(bodyData);
相册空间实例
-
前端上存
点击上存文件时候,添加change事件。我们在type = files的input 可以获取到这个文件的信息。他是一个FileList对象,里面有file对象和一个file的length。
好,接下来,我们要传给后端的就是这个file对象。
uploadFileEl.addEventListener('change',function () {
console.log(this.files)
for (let file of this.files)
{
uploadFile({
file
});
}
})
//发送数据给客户端
function uploadFile(data) {
ajax(
{
method:'post',
url:'/upload',
data,
success(data){
console.log(data)
}
}
)
}
-
后端接口处理
-
koa-body库
post过来的数据,我们要使用Koa-body库来处理
koa-body,根据它的特性,我们就可以用这个库帮我们处理前端传过来的数据。
特性:
- can handle requests such as:
- multipart/form-data
- application/x-www-urlencoded
- application/json
- application/json-patch+json
- application/vnd.api+json
- application/csp-report
- text/xml
- option for patch to Koa or Node, or either
- file uploads
- body, fields and files size limiting
- can handle requests such as:
-
英文版
options 传递的参数:
patchNode{Boolean} Patch request body to Node'sctx.req, defaultfalsepatchKoa{Boolean} Patch request body to Koa'sctx.request, defaulttruejsonLimit{String|Integer} The byte (if integer) limit of the JSON body, default1mbformLimit{String|Integer} The byte (if integer) limit of the form body, default56kbtextLimit{String|Integer} The byte (if integer) limit of the text body, default56kbencoding{String} Sets encoding for incoming form fields, defaultutf-8multipart{Boolean} Parse multipart bodies, defaultfalseurlencoded{Boolean} Parse urlencoded bodies, defaulttruetext{Boolean} Parse text bodies, such as XML, defaulttruejson{Boolean} Parse JSON bodies, defaulttruejsonStrict{Boolean} Toggles co-body strict mode; if set to true - only parses arrays or objects, defaulttrueincludeUnparsed{Boolean} Toggles co-body returnRawBody option; if set to true, for form encodedand and JSON requests the raw, unparsed requesty body will be attached toctx.request.bodyusing aSymbol, defaultfalseformidable{Object} Options to pass to the formidable multipart parseronError{Function} Custom error handle, if throw an error, you can customize the response - onError(error, context), default will throwstrict{Boolean} DEPRECATED If enabled, don't parse GET, HEAD, DELETE requests, defaulttrueparsedMethods{String[]} Declares the HTTP methods where bodies will be parsed, default['POST', 'PUT', 'PATCH']. Replacesstrictoption.
后端我们怎么查看files文件,可以使用
ctx.request.files.处理文件的物理路径,大小等要是用
options.formidable,里面的参数有maxFields{Integer} Limits the number of fields that the querystring parser will decode, default1000maxFieldsSize{Integer} Limits the amount of memory all fields together (except files) can allocate in bytes. If this value is exceeded, an 'error' event is emitted, default2mb (2 * 1024 * 1024)uploadDir{String} Sets the directory for placing file uploads in, defaultos.tmpDir()keepExtensions{Boolean} Files written touploadDirwill include the extensions of the original files, defaultfalsehash{String} If you want checksums calculated for incoming files, set this to either'sha1'or'md5', defaultfalsemultiples{Boolean} Multiple file uploads or no, defaulttrueonFileBegin{Function} Special callback on file begin. The function is executed directly by formidable. It can be used to rename files before saving them to disk. See the docs
中文版:
参数名 描述 类型 默认值 patchNode 将请求体打到原生 node.js 的 ctx.req中Boolean falsepatchKoa 将请求体打到 koa 的 ctx.request中Boolean truejsonLimit JSON 数据体的大小限制 String / Integer 1mbformLimit 限制表单请求体的大小 String / Integer 56kbtextLimit 限制 text body 的大小 String / Integer 56kbencoding 表单的默认编码 String utf-8multipart 是否支持 multipart-formdate的表单Boolean falseurlencoded 是否支持 urlencoded的表单Boolean truetext 是否解析 text/plain的表单Boolean truejson 是否解析 json请求体Boolean truejsonStrict 是否使用 json 严格模式, true会只处理数组和对象Boolean trueformidable 配置更多的关于 multipart的选项Object {}onError 错误处理 Function function(){}stict 严格模式,启用后不会解析 GET, HEAD, DELETE请求Boolean trueformidable 的相关配置参数
参数名 描述 类型 默认值 maxFields 限制字段的数量 Integer 1000maxFieldsSize 限制字段的最大大小 Integer 2 * 1024 * 1024uploadDir 文件上传的文件夹 String os.tmpDir()keepExtensions 保留原来的文件后缀 Boolean falsehash 如果要计算文件的 hash,则可以选择 md5/sha1String falsemultipart 是否支持多文件上传 Boolean trueonFileBegin 文件上传前的一些设置操作 Function function(name,file){}了解了koa-body后,其实我们可以它作为一个中间件返回,免得多个接口要使用的时候重复多写。还能用传入路径作为一个参数使用。
// koabody 处理数据 //upload 中间件 const koaBody = require('koa-body'); module.exports = function upload(dir) { return new koaBody({ //让他支持 `multipart-formdata` 的表单 multipart:true, //处理文件参数 formidable:{ keepExtensions:true, uploadDir : __dirname + '/../public/' + dir } }) }app.js
router.post('/upload',upload('upload'),(ctx)=>{ // console.log(ctx.request.files); ctx.body = '上存成功' }) -
-
前端优化进度条
在ajax函数中配置onload.onprogress接口。
ajax( { method:'post', url:'/upload', data, onprogress(ev){ progressEl.style.width = (ev.loaded/ev.total) * 100 + '%'; }, success(data){ taskProgressStatusEl.innerHTML = '上存成功' setTimeout(function () { li.remove(); },1000) console.log(data) } } )然后ajax函数中封装将onprogress 方法和xhr.upload.onprogress 联系起来
xhr.upload.onprogress = options.onprogress; -
图片数据持久化保存
持久化保存我们要用到mysql2这个库链接Mysql数据库
//链接数据库 let db = mysql.createConnection({ host:'localhost', port:3307, user:'root', password:'123456', database: 'ajax_photos' })使用:
// simple query 查询 connection.query( 'SELECT * FROM `table` WHERE `name` = "Page" AND `age` > 45', function(err, results, fields) { console.log(results); // results contains rows returned by server console.log(fields); // fields contains extra meta data about results, if available } ); // with placeholder 如果复杂的话可以使用占位符 ? connection.query( 'SELECT * FROM `table` WHERE `name` = ? AND `age` > ?', ['Page', 45], function(err, results) { console.log(results); } );插入数据:
//插入数据库 //INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....) db.query("insert into `photos` (`name`) values (?)",[pathname],function (err,result) { console.log('result',result) }) -
跨域访问服务器
我们这个相册实例的静态服务和路由服务分开。一个是专门提供后端返回的数据的服务器(9527),一个提供静态资源的服务器(3000)
目录:
app2 是专门获取后端穿过来的数据
appStatic是储存静态资源的, html css 等
//appStatic app.js const koa = require('koa'); const koaStatic = require('koa-static-cache'); const app = new koa(); //只提供静态服务 app.use(koaStatic('./public',{ prefix:'/public', gzip:true, dynamic:true })) app.listen(3000,()=>{ console.log('服务启动:http://localhost:3000'); })当我们请求,就会报错
http://localhost:3000/public/index.html
那我们把ajax 的url 改成 9527 端口,就会出现前端经常的遇到的问题,跨域了
Access to XMLHttpRequest at 'http://localhost:9527/getPhotos' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这里就需要说到同源策略
浏览器同源策略:
- 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源
- 源 :协议、域名和端口号
- 所以3000端口的请求9527端口的数据 即使,协议、域名一致了,端口号不一致。他就违背了同源策略,不能请求
- ajax会受到同源策略影响
处理方法:
-
jsonp封装
-
ngnix, apache
-
CORS跨域设置(需要后端配合)
CORS(Cross-origin resource sharing),跨域资源共享,是一份浏览器技术的规范,用来避开浏览器的同源策略
简单来说就是解决跨域问题的除了jsonp外的另一种方法;比jsonp更加优雅。
1.('Access-Control-Allow-Origin', '*') //这个表示任意域名都可以访问,默认不能携带cookie了。(必须字段)
简单就是说,往前端发送
Access-Control-Allow-Origin这个头信息,第二参数是接收方的。res.header('Access-Control-Allow-Origin', 'http://www.baidu.com'); //这样写,只有www.baidu.com 可以访问。 res.header('Access-Control-Allow-Origin', '*'); //这个表示任意域名都可以访问。 判断黑白名单,根据返过来的ctx 里面的和数据判断,是否可以设置跨域代码:
创建li时候带上图片资源地址的域,ajax url: 也要修改域
function createLI(data) { let imgLi = document.createElement('li'); data.name = 'http://localhost:9527/public/upload/' + data.name ; imgLi.innerHTML = ` <img src="${data.name}" alt=""> <div class="imgName">${data.imgName}</div>` contentUlEL.appendChild(imgLi); } //getphotos //获取全部 图片 function getAllImage() { ajax({ method: 'get', url: 'http://localhost:9527/getPhotos',2、预检请求(post)
-
简单的请求直接发送,不用发送预检
使用下列方法之一: GET HEAD POST
除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:
以下合集是安全合集,可以设置
Accept Accept-Language Content-Language Content-Type (需要注意额外的限制) DPR Downlink Save-Data Viewport-Width Width
Content-Type 的值仅限于下列三者之一: text/plain multipart/form-data application/x-www-form-urlencoded
请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;
XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 请求中没有使用 ReadableStream 对象。
-
预检请求
那要怎么做,需要在路由配置中,判断是否OPTIONS,然后设置请求头为通过。
PUT DELETE CONNECT OPTIONS TRACE PATCH //发送这个头过去 Access-Control-Allow-Methods: POST, GET, OPTIONS 首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法 ..还有其他更多方法
-
-
代理proxy( koa-server-http-proxy)
-
跨域是浏览器规范,通过同服务器请求数据,不通过浏览器请求,也能解决浏览器限制;
-
转发请求
-
利用http模块实现简单的服务器转发
-
利用 koa-server-http-proxy中间件实现代理
app.use(koaServerHttpProxy('/api', { target: 'http://localhost:4000', pathRewrite: { '^/api': '' } }))
node原生
nodejs在提供静态服务的同时,提供静态代理服务。利用node http模块:
http.request(options[, callback]) http.request(url, [options] [, callback])
作用:为每个服务器维护多个连接以发出 HTTP 请求。 此函数允许显式地发出请求。
因为Node里面没有浏览器的同源策略 , 当我们index.html发起请求时候,通过/api 代理了跨域请求的9527端口的/ getPhotos
官网例子:
const postData = querystring.stringify({ 'msg': '你好世界' }); const options = { protocol:'http:', hostname: 'nodejs.cn', port: 80, path: '/upload', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; //有可能是异步 const req = http.request(options, (res) => { console.log(`状态码: ${res.statusCode}`); console.log(`响应头: ${JSON.stringify(res.headers)}`); res.setEncoding('utf8'); res.on('data', (chunk) => { console.log(`响应主体: ${chunk}`); }); res.on('end', () => { console.log('响应中已无数据'); }); }); req.on('error', (e) => { console.error(`请求遇到问题: ${e.message}`); }); // 将数据写入请求主体。 req.write(postData); req.end(); // Proxy appStatic app.js //proxy //跨域服务代理 app.use(async (ctx,next)=>{ let data = await proxyRequest(ctx); ctx.body =data; await next(); }) function proxyRequest(ctx) { return new Promise(resolve=>{ const options = { protocol:'http:', hostname: 'localhost', port: 9527, path: '/getPhotos', method: 'get', headers: ctx.request.headers }; const req = http.request(options,(res)=>{ let data ='' res.on('data', (chunk) => { data += chunk; console.log(`data: ${chunk}`); }); res.on('end', () => { resolve(data); console.log('no data query'); }); }); req.on('error', (e) => { console.error(`请求遇到问题: ${e.message}`); }); req.write(''); req.end(); }) }利用 koa-server-http-proxy中间件实现代理
const Koa = require('koa') const app = new Koa() const proxy = require('koa-server-http-proxy') app.use(proxy('/api', { //需要跨域的地址 target: 'https://news-at.zhihu.com', //将api重写成目标服务器的地址 pathRewrite: { '^/api': 'api/4/' }, changeOrigin: true })) app.listen(3000) -
用户登录鉴权
一、jwt:json web token是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准,推荐放在头信息,防止拦截伪造
二、生成token
-
jsonwebtoken 模块
-
const token = jwt.sign({ name: reslut[0].username, _id: reslut[0].id }, 'my_token', { expiresIn: '2h' });
-
三、缓存token:cookie 或者是 locaStroage
通过ajax对象拿到头信息。
四、token的组成
-
头部的基本信息;
{ "typ": "JWT", "alg": "HS256" } -
paload :存放自定义信息 ; 预定义信息有如下几个:
iss: 该JWT的签发者 sub: 该JWT所面向的用户 aud: 接收该JWT的一方 exp(expires): 什么时候过期,这里是一个Unix时间戳 iat(issued at): 在什么时候签发的 -
signature 签名 哈希需要有secret;
Access-Control-Allow-Credentials:布尔值 true允许携带凭证;(可选字段)
//客户端设置允许携带用户凭证
xhr.withCredentials = true;
//服务端设置允许携带凭证
ctx.set("Access-Control-Allow-Credentials",true);
五、前端的认证
koa-jwt 库验证
"Authorization","Bearer " + token