golang叠加/重复组合Handler crashed with error runtime error: invalid memory address or nil pointer derefer

Handler crashed with error runtime error: invalid memory address or nil pointer dereference

最近新接触golang,用的beego框架;遇到一个挺特别的问题,百度、google都没有这样的情况;程序执行creashed ** Handler crashed with error runtime error: invalid memory address or nil pointer dereference ** 网上搜索了一波;都是变量没有实例化或者实例化之后指针没有采用指针调用的方式,亦或全局变量被局部定义了的一些案例。

之前有遇见过

import (
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/orm"
	_ "github.com/go-sql-driver/mysql"
	//_ "github.com/astaxie/beego/session/mysql" // mysql 存储 session 时启用,需注释上一行 mysql-driver 因为该包已导入了 mysql-driver
	_ "liumao801/lmadmin/models"
)

注释里面已经写明了原因 **包 github.com/astaxie/beego/session/mysql 已导入了 github.com/go-sql-driver/mysql 如果再次导入 github.com/go-sql-driver/mysql 就重复了 ** 因为程序只实例化了一次 mysql;第二次导入的 mysql 只定义了,没有实例化(不知道这样的说法是否正确,我是这么理解的) 。

下面看看我这次遇见的问题:
golang叠加/重复组合Handler crashed with error runtime error: invalid memory address or nil pointer derefer_第1张图片

代码如下(错误代码):
controller/TestController.go

package controllers

type TestController struct {
	BaseController   // BaseController 里面也组合了 beego.Controller
	UploadController
}
func (c *TestController) Upload() {
	c.LmUpload("upload")	// 这里调用公共的上传方法
}

controller/UploadController.go

package controllers

import (
	"bytes"
	"fmt"
	"github.com/astaxie/beego"
	"io"
	"liumao801/lmadmin/utils"
	"mime/multipart"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)


type UploadController struct {
	beego.Controller // 这里也组合 beego.Controller
}

type Sizer interface {
	Size() int64
}

const (
	LOCAL_FILE_DIR		= "static/upload/"
	MIN_FILE_SIZE 		= 1			// bytes
	MAX_FILE_SIZE 		= 5000000	// bytes
	IMAGE_TYPES 		= "(jpg|gif|p?jpeg|(x-)?png)"
	ACCEPT_FILE_TYPES = IMAGE_TYPES
	EXPIRATION_TIME 	= 300	// seconds
	THUMBNAIL_PARAM 	= "=s80"
)

var (
	imageTypes 		= regexp.MustCompile(IMAGE_TYPES)
	acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES)
)

type FileInfo struct {
	Url 		 string `json:"url,omitempty"`
	ThumbnailUrl string `json:"thumbnailUrl,omitempty"`
	Name 		 string `json:"name"`
	Type 		 string `json:"type"`
	Size 		 int64  `json:"size"`
	Error 		 string `json:"error,omitempty"`
	DeleteUrl 	 string `json:"deleteUrl,omitempty"`
	DeleteType 	 string `json:"deleteType,omitempty"`
}
// 检测文件类型是否合法
func (fi *FileInfo) ValidateType() (valid bool) {
	if acceptFileTypes.MatchString(fi.Type) {
		return true
	}

	fi.Error = "Filetype not allowed"
	return false
}
// 检查文件大小是否合法
func (fi *FileInfo) ValidateSize() (valid bool) {
	if fi.Size < MIN_FILE_SIZE {
		fi.Error = "File is too small"
	} else if fi.Size > MAX_FILE_SIZE {
		fi.Error = "File is too large"
	} else {
		return true
	}
	return false
}
// 检测是否合法
func (fi *FileInfo) check(err error)  {
	if err != nil {
		panic(err)
	}
}
func (fi *FileInfo) escape(s string) string {
	return strings.Replace(url.QueryEscape(s), "+", "%20", -1)
}
func (fi *FileInfo) getFormValue(p *multipart.Part) string{
	var b bytes.Buffer
	io.CopyN(&b, p, int64((1<<20)))	// Copy max: 1 MiB
	return b.String()
}
// 截取字符串
func substr(s string, pos, length int) string {
	runes := []rune(s)
	l := pos + length
	if l > len(runes) {
		l = len(runes)
	}
	return string(runes[pos:l])
}
// 获取父级 目录
func getParentDirectory(dir string) string {
	return substr(dir, 0, strings.LastIndex(dir, "/"))
}
// 获取当前目录
func getCurrentDirectory() string {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		utils.LogError(err)
	}
	return strings.Replace(dir, "\\", "/", -1)
}
// 获取文件后缀
func (fi *FileInfo) fileExt() {
	ext := path.Ext(fi.Name)
	fi.Type = ext
}

type UploadResult struct {
	Url 		string	`json:"url"`
	Uploaded 	bool	`json:"uploaded"`
	Msg 		string	`json:"msg"`
}
func (c *UploadController) CommonUpload() {
	var allfiles map[string][]*multipart.FileHeader = c.Ctx.Request.MultipartForm.File  // 这里会报错 crashed

	uped := &UploadResult{}
	var rel []*UploadResult

	for k, _ := range allfiles {
		file, err := c.LmUpload(k)
		if err != "" {
			uped.Uploaded = false
			uped.Msg = err
			uped.Url = ""
		} else {
			uped.Uploaded = true
			uped.Url = file
			uped.Msg = ""
		}
		if c.GetString("refer") == "CKEDITOR" {
			c.Data["json"] = uped
			c.ServeJSON()
			c.StopRun()
		}
		rel = append(rel, uped)
	}

	c.Data["json"] = rel
	c.ServeJSON()
}

// 文件上传的公共保存方法
// 返回文件路径和错误信息
// 返回第一个参数是文件;第二个参数是错误提示,""代表没有错误
func (c *UploadController) LmUpload(fileName string) (string, string) {
	a := c.GetString("command")
	beego.Info("command", a)
	file, fileHeader, err := c.GetFile(fileName) // 这里会报错 crashed
	if err != nil {
		return "", "文件获取失败"
	}
	defer file.Close()

	fi := &FileInfo{
		Name: fileHeader.Filename,
	}
	// 获取文件类型
	fi.fileExt()

	if !fi.ValidateType() {
		return "", "文件类型错误"
	}

	if sizeInterface, ok := file.(Sizer); ok {
		fi.Size = sizeInterface.Size()
		if !fi.ValidateSize() {
			return "", fi.Error
		}
	} else {
		return "", "文件大小获取失败"
	}
	now := time.Now()
	ctrlName, _ := c.GetControllerAndAction()
	dirPath := LOCAL_FILE_DIR + strings.ToLower(ctrlName[0 : len(ctrlName)-10]) + "/" + now.Format("2006-01") + "/" + now.Format("02")
	fileExt := strings.TrimLeft(fi.Type, ".")
	fileSaveName := fmt.Sprintf("%s_%d.%s", RandCode(5, 3), now.Unix(), fileExt)
	filePath := fmt.Sprintf("%s/%s", dirPath, fileSaveName)

	if !IsDir(dirPath) {
		if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
			return "", "文件夹“" + dirPath + "”创建失败"
		}
	}
	// 保存位置在 static/upload, 没有文件夹要先创建
	c.SaveToFile(fileName, filePath)
	return "/" + filePath, ""
}

本想着写一个公共的类来执行上传,在其他要用的地方直接组合到struct里面就行了;但是一直“Handler crashed with error runtime error: invalid memory address or nil pointer dereference”。这和之前做php的oop思想有点关系,php里面没有遇见类似的问题;因为一直用的require_once;所以这里也重复import了,但是报错了;报错的原因是重复定义了beego.Controller(一位叫“赤脚大仙”的朋友指出来的);但是框架本身却只实例化了一次beego.Controller或beego.Controller 里面import的某些包;所以第二次import的beego.Controller并未被实例化或beego.Controller 里面import的某些包没有实例化(不知道这样的说法是否正确,我是这么理解的)。从而导致 c.GetFile(fileName) // 这里会报错 crashed

解决的方法有两种:

1、让 BaseController 继承 UploadController,只 import 一次 beego.Controller

// file UploadController.go
package controller
import (
	"github.com/astaxie/beego"
)
type UploadController struct {
	beego.Controller
}

// file BaseController.go
package controller

import (
	"github.com/astaxie/beego"
)
type BaseController struct {
	//beego.Controller
	UploadController
}

// file TestController.go
package controller

import (
	"github.com/astaxie/beego"
)
type TestController struct {
	//beego.Controller
	BaseController 
}

像这样一层一层的组合进去;就没问题了;

2、把上传方法 func LmUpload 写成公共函数,把 *beego.Controller 当参数传过去“这是一位叫xeleven的朋友说的”比方法1的多层嵌套要好

修改之后的 func LmUpload 代码如下:

package controller

// 文件上传的公共保存方法
// 返回文件路径和错误信息
// 返回第一个参数是文件;第二个参数是错误提示,""代表没有错误
func LmUpload(c *beego.Controller, fileName string) (string, string) {
	a := c.GetString("command")
	beego.Info("command", a)
	file, fileHeader, err := c.GetFile(fileName)
	if err != nil {
		return "", "文件获取失败"
	}
	defer file.Close()

	fi := &FileInfo{
		Name: fileHeader.Filename,
	}
	// 获取文件类型
	fi.fileExt()

	if !fi.ValidateType() {
		return "", "文件类型错误"
	}

	if sizeInterface, ok := file.(Sizer); ok {
		fi.Size = sizeInterface.Size()
		if !fi.ValidateSize() {
			return "", fi.Error
		}
	} else {
		return "", "文件大小获取失败"
	}
	now := time.Now()
	ctrlName, _ := c.GetControllerAndAction()
	dirPath := LOCAL_FILE_DIR + strings.ToLower(ctrlName[0 : len(ctrlName)-10]) + "/" + now.Format("2006-01") + "/" + now.Format("02")
	fileExt := strings.TrimLeft(fi.Type, ".")
	fileSaveName := fmt.Sprintf("%s_%d.%s", RandCode(5, 3), now.Unix(), fileExt)
	filePath := fmt.Sprintf("%s/%s", dirPath, fileSaveName)

	beego.Info("filePath===========",filePath)
	if !IsDir(dirPath) {
		if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
			return "", "文件夹“" + dirPath + "”创建失败"
		}
	}
	// 保存位置在 static/upload, 没有文件夹要先创建
	c.SaveToFile(fileName, filePath)
	return "/" + filePath, ""
}

这样在调用的时候直接 ** LmUpload(*beego.Controller, fileName) ** 就可以了;以之前的TestController 的 Upload 方法调用为例

package controllers

type TestController struct {
	BaseController   // BaseController 里面也import了 beego.Controller
}
func (c *TestController) Upload() {
	LmUpload(&c.Controller, "upload")	// 这里调用公共的上传方法
}

经测试;以上2中方法都能解决重复组合的问题

你可能感兴趣的:(golang,beego)