如何用Laravel在RESTFul API中处理Angular Base64图片

275 阅读4分钟

用Laravel在RESTFul API中处理Angular Base64图像

在Web应用程序中处理图像已经成为一种常态。几乎99%的应用程序,我们每天都会以这样或那样的方式与之互动,有图像。

然而,处理后端图像已经被证明是一个非常复杂的任务。出于这个原因,开发人员想出了一些处理图像的替代方法。

本教程将介绍如何从Angular应用程序中以base64格式上传图片,并以图片的形式上传到服务器。

前提条件

要跟随本教程,你需要具备以下条件。

  • 本地安装了PHP 7.3+。
  • 安装了Laravel 8。
  • 安装了Angular 12。
  • 在本地安装SQL和MySQL的基本知识。
  • 一个你感兴趣的IDE。在本文中,我们将同时使用PhpStorm for PHP和Webstorm for Angular。

在本教程结束时,读者应该知道如何在Angular和Laravel中处理base64图片。

设置Angular应用程序

有不同的方法来设置Angular应用程序。然而,本文使用Angular CLI来安装我们的应用程序。

键入以下内容来检查你当前安装的版本。

# command to check the current ng version
ng --version
...
# My installed version CLI (this may differ from your version)
Angular CLI: 12.2.3
# My current Node version 
Node: 16.5.0 
# My current npm version
Package Manager: npm 7.19.1
# On ubuntu 20.04
OS: linux x64
... 

注意,上面的输出可能有所不同,不一定符合你的版本

接下来,继续并通过运行以下命令安装Angular应用程序。

ng new base64 # this installs a new angular application

根据你的网络连接情况,上述命令可能需要一些时间来执行。

安装完成后,导航到项目根目录,并创建一个图像组件,如下所示。

cd base64
ng g c image

上述命令生成了四个文件,包括模板和TypeScript文件。

现在我们有了图像组件,编辑app.component.html 文件,如下图所示。

<app-image></app-image>

上面的标签确保图像组件在执行AppComponent 时得到显示。

让我们改变我们的ImageComponent 模板,如下图所示。

<div class="content mat-elevation-z8">
  <h2 class="text-center text-dark">Upload Image</h2>
  <form [formGroup]="uploadForm" (ngSubmit)="onSubmit()">
      <div class="form-row m-3">
        <div class="col-md-6">
          <label for="news_banner">Image</label>
          <input type="file" id="file" (change)="handleImageUpload($event)" class="form-control-file"required="">
        </div>
      </div>
  </form>
</div>

你注意到我们在上面的模板中添加了一个onchange 事件处理程序。这保证了只有在每次添加新图片时才会上传图片。

接下来,编辑ImageComponent 脚本文件,如下图所示。

...
import {ApiService} from "../../../core/services/api.service";
import {ToastrService} from "ngx-toastr";
import * as _ from 'lodash';
...
export class ImageComponent implements OnInit {

  //image
  uploadImageBase64: string;
  ...
  /**
   * on image submit
   */
  onSubmit(){
    let upload:uploads={
      banner_image: this.uploadImageBase64,
    }
    this.apiService.uploadImage(news)
      .subscribe((res)=>{
        if(res){
          // if response is successful
          this.toastrService.success(res.message);
          this.submitting=false;
        }
        else{
          // if the response fails, show error alert
          this.toastrService.error(res.message,'Failed');
          this.submitting=false;
        }
      },error => {
        //if an error occurs during the api request
        this.toastrService.error(error.error.message,'Error');
        this.submitting=false;
      })
  }

  /**
   * handle image upload
   * @param fileToUpload
   */
  // @ts-ignore
  handleImageUpload(fileToUpload) {
    // check for image to upload
    // this checks if the user has uploaded any file
    if (fileToUpload.target.files && fileToUpload.target.files[0]) {
      // calculate your image sizes allowed for upload
      const max_size = 20971510;
      // the only MIME types allowed
      const allowed_types = ['image/png', 'image/jpeg','image/jpg'];
      // max image height allowed
      const max_height = 14200;
      //max image width allowed
      const max_width = 15600;

      // check the file uploaded by the user
      if (fileToUpload.target.files[0].size > max_size) {
        //show error
        this.error = 'max image size allowed is ' + max_size / 1000 + 'Mb';
        //show an error alert using the Toastr service.
        this.toastrService.error(this.imageError,'Error');
        return false;
      }
      // check for allowable types
      if (!_.includes(allowed_types, fileInput.target.files[0].type)) {
        // define the error message due to wrong MIME type
        let error = 'The allowed images are: ( JPEG | JPG | PNG )';
        // show an error alert for MIME
        this.toastrService.error(error,'Error');
        //return false since the MIME type is wrong
        return false;
      }
      // define a file reader constant
      const reader = new FileReader();
      // read the file on load
      reader.onload = (e: any) => {
        // create an instance of the Image()
        const image = new Image();
        // get the image source
        image.src = e.target.result;
        // @ts-ignore
        image.onload = rs => {
          // get the image height read
          const img_height = rs.currentTarget['height'];
          // get the image width read
          const img_width = rs.currentTarget['width'];
          // check if the dimensions meet the required height and width
          if (img_height > max_height && img_width > max_width) {
            // throw error due to unmatched dimensions
            error =
              'Maximum dimensions allowed: ' +
              max_height +
              '*' +
              max_width +
              'px';
            return false;
          } else {
            // otherise get the base64 image
            this.uploadedImageBase64 = e.target.result;
           
          }
        };
      };
      // reader as data url
      reader.readAsDataURL(fileToUpload.target.files[0]);
    }
  }
}

我们在上面的脚本中设置了onSubmit() 方法。这个方法是用来提交上传的base64图片的。

handleImageUpload() 方法是我们的图像处理程序。它首先检查被上传的图像大小。如果图片的尺寸符合我们预定的尺寸,我们就上传它。

接下来,我们检查图像是否只包含所需的MIME,即JPG、PNG和JPEG。当我们的检查完成后,我们使用FileReader 方法处理我们的图像。

为图片上传设置服务器

现在,我们已经有了Angular应用程序并运行,让我们来设置我们的Laravel 8服务器来处理图像。

首先, 通过编辑.env 文件来设置数据库的配置,如下图所示。

...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=image
DB_USERNAME=yourDBusername
DB_PASSWORD=yourDBpassword

接下来, 通过运行以下命令来创建Image 模型:

php artisan make:model Image --m

上述命令在App/Models 文件夹中生成了一个模型和一个迁移文件.

现在,编辑该模型和迁移文件,如下图所示。

<?php
//edit model as shown below
namespace App\Models;
class Image extends Model
{
    ....
    protected $fillable=[
        'image_path',
    ];
  ...
}

上面的脚本创建了一个image_path 的模型,我们以后会用它来存储我们上传的文件的路径。

<?php

...
class CreateImageTable extends Migration
{
    public function up()
    {
        Schema::create('images', function (Blueprint $table) {
            $table->id();
            $table->string('image_path')->nullable();
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('images');
    }
}

上面的脚本创建了我们的数据库表,用来存储我们上传的文件细节。

接下来,执行下面的命令来迁移我们的数据库。

php artisan migrate

随着数据库和模型设置的完成,让我们创建一个控制器来处理Angular应用程序中的图像。

为了处理这个问题,首先在App/Repos 命名空间中创建一个图像库,并按以下方式更新。

<?php
namespace App\Repos;
...
use Intervention\Image\Facades\Image;
...
class ImageRepository
{
  // define a method to upload our image
    public function upload_image($base64_image,$image_path){
        //The base64 encoded image data
        $image_64 = $base64_image;
        // exploed the image to get the extension
        $extension = explode(';base64',$image_64);
        //from the first element
        $extension = explode('/',$extension[0]);
        // from the 2nd element
        $extension = $extension[1];

        $replace = substr($image_64, 0, strpos($image_64, ',')+1);

        // finding the substring from 
        // replace here for example in our case: data:image/png;base64,
        $image = str_replace($replace, '', $image_64);
        // replace
        $image = str_replace(' ', '+', $image);
        // set the image name using the time and a random string plus
        // an extension
        $imageName = time().'_'.Str::random(20).'.'.$extension;
        // save the image in the image path we passed from the 
        // function parameter.
        Storage::disk('public')->put($image_path.'/' .$imageName, base64_decode($image));
        // return the image path and feed to the function that requests it
        return $image_path.'/'.$imageName;
    }
}

在上面的脚本中,我们已经定义了ImageRepository 类。该类有一个upload_image() 方法,该方法需要两个参数,即从前端提交的base64图片和image_path。

上述方法中传递的图像路径将是我们存储图像的地方。

该函数也有几个变量。

  • $image_64 持有base64格式的图像。
  • $extension 指的是图片的扩展名,即png。
  • $imageName.我们使用PHP内置的方法time() 和一个随机的字符串串联在一起得出图片的名字。这确保了图像名称的唯一性。
  • 返回语句返回图片的路径。

接下来,让我们来创建我们的控制器。运行以下命令,在App/Http/Controllers 目录下创建一个控制器。

php artisan make:controller ImageUploaderController

打开这个新文件App/Http/Controllers/ImageUploaderController.php ,并进行如下更新。

<?php
    /**
     * @group Image
     * Create new images uploaded
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function createUploadedImages(Request $request) {
      // validate the incoming API requests to ensure
      // that they contain the images to upload
        $validator=Validator::make($request->all(),[
            'image'       =>'required',
        ]);

      // if the request does not contain the image
      // handle the error message as shown below
        if($validator->fails()){
            return response()->json([
                'success'=>false,
                'message'=>$validator->errors()->first(),
            ],400);
        }
        // otherwise the image has been received and it's time handle them
       $attachment_url= (new ImageRepository)
       ->upload_image($request->input('banner_image'),'test-images');
       //use Image model to create image
        $image=Image::create([
            'image' =>$attachment_url
        ]);
        // return the success response
        return response()->json([
            'success'=>true,
            'message'=>"You have successfully created a test image",
        ],201);
    }

在上面的控制器中,我们定义了createUploadedImages 方法来处理请求。然后,我们继续验证这个请求并处理图像。

我们调用了图像库类来处理我们的图像。然后我们使用图像类实例在数据库表中创建一个图像。

最后,返回语句向上传应用程序发送一个成功信息,在这种情况下,Angular前端。

测试

为了测试我们的应用程序,我们需要定义我们的路由,并确保所有提交给服务器的图片是base64的。

因此,编辑routes/api.php 文件,如下图所示。

...
 Route::post('image-uploader',[ImageUploaderController::class,'createUploadedImages']);
...

接下来,通过运行以下命令为应用程序提供服务。

php artisan serve

现在你已经在你的Angular应用程序中运行了API服务器,编辑你最初创建的api服务,如下所示。

....
/**
   * Upload new images
   */
  uploadImage(image:Image):Observable<Image>{
    return this.httpClient.post<Image>(`${environment.API_BASE_URL}/image-uploader`,image);
  }
  ...

上述api服务确保我们只向我们在API服务器路由中定义的路由提交图片。

就这样,你现在可以使用Angular和Laravel轻松地处理图片了。

总结

在这篇文章中,我们已经深入地讨论了图片上传。我们还看到,我们如何使用事件将图像转换为base64。

我们也看到了我们如何使用图像库来处理base64图像,并将图像路径保存到服务器上的数据库中。