AdilA
@AdilA
Нравится кодить, изучаю go c echo

Как парсить форму multipart/form-data в Go с фреймворком echo?

Доброго времени суток! Изучаю язык golang, выбрал фреймворк echo. В принципе все понятно кроме некоторых фундаментальных вещей и синтаксиса. Решил изучать на реальном проекте, так как считаю что это самый эффективный способ. Взялся за проект, обычный сайт + админка, все было хорошо пока не дошел до HTML, а именно форм, требуется авторизовывать пользователя и выдавать ему токен, все отлично работает с JSON, но для того чтобы был SEO приходится рендерить HTML поэтому решил не использовать JS Front end framework'и типа VUE, и сделать все на HTML5, добрался до форм и не могу понять как спарсить данные с формы и засунуть их в MONGODB.

Создал модель

package model

import (
	mongopagination "github.com/gobeam/mongo-go-pagination"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

type User struct {
	*UserInput `bson:",inline"`
	ID         primitive.ObjectID `json:"id" xml:"id" form:"id" bson:"_id,omitempty"`
}

type UserInput struct {
	FirstName string `json:"firstName" xml:"firstName" form:"firstName" bson:"firstName" validate:"required"`
	LastName  string `json:"lastName" xml:"lastName" form:"lastName" bson:"lastName" validate:"required"`
	Email     string `json:"email" xml:"email" form:"email" bson:"email" validate:"required,email"`
	Password  string `json:"password,omitempty" form:"password,omitempty" xml:"password,omitempty" bson:"password" validate:"required"`
}

type LoginInput struct {
	Email    string `json:"email" xml:"email" bson:"email" validate:"required,email"`
	Password string `json:"password" xml:"password" bson:"password" validate:"required"`
}

type PagedUser struct {
	Data     []User                         `json:"data" xml:"data"`
	PageInfo mongopagination.PaginationData `json:"pageInfo" xml:"pageInfo"`
}


С формы public/signup.html

{{define "signup"}}
  {{template "head"}}
  {{template "header"}}
    <div class="signup">
      <div class="container">
        <div class="row">
          <div class="col-md-6">
            <form method="POST" action="/api/v1/signup">     
              <label>Email</label>
              <input email="email" type="email"/>

              <label>FirstName</label>
              <input firstName="firstName" type="text"/>

              <label>LastName</label>
              <input lastName="lastName" type="text"/>  

              <label>Password</label>
              <input password="password" type="password"/>

              <input type="submit" value="submit" />
          </form>
          </div>
          <div class="col-md-6">dd</div>
        </div>
      </div>
    </div>
  {{template "footer"}}
{{end}}


Посылаю форму на роут

package routes

import (
	"Avangard/controller"

	"github.com/labstack/echo/v4"
)

func GetUserApiRoutes(e *echo.Echo, userController *controller.UserController) {
	v1 := e.Group("/api/v1")
	{
		v1.POST("/login", userController.AuthenticateUser)
		v1.GET("/users", userController.GetAllUser)
		v1.POST("/signup", userController.SaveUser)
		v1.GET("/users/:id", userController.GetUser)
		v1.PUT("/users/:id", userController.UpdateUser)
		v1.DELETE("/users/:id", userController.DeleteUser)

	}
}


После чего обрабатываю в контроллере

package controller

import (
	"Avangard/exception"
	model "Avangard/models"
	"Avangard/repository"
	"Avangard/security"
	"Avangard/util"
	"log"
	"net/http"
	"strconv"

	"github.com/labstack/echo/v4"
)

type UserController struct {
	userRepository repository.UserRepository
	authValidator  *security.AuthValidator
}

func NewUserController(userRepository repository.UserRepository, authValidator *security.AuthValidator) *UserController {
	return &UserController{userRepository: userRepository, authValidator: authValidator}
}

func (userController *UserController) SaveUser(c echo.Context) error {

	payload := new(model.UserInput)
	//email := c.FormValue("email")
	log.Println(c, payload)
	if err := util.BindAndValidate(c, payload); err != nil {
		return err
	}

	_, err := userController.userRepository.FindByEmail(payload.Email)
	if err == nil {
		return exception.ConflictException("User", "email", payload.Email)
	}

	user := &model.User{UserInput: payload}

	//encrypt password
	err = beforeSave(user)
	if err != nil {
		return err
	}

	createdUser, err := userController.userRepository.SaveUser(user)
	if err != nil {
		return err
	}

	return util.Negotiate(c, http.StatusCreated, createdUser)
}

func beforeSave(user *model.User) (err error) {
	hashedPassword, err := util.EncryptPassword(user.Password)
	if err != nil {
		return err
	}
	user.Password = string(hashedPassword)
	return nil
}


После какая то магия в репозитории (я так и не понял как это работает, разбираюсь)

package repository

import (
	"Avangard/exception"
	model "Avangard/models"
	"context"

	paginate "github.com/gobeam/mongo-go-pagination"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
)

var cntx context.Context = context.TODO()

type UserRepository interface {
	GetAllUser(page int64, limit int64) (*model.PagedUser, error)
	SaveUser(user *model.User) (*model.User, error)
	FindByEmail(email string) (*model.User, error)
	GetUser(id string) (*model.User, error)
	UpdateUser(id string, user *model.User) (*model.User, error)
	DeleteUser(id string) error
}

type userRepositoryImpl struct {
	Connection *mongo.Database
}

func (userRepository *userRepositoryImpl) FindByEmail(email string) (*model.User, error) {
	var existingUser model.User
	filter := bson.M{"email": email}
	err := userRepository.Connection.Collection("users").FindOne(cntx, filter).Decode(&existingUser)
	if err != nil {
		return nil, err
	}
	return &existingUser, nil
}

func NewUserRepository(Connection *mongo.Database) UserRepository {
	return &userRepositoryImpl{Connection: Connection}
}

func (userRepository *userRepositoryImpl) GetAllUser(page int64, limit int64) (*model.PagedUser, error) {
	var users []model.User

	filter := bson.M{}

	collection := userRepository.Connection.Collection("users")

	//	projection := bson.D{
	//		{"id", 1},
	//		{"firstName", 1},
	//		{"lastName", 1},
	//		{"email", 1},
	//	}
	//

	projection := bson.D{
		{
			Key:   "id",
			Value: 1,
		},
		{
			Key:   "firstName",
			Value: 1,
		}, {
			Key:   "lastName",
			Value: 1,
		},
		{
			Key:   "email",
			Value: 1,
		}}

	paginatedData, err := paginate.New(collection).Context(cntx).Limit(limit).Page(page).Select(projection).Filter(filter).Decode(&users).Find()
	if err != nil {
		return nil, err
	}

	return &model.PagedUser{
		Data:     users,
		PageInfo: paginatedData.Pagination,
	}, nil
}

func (userRepository *userRepositoryImpl) SaveUser(user *model.User) (*model.User, error) {
	user.ID = primitive.NewObjectID()

	_, err := userRepository.Connection.Collection("users").InsertOne(cntx, user)
	if err != nil {
		return nil, err
	}

	user.Password = ""
	return user, nil
}

func (userRepository *userRepositoryImpl) GetUser(id string) (*model.User, error) {
	var existingUser model.User
	objectId, _ := primitive.ObjectIDFromHex(id)
	filter := bson.M{"_id": objectId}

	err := userRepository.Connection.Collection("users").FindOne(cntx, filter).Decode(&existingUser)
	if err != nil {
		return nil, exception.ResourceNotFoundException("User", "id", id)
	}

	existingUser.Password = ""
	return &existingUser, nil
}

func (userRepository *userRepositoryImpl) UpdateUser(id string, user *model.User) (*model.User, error) {
	objectId, _ := primitive.ObjectIDFromHex(id)
	filter := bson.M{"_id": objectId}

	result, err := userRepository.Connection.Collection("users").ReplaceOne(cntx, filter, user)
	if err != nil {
		return nil, err
	}
	if result.MatchedCount == 0 {
		return nil, exception.ResourceNotFoundException("User", "id", id)
	}

	user.ID = objectId
	user.Password = ""
	return user, nil
}

func (userRepository *userRepositoryImpl) DeleteUser(id string) error {
	objectId, _ := primitive.ObjectIDFromHex(id)
	filter := bson.M{"_id": objectId}

	result, err := userRepository.Connection.Collection("users").DeleteOne(cntx, filter)
	if err != nil {
		return err
	}
	if result.DeletedCount == 0 {
		return exception.ResourceNotFoundException("User", "id", id)
	}

	return nil
}


Эта магия тоже не до конца понятна, как обрабатывать form-data и что именно возвращать

package util

import (
	"github.com/labstack/echo/v4"
)

func Negotiate(c echo.Context, code int, i interface{}) error {
	mediaType := c.QueryParam("mediaType")

	switch mediaType {
	case "xml":
		return c.XML(code, i)
	case "json":
		return c.JSON(code, i)
	//case "multipart/form-data":
	//	return
	default:
		return c.JSON(code, i)
	}
}


Если с Postman указать Content-Type application/json и отправить данные то все нормально работает, но если отправлять с формы multipart/form-data то просто не проходит валидация, пишет что валидация не прошла {"time":"2021-05-22T21:45:31.134641998+05:00","level":"ERROR","prefix":"echo","file":"error.go","line":"53","message":"code=400, message=Key: 'UserInput.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag\nKey: 'UserInput.LastName' Error:Field validation for 'LastName' failed on the 'required' tag\nKey: 'UserInput.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'UserInput.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

Помогите пожалуйста!
  • Вопрос задан
  • 511 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы