Джин (шесть): загрузка файла

Go Gin

Эта статья была впервые опубликована вISLAND

Раньше я использовал базу данных для добавления кратких ответов и функций запроса, сегодня я снова использую базу данных для выполнения некоторых других функций, таких как загрузка и отображение аватаров.

📷Добавить аватар пользователя

Когда пользователь вошел в систему, пользователь текущего пользователя будет отображаться в правом верхнем углу страницы.email. Ниже делаем кликemailВойдите на страницу сведений о пользователе и измените информацию.

Сначала улучшите внутренний интерфейс. со стороны пользователяidчтобы получить данные пользователя, и в то же время мы написали страницу с ошибкойerror.tmpl, чтобы отобразить сообщение об ошибке.

userHandler.go

func UserProfile(context *gin.Context) {
	id := context.Query("id")
	var user model.UserModel
	i, err := strconv.Atoi(id)
	u, e := user.QueryById(i)
	if e != nil || err != nil {
		context.HTML(http.StatusOK, "error.tmpl", gin.H{
			"error": e,
		})
	}
	context.HTML(http.StatusOK, "user_profile.tmpl", gin.H{
		"user": u,
	})
}

Получите идентификатор, переданный внешним интерфейсом в коде, черезstrconv.Atoi()Преобразование типа String в тип int.user.QueryById()Наш метод - это метод, используемый для идентификатора запроса.

в ходе выполненияQueryByIdметод, мы должныuserСтруктура и база данных делают простую модификацию.

type UserModel struct {
	Id       int            `form:"id"`
	Email    string         `form:"email" binding:"email"`
	Password string         `form:"password" `
	Avatar   sql.NullString
}

Добавляем новую строкуAvatar, типsql.NullString. Зачемsql.NullString? Поскольку поле в нашей базе данных изначальноnull,а такжеstringтип не приемлемnullтип, поэтому мы можем использовать толькоNullStringиди прямоnullСтроки обрабатываются.

При этом для добавления в базу добавить новый столбецavatarполе.

база данных после модификации

create table user
(
	id int auto_increment
		primary key,
	email varchar(30) not null,
	password varchar(40) not null,
	avatar varchar(100) null
)
comment '用户表';

После завершения предыдущей работы по модификации можно заняться остальными.

🕵️‍Получить информацию о пользователе

существуетuserModel.goПолучить информацию о пользователе изQueryByIdметод

func (user *UserModel) QueryById(id int) (UserModel, error) {
	u := UserModel{}
	row := initDB.Db.QueryRow("select * from user where id = ?;", id)
	e := row.Scan(&u.Id, &u.Email, &u.Password, &u.Avatar)
	if e != nil {
		log.Panicln(e)
	}
	return u, e
}

Этот метод в основном аналогичен методу запроса пользователей по почтовому ящику в предыдущем разделе.

Как только этот метод завершен, мы можем добавить наш маршрут.

userRouter.GET("/profile/", handler.UserProfile)

На этом фоновая работа завершена, осталось доработать интерфейс.

Первое, что нужно сделать, это переписать и разделить блоки внешнего кода.

template
|
|-error.tmpl
|-header.tmpl
|-index.tmpl
|-login.tmpl
|-nav.tmpl
|-user_profile.tmpl

мы будемindexсерединаheadКод в разделе ярлыков перемещен вheaderв, будетheaderИсходный код был перемещен вnav.tmplсередина.

index.tmpl

{{template "header"}}
<header>
    {{template "nav" .}}
</header>
<main>

</main>

header.tmpl

{{ define "header" }}
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <link rel="stylesheet" href="/statics/css/bootstrap.min.css">
        <link rel="stylesheet" href="/statics/css/bootstrap-grid.min.css">
        <link rel="stylesheet" href="/statics/css/bootstrap-reboot.min.css">
        <script src="/statics/js/jquery.min.js" rel="script"></script>
        <script src="/statics/js/Popper.js" rel="script"></script>
        <script rel="script" src="/statics/js/bootstrap.bundle.js"></script>
        <title>Gin Hello</title>
    </head>
{{end}}

существуетnav.tmplсередина

{{ if .email }}
	<ul class="navbar-nav ">
		<li class="nav-item">
			<a class="nav-link" href="/user/profile?id={{.id}}">{{ .email }}</a>
		</li>
	</ul>
{{ else }}
	<ul class="navbar-nav ">
		<li class="nav-item">
			<a class="nav-link" data-toggle="modal" data-target="#login-modal">登录</a>
		</li>
		<li class="nav-item">
			<a class="nav-link" data-toggle="modal" data-target="#register-modal">注册</a>
		</li>
	</ul>
{{end}}

через путь/user/profile?id={{ .id }}будетidБэкэнд передачи данных.

Когда данные будут получены успешно, они перейдут кuser_profile.tmplсередина

user_profile.tmpl

{{template "header"}}
{{template "nav"}}
<div class="container">
    <div class="row">
        <div class="col-sm">

            <div>
                <img src="{{ .user.Avatar.String }}" alt="avatar" class="rounded-circle">
            </div>

        </div>
        <div class="col-sm">
            <form method="post" action="/user/update" enctype="multipart/form-data">
                <div class="form-group" hidden>
                    <label for="user-id">id</label>
                    <input type="text" id="user-id"
                           name="id"
                           value="{{ .user.Id }}">
                </div>
                <div class="form-group">
                    <label for="user-email">Email</label>
                    <input type="email" class="form-control" id="user-email" aria-describedby="emailHelp"
                           name="email"
                           readonly
                           placeholder="Enter email"
                           value="{{ .user.Email }}">
                </div>
                <div class="form-group">
                    <label for="user-password">密码</label>
                    <input type="password" class="form-control" id="user-password" placeholder="密码" name="password"
                           value="{{.user.Password}}">
                </div>
                <div class="form-group">
                    <label for="user-avatar">上传头像</label>
                    <input type="file" class="form-control-file" id="user-avatar" name="avatar-file">
                </div>
                <button type="submit" class="btn btn-primary">保存</button>
            </form>
        </div>
        <div class="col-sm"></div>
    </div>
</div>

Электронная почта на этой странице не может быть отредактирована, пароль может быть изменен, а аватар может быть загружен.

На этом наша страница готова.

🛫 Загрузить аватар

После заполнения основной страницы пришло время загрузить аватар.

существуетuserHandler.goДобавить кUpdateUserProfileметод

func UpdateUserProfile(context *gin.Context) {
	var user model.UserModel
	if err := context.ShouldBind(&user); err != nil {
		context.HTML(http.StatusOK, "error.tmpl", gin.H{
			"error": err.Error(),
		})
		log.Panicln("绑定发生错误 ", err.Error())
	}
	file, e := context.FormFile("avatar-file")
	if e != nil {
		context.HTML(http.StatusOK, "error.tmpl", gin.H{
			"error": e,
		})
		log.Panicln("文件上传错误", e.Error())
	}
}

по привязке данныхid emailПривязать с паролем, затем пройтиcontext.FormFile()Получить данные файла.

Данные файла можно получить, затем полученный файл следует сохранить.

Сначала напишите класс инструмента, чтобы получить корневой путь нашего проекта.

создать новыйutilsпапка,utilsЧжунсинpathUtils.go

package utils

import (
	"log"
	"os"
	"os/exec"
	"strings"
)

func RootPath() string {
	s, err := exec.LookPath(os.Args[0])
	if err != nil {
		log.Panicln("发生错误",err.Error())
	}
	i := strings.LastIndex(s, "\\")
	path := s[0 : i+1]
	return path
}

Напишите класс инструмента, чтобы мы могли использовать его напрямую в будущем.

// 省略部分代码
path := utils.RootPath()
path = path + "avatar\\"
e = os.MkdirAll(path, os.ModePerm)
if e != nil {
	context.HTML(http.StatusOK, "error.tmpl", gin.H{
		"error": e,
	})
	log.Panicln("无法创建文件夹", e.Error())
}
fileName := strconv.FormatInt(time.Now().Unix(), 10) + file.Filename
e = context.SaveUploadedFile(file, path+fileName)
if e != nil {
	context.HTML(http.StatusOK, "error.tmpl", gin.H{
		"error": e,
	})
	log.Panicln("无法保存文件", e.Error())
}

Получением текущего времени обеспечивается уникальность изображения, и загруженное изображение не будет перезаписано из-за того же имени.

Вот вопрос для размышления.Так как мы загрузили аватар, нам нужно отобразить его на странице.Отображению аватара нужно получить адрес, так как же получить адрес после сохранения изображения?

При настройке маршрута ранее мы один раз задали каталог статического файла, и изображение аватара также является статическим файлом, поэтому нам нужно снова установить каталог загрузки.

initRouter.go

router.StaticFS("/avatar", http.Dir(utils.RootPath()+"avatar/"))

Мы отображаем путь, который мы загрузили, как/avatarПосле этого мы можем получить доступ к ресурсам, изменив путь.

Завершите наш окончательный код

avatarUrl := "http://localhost:8080/avatar/" + fileName
	user.Avatar = sql.NullString{String: avatarUrl}
	e = user.Update(user.Id)
	if e != nil {
		context.HTML(http.StatusOK, "error.tmpl", gin.H{
			"error": e,
		})
		log.Panicln("数据无法更新", e.Error())
	}
	context.Redirect(http.StatusMovedPermanently, "/user/profile?id="+strconv.Itoa(user.Id))

Когда изображение сообщает об ошибке, мы снова перенаправляем на/user/profileRouting, на этой странице также будут отображаться наши новые данные.

最后效果

✍Резюме

Благодаря обучению в этой главе некоторые операции с базой данных используются снова, а также изучается загрузка файлов и улучшается отображение статических файлов на странице.

👩‍💻 Код для этой главы

Github

историческая статья

Джин (1): Привет
Джин (2): маршрутизатор маршрутизации
Джин (три): шаблон tmpl
Джин (четыре): проверка отправки формы и привязка модели
Джин (5): подключиться к MySQL
Джин (шесть): загрузка файла
Джин (семь): использование и определение промежуточного программного обеспечения Джин (8): использование файлов cookie

Личный публичный аккаунт

Я только недавно подал заявку на публичный аккаунт, и в будущем статьи будут отправляться синхронно.Приглашаю всех обратить внимание