Github source address

Post plate design

Usually, a BBS will be divided into many plates. Here we can first design the table structure of the plates:

Create table 'community' (' id 'int(11) not null auto_increment, -- community_id' int(10) unsigned not null, -- Community_name varchar(128) collate UTf8MB4_general_ci not null, 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;Copy the code

Rewrite the routing

Usually when our API goes live there will be some updates like V1, V2 and so on, so we can change our route registration

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 R.se (logger.ginlogger (), Logger.ginrecovery (true)) //v1 version of the route v1 := r.group ("/ API /v1") // register v1.POST("/register", Controllers. RegisterHandler)/login/v1. POST ("/login ", controllers. LoginHandler) v1. GET ("/community ", Controllers.Com munityHandler) / / verification mechanism of JWT v1. GET ("/ping ", middleware JWTAuthMiddleWare (), Func (context *gin.Context) {// Here post man is using 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 }Copy the code

In this way, the interface upgrade will not be confused

Complete the interface of the plate list

This is a little bit easier, but let’s go with the normal MVC idea

1 Define our community Model 2 define 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"` }Copy the code
// 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) }Copy the code
func GetCommunityList() (communityList []*models.Community, err error) {
   return mysql.GetCommunityList()

}
Copy the code
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 ! If err == sql.errnorows {zap.l ().warn ("no community ") err = nil}} return}Copy the code

Then inspect the goods

Plate details

With the previous foundation, we can complete an interface according to the gourd gourd, and return the corresponding plate details given an ID

Similar to:

Define the route first:

v1.GET("/community/:id", controllers.CommunityDetailHandler)
Copy the code

And then you define the controller

Note that you need to perform a type conversion to retrieve the value of the corresponding ID parameter

func CommunityDetailHandler(c *gin.Context) { communityIdStr := c.Param("id") communityId, Err := strconv.ParseInt(communityIdStr, 10, 64) // Check 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) }Copy the code

Finally, complete our logic and DAO

func GetCommunityById(id int64) (model *models.Community, err error) {
   return mysql.GetCommunityById(id)

}
Copy the code
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 ! If err == sql.errnorows {zap.l ().warn ("no community ") err = nil}} return community, if err == sql.errnorows {zap.l ().warn ("no community ") err = nil err }Copy the code

post

With the concept of sections and the concept of users, we can define our posts

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 'content ', ` author_id ` bigint (20) not null comment 'the author id, ` community_id ` bigint (20) not null default' 1 'comment' plate id ', 'status' tinyint(4) not null default '1' comment' status ', 'create_time' TIMESTAMP not null default current_TIMESTAMP comment 'create_time ', 'update_time' TIMESTAMP not null default current_timestamp on UPDATE current_timestamp comment 'update_time ', Primary key (' id '); primary key (' id '); key (' id '); key `idx_community_id` (`community_id`) ) engine = InnoDB default charset = utf8mb4 collate = utf8mb4_general_ci;Copy the code

Struct of a post is also defined:

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"` }Copy the code

Memory alignment optimization

Struct (int, int, string, string) struct (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))

}
Copy the code

It is obvious that p1 has a larger memory size than P2

We won’t go into the concept of memory alignment for the moment, but we’ll just define structs as close together as possible

Posting logic

With the above foundation, the logic of Posting is very simple

  1. Do parameter verification first
  2. Get the userId of the poster through the middleware
  3. Snowflake algorithm generates post ID
  4. Inserting a database
  5. Post successful returns the post ID

First rewrite our struct and add a 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"`
}
Copy the code

And then let’s write the Controller layer

Func CreatePostHandler(c *gin.Context) {p := new(models.post) if err := c.ShouldBindJSON(p); err ! = nil { zap.L().Error("CreatePostHandler with invalid param", Zap. Error(err)) // The json format Error is not a validator Error and cannot be translated. So here to do type judgment is incremented, ok: = err. (the validator. ValidationErrors) if! ok { ResponseError(c, CodeInvalidParam) } else { ResponseErrorWithMsg(c, CodeInvalidParam, Errs.translate (trans)))} Return} authorId := 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 ! Return ResponseError(c, CodeServerBusy) return ResponseSuccess(c, MSG)}Copy the code

Logic layer

// chuan func CreatePost(post *models.Post) (msg string, Error) {post.id = snowflake.GenId() zap.l ().debug ("createPostLogic", zap.int64 ("postId", post.Id)) err = mysql.InsertPost(post) if err ! Return strconv.FormatInt(post.id, 10), nil} {return "failed", err}Copy the code

Finally, there is the DAO layer

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 }Copy the code

Finally, look at the effect: