Gin框架简易搭建(4)--项目开发

项目信息

项目四个初始功能所在处,很快将对投票验证信息以及录入活动和参赛者的功能将在后续实现

需求分析

这个项目需要如下的功能:

  1. 参赛人员列表及详情(前端)
  2. 排行榜
  3. 投票功能
  4. 用户的注册和登陆
  5. 活动规则(前端)

数据库

表名称参数一(主键)参数二参数三参数四参数五参数六参数七参数八
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(添加时间)

补充

  1. gin.Context,这个结构体方便我们通过它访问http请求的各种信息。简而言之,就是通过修改它的内容,在返回的报文显示。
  2. module文件夹中的结构体Json后缀是什么意思?在json中,后缀里的名字就是这个结构体成员变量的名称;而程序中该成员变量的名称是前面声明的名称
  3. where查询:查到的话——Error返回nil;没查到返回错误信息,这一点还是挺容易写错的
  4. c.DefaultPostForm获取方式:(参数1,参数2)返回值——参数1是键名,参数2是设置的“若没有查询到的返回默认值”。("username",""),这种用法,就是说如果查询到就返回查询到的用户名,查不到就返回空字符串。

数据库注意事项的

这个项目的主要问题来源于数据库

  1. 传输类型:注意结构体声明的时候,类型和数据库的类型是否一致

  2. 字段名:字段名在gormmysql之间存在转化关系,建议程序里面使用驼峰命名,数据库中使用下划线隔开

    1. 数据库字段名与结构体字段名的转换
    • 默认转换规则GORM默认会将结构体字段的驼峰命名转换为数据库字段的下划线命名。例如,Go 结构体中的字段 UserName 将自动映射到数据库中的字段 user_name

    • 自定义转换:如果需要自定义字段名的转换规则,可以通过标签(tag)来指定。例如:

      type User struct {
          UserName string `gorm:"column:username"`
      }
      

      在这种情况下,即使结构体字段名为UserName,GORM 也会将其映射到数据库中的username字段。

    1. 标签的使用
    • column 标签:通过 gorm:"column:xxx" 标签可以显式指定数据库中的字段名。
    • type 标签:可以指定数据库字段的数据类型。
    • size 标签:可以指定字段的大小(例如字符串长度)。
    • primaryKey 标签:指定字段为数据库表的主键。
    1. 自动迁移
    • 当使用 GORM的自动迁移功能(AutoMigrate)时,GORM会根据结构体字段名自动生成数据库表结构。这种情况下,GORM会自动应用上述的字段名转换规则。
    1. 忽略字段
    • 如果某些字段不需要映射到数据库表中,可以使用 gorm:"-" 标签将其忽略。
  3. sql语句查错:在sql语句使用时,建议直接在Gorm中文文档中复制,在报错信息出现时,建议直接将error打印在json文件中,在测试的插件中以报文形式返回。

代码解释

这个项目的代码可以参考项目地址里面的完整代码,下面的部分是对于开发的四个简单功能进行详细的解释

首先,我们的设计模式依旧是路由-->类controller-->模块func,对于核心的设计,选择的方法是获取信息-->验证-->录入数据库并返回json

注册

首先,注册我们使用用户名、密码、确认密码作为输入,将用户类的部分(id和用户名)通过新建结构体的方式进行返回。验证部分:

  1. 首先我们需要对输入进行验证,保证输入不为空
  2. 之后,要保证输入的密码与确认密码一致
  3. 最后,对用户数据库进行遍历,发现没有重复的用户名
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 User
	err := dao.Db.Where("username =?", username).First(&user).Error
	return 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).Error
	userapi := 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 User
	err := dao.Db.Where("id =?", id).First(&user).Error
	return 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, "已经投票过了")
	}

	//添加vote
	rs, 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 Player
	dao.Db.Model(&player).Where("id =?", id).UpdateColumn("score", gorm.Expr("score + ?", 1))
}

//下面两个都是在vote models
// 用来检查是否投过票了
func GetVoteInfo(userId int, playerId int) (Vote, error) {
	var vote Vote
	err := dao.Db.Where("user_id =? AND player_id =?", userId, playerId).First(&vote).Error
	return 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).Error
	return 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 []Player
	err := dao.Db.Where("aid =?", aid).Order(sort).Find(&players).Error
	return players, err
}
### 创建或配置 Gin 项目的示例教程 #### 1. 安装依赖 在开始之前,需要确保已安装 Go 环境并设置好 GOPATH 和 GOBIN 路径。接着通过以下命令安装 Gin 框架: ```bash go get -u github.com/gin-gonic/gin ``` #### 2. 初始化项目结构 在一个空文件夹中初始化一个新的 Go 模块,并创建必要的目录结构。可以运行以下命令来初始化模块: ```bash go mod init myginproject ``` 这会生成 `go.mod` 文件,记录项目的依赖项。 #### 3. 编写基础代码 基于提供的示例[^3],可以在项目根目录下创建一个名为 `main.go` 的文件,并编写如下代码: ```go package main import ( "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { c.String(200, "Hello, EuanSu") }) r.Run(":8080") // 默认监听端口为 8080 } ``` 这段代码定义了一个简单的 HTTP Server,当访问 `/` 路由时返回字符串 `"Hello, EuanSu"`。 #### 4. 配置环境变量 如果项目涉及敏感数据(如 API 密钥),建议使用 `.env` 文件管理这些变量。按照引用中的说明[^4],先创建一个 `.env.sample` 文件作为模板,再将其重命名为 `.env` 并填充实际值。随后在代码中引入 `godotenv` 库加载环境变量: ```go package main import ( "log" "os" "github.com/joho/godotenv" "github.com/gin-gonic/gin" ) func init() { err := godotenv.Load() if err != nil { log.Fatalf("无法加载 .env 文件:%v", err) } } func main() { auth0Domain := os.Getenv("AUTH0_DOMAIN") r := gin.Default() r.GET("/auth-domain", func(c *gin.Context) { c.JSON(200, gin.H{ "domain": auth0Domain, }) }) r.Run(":8080") } ``` 此部分展示了如何读取环境变量并将它嵌入到响应逻辑中。 #### 5. 使用热加载工具提升开发效率 为了提高开发体验,可利用第三方插件实现代码修改后的自动重启功能。推荐使用的工具有 `air` 或者 `fresh`。以下是 `air` 的基本用法[^1]: - 下载工具:`go install github.com/cosmtrek/air@latest` - 启动服务:`air` 这样无需手动停止和重新启动服务器即可看到更改效果。 #### 6. 参考官方示例仓库扩展功能 对于更复杂的场景需求,可以直接参考官方维护的示例代码库[^2]获取灵感和支持。该资源包含了众多实用案例,比如处理表单提交、上传文件以及集成数据库操作等。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值