帖子板块的设计
通常一个bbs 会分很多板块, 我们这里可以先设计一下 板块的表结构:
create table `community`
(
`id` int(11) not null auto_increment,
-- 板块id
`community_id` int(10) unsigned not null,
-- 板块名称
`community_name` varchar(128) collate utf8mb4_general_ci not null,
-- 板块介绍
`introduction` varchar(256) collate utf8mb4_general_ci not null,
`create_time` timestamp not null default current_timestamp,
`update_time` timestamp not null default current_timestamp on update current_timestamp,
primary key (`id`),
unique key `idx_community_id` (`community_id`),
unique key `idx_community_name` (`community_name`)
) engine = InnoDB
default charset = utf8mb4
collate = utf8mb4_general_ci;
复制代码
改写路由
通常我们的api 上线之后 可能会做一些升级 比如v1 v2 之类的,所以我们可以改一下我们的路由注册
package route
import (
"go_web_app/controllers"
"go_web_app/logger"
"go_web_app/middleware"
"net/http"
"go.uber.org/zap"
"github.com/gin-gonic/gin"
)
func Setup(mode string) *gin.Engine {
if mode == gin.ReleaseMode {
gin.SetMode(mode)
}
r := gin.New()
// 最重要的就是这个日志库
r.Use(logger.GinLogger(), logger.GinRecovery(true))
//v1 版本的路由
v1 := r.Group("/api/v1")
// 注册
v1.POST("/register", controllers.RegisterHandler)
// 登录
v1.POST("/login", controllers.LoginHandler)
v1.GET("/community", controllers.CommunityHandler)
//验证jwt机制
v1.GET("/ping", middleware.JWTAuthMiddleWare(), func(context *gin.Context) {
// 这里post man 模拟的 将token auth-token
zap.L().Debug("ping", zap.String("ping-username", context.GetString("username")))
controllers.ResponseSuccess(context, "pong")
})
r.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "ok")
})
return r
}
复制代码
这样以后接口升级 就不会迷茫了
完成板块list的 接口
这个比较简单了,我们还是按照 正常的mvc思路走
1 定义 我们的community model 2 定义 controller 3 logic 4 dao
type Community struct {
Id int64 `json:"id" db:"community_id"`
Name string `json:"name" db:"community_name"`
Introdution string `json:"Introdution" db:"introdution"`
}
复制代码
// CommunityHandler 板块
func CommunityHandler(c *gin.Context) {
data, err := logic.GetCommunityList()
if err != nil {
zap.L().Error("GetCommunityList error", zap.Error(err))
ResponseError(c, CodeServerBusy)
return
}
ResponseSuccess(c, data)
}
复制代码
func GetCommunityList() (communityList []*models.Community, err error) {
return mysql.GetCommunityList()
}
复制代码
package mysql
import (
"database/sql"
"go_web_app/models"
"go.uber.org/zap"
)
func GetCommunityList() (communityList []*models.Community, err error) {
sqlStr := "select community_id,community_name from community"
err = db.Select(&communityList, sqlStr)
if err != nil {
// 空数据的时候 不算错误 只是没有板块而已
if err == sql.ErrNoRows {
zap.L().Warn("no community ")
err = nil
}
}
return
}
复制代码
然后验验货
板块详情
有了前面的基础,我们再依葫芦画瓢, 完成一个接口,给定一个id 返回对应的 板块详情
类似:
扫描二维码关注公众号,回复:
13471054 查看本文章

先定义路由:
v1.GET("/community/:id", controllers.CommunityDetailHandler)
复制代码
然后 定义controller
这里要注意 要做下 类型转换,取出对应的id 参数值
func CommunityDetailHandler(c *gin.Context) {
communityIdStr := c.Param("id")
communityId, err := strconv.ParseInt(communityIdStr, 10, 64)
// 校验参数是否正确
if err != nil {
zap.L().Error("GetCommunityListDetail error", zap.Error(err))
ResponseError(c, CodeInvalidParam)
return
}
data, err := logic.GetCommunityById(communityId)
if err != nil {
zap.L().Error("GetCommunityListDetail error", zap.Error(err))
ResponseError(c, CodeServerBusy)
return
}
ResponseSuccess(c, data)
}
复制代码
最后完成我们的logic与dao
func GetCommunityById(id int64) (model *models.Community, err error) {
return mysql.GetCommunityById(id)
}
复制代码
func GetCommunityById(id int64) (community *models.Community, err error) {
community = new(models.Community)
sqlStr := "select community_id,community_name,introduction,create_time " +
"from community where community_id=?"
err = db.Get(community, sqlStr, id)
if err != nil {
// 空数据的时候 不算错误 只是没有板块而已
if err == sql.ErrNoRows {
zap.L().Warn("no community ")
err = nil
}
}
return community, err
}
复制代码
帖子
有了板块的概念和用户的概念,我们就可以定义我们的帖子了
drop table if exists `post`;
create table `post`
(
`id` bigint(20) not null auto_increment,
`post_id` bigint(20) not null comment '帖子id',
`title` varchar(128) collate utf8mb4_general_ci not null comment '标题',
`content` varchar(8192) collate utf8mb4_general_ci not null comment '内容',
`author_id` bigint(20) not null comment '作者id',
`community_id` bigint(20) not null default '1' comment '板块id',
`status` tinyint(4) not null default '1' comment '帖子状态',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
primary key (`id`),
-- 唯一索引
unique key `idx_post_id` (`post_id`),
-- 普通索引
key `idx_author_id` (`author_id`),
key `idx_community_id` (`community_id`)
) engine = InnoDB
default charset = utf8mb4
collate = utf8mb4_general_ci;
复制代码
同样的 也要定义帖子的struct:
type Post struct {
Status int32 `json:"status" db:"status"`
CommunityId int64 `json:"community_id" db:"community_id"`
Id int64 `json:"id" db:"post_id"`
AuthorId int64 `json:"author_id" db:"author_id"`
Title string `json:"title" db:"title"`
Content string `json:"content" db:"content"`
CreateTime time.Time `json:"create_time" db:"create_time"`
}
复制代码
内存对齐 优化
上面看到 struct的定义 有个特点,int和int的在一起,string和string的摆放在一起,那么这么做有什么好处呢?
package models
import (
"fmt"
"testing"
"unsafe"
)
type P1 struct {
a int8
b string
c int8
}
type P2 struct {
a int8
c int8
b string
}
func TestPP(t *testing.T) {
p1 := P1{
a: 1,
b: "b",
c: 2,
}
p2 := P2{
a: 1,
c: 2,
b: "b",
}
fmt.Println("p1:", unsafe.Sizeof(p1))
fmt.Println("p2:", unsafe.Sizeof(p2))
}
复制代码
明显看出来 p1的内存大小是比p2要大一些的
在这里暂且就不细说内存对齐的概念,我们只要知道 定义struct的时候 尽量把同一类型的放在一起 即可
发帖逻辑
有了上述的基础 我们要做发帖的逻辑 就很简单了
- 首先做参数验证
- 通过中间件获取发帖人的userId
- 雪花算法生成 帖子id
- 插入数据库
- 发帖成功返回帖子id
首先改写我们的struct 添加 binding tag
type Post struct {
Status int32 `json:"status" db:"status"`
CommunityId int64 `json:"community_id" db:"community_id" binding:"required"`
Id int64 `json:"id" db:"post_id"`
AuthorId int64 `json:"author_id" db:"author_id"`
Title string `json:"title" db:"title" binding:"required" `
Content string `json:"content" db:"content" binding:"required" `
CreateTime time.Time `json:"create_time" db:"create_time"`
}
复制代码
然后 写一下controller 层
func CreatePostHandler(c *gin.Context) {
// 获取参数和参数校验
p := new(models.Post)
// 这里只能校验下 是否是标准的json格式 之类的 比较简单
if err := c.ShouldBindJSON(p); err != nil {
zap.L().Error("CreatePostHandler with invalid param", zap.Error(err))
// 因为有的错误 比如json格式不对的错误 是不属于validator错误的 自然无法翻译,所以这里要做类型判断
errs, ok := err.(validator.ValidationErrors)
if !ok {
ResponseError(c, CodeInvalidParam)
} else {
ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans)))
}
return
}
// 取不到userId 则提示需要登录
authorId, err := getCurrentUserId(c)
if err != nil {
ResponseError(c, CodeNoLogin)
return
}
p.AuthorId = authorId
msg, err := logic.CreatePost(p)
zap.L().Info("CreatePostHandlerSuccess", zap.String("postId", msg))
if err != nil {
ResponseError(c, CodeServerBusy)
return
}
ResponseSuccess(c, msg)
// 创建帖子
// 返回响应
}
复制代码
logic层
// chuan
func CreatePost(post *models.Post) (msg string, err error) {
// 雪花算法 生成帖子id
post.Id = snowflake.GenId()
zap.L().Debug("createPostLogic", zap.Int64("postId", post.Id))
err = mysql.InsertPost(post)
if err != nil {
return "failed", err
}
//发表帖子成功时 要把帖子id 回给 请求方
return strconv.FormatInt(post.Id, 10), nil
}
复制代码
最后就是dao层
func InsertPost(post *models.Post) error {
sqlstr := `insert into post(post_id,title,content,author_id,community_id) values(?,?,?,?,?)`
_, err := db.Exec(sqlstr, post.Id, post.Title, post.Content, post.AuthorId, post.CommunityId)
if err != nil {
zap.L().Error("InsertPost dn error", zap.Error(err))
return err
}
return nil
}
复制代码
最后看下效果: