Go语言工程进阶课后实践|青训营

112 阅读7分钟

需求

  1. 支持对话题发布回帖。
  2. 回帖id生成需要保证不重复、唯一性。
  3. 新加回帖追加到本地文件,同时需要更新索引,注意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

路径记录

Screenshot 2023-08-13 at 16.37.28.png

实现思路

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字段中。如果查询成功,则返回一个成功响应,否则返回一个错误响应。