# Gin(六):文件上传 之前使用了数据库做了简单的增加和查询功能,今天再次使用数据库完成一些其他功能,比如说头像的上传和显示。 # 新增用户头像 当用户登录完成后,页面右上角会显示当前用户的用户 `email` 。下面我们做点击 `email` 进入用户详情页,并可以修改信息。 先完善后端接口。通过用户的 `id` 来获取用户的详细信息,同时我们写了一个错误页 `error.tmpl`,来进行错误信息的展示。 `userHandler.go` ```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, }) } ``` 代码中获取前端传递的id,通过 `strconv.Atoi()` 将String 类型转化为 int 类型。`user.QueryById()` 方法是我们用来进行对id查询的方法。 在进行 `QueryById` 方法之前,我们要对 `user` 结构体和数据库进行一下简单的修改。 ```go 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` 字段。 修改后数据库 ```sql 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` 方法 ```go 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 } ``` 该方法基本和上一节的通过邮箱查询用户方法基本一致。 完成该方法后,就可以将我们的路由添加上。 ```go userRouter.GET("/profile/", handler.UserProfile) ``` 此时就完成了后台的工作,剩下来就是对前端进行修改。 首先要重写划分一下前端的代码块。 ```html template | |-error.tmpl |-header.tmpl |-index.tmpl |-login.tmpl |-nav.tmpl |-user_profile.tmpl ``` 我们将原来 `index` 中 `head` 标签部分的代码移动到 `header` 中,将 `header` 原有的代码移动到 `nav.tmpl` 中。 `index.tmpl` ```html {{template "header"}}
{{template "nav" .}}
``` `header.tmpl` ```html {{ define "header" }} Gin Hello {{end}} ``` 在 `nav.tmpl` 中 ```html {{ if .email }} {{ else }} {{end}} ``` 通过路径 `/user/profile?id={{ .id }}` 将 `id` 数据传递后台。 当数据获取成功后,会转跳到 `user_profile.tmpl` 中 `user_profile.tmpl` ```html {{template "header"}} {{template "nav"}}
avatar
``` 该页面的 Email 不可编辑,密码可以修改,头像可以上传。 此时我们的页面就完成了。 # 上传头像 完成了基本页面,就要进行头像上传了。 在 `userHandler.go` 添加 `UpdateUserProfile` 方法 ```go 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` ```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 = filepath.Join(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` ```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/profile` 路由,这也页面上就会显示我们新的数据。 ![页面效果图](./img/6.1.jpeg) # 总结 通过本章节的学习 再次使用到了数据库的一些操作,同时也对文件上传进行了学习,并且完善了页面对于静态文件的显示。