项目信息
项目四个初始功能所在处,很快将对投票验证信息以及录入活动和参赛者的功能将在后续实现
需求分析
这个项目需要如下的功能:
- 参赛人员列表及详情(前端)
- 排行榜
- 投票功能
- 用户的注册和登陆
- 活动规则(前端)
数据库
表名称 | 参数一(主键) | 参数二 | 参数三 | 参数四 | 参数五 | 参数六 | 参数七 | 参数八 |
---|---|---|---|---|---|---|---|---|
activity(活动表) | id(主键) | name(活动名称) | add_time(添加时间) | |||||
player(参赛选手表) | id(主键) | nickname(选手名称) | aid(参与活动) | ref(编号) | avatar(头像int) | score(得分) | declaration(个性签名) | update_time(更新时间) |
user(群众评委) | id(主键) | username(用户名) | password(密码) | add_time(添加时间) | update_time(更新时间) | |||
vote(用户给选手点赞记录表) | id(主键) | user_id(投票者id) | player_id(参赛选手id) | add_time(添加时间) |
补充
gin.Context
,这个结构体方便我们通过它访问http
请求的各种信息。简而言之,就是通过修改它的内容,在返回的报文显示。- module文件夹中的结构体
Json
后缀是什么意思?在json
中,后缀里的名字就是这个结构体成员变量的名称;而程序中该成员变量的名称是前面声明的名称 - where查询:查到的话——Error返回nil;没查到返回错误信息,这一点还是挺容易写错的
c.DefaultPostForm
获取方式:(参数1,参数2)返回值——参数1是键名,参数2是设置的“若没有查询到的返回默认值”。("username","")
,这种用法,就是说如果查询到就返回查询到的用户名,查不到就返回空字符串。
数据库注意事项的
这个项目的主要问题来源于数据库
-
传输类型:注意结构体声明的时候,类型和数据库的类型是否一致
-
字段名:字段名在
gorm
和mysql
之间存在转化关系,建议程序里面使用驼峰命名,数据库中使用下划线隔开- 数据库字段名与结构体字段名的转换
-
默认转换规则:
GORM
默认会将结构体字段的驼峰命名转换为数据库字段的下划线命名。例如,Go 结构体中的字段UserName
将自动映射到数据库中的字段user_name
。 -
自定义转换:如果需要自定义字段名的转换规则,可以通过标签(tag)来指定。例如:
type User struct {UserName string `gorm:"column:username"` }
在这种情况下,即使结构体字段名为
UserName
,GORM 也会将其映射到数据库中的username
字段。
- 标签的使用
column
标签:通过gorm:"column:xxx"
标签可以显式指定数据库中的字段名。type
标签:可以指定数据库字段的数据类型。size
标签:可以指定字段的大小(例如字符串长度)。primaryKey
标签:指定字段为数据库表的主键。
- 自动迁移
- 当使用
GORM
的自动迁移功能(AutoMigrate
)时,GORM
会根据结构体字段名自动生成数据库表结构。这种情况下,GORM
会自动应用上述的字段名转换规则。
- 忽略字段
- 如果某些字段不需要映射到数据库表中,可以使用
gorm:"-"
标签将其忽略。
-
sql
语句查错:在sql
语句使用时,建议直接在Gorm
中文文档中复制,在报错信息出现时,建议直接将error打印在json
文件中,在测试的插件中以报文形式返回。
代码解释
这个项目的代码可以参考项目地址里面的完整代码,下面的部分是对于开发的四个简单功能进行详细的解释
首先,我们的设计模式依旧是路由-->类controller-->模块func
,对于核心的类
设计,选择的方法是获取信息-->验证-->录入数据库并返回json
注册
首先,注册我们使用用户名、密码、确认密码作为输入,将用户类的部分(id和用户名)通过新建结构体的方式进行返回。验证部分:
- 首先我们需要对输入进行验证,保证输入不为空
- 之后,要保证输入的密码与确认密码一致
- 最后,对用户数据库进行遍历,发现没有重复的用户名
func (u UserController) Register(c *gin.Context) {//接受用户名 密码以及确认密码username := c.DefaultPostForm("username", "")password := c.DefaultPostForm("password", "")confirmPassword := c.DefaultPostForm("confirm_password", "")//验证 输入是否存在某项为空 密码和确认密码是否一致 是否已经存在该用户if username == "" || password == "" || confirmPassword == "" {ReturnError(c, 4001, "输入的用户名、密码、确认密码其中有空项")return}if password != confirmPassword {ReturnError(c, 4002, "密码和确认密码不一致")return}user1, _ := models.CheckUserExist(username)if user1.Id != 0 {ReturnError(c, 4003, "该用户已存在")return}//创建用户userapi, err2 := models.AddUser(username, EncryMd5(password))if err2 != nil {ReturnError(c, 4004, "保存用户失败")return}ReturnSuccess(c, 0, "注册成功", userapi, 1)
}
函数说明:
// 判断用户名是否已经存在
func CheckUserExist(username string) (User, error) {var user Usererr := dao.Db.Where("username =?", username).First(&user).Errorreturn user, err
}// 保存用户
func AddUser(username, password string) (UserApi, error) {user := User{Username: username, Password: password, AddTime: time.Now().Unix(), UpdateTime: time.Now().Unix()}err := dao.Db.Create(&user).Erroruserapi := UserApi{Username: username, Userid: user.Id}return userapi, err
}
登陆
登陆时,设计思路与上面的注册一致,只是在验证环节需要将输入的密码和数据库里面的加密版本进行比较,看是否一致
// 登陆
func (u UserController) Login(c *gin.Context) {//接受用户名 密码username := c.DefaultPostForm("username", "")password := c.DefaultPostForm("password", "")//验证 用户名或者密码为空 用户名不存在 密码错误if username == "" || password == "" {ReturnError(c, 4011, "用户名或密码为空")return}user1, err := models.CheckUserExist(username)if err != nil {ReturnError(c, 4012, "用户名不存在")return}if user1.Password != EncryMd5(password) {ReturnError(c, 4013, "密码错误")return}使用sessions//session := sessions.Default(c)//session.Set("Login"+strconv.Itoa(user1.Id), user1.Id)//session.Save()//返回登录信息date := UserLoginApi{Id: user1.Id,Username: user1.Username,}ReturnSuccess(c, 0, "登录成功", date, 1)
}
函数说明:
// 通过Id来查找用户
func CheckUserById(id int) (User, error) {var user Usererr := dao.Db.Where("id =?", id).First(&user).Errorreturn user, err
}
投票
在实现这个功能的时候,需要注意:我们既要像注册功能一样在vote表中更新一条记录,又要对参赛者的字段进行更新。字段的更新我们使用Gorm
提供的语句:
db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;
源代码:
type VoteController struct{}func (v VoteController) AddVote(c *gin.Context) {//获取数据userIdStr := c.DefaultPostForm("user_id", "0")playerIdStr := c.DefaultPostForm("player_id", "0")userId, _ := strconv.Atoi(userIdStr)playerId, _ := strconv.Atoi(playerIdStr)//检查数输入if userId == 0 || playerId == 0 {ReturnError(c, 4031, "输入参数为空,请重新输入")}user, _ := models.CheckUserById(userId)if user.Id == 0 {ReturnError(c, 4032, "用户不存在")}player, _ := models.GetPlayerById(playerId)if player.Id == 0 {ReturnError(c, 4033, "参赛者不存在")}//检查是否已经vote了 返回值重复投票vote, _ := models.GetVoteInfo(userId, playerId)if vote.Id != 0 { //已经投过票了ReturnError(c, 4034, "已经投票过了")}//添加voters, err := models.AddVote(userId, playerId)if err == nil {models.UpdateScoreByVote(playerId)ReturnSuccess(c, 0, "投票成功", rs, 1)return}ReturnError(c, 4035, err.Error())
}
函数说明:
// 通过投票来更新得分 player models
func UpdateScoreByVote(id int) {var player Playerdao.Db.Model(&player).Where("id =?", id).UpdateColumn("score", gorm.Expr("score + ?", 1))
}//下面两个都是在vote models
// 用来检查是否投过票了
func GetVoteInfo(userId int, playerId int) (Vote, error) {var vote Voteerr := dao.Db.Where("user_id =? AND player_id =?", userId, playerId).First(&vote).Errorreturn vote, err
}// 实现投票的记录
func AddVote(userId int, playerId int) (int, error) {vote := Vote{UserId: userId,PlayerId: playerId,AddTime: time.Now().Unix(),}err := dao.Db.Create(&vote).Errorreturn vote.Id, err
}
排行榜
这里使用了一个order关键字 定义排序字段以及顺序
func (p PlayerController) GetRanking(c *gin.Context) {//获取活动编号aidStr := c.DefaultPostForm("aid", "0")aid, _ := strconv.Atoi(aidStr)rs, err := models.GetPlayers(aid, "score desc")if err != nil {ReturnError(c, 4021, "获取排名失败")return}ReturnSuccess(c, 0, "获取成功", rs, 1)return
}
函数说明:
// 获取某种顺序排列的某一活动的玩家列表 DESC降序 ASC升序
func GetPlayers(aid int, sort string) ([]Player, error) {var players []Playererr := dao.Db.Where("aid =?", aid).Order(sort).Find(&players).Errorreturn players, err
}