Use Beego and MySQL to implement the application of posts and comments, and conduct interface testing (with source code and code depth analysis)

Small project introduction

After analyzing the requirements, I added some additional things, such as adding userusers, because I considered that posts or comments (similar to replies) will have authors, and the main functions are adding posts, viewing all or a single post, adding Comments, view all comments on a post, or individual comments. I store the data in the database, not in memory.

First, run the terminal to bee api apinamecreate a new beego project. apiThe command allows us to implement the API application very conveniently.

image.png

Open the generated project in the IDE, and configure app.confthe part about the database.

I choose the database MySQL, app.confthe file is as follows:

appname = post
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true

username = root
password =
host = 127.0.0.1
port = 3306
database = post

The database is named post, so I first Navicatcreate the database in the software, and the coding rules are utf8mb4, to facilitate subsequent operations.

image.png

The final project structure is as follows:

image.png

confThe folder contains the project configuration files, controllersthe folder is responsible for handling the business logic, modelsthe folder is related to the database model, the folder routerscontains all the routes of the project, testsand the folder contains the test files, which I intend to use in this article postmanfor interface testing.

Source code analysis

Next, display and analyze the source code of each file.

main.go

package main

import (
    "fmt"
    "github.com/beego/beego/v2/client/orm"
    "github.com/beego/beego/v2/server/web"
    _ "github.com/go-sql-driver/mysql"
    "github.com/prometheus/common/log"
    _ "post/models"
    _ "post/routers"
)

func init() {
    
    
    username, _ := web.AppConfig.String("username")
    password, _ := web.AppConfig.String("password")
    host, _ := web.AppConfig.String("host")
    port, _ := web.AppConfig.String("port")
    database, _ := web.AppConfig.String("database")

    datasource := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&loc=Local", username, password, host, port, database)
    err := orm.RegisterDataBase("default", "mysql", datasource)
    if err != nil {
    
    
       log.Fatal(err)
    }

    err = orm.RunSyncdb("default", false, true)
    if err != nil {
    
    
       log.Fatal(err)
    }

}

func main() {
    
    
    orm.RunCommand()
    web.Run()
}
  1. Importing packages: The code starts by importing some necessary packages:
  • "fmt" is used for formatted input and output.
  • "github.com/beego/beego/v2/client/orm" is the Beego ORM package.
  • "github.com/beego/beego/v2/server/web" is the Beego web server package.
  • "github.com/go-sql-driver/mysql" is the MySQL database driver.
  • "github.com/prometheus/common/log" for logging.
  • "post/models" and "post/routers" are used to import models and router configuration. (These are only for side effects, as they may register models and routes during initialization.)
  1. init()Function: init()The function is called automatically when the program starts. In this function, the code Beegoreads MySQLthe database's credentials (username, password, host, port, and database name) using configuration and builds a datasource string to connect to the database. datasourcevariable contains the connection string.

The code then uses orm.RegisterDataBase()the function to register the MySQL database with Beego ORM. The parameters of this function are:

  • "default": This is an alias for the database connection that will be referenced by "default" in other parts of the application.
  • "mysql": The driver name of the MySQL database.
  • datasource: The previously constructed data source string.

Next, call orm.RunSyncdb()the function post/modelsto create a database table based on the model definition in the package. The parameters of this function are:

  • "default": The database connection alias used to create the table.
  • false: This parameter indicates whether to drop the existing table before creating the new one. Here it is set to false, which means that the existing table will not be dropped.
  • true: This parameter indicates whether to create the table if it does not exist. Set to true, so tables will be created if they don't exist.
  1. main() function: main()The function is the entry point of the program. It calls two functions:
  • orm.RunCommand(): This function parses the ORM-related command-line arguments and performs the appropriate action. For example, it can generate model files, controllers or database migration scripts based on ORM configuration.
  • web.Run(): This function starts the Beego web server and handles incoming HTTP requests.

The application listens for incoming HTTP requests, on port 8080 by default, and routes them to the appropriate controller actions, which are defined in the routing configuration.

router.go

package routers

import (
    "github.com/beego/beego/v2/server/web"
    "post/controllers"
)

func init() {
    
    
    web.Router("/post", &controllers.PostController{
    
    }, "get:PostList;post:PostAdd")
    web.Router("/comment", &controllers.CommentController{
    
    }, "get:CommentList;post:CommentAdd")

}

This code defines the routing configuration in the Beego framework. Routing configuration is used to map different URL paths to corresponding controllers and handlers.

Let's explain this code:

  1. Import package: Two packages are imported in the code:
  • "github.com/beego/beego/v2/server/web": This is the web server package of the Beego framework, which is used to set up routing and handle HTTP requests.
  • "post/controllers": This is a custom controller package for handling different requests.
  1. init() function: init()The function is called automatically when the package is imported. In this function, we define two routing rules:
  • The first routing rule:

    web.Router("/post", &controllers.PostController{
          
          }, "get:PostList;post:PostAdd")
    
    • web.Router("/post": This means that the path "/post" is mapped to the following controllers and processing functions.
    • &controllers.PostController{}: This is PostControlleran instance of that handles requests related to the "/post" path.
    • "get:PostList;post:PostAdd": This is the routing rule string, which specifies the request method and the corresponding processing function. Here, "get:PostList" means to map a GET request to the method PostControllerof PostList, and "post:PostAdd" means to map a POST request to the method PostControllerof PostAdd.
  • The second routing rule:

    web.Router("/comment", &controllers.CommentController{
          
          }, "get:CommentList;post:CommentAdd")
    
    • web.Router("/comment": This means that the path "/comment" is mapped to the following controllers and processing functions.
    • &controllers.CommentController{}: This is CommentControlleran instance of that handles requests related to the "/comment" path.
    • "get:CommentList;post:CommentAdd": This is the routing rule string, which specifies the request method and the corresponding processing function. "get:CommentList" means to map a GET request to the method of , and "post:CommentAdd" means to map a POST request to CommentControllerthe method of .CommentListCommentControllerCommentAdd

models/user.go

package models

import (
    "github.com/beego/beego/v2/client/orm"
    "time"
)

type User struct {
    
    
    Id         int        `orm:"pk;auto"`
    Name       string     `orm:"description(用户名)"`
    Posts      []*Post    `orm:"reverse(many)"`
    Comments   []*Comment `orm:"reverse(many)"`
    CreateTime time.Time  `orm:"auto_now_add;type(datetime);description(创建时间)"`
}

func init() {
    
    
    // 需要在init中注册定义的model
    orm.RegisterModel(new(User), new(Post), new(Comment))
}

This code defines three data models: User, Post, and Comment, and registers them with the Beego ORM package.

Let's explain this code step by step:

  1. Import package: Two packages are imported in the code:
  • "github.com/beego/beego/v2/client/orm": This is the Beego ORM package for database operations and object-relational mapping.
  • "time": This is the time package in the Go standard library for handling time-related operations.
  1. User structure: A structure named User is defined, representing the user table in the database.
  • Id int orm:"pk;auto": The Id field is the primary key, "pk" means the primary key, and "auto" means auto-increment.
  • Name string orm:"description(用户名)": The Name field is the username field, and "description (username)" is the description of the field.
  • Posts []*Post orm:"reverse(many)": Posts is a one-to-many reverse relationship between users and posts, which means that a user can have multiple posts.
  • Comments []*Comment orm:"reverse(many)": Comments is a one-to-many reverse relationship between users and comments, indicating that a user can have multiple comments.
  • CreateTime time.Time orm:"auto_now_add;type(datetime);description(创建时间)": The CreateTime field is the creation time field, "auto_now_add" means it is automatically set to the current time when creating a new record, "type(datetime)" means the field type is datetime, and "description (creation time)" is the description of the field.
  1. init() function: init()The function is called automatically when the package is imported. Here, the code calls orm.RegisterModel()the function to register the defined model for mapping with the database table at application runtime.
  • orm.RegisterModel(new(User), new(Post), new(Comment)): Register the three models of User, Post, and Comment, so that Beego ORM will know their structure and the mapping relationship with the database table.

models/Post.go

package models

import "time"

type Post struct {
    
    
    Id         int        `orm:"pk;auto"`
    Title      string     `orm:"description(帖子标题)"`
    Content    string     `orm:"size(4000);description(帖子内容)"`
    ReadNum    int        `orm:"description(阅读量);default(0)"`
    Author     *User      `orm:"rel(fk);description(作者)"`
    Comments   []*Comment `orm:"reverse(many);description(评论)"`
    CreateTime time.Time  `orm:"auto_now_add;type(datetime);description(创建时间)"`
}

func PostData(posts []Post) (data []interface{
    
    }) {
    
    

    for _, post := range posts {
    
    
       data = append(data, map[string]interface{
    
    }{
    
    
          "id":          post.Id,
          "title":       post.Title,
          "author":      post.Author.Name,
          "content":     post.Content,
          "read_num":    post.ReadNum,
          "create_time": post.CreateTime.Format("2006-1-2 15:04"),
       })
    }
    return
}

This code defines a Postdata model called and provides a function PostDatato Postconvert the data of the structure into a specific format.

Let's explain this code step by step:

  1. PostStructure: Postis a data model representing the posts table in the database.
  • Id int orm:"pk;auto": IdThe field is the primary key, "pk" means the primary key, and "auto" means self-growth.
  • Title string orm:"description(帖子标题)": Titlefield is the post title field, "description(post title)" is the description of the field.
  • Content string orm:"size(4000);description(帖子内容)": ContentThe field is the post content field, "size(4000)" indicates that the maximum length of the field is 4000, and "description (post content)" is the description of the field.
  • ReadNum int orm:"description(阅读量);default(0)": ReadNumThe field is the reading volume field, "description (reading volume)" is the description of the field, and "default(0)" means the default value is 0.
  • Author *User orm:"rel(fk);description(作者)"The : Authorfield is a Userforeign key association field pointing to the structure, indicating the author of this post.
  • Comments []*Comment orm:"reverse(many);description(评论)"The : Commentsfield is a Commentone-to-many reverse association field pointing to the structure, indicating that this post can have multiple comments.
  • CreateTime time.Time orm:"auto_now_add;type(datetime);description(创建时间)": CreateTimeThe field is the creation time field, "auto_now_add" means that the current time is automatically set when a new record is created, "type(datetime)" means the field type is datetime, and "description (creation time)" is the description of the field.
  1. PostDataFunction: PostDataThe function receives a []Postslice that contains multiple Poststructs. The purpose of the function is to convert the data of these structures into a slice in a specific format []interface{}.

In the function, store postseach Poststruct's field value in a by iterating over the slice map[string]interface{}, and then mapadd that to datathe slice.

The specific converted fields are as follows:

  • "id": the post's Idfield.
  • "title": TitleThe field of the post.
  • "author": The name of the author of the post, post.Author.Nameobtained by visiting .
  • "content": the post's Contentfield.
  • "read_num": the post's ReadNumfield.
  • "create_time": CreateTimeThe field of the post, formatted as a string similar to "2006-1-2 15:04".

Finally, the function returns the transformed dataslice.

models/comment.go

package models

import "time"

type Comment struct {
    
    
    Id         int       `orm:"pk;auto"`
    Content    string    `orm:"size(4000);description(评论内容)"`
    Post       *Post     `orm:"rel(fk);description(帖子外键)"`
    Author     *User     `orm:"rel(fk);description(评论人)"`
    CreateTime time.Time `orm:"auto_now_add;type(datetime);description(创建时间)"`
}

func CommentData(comments []Comment) (data []interface{
    
    }) {
    
    

    for _, comment := range comments {
    
    
       data = append(data, map[string]interface{
    
    }{
    
    
          "id":          comment.Id,
          "author":      comment.Author.Name,
          "content":     comment.Content,
          "create_time": comment.CreateTime.Format("2006-1-2 15:04"),
       })
    }
    return
}

This code defines a Commentdata model called and provides a function CommentDatato Commentconvert the data of the structure into a specific format.

Let's explain this code step by step:

  1. CommentStructure: CommentIt is a data model that represents the comment table in the database.
  • Id int orm:"pk;auto": IdThe field is the primary key, "pk" means the primary key, and "auto" means self-growth.
  • Content string orm:"size(4000);description(评论内容)": ContentThe field is the comment content field, "size(4000)" indicates that the maximum length of the field is 4000, and "description (comment content)" is the description of the field.
  • Post *Post orm:"rel(fk);description(帖子外键)"The : Postfield is a Postforeign key associated field pointing to a struct, indicating which post this comment belongs to.
  • Author *User orm:"rel(fk);description(评论人)"The : Authorfield is a Userforeign key associated field pointing to the structure, indicating the author of the comment.
  • CreateTime time.Time orm:"auto_now_add;type(datetime);description(创建时间)": CreateTimeThe field is the creation time field, "auto_now_add" means that the current time is automatically set when a new record is created, "type(datetime)" means the field type is datetime, and "description (creation time)" is the description of the field.
  1. CommentDataFunction: CommentDataThe function receives a []Commentslice that contains multiple Commentstructs. The purpose of the function is to convert the data of these structures into a slice in a specific format []interface{}.

In the function, store commentseach Commentstruct's field value in a by iterating over the slice map[string]interface{}, and then mapadd that to datathe slice.

The specific converted fields are as follows:

  • "id": the comment's Idfield.
  • "author": The name of the author of the comment, comment.Author.Nameobtained by visiting .
  • "content": the comment's Contentfield.
  • "create_time": CreateTimeThe field of the comment, formatted as a string similar to "2006-1-2 15:04".

Finally, the function returns the transformed dataslice.

controllers/post.go

package controllers

import (
    "github.com/beego/beego/v2/client/orm"
    "github.com/beego/beego/v2/server/web"
    "post/models"
)

type PostController struct {
    
    
    web.Controller
}

func (c *PostController) PostList() {
    
    
    o := orm.NewOrm()

    postId, _ := c.GetInt("post_id", 0)
    if postId == 0 {
    
    
       var posts []models.Post
       if _, err := o.QueryTable(new(models.Post)).RelatedSel().All(&posts); err == nil {
    
    
          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 200, "count": len(posts), "data": models.PostData(posts)}
          c.ServeJSON()
       } else {
    
    
          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "获取帖子列表失败"}
          c.ServeJSON()
       }
    } else {
    
    
       var post models.Post

       qs := o.QueryTable(new(models.Post)).Filter("id", postId)

       if err := qs.RelatedSel().One(&post); err == nil {
    
    
          // 阅读数+1
          qs.Update(orm.Params{
    
    "read_num": post.ReadNum + 1})

          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 200, "data": models.PostData([]models.Post{
    
    post})}
          c.ServeJSON()
       } else {
    
    
          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "获取帖子失败"}
          c.ServeJSON()
       }
    }
}

func (c *PostController) PostAdd() {
    
    
    title := c.GetString("title")
    content := c.GetString("content")
    author := c.GetString("author_id")
    if title == "" || content == "" || author == "" {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "参数错误"}
       c.ServeJSON()
       return
    }

    o := orm.NewOrm()
    user := models.User{
    
    }
    if err := o.QueryTable(new(models.User)).Filter("id", author).One(&user); err != nil {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "用户不存在"}
       c.ServeJSON()
       return
    }

    post := models.Post{
    
    
       Title:   title,
       Content: content,
       Author:  &user,
    }

    if _, err := o.Insert(&post); err == nil {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 200, "msg": "添加帖子成功"}
    } else {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "添加帖子失败"}
    }

    c.ServeJSON()

}

This code defines a PostControllercontroller called and implements two handler functions: PostList()and PostAdd(), to handle post-related requests.

Let's explain this code step by step:

  1. Import packages: Some packages are imported in the code:
  • "github.com/beego/beego/v2/client/orm": This is the Beego ORM package for database operations and object-relational mapping.
  • "github.com/beego/beego/v2/server/web": This is the Beego web server package for handling requests and responses.
  • "post/models": This is a custom modelspackage for manipulating data models.
  1. PostControllerStructure: PostControllerIt is a controller structure, inherited web.Controller.
  2. PostList()Function: PostList()is a processing function used to get a list of posts or details of a single post.
  • First, by orm.NewOrm()creating a new ORM object ofor database operations.
  • Checks for post_ida parameter, if not, returns a list of posts. Query all posts in the database, and use models.PostData()the function to convert the list of posts into data in a specific format.
  • If there is post_ida parameter, it means to get the details of a single post. Query the corresponding post in the database post_id, add 1 to its reading count, and then convert the post details into data in a specific format.
  • Finally, the processing result is returned in JSON format.
  1. PostAdd()Function: PostAdd()is a processing function for adding new posts.
  • Get the request parameters title, contentand author_id, if these parameters are empty, return a parameter error.
  • Create a new ORM object ofor database operations.
  • According to author_idthe user corresponding to the query, if the user does not exist, an error that the user does not exist will be returned.
  • Create a new Poststruct, populated with the post's title, content, and author.
  • Insert the post into the database, if the insertion is successful, return a message that the post was added successfully, otherwise return a message that the post was not added.
  • Finally, the processing result is returned in JSON format.

controllers/comment.go

package controllers

import (
    "github.com/beego/beego/v2/client/orm"
    "github.com/beego/beego/v2/server/web"
    "post/models"
)

type CommentController struct {
    
    
    web.Controller
}

func (c *CommentController) CommentList() {
    
    
    o := orm.NewOrm()

    postId, _ := c.GetInt("post_id", 0)
    commentId, _ := c.GetInt("comment_id", 0)
    if postId == 0 {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "参数错误"}
       c.ServeJSON()
       return
    }
    if commentId == 0 {
    
    
       var comments []models.Comment
       if _, err := o.QueryTable(new(models.Comment)).RelatedSel().Filter("post_id", postId).All(&comments); err == nil {
    
    
          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 200, "count": len(comments), "data": models.CommentData(comments)}
          c.ServeJSON()
       } else {
    
    
          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "获取评论列表失败"}
          c.ServeJSON()
       }
    } else {
    
    
       var comment models.Comment

       if err := o.QueryTable(new(models.Comment)).Filter("id", commentId).RelatedSel().One(&comment); err == nil {
    
    

          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 200, "data": models.CommentData([]models.Comment{
    
    comment})}
          c.ServeJSON()
       } else {
    
    
          c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "获取评论失败"}
          c.ServeJSON()
       }
    }
}

func (c *CommentController) CommentAdd() {
    
    
    postId := c.GetString("post_id")
    content := c.GetString("content")
    author := c.GetString("author_id")
    if postId == "" || content == "" || author == "" {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "参数错误"}
       c.ServeJSON()
       return
    }

    o := orm.NewOrm()
    user := models.User{
    
    }
    post := models.Post{
    
    }
    if err := o.QueryTable(new(models.User)).Filter("id", author).One(&user); err != nil {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "用户不存在"}
       c.ServeJSON()
       return
    }
    if err := o.QueryTable(new(models.Post)).Filter("id", postId).One(&post); err != nil {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "帖子不存在"}
       c.ServeJSON()
       return
    }

    comment := models.Comment{
    
    
       Content: content,
       Post:    &post,
       Author:  &user,
    }

    if _, err := o.Insert(&comment); err == nil {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 200, "msg": "添加评论成功"}
    } else {
    
    
       c.Data["json"] = map[string]interface{
    
    }{
    
    "code": 400, "msg": "添加评论失败"}
    }

    c.ServeJSON()
}

This code defines a CommentControllercontroller called and implements two handler functions: CommentList()and CommentAdd(), to handle requests related to comments.

Let's explain this code step by step:

  1. Import packages: Some packages are imported in the code:
  • "github.com/beego/beego/v2/client/orm": This is the Beego ORM package for database operations and object-relational mapping.
  • "github.com/beego/beego/v2/server/web": This is the Beego web server package for handling requests and responses.
  • "post/models": This is a custom modelspackage for manipulating data models.
  1. CommentControllerStructure: CommentControllerIt is a controller structure, inherited web.Controller.
  2. CommentList()Function: CommentList()It is a processing function used to get the list of comments or the details of a single comment.
  • First, by orm.NewOrm()creating a new ORM object ofor database operations.
  • Checks whether there are post_idparameters and comment_idparameters, post_idand returns a parameter error if the parameter is empty.
  • If comment_idthe parameter is empty, returns a list of comments for the post. post_idQuery all comments of the corresponding post in the database , and use models.CommentData()the function to convert the list of comments into data in a specific format.
  • If comment_idthe parameter is not empty, it means to get the details of a single comment. comment_idQuery the corresponding comment in the database and convert it into data in a specific format.
  • Finally, the processing result is returned in JSON format.
  1. CommentAdd()Function: CommentAdd()is a processing function for adding new comments.
  • Get the request parameters post_id, contentand author_id, if these parameters are empty, return a parameter error.
  • Create a new ORM object ofor database operations.
  • According to author_idthe user corresponding to the query, if the user does not exist, an error that the user does not exist will be returned.
  • According to post_idthe corresponding post query, if the post does not exist, an error that the post does not exist will be returned.
  • Create a new Commentstruct, populated with the comment's content, post, and author.
  • Insert the comment into the database, if the insertion is successful, return a message that the comment was added successfully, otherwise return a message that the comment failed to be added.
  • Finally, the processing result is returned in JSON format.

interface test

Run the beego project, the command is bee run, and then use postmanthe software for interface testing.

image.png

First, I inserted two pieces of user data into the database to facilitate subsequent interface testing:

image.png

test add post

Enter the correct parameters in the form:

image.png

Wrong parameters entered in the form (not submitted content):

image.png

The post interface test was added successfully.

test view post

Without parameters, query all posts:

image.png

Add post_idparameters to query a single post:

image.png

Check out the post interface test was successful.

test add comment

image.png

If user does not exist:

image.png

If the post does not exist:

image.png

If parameter is missing:

image.png

Add comment interface test is successful.

Test View Reviews

View all comments on a post:

image.png

View a comment on a post:

image.png

Do not pass in post_id:

image.png

Pass in non-existent post_iddisplay without comments (you can also modify the code to return a message that the post does not exist):

image.png

Check out the comments interface test was successful.

Guess you like

Origin blog.csdn.net/m0_63230155/article/details/132009576