1.根据业务完成不同功能数据表的设计和编写
注册功能
在models/user.go中定义结构体 验证用户名是否已存在 添加用户
package models
import (
"gin-ranking/dao"
"time"
)
type User struct {
Id int `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
AddTime int64 `json:"addTime"`
UpdateTime int64 `json:"updateTime"`
}
func (User) TableName() string {
return "user"
}
func GetUserInfoByUsername(username string) (User, error) {
var user User
err := dao.Db.Where("username=?", username).First(&user).Error
return user, err
}
func AddUser(username string, password string) (int, error) {
user := User{Username: username, Password: password, AddTime: time.Now().Unix(), UpdateTime: time.Now().Unix()}
err := dao.Db.Create(&user).Error
return user.Id, err
}
在 controllers/user.go中编写注册功能接口
package controllers
import (
"gin-ranking/models"
"github.com/gin-gonic/gin"
)
type UserController struct {
}
func (u UserController) Register(c *gin.Context) {
//获取参数信息
username := c.DefaultPostForm("username", "")
password := c.DefaultPostForm("password", "")
confirmPassword := c.DefaultPostForm("confirmPassword", "")
if username == "" || password == "" || confirmPassword == "" {
ReturnError(c, 4001, "请输入正确的信息")
return
}
if password != confirmPassword {
ReturnError(c, 4001, "密码和确认密码不相同")
return
}
user, err := models.GetUserInfoByUsername(username)
if user.Id != 0 {
ReturnError(c, 4001, "此用户名已存在")
return
}
_, err = models.AddUser(username, EncyMd5(password))
if err != nil {
ReturnError(c, 4002, "注册失败,请重试")
return
}
ReturnSuccess(c, 0, "success", "", 1)
}
controllers/common.go 对密码进行加密
func EncyMd5(s string) string {
ctx := md5.New()
ctx.Write([]byte(s))
return hex.EncodeToString(ctx.Sum(nil))
}
登录
安装
go get -u github.com/gin-contrib/sessions/redis
go get -u github.com/gin-contrib/sessions
登录信息保存在session redis中
config/redis.go
package config
const (
RedisAddress = "localhost:6379"
)
controllers/user.go
// 如果直接使用上面的结构体 会将密码也返回 所以这里重新定义一个结构体用以返回
type UserApi struct {
Id int `json:"id"`
Username string `json:"username"`
}
func (u UserController) Login(c *gin.Context) {
// 接收用户名和密码
username := c.DefaultPostForm("username", "")
password := c.DefaultPostForm("password", "")
if username == "" || password == "" {
ReturnError(c, 4001, "请输入用户名和密码")
return
}
user, _ := models.GetUserInfoByUsername(username)
if user.Id == 0 {
ReturnError(c, 4004, "用户名或密码不正确")
return
}
if user.Password != EncyMd5(password) {
ReturnError(c, 4004, "用户名或密码不正确")
return
}
// 用户信息存储到session
session := sessions.Default(c)
//Int转换为字符串
session.Set("login:"+strconv.Itoa(user.Id), user.Id)
session.Save()
data := UserApi{Id: user.Id, Username: user.Username}
ReturnSuccess(c, 0, "登录成功", data, 1)
}
基于mysql实现投票功能
首先获取活动参赛选手接口
点击为他投票获取详情接口
controllers/player.go
package controllers
import (
"gin-ranking/models"
"github.com/gin-gonic/gin"
"strconv"
)
type PlayerController struct{}
func (p PlayerController) GetPlayers(c *gin.Context) {
aidStr := c.DefaultPostForm("aid", "0")
aid, _ := strconv.Atoi(aidStr)
rs, err := models.GetPlayers(aid, "id asc")
if err != nil {
ReturnError(c, 4004, "没有相关信息")
return
}
ReturnSuccess(c, 0, "success", rs, 1)
}
models/player.go
package models
import (
"gin-ranking/dao"
"github.com/jinzhu/gorm"
)
type Player struct {
Id int `json:"id"`
Aid int `json:"aid"`
Ref string `json:"ref"`
Nickname string `json:"nickname"`
Declaration string `json:"declaration"`
Avatar string `json:"avatar"`
Score int `json:"score"`
//AddTime int64 `json:"addTime"`
//UpdateTime int64 `json:"updateTime"`
}
func (Player) TableName() string {
return "player"
}
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
}
router/routers.go
player := r.Group("/player")
{
player.POST("/list", controllers.PlayerController{}.GetPlayers)
}
投票功能
controllers/vote.go
package controllers
import (
"gin-ranking/models"
"github.com/gin-gonic/gin"
"strconv"
)
type VoteController struct{}
func (v VoteController) AddVote(c *gin.Context) {
userIdStr := c.DefaultPostForm("userId", "0")
playerIdStr := c.DefaultPostForm("playerId", "0")
userId, _ := strconv.Atoi(userIdStr)
playerId, _ := strconv.Atoi(playerIdStr)
if userId == 0 || playerId == 0 {
ReturnError(c, 4001, "请输入正确的信息")
return
}
user, _ := models.GetUserInfo(userId)
if user.Id == 0 {
ReturnError(c, 4001, "投票用户不存在")
return
}
player, _ := models.GetPlayerInfo(playerId)
if player.Id == 0 {
ReturnError(c, 4001, "选手不存在")
return
}
vote, _ := models.GetVoteInfo(userId, playerId)
if vote.Id != 0 {
ReturnError(c, 4001, "已投票")
return
}
rs, err := models.AddVote(userId, playerId)
if err == nil {
//更新选手票数 自增1
models.UpdatePlayerScore(playerId)
//更新redis
//var redisKey string
//redisKey = "ranking:" + strconv.Itoa(player.Aid)
//cache.Rdb.ZIncrBy(cache.Rctx, redisKey, 1, strconv.Itoa(playerId))
ReturnSuccess(c, 0, "success", rs, 1)
return
}
ReturnError(c, 4004, "请联系管理员")
}
models/vote.go
package models
import (
"gin-ranking/dao"
"time"
)
type Vote struct {
Id int `json:"id"`
UserId int `json:"userId"`
PlayerId int `json:"playerId"`
AddTime int64 `json:"addTime"`
}
func (Vote) TableName() string {
return "vote"
}
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
}
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
}
router/routers.go
vote := r.Group("/vote")
{
vote.POST("/add", controllers.VoteController{}.AddVote)
}
models/user.go
func GetUserInfo(id int) (User, error) {
var user User
err := dao.Db.Where("id = ?", id).First(&user).Error
return user, err
}
models/player.go
func GetPlayerInfo(id int) (Player, error) {
var player Player
err := dao.Db.Where("id = ?", id).First(&player).Error
return player, err
}
models/player.go
func UpdatePlayerScore(id int) {
var player Player
dao.Db.Model(&player).Where("id = ?", id).UpdateColumn("score", gorm.Expr("score + ?", 1))
}
基于mysql数据库orderby排序实现排名展示功能
controllers/player.go
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, 4004, "没有相关信息")
return
}
ReturnSuccess(c, 0, "success", rs, 1)
return
}
modesl/player.go
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
}
r.POST("/ranking", controllers.PlayerController{}.GetRanking)
使用redis的有序集合 Sorted Sets优化排行榜功能
go get -u github.com/redis/go-redis/v9
controllers/player.go
package controllers
import (
"gin-ranking/cache"
"gin-ranking/models"
"github.com/gin-gonic/gin"
"strconv"
"time"
)
type PlayerController struct{}
func (p PlayerController) GetPlayers(c *gin.Context) {
aidStr := c.DefaultPostForm("aid", "0")
aid, _ := strconv.Atoi(aidStr)
rs, err := models.GetPlayers(aid, "id asc")
if err != nil {
ReturnError(c, 4004, "没有相关信息")
return
}
ReturnSuccess(c, 0, "success", rs, 1)
}
func (p PlayerController) GetRanking(c *gin.Context) {
aidStr := c.DefaultPostForm("aid", "0")
aid, _ := strconv.Atoi(aidStr)
var redisKey string
// 通过aid区分活动
redisKey = "ranking:" + aidStr
// 这段代码是使用 Go 语言操作 Redis 数据库,通过 ZRevRange 方法获取有序集合中指定范围内的成员,并按照分数从大到小排序。其中,redisKey 是有序集合的键名,0 和 -1 分别表示起始位置和结束位置,-1 表示最后一个成员。
rs, err := cache.Rdb.ZRevRange(cache.Rctx, redisKey, 0, -1).Result()
// 如果有就直接返回
if err == nil && len(rs) > 0 {
var players []models.Player
for _, value := range rs {
id, _ := strconv.Atoi(value)
rsInfo, _ := models.GetPlayerInfo(id)
if rsInfo.Id > 0 {
players = append(players, rsInfo)
}
}
ReturnSuccess(c, 0, "success", players, 1)
return
}
// 如果没有就直接从数据库获取
rsDb, errDb := models.GetPlayers(aid, "score desc")
//获取到值
if errDb == nil {
// 从mysql数据库获取到的值再存入到redis中
for _, value := range rsDb {
cache.Rdb.ZAdd(cache.Rctx, redisKey, cache.Zscore(value.Id, value.Score)).Err()
}
//遍历完成以后为rediskey设置过期时间
cache.Rdb.Expire(cache.Rctx, redisKey, 24*time.Hour)
ReturnSuccess(c, 0, "success", rs, 1)
return
}
ReturnError(c, 4004, "没有相关信息")
return
}
cache/redis.go
package cache
import (
"context"
"gin-ranking/config"
"github.com/redis/go-redis/v9"
)
var (
Rdb *redis.Client
Rctx context.Context
)
func init() {
Rdb = redis.NewClient(&redis.Options{
Addr: config.RedisAddress,
Password: config.RedisPassword, // 没有密码,默认值
DB: config.RedisDb, // 默认DB 0
})
Rctx = context.Background()
}
func Zscore(id int, score int) redis.Z {
return redis.Z{Score: float64(score), Member: id}
}
config/redis.go
package config
const (
RedisAddress = "localhost:6379"
RedisPassword = "123456"
RedisDb = 0
)
问题:当我们投票的时候,我们更新的是数据库,这个时候如果不更新redis,在缓存没有过期的这个时间段,它的排行榜是不变的,所以要优化一下投票的代码
前面是参赛选手的id 后面是分数