In the previous section, we covered article creation and modification page authoring and operations. However, the uploading of images is not handled. Here we show you how to configure the image upload function.

Image upload JS processing

The rich text editor LayEdit we use supports image uploading by default, but we need to configure the back-end receiving path. We open app.js, modify the initialization parameters of layEdit editor, and add the configuration of image uploading:

if($('#text-editor').length) {
    editorIndex = layedit.build('text-editor', {
        height: 450.uploadImage: {
            url: '/attachment/upload'.type: 'post'}}); }Copy the code

Ok, we now add the uploadImage parameter, declare that the submit is POST, and set the back-end receive path to /attachment/upload. We then simply return the result to the specified format defined by LayEdit, and the editor will automatically insert the image into the editor.

Image uploading back-end logic processing

Above we defined the image upload and receive path as /attachment/upload. According to this path, we created the image processing controller, created a new attachment.go file under the controller, and added the AttachmentUpload() function:

package controller

import (
	"github.com/kataras/iris/v12"
	"irisweb/config"
	"irisweb/provider"
)

func AttachmentUpload(ctx iris.Context) {
	file, info, err := ctx.FormFile("file")
	iferr ! =nil {
		ctx.JSON(iris.Map{
			"status": config.StatusFailed,
			"msg":    err.Error(),
		})
		return
	}
	defer file.Close()

	attachment, err := provider.AttachmentUpload(file, info)
	iferr ! =nil {
		ctx.JSON(iris.Map{
			"status": config.StatusFailed,
			"msg":    err.Error(),
		})
		return
	}

	ctx.JSON(iris.Map{
		"code": config.StatusOK,
		"msg":  ""."data": iris.Map{
			"src": attachment.Logo,
			"title": attachment.FileName,
		},
	})
}
Copy the code

This controller is only responsible for receiving user submitted photos, judge whether the normal submitted pictures, if not, return an error, if it is will transfer the file of the picture to the provider. AttachmentUpload () to deal with. Finally, the processing results are returned to the front end.

In the Provider folder, we create an attachment.go file and add AttachmentUpload() and GetAttachmentByMd5() :

package provider

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/nfnt/resize"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"irisweb/config"
	"irisweb/library"
	"irisweb/model"
	"log"
	"mime/multipart"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

func AttachmentUpload(file multipart.File, info *multipart.FileHeader) (*model.Attachment, error) {
	db := config.DB
	// Get the width and height
	bufFile := bufio.NewReader(file)
	img, imgType, err := image.Decode(bufFile)
	iferr ! =nil {
		// Cannot get image size
		fmt.Println("Unable to get image size")
		return nil, err
	}
	imgType = strings.ToLower(imgType)
	width := uint(img.Bounds().Dx())
	height := uint(img.Bounds().Dy())
	fmt.Println("width = ", width, " height = ", height)
	// Upload JPG, JPEG, GIF, PNG only
	ifimgType ! ="jpg"&& imgType ! ="jpeg"&& imgType ! ="gif"&& imgType ! ="png" {
		return nil, errors.New(fmt.Sprintf("Unsupported image format: %s.", imgType))
	}
	if imgType == "jpeg" {
		imgType = "jpg"
	}

	fileName := strings.TrimSuffix(info.Filename, path.Ext(info.Filename))
	log.Printf(fileName)

	_, err = file.Seek(0.0)
	iferr ! =nil {
		return nil, err
	}
	// Get the MD5 of the file and check whether the database already exists
	md5hash := md5.New()
	bufFile = bufio.NewReader(file)
	_, err = io.Copy(md5hash, bufFile)
	iferr ! =nil {
		return nil, err
	}
	md5Str := hex.EncodeToString(md5hash.Sum(nil))
	_, err = file.Seek(0.0)
	iferr ! =nil {
		return nil, err
	}

	attachment, err := GetAttachmentByMd5(md5Str)
	if err == nil {
		ifattachment.Status ! =1 {
			/ / update the status
			attachment.Status = 1
			err = attachment.Save(db)
			iferr ! =nil {
				return nil, err
			}
		}
		// Return directly
		return attachment, nil
	}

	// If the image width is greater than 750, it is automatically compressed to 750, GIF cannot be processed
	buff := &bytes.Buffer{}

	if width > 750&& imgType ! ="gif" {
		newImg := library.Resize(750.0, img, resize.Lanczos3)
		width = uint(newImg.Bounds().Dx())
		height = uint(newImg.Bounds().Dy())
		if imgType == "jpg" {
			// Save the cropped image
			_ = jpeg.Encode(buff, newImg, nil)}else if imgType == "png" {
			// Save the cropped image
			_ = png.Encode(buff, newImg)
		}
	} else {
		_, _ = io.Copy(buff, file)
	}

	tmpName := md5Str[8:24] + "." + imgType
	filePath := strconv.Itoa(time.Now().Year()) + strconv.Itoa(int(time.Now().Month())) + "/" + strconv.Itoa(time.Now().Day()) + "/"

	// Write the file locally
	basePath := config.ExecPath + "public/uploads/"
	// Check if the folder exists and create it if it does not
	_, err = os.Stat(basePath + filePath)
	iferr ! =nil && os.IsNotExist(err) {
		err = os.MkdirAll(basePath+filePath, os.ModePerm)
		iferr ! =nil {
			return nil, err
		}
	}

	originFile, err := os.OpenFile(basePath + filePath + tmpName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	iferr ! =nil {
		// Cannot be created
		return nil, err
	}

	defer originFile.Close()

	_, err = io.Copy(originFile, buff)
	iferr ! =nil {
		// Failed to write the file
		return nil, err
	}

	// Generate thumbnails of 250 width
	thumbName := "thumb_" + tmpName

	newImg := library.ThumbnailCrop(250.250, img)
	if imgType == "jpg" {
		_ = jpeg.Encode(buff, newImg, nil)}else if imgType == "png" {
		_ = png.Encode(buff, newImg)
	} else if imgType == "gif" {
		_ = gif.Encode(buff, newImg, nil)
	}

	thumbFile, err := os.OpenFile(basePath + filePath + thumbName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	iferr ! =nil {
		// Cannot be created
		return nil, err
	}

	defer thumbFile.Close()

	_, err = io.Copy(thumbFile, buff)
	iferr ! =nil {
		// Failed to write the file
		return nil, err
	}

	// File upload completed
	attachment = &model.Attachment{
		Id:           0,
		FileName:     fileName,
		FileLocation: filePath + tmpName,
		FileSize:     int64(info.Size),
		FileMd5:      md5Str,
		Width:        width,
		Height:       height,
		Status:       1,
	}
	attachment.GetThumb()

	err = attachment.Save(db)
	iferr ! =nil {
		return nil, err
	}

	return attachment, nil
}

func GetAttachmentByMd5(md5 string) (*model.Attachment, error) {
	db := config.DB
	var attach model.Attachment

	if err := db.Where("`status` ! = 99").Where("`file_md5` = ?", md5).First(&attach).Error; err ! =nil {
		return nil, err
	}

	attach.GetThumb()

	return &attach, nil
}
Copy the code

Because it’s image processing, there’s a little bit more code here. Since we need to take into account the uploaded images are JPG, PNG, GIF, etc., we need to introduce these packages separately to parse the images.

Above, the width, height and file size of the picture are obtained by analyzing the picture, and the MD5 value of the picture is also calculated. To prevent users from uploading the same image repeatedly, we determine whether the image is repeated according to the MD5 value of the image. If the image is the same, we do not perform subsequent processing and directly return to the image path that has been stored on the server.

In order to reduce the pressure on the server, we made a judgment on the image size when uploading the image. If the width is larger than 750 pixels, we will automatically compress the image to 750 pixels wide. Then, according to the date of uploading the picture, it is automatically stored in the directory of the server by year and a random name. If the directory does not exist, create it first.

A 250 pixel wide thumbnail is also created for each image as it is processed. This thumbnail will be cropped in the center.

As we have noticed above, we use a library/image image processing function for zooming and cropping. Since image processing is relatively complicated, we separate zooming and cropping into the Library. Now library creates an image.go file to store the image manipulation functions:

package library

import (
	"fmt"
	"github.com/nfnt/resize"
	"github.com/oliamb/cutter"
	"image"
)

func ThumbnailCrop(minWidth, minHeight uint, img image.Image) image.Image {
	origBounds := img.Bounds()
	origWidth := uint(origBounds.Dx())
	origHeight := uint(origBounds.Dy())
	newWidth, newHeight := origWidth, origHeight

	// Return original image if it have same or smaller size as constraints
	if minWidth >= origWidth && minHeight >= origHeight {
		return img
	}

	if minWidth > origWidth {
		minWidth = origWidth
	}

	if minHeight > origHeight {
		minHeight = origHeight
	}

	// Preserve aspect ratio
	if origWidth > minWidth {
		newHeight = uint(origHeight * minWidth / origWidth)
		if newHeight < 1 {
			newHeight = 1
		}
		//newWidth = minWidth
	}

	if newHeight < minHeight {
		newWidth = uint(newWidth * minHeight / newHeight)
		if newWidth < 1 {
			newWidth = 1
		}
		//newHeight = minHeight
	}

	if origWidth > origHeight {
		newWidth = minWidth
		newHeight = 0
	}else {
		newWidth = 0
		newHeight = minHeight
	}

	thumbImg := resize.Resize(newWidth, newHeight, img, resize.Lanczos3)
	//return CropImg(thumbImg, int(minWidth), int(minHeight))
	return thumbImg
}

func Resize(width, height uint, img image.Image, interp resize.InterpolationFunction) image.Image {
	return resize.Resize(width, height, img, interp)
}

func Thumbnail(width, height uint, img image.Image, interp resize.InterpolationFunction) image.Image {
	return resize.Thumbnail(width, height, img, interp)
}

func CropImg(srcImg image.Image, dstWidth, dstHeight int) image.Image {
	//origBounds := srcImg.Bounds()
	//origWidth := origBounds.Dx()
	//origHeight := origBounds.Dy()

	dstImg, err := cutter.Crop(srcImg, cutter.Config{
		Height: dstHeight,      // height in pixel or Y ratio(see Ratio Option below)
		Width:  dstWidth,       // width in pixel or X ratio
		Mode:   cutter.Centered, // Accepted Mode: TopLeft, Centered
		//Anchor: image.Point{
		//	origWidth / 12,
		//	origHeight / 8}, // Position of the top left point
		Options: 0.// Accepted Option: Ratio
	})
	fmt.Println()
	iferr ! =nil {
		fmt.Println("Cannot crop image:" + err.Error())
		return srcImg
	}
	return dstImg
}
Copy the code

I copied this photo manipulation file from someone else. It supports many forms of picture zooming and cropping. We used github.com/nfnt/resize package for image scaling and github.com/oliamb/cutter package for image cropping. I won’t go into details here, just know that it can crop and zoom images.

Image upload routing processing

After the above logic is processed, we still need to add the route to provide front-end access. Modify the route/base.go file and add the following code:

attachment := app.Party("/attachment", controller.Inspect)
{
  attachment.Post("/upload", controller.AttachmentUpload)
}
Copy the code

Here again, we define it as a routing group for future extension.

Verification of test results

The above operation is finished, let’s restart the project, type a different article, add pictures to the article, and see if you can upload pictures normally. If nothing else, we should see the image appear in the edit box.

The complete project sample code is hosted on GitHub. The complete project code can be viewed at github.com/fesiong/gob… You can also fork a copy to make changes on it.