go个人论坛项目

搭建个人论坛

在这里插入图片描述

项目地址:MyForum: go+gin+vue搭建论坛 - Gitee.com

PS:有些地方没有写好,请选择性查看

初始化项目

  1. 创建目录结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 利用ini配置初始化框架
[server]
AppMode = debug
HttpPort = :3000
JwtKey = "dhjasdkajh321"[database]
Db = mysql
DbHost = localhost
DbPort = 3306
DbUser = root
DbPassWord = flzx3qc
DbName = gForum
package configimport ("fmt""gopkg.in/ini.v1"
)var (AppMode stringHttpPort  stringJwtKey stringDb stringDbHost stringDbPort stringDbUser stringDbPassWord stringDbName string
)func init(){file, err := ini.Load("config/config.ini")if err!=nil{fmt.Println("参数初始化错误:",err)}LoadServer(file)LoadDataBase(file)
}func LoadServer(file *ini.File){AppMode = file.Section("server").Key("AppMode").MustString("debug")HttpPort = file.Section("server").Key("HttpPort").MustString("3000")JwtKey = file.Section("server").Key("JwtKey").MustString("dhajsfkas")
}func LoadDataBase(file *ini.File){Db = file.Section("databse").Key("Db").MustString("")DbHost = file.Section("databse").Key("DbHost").MustString("")DbPort = file.Section("databse").Key("DbPort").MustString("")DbUser = file.Section("databse").Key("DbUser").MustString("")DbPassWord = file.Section("databse").Key("DbPassWord").MustString("")DbName = file.Section("databse").Key("DbName").MustString("")
}
  1. 搭建gin框架
package myforumimport "MY-FORUM/routers"func main() {routers.InitRouter()
}
package routersimport ("MY-FORUM/config""github.com/gin-gonic/gin"
)func InitRouter() {gin.SetMode(config.AppMode)r := gin.Default()r.Run(config.HttpPort)
}

注册登录模块

创建数据库

package mainimport ("MY-FORUM/models""MY-FORUM/routers"
)func main() {// 连接数据库models.InitDb()// 配置路由routers.InitRouter()
}
package modelsimport ("MY-FORUM/config""fmt""time""gorm.io/driver/mysql""gorm.io/gorm"
)var db *gorm.DB
var err errorfunc InitDb(){dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local",config.DbUser, config.DbPassWord, config.DbHost, config.DbPort, config.DbName)db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})if err!=nil{fmt.Println("数据库连接失败", err)}sqlDB, err := db.DB()if err!=nil{fmt.Println("数据库连接失败:", err)}// 模型迁移db.AutoMigrate(&User{})// 设置连接池最大闲置连接数sqlDB.SetMaxIdleConns(10)// 设置数据库的最大连接数量sqlDB.SetMaxOpenConns(100)// 设置连接的最大可复用时间sqlDB.SetConnMaxLifetime(10*time.Second)
}
package modelsimport "gorm.io/gorm"type User struct {gorm.ModelUserId int64 `gorm:"type:bigint(20);primaryKey;autoIncrement" josn:"userid"`UserName string `gorm:"type:varchar(64);not null" json:"username"`PassWord string `gorm:"type:varchar(64);not null" json:"password"`Email string `gorm:"type:varchar(64)" json:"email"`Gender int `gorm:"type:tinyint(4)" json:"gender"`Role int `gorm:"type:tinyint(4);default:1" json:"role"`
}

注册

  1. 定义注册接收表单结构体,并利用validator进行参数校验设置

PS:binding在绑定时进行校验,validate需要手动调用函数进行校验(参考:Go 验证器 validator 详解 | Go 技术论坛)

type UserSignUp struct{UserName string `json:"username" validate:"required"`PassWord string `json:"password" validate:"required"`ConfirmPassWord string `json:"confirm_password" validate:"required,eqfield=PassWord"`
}
  1. 进行gorm操作

查询用户名是否已经存在

// 判断username是否已经存在
func FindUserNameExist(username string)bool{var user Usererr := db.Where("user_name = ?", username).First(&user).Errorif err!=nil{fmt.Println("用户名称查询失败", err)}if user.ID > 0{return true}else{return false}
}

添加用户

// 添加用户
func AddUser(user User)int{err := db.Create(&user).Errorif err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}
  1. 进行api函数的编写
func SignUp(c *gin.Context){var userSignUp models.UserSignUp_ = c.ShouldBind(&userSignUp)err := translater.Validate.Struct(userSignUp)// 参数校验有问题if err!=nil{msg := []string{}for _, err := range err.(validator.ValidationErrors) {msg = append(msg, err.Translate(translater.Trans))}fmt.Println(msg)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : msg,"data" : userSignUp,})c.Abort()return}username := userSignUp.UserNamepassword := userSignUp.PassWordif models.FindUserNameExist(username){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.USERNAME_IS_EXIST),"data" : userSignUp,})c.Abort()return}user := models.User{UserName: username,PassWord: password,}code := models.AddUser(user)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : userSignUp,})
}
  1. 注册路由
func InitUserRouter(r *gin.Engine){UserRouter := r.Group("v1/user")UserRouter.POST("add", controller.SignUp) // 用户注册
}

登录

  1. 定义接收登录表达信息的结构体
type UserLogin struct{UserName string `json:"username" validate:"required"`PassWord string  `json:"password" validate:"required"`AccessToken stringRefreshToken string
}
  1. 在库中查询密码
// 获取用户密码
func GetPassWord(username string)string{var user Usererr := db.Where("user_name = ?", username).First(&user).Errorif err!=nil{fmt.Println("密码获取失败", err)}return user.PassWord
}
  1. 获取Token
package jwtimport ("MY-FORUM/config""errors""time""github.com/dgrijalva/jwt-go"
)type MyClaims struct {UserName string `json:"username"`jwt.StandardClaims
}var mySecret = []byte(config.JwtKey)func keyFunc(_ *jwt.Token) (i interface{}, err error) {return mySecret, nil
}const tokenExpire = 2*time.Hour// 生成accessToken refreshToken
func GenToken(username string) (aToken, rToken string, err error) {// 创建一个自己的声明c := MyClaims{username,jwt.StandardClaims{	// JWT规定的7个官方字段ExpiresAt: time.Now().Add(tokenExpire).Unix(), // 过期时间Issuer:    "why",                                 // 签发人},}// 加密并获得完整的编码后的字符串tokenaToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)// refresh token 不需要存任何自定义数据rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ExpiresAt: time.Now().Add(time.Second * 30).Unix(), // 过期时间Issuer:    "bluebell",                              // 签发人}).SignedString(mySecret)// 使用指定的secret签名并获得完整的编码后的字符串tokenreturn
}
  1. 编写登录api
// 登录
func Login(c *gin.Context){var userLogin models.UserLogin_ = c.ShouldBind(&userLogin)err := Validate.Struct(userLogin)// 参数校验错误if err!=nil{msg := []string{}for _, err := range err.(validator.ValidationErrors){msg = append(msg, err.Translate(Trans))}c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : msg,"data" : userLogin,})c.Abort()return}// 判断用户是否存在if !models.FindUserNameExist(userLogin.UserName){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.USER_NOT_EXIST),"data" : userLogin,})c.Abort()return}// 验证密码是否正确password := encrypt.GenerateSpw(userLogin.PassWord)truePassword := models.GetPassWord(userLogin.UserName)if password!=truePassword{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.PASSWORD_IS_WRONG),"data" : userLogin,})c.Abort()return}// 获取tokenatoken,rtoken, err := jwt.GenToken(userLogin.UserName)if err != nil {c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.TOKEN_GET_ERROR),"data" : userLogin,})c.Abort()return}userLogin.AccessToken = atokenuserLogin.RefreshToken = rtokenc.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.SUCCES),"data" : userLogin,})
}

鉴权

jwt鉴权中间件

func JwtAuthMiddle() func(c *gin.Context){return func(c *gin.Context){// 验证header中是否有AuthorizationauthHeader := c.GetHeader("Authorization")if authHeader=="" || !strings.HasPrefix(authHeader,"Bearer "){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : "用户权限不足",})c.Abort()return}// 解析atokenatoken := authHeader[7:]claims, err := jwt.ParseToken(atoken)if err!=nil{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.TOKEN_PARSE_ERROR),"data" : atoken,})c.Abort()return}// 验证用户username := claims.UserNameif !models.FindUserNameExist(username){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.USER_NOT_EXIST),"data" : username,})c.Abort()return}// 将username写入上下文c.Set("username", username)c.Next()}
}

rtoken refresh atoken

// rtoken刷新atoken
func RefreshToken(atoken, rtoken string)(token string, err error){// 判断rtoken是否有效if _,err = jwt.Parse(rtoken, keyFunc);err!=nil{return}// 从旧的atoken中解析出来usernameclaims, err := ParseToken(atoken)// 如果还未过期,则返回开始的atokenif err==nil{token = atokenreturn}// 判断atoken是否为过期类型的错误v,_ := err.(*jwt.ValidationError)if v.Errors == jwt.ValidationErrorExpired{token,err = GetaToken(claims.UserName)err = nil}return
}
// 刷新atoken
func RefreshToken(c *gin.Context){rtoken := c.Query("rtoken")// 验证header中是否有AuthorizationauthHeader := c.GetHeader("Authorization")if authHeader=="" || !strings.HasPrefix(authHeader,"Bearer "){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : "Token不存在",})c.Abort()return}// 解析atokenatoken := authHeader[7:]atoken,err := jwt.RefreshToken(atoken, rtoken)if err!=nil{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : "Token解析失败",})c.Abort()return}c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.SUCCES),"atoken" : atoken,})
}

优化

密码加密存储
package encryptimport ("MY-FORUM/config""encoding/base64""fmt""golang.org/x/crypto/scrypt"
)func GenerateSpw(pw string) string {const KeyLen = 22const N = 1 << 10                             // CPU内存成本参数,必须是一个大于1的2的幂const r, p = 8, 1                             // r*p <2^30// salt长度为8比较好npw, err := scrypt.Key([]byte(pw), config.Salt, N, r, p, KeyLen)if err != nil {fmt.Println("密码加密失败", err)}return base64.StdEncoding.EncodeToString(npw) // 获取编码后的字符串
}
func (u *User)BeforeSave(_ *gorm.DB)(err error){u.PassWord = encrypt.GenerateSpw(u.PassWord)return nil
}
雪花算法生成用户ID

社区分类及详情模块

创建数据库

package modelsimport "gorm.io/gorm"type Community struct {gorm.ModelCommunityId int `gorm:"type:int(10)" json:"community_id"`CommunityName string `gorm:"type:varchar(64)" json:"community_name"`Introduction string `gorm:"type:varchar(256)" json:"introduction"`
}
// 模型迁移
db.AutoMigrate(&User{},&Community{})
package modelsimport ("MY-FORUM/utils/errmsg""fmt""time""gorm.io/gorm"
)type Community struct {gorm.ModelCommunityName string `gorm:"type:varchar(64)" json:"community_name" validate:"required"`Introduction  string `gorm:"type:varchar(256)" json:"introduction" validate:"required"`Issure string `gorm:"type:varchar(256)" json:"issure" validate:"required"`
}type CommunityDetail struct {ID            int       `json:"id" db:"id"`CommunityName string    `json:"community_name" db:"community_name"`Introduction  string    `json:"introduction" db:"introduction"`CreatedAt      time.Time `json:"created_at" db:"created_at"`UpdatedAt     time.Time `json:"updated_at" db:"updated_at"`Issure string `json:"issure" db:"issure"`
}// 判断社区是否存在
func FindCommunity(name string) bool {var comm Communityerr := db.Where("community_name = ?", name).First(&comm).Errorif err != nil {fmt.Println("社区查询失败", err)}if comm.ID > 0 {return true}return false
}// 判断id是否存在
func FindCommunityByID(id int)(bool,string){var comm Communityerr := db.Where("id = ?", id).First(&comm).Errorif err != nil {fmt.Println("社区查询失败", err)}if comm.ID > 0 {return true, comm.Issure}return false,""
}// 添加社区
func AddCommunity(comm Community) int {err := db.Create(&comm).Errorif err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}// 修改社区
func ChangeCommunity(comm Community)int{mp := map[string]interface{}{}mp["Community_name"] = comm.CommunityNamemp["introduction"] = comm.Introductionerr := db.Model(&comm).Updates(mp).Errorif err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}// 删除社区
func DeleteCommunity(id int)int{var comm Communityerr := db.Where("id = ?", id).Delete(&comm).Errorif err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}// 查询社区列表(分页)
func GetCommunityList(pagesize, pagenum int)([]Community, int){var commList []Communityerr := db.Select("id", "community_name").Limit(pagesize).Offset((pagenum-1)*pagesize).Find(&commList).Errorif err!=nil{return commList, errmsg.ERROR}return commList, errmsg.SUCCES
}// 查询社区详情
func GetCommunityDetail(id int)(CommunityDetail, int){var comm CommunityDetailerr := db.Model(&Community{}).Where("id = ?",id).First(&comm).Errorif err!=nil{fmt.Println("社区详情查询失败", err)return comm, errmsg.ERROR}return comm, errmsg.SUCCES
}

增加社区类别(鉴权)

// 添加社区
func AddCommunity(c *gin.Context){var community models.Community_ = c.ShouldBind(&community)// 获取中间件的usernameusername := c.GetString("username")community.Issure = username// 参数校验errs := Validate.Struct(community)if errs!=nil{msg := []string{}for _,err := range errs.(validator.ValidationErrors){msg = append(msg, err.Translate(Trans))}c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : msg,"data" : community,})c.Abort()return}// 判断社区是否存在if models.FindCommunity(community.CommunityName){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.COMMUNITY_IS_EXIST),"data" : community,})c.Abort()return}// 添加社区code := models.AddCommunity(community)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : community,})
}

获取社区分类列表

func GetCommunityList(c *gin.Context){pagesize,_ := strconv.Atoi(c.Query("pagesize"))pagenum,_ := strconv.Atoi(c.Query("pagenum"))if pagesize<=0{pagesize = -1}if pagenum<=1{pagenum = 1}commList, code := models.GetCommunityList(pagesize, pagenum)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : commList,})
}

根据社区ID查找分类详情

// 查询单个社区详情
func GetCommunityDetail(c *gin.Context){id,_ := strconv.Atoi(c.Query("id"))comm, code := models.GetCommunityDetail(id)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : comm,})
}

修改社区(鉴权)

// 修改社区
func ChangeCommunity(c *gin.Context){var community models.Community_ = c.ShouldBind(&community)// 获取中间件的usernameusername := c.GetString("username")community.Issure = username// 参数校验errs := Validate.Struct(community)if errs!=nil{msg := []string{}for _,err := range errs.(validator.ValidationErrors){msg = append(msg, err.Translate(Trans))}c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : msg,"data" : community,})c.Abort()return}// 判断id是否存在flag, issure := models.FindCommunityByID(int(community.ID))if !flag{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.COMMUNITY_IS_NOT_EXIST),"data" : community,})c.Abort()return}// 判断是否为issureif issure!=c.GetString("username"){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.USER_NOT_TRUE),"data" : community,})c.Abort()return}// 判断社区名称是否存在if models.FindCommunity(community.CommunityName){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.COMMUNITY_IS_EXIST),"data" : community,})c.Abort()return}// 修改社区code := models.ChangeCommunity(community)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : community,})
}

删除社区(鉴权)

// 删除社区
func DeleteCommunity(c *gin.Context){id,_ :=strconv.Atoi(c.Query("id"))// 判断id是否存在flag, issure := models.FindCommunityByID(id)if !flag{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.COMMUNITY_IS_NOT_EXIST),"data" : id,})c.Abort()return}// 判断是否为issureif issure!=c.GetString("username"){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.USER_NOT_TRUE),"data" : id,})c.Abort()return}code := models.DeleteCommunity(id)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : id,})
}

论坛帖子模块

创建数据库

package modelsimport ("MY-FORUM/utils/errmsg""gorm.io/gorm"
)type Post struct {gorm.ModelTitle string `gorm:"type:varchar(64)" json:"title" validate:"required"`Content string `gorm:"type:longtext" json:"content" validate:"required"`AuthorName string `gorm:"type:varchar(64);not null" json:"author_name"`CommunityId int `gorm:"type:bigint;not null" json:"community_id" validate:"required"`Status int `gorm:"type:tinyint(4);default:0" json:"status"`
}// 添加帖子数据
func AddPost(post Post)int{err := db.Create(&post).Errorif err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}// 分页展示帖子列表
func GetPostList(pagesize, pagenum int)([]Post, int){var posts []Posterr := db.Limit(pagesize).Offset((pagenum-1)*pagesize).Find(&posts).Errorif err!=nil{return posts, errmsg.ERROR}return posts, errmsg.SUCCES
}// 获取帖子详情
func GetPostDetail(id int)(Post, int){var post Posterr := db.Where("id = ?", id).First(&post).Errorif err!=nil{return post, errmsg.ERROR}return post, errmsg.SUCCES
}// 修改帖子
func ChangePost(post Post)int{mp := map[string]interface{}{}mp["title"] = post.Titlemp["content"] = post.Contentmp["community_id"] = post.CommunityIdmp["status"] = post.Statuserr := db.Model(&Post{}).Where("id = ?", post.ID).Updates(mp).Errorif err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}// 删除帖子
func DeletePost(id int)int{var post Posterr := db.Where("id = ?", id).Delete(&post).Errorif err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}
// 模型迁移db.AutoMigrate(&User{},&Community{},&Post{})

创建帖子

func AddPost(c *gin.Context){var post models.Post_ = c.ShouldBind(&post)post.AuthorName = c.GetString("username")// 进行参数校验err := Validate.Struct(post)if err!=nil{msg := []string{}for _,err := range err.(validator.ValidationErrors){msg = append(msg, err.Translate(Trans))}c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : msg,"data" : post,})c.Abort()return}// 判断社区是否存在if flag,_ :=models.FindCommunityByID(post.CommunityId);!flag{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.COMMUNITY_IS_NOT_EXIST),"data" : post,})c.Abort()return}// 创建帖子code := models.AddPost(post)if code!=errmsg.SUCCES{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : post,})c.Abort()return}// redis创建帖子code = redis.CreatePost(int(post.ID), post.CommunityId, post.Title, post.Content, post.AuthorName)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : post,})
}

分页展示帖子列表

func GetPostList(c *gin.Context){pagesize,_ := strconv.Atoi(c.Query("pagesize"))pagenum,_ := strconv.Atoi(c.Query("pagenum"))if pagesize <= 0{pagesize = -1}if pagenum < 1{pagenum = 1}posts, code := models.GetPostList(pagesize, pagenum)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : posts,})
}

获取帖子详情

func GetPostDetail(c *gin.Context){id,_ :=strconv.Atoi(c.Query("post_id"))_,code := models.GetPostDetail(id)if code==errmsg.ERROR{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.POST_IS_NOT_EXIST),})c.Abort()return}post, code := models.GetPostDetail(id)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : post,})
}

修改帖子

func ChangePost(c *gin.Context){var post models.Post_ = c.ShouldBind(&post)// 进行参数校验err := Validate.Struct(post)if err!=nil{msg := []string{}for _,err := range err.(validator.ValidationErrors){msg = append(msg, err.Translate(Trans))}c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : msg,"data" : post,})c.Abort()return}// 判断帖子是否存在findPost,code := models.GetPostDetail(int(post.ID))if code==errmsg.ERROR{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.POST_IS_NOT_EXIST),"data" : post,})c.Abort()return}// 判断是否为自己的帖子if findPost.AuthorName != c.GetString("username"){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.USER_NOT_TRUE),"data" : post,})c.Abort()return}// 判断社区是否存在if flag,_ :=models.FindCommunityByID(post.CommunityId);!flag{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.COMMUNITY_IS_NOT_EXIST),"data" : post,})c.Abort()return}// 修改帖子内容code = models.ChangePost(post)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : post,})
}

删除帖子

func DeletePost(c *gin.Context){id,_ := strconv.Atoi(c.Query("post_id"))// 判断帖子是否存在findPost,code := models.GetPostDetail(id)if code==errmsg.ERROR{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.POST_IS_NOT_EXIST),"post_id" : id,})c.Abort()return}// 判断是否为自己的帖子if findPost.AuthorName != c.GetString("username"){c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.USER_NOT_TRUE),"data" : findPost,})c.Abort()return}// 删除帖子code = models.DeletePost(id)c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(code),"data" : findPost,})
}

按照时间or分数排序展示帖子列表

全部帖子
某一社区帖子

优化

redis缓存帖子列表
redis mysql 数据一致性

投票模块

redis数据库

package redisimport ("MY-FORUM/config""fmt""github.com/go-redis/redis"
)var (client *redis.Client
)func init(){client = redis.NewClient(&redis.Options{Addr : config.RedisHost+":"+config.RedisPort,Password : "",DB : 0,PoolSize:8,MinIdleConns:1,})_,err :=client.Ping().Result()if err!=nil{fmt.Println("redis连接失败", err)}
}func Close(){_ = client.Close()
}

在redis中添加帖子相关信息

// redis存储帖子信息
func CreatePost(postid, community int, title, content, author_name string) int {now := time.Now().Unix()votedKey := PostVotedZSet+strconv.Itoa(postid)communityKey := CommunityPostSet+strconv.Itoa(community)// 事务操作// 创建一个事务管道pipeline := client.TxPipeline()// 设置投票过期时间pipeline.Expire(votedKey, time.Second*OneWekSecond)// 添加帖子时间排序pipeline.ZAdd(PostTimeZSet, redis.Z{Score: float64(now),Member: postid,})// 添加帖子分数排序pipeline.ZAdd(PostScoreZSet, redis.Z{Score: float64(0),Member: postid,})// 添加到对应的社区pipeline.SAdd(communityKey, postid)// 执行管道中所有命令_, err := pipeline.Exec()if err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}

为帖子投票

func PostVoteHandler(c *gin.Context) {var item PostVote_ = c.ShouldBind(&item)// 进行参数校验errs := Validate.Struct(item)if errs!=nil{msg := []string{}for _,err := range errs.(validator.ValidationErrors){msg = append(msg, err.Translate(Trans))}c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.ERROR),"msg" : msg,"data" : item,})c.Abort()return}// 判断帖子是否存在post_id,_ := strconv.Atoi(item.PostId)_,code := models.GetPostDetail(post_id)if code==errmsg.ERROR{c.JSON(http.StatusOK, gin.H{"status" : errmsg.GetErrMsg(errmsg.POST_IS_NOT_EXIST),"data" : item,})c.Abort()return}// 进行投票code = redis.VoteForPost(c.GetString("username"), item.PostId, float64(item.Direction))c.JSON(http.StatusOK, gin.H{"status": errmsg.GetErrMsg(code),"data": item,})
}
// 为帖子投票
func VoteForPost(username, postid string, v float64)int {// 判断现在是否还在投票期// 获取帖子发布日期postTime := client.ZScore(PostTimeZSet, string(postid)).Val()if float64(time.Now().Unix()-int64(postTime))>OneWekSecond{return errmsg.POST_VOTE_IS_EXPIRE}key := PostVotedZSet+postid// 查看当前用户给帖子的投票记录ov := client.ZScore(key, username).Val()// 判断是否重复投票if ov==v{return errmsg.POST_USER_VOTED}// 计算两次投票的差值diff := v-ov// 创建一个事务管道pipeline := client.TxPipeline()// 更新帖子分数pipeline.ZIncrBy(PostScoreZSet, VoteScore*diff, postid)// 更新用户投票记录pipeline.ZRem(key, username)pipeline.ZAdd(key, redis.Z{Score: v,Member: username,})_,err := pipeline.Exec()if err!=nil{return errmsg.ERROR}return errmsg.SUCCES
}

优化

利用Reddit算法计算帖子排名分数

评论模块

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/32173.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

日志系统项目——准备工作了解类的设计模式如单例模式、工厂模式、代理模式

1.六大原则 1.1 单一职责原则 类的职责应该单⼀&#xff0c;⼀个⽅法只做⼀件事。职责划分清晰了&#xff0c;每次改动到最⼩单位的⽅法或 类。 使⽤建议&#xff1a;两个完全不⼀样的功能不应该放⼀个类中&#xff0c;⼀个类中应该是⼀组相关性很⾼的函 数、数据的封装 ⽤例…

股指期货基差怎么计算?公式介绍

先说说啥是基差。简单来说&#xff0c;基差就是股指期货价格和现货指数价格之间的差值。就好比你手里有一张股票指数的“未来提货券”&#xff08;股指期货&#xff09;&#xff0c;但你现在就能买到股票指数&#xff08;现货指数&#xff09;&#xff0c;这两者之间的价格差&a…

Comfyui 与 SDwebui

ComfyUI和SD WebUI是基于Stable Diffusion模型的两种不同用户界面工具&#xff0c;它们在功能、用户体验和适用场景上各有优劣。 1. 功能与灵活性 ComfyUI&#xff1a;ComfyUI以其节点式工作流设计为核心&#xff0c;强调用户自定义和灵活性。用户可以通过连接不同的模块&…

深圳传音控股手机软件开发岗内推

1.负责手机UI、功能开发 2.负责手机具体模块(通信、多媒体、系统、应用)独立开发 3.负责手机软件调试、log分析等 推荐码&#xff1a;EVHPB3 &#xff0c;简历第一时间送到HR面前&#xff5e;

never_give_up

一个很有意思的题&#xff1a; never_give_up - Bugku CTF平台 注意到注释里面有1p.html&#xff0c;我们直接在源代码界面看&#xff0c;这样就不会跳转到它那个链接的&#xff1a; 然后解码可得&#xff1a; ";if(!$_GET[id]) {header(Location: hello.php?id1);exi…

Aliyun CTF 2025 web 复现

文章目录 ezoj打卡OKoffens1veFakejump server ezoj 进来一看是算法题&#xff0c;先做了试试看,gpt写了一个高效代码通过了 通过后没看见啥&#xff0c;根据页面底部提示去/source看到源代码&#xff0c;没啥思路&#xff0c;直接看wp吧&#xff0c;跟算法题没啥关系,关键是去…

BigFoot EventAlertMod lua

BigFoot EventAlertMod lua脚本插件&#xff0c;追踪当前目标的DOT&#xff0c;自身的HOT&#xff0c;持续时间监控 D:\Battle.net\World of Warcraft\_classic_\Interface\AddOns\EventAlertMod 想知道技能的ID&#xff0c;执行命令如下&#xff1a;本例子为“神圣牺牲” /e…

ICLR 2025|DAMO开发者矩阵合作专场

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; AITIME 01 ICLR 2025预讲会合作专场 AITIME 02 专场信息 01 Dynamic Diffusion Transformer 讲者&#xff1a;赵望博&#xff0c;达摩院研究型实习生 时间&#xff1a;3月12日 19:00-19:15 报告简介&#xff1a…

解决jsch远程sftp连接报错:Exception:Algorithm negotiation fail

问题背景 今天遇见了使用JSch连接服务器时&#xff0c;报错Exception:Algorithm negotiation fail的问题&#xff01;研究了半天哇&#xff01;终于解决啦&#xff01;把解决方案在这里给大家共享一下子&#xff01; 问题原因 问题原因在于&#xff0c;JSch所支持的加密算法…

【C++】C++11新特性

目录 列表初始化 左值与右值 左值引用和右值引用 移动构造和移动赋值 类型推导 lambda 捕捉列表 函数对象及绑定 bind函数 包装器 Args参数包 抛异常 列表初始化 在C11中一切皆可用列表初始化。 用法&#xff1a;直接在变量名后面加上初始化列表进行初始化 cl…

FreeBSD下安装npm Node.js的22版本 并简单测试js服务器

FreeBSD下安装Node.js 在FreeBSD下安装Node.js很方便&#xff0c;直接pkg安装即可。 使用pkg install安装npm sudo pkg install npm-node22 Updating FreeBSD repository catalogue... Fetching data.pkg: 100% 7 MiB 2.5MB/s 00:03 Processing entries: 100% FreeB…

云原生可观测性体系:数字世界的神经感知网络

引言&#xff1a;从监控到全景式观测的范式升级 Datadog每日处理百万亿指标&#xff0c;Elastic APM实现万级服务拓扑动态发现。Grafana Loki日志分析延迟降至200ms内&#xff0c;Prometheus单集群支持千万时序存储。Uber通过全链路追踪压缩故障定位时间至秒级&#xff0c;Net…

基于VMware的Ubuntu22.04系统安装和配置以及解决Ubuntu共享文件夹无法实现的问题

一、前期准备 本次安装的虚拟机软件是 VMware Workstation Pro 17 登录跳转到 所有产品 进行下载 ​​​跳转到下载页面​​​ 选择 Windows 产品进行安装 勾选协议同意下载 离线版提供&#xff1a;大家根据自己电脑版本配置进行选择下载 本篇使用的虚拟机版本为 VMware Wor…

线程同步与互斥

目录 资源共享问题 &#xff08;一&#xff09;临界资源与临界区 &#xff08;二&#xff09;多线程并发访问问题 &#xff08;三&#xff09;锁 互斥锁原理 加锁原理 解锁原理 互斥锁相关操作接口 互斥锁封装 死锁 死锁产生的四个必要条件 解决死锁方法 &#xff…

SpringMVC 基本概念与代码示例

1. SpringMVC 简介 SpringMVC 是 Spring 框架中的一个 Web 层框架&#xff0c;基于 MVC&#xff08;Model-View-Controller&#xff09; 设计模式&#xff0c;提供了清晰的分层结构&#xff0c;适用于 Web 应用开发 SpringMVC 主要组件 DispatcherServlet&#xff08;前端控…

Banana Pi OpenWRT One Wifi6 OpenWrt社区官方开源路由器评测

第一款不可破解、开源、版权软件、符合 FCC、CE 和 RoHS 的维修权路由器 OpenWRT项目今年已经20岁了&#xff0c;为了纪念这一时刻&#xff0c;Banana Pi OpenWrt One/AP-24.XY路由器开发系统已经上市。这是OpenWRT团队与硬件公司的第一个联合项目。选择 Banana Pi&#xff0c;…

打造智能钉钉机器人:借助智谱GLM-4-Flash实现高效智能回复(文末附源码)

文章目录 前言一、准备工作&#xff08;一&#xff09;钉钉机器人&#xff08;二&#xff09;智谱 GLM-4-Flash&#xff08;三&#xff09;内网穿透工具 cpolar&#xff08;四&#xff09;需要准备的工具和环境 二、钉钉机器人的创建与配置步骤1&#xff1a;创建钉钉机器人步骤…

react基础语法视图层类组件

react基础语法视图层&类组件 MVVM *区别mvc&mvvm 两者的区别&#xff1a; 数据模型去渲染视图。数据层改了&#xff0c;vue自己会监听到帮我们拿最新的数据去渲染视图&#xff1b;构建数据构建视图&#xff0c;数据驱动的思想。这一套是非常相似的。 视图中的内容改变&…

数据结构--【顺序表与链表】笔记

顺序表 template <class T> class arrList :public List<T> //表示 arrList 类以公有继承的方式继承自 List<T> 类 //公有继承意味着 List<T> 类的公共成员在 arrList 类中仍然是公共成员&#xff0c;受保护成员在 arrList 类中仍然是受保护成员。 { …

Docker容器与宿主机目录映射深度解析

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、Docker容器与宿主机目录映射基础原理 在深入了解如何查询 Docker 容器目录在宿主机的映射目录之前&#xff0c;有必要先明晰其背后的基础原理。Docker 容器通过挂载&#xff08;mount&#xff09;机制将宿主机的…