Files
nl_cms-api/internal/service/article.go
2025-07-29 12:45:07 +08:00

355 lines
8.4 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sArticle struct{}
func Article() *sArticle {
return &sArticle{}
}
// GetById 根据ID获取文章信息
func (s *sArticle) GetById(ctx context.Context, id int) (*model.Article, error) {
article, err := dao.Article.GetById(ctx, id)
if err != nil {
return nil, err
}
if article != nil {
// 增加浏览次数
go func() {
dao.Article.IncrementViewCount(context.Background(), id)
}()
}
return article, nil
}
// GetBySlug 根据URL别名获取文章
func (s *sArticle) GetBySlug(ctx context.Context, slug string) (*model.Article, error) {
article, err := dao.Article.GetBySlug(ctx, slug)
if err != nil {
return nil, err
}
if article != nil {
// 增加浏览次数
go func() {
dao.Article.IncrementViewCount(context.Background(), article.Id)
}()
}
return article, nil
}
// List 获取文章列表
func (s *sArticle) List(ctx context.Context, req *model.ArticleListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
articles, total, err := dao.Article.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: articles,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Create 创建文章
func (s *sArticle) Create(ctx context.Context, req *model.CreateArticleRequest) error {
// 检查URL别名是否存在
if req.Slug != "" {
existArticle, err := dao.Article.GetBySlug(ctx, req.Slug)
if err != nil {
return err
}
if existArticle != nil {
return gerror.New("URL别名已存在")
}
} else {
// 如果没有提供slug则根据标题生成
req.Slug = s.generateSlug(req.Title)
}
// 获取当前用户ID
authorId := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("admin_id"))
if authorId == 0 {
authorId = gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("user_id"))
}
// 处理HTML内容如果是Markdown需要转换
htmlContent := req.Content
if req.Content != "" {
// 这里可以添加Markdown到HTML的转换逻辑
htmlContent = s.markdownToHTML(req.Content)
}
// 创建文章
article := &model.Article{
Title: req.Title,
Slug: req.Slug,
Summary: req.Summary,
Content: req.Content,
HtmlContent: htmlContent,
CoverImage: req.CoverImage,
CategoryId: req.CategoryId,
Tags: req.Tags,
AuthorId: authorId,
IsPublished: req.IsPublished,
IsFeatured: req.IsFeatured,
IsTop: req.IsTop,
SeoTitle: req.SeoTitle,
SeoDescription: req.SeoDescription,
SeoKeywords: req.SeoKeywords,
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
if req.IsPublished == 1 {
article.PublishedAt = gtime.Now()
}
_, err := dao.Article.Create(ctx, article)
return err
}
// Update 更新文章
func (s *sArticle) Update(ctx context.Context, id int, req *model.UpdateArticleRequest) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
// 检查URL别名是否被其他文章使用
if req.Slug != "" && req.Slug != article.Slug {
existArticle, err := dao.Article.GetBySlug(ctx, req.Slug)
if err != nil {
return err
}
if existArticle != nil && existArticle.Id != id {
return gerror.New("URL别名已被使用")
}
}
// 处理HTML内容
htmlContent := req.Content
if req.Content != "" {
htmlContent = s.markdownToHTML(req.Content)
}
// 更新文章
updateData := g.Map{
"title": req.Title,
"slug": req.Slug,
"summary": req.Summary,
"content": req.Content,
"html_content": htmlContent,
"cover_image": req.CoverImage,
"category_id": req.CategoryId,
"tags": req.Tags,
"is_published": req.IsPublished,
"is_featured": req.IsFeatured,
"is_top": req.IsTop,
"seo_title": req.SeoTitle,
"seo_description": req.SeoDescription,
"seo_keywords": req.SeoKeywords,
"updated_at": gtime.Now(),
}
// 如果从草稿变为发布状态,设置发布时间
if req.IsPublished == 1 && article.IsPublished == 0 {
updateData["published_at"] = gtime.Now()
}
return dao.Article.Update(ctx, id, updateData)
}
// Delete 删除文章
func (s *sArticle) Delete(ctx context.Context, id int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
return dao.Article.Delete(ctx, id)
}
// UpdateStatus 更新文章状态
func (s *sArticle) UpdateStatus(ctx context.Context, id int, isPublished int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
updateData := g.Map{
"is_published": isPublished,
"updated_at": gtime.Now(),
}
// 如果是发布状态,设置发布时间
if isPublished == 1 && article.IsPublished == 0 {
updateData["published_at"] = gtime.Now()
}
return dao.Article.Update(ctx, id, updateData)
}
// SetFeatured 设置推荐状态
func (s *sArticle) SetFeatured(ctx context.Context, id int, isFeatured int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
return dao.Article.Update(ctx, id, g.Map{
"is_featured": isFeatured,
"updated_at": gtime.Now(),
})
}
// SetTop 设置置顶状态
func (s *sArticle) SetTop(ctx context.Context, id int, isTop int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
return dao.Article.Update(ctx, id, g.Map{
"is_top": isTop,
"updated_at": gtime.Now(),
})
}
// GetStats 获取文章统计信息
func (s *sArticle) GetStats(ctx context.Context) (map[string]interface{}, error) {
// 获取总文章数
totalCount, err := dao.Article.GetCount(ctx)
if err != nil {
return nil, err
}
// 获取已发布文章数
publishedCount, err := dao.Article.GetPublishedCount(ctx)
if err != nil {
return nil, err
}
// 获取草稿数
draftCount := totalCount - publishedCount
// 获取推荐文章数
featuredCount, err := dao.Article.GetFeaturedCount(ctx)
if err != nil {
return nil, err
}
return map[string]interface{}{
"total_count": totalCount,
"published_count": publishedCount,
"draft_count": draftCount,
"featured_count": featuredCount,
}, nil
}
// GetFeatured 获取推荐文章
func (s *sArticle) GetFeatured(ctx context.Context, limit int) ([]*model.Article, error) {
if limit <= 0 {
limit = 5
}
return dao.Article.GetFeatured(ctx, limit)
}
// GetLatest 获取最新文章
func (s *sArticle) GetLatest(ctx context.Context, limit int) ([]*model.Article, error) {
if limit <= 0 {
limit = 10
}
return dao.Article.GetLatest(ctx, limit)
}
// generateSlug 生成URL别名
func (s *sArticle) generateSlug(title string) string {
// 简单的slug生成逻辑实际项目中可能需要更复杂的处理
slug := strings.ToLower(title)
slug = strings.ReplaceAll(slug, " ", "-")
slug = strings.ReplaceAll(slug, " ", "-") // 全角空格
// 移除特殊字符,只保留字母、数字、中文和连字符
// 这里简化处理实际项目中可能需要更完善的slug生成逻辑
return slug
}
// markdownToHTML 将Markdown转换为HTML
func (s *sArticle) markdownToHTML(markdown string) string {
// 这里应该使用Markdown解析库如github.com/russross/blackfriday
// 暂时直接返回原内容实际项目中需要实现Markdown解析
return markdown
}
// Search 搜索文章
func (s *sArticle) Search(ctx context.Context, keyword string, page, pageSize int) (*model.PageResponse, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
articles, total, err := dao.Article.Search(ctx, keyword, page, pageSize)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: articles,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}