package attention
import "fmt"
func AppendInt() {
intArray := [3]int64{1, 2, 3}
func(arr [3]int64) {
arr[2] = 4
fmt.Println("inner func array:",arr)
}(intArray)
fmt.Println("outer func array:",intArray)
}
这段代码定义了一个名为 AppendInt 的函数,其目的是演示对数组的操作和传递。
首先,在 package attention 中导入了 "fmt" 包,以便在代码中使用 fmt.Println 函数。
然后,定义了 AppendInt 函数,该函数没有参数和返回值。
在函数体内部,声明了一个长度为 3 的整型数组 intArray,并初始化为 [1, 2, 3]。
接下来,定义了一个匿名函数,并立即调用该函数。这个匿名函数接受一个数组作为参数,并在函数体内部对数组进行操作。
在匿名函数中,将传入的数组的索引为 2 的元素修改为 4,然后使用 fmt.Println 函数打印修改后的数组。
在匿名函数执行完毕后,控制流回到 AppendInt 函数,使用 fmt.Println 函数打印未被修改的 intArray 数组。
最终的输出结果是:
inner func array: [1 2 4]
outer func array: [1 2 3]
可以看到,在匿名函数中修改的数组只在匿名函数内部有效,不会影响到外部的 intArray 数组。这是因为在 Go 语言中,数组作为参数传递给函数时是按值传递的,即传递的是数组的副本而不是原始数组本身。因此,在匿名函数中对传入的数组进行修改只会影响到副本,而不会修改原始数组。
package attention
import "testing"
func TestAppendInt(t *testing.T) {
AppendInt()
}
这段代码是一个测试代码,用于测试之前提到的 AppendInt 函数。
首先,在 package attention 中导入了 "testing" 包,以便在代码中使用测试相关的函数和结构。
然后,定义了一个名为 TestAppendInt 的测试函数,它接受一个 *testing.T 类型的参数 t。
在函数体内部,调用了之前提到的 AppendInt 函数。
测试函数的目的是确保 AppendInt 函数能够正常运行而不发生任何错误。如果 AppendInt 函数内部存在错误,测试函数会在运行过程中引发错误,并将错误信息记录在 *testing.T 类型的参数 t 中。
通过这样的测试函数,可以在执行测试时查看输出结果和错误信息,并确保代码的正确性。
要运行这个测试函数,你可以使用测试框架(如 Go 自带的 go test 命令)来运行测试。
package attention
import (
"time"
)
func closure() {
for i := 0; i < 3; i++ {
go func() {
println(i)
}()
}
time.Sleep(3 * time.Second)
}
func closure1() {
for i := 0; i < 3; i++ {
go func(j int) {
println(j)
}(i)
}
time.Sleep(3 * time.Second)
}
这段代码展示了两个函数 closure 和 closure1,它们演示了闭包在并发编程中的一些特性。
首先,在代码中导入了 "time" 包,以便在程序中使用时间相关的功能。
closure 函数定义了一个循环,从 0 到 2 迭代一个变量 i。在每次迭代中,它创建了一个匿名函数并立即启动一个新的 goroutine 来执行该函数。匿名函数打印变量 i 的值。由于匿名函数是在 goroutine 中执行的,它们可以并发地执行。
然而,由于闭包中的变量 i 是通过引用捕获的,而不是通过值传递的,因此在 goroutine 执行时,循环可能已经继续执行,导致 i 的值已经被修改。因此,所有的 goroutine 最终都会打印出 3,而不是预期的 0、1、2。
为了解决这个问题,closure1 函数使用了一个不同的方法。在每次迭代中,它创建了一个匿名函数,并将循环变量 i 作为参数传递给匿名函数。通过将 i 的值传递给匿名函数的参数 j,确保每个 goroutine 在执行时使用的是不同的变量值。这样,每个 goroutine 打印的值就是预期的 0、1、2。
最后,通过调用 time.Sleep(3 * time.Second) 函数,主函数会等待 3 秒钟,以确保所有 goroutine 完成执行。这样可以确保在程序退出之前所有的 goroutine 都有足够的时间打印输出。
package attention
import (
"bytes"
"encoding/json"
"fmt"
)
func NumUnmarshal(){
jsonStr:=`{"id":1,"name":"Jerry"}`
var res map[string]interface{}
_ = json.Unmarshal([]byte(jsonStr), &res)
fmt.Printf("%T\n",res["id"])
i := res["id"].(int64)
fmt.Println(i)
}
func NumDecode(){
jsonStr:=`{"id":1,"name":"Jerry"}`
var res map[string]interface{}
decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr)))
decoder.UseNumber()
_ = decoder.Decode(&res)
i,_ := res["id"].(json.Number).Int64()
fmt.Println(i)
}
以下是对代码的逐行解释:
import (
"bytes"
"encoding/json"
"fmt"
)
这段代码导入了三个包:bytes、encoding/json 和 fmt。bytes 包提供了操作字节切片的函数,encoding/json 包用于 JSON 的编码和解码,fmt 包用于格式化和打印输出。
func NumUnmarshal(){
jsonStr:=`{"id":1,"name":"Jerry"}`
var res map[string]interface{}
_ = json.Unmarshal([]byte(jsonStr), &res)
fmt.Printf("%T\n",res["id"])
i := res["id"].(int64)
fmt.Println(i)
}
这段代码定义了一个名为 NumUnmarshal 的函数。在函数内部,定义了一个 JSON 字符串 jsonStr,表示一个包含 id 和 name 字段的 JSON 对象。
接下来,声明了一个变量 res,它是一个空的 map[string]interface{} 类型,用于存储 JSON 解码后的结果。
然后,使用 json.Unmarshal 函数将 JSON 字符串解码为 Go 的数据结构,并将解码结果存储到 res 变量中。
接着,使用 fmt.Printf 打印出 res["id"] 的类型。%T 是一个格式化占位符,用于输出值的类型信息。
然后,将 res["id"] 断言为 int64 类型,并将结果存储到变量 i 中。
最后,使用 fmt.Println 打印出变量 i 的值。
func NumDecode(){
jsonStr:=`{"id":1,"name":"Jerry"}`
var res map[string]interface{}
decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr)))
decoder.UseNumber()
_ = decoder.Decode(&res)
i,_ := res["id"].(json.Number).Int64()
fmt.Println(i)
}
这段代码定义了一个名为 NumDecode 的函数,它与 NumUnmarshal 函数的目的相同,即将 JSON 字符串解码为 Go 的数据结构。
在函数内部,首先定义了一个 JSON 字符串 jsonStr 和一个空的 map[string]interface{} 类型的变量 res,与之前的函数相似。
然后,使用 json.NewDecoder 函数创建一个 JSON 解码器,并将 JSON 字符串的字节切片传递给解码器。
接着,通过调用 decoder.UseNumber(),设置解码器的选项,以便在解码过程中将数字解码为 json.Number 类型。
然后,使用 decoder.Decode 函数将 JSON 解码为 Go 的数据结构,并将结果存储到 res 变量中。
接下来,通过断言将 res["id"] 转换为 json.Number 类型,并使用 Int64() 方法将其转换为 int64 类型,并将结果存储到变量 i 中。
最后,使用 fmt.Println 打印出变量 i 的值。
package attention
import (
"unicode/utf8"
)
func length(){
str:="⬇汉字"
println(len(str))
}
func length1(){
str:="⬇汉字"
println(utf8.RuneCountInString(str))
}
以下是对代码的逐行解释:
import (
"unicode/utf8"
)
这段代码导入了 "unicode/utf8" 包,用于处理 Unicode 字符串的相关函数。
func length(){
str:="⬇汉字"
println(len(str))
}
这段代码定义了一个名为 length 的函数。在函数内部,定义了一个字符串变量 str,其值为 "⬇汉字",包含了一个箭头符号和两个汉字。
然后,使用 len() 函数打印出字符串 str 的字节长度。由于 Go 语言中的字符串是基于字节的,len() 函数返回的是字符串的字节长度。
func length1(){
str:="⬇汉字"
println(utf8.RuneCountInString(str))
}
这段代码定义了一个名为 length1 的函数,与之前的函数目的相同。
在函数内部,同样定义了一个字符串变量 str,其值为 "⬇汉字"。
然后,使用 utf8.RuneCountInString() 函数计算字符串 str 中 Unicode 字符的数量,并打印出结果。
由于 Unicode 字符可能由多个字节表示,因此直接使用 len() 函数计算的是字符串的字节长度,而不是字符长度。使用 utf8.RuneCountInString() 函数可以正确计算字符串中 Unicode 字符的数量。
通过这两个函数,我们可以得到字符串的字节长度和字符长度,从而更准确地处理包含 Unicode 字符的字符串。
package concurrence
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
以下是对代码的逐行解释:
func CalSquare() {
这段代码定义了一个名为 CalSquare 的函数,用于计算数字的平方。
src := make(chan int)
dest := make(chan int, 3)
这两行代码声明了两个通道变量 src 和 dest。src 是一个无缓冲通道,用于将数字发送给计算平方的 goroutine。dest 是一个带有缓冲区大小为 3 的通道,用于接收计算得到的平方值。
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
这段代码启动一个匿名的 goroutine,用于向 src 通道发送数字。通过循环,将数字 0 到 9 发送到 src 通道中。使用 defer 关键字在 goroutine 结束时关闭 src 通道。
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
这段代码启动另一个匿名的 goroutine,用于从 src 通道接收数字并计算平方后发送到 dest 通道。通过 range src 循环,不断从 src 通道接收数字,并将其平方值发送到 dest 通道中。使用 defer 关键字在 goroutine 结束时关闭 dest 通道。
for i := range dest {
println(i)
}
这段代码使用 range dest 循环从 dest 通道接收计算得到的平方值,并将其打印出来。循环将一直进行,直到 dest 通道关闭,即所有平方值都被接收并处理。
整个流程可以总结为以下几个步骤:
- 启动一个 goroutine,向
src通道发送数字。 - 启动另一个 goroutine,从
src通道接收数字并计算平方后发送到dest通道。 - 在主 goroutine 中,循环从
dest通道接收计算得到的平方值并打印出来。 - 当所有平方值都被接收后,
dest通道关闭,循环结束。
package concurrence
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func ManyGo() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
以下是对代码的逐行解释:
import (
"fmt"
"sync"
)
这段代码导入了 "fmt" 和 "sync" 包。"fmt" 包提供了格式化和打印输出的函数,"sync" 包提供了同步操作的函数和类型。
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
这段代码定义了一个名为 hello 的函数,用于打印 "hello world" 加上一个整数参数的字符串。
func ManyGo() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
这段代码定义了一个名为 ManyGo 的函数。在函数内部,首先声明了一个 sync.WaitGroup 类型的变量 wg,用于等待并同步多个 goroutine。
然后,使用 for 循环创建了 5 个 goroutine。在每次循环中,调用 wg.Add(1) 将 wg 的计数器加一,表示要等待一个额外的 goroutine。
接着,启动了一个匿名的 goroutine。这个 goroutine 接受一个参数 j,并在结束时调用 wg.Done() 来通知 wg 计数器减一。
在 goroutine 的函数体内,调用了 hello(j) 函数,将参数 j 传递给 hello 函数。
最后,通过调用 wg.Wait() 阻塞主 goroutine,直到所有的 goroutine 都执行完毕。这样可以确保在 wg 计数器变为零之前,主 goroutine 不会继续执行。
整个流程可以总结为以下几个步骤:
- 声明一个
sync.WaitGroup类型的变量wg,用于等待和同步多个 goroutine。 - 使用循环创建多个 goroutine。
- 每次循环中,调用
wg.Add(1)将wg的计数器加一,表示要等待一个额外的 goroutine。 - 启动一个匿名的 goroutine,接受一个参数
j,在结束时调用wg.Done()。 - 在 goroutine 的函数体内,调用其他函数并传递参数。
- 调用
wg.Wait()阻塞主 goroutine,直到所有的 goroutine 执行完毕。
package handler
import (
"strconv"
"github.com/Moonlight-Zhao/go-project-example/service"
)
type PageData struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func QueryPageInfo(topicIdStr string) *PageData {
//参数转换
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
//获取service层结果
pageInfo, err := service.QueryPageInfo(topicId)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: pageInfo,
}
}
以下是对代码的逐行解释:
import (
"strconv"
"github.com/Moonlight-Zhao/go-project-example/service"
)
这段代码导入了 "strconv" 包和一个自定义的包 "github.com/Moonlight-Zhao/go-project-example/service"。"strconv" 包提供了字符串和基本类型之间的转换函数。
type PageData struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
这段代码定义了一个结构体类型 PageData,用于表示页面数据。它包含了三个字段:Code、Msg 和 Data。这些字段的类型分别为 int64、string 和 interface{}。结构体的字段标签 json:"code"、json:"msg" 和 json:"data" 指定了在 JSON 序列化和反序列化时对应的字段名称。
func QueryPageInfo(topicIdStr string) *PageData {
//参数转换
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
//获取service层结果
pageInfo, err := service.QueryPageInfo(topicId)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: pageInfo,
}
}
这段代码定义了一个名为 QueryPageInfo 的函数,用于查询页面信息。
函数接受一个 topicIdStr 参数,表示主题ID的字符串形式。首先,通过调用 strconv.ParseInt 函数将 topicIdStr 转换为 int64 类型的 topicId。如果转换过程中发生错误,函数会返回一个带有错误信息的 PageData 结构体指针,其中 Code 被设置为 -1,Msg 被设置为错误的字符串描述。
接着,调用 service.QueryPageInfo 函数获取服务层的结果 pageInfo。如果获取结果过程中发生错误,函数会返回一个带有错误信息的 PageData 结构体指针,同样设置 Code 为 -1,Msg 为错误的字符串描述。
最后,如果查询和获取结果都成功,函数会返回一个 PageData 结构体指针,其中 Code 被设置为 0,Msg 被设置为 "success",Data 被设置为 pageInfo。
通过这个函数,可以将主题ID字符串转换为整数并调用服务层获取页面信息,然后根据查询和获取结果的情况返回相应的 PageData 结构体指针。
package handler
import (
"github.com/Moonlight-Zhao/go-project-example/service"
"strconv"
)
func PublishPost(uidStr, topicIdStr, content string) *PageData {
//参数转换
uid, _ := strconv.ParseInt(uidStr, 10, 64)
topic, _ := strconv.ParseInt(topicIdStr, 10, 64)
//获取service层结果
postId, err := service.PublishPost(topic, uid, content)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: map[string]int64{
"post_id": postId,
},
}
}
以下是对代码的逐行解释:
import (
"github.com/Moonlight-Zhao/go-project-example/service"
"strconv"
)
这段代码导入了 "github.com/Moonlight-Zhao/go-project-example/service" 包和 "strconv" 包。前者是一个自定义的包,后者提供了字符串和基本类型之间的转换函数。
func PublishPost(uidStr, topicIdStr, content string) *PageData {
//参数转换
uid, _ := strconv.ParseInt(uidStr, 10, 64)
topic, _ := strconv.ParseInt(topicIdStr, 10, 64)
//获取service层结果
postId, err := service.PublishPost(topic, uid, content)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: map[string]int64{
"post_id": postId,
},
}
}
这段代码定义了一个名为 PublishPost 的函数,用于发布帖子。
函数接受三个参数:uidStr、topicIdStr 和 content,分别表示用户ID的字符串形式、主题ID的字符串形式和帖子内容。
首先,通过调用 strconv.ParseInt 函数将 uidStr 和 topicIdStr 转换为 int64 类型的变量 uid 和 topic。由于这里使用了 _ 来忽略返回的错误值,所以在转换过程中的错误将被忽略。
接着,调用 service.PublishPost 函数来发布帖子,并将结果赋值给变量 postId。如果发布过程中发生错误,函数会返回一个带有错误信息的 PageData 结构体指针,其中 Code 被设置为 -1,Msg 被设置为错误的字符串描述。
最后,如果发布成功,函数会返回一个 PageData 结构体指针,其中 Code 被设置为 0,Msg 被设置为 "success",Data 被设置为一个包含帖子ID的 map[string]int64 类型。
通过这个函数,可以将用户ID和主题ID字符串转换为整数,并调用服务层发布帖子。根据发布结果的情况返回相应的 PageData 结构体指针。
package repository
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var db *gorm.DB
func Init() error {
var err error
dsn := "root:00000000@tcp(127.0.0.1:3306)/community?charset=utf8mb4&parseTime=True&loc=Local"
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
return err
}
以下是对代码的逐行解释:
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
这段代码导入了 "gorm.io/driver/mysql" 和 "gorm.io/gorm" 包。"gorm.io/driver/mysql" 包提供了与 MySQL 数据库的连接和操作相关的功能,"gorm.io/gorm" 包是 GORM ORM 库的主要包,用于进行数据库操作。
var db *gorm.DB
这段代码声明了一个全局变量 db,其类型为 *gorm.DB。gorm.DB 是 GORM 库提供的数据库连接对象。
func Init() error {
var err error
dsn := "root:00000000@tcp(127.0.0.1:3306)/community?charset=utf8mb4&parseTime=True&loc=Local"
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
return err
}
这段代码定义了一个名为 Init 的函数,用于初始化数据库连接。
函数内部首先声明了一个 err 变量,用于存储可能发生的错误。
然后,定义了一个 dsn 变量,其中包含了连接数据库所需的数据源名称。这个数据源名称指定了数据库的地址、用户名、密码、数据库名以及其他连接选项,比如字符集和解析时间等。
接着,调用 gorm.Open 函数来打开数据库连接。这个函数接受两个参数,第一个参数是数据库驱动名,这里使用的是 "mysql" 驱动;第二个参数是一个 gorm.Config 类型的指针,用于配置数据库连接的一些选项。
gorm.Open 函数返回一个 *gorm.DB 类型的数据库连接对象,将其赋值给全局变量 db。
最后,将可能发生的错误返回给调用者。
通过调用这个 Init 函数,可以初始化全局的数据库连接 db,使其可以用于后续的数据库操作。
package repository
import (
"github.com/Moonlight-Zhao/go-project-example/util"
"gorm.io/gorm"
"sync"
"time"
)
type Post struct {
Id int64 `gorm:"column:id"`
ParentId int64 `gorm:"parent_id"`
UserId int64 `gorm:"column:user_id"`
Content string `gorm:"column:content"`
DiggCount int32 `gorm:"column:digg_count"`
CreateTime time.Time `gorm:"column:create_time"`
}
func (Post) TableName() string {
return "post"
}
type PostDao struct {
}
var postDao *PostDao
var postOnce sync.Once
func NewPostDaoInstance() *PostDao {
postOnce.Do(
func() {
postDao = &PostDao{}
})
return postDao
}
func (*PostDao) QueryPostById(id int64) (*Post, error) {
var post Post
err := db.Where("id = ?", id).Find(&post).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
if err != nil {
util.Logger.Error("find post by id err:" + err.Error())
return nil, err
}
return &post, nil
}
func (*PostDao) QueryPostByParentId(parentId int64) ([]*Post, error) {
var posts []*Post
err := db.Where("parent_id = ?", parentId).Find(&posts).Error
if err != nil {
util.Logger.Error("find posts by parent_id err:" + err.Error())
return nil, err
}
return posts, nil
}
func (*PostDao) CreatePost(post *Post) error {
if err := db.Create(post).Error; err != nil {
util.Logger.Error("insert post err:" + err.Error())
return err
}
return nil
}
以下是对代码的逐行解释:
import (
"github.com/Moonlight-Zhao/go-project-example/util"
"gorm.io/gorm"
"sync"
"time"
)
这段代码导入了 "github.com/Moonlight-Zhao/go-project-example/util" 包,"gorm.io/gorm" 包,"sync" 包和 "time" 包。前者是一个自定义的包,后者提供了与时间相关的功能,"sync" 包提供了并发相关的功能。
type Post struct {
Id int64 `gorm:"column:id"`
ParentId int64 `gorm:"parent_id"`
UserId int64 `gorm:"column:user_id"`
Content string `gorm:"column:content"`
DiggCount int32 `gorm:"column:digg_count"`
CreateTime time.Time `gorm:"column:create_time"`
}
这段代码定义了一个名为 Post 的结构体。该结构体表示一个帖子,包含了帖子的各个属性。使用了 gorm 的 column tag 来指定结构体字段与数据库表列之间的映射关系。
func (Post) TableName() string {
return "post"
}
这是 Post 结构体的一个方法,用于返回数据库表名。在这里,它返回了字符串 "post",表示该结构体对应的数据库表名为 "post"。
type PostDao struct {
}
这段代码定义了一个名为 PostDao 的结构体。该结构体表示帖子的数据访问对象(DAO),用于封装对帖子数据的访问操作。
var postDao *PostDao
var postOnce sync.Once
func NewPostDaoInstance() *PostDao {
postOnce.Do(
func() {
postDao = &PostDao{}
})
return postDao
}
这段代码定义了一个名为 NewPostDaoInstance 的函数,用于获取 PostDao 的单例实例。
在函数内部,使用了 sync.Once 类型的 postOnce 变量来保证只执行一次实例化操作。在第一次调用函数时,使用匿名函数初始化 postDao 变量,创建一个 PostDao 结构体的实例,并将其赋值给 postDao 变量。
最后,返回 postDao 变量作为 PostDao 的单例实例。
func (*PostDao) QueryPostByParentId(parentId int64) ([]*Post, error) {
var posts []*Post
err := db.Where("parent_id = ?", parentId).Find(&posts).Error
if err != nil {
util.Logger.Error("find posts by parent_id err:" + err.Error())
return nil, err
}
return posts, nil
}
这是 PostDao 结构体的另一个方法,用于根据父帖子ID查询相关的子帖子信息。
方法接受一个 parentId 参数,表示要查询的父帖子ID。在方法内部,使用 db 对象执行数据库查询操作,根据 parentId 查询相关的子帖子信息,并将结果存储在 posts 切片中。
如果查询过程中发生错误,会将错误信息记录到日志并返回错误。否则,返回查询到的子帖子切片。
func (*PostDao) CreatePost(post *Post) error {
if err := db.Create(post).Error; err != nil {
util.Logger.Error("insert post err:" + err.Error())
return err
}
return nil
}
这是 PostDao 结构体的另一个方法,用于创建新的帖子。
方法接受一个 post 参数,表示要创建的帖子对象。在方法内部,使用 db 对象执行数据库插入操作,将 post 对象插入数据库中。
如果插入过程中发生错误,会将错误信息记录到日志并返回错误。否则,返回 nil 表示插入成功。
通过这些方法,PostDao 提供了对帖子数据的查询和创建操作,通过调用这些方法可以与数据库交互,对帖子数据进行存取操作。
package repository
import (
"github.com/Moonlight-Zhao/go-project-example/util"
"sync"
"time"
)
type Topic struct {
Id int64 `gorm:"column:id"`
UserId int64 `gorm:"column:user_id"`
Title string `gorm:"column:title"`
Content string `gorm:"column:content"`
CreateTime time.Time `gorm:"column:create_time"`
}
func (Topic) TableName() string {
return "topic"
}
type TopicDao struct {
}
var topicDao *TopicDao
var topicOnce sync.Once
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
func (*TopicDao) QueryTopicById(id int64) (*Topic, error) {
var topic Topic
err := db.Where("id = ?", id).Find(&topic).Error
if err != nil {
util.Logger.Error("find topic by id err:" + err.Error())
return nil, err
}
return &topic, nil
}
以下是对代码的逐行解释:
import (
"github.com/Moonlight-Zhao/go-project-example/util"
"sync"
"time"
)
这段代码导入了 "github.com/Moonlight-Zhao/go-project-example/util" 包,"sync" 包和 "time" 包。前者是一个自定义的包,后者提供了与时间相关的功能,"sync" 包提供了并发相关的功能。
type Topic struct {
Id int64 `gorm:"column:id"`
UserId int64 `gorm:"column:user_id"`
Title string `gorm:"column:title"`
Content string `gorm:"column:content"`
CreateTime time.Time `gorm:"column:create_time"`
}
这段代码定义了一个名为 Topic 的结构体。该结构体表示一个话题,包含了话题的各个属性。使用了 gorm 的 column tag 来指定结构体字段与数据库表列之间的映射关系。
func (Topic) TableName() string {
return "topic"
}
这是 Topic 结构体的一个方法,用于返回数据库表名。在这里,它返回了字符串 "topic",表示该结构体对应的数据库表名为 "topic"。
type TopicDao struct {
}
这段代码定义了一个名为 TopicDao 的结构体。该结构体表示话题的数据访问对象(DAO),用于封装对话题数据的访问操作。
var topicDao *TopicDao
var topicOnce sync.Once
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
这段代码定义了一个名为 NewTopicDaoInstance 的函数,用于获取 TopicDao 的单例实例。
在函数内部,使用了 sync.Once 类型的 topicOnce 变量来保证只执行一次实例化操作。在第一次调用函数时,使用匿名函数初始化 topicDao 变量,创建一个 TopicDao 结构体的实例,并将其赋值给 topicDao 变量。
最后,返回 topicDao 变量作为 TopicDao 的单例实例。
func (*TopicDao) QueryTopicById(id int64) (*Topic, error) {
var topic Topic
err := db.Where("id = ?", id).Find(&topic).Error
if err != nil {
util.Logger.Error("find topic by id err:" + err.Error())
return nil, err
}
return &topic, nil
}
这是 TopicDao 结构体的一个方法,用于根据话题ID查询话题信息。
方法接受一个 id 参数,表示要查询的话题ID。在方法内部,使用 db 对象执行数据库查询操作,根据 id 查询对应的话题信息,并将结果存储在 topic 变量中。
如果查询过程中发生错误,会将错误信息记录到日志并返回错误。否则,返回查询到的话题对象的指针。
package repository
import (
"github.com/Moonlight-Zhao/go-project-example/util"
"gorm.io/gorm"
"sync"
"time"
)
type User struct {
Id int64 `gorm:"column:id"`
Name string `gorm:"column:name"`
Avatar string `gorm:"column:avatar"`
Level int `gorm:"column:level"`
CreateTime time.Time `gorm:"column:create_time"`
ModifyTime time.Time `gorm:"column:modify_time"`
}
func (User) TableName() string {
return "user"
}
type UserDao struct {
}
var userDao *UserDao
var userOnce sync.Once
func NewUserDaoInstance() *UserDao {
userOnce.Do(
func() {
userDao = &UserDao{}
})
return userDao
}
func (*UserDao) QueryUserById(id int64) (*User, error) {
var user User
err := db.Where("id = ?", id).Find(&user).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
if err != nil {
util.Logger.Error("find user by id err:" + err.Error())
return nil, err
}
return &user, nil
}
func (*UserDao) MQueryUserById(ids []int64) (map[int64]*User, error) {
var users []*User
err := db.Where("id in (?)", ids).Find(&users).Error
if err != nil {
util.Logger.Error("batch find user by id err:" + err.Error())
return nil, err
}
userMap := make(map[int64]*User)
for _, user := range users {
userMap[user.Id] = user
}
return userMap, nil
}
以下是对代码的逐行解释:
import (
"github.com/Moonlight-Zhao/go-project-example/util"
"gorm.io/gorm"
"sync"
"time"
)
这段代码导入了 "github.com/Moonlight-Zhao/go-project-example/util" 包,"gorm.io/gorm" 包,"sync" 包和 "time" 包。前者是一个自定义的包,后者提供了与时间相关的功能,"gorm.io/gorm" 包是 GORM ORM 库,"sync" 包提供了并发相关的功能。
type User struct {
Id int64 `gorm:"column:id"`
Name string `gorm:"column:name"`
Avatar string `gorm:"column:avatar"`
Level int `gorm:"column:level"`
CreateTime time.Time `gorm:"column:create_time"`
ModifyTime time.Time `gorm:"column:modify_time"`
}
这段代码定义了一个名为 User 的结构体。该结构体表示一个用户,包含了用户的各个属性。使用了 gorm 的 column tag 来指定结构体字段与数据库表列之间的映射关系。
func (User) TableName() string {
return "user"
}
这是 User 结构体的一个方法,用于返回数据库表名。在这里,它返回了字符串 "user",表示该结构体对应的数据库表名为 "user"。
type UserDao struct {
}
这段代码定义了一个名为 UserDao 的结构体。该结构体表示用户的数据访问对象(DAO),用于封装对用户数据的访问操作。
var userDao *UserDao
var userOnce sync.Once
func NewUserDaoInstance() *UserDao {
userOnce.Do(
func() {
userDao = &UserDao{}
})
return userDao
}
这段代码定义了一个名为 NewUserDaoInstance 的函数,用于获取 UserDao 的单例实例。
在函数内部,使用了 sync.Once 类型的 userOnce 变量来保证只执行一次实例化操作。在第一次调用函数时,使用匿名函数初始化 userDao 变量,创建一个 UserDao 结构体的实例,并将其赋值给 userDao 变量。
最后,返回 userDao 变量作为 UserDao 的单例实例。
func (*UserDao) QueryUserById(id int64) (*User, error) {
var user User
err := db.Where("id = ?", id).Find(&user).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
if err != nil {
util.Logger.Error("find user by id err:" + err.Error())
return nil, err
}
return &user, nil
}
这是 UserDao 结构体的一个方法,用于根据用户ID查询用户信息。
方法接受一个 id 参数,表示要查询的用户ID。在方法内部,使用 db 对象执行数据库查询操作,根据 id 查询对应的用户信息,并将结果存储在user 变量中。
如果查询结果为空(即用户不存在),则返回 nil, nil,表示未找到用户。
如果查询过程中发生错误,会将错误信息记录到日志中,并返回 nil, err,表示查询出错。
如果查询成功,将查询到的用户信息返回给调用方。
func (*UserDao) MQueryUserById(ids []int64) (map[int64]*User, error) {
var users []*User
err := db.Where("id in (?)", ids).Find(&users).Error
if err != nil {
util.Logger.Error("batch find user by id err:" + err.Error())
return nil, err
}
userMap := make(map[int64]*User)
for _, user := range users {
userMap[user.Id] = user
}
return userMap, nil
}
这是 UserDao 结构体的另一个方法,用于批量根据用户ID查询用户信息。
方法接受一个 ids 参数,表示要查询的用户ID列表。在方法内部,使用 db 对象执行数据库查询操作,根据 ids 查询对应的用户信息,并将结果存储在 users 切片中。
如果查询过程中发生错误,会将错误信息记录到日志中,并返回 nil, err,表示查询出错。
如果查询成功,将查询到的用户信息转换为以用户ID为键、用户信息为值的 map,并返回给调用方。
这样,调用方可以通过用户ID快速访问对应的用户信息。
package service
import (
"errors"
"github.com/Moonlight-Zhao/go-project-example/repository"
"time"
"unicode/utf8"
)
func PublishPost(topicId, userId int64, content string) (int64, error) {
return NewPublishPostFlow(topicId, userId, content).Do()
}
func NewPublishPostFlow(topicId, userId int64, content string) *PublishPostFlow {
return &PublishPostFlow{
userId: userId,
content: content,
topicId: topicId,
}
}
type PublishPostFlow struct {
userId int64
content string
topicId int64
postId int64
}
func (f *PublishPostFlow) Do() (int64, error) {
if err := f.checkParam(); err != nil {
return 0, err
}
if err := f.publish(); err != nil {
return 0, err
}
return f.postId, nil
}
func (f *PublishPostFlow) checkParam() error {
if f.userId <= 0 {
return errors.New("userId id must be larger than 0")
}
if utf8.RuneCountInString(f.content) >= 500 {
return errors.New("content length must be less than 500")
}
return nil
}
func (f *PublishPostFlow) publish() error {
post := &repository.Post{
ParentId: f.topicId,
UserId: f.userId,
Content: f.content,
CreateTime: time.Now(),
}
if err := repository.NewPostDaoInstance().CreatePost(post); err != nil {
return err
}
f.postId = post.Id
return nil
}
这段代码位于 service 包中,主要是用于实现发布帖子的功能。
func PublishPost(topicId, userId int64, content string) (int64, error) {
return NewPublishPostFlow(topicId, userId, content).Do()
}
这是 service 包中的一个公共函数,用于发布帖子。函数接受 topicId、userId 和 content 作为参数,表示帖子的主题ID、发布用户的ID以及帖子的内容。函数通过创建一个 PublishPostFlow 对象,并调用其 Do 方法来执行发布帖子的流程。
func NewPublishPostFlow(topicId, userId int64, content string) *PublishPostFlow {
return &PublishPostFlow{
userId: userId,
content: content,
topicId: topicId,
}
}
NewPublishPostFlow 函数用于创建一个 PublishPostFlow 对象,并初始化其属性。函数接受 topicId、userId 和 content 作为参数,将这些参数作为 PublishPostFlow 对象的属性,并返回该对象的指针。
type PublishPostFlow struct {
userId int64
content string
topicId int64
postId int64
}
PublishPostFlow 结构体表示发布帖子的流程。它包含了 userId、content、topicId 和 postId 四个属性。
func (f *PublishPostFlow) Do() (int64, error) {
if err := f.checkParam(); err != nil {
return 0, err
}
if err := f.publish(); err != nil {
return 0, err
}
return f.postId, nil
}
PublishPostFlow 结构体定义了一个 Do 方法,用于执行发布帖子的流程。在 Do 方法中,首先调用 checkParam 方法来检查参数的合法性,如果参数校验失败,则返回对应的错误。接着调用 publish 方法来执行真正的发布逻辑,如果发布失败,则返回对应的错误。最后,返回发布的帖子ID。
func (f *PublishPostFlow) checkParam() error {
if f.userId <= 0 {
return errors.New("userId id must be larger than 0")
}
if utf8.RuneCountInString(f.content) >= 500 {
return errors.New("content length must be less than 500")
}
return nil
}
checkParam 方法用于检查参数的合法性。在该方法中,首先检查 userId 是否大于0,如果小于等于0,则返回对应的错误。接着,使用 utf8.RuneCountInString 函数来检查帖子内容的长度是否小于500个字符,如果超过500个字符,则返回对应的错误。如果参数校验通过,则返回 nil。
func (f *PublishPostFlow) publish() error {
post := &repository.Post{
ParentId: f.topicId,
UserId: f.userId,
Content: f.content,
CreateTime: time.Now(),
}
if err := repository.NewPostDaoInstance().CreatePost(post); err != nil {
return err
}
f.postId = post.Id
return nil
}
publish 方法用于执行实际的帖子发布逻辑。在该方法中,首先创建一个 repository.Post 对象,并将发布帖子所需的属性赋值给该对象的对应字段,其中 ParentId 表示帖子所属的主题ID,UserId 表示发布帖子的用户ID,Content 表示帖子的内容,CreateTime 表示帖子的创建时间。
接着,调用 repository.NewPostDaoInstance() 创建一个 PostDao 对象,并调用其 CreatePost 方法来将帖子对象插入数据库。如果插入过程中发生错误,则返回对应的错误。
最后,将插入成功后的帖子ID赋值给 f.postId 属性,并返回 nil 表示发布成功。
package service
import (
"github.com/Moonlight-Zhao/go-project-example/repository"
"github.com/Moonlight-Zhao/go-project-example/util"
"os"
"testing"
)
func TestMain(m *testing.M) {
if err := repository.Init(); err != nil {
os.Exit(1)
}
if err := util.InitLogger(); err != nil {
os.Exit(1)
}
m.Run()
}
func TestPublishPost(t *testing.T) {
type args struct {
topicId int64
userId int64
content string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "测试发布回帖",
args: args{
topicId: 1,
userId: 2,
content: "再次回帖",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := PublishPost(tt.args.topicId, tt.args.userId, tt.args.content)
if (err != nil) != tt.wantErr {
t.Errorf("PublishPost() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestMain(m *testing.M) {
if err := repository.Init(); err != nil {
os.Exit(1)
}
if err := util.InitLogger(); err != nil {
os.Exit(1)
}
m.Run()
}
TestMain 函数是测试包的入口函数,用于初始化测试环境。在该函数中,首先调用 repository.Init() 初始化数据库连接,如果初始化过程中发生错误,则调用 os.Exit(1) 终止测试执行。接着调用 util.InitLogger() 初始化日志记录器,如果初始化过程中发生错误,则同样调用 os.Exit(1) 终止测试执行。最后调用 m.Run() 来运行测试函数。
func TestPublishPost(t *testing.T) {
// 测试参数定义
type args struct {
topicId int64
userId int64
content string
}
// 测试用例定义
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "测试发布回帖",
args: args{
topicId: 1,
userId: 2,
content: "再次回帖",
},
wantErr: false,
},
}
// 遍历测试用例
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 调用被测试函数
_, err := PublishPost(tt.args.topicId, tt.args.userId, tt.args.content)
// 检查错误是否符合预期
if (err != nil) != tt.wantErr {
t.Errorf("PublishPost() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
TestPublishPost 函数是用于测试 PublishPost 函数的单元测试函数。在该函数中,首先定义了一个 args 结构体来表示测试参数,其中包括 topicId、userId 和 content。接着定义了一个测试用例切片 tests,其中包含了一个测试用例。测试用例中定义了一个名称、测试参数和预期的错误结果。
然后通过遍历测试用例切片,逐个运行测试用例。在每个测试用例中,调用 PublishPost 函数,并将测试参数传入。然后检查返回的错误结果是否符合预期,如果不符合预期,则使用 t.Errorf 输出错误信息。
这样,通过单元测试可以验证 PublishPost 函数在给定参数下的行为是否符合预期。
package service
import (
"errors"
"fmt"
"github.com/Moonlight-Zhao/go-project-example/repository"
"sync"
)
type TopicInfo struct {
Topic *repository.Topic
User *repository.User
}
type PostInfo struct {
Post *repository.Post
User *repository.User
}
type PageInfo struct {
TopicInfo *TopicInfo
PostList []*PostInfo
}
func QueryPageInfo(topicId int64) (*PageInfo, error) {
return NewQueryPageInfoFlow(topicId).Do()
}
func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicId: topId,
}
}
type QueryPageInfoFlow struct {
topicId int64
pageInfo *PageInfo
topic *repository.Topic
posts []*repository.Post
userMap map[int64]*repository.User
}
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
if err := f.checkParam(); err != nil {
return nil, err
}
if err := f.prepareInfo(); err != nil {
return nil, err
}
if err := f.packPageInfo(); err != nil {
return nil, err
}
return f.pageInfo, nil
}
func (f *QueryPageInfoFlow) checkParam() error {
if f.topicId <= 0 {
return errors.New("topic id must be larger than 0")
}
return nil
}
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
var topicErr, postErr error
go func() {
defer wg.Done()
topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
if err != nil {
topicErr = err
return
}
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId)
if err != nil {
postErr = err
return
}
f.posts = posts
}()
wg.Wait()
if topicErr != nil {
return topicErr
}
if postErr != nil {
return postErr
}
//获取用户信息
uids := []int64{f.topic.Id}
for _, post := range f.posts {
uids = append(uids, post.Id)
}
userMap, err := repository.NewUserDaoInstance().MQueryUserById(uids)
if err != nil {
return err
}
f.userMap = userMap
return nil
}
func (f *QueryPageInfoFlow) packPageInfo() error {
//topic info
userMap := f.userMap
topicUser, ok := userMap[f.topic.UserId]
if !ok {
return errors.New("has no topic user info")
}
//post list
postList := make([]*PostInfo, 0)
for _, post := range f.posts {
postUser, ok := userMap[post.UserId]
if !ok {
return errors.New("has no post user info for " + fmt.Sprint(post.UserId))
}
postList = append(postList, &PostInfo{
Post: post,
User: postUser,
})
}
f.pageInfo = &PageInfo{
TopicInfo: &TopicInfo{
Topic: f.topic,
User: topicUser,
},
PostList: postList,
}
return nil
}
func TestMain(m *testing.M) {
if err := repository.Init(); err != nil {
os.Exit(1)
}
if err := util.InitLogger(); err != nil {
os.Exit(1)
}
m.Run()
}
TestMain 函数是测试包的入口函数,用于初始化测试环境。在该函数中,首先调用 repository.Init() 初始化数据库连接,如果初始化过程中发生错误,则调用 os.Exit(1) 终止测试执行。接着调用 util.InitLogger() 初始化日志记录器,如果初始化过程中发生错误,则同样调用 os.Exit(1) 终止测试执行。最后调用 m.Run() 来运行测试函数。
func TestPublishPost(t *testing.T) {
// 测试参数定义
type args struct {
topicId int64
userId int64
content string
}
// 测试用例定义
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "测试发布回帖",
args: args{
topicId: 1,
userId: 2,
content: "再次回帖",
},
wantErr: false,
},
}
// 遍历测试用例
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 调用被测试函数
_, err := PublishPost(tt.args.topicId, tt.args.userId, tt.args.content)
// 检查错误是否符合预期
if (err != nil) != tt.wantErr {
t.Errorf("PublishPost() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
TestPublishPost 函数是用于测试 PublishPost 函数的单元测试函数。在该函数中,首先定义了一个 args 结构体来表示测试参数,其中包括 topicId、userId 和 content。接着定义了一个测试用例切片 tests,其中包含了一个测试用例。测试用例中定义了一个名称、测试参数和预期的错误结果。
然后通过遍历测试用例切片,逐个运行测试用例。在每个测试用例中,调用 PublishPost 函数,并将测试参数传入。然后检查返回的错误结果是否符合预期,如果不符合预期,则使用 t.Errorf 输出错误信息。
这样,通过单元测试可以验证 PublishPost 函数在给定参数下的行为是否符合预期。
package service
import (
"errors"
"fmt"
"github.com/Moonlight-Zhao/go-project-example/repository"
"sync"
)
type TopicInfo struct {
Topic *repository.Topic
User *repository.User
}
type PostInfo struct {
Post *repository.Post
User *repository.User
}
type PageInfo struct {
TopicInfo *TopicInfo
PostList []*PostInfo
}
func QueryPageInfo(topicId int64) (*PageInfo, error) {
return NewQueryPageInfoFlow(topicId).Do()
}
func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicId: topId,
}
}
type QueryPageInfoFlow struct {
topicId int64
pageInfo *PageInfo
topic *repository.Topic
posts []*repository.Post
userMap map[int64]*repository.User
}
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
if err := f.checkParam(); err != nil {
return nil, err
}
if err := f.prepareInfo(); err != nil {
return nil, err
}
if err := f.packPageInfo(); err != nil {
return nil, err
}
return f.pageInfo, nil
}
func (f *QueryPageInfoFlow) checkParam() error {
if f.topicId <= 0 {
return errors.New("topic id must be larger than 0")
}
return nil
}
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
var topicErr, postErr error
go func() {
defer wg.Done()
topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
if err != nil {
topicErr = err
return
}
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId)
if err != nil {
postErr = err
return
}
f.posts = posts
}()
wg.Wait()
if topicErr != nil {
return topicErr
}
if postErr != nil {
return postErr
}
//获取用户信息
uids := []int64{f.topic.Id}
for _, post := range f.posts {
uids = append(uids, post.Id)
}
userMap, err := repository.NewUserDaoInstance().MQueryUserById(uids)
if err != nil {
return err
}
f.userMap = userMap
return nil
}
func (f *QueryPageInfoFlow) packPageInfo() error {
//topic info
userMap := f.userMap
topicUser, ok := userMap[f.topic.UserId]
if !ok {
return errors.New("has no topic user info")
}
//post list
postList := make([]*PostInfo, 0)
for _, post := range f.posts {
postUser, ok := userMap[post.UserId]
if !ok {
return errors.New("has no post user info for " + fmt.Sprint(post.UserId))
}
postList = append(postList, &PostInfo{
Post: post,
User: postUser,
})
}
f.pageInfo = &PageInfo{
TopicInfo: &TopicInfo{
Topic: f.topic,
User: topicUser,
},
PostList: postList,
}
return nil
}
type TopicInfo struct {
Topic *repository.Topic
User *repository.User
}
type PostInfo struct {
Post *repository.Post
User *repository.User
}
type PageInfo struct {
TopicInfo *TopicInfo
PostList []*PostInfo
}
在这段代码中定义了三个结构体:TopicInfo、PostInfo 和 PageInfo。TopicInfo 结构体包含了一个指向 repository.Topic 的指针和一个指向 repository.User 的指针。PostInfo 结构体包含了一个指向 repository.Post 的指针和一个指向 repository.User 的指针。PageInfo 结构体包含了一个指向 TopicInfo 的指针和一个 PostInfo 切片。
func QueryPageInfo(topicId int64) (*PageInfo, error) {
return NewQueryPageInfoFlow(topicId).Do()
}
QueryPageInfo 函数用于查询页面信息,接受一个 topicId 参数,返回一个 PageInfo 结构体指针和一个错误。在函数内部,它通过调用 NewQueryPageInfoFlow(topicId) 创建一个 QueryPageInfoFlow 对象,并调用其 Do 方法来执行查询并返回结果。
func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicId: topId,
}
}
NewQueryPageInfoFlow 函数用于创建一个 QueryPageInfoFlow 对象,并接收一个 topId 参数来设置其 topicId 字段,然后返回该对象的指针。
type QueryPageInfoFlow struct {
topicId int64
pageInfo *PageInfo
topic *repository.Topic
posts []*repository.Post
userMap map[int64]*repository.User
}
QueryPageInfoFlow 结构体定义了一个查询页面信息的流程。它包含了 topicId、pageInfo、topic、posts 和 userMap 字段。其中,topicId 表示要查询的话题的 ID,pageInfo 表示查询结果的页面信息,topic 表示查询到的话题对象,posts 表示查询到的帖子对象列表,userMap 是一个用户 ID 到用户对象的映射。
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
if err := f.checkParam(); err != nil {
return nil, err
}
if err := f.prepareInfo(); err != nil {
return nil, err
}
if err := f.packPageInfo(); err != nil {
return nil, err
}
return f.pageInfo, nil
}
Do 方法用于执行查询页面信息的流程。首先调用 checkParam 方法检查参数的有效性,如果参数无效,则返回相应的错误。然后调用 prepareInfo 方法准备查询所需的信息,如果准备过程中出现错误,则返回相应的错误。最后调用 packPageInfo 方法打包页面信息,如果打包过程中出现错误,则返回相应的错误。如果所有步都成功完成,则返回查询到的页面信息 f.pageInfo 和 nil 错误。
goCopy code
func (f *QueryPageInfoFlow) checkParam() error { if f.topicId <= 0 { returnerrors.New("topic id must be larger than 0") } return nil }
checkParam 方法用于检查参数的有效性。如果 topicId 小于等于 0,则返回一个错误,表示话题 ID 必须大于 0。
goCopy code
func (f *QueryPageInfoFlow) prepareInfo() error { //获取topic信息 var wg sync.WaitGroup wg.Add(2) var topicErr, postErr error go func() { defer wg.Done() topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId) if err != nil { topicErr = errreturn } f.topic = topic }() //获取post列表 go func() { defer wg.Done() posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId) if err != nil { postErr = err return } f.posts = posts }() wg.Wait() if topicErr != nil { return topicErr } ifpostErr != nil { return postErr } //获取用户信息 uids := []int64{f.topic.Id} for _, post := range f.posts { uids = append(uids, post.Id) } userMap, err := repository.NewUserDaoInstance().MQueryUserById(uids) if err != nil { return err } f.userMap = userMap return nil }
prepareInfo 方法用于准备查询所需的信息。它并发地获取话题信息和帖子列表,并等待它们的完成。首先,通过调用 repository.NewTopicDaoInstance().QueryTopicById(f.topicId) 获取指定话题 ID 的话题信息,并将其赋值给 f.topic。然后,通过调用 repository.NewPostDaoInstance().QueryPostByParentId(f.topicId) 获取指定话题 ID 的帖子列表,并将其赋值给 f.posts。同时使用一个 WaitGroup 来等待两个并发操作的完成。
接下来,检查获取话题信息和帖子列表过程中是否出现错误。如果出现错误,则返回相应的错误。否则,从话题和帖子中提取出所有的用户 ID,并将它们存储在 uids 切片中。然后,调用 repository.NewUserDaoInstance().MQueryUserById(uids) 并传入用户 ID 切片,以获取所有用户的详细信息,并将其存储在 f.userMap 中,其中键是用户 ID,值是用户对象。
最后,如果获取用户信息的过程中出现错误,则返回相应的错误;否则,返回 nil,表示准备信息的过程顺利完成。
postList := make([]*PostInfo, 0)
for _, post := range f.posts {
postUser, ok := userMap[post.UserId]
if !ok {
return errors.New("has no post user info for " + fmt.Sprint(post.UserId))
}
postList = append(postList, &PostInfo{
Post: post,
User: postUser,
})
}
f.pageInfo = &PageInfo{
TopicInfo: &TopicInfo{
Topic: f.topic,
User: topicUser,
},
PostList: postList,
}
return nil
packPageInfo 方法用于打包页面信息。首先,创建一个空的 postList 切片。然后,遍历帖子列表 f.posts,对于每个帖子,从 userMap 中获取对应的用户信息 postUser。如果获取不到用户信息,则返回一个错误,表示找不到对应的用户信息。
接下来,将帖子信息和对应的用户信息组合成一个 PostInfo 对象,并将其添加到 postList 中。
最后,创建一个 PageInfo 对象,其中包含了话题信息和帖子列表信息。将它赋值给 f.pageInfo,并返回 nil,表示页面信息的打包过程成功完成。
以上就是代码中各个函数和方法的逐行解释。它们主要用于发布帖子、查询页面信息等功能的实现。
package service
import (
"testing"
)
func TestQueryPageInfo(t *testing.T) {
type args struct {
topicId int64
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "查询页面",
args: args{
topicId: 1,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := QueryPageInfo(tt.args.topicId)
if (err != nil) != tt.wantErr {
t.Errorf("QueryPageInfo() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
这是一个测试函数 TestQueryPageInfo,用于测试 QueryPageInfo 函数。在测试中,定义了一个 args 结构,包含了待查询的话题 ID。然后,创建了一个测试案例的切片 tests,其中包含了一个测试案例。
在该测试案例中,给定了一个话题 ID,并期望不会发生错误。
接下来,通过 for 循环遍历测试案例切片,对每个测试案例进行单独的测试。在每个测试中,调用 QueryPageInfo 函数,并将返回的结果和期望的错误进行比较。如果返回的错误与期望的错误不一致,则输出测试失败的信息。
这个测试函数用于验证 QueryPageInfo 函数的功能是否正确,确保在给定合法的话题 ID 时能够正常查询页面信息,并且不会返回错误。
package util
import "go.uber.org/zap"
var Logger *zap.Logger
func InitLogger() error {
var err error
Logger, err = zap.NewProduction()
if err != nil {
return err
}
return nil
}
import "go.uber.org/zap"
var Logger *zap.Logger
func InitLogger() error {
var err error
Logger, err = zap.NewProduction()
if err != nil {
return err
}
return nil
}
这段代码定义了一个日志记录器的全局变量 Logger,它使用了 Uber 开源的 Zap 日志库。该变量的类型是 *zap.Logger。
函数 InitLogger() 用于初始化日志记录器。它首先声明一个 err 变量用于存储可能发生的错误。然后,调用 zap.NewProduction() 函数创建一个生产环境下的 Zap 日志记录器,并将返回的日志记录器赋值给全局变量 Logger。如果创建过程中发生错误,将错误赋值给 err 变量,并返回该错误。
通过调用 InitLogger() 函数,可以初始化全局的日志记录器 Logger,使其可以在整个应用程序中使用。这样,其他模块和函数就可以通过 Logger 变量进行日志记录操作。
package main
import (
"github.com/Moonlight-Zhao/go-project-example/handler"
"github.com/Moonlight-Zhao/go-project-example/repository"
"github.com/Moonlight-Zhao/go-project-example/util"
"gopkg.in/gin-gonic/gin.v1"
"os"
)
func main() {
if err := Init(); err != nil {
os.Exit(-1)
}
r := gin.Default()
r.Use(gin.Logger())
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param("id")
data := handler.QueryPageInfo(topicId)
c.JSON(200, data)
})
r.POST("/community/post/do", func(c *gin.Context) {
uid, _ := c.GetPostForm("uid")
topicId, _ := c.GetPostForm("topic_id")
content, _ := c.GetPostForm("content")
data := handler.PublishPost(uid, topicId, content)
c.JSON(200, data)
})
err := r.Run()
if err != nil {
return
}
}
func Init() error {
if err := repository.Init(); err != nil {
return err
}
if err := util.InitLogger(); err != nil {
return err
}
return nil
}
import (
"github.com/Moonlight-Zhao/go-project-example/handler"
"github.com/Moonlight-Zhao/go-project-example/repository"
"github.com/Moonlight-Zhao/go-project-example/util"
"gopkg.in/gin-gonic/gin.v1"
"os"
)
func main() {
if err := Init(); err != nil {
os.Exit(-1)
}
r := gin.Default()
r.Use(gin.Logger())
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param("id")
data := handler.QueryPageInfo(topicId)
c.JSON(200, data)
})
r.POST("/community/post/do", func(c *gin.Context) {
uid, _ := c.GetPostForm("uid")
topicId, _ := c.GetPostForm("topic_id")
content, _ := c.GetPostForm("content")
data := handler.PublishPost(uid, topicId, content)
c.JSON(200, data)
})
err := r.Run()
if err != nil {
return
}
}
func Init() error {
if err := repository.Init(); err != nil {
return err
}
if err := util.InitLogger(); err != nil {
return err
}
return nil
}
这段代码是应用程序的入口函数 main() 和初始化函数 Init()。
在 main() 函数中,首先调用 Init() 函数来初始化应用程序的依赖项。如果初始化过程中发生错误,将通过 os.Exit(-1) 来退出应用程序。
然后创建一个默认的 Gin 路由实例 r := gin.Default(),该实例将用于处理 HTTP 请求。
通过 r.Use(gin.Logger()) 将 Gin 的日志中间件添加到路由中,以记录每个请求的日志。
定义了三个路由处理函数:
- GET 请求 "/ping",返回 JSON 格式的响应
{"message": "pong"}。 - GET 请求 "/community/page/get/:id",根据 URL 中的参数 "id" 调用
handler.QueryPageInfo()函数,并将返回的数据以 JSON 格式响应给客户端。 - POST 请求 "/community/post/do",从 POST 表单中获取 "uid"、"topic_id" 和 "content",然后调用
handler.PublishPost()函数处理发布帖子的逻辑,并将返回的数据以 JSON 格式响应给客户端。
最后,通过调用 r.Run() 启动应用程序的 HTTP 服务,监听并处理传入的请求。
Init() 函数用于初始化应用程序的依赖项。它首先调用 repository.Init() 初始化数据库连接和数据存储相关的内容,然后调用 util.InitLogger() 初始化日志记录器。如果初始化过程中发生错误,将返回该错误。