react+koa实现图片上传功能

技术栈

koa2 + react16.8
github 地址连接javascript

项目步骤

  • 前端开发

    1.建立react app
    yarn create react-app my-app
    2.用material-ui编写页面,代码以下html

    <form action="/upload"  method="post" encType="multipart/form-data" style={{width:'100%'}}>
      <div>
        <input
            accept="image/*"
            className={classes.input}
            id="contained-button-file"
            multiple
            type="file"
            onChange={uploadImg}
    
        />
        <label htmlFor="contained-button-file">
          <Button variant="contained"
                  component="span"
                  className={classes.button}
          >
            添加图片
          </Button>
        </label>
        <GridList cellHeight={100} className={classes.gridList} cols={3}>
          {/*{console.log('html',values.images&&values.images)}*/}
          { values.images && values.images.map((item,index) => (
              <GridListTile key={index}
                            cols={item.cols || 1}
                            data-index = {index}
              >
    
                <strong>{item.name}</strong>
                <a href="javascript:void(0)"
                   className="upload-delete"
                   title="删除"
                   data-index = {index}
                   onClick={deleteImg}
                >删除{index}</a>
                <br/>
                <img src={item.thumb} />
              </GridListTile>
          ))}
    
        </GridList>
    
      </div>
    </form>
    {
      values.images &&values.images.length ?
    
          <Button variant="contained"
                  color="primary"
                  style={{height:'40px',width:'20%'}}
                  className={classes.margin}
                  onClick={() => handleUpload()}>
            上传图片
          </Button>:''
    }

    3.其中须要用的函数代码以下:前端

    const classes = useStyles();
    const [values, setValues] = React.useState({
      age: '',
      images:[],
      uploadHistory:[],
    });
    
    function uploadImg(e) {
      e.preventDefault();
      let target = e.target;
      let files = target.files;
      let count = files.length;
      for (let i = 0; i < count; i++) {
        files[i].thumb = URL.createObjectURL(files[i])
      }
      // convert to array
      //Array.prototype.slice.call(arguments)能将具备length属性的对象(key值为数字)转成数组。
      // []是Array的示例,因此能够直接使用[].slice()方法。
      files = Array.prototype.slice.call(files, 0);
      files = files.filter(function (file) {
        return /image/i.test(file.type)
      });
      setValues(oldValues => ({
        ...oldValues,
        images: values.images.concat(files)
      }));
    
    }
    
    const deleteImg = (e) => {
    
      let index = e.target.getAttribute('data-index');
      let result = values.images.splice(index,1);
    
      setValues(oldValues => ({
        ...oldValues,
        images: values.images
      }));
    };
    function handleUpload() {
      for (let i = 0, file; file = values.images[i]; i++) {
        ((file) => {
          let xhr = new XMLHttpRequest();
          if (xhr.upload) {
            // 上传中
            console.log('上传中')
            xhr.upload.addEventListener("progress", (e) => {
              // handleProgress(file, e.loaded, e.total, i);
            }, false);
    
            // 文件上传成功或是失败
            xhr.onreadystatechange = (e) => {
              if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                  console.log('handleSuccess'  ,file )
                  // handleSuccess(file, xhr.responseText);
                  // this.handleDeleteFile(file);
    
                  if (!values.images.length) {
                    //所有完毕
                    handleComplete();
                    console.log('所有上传完成!');
                  }
                } else {
                  handleFailure(file, xhr.responseText);
                  console.log('上传出错!');
                }
              }
            };
    
            const form = new FormData();
            form.append("filedata", file);
            // 开始上传
            xhr.open("POST", "/upload", true);
            // xhr.setRequestHeader("FILENAME", file.name);
            console.log('form',form);
            xhr.send(form);
          }
        })(file)
      }
    }
    //当上传图片的时候,若是还须要从前端日后端传参数能够使用`form.append("filedata", file);`的方法
  • 后端开发

1.koa搭建框架代码:java

const Koa = require('koa');
const app = new Koa();
app.use(statics(
    path.join(__dirname,staticPath)
));
app.use(statics('.'));

app.use(bodyParser());

app.listen(9000); //后端接听9000端口

2.前端引入静态文件,这样react,build好的文件直接和后端连接起来react

const statics = require('koa-static'); //使用koa-static
const staticPath = './build';
app.use(statics(
    path.join(__dirname,staticPath)
));
app.use(statics('.'));

3.get 和put 方法设置git

app.use(async(ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    // ctx.response.body = '<h1>Hello,koa2222!</h1>';
    //   console.log('ctx use',ctx.url,ctx.method);
    if(ctx.method === 'GET'){ //当请求时GET请求时
        ctx.body =ctx.response.body;
    }else if(ctx.url==='/' && ctx.method === 'POST'){ //当请求时POST请求时
        ctx.body=await parsePostData(ctx);
    }else{
        //其它请求显示404页面
        ctx.body='<h1>404!</h1>';
    }

});

function parsePostData( ctx ) {
    return new Promise((resolve, reject) => {
        try {
            let postdata = "";
            ctx.req.addListener('data', (data) => {
                postdata += data
            })
            ctx.req.addListener("end",function(){
                let parseData = parseQueryStr( postdata );
                resolve( parseData )
            })
        } catch ( err ) {
            reject(err)
        }
    })
}
function parseQueryStr( queryStr ) {
    let queryData = {};
    let queryStrList = queryStr.split('&');
    console.log( queryStrList );
    for (  let [ index, queryStr ] of queryStrList.entries()  ) {
        let itemList = queryStr.split('=');
        queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
    }
    return queryData
}

4.跨域设置github

const cors = require('koa-cors');
app.use(cors());

5.图片上传api实现json

router.post('/upload',  async(ctx, next) => { //提交图片接口
    var form = new multiparty.Form({uploadDir: './files/'});
    // 上传完成后处理
    form.parse(ctx.req, function(err, fields, files) {
    //fields里有前端append的参数
        if (err) {
            throw err;
        } else {
            //form.append ()的值在fields中
            processImg(ctx.req, ctx.res, files,fields).then(function(data) {
                console.log('hshsh')
                // 设置跨域
                // allowCross(ctx);
                // res.json({
                //     res: JSON.parse(data.filesTmp),
                //     relPath: data.relPath,
                // })
            }).catch(function(err) {
                console.log(err)
            });
        }
    });

});

function processImg(req, res, files,fields) {
    return new Promise(function(resolve, reject) {
        const _img = files.filedata[0];
        const uploadedPath = _img.path;
        const originalFilename = _img.originalFilename;
        let dstPath = './imgs/'+ originalFilename;
        // console.log('dstPath',dstPath);
        //  (目前的路径,重命名后的路径)重命名
        fs.rename(uploadedPath, dstPath, function(err) {
            if (err) {
                reject(err)
            } else {
                console.log('rename ok!');
            }
        });
    });
}

总结

前端:
1.material-ui地址:https://material-ui.com/ 它是React组件,实现了谷歌Material Design设计规范。世界上最流行的React界面框架。
2.图片是file类型,须要转数组Array.prototype.slice.call
3.当上传图片的时候,若是还须要从前端日后端传参数能够使用form.append("filedata", file);的方法
后端:
1.后端的form.parse方法的fields里有前端append的参数;
2.跨域使用koa-cors
3.上传的图片名字不是图片自己的名字,能够fs.rename来从新命名
4.getput方法须要区分,能够使用上文中的代码。
5.koa-static能够将react 中build后的文件同后端连接起来后端