文件上传

Buffalo

从 v0.10.3开始

Buffalo 允许轻松处理从表单上载的文件。 存储这些文件,比如到磁盘或 S3,取决于最终的开发人员: Buffalo 只是让您能够轻松地从请求访问文件。

配置表单

可以使用 f.FileTag 表单帮助器快速向表单添加file元素。 当使用这个方法时,表单的enctype类型会自动切换为multipart / form-data

接收表单文件

buffalo.Context c.File接受一个字符串,表单文件参数的名称,并将返回一个binding.File

func SomeHandler(c buffalo.Context) error {
 // ...
 f, err := c.File("someFile")
 if err != nil {
 return errors.WithStack(err)
 }
 // ...
}

绑定到结构体

c.Bind 允许将表单元素绑定到结构,但它也可以将上传的文件附加到结构。 为此,struct 属性的类型必须是binding.File类型。

在下面的示例中,您可以看到一个模型,它被配置为具有类型绑定的 MyFile 属性。 这个示例模型上有一个 AfterCreate 回调,在模型成功保存到数据库之后将文件保存到磁盘。

// models/widget.go
type Widget struct {
 ID        uuid.UUID    `json:"id" db:"id"`
 CreatedAt time.Time    `json:"created_at" db:"created_at"`
 UpdatedAt time.Time    `json:"updated_at" db:"updated_at"`
 Name      string       `json:"name" db:"name"`
 MyFile    binding.File `db:"-" form:"someFile"`
}func (w *Widget) AfterCreate(tx *pop.Connection) error {
 if !w.MyFile.Valid() {
 return nil
 }
 dir := filepath.Join(".", "uploads")
 if err := os.MkdirAll(dir, 0755); err != nil {
 return errors.WithStack(err)
 }
 f, err := os.Create(filepath.Join(dir, w.MyFile.Filename))
 if err != nil {
 return errors.WithStack(err)
 }
 defer f.Close()
 _, err = io.Copy(f, w.MyFile)
 return err
}

注意: 由于 db: “-”结构标记,MyFile 属性没有保存到数据库中。

测试文件上传

Http 测试库, github.com/gobuffalo/httptest 文件包(包含在布法罗测试用的 github.com/gobuffalo/suite 文件包中)已经更新,包含了两个新功能: MultiPartPostMultiPartPut

这些方法的工作原理和 PostPut 方法一样,但是它们提交的是一个多部分的表单,并且可以接受上传的文件。

PostPutMultiPartPostMultiPartPut 一样,第一个参数采用结构或映射: 这相当于您发布的 HTML 表单。 这些方法采用一个可变参数的第二个参数 httptest.File

一个httptest.File 文件需要表单参数的名称 ParamName、文件的名称 FileNameio.Reader,大概是你想要上传的文件。

/ actions/widgets_test.go
​
func (as *ActionSuite) Test_WidgetsResource_Create() {
 // clear out the uploads directory
 os.RemoveAll("./uploads")// setup a new Widget
 w := &models.Widget{Name: "Foo"}// find the file we want to upload
 r, err := os.Open("./logo.svg")
 as.NoError(err)
 // setup a new httptest.File to hold the file information
 f := httptest.File{
 // ParamName is the name of the form parameter
 ParamName: "someFile",
 // FileName is the name of the file being uploaded
 FileName: r.Name(),
 // Reader is the file that is to be uploaded, any io.Reader works
 Reader: r,
 }// Post the Widget and the File(s) to /widgets
 res, err := as.HTML("/widgets").MultiPartPost(w, f)
 as.NoError(err)
 as.Equal(302, res.Code)// assert the file exists on disk
 _, err = os.Stat("./uploads/logo.svg")
 as.NoError(err)// assert the Widget was saved to the DB correctly
 as.NoError(as.DB.First(w))
 as.Equal("Foo", w.Name)
 as.NotZero(w.ID)
}

// actions/widgets.go// Create adds a Widget to the DB. This function is mapped to the
// path POST /widgets
func (v WidgetsResource) Create(c buffalo.Context) error {
 // Allocate an empty Widget
 widget := &models.Widget{}// Bind widget to the html form elements
 if err := c.Bind(widget); err != nil {
 return errors.WithStack(err)
 }// Get the DB connection from the context
 tx, ok := c.Value("tx").(*pop.Connection)
 if !ok {
 return errors.WithStack(errors.New("no transaction found"))
 }// Validate the data from the html form
 verrs, err := tx.ValidateAndCreate(widget)
 if err != nil {
 return errors.WithStack(err)
 }if verrs.HasAny() {
 // Make widget available inside the html template
 c.Set("widget", widget)// Make the errors available inside the html template
 c.Set("errors", verrs)// Render again the new.html template that the user can
 // correct the input.
 return c.Render(422, r.HTML("widgets/new.html"))
 }// If there are no errors set a success message
 c.Flash().Add("success", "Widget was created successfully")// and redirect to the widgets index page
 return c.Redirect(302, "/widgets/%s", widget.ID)
}

// models/widgets.gopackage models
​
import (
 "encoding/json"
 "io"
 "os"
 "path/filepath"
 "time""github.com/gobuffalo/buffalo/binding"
 "github.com/gobuffalo/pop"
 "github.com/markbates/validate"
 "github.com/markbates/validate/validators"
 "github.com/pkg/errors"
 "github.com/satori/go.uuid"
)
​
type Widget struct {
 ID        uuid.UUID    `json:"id" db:"id"`
 CreatedAt time.Time    `json:"created_at" db:"created_at"`
 UpdatedAt time.Time    `json:"updated_at" db:"updated_at"`
 Name      string       `json:"name" db:"name"`
 MyFile    binding.File `db:"-" form:"someFile"`
}// String is not required by pop and may be deleted
func (w Widget) String() string {
 jw, _ := json.Marshal(w)
 return string(jw)
}// Widgets is not required by pop and may be deleted
type Widgets []Widget
​
// String is not required by pop and may be deleted
func (w Widgets) String() string {
 jw, _ := json.Marshal(w)
 return string(jw)
}func (w *Widget) AfterCreate(tx *pop.Connection) error {
 if !w.MyFile.Valid() {
 return nil
 }
 dir := filepath.Join(".", "uploads")
 if err := os.MkdirAll(dir, 0755); err != nil {
 return errors.WithStack(err)
 }
 f, err := os.Create(filepath.Join(dir, w.MyFile.Filename))
 if err != nil {
 return errors.WithStack(err)
 }
 defer f.Close()
 _, err = io.Copy(f, w.MyFile)
 return err
}// Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method.
// This method is not required and may be deleted.
func (w *Widget) Validate(tx *pop.Connection) (*validate.Errors, error) {
 return validate.Validate(
 &validators.StringIsPresent{Field: w.Name, Name: "Name"},
 ), nil
}// ValidateCreate gets run every time you call "pop.ValidateAndCreate" method.
// This method is not required and may be deleted.
func (w *Widget) ValidateCreate(tx *pop.Connection) (*validate.Errors, error) {
 return validate.NewErrors(), nil
}// ValidateUpdate gets run every time you call "pop.ValidateAndUpdate" method.
// This method is not required and may be deleted.
func (w *Widget) ValidateUpdate(tx *pop.Connection) (*validate.Errors, error) {
 return validate.NewErrors(), nil
}
本作品采用《CC 协议》,转载必须注明作者和本文链接

曹阿宇

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!