项目需求
- 展示话题(标题,文字描述)和回帖列表;
- 暂不考虑前端页面实现,仅仅实现一个本地 web 服务;
- 话题和回帖数据用文件存储。
分层结构
- 主要分为以下几层:
- 数据层 (repository):数据 Model,外部数据的增删改查;
- 逻辑层 (service):业务 Entity,处理核心业务逻辑输出;
- 视图层 (controller):视图 view,处理和外部的交互逻辑。
组件
- gorm:用于操作数据库的CRUD
- gin:负责网络部分的路由,网络信息的发送和接收
项目搭建
-
在项目构建之初,我们用 go mod init 进行初始化,然后通过 go get gopkg.in/gin-gonic/g… 来远程拉取代码包及其依赖包,并自动完成编译和安装。
-
根据之前的需求分析对数据层 Repository 的数据库部分进行代码编写,应该实现两个基本的查询操作:
- 根据话题 Id 查询话题:QueryTopicById
type Topic struct {
Id int64 `gorm:"column:id"`
UserId int64 `gorm:"column:user_id"`
Title string `gorm:"column:title"`
Content string `gorm:"column:content"`
CreateTime time.Time `gorm:"column:create_time"`
}
func (Topic) TableName() string {
return "topic"
}
type TopicDao struct {
}
var topicDao *TopicDao
var topicOnce sync.Once
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
func (*TopicDao) QueryTopicById(id int64) (*Topic, error) {
var topic Topic
err := db.Where("id = ?", id).Find(&topic).Error
if err != nil {
util.Logger.Error("find topic by id err:" + err.Error())
return nil, err
}
return &topic, nil
}
* 根据话题 Id 查询所有帖子数据:QueryPostByParentId
type Post struct {
Id int64 `gorm:"column:id"`
ParentId int64 `gorm:"parent_id"`
UserId int64 `gorm:"column:user_id"`
Content string `gorm:"column:content"`
DiggCount int32 `gorm:"column:digg_count"`
CreateTime time.Time `gorm:"column:create_time"`
}
func (Post) TableName() string {
return "post"
}
type PostDao struct {
}
var postDao *PostDao
var postOnce sync.Once
func NewPostDaoInstance() *PostDao {
postOnce.Do(
func() {
postDao = &PostDao{}
})
return postDao
}
func (*PostDao) QueryPostById(id int64) (*Post, error) {
var post Post
err := db.Where("id = ?", id).Find(&post).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
if err != nil {
util.Logger.Error("find post by id err:" + err.Error())
return nil, err
}
return &post, nil
}
func (*PostDao) QueryPostByParentId(parentId int64) ([]*Post, error) {
var posts []*Post
err := db.Where("parent_id = ?", parentId).Find(&posts).Error
if err != nil {
util.Logger.Error("find posts by parent_id err:" + err.Error())
return nil, err
}
return posts, nil
}
func (*PostDao) CreatePost(post *Post) error {
if err := db.Create(post).Error; err != nil {
util.Logger.Error("insert post err:" + err.Error())
return err
}
return nil
}
- 在数据库中查询到数据后,将其传输到逻辑层 Service 进行处理:参数校验 checkParam -> 准备数据 prepareInfo -> 组装实体 packPageInfo ;
- query_page
type TopicInfo struct {
Topic *repository.Topic
User *repository.User
}
type PostInfo struct {
Post *repository.Post
User *repository.User
}
type PageInfo struct {
TopicInfo *TopicInfo
PostList []*PostInfo
}
func QueryPageInfo(topicId int64) (*PageInfo, error) {
return NewQueryPageInfoFlow(topicId).Do()
}
func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicId: topId,
}
}
type QueryPageInfoFlow struct {
topicId int64
pageInfo *PageInfo
topic *repository.Topic
posts []*repository.Post
userMap map[int64]*repository.User
}
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
if err := f.checkParam(); err != nil {
return nil, err
}
if err := f.prepareInfo(); err != nil {
return nil, err
}
if err := f.packPageInfo(); err != nil {
return nil, err
}
return f.pageInfo, nil
}
func (f *QueryPageInfoFlow) checkParam() error {
if f.topicId <= 0 {
return errors.New("topic id must be larger than 0")
}
return nil
}
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
var topicErr, postErr error
go func() {
defer wg.Done()
topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
if err != nil {
topicErr = err
return
}
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId)
if err != nil {
postErr = err
return
}
f.posts = posts
}()
wg.Wait()
if topicErr != nil {
return topicErr
}
if postErr != nil {
return postErr
}
//获取用户信息
uids := []int64{f.topic.Id}
for _, post := range f.posts {
uids = append(uids, post.Id)
}
userMap, err := repository.NewUserDaoInstance().MQueryUserById(uids)
if err != nil {
return err
}
f.userMap = userMap
return nil
}
func (f *QueryPageInfoFlow) packPageInfo() error {
//topic info
userMap := f.userMap
topicUser, ok := userMap[f.topic.UserId]
if !ok {
return errors.New("has no topic user info")
}
//post list
postList := make([]*PostInfo, 0)
for _, post := range f.posts {
postUser, ok := userMap[post.UserId]
if !ok {
return errors.New("has no post user info for " + fmt.Sprint(post.UserId))
}
postList = append(postList, &PostInfo{
Post: post,
User: postUser,
})
}
f.pageInfo = &PageInfo{
TopicInfo: &TopicInfo{
Topic: f.topic,
User: topicUser,
},
PostList: postList,
}
return nil
}
- publish_post
func PublishPost(topicId, userId int64, content string) (int64, error) {
return NewPublishPostFlow(topicId, userId, content).Do()
}
func NewPublishPostFlow(topicId, userId int64, content string) *PublishPostFlow {
return &PublishPostFlow{
userId: userId,
content: content,
topicId: topicId,
}
}
type PublishPostFlow struct {
userId int64
content string
topicId int64
postId int64
}
func (f *PublishPostFlow) Do() (int64, error) {
if err := f.checkParam(); err != nil {
return 0, err
}
if err := f.publish(); err != nil {
return 0, err
}
return f.postId, nil
}
func (f *PublishPostFlow) checkParam() error {
if f.userId <= 0 {
return errors.New("userId id must be larger than 0")
}
if utf8.RuneCountInString(f.content) >= 500 {
return errors.New("content length must be less than 500")
}
return nil
}
func (f *PublishPostFlow) publish() error {
post := &repository.Post{
ParentId: f.topicId,
UserId: f.userId,
Content: f.content,
CreateTime: time.Now(),
}
if err := repository.NewPostDaoInstance().CreatePost(post); err != nil {
return err
}
f.postId = post.Id
return nil
}