ac49e1458c
- 新增 marketing 模块:model/repository/service/handler 四层架构 - 数据模型:marketing_categories、marketing_templates、marketing_user_downloads - 小程序端接口:分类列表、模板列表/详情、下载记录、广告回调 - 管理后台接口:分类/模板 CRUD、下载统计(X-Admin-Token 鉴权) - 路由注册:接入现有 AuthMiddleware,新增 AdminTokenMiddleware - Web 管理后台:单页面 Vue3 + Element Plus(分类管理、模板管理、数据概览) Closes #37, #38, #39, #40 Made-with: Cursor
104 lines
2.9 KiB
Go
104 lines
2.9 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"wx_service/internal/marketing/model"
|
|
"wx_service/internal/marketing/repository"
|
|
)
|
|
|
|
var (
|
|
ErrDownloadTemplateRequired = errors.New("请选择模板")
|
|
)
|
|
|
|
type DownloadService struct {
|
|
downloadRepo *repository.DownloadRepository
|
|
templateRepo *repository.TemplateRepository
|
|
}
|
|
|
|
func NewDownloadService(downloadRepo *repository.DownloadRepository, templateRepo *repository.TemplateRepository) *DownloadService {
|
|
return &DownloadService{
|
|
downloadRepo: downloadRepo,
|
|
templateRepo: templateRepo,
|
|
}
|
|
}
|
|
|
|
type CreateDownloadRequest struct {
|
|
TemplateID uint `json:"template_id"`
|
|
LogoURL string `json:"logo_url"`
|
|
LogoX float64 `json:"logo_x"`
|
|
LogoY float64 `json:"logo_y"`
|
|
LogoW float64 `json:"logo_w"`
|
|
LogoH float64 `json:"logo_h"`
|
|
}
|
|
|
|
type DownloadListResponse struct {
|
|
Downloads []model.MarketingDownload `json:"downloads"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
}
|
|
|
|
func (s *DownloadService) Create(userID uint, req CreateDownloadRequest) (*model.MarketingDownload, error) {
|
|
if req.TemplateID == 0 {
|
|
return nil, ErrDownloadTemplateRequired
|
|
}
|
|
|
|
dl := &model.MarketingDownload{
|
|
UserID: userID,
|
|
TemplateID: req.TemplateID,
|
|
LogoURL: req.LogoURL,
|
|
LogoX: req.LogoX,
|
|
LogoY: req.LogoY,
|
|
LogoW: req.LogoW,
|
|
LogoH: req.LogoH,
|
|
}
|
|
|
|
if err := s.downloadRepo.Create(dl); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_ = s.templateRepo.IncrementDownloadCount(req.TemplateID)
|
|
return dl, nil
|
|
}
|
|
|
|
func (s *DownloadService) MarkAdCompleted(downloadID uint) error {
|
|
return s.downloadRepo.MarkAdCompleted(downloadID)
|
|
}
|
|
|
|
func (s *DownloadService) ListByUser(userID uint, page, pageSize int) (*DownloadListResponse, error) {
|
|
downloads, total, err := s.downloadRepo.FindByUser(userID, page, pageSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &DownloadListResponse{
|
|
Downloads: downloads,
|
|
Total: total,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}, nil
|
|
}
|
|
|
|
func (s *DownloadService) GetStats() (*repository.DownloadStats, error) {
|
|
return s.downloadRepo.GetStats()
|
|
}
|
|
|
|
// IsBadRequestError checks if the error is a client-side validation error.
|
|
func IsBadRequestError(err error) bool {
|
|
return errors.Is(err, ErrDownloadTemplateRequired) ||
|
|
errors.Is(err, ErrCategoryNameRequired) ||
|
|
errors.Is(err, ErrCategoryNameTooLong) ||
|
|
errors.Is(err, ErrCategoryHasTemplates) ||
|
|
errors.Is(err, ErrTemplateTitleRequired) ||
|
|
errors.Is(err, ErrTemplateTitleTooLong) ||
|
|
errors.Is(err, ErrTemplateImageRequired) ||
|
|
errors.Is(err, ErrTemplateCategoryRequired)
|
|
}
|
|
|
|
// IsNotFoundError checks if the error is a not-found error.
|
|
func IsNotFoundError(err error) bool {
|
|
return errors.Is(err, repository.ErrCategoryNotFound) ||
|
|
errors.Is(err, repository.ErrTemplateNotFound) ||
|
|
errors.Is(err, repository.ErrDownloadNotFound)
|
|
}
|