文件结构
│ 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)
}
结果:
经过多轮测试,其生成数组在post文件中如下:
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)
}
结果:
经过多轮测试其生成结果如下: