从blob转换为二进制以将其保存到mongodb

时间:2017-11-30 13:04:54

标签: mongodb reactjs react-redux dropzone.js

我从前端到我的节点快速服务器得到了这样的对象。

{ picture: [ { preview: 'blob:http://localhost:3000/1f413443-83d8-499e-a432-9ac51a2592b7' } ],
  name: 'fsdfs',
  description: 'fdfd',
  url: 'fdfd',
  about: 'dfdf' }

我收到此错误:

TypeError: path must be a string or Buffer
    at TypeError (native)

这是我保存到mongodb的功能

exports.create_a_project = function(req, res) {
  console.log(req.body);
  var new_project = new Project(req.body);

  new_project.picture.data = fs.readFileSync(req.body.picture[0]);
  new_project.picture.contentType = 'image/png';
  new_project.save(function(err, project) {
    if (err)
      res.send(err);
    res.json(project);
  });
};

我需要将我收到的图像转换成二进制文件以便保存。 或者我需要从客户端自己发送它作为二进制base64。

我的客户端我使用react redux Dropzone发送我的数据。

这是我的表格及其外观。

import React from 'react';
import {Field, reduxForm} from 'redux-form';
import Dropzone from 'react-dropzone';

const FILE_FIELD_NAME = 'picture';

const renderDropzoneInput = (field) => {
  const files = field.input.value;
  return (
      <div>
        <Dropzone
            name={field.name}
            onDrop={(filesToUpload, e) => field.input.onChange(filesToUpload)}
        >
          <div>Try dropping some files here, or click to select files to
            upload.
          </div>
        </Dropzone>
        {field.meta.touched &&
        field.meta.error &&
        <span className="error">{field.meta.error}</span>}
        {files && Array.isArray(files) && (
            <ul>
              {files.map((file, i) => <li key={i}>{file.name}</li>)}
            </ul>
        )}
      </div>
  );
};

const validate = values => {
  const errors = {};
  if (!values.name) {
    errors.name = 'Required';
  } else if (values.name.length > 15) {
    errors.name = 'Must be 15 characters or less';
  }
  if (!values.description) {
    errors.description = 'Required';
  } else if (values.description.length > 15) {
    errors.description = 'Must be 75 characters or less';
  }

  if (!values.url) {
    errors.url = 'Required';
  } else if (values.url.length > 15) {
    errors.url = 'Must be 15 characters or less';
  }
  if (!values.about) {
    errors.about = 'Required';
  } else if (values.about.length > 15) {
    errors.about = 'Must be 15 characters or less';
  }
  if (!values.picture) {
    errors.picture = 'Required';
  } else if (values.picture.length > 15) {
    errors.picture = 'Must be 15 characters or less';
  }
  // if (!values.email) {
  //   errors.email = 'Required';
  // } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
  //   errors.email = 'Invalid email address';
  // }
  // if (!values.age) {
  //   errors.age = 'Required';
  // } else if (isNaN(Number(values.age))) {
  //   errors.age = 'Must be a number';
  // } else if (Number(values.age) < 18) {
  //   errors.age = 'Sorry, you must be at least 18 years old';
  // }
  return errors;
};

const warn = values => {
  const warnings = {};
  // if (values.age < 19) {
  //   warnings.age = 'Hmm, you seem a bit young...';
  // }
  return warnings;
};

const renderField = ({input, label, type, meta: {touched, error, warning}}) => (
    <div>
      <label>{label}</label>
      <div>
        <input {...input} placeholder={label} type={type}/>
        {touched && ((error && <span>{error}</span>) ||
            (warning && <span>{warning}</span>))}
      </div>
    </div>
);

const SyncValidationForm = (props) => {
  const {handleSubmit, pristine, reset, submitting} = props;
  return (
      <form onSubmit={handleSubmit}>
        <Field name="name" type="text" component={renderField}
               label="Name"/>
        <Field name="description" type="text" component={renderField}
               label="Description"/>
        <Field name="url" type="text" component={renderField}
               label="Url"/>
        <Field name="about" type="text" component={renderField}
               label="About"/>
        {/*<Field name="picture" type="text" component={renderField}*/}
               {/*label="Picture"/>*/}
        <Field
            name={FILE_FIELD_NAME}
            component={renderDropzoneInput}
        />
        {/*<Field name="email" type="email" component={renderField} label="Email"/>*/}
        {/*<Field name="age" type="number" component={renderField} label="Age"/>*/}
        <div>
          <button type="submit" disabled={submitting}>Submit</button>
          <button type="button" disabled={pristine || submitting}
                  onClick={reset}>Clear Values
          </button>
        </div>
      </form>
  );
};

export default reduxForm({
  form: 'syncValidation',  // a unique identifier for this form
  validate,                // <--- validation function given to redux-form
  warn                     // <--- warning function given to redux-form
})(SyncValidationForm);

这是我的服务功能,处理其余的api。

const addProject = (newProject) => {

  let data = JSON.stringify(newProject);



  return axios.post('http://localhost:3008/projects', data, {
        headers: {
          'Content-Type': 'application/json',
        }
      }
  ).then(response => {
    // console.log(response)
  }).catch(error => {
    console.log(error)
  });
};

1 个答案:

答案 0 :(得分:0)

所以我认为你的问题就在这一行:

data

你正在插入{m}的mongoDB表列会给你带来错误。它期待一个字符串或缓冲区,你给它一个File对象。

您可以使用what I posted here获取base64字节字符串,我将尝试与您的代码集成,如下所示:

您需要确保拥有一个变量来收集您的文件。这就是我设置页面顶部的方式:

import React from 'react'
import Reflux from 'reflux'
import {Form, Card, CardBlock, CardHeader, CardText, Col, Row, Button } from 'reactstrap'
import actions from '~/actions/actions'
import DropZone from 'react-dropzone'

// stores
import SomeStore from '~/stores/someStore.jsx'

Reflux.defineReact(React)
export default class myUploaderClass extends Reflux.Component {
  constructor(props) {
    super(props);
    this.state = {
        attachments: [],
    };
    this.stores = [
        SomeStore,
    ]
    ....

然后绑定新函数:

    ....
    this.getData = this.getData.bind(this);
    this.processFile = this.processFile.bind(this);
    this.errorHandler = this.errorHandler.bind(this);
    this.progressHandler = this.progressHandler.bind(this);
  } // close constructor

然后我们将字节提供给attachments并将其发送到new_project.picture.data。对我来说,我使用onDrop运行DropZone onDrop={this.uploadFile}的函数。我无法真正告诉你他在做什么,因为我不知道filesToUpload指的是什么。我的uploadFile看起来像这样:

uploadFile(event){
  this.setState({
    files: event,
  });

  document.getElementById('docDZ').classList.remove('dragover');
  document.getElementById('progress').textContent = '000';
  document.getElementById('progressBar').style.width = '0%';

  this.state.files = event;  // just for good measure
  for (let i = 0; i < this.state.files.length; i++) {
    const a = i + 1;
    console.log('in loop, pass: ' + a);
    const f = this.state.files[i];

    this.getData(f); // this will load the file to SomeStore.state.attachments
  }
}

这将是为DropZone删除/添加的每个文件运行的getData函数:

getData(f) {
    const reader = new FileReader();
    reader.onerror = this.errorHandler;
    reader.onprogress = this.progressHandler;
    reader.onload = this.processFile(f);
    reader.readAsDataURL(f);
}

然后processFile()运行onload

processFile(theFile) {
  return function(e) {
    const bytes = e.target.result.split('base64,')[1];
    const fileArray = [];

    // *** Collect any existing attachments ***
    // I found I could not get it from this.state, but had to use
    // my store, instead
    if (SomeStore.state.attachments.length > 0) {
      for (let i=0; i < SomeStore.state.attachments.length; i++) {
        fileArray.push(SomeStore.state.attachments[i]);
     }
    }

    // Add the new one to this.state
    fileArray.push(bytes);

    // Update the state
    SomeStore.setState({
      attachments: fileArray,
    });
    // This seemed to automatically add it to this.state, for me.
  }
}

一旦你有了,你应该能够做到:

new_project.picture.data = this.state.attachments[0];

如果没有,出于某种原因,你可能会尝试在exports.create_a_project()内调用它,这是你做的第一件事:

getData(req.body.picture[0]);

这甚至可以在不必修改您的onDrop例程的情况下工作。如果this.state.attachments没有任何内容,那么您的SomeStore.state.attachments肯定应该,假设您已将其保存到名为stores的文件夹someStore.jsx

import Reflux from 'reflux'
import Actions from '~/actions/actions`

class SomeStore extends Reflux.Store
{
    constructor()
    {
        super();
        this.state = {
            attachments: [],
        };
        this.listenables = Actions;
        this.baseState = {
            attachments: [],
        };
    }

    onUpdateFields(name, value) {
        this.setState({
            [name]: value,
        });
    }

    onResetFields() {
        this.setState({
           attachments: [],
        });
    }
}
const reqformdata = new SomeStore

export default reqformdata

附加功能

errorHandler(e){
    switch (e.target.error.code) {
      case e.target.error.NOT_FOUND_ERR:
        alert('File not found.');
        break;
      case e.target.error.NOT_READABLE_ERR:
        alert('File is not readable.');
        break;
      case e.target.error.ABORT_ERR:
        break;    // no operation
      default:
        alert('An error occurred reading this file.');
        break;
    }
  }

progressHandler(e) {
    if (e.lengthComputable){
      const loaded = Math.round((e.loaded / e.total) * 100);
      let zeros = '';

      // Percent loaded in string
      if (loaded >= 0 && loaded < 10) {
        zeros = '00';
      }
      else if (loaded < 100) {
        zeros = '0';
      }

      // Display progress in 3-digits and increase bar length
      document.getElementById("progress").textContent = zeros + loaded.toString();
      document.getElementById("progressBar").style.width = loaded + '%';
    }
  }

我的DropZone&amp;适用的进度指标标记:

render(){

const dropZoneStyle = {
  height: "34px",
  width: "300px",
  border: "1px solid #ccc",
  borderRadius: "4px",
};

return (
  <Form>
    <Col xs={5}>
            <DropZone type="file" id="docDZ"
              onDrop={this.uploadFile}
              onDropRejected={this.onDropRejected}
              onClick={this.handleUploadClick}
              onChange={this.handleChange}
              onDragEnter={this.handleDragEnter}
              onDragLeave={this.handleDragLeave}
              accept=".doc, .docx, .gif, .png, .jpg, .jpeg, .pdf"
              multiple="true"
              maxSize={999000}
              style={dropZoneStyle}>
               {'Click HERE to upload or drop files here...'}
            </DropZone>
            <table id="tblProgress">
              <tbody>
                <tr>
                  <td><b><span id="progress">000</span>%</b> <span className="progressBar"><span id="progressBar" /></span></td>
                </tr>
              </tbody>
            </table>
          </Col>
      </Row>
    </Form>
    )
  } // close render
}  // close class

和CSS:

.progressBar {
  background-color: rgba(255, 255, 255, .1);
  width: 100%;
  height: 26px;
}
#progressBar {
  background-color: rgba(87, 184, 208, .5);
  content: '';
  width: 0;
  height: 26px;
}

您缺少的其他功能:

handleUploadClick(){
  return this.state;
}

handleChange(){
  this.state.errors.fileError = "";
}

handleDragEnter(event){
  event.preventDefault();
  document.getElementById("docDZ").classList.add("dragover");
}

handleDragLeave(event){
  event.preventDefault();
  document.getElementById("docDZ").classList.remove("dragover");
}

onDropRejected(files){
    const errors ={}
    let isAlertVisible = false;

    for(let i=0, j = files.length; i < j; i++){
      const file = files[i];
      const ext = file.name.split('.').pop().toLowerCase();
      //console.log(ext)

     if(this.isFileTypeValid(ext)===false){
        errors.fileError = "Only image files (JPG, GIF, PNG), Word files (DOC, DOCX), and PDF files are allowed.";
        isAlertVisible = true;
     }

     if(ext === "docx" || ext ==="gif" || ext ==="png" || ext ==="jpg" || ext ==="jpeg" || ext ==="pdf" || ext ==="doc" && file.size > 999000){
      errors.fileError = "Exceeded File Size limit! The maximum file size for uploads is 999 KB.";
      isAlertVisible = true;
    }

    this.setState({
      "errors": errors,
      "isAlertVisible": isAlertVisible,
    })
  }
}