搭建个人论坛
项目地址:MyForum: go+gin+vue搭建论坛 - Gitee.com
PS:有些地方没有写好,请选择性查看
初始化项目
- 创建目录结构
- 利用ini配置初始化框架
[server]
AppMode = debug
HttpPort = :3000
JwtKey = "dhjasdkajh321"
[database]
Db = mysql
DbHost = localhost
DbPort = 3306
DbUser = root
DbPassWord = flzx3qc
DbName = gForum
package config
import (
"fmt"
"gopkg.in/ini.v1"
)
var (
AppMode string
HttpPort string
JwtKey string
Db string
DbHost string
DbPort string
DbUser string
DbPassWord string
DbName 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("")
}
- 搭建gin框架
package myforum
import "MY-FORUM/routers"
func main() {
routers.InitRouter()
}
package routers
import (
"MY-FORUM/config"
"github.com/gin-gonic/gin"
)
func InitRouter() {
gin.SetMode(config.AppMode)
r := gin.Default()
r.Run(config.HttpPort)
}
注册登录模块
创建数据库
package main
import (
"MY-FORUM/models"
"MY-FORUM/routers"
)
func main() {
// 连接数据库
models.InitDb()
// 配置路由
routers.InitRouter()
}
package models
import (
"MY-FORUM/config"
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var db *gorm.DB
var err error
func 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 models
import "gorm.io/gorm"
type User struct {
gorm.Model
UserId 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"`
}
注册
- 定义注册接收表单结构体,并利用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"`
}
- 进行gorm操作
查询用户名是否已经存在
// 判断username是否已经存在
func FindUserNameExist(username string)bool{
var user User
err := db.Where("user_name = ?", username).First(&user).Error
if err!=nil{
fmt.Println("用户名称查询失败", err)
}
if user.ID > 0{
return true
}else{
return false
}
}
添加用户
// 添加用户
func AddUser(user User)int{
err := db.Create(&user).Error
if err!=nil{
return errmsg.ERROR
}
return errmsg.SUCCES
}
- 进行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.UserName
password := userSignUp.PassWord
if 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,
})
}
- 注册路由
func InitUserRouter(r *gin.Engine){
UserRouter := r.Group("v1/user")
UserRouter.POST("add", controller.SignUp) // 用户注册
}
登录
- 定义接收登录表达信息的结构体
type UserLogin struct{
UserName string `json:"username" validate:"required"`
PassWord string `json:"password" validate:"required"`
AccessToken string
RefreshToken string
}
- 在库中查询密码
// 获取用户密码
func GetPassWord(username string)string{
var user User
err := db.Where("user_name = ?", username).First(&user).Error
if err!=nil{
fmt.Println("密码获取失败", err)
}
return user.PassWord
}
- 获取Token
package jwt
import (
"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", // 签发人
},
}
// 加密并获得完整的编码后的字符串token
aToken, 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签名并获得完整的编码后的字符串token
return
}
- 编写登录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
}
// 获取token
atoken,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 = atoken
userLogin.RefreshToken = rtoken
c.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中是否有Authorization
authHeader := c.GetHeader("Authorization")
if authHeader=="" || !strings.HasPrefix(authHeader,"Bearer "){
c.JSON(http.StatusOK, gin.H{
"status" : errmsg.GetErrMsg(errmsg.ERROR),
"msg" : "用户权限不足",
})
c.Abort()
return
}
// 解析atoken
atoken := 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.UserName
if !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中解析出来username
claims, err := ParseToken(atoken)
// 如果还未过期,则返回开始的atoken
if err==nil{
token = atoken
return
}
// 判断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中是否有Authorization
authHeader := 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
}
// 解析atoken
atoken := 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 encrypt
import (
"MY-FORUM/config"
"encoding/base64"
"fmt"
"golang.org/x/crypto/scrypt"
)
func GenerateSpw(pw string) string {
const KeyLen = 22
const 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 models
import "gorm.io/gorm"
type Community struct {
gorm.Model
CommunityId 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 models
import (
"MY-FORUM/utils/errmsg"
"fmt"
"time"
"gorm.io/gorm"
)
type Community struct {
gorm.Model
CommunityName 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 Community
err := db.Where("community_name = ?", name).First(&comm).Error
if err != nil {
fmt.Println("社区查询失败", err)
}
if comm.ID > 0 {
return true
}
return false
}
// 判断id是否存在
func FindCommunityByID(id int)(bool,string){
var comm Community
err := db.Where("id = ?", id).First(&comm).Error
if err != nil {
fmt.Println("社区查询失败", err)
}
if comm.ID > 0 {
return true, comm.Issure
}
return false,""
}
// 添加社区
func AddCommunity(comm Community) int {
err := db.Create(&comm).Error
if err!=nil{
return errmsg.ERROR
}
return errmsg.SUCCES
}
// 修改社区
func ChangeCommunity(comm Community)int{
mp := map[string]interface{}{}
mp["Community_name"] = comm.CommunityName
mp["introduction"] = comm.Introduction
err := db.Model(&comm).Updates(mp).Error
if err!=nil{
return errmsg.ERROR
}
return errmsg.SUCCES
}
// 删除社区
func DeleteCommunity(id int)int{
var comm Community
err := db.Where("id = ?", id).Delete(&comm).Error
if err!=nil{
return errmsg.ERROR
}
return errmsg.SUCCES
}
// 查询社区列表(分页)
func GetCommunityList(pagesize, pagenum int)([]Community, int){
var commList []Community
err := db.Select("id", "community_name").Limit(pagesize).Offset((pagenum-1)*pagesize).Find(&commList).Error
if err!=nil{
return commList, errmsg.ERROR
}
return commList, errmsg.SUCCES
}
// 查询社区详情
func GetCommunityDetail(id int)(CommunityDetail, int){
var comm CommunityDetail
err := db.Model(&Community{}).Where("id = ?",id).First(&comm).Error
if 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)
// 获取中间件的username
username := 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)
// 获取中间件的username
username := 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
}
// 判断是否为issure
if 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
}
// 判断是否为issure
if 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 models
import (
"MY-FORUM/utils/errmsg"
"gorm.io/gorm"
)
type Post struct {
gorm.Model
Title 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).Error
if err!=nil{
return errmsg.ERROR
}
return errmsg.SUCCES
}
// 分页展示帖子列表
func GetPostList(pagesize, pagenum int)([]Post, int){
var posts []Post
err := db.Limit(pagesize).Offset((pagenum-1)*pagesize).Find(&posts).Error
if err!=nil{
return posts, errmsg.ERROR
}
return posts, errmsg.SUCCES
}
// 获取帖子详情
func GetPostDetail(id int)(Post, int){
var post Post
err := db.Where("id = ?", id).First(&post).Error
if err!=nil{
return post, errmsg.ERROR
}
return post, errmsg.SUCCES
}
// 修改帖子
func ChangePost(post Post)int{
mp := map[string]interface{}{}
mp["title"] = post.Title
mp["content"] = post.Content
mp["community_id"] = post.CommunityId
mp["status"] = post.Status
err := db.Model(&Post{}).Where("id = ?", post.ID).Updates(mp).Error
if err!=nil{
return errmsg.ERROR
}
return errmsg.SUCCES
}
// 删除帖子
func DeletePost(id int)int{
var post Post
err := db.Where("id = ?", id).Delete(&post).Error
if 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 redis
import (
"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
}