需求
- 支持对话题发布回帖。
- 回帖id生成需要保证不重复、唯一性。
- 新加回帖追加到本地文件,同时需要更新索引,注意Map的并发安全问题。
web框架(Gin)
基础概念:
Gin 是一个用 Go 语言编写的 Web 框架,它的设计目标是提供一种快速开发 Web 应用程序的方式。Gin 框架具有以下特点:
1.高性能:Gin 框架采用了基于 Radix 树的路由算法,可以快速地匹配路由,从而提高了路由的处理效率。
2.中间件支持:Gin 框架支持中间件,可以方便地实现各种功能,例如认证、日志记录、性能监控等。
3.路由分组:Gin 框架支持路由分组,可以将相似的路由分组到一起,从而提高代码的可读性和可维护性。
4.JSON 验证:Gin 框架支持 JSON 验证,可以方便地验证请求参数的合法性。
5.错误处理:Gin 框架支持自定义错误处理函数,可以方便地处理各种错误情况。
入门指南🔗:github.com/gin-gonic/g…
GETTING GIN
1.把gin引入代码中
import "github.com/gin-gonic/gin"
2.下载安装
$ go get -u github.com/gin-gonic/gin
路径记录
实现思路
1.根据需求用例,画出ER图:是USER对应
展示话题(Topic)
id
title
content
create_time
回帖列表(PostList):
id
topic_id
content
create_time
然后就用json把结构体编码
2.进行分层结构
我们把应用程序分成了三部份:
数据层(Repository):数据 Model,外部数据的增删改查
逻辑层(Service):业务Entity,处理核心业务逻辑输出
视图层(Controller):视图riew,处理和外部的交互逻辑
(目录结构一一对应)
1.Repository-index(解决查询问题)
把元数据变成索引,数据行创建内存map
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64]*[Post]
)
2.Service层
Service层是承接Repository层的业务逻辑层。它负责实现应用程序的业务逻辑和规则,并与Repository层进行交互以获取和保存数据。 特别是在处理话题信息和回帖信息时这是用到了并行处理,因为这个没有依赖可以一起操作
3.Controller
- 进行构建view对象&&业务错误码
4.Router
- 进行初始化数据索引
- 初始化引擎配置
- 构建路由
- 启动服务
运行代码
Repository
package repository
import (
"github.com/Moonlight-Zhao/go-project-example/utils"
"log"
"sync"
)
type Topic struct {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
type TopicDao struct {
}
var (
topicDao *TopicDao
topicOnce sync.Once
)
func (Topic) TableName() string {
return "topic"
}
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
func (*TopicDao) QueryTopicById(id int64) *Topic {
return topicIndexMap[id]
}
func (*TopicDao) Insert(topic Topic) error {
result := utils.DB.Create(&topic)
if result.Error != nil {
// 插入失败,处理错误情况
log.Println("插入失败:", result.Error)
return result.Error
}
Init()
return nil
}
func (*TopicDao) findAllTopics() ([]Topic, error) {
var topics []Topic
result := utils.DB.Find(&topics)
if result.Error != nil {
return nil, result.Error
}
return topics, nil
}
在该示例中,首先定义了一个名为Topic的结构体,它包含了Topic实体的属性(Id、Title、Content和CreateTime)。然后,定义了一个名为TopicDao的结构体,它包含了一些用于访问Topic实体数据的方法。
在NewTopicDaoInstance函数中,使用了sync.Once类型的topicOnce变量来保证TopicDao只被创建一次。在该函数中,我们创建了一个新的TopicDao实例,并将其赋值给全局变量topicDao。
QueryTopicById方法用于根据ID查询Topic实体。在该方法中,我们可以看到topicIndexMap变量,这是一个全局变量,它是一个map类型,用于存储Topic实体。在该示例中,我们假设在程序启动时,已经将所有Topic实体加载到了topicIndexMap中。
Insert方法用于向数据库中插入新的Topic实体。在该方法中,我们首先使用utils.DB.Create函数向数据库中插入新的记录。如果插入失败,则返回一个错误,并打印错误信息。如果插入成功,则调用Init函数重新加载所有Topic实体。
findAllTopics方法用于返回所有的Topic实体。在该方法中,我们首先定义了一个名为topics的空切片,然后使用utils.DB.Find函数从数据库中查询所有的Topic记录,并将结果保存到topics切片中。如果查询失败,则返回一个错误。
索引(查询)
package repository
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
func Init() error {
if err := initTopicIndexNewMap(); err != nil {
return err
}
return nil
}
// 从数据库中读取表来初始化索引
func initTopicIndexNewMap() error {
rows, err := topicDao.findAllTopics()
if err != nil {
return err
}
topicTmpMap := make(map[int64]*Topic)
for _, topic := range rows {
topicTmpMap[topic.Id] = &topic
}
// 将结果存储到全局变量 topicIndexMap 中
topicIndexMap = topicTmpMap
return nil
}
这是一个用于初始化Topic索引的示例代码。在该示例中,定义了两个全局变量,分别是topicIndexMap和postIndexMap,它们分别用于存储Topic和Post实体的索引。然后,定义了一个名为Init的函数,用于初始化索引。
在Init函数中,我们首先调用了initTopicIndexNewMap函数,该函数用于从数据库中读取Topic实体数据,并将其存储到全局变量topicIndexMap中。如果读取失败,则返回一个错误。
在initTopicIndexNewMap函数中,我们首先调用了topicDao.findAllTopics方法,该方法用于查询所有的Topic记录。如果查询失败,则返回一个错误。
然后,我们定义了一个名为topicTmpMap的map类型变量,用于临时存储查询结果。在for循环中,我们遍历查询结果,并将每个Topic实体的ID作为key,将整个Topic实体作为value存储到topicTmpMap中。
最后,我们将topicTmpMap赋值给全局变量topicIndexMap,并返回nil表示初始化成功。
Service
package service
import (
"github.com/Moonlight-Zhao/go-project-example/repository"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func Insert(context *gin.Context) {
// 接收form表单数据
title := context.PostForm("title")
content := context.PostForm("content")
currentTime := time.Now()
createTime := currentTime.Unix()
u1 := repository.Topic{
Id: 0,
Title: title,
Content: content,
CreateTime: createTime,
}
topicDao := repository.NewTopicDaoInstance()
err := topicDao.Insert(u1)
// repository.
if err != nil {
repository.Init()
context.JSON(http.StatusBadRequest, gin.H{"msg": "发布失败"})
} else {
repository.Init()
context.JSON(http.StatusOK, gin.H{"msg": "发布成功"})
}
}
这是一个用于发布Topic的示例代码。在该示例中,定义了一个名为Insert的函数,用于处理HTTP POST请求,并将表单数据插入到数据库中。
在Insert函数中,我们首先使用context.PostForm函数从HTTP POST请求中获取表单数据。然后,使用time.Now函数获取当前时间,并将其转换为Unix时间戳格式。
接下来,我们创建了一个名为u1的Topic实体,并将表单数据和时间戳赋值给它。然后,我们创建了一个名为topicDao的TopicDao实例,并调用它的Insert方法将Topic实体插入到数据库中。
如果插入失败,则返回一个错误,并在响应中返回“发布失败”的消息。如果插入成功,则在响应中返回“发布成功”的消息。
在响应之后,我们调用repository.Init函数重新加载所有的Topic实体。这样可以保证索引数据是最新的,并且可以避免出现数据不一致的情况。
Cotroller
package cotroller
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(),
}
}
pageInfo, err := service.QueryPageInfo(topicId)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: pageInfo,
}
}
这是一个用于查询页面信息的示例代码。在该示例中,定义了一个名为PageData的结构体,它包含了返回给客户端的数据格式。然后,定义了一个名为QueryPageInfo的函数,用于查询页面信息。
在QueryPageInfo函数中,我们首先使用strconv.ParseInt函数将topicIdStr转换为int64类型的topicId。如果转换失败,则返回一个错误响应。
然后,我们调用service.QueryPageInfo函数查询页面信息,并将结果保存到pageInfo变量中。如果查询失败,则返回一个错误响应。
最后,我们创建了一个名为PageData的结构体,并将查询结果保存到它的Data字段中。如果查询成功,则返回一个成功响应,否则返回一个错误响应。