go个人论坛项目

发布于:2025-03-16 ⋅ 阅读:(16) ⋅ 点赞:(0)

搭建个人论坛

在这里插入图片描述

项目地址: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 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("")
}
  1. 搭建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"`
}

注册

  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 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
}
  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.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,
	})
}
  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 string
	RefreshToken string
}
  1. 在库中查询密码
// 获取用户密码
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
}
  1. 获取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
}
  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
	}
	// 获取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
}

优化

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

评论模块