add article
This commit is contained in:
@@ -0,0 +1,487 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"tyapi-server/internal/domains/article/entities"
|
||||
"tyapi-server/internal/domains/article/repositories"
|
||||
repoQueries "tyapi-server/internal/domains/article/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormArticleRepository GORM文章仓储实现
|
||||
type GormArticleRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.ArticleRepository = (*GormArticleRepository)(nil)
|
||||
|
||||
// NewGormArticleRepository 创建GORM文章仓储
|
||||
func NewGormArticleRepository(db *gorm.DB, logger *zap.Logger) *GormArticleRepository {
|
||||
return &GormArticleRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建文章
|
||||
func (r *GormArticleRepository) Create(ctx context.Context, entity entities.Article) (entities.Article, error) {
|
||||
r.logger.Info("创建文章", zap.String("id", entity.ID), zap.String("title", entity.Title))
|
||||
|
||||
err := r.db.WithContext(ctx).Create(&entity).Error
|
||||
if err != nil {
|
||||
r.logger.Error("创建文章失败", zap.Error(err))
|
||||
return entity, err
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取文章
|
||||
func (r *GormArticleRepository) GetByID(ctx context.Context, id string) (entities.Article, error) {
|
||||
var entity entities.Article
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("id = ?", id).
|
||||
First(&entity).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entity, fmt.Errorf("文章不存在")
|
||||
}
|
||||
r.logger.Error("获取文章失败", zap.String("id", id), zap.Error(err))
|
||||
return entity, err
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// Update 更新文章
|
||||
func (r *GormArticleRepository) Update(ctx context.Context, entity entities.Article) error {
|
||||
r.logger.Info("更新文章", zap.String("id", entity.ID))
|
||||
|
||||
err := r.db.WithContext(ctx).Save(&entity).Error
|
||||
if err != nil {
|
||||
r.logger.Error("更新文章失败", zap.String("id", entity.ID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除文章
|
||||
func (r *GormArticleRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除文章", zap.String("id", id))
|
||||
|
||||
err := r.db.WithContext(ctx).Delete(&entities.Article{}, "id = ?", id).Error
|
||||
if err != nil {
|
||||
r.logger.Error("删除文章失败", zap.String("id", id), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindByAuthorID 根据作者ID查找文章
|
||||
func (r *GormArticleRepository) FindByAuthorID(ctx context.Context, authorID string) ([]*entities.Article, error) {
|
||||
var articles []entities.Article
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("author_id = ?", authorID).
|
||||
Order("created_at DESC").
|
||||
Find(&articles).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("根据作者ID查找文章失败", zap.String("author_id", authorID), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Article, len(articles))
|
||||
for i := range articles {
|
||||
result[i] = &articles[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindByCategoryID 根据分类ID查找文章
|
||||
func (r *GormArticleRepository) FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.Article, error) {
|
||||
var articles []entities.Article
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("category_id = ?", categoryID).
|
||||
Order("created_at DESC").
|
||||
Find(&articles).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("根据分类ID查找文章失败", zap.String("category_id", categoryID), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Article, len(articles))
|
||||
for i := range articles {
|
||||
result[i] = &articles[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindByStatus 根据状态查找文章
|
||||
func (r *GormArticleRepository) FindByStatus(ctx context.Context, status entities.ArticleStatus) ([]*entities.Article, error) {
|
||||
var articles []entities.Article
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("status = ?", status).
|
||||
Order("created_at DESC").
|
||||
Find(&articles).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("根据状态查找文章失败", zap.String("status", string(status)), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Article, len(articles))
|
||||
for i := range articles {
|
||||
result[i] = &articles[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindFeatured 查找推荐文章
|
||||
func (r *GormArticleRepository) FindFeatured(ctx context.Context) ([]*entities.Article, error) {
|
||||
var articles []entities.Article
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("is_featured = ? AND status = ?", true, entities.ArticleStatusPublished).
|
||||
Order("published_at DESC").
|
||||
Find(&articles).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("查找推荐文章失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Article, len(articles))
|
||||
for i := range articles {
|
||||
result[i] = &articles[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Search 搜索文章
|
||||
func (r *GormArticleRepository) Search(ctx context.Context, query *repoQueries.SearchArticleQuery) ([]*entities.Article, int64, error) {
|
||||
var articles []entities.Article
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Article{})
|
||||
|
||||
// 应用搜索条件
|
||||
if query.Keyword != "" {
|
||||
keyword := "%" + query.Keyword + "%"
|
||||
dbQuery = dbQuery.Where("title LIKE ? OR content LIKE ? OR summary LIKE ?", keyword, keyword, keyword)
|
||||
}
|
||||
|
||||
if query.CategoryID != "" {
|
||||
dbQuery = dbQuery.Where("category_id = ?", query.CategoryID)
|
||||
}
|
||||
|
||||
if query.AuthorID != "" {
|
||||
dbQuery = dbQuery.Where("author_id = ?", query.AuthorID)
|
||||
}
|
||||
|
||||
if query.Status != "" {
|
||||
dbQuery = dbQuery.Where("status = ?", query.Status)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
r.logger.Error("获取搜索结果总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if query.OrderBy != "" {
|
||||
orderDir := "DESC"
|
||||
if query.OrderDir != "" {
|
||||
orderDir = strings.ToUpper(query.OrderDir)
|
||||
}
|
||||
dbQuery = dbQuery.Order(fmt.Sprintf("%s %s", query.OrderBy, orderDir))
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if query.Page > 0 && query.PageSize > 0 {
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
}
|
||||
|
||||
// 预加载关联数据
|
||||
dbQuery = dbQuery.Preload("Category").Preload("Tags")
|
||||
|
||||
// 获取数据
|
||||
if err := dbQuery.Find(&articles).Error; err != nil {
|
||||
r.logger.Error("搜索文章失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Article, len(articles))
|
||||
for i := range articles {
|
||||
result[i] = &articles[i]
|
||||
}
|
||||
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
// ListArticles 获取文章列表
|
||||
func (r *GormArticleRepository) ListArticles(ctx context.Context, query *repoQueries.ListArticleQuery) ([]*entities.Article, int64, error) {
|
||||
var articles []entities.Article
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Article{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.Status != "" {
|
||||
dbQuery = dbQuery.Where("status = ?", query.Status)
|
||||
}
|
||||
|
||||
if query.CategoryID != "" {
|
||||
dbQuery = dbQuery.Where("category_id = ?", query.CategoryID)
|
||||
}
|
||||
|
||||
if query.TagID != "" {
|
||||
// 通过标签关联表筛选
|
||||
dbQuery = dbQuery.Joins("JOIN article_tag_relations ON articles.id = article_tag_relations.article_id").
|
||||
Where("article_tag_relations.tag_id = ?", query.TagID)
|
||||
}
|
||||
|
||||
if query.Title != "" {
|
||||
dbQuery = dbQuery.Where("title ILIKE ?", "%"+query.Title+"%")
|
||||
}
|
||||
|
||||
if query.Summary != "" {
|
||||
dbQuery = dbQuery.Where("summary ILIKE ?", "%"+query.Summary+"%")
|
||||
}
|
||||
|
||||
if query.IsFeatured != nil {
|
||||
dbQuery = dbQuery.Where("is_featured = ?", *query.IsFeatured)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
r.logger.Error("获取文章列表总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if query.OrderBy != "" {
|
||||
orderDir := "DESC"
|
||||
if query.OrderDir != "" {
|
||||
orderDir = strings.ToUpper(query.OrderDir)
|
||||
}
|
||||
dbQuery = dbQuery.Order(fmt.Sprintf("%s %s", query.OrderBy, orderDir))
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if query.Page > 0 && query.PageSize > 0 {
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
}
|
||||
|
||||
// 预加载关联数据
|
||||
dbQuery = dbQuery.Preload("Category").Preload("Tags")
|
||||
|
||||
// 获取数据
|
||||
if err := dbQuery.Find(&articles).Error; err != nil {
|
||||
r.logger.Error("获取文章列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Article, len(articles))
|
||||
for i := range articles {
|
||||
result[i] = &articles[i]
|
||||
}
|
||||
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
// CountByCategoryID 统计分类文章数量
|
||||
func (r *GormArticleRepository) CountByCategoryID(ctx context.Context, categoryID string) (int64, error) {
|
||||
var count int64
|
||||
|
||||
err := r.db.WithContext(ctx).Model(&entities.Article{}).
|
||||
Where("category_id = ?", categoryID).
|
||||
Count(&count).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("统计分类文章数量失败", zap.String("category_id", categoryID), zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountByStatus 统计状态文章数量
|
||||
func (r *GormArticleRepository) CountByStatus(ctx context.Context, status entities.ArticleStatus) (int64, error) {
|
||||
var count int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Article{})
|
||||
|
||||
if status != "" {
|
||||
dbQuery = dbQuery.Where("status = ?", status)
|
||||
}
|
||||
|
||||
err := dbQuery.Count(&count).Error
|
||||
if err != nil {
|
||||
r.logger.Error("统计状态文章数量失败", zap.String("status", string(status)), zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// IncrementViewCount 增加阅读量
|
||||
func (r *GormArticleRepository) IncrementViewCount(ctx context.Context, articleID string) error {
|
||||
err := r.db.WithContext(ctx).Model(&entities.Article{}).
|
||||
Where("id = ?", articleID).
|
||||
UpdateColumn("view_count", gorm.Expr("view_count + ?", 1)).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("增加阅读量失败", zap.String("article_id", articleID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 实现 BaseRepository 接口的其他方法
|
||||
func (r *GormArticleRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Article{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
dbQuery = dbQuery.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
search := "%" + options.Search + "%"
|
||||
dbQuery = dbQuery.Where("title LIKE ? OR content LIKE ?", search, search)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := dbQuery.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Article{}).
|
||||
Where("id = ?", id).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Article{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) Restore(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.Article{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) CreateBatch(ctx context.Context, entities []entities.Article) error {
|
||||
return r.db.WithContext(ctx).Create(&entities).Error
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Article, error) {
|
||||
var articles []entities.Article
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&articles).Error
|
||||
return articles, err
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) UpdateBatch(ctx context.Context, entities []entities.Article) error {
|
||||
return r.db.WithContext(ctx).Save(&entities).Error
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Article{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
func (r *GormArticleRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Article, error) {
|
||||
var articles []entities.Article
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Article{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
dbQuery = dbQuery.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
search := "%" + options.Search + "%"
|
||||
dbQuery = dbQuery.Where("title LIKE ? OR content LIKE ?", search, search)
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := "DESC"
|
||||
if options.Order != "" {
|
||||
order = strings.ToUpper(options.Order)
|
||||
}
|
||||
dbQuery = dbQuery.Order(fmt.Sprintf("%s %s", options.Sort, order))
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
// 预加载关联数据
|
||||
if len(options.Include) > 0 {
|
||||
for _, include := range options.Include {
|
||||
dbQuery = dbQuery.Preload(include)
|
||||
}
|
||||
}
|
||||
|
||||
err := dbQuery.Find(&articles).Error
|
||||
return articles, err
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tyapi-server/internal/domains/article/entities"
|
||||
"tyapi-server/internal/domains/article/repositories"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormCategoryRepository GORM分类仓储实现
|
||||
type GormCategoryRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.CategoryRepository = (*GormCategoryRepository)(nil)
|
||||
|
||||
// NewGormCategoryRepository 创建GORM分类仓储
|
||||
func NewGormCategoryRepository(db *gorm.DB, logger *zap.Logger) *GormCategoryRepository {
|
||||
return &GormCategoryRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建分类
|
||||
func (r *GormCategoryRepository) Create(ctx context.Context, entity entities.Category) (entities.Category, error) {
|
||||
r.logger.Info("创建分类", zap.String("id", entity.ID), zap.String("name", entity.Name))
|
||||
|
||||
err := r.db.WithContext(ctx).Create(&entity).Error
|
||||
if err != nil {
|
||||
r.logger.Error("创建分类失败", zap.Error(err))
|
||||
return entity, err
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取分类
|
||||
func (r *GormCategoryRepository) GetByID(ctx context.Context, id string) (entities.Category, error) {
|
||||
var entity entities.Category
|
||||
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entity, fmt.Errorf("分类不存在")
|
||||
}
|
||||
r.logger.Error("获取分类失败", zap.String("id", id), zap.Error(err))
|
||||
return entity, err
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// Update 更新分类
|
||||
func (r *GormCategoryRepository) Update(ctx context.Context, entity entities.Category) error {
|
||||
r.logger.Info("更新分类", zap.String("id", entity.ID))
|
||||
|
||||
err := r.db.WithContext(ctx).Save(&entity).Error
|
||||
if err != nil {
|
||||
r.logger.Error("更新分类失败", zap.String("id", entity.ID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除分类
|
||||
func (r *GormCategoryRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除分类", zap.String("id", id))
|
||||
|
||||
err := r.db.WithContext(ctx).Delete(&entities.Category{}, "id = ?", id).Error
|
||||
if err != nil {
|
||||
r.logger.Error("删除分类失败", zap.String("id", id), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindActive 查找启用的分类
|
||||
func (r *GormCategoryRepository) FindActive(ctx context.Context) ([]*entities.Category, error) {
|
||||
var categories []entities.Category
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("active = ?", true).
|
||||
Order("sort_order ASC, created_at ASC").
|
||||
Find(&categories).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("查找启用分类失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Category, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindBySortOrder 按排序查找分类
|
||||
func (r *GormCategoryRepository) FindBySortOrder(ctx context.Context) ([]*entities.Category, error) {
|
||||
var categories []entities.Category
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Order("sort_order ASC, created_at ASC").
|
||||
Find(&categories).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("按排序查找分类失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Category, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CountActive 统计启用分类数量
|
||||
func (r *GormCategoryRepository) CountActive(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
|
||||
err := r.db.WithContext(ctx).Model(&entities.Category{}).
|
||||
Where("active = ?", true).
|
||||
Count(&count).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("统计启用分类数量失败", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// 实现 BaseRepository 接口的其他方法
|
||||
func (r *GormCategoryRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Category{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
dbQuery = dbQuery.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
search := "%" + options.Search + "%"
|
||||
dbQuery = dbQuery.Where("name LIKE ? OR description LIKE ?", search, search)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := dbQuery.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Category{}).
|
||||
Where("id = ?", id).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Category{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) Restore(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.Category{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) CreateBatch(ctx context.Context, entities []entities.Category) error {
|
||||
return r.db.WithContext(ctx).Create(&entities).Error
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Category, error) {
|
||||
var categories []entities.Category
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&categories).Error
|
||||
return categories, err
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) UpdateBatch(ctx context.Context, entities []entities.Category) error {
|
||||
return r.db.WithContext(ctx).Save(&entities).Error
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Category{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
func (r *GormCategoryRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Category, error) {
|
||||
var categories []entities.Category
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Category{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
dbQuery = dbQuery.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
search := "%" + options.Search + "%"
|
||||
dbQuery = dbQuery.Where("name LIKE ? OR description LIKE ?", search, search)
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := "DESC"
|
||||
if options.Order != "" {
|
||||
order = options.Order
|
||||
}
|
||||
dbQuery = dbQuery.Order(fmt.Sprintf("%s %s", options.Sort, order))
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("sort_order ASC, created_at ASC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
// 预加载关联数据
|
||||
if len(options.Include) > 0 {
|
||||
for _, include := range options.Include {
|
||||
dbQuery = dbQuery.Preload(include)
|
||||
}
|
||||
}
|
||||
|
||||
err := dbQuery.Find(&categories).Error
|
||||
return categories, err
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tyapi-server/internal/domains/article/entities"
|
||||
"tyapi-server/internal/domains/article/repositories"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormTagRepository GORM标签仓储实现
|
||||
type GormTagRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.TagRepository = (*GormTagRepository)(nil)
|
||||
|
||||
// NewGormTagRepository 创建GORM标签仓储
|
||||
func NewGormTagRepository(db *gorm.DB, logger *zap.Logger) *GormTagRepository {
|
||||
return &GormTagRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建标签
|
||||
func (r *GormTagRepository) Create(ctx context.Context, entity entities.Tag) (entities.Tag, error) {
|
||||
r.logger.Info("创建标签", zap.String("id", entity.ID), zap.String("name", entity.Name))
|
||||
|
||||
err := r.db.WithContext(ctx).Create(&entity).Error
|
||||
if err != nil {
|
||||
r.logger.Error("创建标签失败", zap.Error(err))
|
||||
return entity, err
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取标签
|
||||
func (r *GormTagRepository) GetByID(ctx context.Context, id string) (entities.Tag, error) {
|
||||
var entity entities.Tag
|
||||
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entity, fmt.Errorf("标签不存在")
|
||||
}
|
||||
r.logger.Error("获取标签失败", zap.String("id", id), zap.Error(err))
|
||||
return entity, err
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// Update 更新标签
|
||||
func (r *GormTagRepository) Update(ctx context.Context, entity entities.Tag) error {
|
||||
r.logger.Info("更新标签", zap.String("id", entity.ID))
|
||||
|
||||
err := r.db.WithContext(ctx).Save(&entity).Error
|
||||
if err != nil {
|
||||
r.logger.Error("更新标签失败", zap.String("id", entity.ID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除标签
|
||||
func (r *GormTagRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除标签", zap.String("id", id))
|
||||
|
||||
err := r.db.WithContext(ctx).Delete(&entities.Tag{}, "id = ?", id).Error
|
||||
if err != nil {
|
||||
r.logger.Error("删除标签失败", zap.String("id", id), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindByArticleID 根据文章ID查找标签
|
||||
func (r *GormTagRepository) FindByArticleID(ctx context.Context, articleID string) ([]*entities.Tag, error) {
|
||||
var tags []entities.Tag
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Joins("JOIN article_tag_relations ON article_tag_relations.tag_id = tags.id").
|
||||
Where("article_tag_relations.article_id = ?", articleID).
|
||||
Find(&tags).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("根据文章ID查找标签失败", zap.String("article_id", articleID), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Tag, len(tags))
|
||||
for i := range tags {
|
||||
result[i] = &tags[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindByName 根据名称查找标签
|
||||
func (r *GormTagRepository) FindByName(ctx context.Context, name string) (*entities.Tag, error) {
|
||||
var tag entities.Tag
|
||||
|
||||
err := r.db.WithContext(ctx).Where("name = ?", name).First(&tag).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
r.logger.Error("根据名称查找标签失败", zap.String("name", name), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// AddTagToArticle 为文章添加标签
|
||||
func (r *GormTagRepository) AddTagToArticle(ctx context.Context, articleID string, tagID string) error {
|
||||
// 检查关联是否已存在
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Table("article_tag_relations").
|
||||
Where("article_id = ? AND tag_id = ?", articleID, tagID).
|
||||
Count(&count).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("检查标签关联失败", zap.String("article_id", articleID), zap.String("tag_id", tagID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
// 关联已存在,不需要重复添加
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建关联
|
||||
err = r.db.WithContext(ctx).Exec(`
|
||||
INSERT INTO article_tag_relations (id, article_id, tag_id, created_at)
|
||||
VALUES (UUID(), ?, ?, NOW())
|
||||
`, articleID, tagID).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("添加标签到文章失败", zap.String("article_id", articleID), zap.String("tag_id", tagID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("添加标签到文章成功", zap.String("article_id", articleID), zap.String("tag_id", tagID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTagFromArticle 从文章移除标签
|
||||
func (r *GormTagRepository) RemoveTagFromArticle(ctx context.Context, articleID string, tagID string) error {
|
||||
err := r.db.WithContext(ctx).Exec(`
|
||||
DELETE FROM article_tag_relations
|
||||
WHERE article_id = ? AND tag_id = ?
|
||||
`, articleID, tagID).Error
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error("从文章移除标签失败", zap.String("article_id", articleID), zap.String("tag_id", tagID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("从文章移除标签成功", zap.String("article_id", articleID), zap.String("tag_id", tagID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetArticleTags 获取文章的所有标签
|
||||
func (r *GormTagRepository) GetArticleTags(ctx context.Context, articleID string) ([]*entities.Tag, error) {
|
||||
return r.FindByArticleID(ctx, articleID)
|
||||
}
|
||||
|
||||
// 实现 BaseRepository 接口的其他方法
|
||||
func (r *GormTagRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Tag{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
dbQuery = dbQuery.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
search := "%" + options.Search + "%"
|
||||
dbQuery = dbQuery.Where("name LIKE ?", search)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := dbQuery.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Tag{}).
|
||||
Where("id = ?", id).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Tag{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) Restore(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.Tag{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) CreateBatch(ctx context.Context, entities []entities.Tag) error {
|
||||
return r.db.WithContext(ctx).Create(&entities).Error
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Tag, error) {
|
||||
var tags []entities.Tag
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&tags).Error
|
||||
return tags, err
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) UpdateBatch(ctx context.Context, entities []entities.Tag) error {
|
||||
return r.db.WithContext(ctx).Save(&entities).Error
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Tag{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
func (r *GormTagRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Tag, error) {
|
||||
var tags []entities.Tag
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Tag{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
dbQuery = dbQuery.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
search := "%" + options.Search + "%"
|
||||
dbQuery = dbQuery.Where("name LIKE ?", search)
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := "DESC"
|
||||
if options.Order != "" {
|
||||
order = options.Order
|
||||
}
|
||||
dbQuery = dbQuery.Order(fmt.Sprintf("%s %s", options.Sort, order))
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("created_at ASC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
// 预加载关联数据
|
||||
if len(options.Include) > 0 {
|
||||
for _, include := range options.Include {
|
||||
dbQuery = dbQuery.Preload(include)
|
||||
}
|
||||
}
|
||||
|
||||
err := dbQuery.Find(&tags).Error
|
||||
return tags, err
|
||||
}
|
||||
Reference in New Issue
Block a user