(go + react) 博客项目(二)

前言

 本文内容是博客项目的后端代码逻辑的编写与实现,不了解的小伙伴可以去看前期的项目准备部分——(go + react) 博客项目(一)。之前提到我们已经通过gf框架的代码生成工具生成了接口,现在我们可以正式开始后端逻辑的编写。

 关于框架说明:按照框架的设计思想,接口的具体实现是在/internal/controller包中,service层的接口声明在/internal/service包,service的具体实现代码存放在/internal/logic包中。

service层代码

文章 article

 在/internal/controller/article包中,gf框架已经生成好了接口,只需要我们去实现了,可以注意到我们之前在api层定义的文件,也都在这里面有了对应的文件,具体的代码就在这些文件中编写
请添加图片描述
 但是我们现在不关注controller中的内容,我们的工作从service层开始:在/internal/logic包中创建article文件夹,并创建"article.go"文件,声明出我们的articleService的结构体,后面文章相关的service函数都要声明为这个结构体的属性。

type sArticleService struct{}

创建文章

 创建文章的数据校验比较简单,作者、标题、内容不为空就可以写入到数据库了,然后再把id拿出来返回。主要处理逻辑还是要放在logic层,在/internal/logic/article包中创建"create_article.go"文件,进行代码编写。

func (s *sArticleService) CreateArticle(ctx context.Context, title string, content string, authorId int) (id int, err error) {
	// 判断标题和内容是否为空
	if err = s.checkTitleAndContent(title, content); err != nil {
		return -1, err
	}
	// 判断作者是否存在
	if err = s.checkUserExist(ctx, authorId); err != nil {
		return -1, err
	}
	// 创建文章, 返回文章ID
	aid, err := dao.LandcBlogArticle.Ctx(ctx).InsertAndGetId(&entity.LandcBlogArticle{
		Title:    title,
		Content:  content,
		AuthorId: authorId,
	})
	if err != nil {
		return -1, gerror.New("create article failed")
	}
	return int(aid), nil
}

func (s *sArticleService) checkTitleAndContent(title string, content string) error {
	if gstr.Trim(title) == "" {
		return gerror.New("title is empty")
	}
	if gstr.Trim(content) == "" {
		return gerror.New("content is empty")
	}
	return nil
}

func (s *sArticleService) checkUserExist(ctx context.Context, userId int) error {
	count, err := dao.LandcBlogUser.Ctx(ctx).Where("id = ?", userId).Count()
	if err != nil {
		return err
	}
	if count == 0 {
		return gerror.New("user not exist")
	}
	return nil
}

注意:其中形参中ctx的类型是Contenxt,可能存在多个包,我们选context包:import context

删除文章

 删除文章需要校验文章是否存在,用户是否存在,以及文章是否为用户所编写的内容,成功则返回true,失败返回false并说明原因。在/internal/logic/article包中创建"delete_article.go"文件,进行代码编写。

func (s *sArticleService) DeleteArticle(ctx context.Context, id int, authorId int) (success bool, info string, err error) {
	// 判断文章是否存在
	if info, err = s.checkArticleExist(ctx, id); err != nil || info != "" {
		return false, "", err
	}
	// 判断作者是否存在
	if err = s.checkUserExist(ctx, authorId); err != nil {
		return false, "", err
	}
	// 删除文章
	result, err := dao.LandcBlogArticle.Ctx(ctx).Where("id = ?", id).Where("author_id = ?", authorId).Delete()
	if err != nil {
		return false, "", err
	}
	// 根据影响行数判断是否成功
	affected, err := result.RowsAffected()
	if err != nil {
		return false, "", err
	}
	// 如果影响行数是0,说明数据库中没有对应的数据
	if affected == 0 {
		return false, "无权限删除文章", nil
	}

	return true, "", nil
}

func (s *sArticleService) checkArticleExist(ctx context.Context, id int) (info string, err error) {
	count, err := dao.LandcBlogArticle.Ctx(ctx).Where("id = ?", id).Count()
	if err != nil {
		return "", err
	}
	if count == 0 {
		return "article not exist", nil
	}
	return "", nil
}

修改文章

 修改文章的校验内容比较多,文章是否存在,作者是否存在,文章是否为作者所写,文章的标题和内容是否为空。在/internal/logic/article包中创建"modify_article.go"文件,进行代码编写。

func (s *sArticleService) ModifyArticle(ctx context.Context, id int, title string, content string, authorId int) (success bool, info string, err error) {

	// 判断标题和内容是否为空
	if err = s.checkTitleAndContent(title, content); err != nil {
		return false, "", err
	}
	// 判断作者是否存在
	if err = s.checkUserExist(ctx, authorId); err != nil {
		return false, "", err
	}
	// 判断文章是否存在
	if info, err = s.checkArticleExist(ctx, authorId); err != nil || info != "" {
		return false, info, err
	}
	// 修改文章,根据影响行数判断文章和作者是否匹配
	affected, err := dao.LandcBlogArticle.Ctx(ctx).UpdateAndGetAffected(&entity.LandcBlogArticle{
		Id:       id,
		Title:    title,
		Content:  content,
		AuthorId: authorId,
	}, "id = ?", id)
	if err != nil {
		return false, "", err
	}
	if affected <= 0 {
		return false, "无权限修改文章", nil
	}
	return true, "", nil
}

查询文章内容

 查询文章内容简单一些,只需要校验文章是否存在即可,由于我们还需要向前端返回用户信息,因此service层除了查询文章,还需要查询用户信息。在/internal/logic/article包中创建"describe_article.go"文件,进行代码编写。

func (s *sArticleService) DescribeArticle(ctx context.Context, id int) (article *entity.LandcBlogArticle, author *entity.LandcBlogUser, err error) {
	// 判断文章是否存在
	if info, err := s.checkArticleExist(ctx, id); err != nil || info != "" {
		if err != nil {
			return nil, nil, err
		}
		return nil, nil, gerror.New(info)
	}
	err = dao.LandcBlogArticle.Ctx(ctx).Where("id = ?", id).Scan(&article)
	if err != nil {
		return nil, nil, err
	}
	err = dao.LandcBlogUser.Ctx(ctx).Where("id = ?", article.AuthorId).Scan(&author)
	if err != nil {
		return nil, nil, err
	}
	return article, author, nil
}

查询文章列表

 由于查询文章列表的复用,我们需要判断用户id的合法性,以及对searchKey做模糊查询,在/internal/logic/article包中创建"describe_article_list.go"文件,进行代码编写。

func (s *sArticleService) DescribeArticleList(ctx context.Context, id int, searchKey string) (articleAndAuthor *gmap.AnyAnyMap, err error) {
	articleOrm := dao.LandcBlogArticle.Ctx(ctx)
	var author *entity.LandcBlogUser
	// 判断用户id的合法性
	if id > 0 {
		record, err := dao.LandcBlogUser.Ctx(ctx).Where("id = ?", id).One()
		if err != nil {
			return nil, err
		}
		if record == nil {
			return nil, gerror.New("用户不存在")
		}
		err = record.Struct(author)
		if err != nil {
			return nil, err
		}
		articleOrm = articleOrm.Where("author_id = ?", id)
	}
	if searchKey != "" {
		articleOrm = articleOrm.Where("title like ?", "%"+searchKey+"%")
		articleOrm = articleOrm.WhereOr("content like ?", "%"+searchKey+"%")
	}
	articleOrm = articleOrm.Order("id desc")
	articleResult, err := articleOrm.All()
	if err != nil {
		return nil, err
	}
	articleAndAuthor = gmap.NewAnyAnyMap()
	for _, v := range articleResult {
		var article *entity.LandcBlogArticle
		err := v.Struct(article)
		if err != nil {
			return nil, err
		}
		if id > 0 {
			articleAndAuthor.Set(&article, &author)
		} else {
			err = dao.LandcBlogUser.Ctx(ctx).Where("id = ?", article.AuthorId).Scan(author)
			if err != nil {
				return nil, err
			}
			articleAndAuthor.Set(&article, &author)
		}
	}
	return articleAndAuthor, nil
}

用户 user

 像之前一样,在/internal/logic包下面创建"user"包,再在"user"下面创建"user.go",然后声明好user的service结构体。再次声明,用户模块的实现极为简单,仅做练习使用。

type sUserService struct{}

创建用户

 创建用户的检查和创建文章的检查大致相同,检查用户名和密码是否为空,用户还要多加一条用户名唯一性的检验,在/internal/logic/user文件夹下创建"create_user.go"文件。

func (s *sUserService) CreateUser(ctx context.Context, name string, password string) (id int, err error) {
	if err := s.checkNameAndPassword(name, password); err != nil {
		return -1, err
	}
	if err := s.checkNameExist(ctx, name); err != nil {
		return -1, err
	}
	uid, err := dao.LandcBlogUser.Ctx(ctx).InsertAndGetId(&entity.LandcBlogUser{
		Name:     name,
		Password: password,
	})
	if err != nil {
		return -1, err
	}
	return int(uid), nil
}
func (s *sUserService) checkNameAndPassword(name string, password string) error {
	if gstr.Trim(name) == "" {
		return gerror.New("name is empty")
	}
	if gstr.Trim(password) == "" {
		return gerror.New("password is empty")
	}
	return nil
}
func (s *sUserService) checkNameExist(ctx context.Context, name string) error {
	if count, err := dao.LandcBlogUser.Ctx(ctx).Where("name = ?", name).Count(); err != nil || count > 0 {
		if err != nil {
			return err
		}
		return gerror.New("name already exist")
	}
	return nil
}

删除用户

 删除用户简单实现就可以了,判断用户id是否存在,存在即可删除。在/internal/logic/user文件夹下创建"delete_user.go"文件。

func (s *sUserService) DeleteUser(ctx context.Context, id int) (success bool,info string, err error) {
	result, err := dao.LandcBlogUser.Ctx(ctx).Where("id = ?", id).Delete()
	if err != nil {
		return false, "", err
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return false, "", err
	}
	if affected == 0 {
		return false, "用户不存在", nil
	}
	return true, "", nil
}

修改用户

 修改用户需要校验用户id是否存在,以及用户名是是否重复。在/internal/logic/user文件夹下创建"modify_user.go"文件。

func (s *sUserService) ModifyUser(ctx context.Context, id int, name string, password string) (success bool, info string, err error) {
	if err := s.checkNameAndPassword(name, password); err != nil {
		return false, "", err
	}
	if err := s.checkNameExist(ctx, name); err != nil {
		var tmpId int32
		one, tempErr := dao.LandcBlogUser.Ctx(ctx).Where("name = ?", name).Fields("id").One()
		if tempErr != nil {
			return false, "", tempErr
		}
		tmpId = one.GMap().Get("id").(int32)
		if int(tmpId) != id {
			return false, "", err
		}
	}
	affected, err := dao.LandcBlogUser.Ctx(ctx).UpdateAndGetAffected(&entity.LandcBlogUser{Name: name, Password: password}, "id = ?", id)
	if err != nil {
		return false, "", err
	}
	if affected == 0 {
		return false, "用户不存在", nil
	}
	return true, "", nil
}

查询/登录

 使用用户名和密码查询出用户id,返回回去,如果查询不到就是用户名或密码出现错误。在/internal/logic/user文件夹下创建"describe_user.go"文件。

func (s *sUserService) DescribeUser(ctx context.Context, name string, password string) (id int, err error) {
	user := &entity.LandcBlogUser{}
	err = dao.LandcBlogUser.Ctx(ctx).Where("name = ?", name).Where("password = ?", password).Scan(user)
	if err != nil {
		return 0, gerror.New("用户名或密码错误")
	}
	return user.Id, nil
}

评论 comment

 在/internal/logic包下面创建"comment"包,再在"comment"下面创建"comment.go",然后声明好comment的service结构体。

type sCommentService struct {}

创建评论

 创建评论需要检验用户以及文章的合法性,除此之外还要注意parentId的合法性,如果parentId不存在或者两个文章id不同,是不允许创建的,再就是comment的内容不可以为空。在/internal/logic/comment文件夹下创建"create_comment.go"文件。

func (s *sCommentService) CreateComment(ctx context.Context, articleId int, authorId int, content string, parentId int) (id int, err error) {
	count, err := dao.LandcBlogArticle.Ctx(ctx).Where("id = ?", articleId).Count()
	if err != nil {
		return 0, err
	}
	if count == 0 {
		return 0, gerror.New("article not found")
	}
	count, err = dao.LandcBlogUser.Ctx(ctx).Where("id = ?", authorId).Count()
	if err != nil {
		return 0, err
	}
	if count == 0 {
		return 0, gerror.New("user not found")
	}
	if parentId > 0 {
		var parent = &entity.LandcBlogComment{}
		err := dao.LandcBlogComment.Ctx(ctx).Where("id = ?", parentId).Scan(parent)
		if err != nil {
			return 0, err
		}
		if parent == nil {
			return 0, gerror.New("parent comment not found")
		}
		if parent.ArticleId != articleId {
			return 0, gerror.New("parent comment not belong to article")
		}
	}
	if gstr.Trim(content) == "" {
		return 0, gerror.New("content is empty")
	}
	insertId, err := dao.LandcBlogComment.Ctx(ctx).InsertAndGetId(entity.LandcBlogComment{
		ArticleId: articleId,
		AuthorId:  authorId,
		Content:   content,
		ParentId:  parentId,
	})
	if err != nil {
		return 0, err
	}
	return int(insertId), nil
}

删除评论

 删除评论没有什么好说的,和之前的删除逻辑基本一致。在/internal/logic/comment文件夹下创建"delete_comment.go"文件。

func (s *sCommentService) DeleteComment(ctx context.Context, id int, authorId int) (success bool, info string, err error) {
	result, err := dao.LandcBlogComment.Ctx(ctx).Where("id = ? and author_id = ?", id, authorId).Delete()
	if err != nil {
		return false, "", err
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return false, "", err
	}
	if affected == 0 {
		return false, "无权限删除", nil
	}
	return true, "", nil
}

获取评论列表

 在获取评论列表的时候就比较复杂了,检查文章存不存在,然后就是关系的组装,创建一个list用来存放文章的评论,再便利文章下所有的评论,根据parentId找到父评论添加到children列表中,为了方便寻找添加了一个map。在/internal/logic/comment文件夹下创建"describe_comment_list.go"文件。

func (s *sCommentService) DescribeCommentList(ctx context.Context, aid int) (commentList []v1.CommentItem, err error) {
	count, err := dao.LandcBlogArticle.Ctx(ctx).Where("id=?", aid).Count()
	if err != nil {
		return nil, err
	}
	if count == 0 {
		return nil, gerror.New("文章不存在")
	}
	array, err := dao.LandcBlogComment.Ctx(ctx).Where("article_id=?", aid).OrderAsc("id").All()
	if err != nil {
		return nil, err
	}

	commentList = make([]v1.CommentItem, 0)
	commentMap := gmap.NewIntAnyMap()
	for _, v := range array {
		var (
			ormItem = &entity.LandcBlogComment{}
			author  = &entity.LandcBlogUser{}
			item    v1.CommentItem
			itemPtr *v1.CommentItem
		)
		err := v.Struct(ormItem)

		if err != nil {
			return nil, err
		}

		err = dao.LandcBlogUser.Ctx(ctx).Where("id=?", ormItem.AuthorId).Scan(&author)
		if err != nil || author == nil {
			continue
		}
		item.Id = ormItem.Id
		item.AuthorName = author.Name
		item.Content = ormItem.Content
		item.AuthorId = author.Id
		item.ChildrenList = make([]v1.CommentItem, 0)
		if ormItem.ParentId <= 0 {
			commentList = append(commentList, item)
			itemPtr = &(commentList[len(commentList)-1])
		} else {
			value := commentMap.Get(ormItem.ParentId)
			if value == nil {
				continue
			}
			commentItem := value.(*v1.CommentItem)
			commentItem.ChildrenList = append(commentItem.ChildrenList, item)
			item = commentList[len(commentItem.ChildrenList)-1]
		}
		commentMap.Set(ormItem.Id, itemPtr)
	}
	return commentList, nil
}

生成service接口

 到此,所有的logic层代码都变写完了,现在需要声明接口,可以通过gf的代码生成工具实现,在项目文件夹中输入以下命令,就可以在service包中生成接口。

gf gen service

请添加图片描述
 生成好service层接口之后,需要将logic层的实现注册进去,这样才能在controller层调用,在"/internal/logic/article/article.go"、"/internal/logic/user/user.go"、"/internal/logic/comment/comment.go"这三个文件中分别加入以下代码:

// article.go
func init() {
	service.RegisterArticleService(&sArticleService{})
}
// user.go
func init() {
	service.RegisterUserService(&sUserService{})
}
//comment.go
func init() {
	service.RegisterCommentService(&sCommentService{})
}

这样,所有service层的代码算是全部结束了,可以开始controller层的代码了

controller层代码

 controller的代码其实比较简单了,就只是调用service层代码然后将返回值封装成res就可以了,进入/internal/controller包,完成每个函数的实现,在这里不进行具体解释了,只贴出代码。

文章

article_v1_create_article.go

func (c *ControllerV1) CreateArticle(ctx context.Context, req *v1.CreateArticleReq) (res *v1.CreateArticleRes, err error) {
	id, err := service.ArticleService().CreateArticle(ctx, req.Title, req.Content, req.AuthorId)
	if err != nil {
		return nil, err
	}
	return &v1.CreateArticleRes{Id: id}, nil
}

article_v1_delete_article.go

func (c *ControllerV1) DeleteArticle(ctx context.Context, req *v1.DeleteArticleReq) (res *v1.DeleteArticleRes, err error) {
	success, info, err := service.ArticleService().DeleteArticle(ctx, req.Id, req.AuthorId)
	if err != nil {
		info = err.Error()
	}
	res = &v1.DeleteArticleRes{
		Success: success,
		Info:    info,
	}
	return
}

article_v1_describe_article.go

func (c *ControllerV1) DescribeArticle(ctx context.Context, req *v1.DescribeArticleReq) (res *v1.DescribeArticleRes, err error) {
	article, author, err := service.ArticleService().DescribeArticle(ctx, req.Id)
	if err != nil {
		return nil, err
	}
	res = &v1.DescribeArticleRes{
		Id:         article.Id,
		Title:      article.Title,
		Content:    article.Content,
		AuthorId:   author.Id,
		AuthorName: author.Name,
	}
	return res, nil
}

article_v1_describe_article_list.go

func (c *ControllerV1) DescribeArticleList(ctx context.Context, req *v1.DescribeArticleListReq) (res *v1.DescribeArticleListRes, err error) {
	articleAndAuthor, err := service.ArticleService().DescribeArticleList(ctx, req.Id, req.SearchKey)
	if err != nil {
		return nil, err
	}
	res = &v1.DescribeArticleListRes{}
	res.ArticleList = make([]v1.ArticleListItem, articleAndAuthor.Size())
	for article, author := range articleAndAuthor.Map() {
		blogArticle := *(article.(**entity.LandcBlogArticle))
		user := *(author.(**entity.LandcBlogUser))
		res.ArticleList = append(res.ArticleList, v1.ArticleListItem{
			Id:         blogArticle.Id,
			Title:      blogArticle.Title,
			AuthorName: user.Name,
		})
	}
	return
}

article_v1_modify_article.go

func (c *ControllerV1) ModifyArticle(ctx context.Context, req *v1.ModifyArticleReq) (res *v1.ModifyArticleRes, err error) {
	success, info, err := service.ArticleService().ModifyArticle(ctx, req.Id, req.Title, req.Content, req.AuthorId)
	if err != nil {
		info = err.Error()
	}
	res = &v1.ModifyArticleRes{
		Success: success,
		Info:    info,
	}
	return
}

用户

user_v1_create_user.go

func (c *ControllerV1) CreateUser(ctx context.Context, req *v1.CreateUserReq) (res *v1.CreateUserRes, err error) {
	id, err := service.UserService().CreateUser(ctx, req.Name, req.Password)
	if err != nil {
		return nil, err
	}
	return &v1.CreateUserRes{Id: id}, nil
}

user_v1_delete_user.go

func (c *ControllerV1) DeleteUser(ctx context.Context, req *v1.DeleteUserReq) (res *v1.DeleteUserRes, err error) {
	success, info, err := service.UserService().DeleteUser(ctx, req.Id)
	if err != nil {
		info = err.Error()
	}
	res = &v1.DeleteUserRes{
		Success: success,
		Info:    info,
	}
	return
}

user_v1_describe_user.go

func (c *ControllerV1) DescribeUser(ctx context.Context, req *v1.DescribeUserReq) (res *v1.DescribeUserRes, err error) {
	id, err := service.UserService().DescribeUser(ctx, req.Name, req.Password)
	if err != nil {
		return nil, err
	}
	res = &v1.DescribeUserRes{
		Id: id,
	}
	return res, nil
}

user_v1_modify_user.go

func (c *ControllerV1) ModifyUser(ctx context.Context, req *v1.ModifyUserReq) (res *v1.ModifyUserRes, err error) {
	success, info, err := service.UserService().ModifyUser(ctx, req.Id, req.Name, req.Password)
	if err != nil {
		info = err.Error()
	}
	res = &v1.ModifyUserRes{
		Success: success,
		Info:    info,
	}
	return
}

评论

comment_v1_create_comment.go

func (c *ControllerV1) CreateComment(ctx context.Context, req *v1.CreateCommentReq) (res *v1.CreateCommentRes, err error) {
	id, err := service.CommentService().CreateComment(ctx, req.ArticleId, req.AuthorId, req.Content, req.ParentId)
	if err != nil {
		return nil, err
	}
	return &v1.CreateCommentRes{Id: id}, nil
}

comment_v1_delete_comment.go

func (c *ControllerV1) DeleteComment(ctx context.Context, req *v1.DeleteCommentReq) (res *v1.DeleteCommentRes, err error) {
	success, info, err := service.CommentService().DeleteComment(ctx, req.Id, req.AuthorId)
	if err != nil {
		info = err.Error()
	}
	res = &v1.DeleteCommentRes{
		Success: success,
		Info:    info,
	}
	return
}

comment_v1_describe_comment_list.go

func (c *ControllerV1) DescribeCommentList(ctx context.Context, req *v1.DescribeCommentListReq) (res *v1.DescribeCommentListRes, err error) {
	list, err := service.CommentService().DescribeCommentList(ctx, req.Aid)
	if err != nil {
		return nil, err
	}
	res = &v1.DescribeCommentListRes{
		CommentList: list,
	}
	return res, nil
}

环境准备

 代码写了这么久,都还没有进行配置,打开/manifest/config/config.yaml文件,在里面配置好数据库连接,最终效果大概如下所示:

server:
  address:     ":8000"
  openapiPath: "/api.json"
  swaggerPath: "/swagger"

logger:
  level : "all"
  stdout: true

database:
  link: "mysql:root:12345678@tcp(localhost:3306)/landc_new_blog?parseTime=true"

 再打开/internal/cmd/cmd.go文件,进行路由配置:

var (
	Main = gcmd.Command{
		Name:  "main",
		Usage: "main",
		Brief: "start http server",
		Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
			s := g.Server()
			s.Use(ghttp.MiddlewareHandlerResponse)
			s.Group("/article", func(group *ghttp.RouterGroup) {
				group.Bind(article.NewV1())
			})
			s.Group("/user", func(group *ghttp.RouterGroup) {
				group.Bind(user.NewV1())
			})
			s.Group("/comment", func(group *ghttp.RouterGroup) {
				group.Bind(comment.NewV1())
			})
			s.Run()
			return nil
		},
	}
)

配置好之后运行,在控制台看到打印接口,就可以进行访问了

结语

 本来以为很快就能写出这篇,但是有很多事情耽搁了,跟大家说一声对不起。本期内容并没有太多的东西,大部分只是代码的展示,下一篇将会开始前端的搭建,希望能够很快搭建完。后续代码会上传到github和gitee上,上传之后我会将连接更新到本篇中,大家可以关注一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值