Files
full/module/base/cms/internal/models/query.go
2026-09-22 21:15:34 +08:00

427 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package models 提供CMS数据模型和数据库操作
// 包含文章、分类、标签、评论等实体的数据结构和CRUD操作
package models
import (
"errors"
"fmt"
"bsm/full/module/base/cms/internal/impl"
pb "bsm/full/module/base/cms/pb"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/bsm-sdk/core/utils"
"gorm.io/gorm"
)
// 数据库表名常量
const (
postTable = "cms_post" // 文章表
accessoryTable = "cms_accessory" // 附件表
postRelateCategoryTable = "cms_relate" // 关联表
)
// InitData 初始化基础数据
// 创建系统必需的默认数据,如根分类等
func InitData() {
var cnt int64 = 0
// 检查是否存在根分类,如果不存在则创建
err := impl.DBService.Model(&CmsCategory{}).Where("identity=?", "_RootCategory").Count(&cnt).Error
if cnt == 0 || err != nil {
data := &CmsCategory{
Title: "根目录",
ParentId: 0, // 0表示顶级分类
Std_Identity: types.Std_Identity{Identity: "_RootCategory"},
}
impl.DBService.Create(data)
}
}
// AddPost 添加文章及其关联数据
// 使用事务确保数据一致性,同时创建文章、分类关联、标签关联和附件信息
// 参数:
// - post: 文章数据模型
// - accessory: 附件数据列表
// - category: 分类标识列表
// - tags: 标签标识列表
//
// 返回:
// - error: 错误信息
func AddPost(post *CmsPost, accessory []*pb.AccessoryItem, category, tags []string) (err error) {
var (
accessoryPath = make([]CmsAccessory, 0) // 文章附件数据
categoryData = make([]CmsRelateCategory, 0) // 文章分类关联数据
TagsData = make([]CmsRelateTags, 0) // 文章标签关联数据
)
// 构建分类关联数据
for _, va := range category {
categoryData = append(categoryData, CmsRelateCategory{
Identity: utils.UUID(),
PostIdentity: post.Identity,
CategoryIdentity: va,
})
}
// 构建标签关联数据
for _, val := range tags {
TagsData = append(TagsData, CmsRelateTags{
Identity: utils.ULID(),
PostIdentity: post.Identity,
TagsIdentity: val,
})
}
// 使用事务确保数据一致性
return impl.DBService.Transaction(func(tx *gorm.DB) error {
// 创建文章记录
if err = tx.Create(post).Error; err != nil {
return err
}
// 创建附件关联记录
if len(accessory) != 0 {
for _, v := range accessory {
accessoryPath = append(accessoryPath, CmsAccessory{
Std_Identity: types.Std_Identity{Identity: utils.UUID()},
PostId: post.ID,
PostIdentity: post.Identity,
FilePath: v.FilePath,
Title: v.Title,
})
}
if err := tx.Create(&accessoryPath).Error; err != nil {
return err
}
}
// 创建分类关联记录
if len(category) != 0 {
if err := tx.Create(&categoryData).Error; err != nil {
return err
}
}
// 创建标签关联记录
if len(tags) != 0 {
if err := tx.Create(&TagsData).Error; err != nil {
return err
}
}
return nil
})
}
// PostList 文章列表
func PostList(page, size int64, categoryIdentity, keyword string, userType int64) (list []*CmsPost, cnt int64, err error) {
tx := impl.DBService.Debug().Model(&CmsPost{})
if categoryIdentity != "" {
tx = tx.Joins("left join cms_relate_category crc on crc.post_identity = cms_post.identity ")
tx = tx.Where("crc.category_identity = ?", categoryIdentity)
}
if keyword != "" {
tx = tx.Where("cms_post.title like ?", "%"+keyword+"%")
}
if userType != 0 {
tx = tx.Where("cms_post.types = ?", userType)
}
if err = tx.Count(&cnt).Order("cms_post.created_at desc").Limit(int(size)).Offset(int((page - 1) * size)).Error; err != nil {
return nil, 0, errcode.ErrDB
}
err = tx.Preload("Accessories").Preload("Categories.Category").Preload("Tags.Tags").Find(&list).Error
return
}
// ModifyPost 修改文章
func ModifyPost(identity string, post *CmsPost, accessory, category, tags []string) (err error) {
var (
accessoryPath = make([]CmsAccessory, 0)
categoryData = make([]CmsRelateCategory, 0)
tagsData = make([]CmsRelateTags, 0)
)
for _, v := range category {
categoryData = append(categoryData, CmsRelateCategory{
Identity: utils.UUID(),
PostIdentity: identity,
CategoryIdentity: v,
})
}
for _, val := range tags {
tagsData = append(tagsData, CmsRelateTags{
Identity: utils.ULID(),
PostIdentity: identity,
TagsIdentity: val,
})
}
for _, v := range accessory {
accessoryPath = append(accessoryPath, CmsAccessory{
Std_Identity: types.Std_Identity{Identity: utils.UUID()},
PostId: post.ID,
PostIdentity: identity,
FilePath: v,
})
}
return impl.DBService.Transaction(func(tx *gorm.DB) error {
if err = tx.Where("identity = ?", identity).Updates(post).Error; err != nil {
return err
}
// 删除旧的附件、分类和标签关联
if err := tx.Where("post_identity = ?", identity).Delete(&CmsAccessory{}).Error; err != nil {
return err
}
if err := tx.Where("post_identity = ?", identity).Delete(&CmsRelateCategory{}).Error; err != nil {
return err
}
if err := tx.Where("post_identity = ?", identity).Delete(&CmsRelateTags{}).Error; err != nil {
return err
}
// 创建新的分类关联
if len(category) != 0 {
if err := tx.Create(&categoryData).Error; err != nil {
return err
}
}
// 创建新的标签关联
if len(tags) != 0 {
if err := tx.Create(&tagsData).Error; err != nil {
return err
}
}
// 创建新的附件关联
if len(accessory) != 0 {
if err := tx.Create(&accessoryPath).Error; err != nil {
return err
}
}
return nil
})
}
// GetPostForAuth 按 identity 查询文章(不更新点击量),用于写操作的归属校验
func GetPostForAuth(identity string) (*CmsPost, error) {
post := new(CmsPost)
if err := impl.DBService.Where("identity = ?", identity).Take(post).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errcode.ErrRecordNotFound
}
return nil, errcode.ErrDB
}
return post, nil
}
// GetCommentForAuth 按 identity 查询评论,用于写操作的归属校验
func GetCommentForAuth(identity string) (*CmsComment, error) {
comment := new(CmsComment)
if err := impl.DBService.Where("identity = ?", identity).Take(comment).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errcode.ErrRecordNotFound
}
return nil, errcode.ErrDB
}
return comment, nil
}
// DeletePost 删除文章
func DeletePost(identity, authorIdentity string) (err error) {
return impl.DBService.Transaction(func(tx *gorm.DB) error {
if err = tx.Where("identity = ?", identity).Delete(&CmsPost{}).Error; err != nil {
return err
}
if err = tx.Where("post_identity = ?", identity).Delete(&CmsRelateCategory{}).Error; err != nil {
return err
}
if err = tx.Where("post_identity = ?", identity).Delete(&CmsRelateTags{}).Error; err != nil {
return err
}
if err = tx.Where("post_identity = ?", identity).Delete(&CmsAccessory{}).Error; err != nil {
return err
}
return nil
})
}
// GetPost 根据指定字段获取文章详情
// 查询成功后会更新文章的点击量
// 参数:
// - key: 查询字段名(如"identity"、"key"等)
// - val: 查询字段值
//
// 返回:
// - *CmsPost: 文章详情
// - error: 错误信息
func GetPost(key, val string) (*CmsPost, error) {
var post = &CmsPost{}
// 使用事务确保查询和更新操作的原子性
return post, impl.DBService.Transaction(func(tx *gorm.DB) error {
// 查询文章详情,预加载关联数据
if err := tx.Model(&CmsPost{}).Preload("Accessories").Preload("Categories").Preload("Tags").
Where(key+" = ?", val).First(post).Error; err != nil {
return err
}
// 更新文章点击量,只有在查询成功后才更新
if err := tx.Model(&CmsPost{}).Where(key+" = ?", val).
UpdateColumn("hits", gorm.Expr("hits + 1")).Error; err != nil {
return err
}
return nil
})
}
// IncrOrDescPostField 增加或减少文章字段值
// 用于更新文章的点赞、踩赞、评论等计数字段
// 参数:
// - identity: 文章唯一标识
// - column: 要更新的字段名
// - desc: true表示减少false表示增加
//
// 返回:
// - error: 错误信息
func IncrOrDescPostField(identity, column string, desc bool) (err error) {
expr := "%s + ?"
tx := impl.DBService.Model(&CmsPost{}).Where("identity = ?", identity)
if desc {
expr = "%s - ?"
// 减少时限定计数大于 0避免出现负值
tx = tx.Where(column+" > ?", 0)
}
if err := tx.UpdateColumn(column, gorm.Expr(fmt.Sprintf(expr, column), 1)).Error; err != nil {
return err
}
return nil
}
func AddComment(comment *CmsComment, authorIdentity string) (err error) {
err = impl.DBService.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(comment).Error; err != nil {
return err
}
// 添加评论,更新文章评论量
err := tx.Model(&CmsPost{}).Where("identity = ?", comment.PostIdentity).UpdateColumn("comment_hits", gorm.Expr("comment_hits + ?", 1)).Error
if err != nil {
return err
}
if comment.ParentId != 0 {
// 更新被评论的那条评论的计数
err = tx.Model(&CmsComment{}).Where("id = ?", comment.ParentId).UpdateColumn("comment_hits", gorm.Expr("comment_hits + ?", 1)).Error
if err != nil {
return err
}
}
return nil
})
return
}
func ModifyComment(comment *CmsComment) (err error) {
return impl.DBService.Where("identity = ?", comment.Identity).Updates(comment).Error
}
func DeleteComment(identity string) (err error) {
// get comment
comment := new(CmsComment)
err = impl.DBService.Where("identity = ?", identity).First(comment).Error
if err != nil {
return err
}
err = impl.DBService.Transaction(func(tx *gorm.DB) error {
// 统计将被级联删除的子评论数量,保证文章评论计数与实际评论数一致
var childCnt int64
if err := tx.Model(&CmsComment{}).Where("parent_id = ?", comment.ID).Count(&childCnt).Error; err != nil {
return err
}
// 删除评论,更新文章评论量(减去本评论及其被级联删除的子评论总数)
if err := tx.Where("identity = ?", identity).Delete(&CmsComment{}).Error; err != nil {
return err
}
if err := tx.Model(&CmsPost{}).Where("identity = ?", comment.PostIdentity).
UpdateColumn("comment_hits", gorm.Expr("comment_hits - ?", childCnt+1)).Error; err != nil {
return err
}
if comment.ParentId != 0 {
// 更新被评论的那条评论的计数
if err := tx.Model(&CmsComment{}).Where("id = ?", comment.ParentId).
UpdateColumn("comment_hits", gorm.Expr("comment_hits - ?", 1)).Error; err != nil {
return err
}
}
// 删除子级评论
err = tx.Where("parent_id = ?", comment.ID).Delete(&CmsComment{}).Error
if err != nil {
return err
}
return nil
})
return
}
// IncrOrDescCommentField 增加或减少评论字段值
// 用于更新评论的点赞、踩赞等计数字段
// 参数:
// - identity: 评论唯一标识
// - column: 要更新的字段名
// - desc: true表示减少false表示增加
//
// 返回:
// - error: 错误信息
//
// 说明:现有表结构未记录「谁点过赞」,无法做完整去重;
// 完整去重需新增「点赞记录表/字段」,此处仅在计数层做下限保护,避免被刷成负数。
func IncrOrDescCommentField(identity, column string, desc bool) (err error) {
expr := "%s + ?"
tx := impl.DBService.Model(&CmsComment{}).Where("identity = ?", identity)
if desc {
expr = "%s - ?"
// 减少时限定计数大于 0避免出现负值
tx = tx.Where(column+" > ?", 0)
}
if err = tx.UpdateColumn(column, gorm.Expr(fmt.Sprintf(expr, column), 1)).Error; err != nil {
return err
}
return nil
}
// CategoryList 文章分类列表
func CategoryList() (list []CmsCategory, cnt int64, err error) {
result := impl.DBService.Preload("Children").Find(&list)
if result.Error != nil {
err = result.Error
return
}
cnt = result.RowsAffected
return
}
// DeleteCategory 删除分类
func DeleteCategory(identity string) (err error) {
var (
cnt int64 = 0
category CmsCategory
)
err = impl.DBService.Model(&CmsCategory{}).Where("identity = ?", identity).Find(&category).Error
if err != nil {
return err
}
impl.DBService.Model(&CmsCategory{}).Where("parent_id = ?", category.ID).Count(&cnt)
if cnt != 0 {
return errcode.NewError(500, "ErrCategoryHasSubcategories")
}
err = impl.DBService.Where("identity = ?", identity).Delete(&CmsCategory{}).Error
if err != nil {
return err
}
return
}