前端页面
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<style>
.upload-btn {
width: 100px;
height: 100px;
background: #ccc;
line-height: 100px;
text-align: center;
}
#fileinput {
display: none;
}
</style>
</head>
<body>
<div class="upload-btn">上传图片</div>
<input type="file" id="fileinput">
<script>
var btn = document.querySelector( '.upload-btn' );
var input = document.querySelector( '#fileinput' );
var sourceImgUrl = '';
btn.onclick = function() {
input.click();
};
input.onchange = function( e ) {
var $event = e || window.event;
var files = $event.target.files;
console.log( files );
var fr = new FileReader();
fr.onload = function( e ) {
console.log( fr.result );
sourceImgUrl = getFileBlob( fr.result );
upload( sourceImgUrl );
};
fr.readAsDataURL( files[0] );
};
function getFileBlob( file ) {
var data = file.split( ',' )[1];
// atob方法用于解码使用 base-64 编码的字符串。
data = window.atob( data );
// Uint8Array 数组类型表示一个8位无符号整型数组,创建时内容被初始化为0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。
var ia = new Uint8Array( data.length );
for ( var i = 0; i < data.length; i++ ) {
ia[i] = data.charCodeAt( i );
}
// Blob() 构造函数返回一个新的 Blob 对象。 blob的内容由参数数组中给出的值的串联组成。
return new Blob( [ia], {type: 'image/png'} );
}
function upload( data ) {
/*
FormData()
创建一个新的 FormData 对象。
FormData.append()
向 FormData 中添加新的属性值,FormData 对应的属性值存在也不会覆盖原值,而是新增一个值,如果 属性不存在则新增一项属性值。
formData.append(name, value);
formData.append(name, value, filename);
name
value中包含的数据对应的表单名称。
value
表单的值。可以是USVString 或 Blob (包括子类型,如 File)。
filename 可选
传给服务器的文件名称 (一个 USVString), 当一个 Blob 或 File 被作为第二个参数的时候, Blob 对象的默认文件名是 "blob"。 File 对象的默认文件名是该文件的名称。
* */
var fmData = new FormData();
fmData.append( 'avatar', data, 'avatar.png' );
axios.post( 'http://localhost:7001/uploadImg', fmData ).then( res => {
console.log( res );
var src = `http://localhost:7001${res.data.data[0]}`;
var img = document.createElement( 'img' );
img.src = src;
if ( document.querySelector( 'img' ) ) {
document.querySelector( 'img' ).remove();
}
document.body.appendChild( img );
} );
}
</script>
</body>
</html>
前端页面如果需要预览与裁剪或更多的功能可自行添加
后端准备工作
我们推荐直接使用脚手架,只需几条简单指令,即可快速生成项目(npm >=6.1.0):
$ mkdir egg-test && cd egg-test
$ npm init egg --type=simple
$ npm i
$ npm install egg-cors
启动项目:
$ npm run dev
$ open http://localhost:7001
设置跨域
// config.default.js
// 跨域设置
config.security = {
csrf: {
enable: false
},
domainWhiteList: [ '*' ]
};
config.cors = {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
};
// 静态目录配置
config.static = {
prefix: '/public',
dir: path.join(appInfo.baseDir, 'app/public'),
dynamic: true, // 如果当前访问的静态资源没有缓存,则缓存静态文件,和`preload`配合使用;
preload: false,
maxAge: 31536000, // in prod env, 0 in other envs
buffer: true, // in prod env, false in other envs
maxFiles: 1000
};
// plugin.js
// 跨域设置
cors: {
enable: true,
package: 'egg-cors'
},
新建图片文件临时存储目录和前端页面
添加路由
// router.js
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
// 上传图片
router.post('/uploadImg', controller.upload.uploadImg);
};
在controller新建upload.js
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async uploadImg(){
const { ctx } = this;
ctx.body = await ctx.service.upload.uploadImg();
}
}
module.exports = HomeController;
新建service目录及upload.js
// upload.js
'use strict';
const { Service } = require('egg');
const fs = require('fs');
const path = require('path');
const pump = require('mz-modules/pump');
class UserService extends Service {
// 上传图片
async uploadImg() {
// 添加用户信息
const { ctx } = this;
const parts = ctx.multipart({ autoFields: true });
const urls = [];
let stream;
while ((stream = await parts()) != null) {
const fileType = stream.mimeType.split('/')[ 1 ];
const filename = Date.now() + '.' + fileType || stream.filename.toLowerCase();
const target = path.join(this.config.baseDir, 'app/public/temporary', filename);
urls.push(`/public/temporary/${filename}`);
const writeStream = fs.createWriteStream(target);
await pump(stream, writeStream);
}
console.log(urls);
return ctx.helper.success({ ctx, res: urls });
}
}
module.exports = UserService;
新建extend目录及helper.js
// helper.js
// 处理成功响应
exports.success = ({ ctx, res = null, msg = '请求成功' }) => {
// 请求日志
const { method } = ctx.request;
if (method === 'GET') {
ctx.logger.info('request.query', ctx.request.query);
} else {
ctx.logger.info('request.body', ctx.request.body);
}
// 返回日志
ctx.logger.info('response.body', {
code: 0,
data: res,
msg
});
ctx.status = 200;
return {
code: 0,
data: res,
msg
};
};
重启egg项目
在浏览器打开test.html
此刻就可以体验上传图片了!
上传图片逻辑梳理
- 前端上传图片
- 后端接收图片流,生成图片存储在临时目录
- 返回临时图片地址供前端预览
- 待前端将表单其他数据及临时图片地址成功提交到后台后,后台将使用到的图片从临时目录转存到资源服务器并生成新的访问地址存储到数据库!
网友评论