自媒体内容分类功能和tag功能实现
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user