Go 语言进阶与依赖管理(有实战)
1.Go语言进阶
1.1并发VS并行
Go可以充分发挥多核优势,高效运行。
并发:多线程程序在单核心的 cpu 上运行;
并行:多线程程序在多核心的 cpu 上运行。
总的来说,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行,Go程序可以设置使用核心数,以发挥多核计算机的能力。(时间片:CPU分配给各个程序的时间)
1.2 线程
线程是进程的一个执行实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
(而进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位)
一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行。
协程:用户态,轻量级线程,栈 MB 级别。
线程:内核态,线程跑多个协程,栈 KB 级别。
这里解释一下,协程不是进程,也不是线程,它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行
而我们的go语言开启协程也很简单,就是在函数的前面写上go关键字即可
func hello(i int) {
println("helle :" + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
go func(j int) { //就是这个func前面的go
hello(j)
}(i)
}
time.Sleep(time.Second)//使用time.Sleep(time.Second)来阻塞主函数,来防止主函数退出进程
}
对于这个代码,我们可以看到输出的结果是乱序的,但是for循环是有序输出的,
这个也可以代表,代码是通过并行来打印的输出。
1.2.1协程通信共享内存
go语言的最大两个亮点,一个是goroutine,一个就是channel了。在go中,提倡通过通信共享内存而不是通过共享内存而实现通信
Go 通过 channel 实现 CSP(通信顺序进程) 通信模型,主要用于 goroutine 之间的消息传递和事件通知。 有了 channel 和 goroutine 之后,Go 的并发编程变得异常容易和安全,得以让程序员把注意力留到业务上去,实现开发效率的提升。
1.3Channel
Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。
它的操作符是箭头 <- ,箭头指示数据流向,箭头指向哪里,数据就流向哪里
在channel中,容量表示通道最多可容纳的元素数,并表示通道的缓存大小,如果未设置容量,或容量设置为0,则表示通道没有缓存,只有当发送方和接收方准备就绪时,才会进行通信。
如果设置了缓存,则可能不会发生阻塞。只有当缓冲区已满时,发送才会阻止,而只有当缓存为空时,接收才会阻止。零信道无法通信。
make(chan type,[size])
- [size]代表有无缓冲通道
make(chan int)//无缓冲通道
make(chan int,2)//有缓冲通道
下面就是一个通信实现共享内存的例子。
A子协程发送0~9数字
B子协程计算输入数字的平方
主协程输出最后的平方数
package main
import "fmt"
func main() {
// 创建两个通道
src := make(chan int)
dest := make(chan int, 3)
go func() { //协程A
defer close(src)
for i := 0; i < 10; i++ {
src <- i
fmt.Println("src<-", i)
}
}()
go func() { //协程B
defer close(dest)
for i := range src { //有数据传就一直接收
dest <- i * i
fmt.Println("dest<-", i*i)
}
}()
// 主协程
for i := range dest { //有数据传就一直接收
fmt.Println(i)
}
}
打印出子协程的值更方便理解~但是注意print函数会有部分延迟,所以会有点慢打印出。
1.4并发安全锁
可以通过下面一个例子来看一下加了锁和不加锁的区别。
package main
import (
"sync"
"time"
)
func main() {
add()
}
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x++
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x++
}
}
func add() {
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("使用锁:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("不使用锁:", x)
}
输出:
可以看到在并发执行时候会导致数据的不稳定,在go语言中,我们可以使用WaitGroup来执行并发任务的同步。
1.5 WaitGroup
可以替代之前time.Sleep的操作,让主协程等待子协程全部执行完之后再结束
WaitGroup 类实现的功能是:等待一系列协程并发地执行完毕。如果不等待所有协程执行完毕,可能会导致一些线程安全问题。sync.WaitGroup 包含 3 个方法: 方法 作用
| 方法 | 作用 |
|---|---|
| Add(delta int) | 主协程调用该方法,设置 delta 为需要等待的协程数量,就是说计数器+delta |
| Done() | 每个子协程运行起来,当每个子协程执行结束后,调用 Done() 表示子协程运行结束, 计数器-1 |
| Wait() | 当所有协程执行完毕后,代码块可使用 Wait() ,当 计数器 ==0时,才执行后续代码 |
我们对之前那个例子改造一下
package main
import (
"fmt"
"sync"
)
func hello(i int) {
fmt.Println("routine:", i)
}
func main() {
var wg sync.WaitGroup //+
wg.Add(5)//+
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
wg.Done()
}(i)
}
wg.Wait()
}
输出:
在这个过程中,就是首先通过add方法,对计数器+5,然后开启协程,每个协程执行完后,通过done对计数器减少1,最后wait主协程阻塞,计数器为0退出主协程。得到最终的输出结果。
2.依赖管理
2.1发展历程
Gopath->go vender->go module
2.1.1 Gopath
GOPATH是Go语言支持的一个环境变量,value是Go项目的工作区。
缺点:无法实现包的多版本控制
2.1.2 go vender
相当于保存多个依赖文件
缺点:
底层包版本不同,就是说无法控制依赖的版本。更新项目又可能出现依赖冲突,导致编译出错。
2.1.3 Go Modoule
通过go.mod文件管理依赖包版本
通过go get/go mod指令工具管理依赖包
依赖管理三要素:
1.配置文件,描述依赖:go.mod
2.中心仓库管理依赖库:Proxy
3.本地工具:go get/mod
如果一个项目依赖了两个版本,对于go来说,会选择较高的兼容版本
3.项目实战
3.1实现功能:
社区话题页面
-
展示话题(标题,文字描述)和回帖列表
-
暂不考虑前端页面实现,仅仅实现一个本地web服务
-
话题和回帖数据用文件存储
-
支持发布帖子
-
本地id生成要保证不重复
-
append文件 更新索引要注意Map的并发安全问题
从功能入手去一点点分析,用户浏览消费,涉及页面的展示,包括话题内容和回帖的列表,那我们就可以定义两个结构体
3.2分层结构
- 数据层:数据Model,外部数据的增删改查
- 逻辑层:业务Entity,处理核心业务逻辑输出
- 视图层:视图view,处理和外部的交互逻辑
数据层关联底层数据模型,也就是这里的model,封装外部数据的增删改查,我们的数据存储在本地文件,通过文件操作拉取话题,帖子数据;数据层面向逻辑层,对service层透明,屏蔽下游数据差异,也就是不管下游是文件,还是数据库,还是微服务等,对service层的接口模型是不变的。 Servcie逻辑层处理核心业务逻辑,计算打包业务实体entiy,对应我们的需求,就是话题页面,包括话题和回帖列表,并上送给视图层; Controller视图层负责处理和外部的交互逻辑,以view视图的形式返回给客户端,对于我们需求,我们封装json格式化的请求结果, api形式访问就好,
3.3实现
1.数据层
这里我们用map实现内存索引,在服务对外暴露前,利用文件元数据初始化全局内存索引,这样就可以实现0(1)的时间复杂度查找操作。
var(
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
下面是具体的实现,解释一下代码过程,里面也有语句注释,便于理解,首先是打开文件,基于file初始化scammer,通过迭代器方式遍历数据行,转化为结构体存储至内存map,这就是初始化话题内存索引。
//init.go
package repository
import (
"bufio"
"encoding/json"
"os"
)
// 使用索引数据结构提高查询速度
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
// Init 函数用于初始化仓库,接收一个文件路径作为参数
func Init(filePath string) error {
if err := initTopicIndexMap(filePath); err != nil {
return err
}
if err := initPostIndexMap(filePath); err != nil {
return err
}
return nil
}
// 初始化主题索引函数,接收一个文件路径作为参数
func initTopicIndexMap(filePath string) error {
open, err := os.Open(filePath + "topic") //打开文件
if err != nil {
return err
}
defer open.Close() // 在函数结束时关闭文件
// 创建一个扫描器来逐行读取文件内容
scanner := bufio.NewScanner(open)
topicTmpMap := make(map[int64]*Topic) // 创建一个临时map用于存储主题索引
for scanner.Scan() {
text := scanner.Text()
var topic Topic
// 将文本解析为 Topic 结构并存储到map中
if err := json.Unmarshal([]byte(text), &topic); err != nil {
return err
}
topicTmpMap[topic.Id] = &topic
}
topicIndexMap = topicTmpMap // 将临时map内容赋值给topic索引
return nil
}
// post内容同理
func initPostIndexMap(filePath string) error {
open, err := os.Open(filePath + "post")
if err != nil {
return err
}
defer open.Close()
scanner := bufio.NewScanner(open)
postTmpMap := make(map[int64][]*Post)
for scanner.Scan() {
text := scanner.Text()
var post Post
if err := json.Unmarshal([]byte(text), &post); err != nil {
return err
}
posts, ok := postTmpMap[post.ParentId]
if !ok {
postTmpMap[post.ParentId] = []*Post{&post} //如果之前没有这个主题的帖子,就创建一个新的帖子切片,将当前的帖子放入其中,然后将这个切片与主题的父主题 ID 相关联。
continue
}
posts = append(posts, &post) //将当前的帖子添加到之前已经存在的帖子分类中
postTmpMap[post.ParentId] = posts
}
postIndexMap = postTmpMap
return nil
}
有了内存索引,下一步就是实现查询操作就比较简单了,直接根据查询key获得map中的value就好了,这里用到了sync.once,主要适用高并发的场景下只执行一次的场景,这里的基于once的实现模式就是我们平常说的单例模式,减少存储的浪费。
//topic.go
package repository
import (
"sync"
)
// 主题topic的结构体定义
type Topic struct {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
// // TopicDao 结构是主题数据访问对象,用于管理主题信息的访问
type TopicDao struct {
}
var (
topicDao *TopicDao //用于存储 TopicDao 实例的变量
topicOnce sync.Once // 用于保证只初始化一次的同步机制
)
// 创建并返回一个 TopicDao 实例
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
// QueryTopicById 根据主题 ID 查询并返回对应的主题信息
func (*TopicDao) QueryTopicById(id int64) *Topic {
return topicIndexMap[id]
}
//post.go
package repository
import (
"sync"
)
// 主题post的结构体定义
type Post struct {
Id int64 `json:"id"`
ParentId int64 `json:"parent_id"` //就是topic_id
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
type PostDao struct {
}
var (
postDao *PostDao
postOnce sync.Once // 用于保证只初始化一次的同步机制
)
func NewPostDaoInstance() *PostDao {
postOnce.Do(
func() {
postDao = &PostDao{}
})
return postDao
}
// QueryPostById 根据主题 ID 查询对应的帖子信息
func (*PostDao) QueryPostById(parentId int64) []*Post {
return postIndexMap[parentId] //// 返回与给定主题 ID 相关的帖子列表
}
2.逻辑层
有了数据层之后,我们就开始实现service(逻辑)层,首先我们需要先定义一下service实体
type PageInfo struct {
Topic *repository.Topic
PostList []*repository.Post
}
流程呢就是参数校验→准备数据→组装实体
下面是代码流程编排
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
}
关于prepareInfo方法,话题和回帖信息的获取都依赖topicid,这样2者就可以并行执行
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts := repository.NewPostDaoInstance().QueryPostsByParentId(f.topicId)
f.posts = posts
}()
wg.Wait()
return nil
}
那么我们还剩下Paramcheck和pack函数,下面是完整代码(附上注释)
//query_page.go
// query_page.go
package service
import (//classproject是go.mod里面的模块
"classproject/repository"
"errors"
"sync"
)
type PageInfo struct {
Topic *repository.Topic // 主题信息
PostList []*repository.Post // 帖子列表
}
// QueryPageInfo 根据主题 ID 查询并返回主题及帖子信息
func QueryPageInfo(topicId int64) (*PageInfo, error) {
return NewQueryPageInfoFlow(topicId).Do()
}
// QueryPageInfo 根据主题 ID 查询并返回主题及帖子信息
func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicId: topId,
}
}
// QueryPageInfoFlow 结构定义了查询主题及帖子信息的流程
type QueryPageInfoFlow struct {
topicId int64
pageInfo *PageInfo // 存储查询结果
topic *repository.Topic
posts []*repository.Post
}
// Do 执行查询主题及帖子信息的流程,返回查询结果或错误
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
}
// checkParam 检查参数是否合法
func (f *QueryPageInfoFlow) checkParam() error {
if f.topicId <= 0 {
return errors.New("topic id must be larger than 0")
}
return nil
}
// prepareInfo 准备主题和帖子信息
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts := repository.NewPostDaoInstance().QueryPostById(f.topicId)
f.posts = posts
}()
wg.Wait()
return nil
}
// packPageInfo 将主题和帖子列表信息打包成 PageInfo 结构
func (f *QueryPageInfoFlow) packPageInfo() error {
f.pageInfo = &PageInfo{
Topic: f.topic,
PostList: f.posts,
}
return nil
}
3.视图层
Service实现完成之后,下面就是controller(视图)层。这里我们定义一个view对象,通过code msg打包业务状态信息,用data承载业务实体信息,下面是代码。
//query_page.go
package cotroller
import (
"strconv"
"classproject/service"
)
// PageData 结构定义了返回给前端的数据格式
type PageData struct {
Code int64 `json:"code"` // 返回状态码,0 表示成功,-1 表示错误
Msg string `json:"msg"` // 返回状态码,0 表示成功,-1 表示错误
Data interface{} `json:"data"` // 返回状态码,0 表示成功,-1 表示错误
}
// QueryPageInfo 根据主题 ID 查询主题及帖子信息,并返回包装后的数据
func QueryPageInfo(topicIdStr string) *PageData {
// 将传入的主题 ID 字符串转换为整型
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil { // 如果转换失败,返回包含错误信息的 PageData
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
// 调用 service 包中的 QueryPageInfo 函数查询主题及帖子信息
pageInfo, err := service.QueryPageInfo(topicId)
if err != nil {
// 如果查询出错,返回包含错误信息的 PageData
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
// 查询成功,返回包含成功信息和查询结果的 PageData
return &PageData{
Code: 0,
Msg: "success",
Data: pageInfo,
}
}
三层都实现后,最后是web服务的引擎配置,包括初始化数据索引,初始化引擎配置,构建路由,启动服务。
//server.go
package main
import (
"classproject/controller"
"classproject/repository"
"os"
// "gopkg.in/gin-gonic/gin.v1"
"github.com/gin-gonic/gin"
)
func main() {
if err := Init("./data/"); err != nil {
os.Exit(-1)
} //初始化
r := gin.Default() // 创建一个默认的 Gin 路由引擎
// 定义一个 GET 请求处理函数
r.GET("/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param("id") // 获取请求中的主题 ID 参数
data := cotroller.QueryPageInfo(topicId) // 调用控制器函数处理查询请求
c.JSON(200, data) // 返回 JSON 格式的响应数据
})
err := r.Run() // 启动 Gin 服务器
if err != nil {
return
}
}
// Init 函数用于初始化仓库数据
func Init(filePath string) error {
if err := repository.Init(filePath); err != nil {
return err
}
return nil
}
中间省略debug的无数错...一开始导包一直没弄好,然后后面发现代码又有很多小错误orz大家写的时候要仔细一点~
go run运行server.go之后,输入
curl http://localhost:8080/community/page/get/2
结果
3.5课后实践
支持发布帖子。
本地ld生成需要保证不重复、唯一性。
Append 文件,更新索引,注意 Map 的并发安全问题。
//server.go
package main
import (
"classproject/cotroller"
"classproject/repository"
"os"
// "gopkg.in/gin-gonic/gin.v1"
"github.com/gin-gonic/gin"
)
func main() {
if err := Init("./data/"); err != nil {
os.Exit(-1)
} //初始化
r := gin.Default() // 创建一个默认的 Gin 路由引擎
// 定义一个 GET 请求处理函数
r.GET("/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param("id") // 获取请求中的主题 ID 参数
data := cotroller.QueryPageInfo(topicId) // 调用控制器函数处理查询请求
c.JSON(200, data) // 返回 JSON 格式的响应数据
})
r.POST("/community/post", func(c *gin.Context) {
topicId := c.PostForm("topic_id")
content := c.PostForm("content")
data := cotroller.CreatePost(topicId, content)
c.JSON(200, data)
})
err := r.Run() // 启动 Gin 服务器
if err != nil {
return
}
}
// Init 函数用于初始化仓库数据
func Init(filePath string) error {
if err := repository.Init(filePath); err != nil {
return err
}
return nil
}
//init.go
package repository
import (
"bufio"
"encoding/json"
"os"
)
// 使用索引数据结构提高查询速度
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
// Init 函数用于初始化仓库,接收一个文件路径作为参数
func Init(filePath string) error {
if err := initTopicIndexMap(filePath); err != nil {
return err
}
if err := initPostIndexMap(filePath); err != nil {
return err
}
return nil
}
// 初始化主题索引函数,接收一个文件路径作为参数
func initTopicIndexMap(filePath string) error {
open, err := os.Open(filePath + "topic") //打开文件
if err != nil {
return err
}
defer open.Close() // 在函数结束时关闭文件
// 创建一个扫描器来逐行读取文件内容
scanner := bufio.NewScanner(open)
topicTmpMap := make(map[int64]*Topic) // 创建一个临时map用于存储主题索引
for scanner.Scan() {
text := scanner.Text()
var topic Topic
// 将文本解析为 Topic 结构并存储到map中
if err := json.Unmarshal([]byte(text), &topic); err != nil {
return err
}
topicTmpMap[topic.Id] = &topic
}
topicIndexMap = topicTmpMap // 将临时map内容赋值给topic索引
return nil
}
// post内容同理
func initPostIndexMap(filePath string) error {
open, err := os.Open(filePath + "post")
if err != nil {
return err
}
defer open.Close()
scanner := bufio.NewScanner(open)
postTmpMap := make(map[int64][]*Post)
for scanner.Scan() {
text := scanner.Text()
var post Post
if err := json.Unmarshal([]byte(text), &post); err != nil {
return err
}
posts, ok := postTmpMap[post.ParentId]
if !ok {
postTmpMap[post.ParentId] = []*Post{&post} //如果之前没有这个主题的帖子,就创建一个新的帖子切片,将当前的帖子放入其中,然后将这个切片与主题的父主题 ID 相关联。
continue
}
posts = append(posts, &post) //将当前的帖子添加到之前已经存在的帖子分类中
postTmpMap[post.ParentId] = posts
}
postIndexMap = postTmpMap
return nil
}
//post.go
// post.go
package repository
import (
"math/rand"
"sync"
"time"
)
var (
idMutex sync.Mutex
usedIDs = make(map[int64]bool)
idSource *rand.Rand
)
var postMutex sync.Mutex
func init() {
idSource = rand.New(rand.NewSource(time.Now().UnixNano()))
}
// 主题post的结构体定义
type Post struct {
Id int64 `json:"id"`
ParentId int64 `json:"parent_id"` //就是topic_id
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
type PostDao struct {
}
// GenerateUniqueID 用于生成唯一的帖子ID
func GenerateUniqueID() int64 {
idMutex.Lock()
defer idMutex.Unlock()
var id int64
for {
id = int64(idSource.Uint64())
if !usedIDs[id] {
usedIDs[id] = true
break
}
}
return id
}
var (
postDao *PostDao
postOnce sync.Once // 用于保证只初始化一次的同步机制
)
func NewPostDaoInstance() *PostDao {
postOnce.Do(
func() {
postDao = &PostDao{}
})
return postDao
}
// CreatePost 创建帖子并更新索引
func (*PostDao) CreatePost(parentID int64, content string) error {
postID := GenerateUniqueID() // 生成唯一的帖子 ID
post := &Post{
Id: postID,
ParentId: parentID,
Content: content,
CreateTime: time.Now().Unix(), // 设置帖子创建时间
}
postMutex.Lock()
defer postMutex.Unlock()
// 将帖子附加到帖子列表中
postList, ok := postIndexMap[parentID]
if !ok {
postIndexMap[parentID] = []*Post{post}
} else {
postIndexMap[parentID] = append(postList, post)
}
return nil
}
// QueryPostById 根据主题 ID 查询对应的帖子信息
func (*PostDao) QueryPostById(parentId int64) []*Post {
return postIndexMap[parentId] //// 返回与给定主题 ID 相关的帖子列表
}
serviced
// query_page.go
package service
import (
"classproject/repository"
"errors"
"sync"
)
type PageInfo struct {
Topic *repository.Topic // 主题信息
PostList []*repository.Post // 帖子列表
}
// QueryPageInfo 根据主题 ID 查询并返回主题及帖子信息
func QueryPageInfo(topicId int64) (*PageInfo, error) {
return NewQueryPageInfoFlow(topicId).Do()
}
// QueryPageInfo 根据主题 ID 查询并返回主题及帖子信息
func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicId: topId,
}
}
// QueryPageInfoFlow 结构定义了查询主题及帖子信息的流程
type QueryPageInfoFlow struct {
topicId int64
pageInfo *PageInfo // 存储查询结果
topic *repository.Topic
posts []*repository.Post
}
// Do 执行查询主题及帖子信息的流程,返回查询结果或错误
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
}
// checkParam 检查参数是否合法
func (f *QueryPageInfoFlow) checkParam() error {
if f.topicId <= 0 {
return errors.New("topic id must be larger than 0")
}
return nil
}
// prepareInfo 准备主题和帖子信息
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts := repository.NewPostDaoInstance().QueryPostById(f.topicId)
f.posts = posts
}()
wg.Wait()
return nil
}
// packPageInfo 将主题和帖子列表信息打包成 PageInfo 结构
func (f *QueryPageInfoFlow) packPageInfo() error {
f.pageInfo = &PageInfo{
Topic: f.topic,
PostList: f.posts,
}
return nil
}
// CreatePost 创建帖子并更新索引
func (f *QueryPageInfoFlow) CreatePost(content string) error {
return repository.NewPostDaoInstance().CreatePost(f.topicId, content)
}
controller的
// query_page.go
package cotroller
import (
"classproject/service"
"strconv"
)
// PageData 结构定义了返回给前端的数据格式
type PageData struct {
Code int64 `json:"code"` // 返回状态码,0 表示成功,-1 表示错误
Msg string `json:"msg"` // 返回状态码,0 表示成功,-1 表示错误
Data interface{} `json:"data"` // 返回状态码,0 表示成功,-1 表示错误
}
// QueryPageInfo 根据主题 ID 查询主题及帖子信息,并返回包装后的数据
func QueryPageInfo(topicIdStr string) *PageData {
// 将传入的主题 ID 字符串转换为整型
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil { // 如果转换失败,返回包含错误信息的 PageData
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
// 调用 service 包中的 QueryPageInfo 函数查询主题及帖子信息
pageInfo, err := service.QueryPageInfo(topicId)
if err != nil {
// 如果查询出错,返回包含错误信息的 PageData
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
// 查询成功,返回包含成功信息和查询结果的 PageData
return &PageData{
Code: 0,
Msg: "success",
Data: pageInfo,
}
}
func CreatePost(topicIdStr string, content string) *PageData {
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
// 调用创建帖子方法
err = service.NewQueryPageInfoFlow(topicId).CreatePost(content)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: nil,
}
}
Invoke-WebRequest -Method Post -Uri "http://localhost:8080/community/post" -Body "topic_id=10&content=zlx%20nb!!!"
curl http://localhost:8080/community/page/get/10