Go语言工程实践 - 项目课后实践笔记 | 豆包MarsCode AI刷题

96 阅读2分钟

课后实践要求

  1. 支持发布帖子
  2. 本地ID生成不重复,保证唯一
  3. Append文件,更新索引,注意map并发安全问题

实现笔记

由于另外两个需求都依赖于索引,所以我们从第三点的索引开始实现。

首先解决map并发安全问题,已知有如下两种解决方法:

  • 对原有map加读写锁
  • 使用sync.Map代替map

这里使用第二种方法,对原有initTopicIndexMapinitPostIndexMap函数进行修改:

  1. 定义部分:
var (
    topicIndexMap sync.Map
    postIndexMap  sync.Map
)
  1. 函数实现

这里实现上需要修改的只有两个函数里的如下部分:

func initXxxxIndexMap(filePath string) error {
    ...
    xxxxTmpMap := make(map[int64]*Xxxx)
    for scanner.Scan() {
        ...
        xxxxTmpMap[xxxx.Id] = &xxxx
    }
    xxxxIndexMap = xxxxTmpMap
    ...
}

改为如下即可:

func initXxxxIndexMap(filePath string) error {
    ...
    xxxxIndexMap.Clear()
    for scanner.Scan() {
        ...
        xxxxIndexMap.Store(xxxx.Id, &xxxx)
    }
    ...
}
  1. 修改TopicDaoPostDao下相关使用
func (*TopicDao) QueryTopicById(id int64) *Topic {
    if topic, ok := topicIndexMap.Load(id); ok {
       return topic.(*Topic)
    }
    return nil
}
func (*PostDao) QueryPostByTopicId(id int64) []*Post {
    var postList []*Post
    postIndexMap.Range(func(k, v interface{}) bool {
       post := v.(*Post)
       if post.TopicId == id {
          postList = append(postList, post)
       }
       return true
    })
    return postList
}

至此第三点就已经解决了,然后考虑ID生成。

这里使用一种简单的很原始的方法(bushi):

  • 首先可以想到一种简单的不考虑唯一性的做法:生成一个固定位数的随机数
  • 然后我们已知topicIndexMap和postIndexMap的结构都为 ID -> Entry
  • 考虑利用topicIndexMap和postIndexMap保证唯一性

然后可以写出以下睿智代码:

func GenerateID(ids *sync.Map) int64 {
    for {
       rd := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
       id := rd.Int63n(90000000) + 10000000
       if _, ok := ids.Load(id); !ok {
          return id
       }
    }
}

最后我们实现帖子发布,这里主要对repository层讨论(文件操作部分)

和构建索引部分读文件流程基本一致,区别在于我们写操作应该是追加操作。

这里使用 os.OpenFile,并为其设置 os.O_APPEND flag即可。

如下:

open, err := os.OpenFile(topicFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)

整个流程即为:生成ID -> 设置发布时间戳 -> 反序列化 -> 追加文件 -> 更新索引 -> 关闭文件。

完整代码如下:

func (*TopicDao) CreateNewTopic(topic *Topic) error {
    topic.Id = utils.GenerateID(&topicIndexMap)
    topic.CreateTime = time.Now().UnixNano()
    jsonBytes, err := json.Marshal(&topic)
    if err != nil {
       return err
    }
    jsonStr := string(jsonBytes) + "\n"
    open, err := os.OpenFile(topicFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
    if err != nil {
       return err
    }
    defer func(open *os.File) {
       err := open.Close()
       if err != nil {
          return
       }
    }(open)
    if _, err := open.WriteString(jsonStr); err != nil {
       return err
    }
    postIndexMap.Store(topic.Id, topic)
    return nil
}

新建post同理,代码基本一致。

然后在service层添加对传入参数的校验检查,controller层包装一下返回数据,然后暴露http接口即可。

r.POST("/community/topic/create", func(c *gin.Context) {
    var jsonData controller.TopicRequest
    if err := c.ShouldBind(&jsonData); err != nil {
       c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
    data := controller.CreateTopic(jsonData)
    c.JSON(http.StatusOK, data)
})

需要注意的是post的service层参数校验时应该检查TopicId是否合法(帖子的存在性)。