青训营课

96 阅读1分钟

文件结构

│  go.mod
│  go.sum
│  README.md
│  server.go                    # 总程序
├─controller
│      publish_post.go          # 发布post
│      publish_topic.go         # 发布topic
│      query_page_info.go       
├─data
│      post                     # post数据
│      topic                    # topic数据
├─repository
│      db_init.go               # 初始化数据库(这里用文件代替)
│      post.go                  # post数据持久化
│      topic.go                 # topic数据持久化
├─service
│      create_post_test.go
│      create_topic_test.go
│      publish_post.go
│      publish_topic.go
│      query_page_info.go
│      query_page_info_test.go
└─snowflake
        get_snowflake_id.go     # snowflake

repository层

post:在全局加入channel变量:

var (
    postDao     *PostDao
    postOnce    sync.Once
    postNewLine = make(chan string, 3)
)

加入创建post与post数据持久化的函数:

/*repository/post.go*/
func (*PostDao) CreatePost(post *Post) {
    postRwMutex.Lock()
    defer postRwMutex.Unlock()
    postIndexMap[post.Id] = append(postIndexMap[post.ParentId], post)
    go func() {
        defer close(postNewLine)
        lineByte, _ := json.Marshal(post)
        postNewLine <- string(lineByte)
    }()
​
    go func() {
        WritePostFile("../data/")
    }()
}
​
func WritePostFile(filePath string) error {
    for {
        line := <-postNewLine
        open, err := os.OpenFile(filePath+"post", os.O_APPEND|os.O_WRONLY, 0600)
        if err != nil {
            return err
        }
        if _, err := open.WriteString("\n" + line); err != nil {
            open.Close()
            return nil
        }
        open.Close()
    }
}

同理有topic相关函数:

/*repository/topic.go*/
var (
    topicDao     *TopicDao
    topicOnce    sync.Once
    topicNewLine = make(chan string, 3)
)
​
//...func (*TopicDao) CreateTopic(topic *Topic) {
    topicRwMutex.Lock()
    defer topicRwMutex.Unlock()
    topicIndexMap[topic.Id] = topic
    go func() {
        lineByte, _ := json.Marshal(topic)
        topicNewLine <- string(lineByte)
    }()
    go func() {
        WriteTopicFile("../data/")
    }()
}
​
func WriteTopicFile(filePath string) error {
    for {
        line := <-topicNewLine
        open, err := os.OpenFile(filePath+"topic", os.O_APPEND|os.O_WRONLY, 0600)
        if err != nil {
            return err
        }
        if _, err := open.WriteString("\n" + line); err != nil {
            open.Close()
            return nil
        }
        open.Close()
    }
}

service层:

通过调用repository层函数来创建post和topic:

post:

/*service/publish_post.go*/
func CreatePost(postData *PostData) (int64, error) {
    if err := repository.NewTopicDaoInstance().QueryTopicById(postData.ParentId); err == nil {
        return 0, errors.New("parent ID not found")
    }
    post := &repository.Post{
        Id:         GeneratePostId(),
        ParentId:   postData.ParentId,
        Content:    postData.Content,
        CreateTime: time.Now().UnixNano(),
    }
    repository.NewPostDaoInstance().CreatePost(post)
    return post.Id, nil
}
​
func GeneratePostId() int64 {
    return snowflake.GetSnowflakeId()
}

topic:

/*service/publish_topic.go*/
func CreateTopic(topicData *TopicData) (int64, error) {
    topic := &repository.Topic{
        Id:         GenerateTopicId(),
        Title:      topicData.Title,
        Content:    topicData.Content,
        CreateTime: time.Now().UnixNano(),
    }
    repository.NewTopicDaoInstance().CreateTopic(topic)
    return topic.Id, nil
}
​
func GenerateTopicId() int64 {
    return snowflake.GetSnowflakeId()
}

controller层

通过调用service层来创建post和topic:

post:

/*controller/publish_post.go*/
func CreatePost(parentID int64, PostData *PostData) *CreatePostResp {
    info, err := service.CreatePost(
        &service.PostData{
            ParentId: parentID,
            Content:  PostData.Content,
        })
    if err != nil {
        return &CreatePostResp{
            Code: -1,
            Msg:  err.Error(),
        }
    }
    return &CreatePostResp{
        Code: 0,
        Msg:  "success",
        Data: info,
    }
}

topic:

/*controller/publish_topic.go*/
func CreateTopic(topicData *TopicData) *TopicCreateResp {
    info, err := service.CreateTopic(
        &service.TopicData{
            Title:   topicData.Title,
            Content: topicData.Content,
        })
    if err != nil {
        return &TopicCreateResp{
            Code: -1,
            Msg:  err.Error(),
        }
    }
    return &TopicCreateResp{
        Code: 0,
        Msg:  "success",
        Data: info,
    }
}

snowflake

/*snowflake/get_snowflake_id.go*/
var (
    machineID     int64
    cnt           int64
    lastTimeStamp int64
)
​
func GetSnowflakeId() int64 {
    curTimeStamp := time.Now().UnixNano() / 1000000
    if curTimeStamp == lastTimeStamp {
        cnt++
        if cnt > 4095 {
            time.Sleep(time.Millisecond)
            curTimeStamp = time.Now().UnixNano() / 1000000
            lastTimeStamp = curTimeStamp
            cnt = 0
        }
        rightBinValue := curTimeStamp & 0x1FFFFFFFFFF
        rightBinValue <<= 22
        id := rightBinValue | machineID | cnt
        return id
    } else {
        cnt = 0
        lastTimeStamp = curTimeStamp
        rightBinValue := curTimeStamp & 0x1FFFFFFFFFF
        rightBinValue <<= 22
        id := rightBinValue | machineID | cnt
        return id
    }
}

路由加入

/*server.go*/
r.POST("/community/page/post", func(c *gin.Context) {
        var topicData controller.TopicData
        err := c.ShouldBindJSON(&topicData)
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "post topic failed"})
        }
        data := controller.CreateTopic(&topicData)
        c.JSON(http.StatusOK, data)
​
    })
​
    r.POST("/community/page/post/:id", func(c *gin.Context) {
        parentID, err := strconv.ParseInt(c.Param("id"), 10, 64)
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "post ID not a number"})
            return
        }
        var postData controller.PostData
        if err = c.ShouldBindJSON(&postData); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "post ID load failed"})
            return
        }
        data := controller.CreatePost(parentID, &postData)
        c.JSON(http.StatusOK, data)
    })

测试

先运行server.go对数据表进行初始化,然后进行如下测试:

post创建测试:

func TestCreatePost(t *testing.T) {
    postData := &PostData{
        ParentId: 1,
        Content:  "test",
    }
    postId, _ := CreatePost(postData)
    time.Sleep(1)
    assert.NotEqual(t, 0, postId)
    info, _ := QueryPageInfo(postId)
    flag := false
    for _, post := range info.PostList {
        if post.Id == postId && post.Content == postData.Content {
            flag = true
            break
        }
    }
    assert.Equal(t, true, flag)
}

结果:

image-20220510104153117.png

经过多轮测试,其生成数组在post文件中如下:

image-20220510104331538.png

topic创建测试:

func TestCreateTopic(t *testing.T) {
    topicData := &TopicData{
        Title:   "test",
        Content: "test",
    }
    topicId, _ := CreateTopic(topicData)
    assert.NotEqual(t, 0, topicId)
    info, _ := QueryPageInfo(topicId)
    assert.Equal(t, topicId, info.Topic.Id)
    assert.Equal(t, topicData.Title, info.Topic.Title)
    assert.Equal(t, topicData.Content, info.Topic.Content)
}

结果:

image-20220510104225388.png

经过多轮测试其生成结果如下:

image-20220510104406433.png