首页 > 解决方案 > 从离子角度上传文件时出错。但它通过 Postman Laravel API 工作

问题描述

使用 Laravel API 在 Ionic Angular 项目中上传文件时出现错误。通过 Postman 调用时上传工作正常,但在 Ionic 中却不行。

错误: “http://localhost:8000/api/upload-media 的 Http 失败响应:400 错误请求”

离子:上传-test.page.html

<ion-header>
  <ion-toolbar>
    <ion-title>Upload File Test Page</ion-title>
  </ion-toolbar>
</ion-header>
<ion-content>
  <form novalidate="novalidate" [formGroup]="myForm" enctype="multipart/form-data">
    <div class="px-4 py-6">
      <label class="block text-sm">
        <span class="text-gray-700 dark:text-gray-400 font-semibold required">File</span>
        <div class="relative text-gray-500 focus-within:text-purple-600 dark:focus-within:text-purple-400">
          <input class="block w-full pl-10 mt-1 text-sm text-black dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input" placeholder="File ..." type="file" formControlName="file"/>
          <div class="absolute inset-y-0 flex items-center ml-3 pointer-events-none">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
            </svg>
          </div>
        </div>
        <span class="text-xs m-2 text-red-600 dark:text-red-400" *ngIf="myForm.controls.name && myForm.controls.name.touched && myForm.controls.name.status=='INVALID'">Error on this field.</span>
      </label>
      <button class="block w-full px-4 py-2 mt-6 mb-4 text-sm font-medium leading-5 text-center text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:bg-purple-400" (click)="onClickUpload()">
        <svg xmlns="http://www.w3.org/2000/svg" class="inline-block align-middle w-4 mr-2 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
        </svg>
        <span>Upload</span>
      </button>
    </div>
  </form>
</ion-content>

离子:上传-test.page.ts

/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/dot-notation */
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/naming-convention */
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { MediaGalleryService } from '../platform/services/api/media-gallery.service';
import { ToastService } from '../platform/services/utils/toast.service';

@Component({
  selector: 'app-upload-test',
  templateUrl: './upload-test.page.html',
  styleUrls: ['./upload-test.page.scss'],
})
export class UploadTestPage implements OnInit {

  myForm: FormGroup;

  constructor(private formBuilder: FormBuilder, private mediaGalleryService: MediaGalleryService, private toastService: ToastService) {
    /* Form builder */
    this.myForm = this.formBuilder.group({
      file: new FormControl('', Validators.compose([
        Validators.required,
      ])),
      advertisement_id: new FormControl(1, Validators.compose([
        Validators.required,
      ])),
      type: new FormControl('ad_video', Validators.compose([
        Validators.required,
      ])),
      folder: new FormControl('ad_video', Validators.compose([
        Validators.required,
      ])),
      extension: new FormControl('', Validators.compose([
        Validators.required, Validators.pattern('^(jpeg|png|jpg|svg|mp4|txt|pdf|doc|docx)$'),
      ]))
    },{
      validators: []
    });
  }

  ngOnInit() {
  }

  async onClickUpload() {
    console.log(this.myForm.value.file);
    const fileExtension = this.myForm.value.file.split(['.'])[(this.myForm.value.file.split(['.']).length - 1)];
    this.myForm.controls['extension'].setValue(fileExtension);
    //
    console.log(this.myForm);
    //
    if(this.myForm.status == 'VALID') {
      this.mediaGalleryService.save(this.myForm.value).subscribe(
        (response) => {
          console.log(response);
          if(response?.success){
            /* Toast */
            this.toastService.createToast('File Uploaded With Success.', 2000);
          }else{
            /* Toast */
            this.toastService.createToast('Error : '+response.message, 2000);
          }
        },
        (error) => {
          console.log(error);
          /* Toast */
          this.toastService.createToast('Error : '+error?.message, 2000);
        }
      );
    }
  }
}

离子:上传-test.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { UploadTestPageRoutingModule } from './upload-test-routing.module';
import { UploadTestPage } from './upload-test.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    UploadTestPageRoutingModule,
    ReactiveFormsModule,
  ],
  declarations: [UploadTestPage]
})
export class UploadTestPageModule {}

离子:媒体画廊.service.ts

/* eslint-disable quote-props */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/naming-convention */

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class MediaGalleryService {

  constructor(private http: HttpClient) { }

  save(data) {
    let headersObj = new HttpHeaders();
    headersObj.append('Content-Type', 'multipart/form-data');
    headersObj.append('Accept', 'application/json');
    let options = { headers: headersObj };
    //
    return this.http.post<any>(environment.apiURL+'/upload-media', data, options);
  }

  destroy(id,mediaType) {
    return this.http.delete<any>(environment.apiURL+'/medias/'+id+'/'+mediaType);
  }
}

IONIC:身份验证-request.interceptor.ts

/* eslint-disable max-len */
/* eslint-disable space-before-function-paren */
import { HttpEvent, HttpHandler, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { from, Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
import { StorageService } from './storage.service';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationRequestInterceptor {

  constructor(private storageService: StorageService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return from(this.handle(request, next));
  }

  async handle(request: HttpRequest<any>, next: HttpHandler) {
    let authRequest = request;
    let token = null;
    await this.storageService.get(environment.authenticatedUserTokenKey).then(async(receivedToken)=>{await (token = receivedToken);});
    const TOKEN_HEADER_KEY = 'Authorization';
    const CONTENT_TYPE_HEADER_KEY = 'Content-Type';
    const ACCEPT_HEADER_KEY = 'Accept';
    //
    const ACCESS_CONTROL_ALLOW_ORIGIN_KEY = 'Access-Control-Allow-Origin';
    const ACCESS_CONTROL_ALLOW_METHODS_KEY = 'Access-Control-Allow-Methods';
    const ACCESS_CONTROL_ALLOW_HEADERS_KEY = 'Access-Control-Allow-Headers';
    //
    if (token) {
      authRequest = authRequest.clone({ headers: authRequest.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) });
    }
    if (!authRequest.headers.has('Content-Type')) {
      authRequest = authRequest.clone({ headers: authRequest.headers.set(CONTENT_TYPE_HEADER_KEY, 'application/json') });
    }
    authRequest = authRequest.clone({ headers: authRequest.headers.set(ACCEPT_HEADER_KEY, 'application/json') }); //text/plain
    //
    authRequest = authRequest.clone({ headers: authRequest.headers.set(ACCESS_CONTROL_ALLOW_ORIGIN_KEY, '*') });
    authRequest = authRequest.clone({ headers: authRequest.headers.set(ACCESS_CONTROL_ALLOW_METHODS_KEY, 'GET, POST, PUT, DELETE, OPTIONS, PATCH, TRACE, CONNECT, HEAD') });
    authRequest = authRequest.clone({ headers: authRequest.headers.set(ACCESS_CONTROL_ALLOW_HEADERS_KEY, '*') });
    //
    return next.handle(authRequest).toPromise();
  }

}

export const authenticationInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: AuthenticationRequestInterceptor, multi: true }
];

LARAVEL:MediaGalleryController.php

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\MediaGallery;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\str;

class MediaGalleryController extends Controller {
    /* Store a newly created resource in storage. */
    public function store(Request $request) {
        // Check current authenticated user privileges, if he could execute the following operations.
        $authorized = false;
        foreach(Auth::user()->role->functionalities as $functionality) {
            if($functionality->slug == 'manage_my_files' || $functionality->slug == 'manage_all_files'){
                $authorized = true;
                break;
            }
        }
        if(!$authorized) {
            return response()->json(["message"=>"You are not authorized.", "data"=>[], "errors"=>["Unauthorized"], "success"=>false], 401);
        }

        // Validate request.
        $mediaGalleryValidationRules = [
            'type' => ['required', 'in:ad_video,ad_image,instruction_file'],
            'folder' => ['required', 'in:ad_video,ad_image,instruction_file'],
            'file' => ['required', 'file', 'mimes:jpeg,png,jpg,svg,mp4,txt,pdf,doc', 'max:20480'],
            'extension' => ['required', 'string', 'in:jpeg,png,jpg,svg,mp4,txt,pdf,doc'],
            'advertisement_id' => ['required_if:type,==,ad_video,||,type,==,ad_image,||,type,==,instruction_file', 'numeric', 'exists:advertisements,id'],
        ];
        $mediaGalleryValidator = validator($request->all(), $mediaGalleryValidationRules);
        if($mediaGalleryValidator->fails()) {
            return response()->json(["message"=>"Errors on uploading this media to gallery.", "data"=>$request->all(), "errors"=>$mediaGalleryValidator->errors(), "success"=>false], 400);
        }

        // Generate a file name.
        $mediaPath = 'public/medias/' . strtolower($request->get('folder')) . '/';
        $mediaExtension = '.' . $request->get('extension');
        $counter = 0;
        do{
            $counter = $counter + 1;
            $mediaName = date('YmdHis') . '-' . Str::uuid()->toString();
        }while(Storage::disk('public')->exists($mediaPath . $mediaName . $mediaExtension) || $counter<=5);

        // Upload file.
        if($mediaPath && $mediaName && $mediaExtension) {
            Storage::disk('public')->put($mediaPath . $mediaName . $mediaExtension, file_get_contents($request->file('file')->getRealPath()));
            $fileName = '/storage/public/medias/' . strtolower($request->get('folder')) . '/' . $mediaName . $mediaExtension;
        }

        // Save data.
        $mediaGallery = new MediaGallery([
            'type' => $request->get('type'),
            'value' => $fileName,
            //
            'created_by' => Auth::user()->id,
            'updated_by' => Auth::user()->id,
        ]);
        //
        if($request->get('type') == 'ad_video' || $request->get('type') == 'ad_image' || $request->get('type') == 'instruction_file'){
            $mediaGallery->advertisement_id = $request->get('advertisement_id');
        }
        //
        $mediaGallery->save();

        // Return result.
        return response()->json(["message"=>"Media uploaded successfully.", "data"=>$mediaGallery, "errors"=>[], "success"=>true], 201);
    }
}

我希望尽快得到解决方案。非常感谢。

标签: angularlaravelionic-frameworkuploadpostman

解决方案


推荐阅读