大项目 - 记录第一批API的实现 | 青训营

85 阅读5分钟

距离我们组开始做大项目已经过去一周了,期间经历了两位队员的离开,一位队员阳了,等等情况。一开始进度非常缓慢,技术选型也是各种曲折。没想到一个周末已经完成了几个跟用户相关的API的开发,用这篇文章记录一下,也算是给自己巩固一下知识。

技术选型

后端框架gin
数据库模块gorm
数据库MySQL

目前阶段只用到这些。后续可能会用到的:缓存(Redis),消息队列(Redis或者RabbitMQ),对象存储(Amazon S3)。虽然这么短的时间内大概率我们来不及用到这些东西。

项目开发

项目初始化

在1024code的工作台新建一个代码空间。

因为默认的Golang代码空间已经提供了 go.mod ,就不需要 go mod init 命令来新建项目了。

安装所需的依赖包:

go get github.com/gin-gonic/gin
go get gorm.io/gorm
go get gorm.io/driver/mysql

我们可以直接编写代码,到时候直接 go mod tidy 就好了。

数据库

一开始大家在讨论要怎么部署数据库,既方便每个人本地测试,又可以有一个大家都能访问的数据库,存放一些公用的数据。

本地测试一开始是计划用 docker compose配置两个服务,一个是后端服务器,一个是MySQL。只要配置好了,大家直接 docker compose up 就好了,避免了大家用不同的系统导致的各种本地环境不一致的问题,而且方便快捷。

后来发现原来1024code上面提供了现成的数据库资源,包括MySQL,Redis,和PostgreSQL。优点是一键部署,而且把数据库连接所需的信息都设置在了环境变量里面,非常方便。我们就直接推翻了一开始的计划,改用1024code提供的MySQL。

连接数据库的代码:

// 连接到MySQL数据库
User := os.Getenv("MYSQL_USER")
Pass := os.Getenv("MYSQL_PASSWORD")
Host := os.Getenv("MYSQL_HOST")
Port := os.Getenv("MYSQL_PORT")
// dsn := "user:rootpassword@tcp(db:3306)/app?charset=utf8mb4&parseTime=True&loc=Local"
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/mysql?charset=utf8mb4&parseTime=True&loc=Local", User, Pass, Host, Port)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// TODO: defer close db
if err != nil {
    panic("failed to connect database")
}

直接从环境变量读取对应的值,然后生dsn,生成数据库连接。

表单

根据之前我们定义好的数据库表单,定义了如下的表单结构体。

// User 表示应用中的用户
type User struct {
    gorm.Model
    Username string `gorm:"unique"`
    Password string
    Profile  UserProfile `gorm:"foreignKey:UserID"`
}
​
// UserProfile 表示用户的额外信息
type UserProfile struct {
    gorm.Model
    UserID         uint
    Avatar         string
    Background     string
    Signature      string
    FollowCount    int
    FollowerCount  int
    TotalFavorited int
    WorkCount      int
}

根据GORM文档的说明,如果声明了gorm.Model,GORM会为每个结构体自动添加CreatedAtUpdatedAtDeletedAt,等字段。

遵循写的越多要改的越多的原则,我们就先之定义跟用户相关的结构体,等后面开发API时需要用到其它的表的时候再来定义。

在主函数中加入

db.AutoMigrate(&models.User{}, &models.UserProfile{})

用来自动将定义好的表单结构体迁移成MySQL中的表单。

初始化路由

在主函数中初始化路由,并且将之前建立的数据库连接注册为中间件,存入上下位,方面API处理函数跟数据库交互。

r := gin.Default()
​
// 注册中间件将db实例传递给每个处理函数
r.Use(func(c *gin.Context) {
    c.Set("db", db)
    c.Next()
})

API开发

用户注册

POST请求,必需的参数有usernamepassword

最简单的用户名和密码的注册方式,要求用户名保证唯一,创建成功后返回用户id和权限Token

API函数Register(c *gin.Context)的大致处理流程:

  1. 新建一个变量 var user models.User ,通过 c.PostForm("username")c.PostForm("password") 获取请求的用户名和密码,并赋值给 user 对应的字段
  2. 检查用户名和密码非空,且长度在6-25位之间。如果不满足要求,返回HTTP 400 Bad Request响应码
  3. 如果条件满足,试图将用户数据参入数据库:db.Create(&user)。注意我们不需要额外判断用户名是否重复,因为之前定义表单结构体的时候已经声明了用户名字段的属性有gorm:"unique",即唯一性。如果注册用户使用了已经存在的用户名,这里会报错,直接返回HTTP 400 Bad Request
  4. 如果注册成功,返回 HTTP 201 Created 表示创建成功。目前我们还没有加入Token鉴权的逻辑。

用户登录

POST请求,必需的参数有usernamepassword

类似地,通过用户名和密码进行登录,登录成功后返回用户 id 和权限 token。

API函数Login(c *gin.Context)的大致处理流程:

  1. 初始化两个models.User变量,userinputUser,前者是数据库中保存的用户,后者是通过API请求参数得到的输入用户
  2. 验证用户名密码非空,否则返回 HTTP 401 Unauthorized
  3. 通过GORM检索数据库看看名为username的记录是否存在,如果不存在,返回HTTP 401 Unauthorized
  4. 验证userinputUser的密码是否匹配。这里用的是最基本的明文匹配,实际生产中至少应该使用哈希后的密码。
  5. 如果上述验证都通过,返回 HTTP 200 OK,表示验证通过,返回用户ID和Token。

用户信息查询

GET请求,必需的参数有用户的ID和token,用来获取某个用户的主页信息。

API函数GetUser(c *gin.Context)的大致处理流程:

  1. 获取请求的ID,并转换为uint格式。在转换的过程中如果出现错误,说明用户请求的ID并不是一个数字,那肯定不是一个合法的ID,直接返回HTTP 400 Bad Request
  2. 通过GORM从数据库查找这个ID的用户,如果出现gorm.ErrRecordNotFound的错误,返回HTTP 404 Not Found
  3. 如果找到了用户,返回该用户的主页信息。

配置路由

不要忘了配置路由,将路由的URL跟定义的API函数一一对应起来。

r.POST("/douyin/user/register/", user.Register)
r.GET("/douyin/user/", user.GetUser)
r.POST("/douyin/user/login/", user.Login)

最后还要让服务器运行起来,这里设置后台服务器监听8080端口的请求。

r.Run(":8080")