最近新接触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 只定义了,没有实例化(不知道这样的说法是否正确,我是这么理解的) 。
代码如下(错误代码):
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
解决的方法有两种:
// 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
}
像这样一层一层的组合进去;就没问题了;
修改之后的 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中方法都能解决重复组合的问题