这是我参与「第三届青训营 -后端场」笔记创作活动的的第二篇笔记
- go提倡通信来实现贡献内存,而不是共享内存实现通信
- 锁与WaitGroup
1. 版本管理 |Go mod
推荐看极客兔兔,简单易懂。
Go Path的弊端,Go Vender的弊端
Go get的使用 | 重要
2. 单元测试(自己写着玩不咋用,以后得好好用) | Mock打桩
3. 项目实战 | 仅仅是用户浏览页面
4. 课后作业
1. 发布帖子 | 浅写了一下前端
- 浏览器先访问/index 进行访问主页
- 主页会从服务端返回数据库中所有的主题(id,以及主题详情)
- html渲染后返回给浏览器显示出主题内容
- 之后浏览器可以通过主题ID定位到相关主题,并插入对应的贴子详情
- 这样就完成的发布贴子
2. 本地ID生成需要唯一
- 考虑到并发性,多个浏览器同时发布贴子,那么可能导致贴子ID有重复,那么就需要进行并发控制
- 我选择的是原子操作,当然也可以通过读写锁控制
- 首先主题ID,其实不需要实现原子性,因为我没有实现发布主题,但是这里也使用了原子操作
- 然后是贴子ID,具体看下面代码
// 所有主题下帖子数目
var PostSum = AtomicPostSum{
cont: 0,
}
func (a * AtomicPostSum) IncP() int64 {
atomic.AddInt64( & a.cont, 1)
return a.cont
}
func (a * AtomicPostSum) LoadP() int64 {
return a.cont
}
---------------------------------------------------------
// 所有主题数目
var TopicSum = AtomicTopicSum{
cont: 0,
}
func (a * AtomicTopicSum) IncT() int64 {
atomic.AddInt64( & a.cont, 1)
return a.cont
}
func (a * AtomicTopicSum) LoadT() int64 {
return a.cont
}
3. Append文件
C++选手DNA动了, 和linux环境操作文件类似,打开文件时增加os.O_APPEND的flag实现Append
open, _ := os.OpenFile(filePath + "post", os.O_RDWR | os.O_APPEND, 0666)
4. Map的并发问题 | 挺重要的~
经典问题,高并发下Map是非并发安全的,可以使用sync.Map但不是必须同样也可以通过加锁实现
先看下实现吧~
//db_init.go
var (
TopicIndexMap sync.Map
PostIndexMap sync.Map
)
........
for k, v := range topicTmpMap {
TopicIndexMap.Store(k, v)
}
........
for k, v := range postTmpMap {
PostIndexMap.Store(k, v)
}
从文件加载到内存时,主题索引(这里的所说的索引就是sync.Map)以及帖子索引完成构建
// 注册路由,处理前端提交的表单
userGroup.POST("/post", func(c * gin.Context) {
topic := c.PostForm("topic")
userCont := c.PostForm("userCont")
topicId, _ := strconv.ParseInt(topic, 10, 64)
if topicId > repository.TopicSum.LoadT() {
//不太清楚logger是不是这样用的 sad
gin.LoggerWithWriter(os.Stdout, "Not Found TopicId")
return
}
id, err := repository.TopicIndexMap.Load(topicId)
if ! err {
//不太清楚是不是这样用的 sad
gin.LoggerWithWriter(os.Stdout, "Not Found TopicId")
}
userTmp := & repository.Post{
Id: repository.PostSum.IncP(),
ParentId: int64(topicId),
Content: userCont,
CreateTime: time.Now().Unix(),
}
jsonData, _ := json.Marshal(userTmp)
repository.TopicIndexMap.Store(id, jsonData)
filePath := "./data/"
open, _ := os.OpenFile(filePath + "post", os.O_RDWR | os.O_APPEND, 0666)
open.Write([]byte("\n"))
open.Write(jsonData)
})
5. 其他的一些细节
由于使用了sync.Map,通过key取出来的value是空接口类型,那么就需要断言进行类型转换,这里实现的比较简单,没有捕捉panic,如果查询的parentId不存在,那么返回的v就是空接口类型,断言错了,服务端直接寄了,那么就简单在注册路由的时候控制一下parentId(也就是主题ID的大小)。
//同样查询TopicID的时候同样会有这个问题就不再贴代码啦
//post.go
func ( * PostDao) QueryPostsByParentId(parentId int64) [] * Post {
//return PostIndexMap[parentId]
v, _ := PostIndexMap.Load(parentId)
ret := v.([] * Post)
return ret
}
小记一波:这次作业花费了我不少精力,主要是在go mod的使用上,乱七八糟的go get,一通折腾之后总算会用了,再就是最后的这个细节比较重要,另外自己动手浅写了一下前端,端口号搞错了post不了表单,仔细想了一下,服务端都没打印出来请求POST请求,即使不提供服务也应该有反应才对。
还使用了gin的路由组,tmpl渲染这些小知识,继续进步吧!