首页 > 解决方案 > 在 Angular 中上传文件 - 显示在 req.body 而不是 nodejs 服务器的 req.files 中

问题描述

在我使用 multer 作为中间件的 nodejs api 中,我可以在 request.file 属性中看到邮递员发布文件请求,然后当然会保存在我的服务器上。请求使用内容类型表单数据发送。在我的控制器上点击保存后,文件就已经上传了。文件的所有详细信息都在 request.files 属性中

在 Angular 中,附加文件被添加到请求的正文中,而我的 nodejs 应用程序无法保存该文件,因为中间件无法看到它。图像数据以 base64 编码字符串的形式出现

我尝试将 Angular 中的标头设置为 multipart/form-data,但我收到 500 错误“Multipart: Boundary not found”。

在邮递员中,如果我删除表单数据并设置为无,它也不起作用

角组件

  imageFile:any;

  onImagePicked(imageData: string | File) {
    if (typeof imageData === 'string') {
      try {
        /*this.imageFile = this.sharedService.base64toBlob(
          imageData.replace('data:image/jpeg;base64,', ''),
          'image/jpeg'
        );*/
        this.imageFile = imageData;
      } catch (error) {
        console.log('Err' + error);
        return;
      }
    } else {
      this.imageFile = imageData;
    }
  }

  savePhoto() {
    console.log ('Save');
    this.sharedService.uploadPhoto(this.imageFile).subscribe(val => {
      console.log(val);
    });
  }

角服务

  public uploadPhoto(image: File) {
      //let headers = new HttpHeaders();
      //headers = headers.append('Content-Type', 'multipart/form-data');
      const imageData = new FormData();
      imageData.append('image', image);
      return this.httpClient.post(environment.apiURL + this.path, imageData);
      //return this.httpClient.post(environment.apiURL + this.path, imageData, {headers: headers});
  }

Node.js 设置

 public express: express.Application;

  constructor() {
    this.express = express();

    this.setMiddlewares();
    this.setRoutes();
    this.catchErrors();
    this.setSocketServer();
  }

  private setMiddlewares(): void {
    this.express.options('*', cors());
    this.express.use(cors());
    this.express.use((reg, res, next) => {
      res.setHeader('Access-Control-Allow-Origin', '*');
      res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, PUT, PATCH, DELETE');
      res.setHeader('Access-Control-Allow-Headers', '*');
      res.setHeader('Access-Control-Max-Age', 10000);
      next();
    });
    this.express.use(morgan('dev'));
    this.express.use(bodyParser.json());
    this.express.use(bodyParser.urlencoded({ extended: false }));
    this.express.use(helmet());

    const storageConfig = multer.diskStorage({
      destination: (req, file, callback) => callback(null, './files'),
      filename: (req, file, callback) => callback(null, Date.now() + "-"  + file.originalname),
    });
    this.express.use(multer({storage: storageConfig}).any());

  }

  private setRoutes(): void {
    this.express.use('/api', api);
  }

照片路由器

import { Router } from "express";
import DfrPhotoController from "./dfrphoto.controller";

const dfrPhoto: Router = Router();
const controller = new DfrPhotoController();


dfrPhoto.post('/', controller.save);

export default dfrPhoto;

控制器保存

export default class DfrPhotoController {

    // TODO: link to the dfr
    public save = async (req:Request, res:Response): Promise<any> => {

        // Need to see files in request. File is already saved in 
        let files = req.files; 
        console.log (files);
        if (files === null || files === undefined ) {
            res.status(404).send({
                success: false,
                message:'No Files Found'
              });
        }

        console.log("The file was saved!");
        res.status(200).send({
            success: true,
            message:'Photo saved',
            data: files
          });
    }

}

我希望角度文件上传的工作方式与邮递员示例完全相同。我不介意在控制器中调用 save 后立即写入文件,因为我可以向中间件添加验证。如果有人对此有任何想法,我将不胜感激

示例 Angular 发送请求 在此处输入图像描述

谢谢

//Added Component using the image picker (html and ts)
//HTML
  <ion-grid>
    <form [formGroup]="form" >  
        <ion-row size="12">
            <ion-col  size-lg="6" size-xl="6" size="12"   size-md="12">
                <app-camera  (imagePick)="onImagePicked($event)"></app-camera>
                <!-- <ion-thumbnail>
                  <ion-img  width="200" height="200"  [src]="imageFile" ></ion-img>

                </ion-thumbnail>-->
                <img  [src]="imageFile"  >
            </ion-col>
            <ion-col  size-lg="6" size-xl="6" size="12"   size-md="12">
                <ion-label  position="floating">Photo Comments</ion-label>
                <!-- <ion-textarea rows="3" formControlName="rigComments"></ion-textarea>-->
                <ion-textarea rows="3" formControlName="photoComments"></ion-textarea>
            </ion-col>
        </ion-row>
        <ion-row>
            <ion-button (click)="savePhoto()">Save Photo</ion-button>
        </ion-row>
    </form>  
  </ion-grid>

//TS
import { Component, OnInit } from '@angular/core';
import { FormControl, Validators, FormGroup } from '@angular/forms';
import { SharedService } from 'src/app/shared/shared.service';

@Component({
  selector: 'app-dfr-photo',
  templateUrl: './dfr-photo.component.html',
  styleUrls: ['./dfr-photo.component.scss'],
})
export class DfrPhotoComponent implements OnInit {

  form: FormGroup;
  sharedService: SharedService;

  constructor(sharedService: SharedService) {
      this.sharedService = sharedService;
   }

  ngOnInit() {
    this.form = new FormGroup({
      _id: new FormControl(null, {
        updateOn: 'blur',
      }),
      dfrId: new FormControl(null, {
        updateOn: 'blur',
        validators: [Validators.required]
      }),
      photoComments: new FormControl(null, {
        updateOn: 'blur',
        validators: [Validators.required]
      }),
      image: new FormControl(null, {
        updateOn: 'blur'
      })
    });
  }

  imageFile:any;

  onImagePicked(imageData: string | File) {
    if (typeof imageData === 'string') {
      try {
        /*this.imageFile = this.sharedService.base64toBlob(
          imageData.replace('data:image/jpeg;base64,', ''),
          'image/jpeg'
        );*/
        this.imageFile = imageData;
      } catch (error) {
        console.log('Err' + error);
        return;
      }
    } else {
      this.imageFile = imageData;
    }
    this.form.patchValue({ image: imageData });
    this.form.get('image').updateValueAndValidity();
  }

  savePhoto() {
    console.log ('Save');
    console.log(this.form.value.image);
    this.sharedService.uploadPhoto(this.form.value.image).subscribe(val => {
      console.log(val);
    });
  }

}


// Image Picker Code - JS
import { Component, OnInit, ElementRef, EventEmitter, ViewChild, Output, Input } from '@angular/core';
import { Plugins, CameraResultType, CameraSource, Capacitor} from '@capacitor/core';
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
import { Platform } from '@ionic/angular';


@Component({
  selector: 'app-camera',
  templateUrl: './camera.component.html',
  styleUrls: ['./camera.component.scss'],
})
export class CameraComponent implements OnInit {

  @ViewChild('filePicker') filePickerRef: ElementRef<HTMLInputElement>;
  @Output() imagePick = new EventEmitter<string | File>();
  @Input() showPreview = false;
  selectedImage: string;
  usePicker = false;


  constructor( private sanitizer: DomSanitizer, private platform: Platform) { }

  image2: SafeResourceUrl;

  ngOnInit() {
    if ( this.platform.is('desktop')) {
      this.usePicker = true;
    }
  }

  onPickImage() {
    if (!Capacitor.isPluginAvailable('Camera')) {
      this.filePickerRef.nativeElement.click();
      return;
    }
    Plugins.Camera.getPhoto({
      quality: 50,
      source: CameraSource.Prompt,
      correctOrientation: true,
      width: 300,
      resultType: CameraResultType.Base64
    })
      .then(image => {
        const image2: any = image; // to fix access to base64 data
        this.selectedImage = image2.base64Data;
        this.imagePick.emit(image2.base64Data);

      })
      .catch(error => {
        console.log('ERROR ' + error);
        if (this.usePicker) {
          this.filePickerRef.nativeElement.click();
        }
        return false;
      });
  }

  onFileChosen(event: Event) {
    const pickedFile = (event.target as HTMLInputElement).files[0];
    if (!pickedFile) {
      return;
    }
    const fr = new FileReader();
    fr.onload = () => {
      const dataUrl = fr.result.toString();
      this.selectedImage = dataUrl;
      this.imagePick.emit(dataUrl);// (pickedFile);
    };
    fr.readAsDataURL(pickedFile);
  }

}



// Image Picker Code - HTML
  <div class="picker">

    <ion-button color="primary" (click)="onPickImage()" *ngIf="!usePicker">
      <ion-icon name="camera" slot="start"></ion-icon>
      <ion-label>Take Picture</ion-label>
    </ion-button>
  </div>
  <input
    type="file"
    accept="image/jpeg"
    *ngIf="usePicker"
    #filePicker
    (change)="onFileChosen($event)"
  />




// Sidenote - Example of sending directly from the form control (renamed to image)
onImagePicked(imageData: string | File) {
    if (typeof imageData === 'string') {
      try {
        /*this.imageFile = this.sharedService.base64toBlob(
          imageData.replace('data:image/jpeg;base64,', ''),
          'image/jpeg'
        );*/
        this.imageFile = imageData;
      } catch (error) {
        console.log('Err' + error);
        return;
      }
    } else {
      this.imageFile = imageData;
    }
    this.form.patchValue({ image: imageData });
    this.form.get('image').updateValueAndValidity();
  }

  savePhoto() {
    this.sharedService.uploadPhoto(this.form.value.image).subscribe(val => {
      console.log(val);
    });
  }

标签: node.jsangulartypescriptmulter

解决方案


我可以建议 Multer 的替代品吗?

请参阅下面的每周 npm 下载:

穆尔特:466,964

强大:2,116,997

节点服务器:

app.post('/upload', (req, res) => {

    var form = new formidable.IncomingForm()
    form.parse(req)

    form.on('fileBegin', function (name, file) {
        var path = __dirname + '/uploads'
        if (!fs.existsSync(path)) {
            fs.mkdirSync(path)
        }
        file.path = __dirname + '/uploads/' + file.name;
    });

    form.on('file', function (name, file) {
        console.log('Uploaded ' + file.name);
        res.send({ message: 'uploaded' })
    });
})

角模板:

<input type="file" (change)="onFileInput($event)" placeholder="Upload file" accept=".JPG,.pdf,.doc,.docx">

角度分量:

onFileInput(event) {
    let fileList: FileList = event.target.files;
    let file = fileList[0]
    console.log(file);
    let formData: FormData = new FormData();
    formData.append('uploadFile', file, file.name);
    this.http.post('http://localhost:3001/upload', formData).subscribe(
      res => console.log(res)
    )

  }

推荐阅读