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.