自媒体内容分类功能和tag功能实现

This commit is contained in:
2025-08-13 19:57:10 +08:00
parent 30d9036247
commit bdb14c4c7b
8 changed files with 8259 additions and 13 deletions

View File

@@ -34,7 +34,7 @@ func Init() {
}
// 自动迁移
err = DB.AutoMigrate(model.WeMediaUser{}, model.UserFans{}, model.UserBlacklist{})
err = DB.AutoMigrate(model.Moment{}, model.MomentFile{}, model.MomentLike{}, model.MomentComment{}, model.CommentFile{}, model.MomentCollect{})
err = DB.AutoMigrate(model.Moment{}, model.MomentFile{}, model.MomentLike{}, model.MomentComment{}, model.CommentFile{}, model.MomentCollect{}, model.Category{}, model.MomentCategory{}, model.Tag{}, model.MomentTag{})
if err != nil {
hlog.Error("AutoMigrate failed: " + err.Error())
return

View File

@@ -761,3 +761,366 @@ func findAllChildCommentIDs(tx *gorm.DB, parentID int64) ([]uint, error) {
return childIDs, nil
}
// CreateTag 创建tag
func CreateTag(req moment.CreateTagRequest) error {
if req.UserID == "" || req.TagName == "" {
return errors.New("缺少必须参数")
}
db := DB
db.Model(&model.Tag{}).Create(&model.Tag{
TagName: req.TagName,
UserID: req.UserID,
})
return db.Error
}
// DeleteTag 删除tag
func DeleteTag(req moment.DeleteTagRequest) error {
if req.UserID == "" || req.TagName == "" {
return errors.New("缺少必须参数")
}
db := DB
db.Model(&model.Tag{}).
Where("tag_name = ? AND user_id = ?", req.TagName, req.UserID).
Delete(&model.Tag{})
return db.Error
}
// ListTag 获取tag列表
func ListTag(req moment.ListTagRequest) ([]*moment.TagInfo, int32, error) {
// 参数验证
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 {
req.PageSize = 10
}
if req.PageSize > 100 { // 限制最大分页大小,防止恶意请求
req.PageSize = 100
}
db := DB
var total int64
var result []*moment.TagInfo
// 先查询总数
if err := db.Model(&model.Tag{}).Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("查询tag总数失败: %v", err)
}
// 计算偏移量
offset := int(req.PageSize * (req.Page - 1))
// 再查询当前页数据
if err := db.Model(&model.Tag{}).
Select("id,tag_name").
Offset(int(offset)).
Limit(int(req.PageSize)).
Order("created_at DESC"). // 按时间倒序,最新创建的在前
Find(&result).Error; err != nil {
return nil, 0, err
}
return result, int32(total), nil
}
// FindTag 模糊搜索tag列表
func FindTag(keyword *string, page, pageSize int32) ([]*moment.TagInfo, int32, error) {
// 校验分页参数合理性
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 { // 限制最大页大小,防止查询过大
pageSize = 20
}
offset := (page - 1) * pageSize
db := DB.Model(&model.Tag{})
var total int64
var result []*moment.TagInfo
// 处理关键词查询
if keyword != nil && len(*keyword) > 0 {
likePattern := "%" + *keyword + "%"
// 多字段模糊匹配使用括号确保OR条件正确分组
db = db.Where("tag_name LIKE ?", likePattern)
}
// 获取总条数,用于分页计算
if err := db.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("获取tags总数失败: %w", err)
}
// 执行分页查询
if err := db.
Model(model.Tag{}).
Select("id,tag_name").
Offset(int(offset)).
Limit(int(pageSize)).
Order("created_at DESC"). // 按创建时间倒序,最新的在前
Find(&result).Error; err != nil {
return nil, 0, fmt.Errorf("查询tag列表失败: %w", err)
}
return result, int32(total), nil
}
// BindTag 文章绑定标签
func BindTag(req moment.BindTagRequest) error {
// 参数校验
if len(req.MomentID) == 0 || req.TagID == 0 {
return errors.New("缺少必须参数")
}
// 使用事务确保数据一致性
return DB.Transaction(func(tx *gorm.DB) error {
// 转换TagID类型
tagID := uint(req.TagID)
// 遍历所有需要绑定的文章ID
for _, momentID := range req.MomentID {
mID := uint(momentID)
// 检查是否已绑定,避免重复绑定
var count int64
if err := tx.Model(&model.MomentTag{}).
Where("moment_id = ? AND tag_id = ?", mID, tagID).
Count(&count).Error; err != nil {
return fmt.Errorf("检查绑定关系失败: %w", err)
}
// 如果未绑定,则创建关联
if count == 0 {
if err := tx.Create(&model.MomentTag{
MomentID: mID,
TagID: tagID,
}).Error; err != nil {
return fmt.Errorf("创建绑定关系失败: %w", err)
}
}
}
return nil
})
}
// ListMomentsByTag 获取指定标签的文章列表
func ListMomentsByTag(req moment.ListMomentsByTagRequest) ([]*moment.Moment, int32, error) {
// 1. 参数校验
if req.Tag == "" {
return nil, 0, errors.New("标签ID不能为空")
}
// 处理分页参数
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize < 1 || pageSize > 50 {
pageSize = 10 // 限制最大页大小,避免查询压力过大
}
offset := (page - 1) * pageSize
// 2. 构建查询:通过标签关联表查询对应的文章
// 先查询关联关系表,再关联文章主表
db := DB.Model(&model.MomentTag{}).
Joins("JOIN moment ON moment_tag.moment_id = moment.id").
Where("moment_tag.tag_id = ?", req.Tag)
//Where("moment.status = 0") // 只查询正常状态的文章
// 3. 统计符合条件的总条数
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("统计标签下文章总数失败: %w", err)
}
// 4. 分页查询文章基本信息
var moments []*moment.Moment
if err := db.
Select(`moment.id, moment.user_id, moment.content, moment.visibility,
moment.location, moment.like_count, moment.collect_count,
moment.comment_count, moment.created_at`).
Offset(int(offset)).
Limit(int(pageSize)).
Order("moment.created_at DESC"). // 按文章发布时间倒序
Scan(&moments).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return []*moment.Moment{}, 0, nil // 无数据时返回空列表
}
return nil, 0, fmt.Errorf("查询标签下文章列表失败: %w", err)
}
// 5. 批量查询文章图片优化N+1查询问题
if len(moments) > 0 {
// 收集所有文章ID
var momentIDs []uint64
for _, m := range moments {
momentIDs = append(momentIDs, uint64(m.ID))
}
// 一次查询所有文章的图片
var files []*model.MomentFile
if err := DB.Where("moment_id IN (?)", momentIDs).Find(&files).Error; err != nil {
return nil, 0, fmt.Errorf("查询文章图片失败: %w", err)
}
// 建立文章ID到图片的映射
fileMap := make(map[uint64][]*moment.MomentFile)
for _, f := range files {
dtoFile := &moment.MomentFile{
ID: int64(f.ID),
MomentID: int64(f.MomentID),
FileURL: f.FileURL,
FileType: f.FileType,
SortOrder: int8(f.SortOrder),
}
fileMap[uint64(f.MomentID)] = append(fileMap[uint64(f.MomentID)], dtoFile)
}
// 为每个文章分配图片
for _, m := range moments {
m.Files = fileMap[uint64(m.ID)]
}
}
// 6. 转换总条数为int32返回
return moments, int32(total), nil
}
// ListCategory 获取分类列表
func ListCategory() ([]*moment.CategoryInfo, error) {
db := DB
var result []*moment.CategoryInfo
// 查询数据
if err := db.Model(&model.Category{}).
Select("id,category_name").
Find(&result).Error; err != nil {
return nil, err
}
return result, nil
}
// BindCategory 文章绑定分类
func BindCategory(req moment.BindCategoryRequest) error {
// 参数校验
if len(req.MomentID) == 0 || req.CategoryID == 0 {
return errors.New("缺少必须参数")
}
// 使用事务确保数据一致性
return DB.Transaction(func(tx *gorm.DB) error {
// 转换类型
categoryID := uint(req.CategoryID)
// 遍历所有需要绑定的文章ID
for _, momentID := range req.MomentID {
mID := uint(momentID)
// 检查是否已绑定,避免重复绑定
var count int64
if err := tx.Model(&model.MomentCategory{}).
Where("moment_id = ? AND category_id = ?", mID, categoryID).
Count(&count).Error; err != nil {
return fmt.Errorf("检查绑定关系失败: %w", err)
}
// 如果未绑定,则创建关联
if count == 0 {
if err := tx.Create(&model.MomentCategory{
MomentID: mID,
CategoryID: categoryID,
}).Error; err != nil {
return fmt.Errorf("创建绑定关系失败: %w", err)
}
}
}
return nil
})
}
// ListMomentsByCategory 获取指定标签的文章列表
func ListMomentsByCategory(req moment.ListMomentsByCategoryRequest) ([]*moment.Moment, int32, error) {
// 1. 参数校验
if req.CategoryID == 0 {
return nil, 0, errors.New("分类不能为空")
}
// 处理分页参数
page := req.Page
if page < 1 {
page = 1
}
pageSize := req.PageSize
if pageSize < 1 || pageSize > 50 {
pageSize = 10 // 限制最大页大小,避免查询压力过大
}
offset := (page - 1) * pageSize
// 2. 构建查询:通过标签关联表查询对应的文章
// 先查询关联关系表,再关联文章主表
db := DB.Model(&model.MomentCategory{}).
Joins("JOIN moment ON moment_category.moment_id = moment.id").
Where("moment_category.category_id = ?", req.CategoryID)
//Where("moment.status = 0") // 只查询正常状态的文章
// 3. 统计符合条件的总条数
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("统计标签下文章总数失败: %w", err)
}
// 4. 分页查询文章基本信息
var moments []*moment.Moment
if err := db.
Select(`moment.id, moment.user_id, moment.content, moment.visibility,
moment.location, moment.like_count, moment.collect_count,
moment.comment_count, moment.created_at`).
Offset(int(offset)).
Limit(int(pageSize)).
Order("moment.created_at DESC"). // 按文章发布时间倒序
Scan(&moments).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return []*moment.Moment{}, 0, nil // 无数据时返回空列表
}
return nil, 0, fmt.Errorf("查询标签下文章列表失败: %w", err)
}
// 5. 批量查询文章图片优化N+1查询问题
if len(moments) > 0 {
// 收集所有文章ID
var momentIDs []uint64
for _, m := range moments {
momentIDs = append(momentIDs, uint64(m.ID))
}
// 一次查询所有文章的图片
var files []*model.MomentFile
if err := DB.Where("moment_id IN (?)", momentIDs).Find(&files).Error; err != nil {
return nil, 0, fmt.Errorf("查询文章图片失败: %w", err)
}
// 建立文章ID到图片的映射
fileMap := make(map[uint64][]*moment.MomentFile)
for _, f := range files {
dtoFile := &moment.MomentFile{
ID: int64(f.ID),
MomentID: int64(f.MomentID),
FileURL: f.FileURL,
FileType: f.FileType,
SortOrder: int8(f.SortOrder),
}
fileMap[uint64(f.MomentID)] = append(fileMap[uint64(f.MomentID)], dtoFile)
}
// 为每个文章分配图片
for _, m := range moments {
m.Files = fileMap[uint64(m.ID)]
}
}
// 6. 转换总条数为int32返回
return moments, int32(total), nil
}