Article directory
Small project introduction
After analyzing the requirements, I added some additional things, such as adding user
users, 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 apiname
create a new beego project. api
The command allows us to implement the API application very conveniently.
Open the generated project in the IDE, and configure app.conf
the part about the database.
I choose the database MySQL
, app.conf
the 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 Navicat
create the database in the software, and the coding rules are utf8mb4
, to facilitate subsequent operations.
The final project structure is as follows:
conf
The folder contains the project configuration files, controllers
the folder is responsible for handling the business logic, models
the folder is related to the database model, the folder routers
contains all the routes of the project, tests
and the folder contains the test files, which I intend to use in this article postman
for 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()
}
- 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.)
init()
Function:init()
The function is called automatically when the program starts. In this function, the codeBeego
readsMySQL
the database's credentials (username, password, host, port, and database name) using configuration and builds a datasource string to connect to the database.datasource
variable 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/models
to 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 tofalse
, 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 totrue
, so tables will be created if they don't exist.
- 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:
- 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.
- 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 isPostController
an 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 methodPostController
ofPostList
, and "post:PostAdd" means to map a POST request to the methodPostController
ofPostAdd
.
-
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 isCommentController
an 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 toCommentController
the method of .CommentList
CommentController
CommentAdd
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:
- 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.
- 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.
- init() function:
init()
The function is called automatically when the package is imported. Here, the code callsorm.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 Post
data model called and provides a function PostData
to Post
convert the data of the structure into a specific format.
Let's explain this code step by step:
Post
Structure:Post
is a data model representing the posts table in the database.
Id int orm:"pk;auto"
:Id
The field is the primary key, "pk" means the primary key, and "auto" means self-growth.Title string orm:"description(帖子标题)"
:Title
field is the post title field, "description(post title)" is the description of the field.Content string orm:"size(4000);description(帖子内容)"
:Content
The 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)"
:ReadNum
The 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 :Author
field is aUser
foreign key association field pointing to the structure, indicating the author of this post.Comments []*Comment orm:"reverse(many);description(评论)"
The :Comments
field is aComment
one-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(创建时间)"
:CreateTime
The 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.
PostData
Function:PostData
The function receives a[]Post
slice that contains multiplePost
structs. 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 posts
each Post
struct's field value in a by iterating over the slice map[string]interface{}
, and then map
add that to data
the slice.
The specific converted fields are as follows:
- "id": the post's
Id
field. - "title":
Title
The field of the post. - "author": The name of the author of the post,
post.Author.Name
obtained by visiting . - "content": the post's
Content
field. - "read_num": the post's
ReadNum
field. - "create_time":
CreateTime
The field of the post, formatted as a string similar to "2006-1-2 15:04".
Finally, the function returns the transformed data
slice.
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 Comment
data model called and provides a function CommentData
to Comment
convert the data of the structure into a specific format.
Let's explain this code step by step:
Comment
Structure:Comment
It is a data model that represents the comment table in the database.
Id int orm:"pk;auto"
:Id
The field is the primary key, "pk" means the primary key, and "auto" means self-growth.Content string orm:"size(4000);description(评论内容)"
:Content
The 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 :Post
field is aPost
foreign key associated field pointing to a struct, indicating which post this comment belongs to.Author *User orm:"rel(fk);description(评论人)"
The :Author
field is aUser
foreign key associated field pointing to the structure, indicating the author of the comment.CreateTime time.Time orm:"auto_now_add;type(datetime);description(创建时间)"
:CreateTime
The 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.
CommentData
Function:CommentData
The function receives a[]Comment
slice that contains multipleComment
structs. 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 comments
each Comment
struct's field value in a by iterating over the slice map[string]interface{}
, and then map
add that to data
the slice.
The specific converted fields are as follows:
- "id": the comment's
Id
field. - "author": The name of the author of the comment,
comment.Author.Name
obtained by visiting . - "content": the comment's
Content
field. - "create_time":
CreateTime
The field of the comment, formatted as a string similar to "2006-1-2 15:04".
Finally, the function returns the transformed data
slice.
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 PostController
controller called and implements two handler functions: PostList()
and PostAdd()
, to handle post-related requests.
Let's explain this code step by step:
- 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
models
package for manipulating data models.
PostController
Structure:PostController
It is a controller structure, inheritedweb.Controller
.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 objecto
for database operations. - Checks for
post_id
a parameter, if not, returns a list of posts. Query all posts in the database, and usemodels.PostData()
the function to convert the list of posts into data in a specific format. - If there is
post_id
a parameter, it means to get the details of a single post. Query the corresponding post in the databasepost_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.
PostAdd()
Function:PostAdd()
is a processing function for adding new posts.
- Get the request parameters
title
,content
andauthor_id
, if these parameters are empty, return a parameter error. - Create a new ORM object
o
for database operations. - According to
author_id
the 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
Post
struct, 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 CommentController
controller called and implements two handler functions: CommentList()
and CommentAdd()
, to handle requests related to comments.
Let's explain this code step by step:
- 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
models
package for manipulating data models.
CommentController
Structure:CommentController
It is a controller structure, inheritedweb.Controller
.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 objecto
for database operations. - Checks whether there are
post_id
parameters andcomment_id
parameters,post_id
and returns a parameter error if the parameter is empty. - If
comment_id
the parameter is empty, returns a list of comments for the post.post_id
Query all comments of the corresponding post in the database , and usemodels.CommentData()
the function to convert the list of comments into data in a specific format. - If
comment_id
the parameter is not empty, it means to get the details of a single comment.comment_id
Query the corresponding comment in the database and convert it into data in a specific format. - Finally, the processing result is returned in JSON format.
CommentAdd()
Function:CommentAdd()
is a processing function for adding new comments.
- Get the request parameters
post_id
,content
andauthor_id
, if these parameters are empty, return a parameter error. - Create a new ORM object
o
for database operations. - According to
author_id
the 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_id
the corresponding post query, if the post does not exist, an error that the post does not exist will be returned. - Create a new
Comment
struct, 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 postman
the software for interface testing.
First, I inserted two pieces of user data into the database to facilitate subsequent interface testing:
test add post
Enter the correct parameters in the form:
Wrong parameters entered in the form (not submitted content
):
The post interface test was added successfully.
test view post
Without parameters, query all posts:
Add post_id
parameters to query a single post:
Check out the post interface test was successful.
test add comment
If user does not exist:
If the post does not exist:
If parameter is missing:
Add comment interface test is successful.
Test View Reviews
View all comments on a post:
View a comment on a post:
Do not pass in post_id
:
Pass in non-existent post_id
display without comments (you can also modify the code to return a message that the post does not exist):
Check out the comments interface test was successful.