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
- Do parameter verification first
- Get the userId of the poster through the middleware
- Snowflake algorithm generates post ID
- Inserting a database
- 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: