package web import ( "context" "log/slog" "net/netip" "server/internal/db" "server/internal/db/sqlc" "server/internal/model/common" "server/internal/model/request" "server/internal/model/response" "server/internal/pkg/dberr" "server/internal/pkg/errs" "server/internal/pkg/safego" ) type PostService struct { store *db.Store } func NewPostService(store *db.Store) *PostService { return &PostService{ store: store, } } func (s *PostService) List(ctx context.Context, p *common.Pagination) (*common.PageResult[sqlc.ListPublishedPostsRow], error) { params := sqlc.ListPublishedPostsParams{ Limit: p.PageSize, Offset: (p.Page - 1) * p.PageSize, } total, err := s.store.CountPublishedPosts(ctx) if err != nil { return nil, err } list, err := s.store.ListPublishedPosts(ctx, params) if err != nil { return nil, err } return &common.PageResult[sqlc.ListPublishedPostsRow]{ List: list, Total: total, }, nil } func (s *PostService) ListSearch(ctx context.Context, req request.SearchPublishedPostsParams) ([]sqlc.ListPublishedPostsWithFiltersRow, error) { params := sqlc.ListPublishedPostsWithFiltersParams{ CategoryCode: &req.CategoryCode, TagCode: &req.TagCode, } return s.store.ListPublishedPostsWithFilters(ctx, params) } func (s *PostService) GetPost(ctx context.Context, slug string, ip netip.Addr) (*sqlc.GetPublicPostBySlugRow, error) { post, err := s.store.GetPublicPostBySlug(ctx, slug) if err != nil { return nil, dberr.MapNoRows(err, errs.ErrPostNotFound) } // 异步统计浏览量,不阻塞响应 // 用 WithoutCancel 脱离请求 context:否则 handler 一返回、请求 ctx 被取消,写库会被中断 safego.Go(func() { statsCtx := context.WithoutCancel(ctx) if err = s.store.IncrementPostStatsView(statsCtx, sqlc.IncrementPostStatsViewParams{ PostID: post.ID, Ip: ip, }); err != nil { slog.Error("increment post stats view failed", "post_id", post.ID, "error", err) } }) return &post, nil } func (s *PostService) ListCategoryStats(ctx context.Context) ([]sqlc.ListCategoryStatsRow, error) { return s.store.ListCategoryStats(ctx) } func (s *PostService) ListArchives(ctx context.Context) ([]response.ArchiveYear, error) { list, err := s.store.ListArchives(ctx) if err != nil { return nil, err } archive := make([]response.ArchiveYear, 0) for _, item := range list { y := item.PublishedAt.Year() m := int(item.PublishedAt.Month()) if len(archive) == 0 || archive[len(archive)-1].Year != y { archive = append(archive, response.ArchiveYear{ Year: y, Total: 0, ArchiveMonth: make([]response.ArchiveMonth, 0), }) } // 获取索引 lastYearIndex := len(archive) - 1 months := archive[lastYearIndex].ArchiveMonth if len(months) == 0 || months[len(months)-1].Month != m { archive[lastYearIndex].ArchiveMonth = append(archive[lastYearIndex].ArchiveMonth, response.ArchiveMonth{ Month: m, Archive: make([]response.ArchivePost, 0), }) } lastMonthIndex := len(archive[lastYearIndex].ArchiveMonth) - 1 categoryName := "" if item.CategoryName != nil { categoryName = *item.CategoryName } archive[lastYearIndex].ArchiveMonth[lastMonthIndex].Archive = append(archive[lastYearIndex].ArchiveMonth[lastMonthIndex].Archive, response.ArchivePost{ ID: item.ID, Slug: item.Slug, Title: item.Title, PublishedAt: item.PublishedAt, PublishedAtDisplay: item.PublishedAt.Format("01-02"), CategoryName: categoryName, }) archive[lastYearIndex].Total++ } return archive, nil } func (s *PostService) ListPostTags(ctx context.Context) ([]sqlc.Tag, error) { return s.store.ListAllTags(ctx) } func (s *PostService) GetPostsForSitemap(ctx context.Context) ([]sqlc.GetPostsForSitemapRow, error) { return s.store.GetPostsForSitemap(ctx) }