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
135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"wx_service/internal/marketing/model"
|
|
"wx_service/internal/marketing/repository"
|
|
)
|
|
|
|
var (
|
|
ErrTemplateTitleRequired = errors.New("模板名称不能为空")
|
|
ErrTemplateTitleTooLong = errors.New("模板名称不能超过100个字符")
|
|
ErrTemplateImageRequired = errors.New("模板图片不能为空")
|
|
ErrTemplateCategoryRequired = errors.New("请选择所属分类")
|
|
)
|
|
|
|
type TemplateService struct {
|
|
repo *repository.TemplateRepository
|
|
}
|
|
|
|
func NewTemplateService(repo *repository.TemplateRepository) *TemplateService {
|
|
return &TemplateService{repo: repo}
|
|
}
|
|
|
|
type TemplateRequest struct {
|
|
CategoryID uint `json:"category_id"`
|
|
Title string `json:"title"`
|
|
ImageURL string `json:"image_url"`
|
|
ThumbnailURL string `json:"thumbnail_url"`
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
SortOrder int `json:"sort_order"`
|
|
Status *int `json:"status"`
|
|
}
|
|
|
|
type TemplateListResponse struct {
|
|
Templates []model.MarketingTemplate `json:"templates"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
}
|
|
|
|
func (s *TemplateService) Create(req TemplateRequest) (*model.MarketingTemplate, error) {
|
|
if err := validateTemplateRequest(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
status := 1
|
|
if req.Status != nil {
|
|
status = *req.Status
|
|
}
|
|
|
|
tpl := &model.MarketingTemplate{
|
|
CategoryID: req.CategoryID,
|
|
Title: strings.TrimSpace(req.Title),
|
|
ImageURL: strings.TrimSpace(req.ImageURL),
|
|
ThumbnailURL: strings.TrimSpace(req.ThumbnailURL),
|
|
Width: req.Width,
|
|
Height: req.Height,
|
|
SortOrder: req.SortOrder,
|
|
Status: status,
|
|
}
|
|
|
|
if err := s.repo.Create(tpl); err != nil {
|
|
return nil, err
|
|
}
|
|
return tpl, nil
|
|
}
|
|
|
|
func (s *TemplateService) Update(id uint, req TemplateRequest) (*model.MarketingTemplate, error) {
|
|
if err := validateTemplateRequest(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tpl, err := s.repo.FindByID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tpl.CategoryID = req.CategoryID
|
|
tpl.Title = strings.TrimSpace(req.Title)
|
|
tpl.ImageURL = strings.TrimSpace(req.ImageURL)
|
|
tpl.ThumbnailURL = strings.TrimSpace(req.ThumbnailURL)
|
|
tpl.Width = req.Width
|
|
tpl.Height = req.Height
|
|
tpl.SortOrder = req.SortOrder
|
|
if req.Status != nil {
|
|
tpl.Status = *req.Status
|
|
}
|
|
|
|
if err := s.repo.Update(tpl); err != nil {
|
|
return nil, err
|
|
}
|
|
return tpl, nil
|
|
}
|
|
|
|
func (s *TemplateService) Delete(id uint) error {
|
|
return s.repo.Delete(id)
|
|
}
|
|
|
|
func (s *TemplateService) GetByID(id uint) (*model.MarketingTemplate, error) {
|
|
return s.repo.FindByID(id)
|
|
}
|
|
|
|
func (s *TemplateService) List(params repository.TemplateListParams) (*TemplateListResponse, error) {
|
|
templates, total, err := s.repo.FindList(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &TemplateListResponse{
|
|
Templates: templates,
|
|
Total: total,
|
|
Page: params.Page,
|
|
PageSize: params.PageSize,
|
|
}, nil
|
|
}
|
|
|
|
func validateTemplateRequest(req TemplateRequest) error {
|
|
title := strings.TrimSpace(req.Title)
|
|
if title == "" {
|
|
return ErrTemplateTitleRequired
|
|
}
|
|
if len(title) > 100 {
|
|
return ErrTemplateTitleTooLong
|
|
}
|
|
if strings.TrimSpace(req.ImageURL) == "" {
|
|
return ErrTemplateImageRequired
|
|
}
|
|
if req.CategoryID == 0 {
|
|
return ErrTemplateCategoryRequired
|
|
}
|
|
return nil
|
|
}
|