temp
This commit is contained in:
@@ -0,0 +1,495 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CategoryApplicationServiceImpl 分类应用服务实现
|
||||
type CategoryApplicationServiceImpl struct {
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCategoryApplicationService 创建分类应用服务
|
||||
func NewCategoryApplicationService(
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
logger *zap.Logger,
|
||||
) CategoryApplicationService {
|
||||
return &CategoryApplicationServiceImpl{
|
||||
categoryRepo: categoryRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCategory 创建分类
|
||||
func (s *CategoryApplicationServiceImpl) CreateCategory(ctx context.Context, cmd *commands.CreateCategoryCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateCreateCategory(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 检查父分类是否存在
|
||||
if cmd.ParentID != nil && *cmd.ParentID != "" {
|
||||
_, err := s.categoryRepo.GetByID(ctx, *cmd.ParentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("父分类不存在: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 创建分类实体
|
||||
category := entities.ProductCategory{
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
ParentID: cmd.ParentID,
|
||||
Level: cmd.Level,
|
||||
Sort: cmd.Sort,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
}
|
||||
|
||||
// 4. 保存到仓储
|
||||
_, err := s.categoryRepo.Create(ctx, category)
|
||||
if err != nil {
|
||||
s.logger.Error("创建分类失败", zap.Error(err))
|
||||
return fmt.Errorf("创建分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("创建分类成功", zap.String("name", cmd.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCategory 更新分类
|
||||
func (s *CategoryApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd *commands.UpdateCategoryCommand) error {
|
||||
// 1. 获取现有分类
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 更新字段
|
||||
if cmd.Name != "" {
|
||||
category.Name = cmd.Name
|
||||
}
|
||||
if cmd.Code != "" {
|
||||
category.Code = cmd.Code
|
||||
}
|
||||
if cmd.Description != "" {
|
||||
category.Description = cmd.Description
|
||||
}
|
||||
if cmd.ParentID != nil {
|
||||
category.ParentID = cmd.ParentID
|
||||
}
|
||||
if cmd.Level > 0 {
|
||||
category.Level = cmd.Level
|
||||
}
|
||||
if cmd.Sort > 0 {
|
||||
category.Sort = cmd.Sort
|
||||
}
|
||||
if cmd.IsEnabled != nil {
|
||||
category.IsEnabled = *cmd.IsEnabled
|
||||
}
|
||||
if cmd.IsVisible != nil {
|
||||
category.IsVisible = *cmd.IsVisible
|
||||
}
|
||||
|
||||
// 3. 保存到仓储
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("更新分类失败", zap.Error(err))
|
||||
return fmt.Errorf("更新分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCategory 删除分类
|
||||
func (s *CategoryApplicationServiceImpl) DeleteCategory(ctx context.Context, cmd *commands.DeleteCategoryCommand) error {
|
||||
// 1. 检查分类是否存在
|
||||
_, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查是否有子分类
|
||||
children, err := s.categoryRepo.FindByParentID(ctx, &cmd.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("检查子分类失败", zap.Error(err))
|
||||
return fmt.Errorf("检查子分类失败: %w", err)
|
||||
}
|
||||
if len(children) > 0 {
|
||||
return fmt.Errorf("分类下有子分类,无法删除")
|
||||
}
|
||||
|
||||
// 3. 删除分类
|
||||
if err := s.categoryRepo.Delete(ctx, cmd.ID); err != nil {
|
||||
s.logger.Error("删除分类失败", zap.Error(err))
|
||||
return fmt.Errorf("删除分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("删除分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCategoryByID 根据ID获取分类
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoryByID(ctx context.Context, query *queries.GetCategoryQuery) (*responses.CategoryInfoResponse, error) {
|
||||
var category entities.ProductCategory
|
||||
var err error
|
||||
|
||||
if query.ID != "" {
|
||||
category, err = s.categoryRepo.GetByID(ctx, query.ID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("分类ID不能为空")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
response := s.convertToCategoryInfoResponse(&category)
|
||||
|
||||
// 加载父分类信息
|
||||
if category.ParentID != nil && *category.ParentID != "" {
|
||||
parent, err := s.categoryRepo.GetByID(ctx, *category.ParentID)
|
||||
if err == nil {
|
||||
parentResponse := s.convertToCategoryInfoResponse(&parent)
|
||||
response.Parent = parentResponse
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ListCategories 获取分类列表
|
||||
func (s *CategoryApplicationServiceImpl) ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) (*responses.CategoryListResponse, error) {
|
||||
// 构建仓储查询
|
||||
repoQuery := &repoQueries.ListCategoriesQuery{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
ParentID: query.ParentID,
|
||||
Level: query.Level,
|
||||
IsEnabled: query.IsEnabled,
|
||||
IsVisible: query.IsVisible,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
}
|
||||
|
||||
// 调用仓储
|
||||
categories, total, err := s.categoryRepo.ListCategories(ctx, repoQuery)
|
||||
if err != nil {
|
||||
s.logger.Error("获取分类列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取分类列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]responses.CategoryInfoResponse, len(categories))
|
||||
for i, category := range categories {
|
||||
items[i] = *s.convertToCategoryInfoResponse(category)
|
||||
}
|
||||
|
||||
return &responses.CategoryListResponse{
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EnableCategory 启用分类
|
||||
func (s *CategoryApplicationServiceImpl) EnableCategory(ctx context.Context, cmd *commands.EnableCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsEnabled = true
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("启用分类失败", zap.Error(err))
|
||||
return fmt.Errorf("启用分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("启用分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableCategory 禁用分类
|
||||
func (s *CategoryApplicationServiceImpl) DisableCategory(ctx context.Context, cmd *commands.DisableCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsEnabled = false
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("禁用分类失败", zap.Error(err))
|
||||
return fmt.Errorf("禁用分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("禁用分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowCategory 显示分类
|
||||
func (s *CategoryApplicationServiceImpl) ShowCategory(ctx context.Context, cmd *commands.ShowCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsVisible = true
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("显示分类失败", zap.Error(err))
|
||||
return fmt.Errorf("显示分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("显示分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// HideCategory 隐藏分类
|
||||
func (s *CategoryApplicationServiceImpl) HideCategory(ctx context.Context, cmd *commands.HideCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsVisible = false
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("隐藏分类失败", zap.Error(err))
|
||||
return fmt.Errorf("隐藏分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("隐藏分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveCategory 移动分类
|
||||
func (s *CategoryApplicationServiceImpl) MoveCategory(ctx context.Context, cmd *commands.MoveCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查目标父分类是否存在
|
||||
if cmd.ParentID != nil && *cmd.ParentID != "" {
|
||||
_, err := s.categoryRepo.GetByID(ctx, *cmd.ParentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("目标父分类不存在: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
category.ParentID = cmd.ParentID
|
||||
if cmd.Sort > 0 {
|
||||
category.Sort = cmd.Sort
|
||||
}
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("移动分类失败", zap.Error(err))
|
||||
return fmt.Errorf("移动分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("移动分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCategoryTree 获取分类树
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoryTree(ctx context.Context, query *queries.GetCategoryTreeQuery) (*responses.CategoryTreeResponse, error) {
|
||||
categories, err := s.categoryRepo.GetCategoryTree(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取分类树失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取分类树失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
tree := s.buildCategoryTree(categories, query.IncludeDisabled, query.IncludeHidden)
|
||||
|
||||
return &responses.CategoryTreeResponse{
|
||||
Categories: tree,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCategoriesByLevel 根据层级获取分类
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoriesByLevel(ctx context.Context, query *queries.GetCategoriesByLevelQuery) ([]*responses.CategoryInfoResponse, error) {
|
||||
categories, err := s.categoryRepo.FindCategoriesByLevel(ctx, query.Level)
|
||||
if err != nil {
|
||||
s.logger.Error("根据层级获取分类失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("根据层级获取分类失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.CategoryInfoResponse, len(categories))
|
||||
for i, category := range categories {
|
||||
items[i] = s.convertToCategoryInfoResponse(category)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetCategoryPath 获取分类路径
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoryPath(ctx context.Context, query *queries.GetCategoryPathQuery) (*responses.CategoryPathResponse, error) {
|
||||
path, err := s.buildCategoryPath(ctx, query.CategoryID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取分类路径失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取分类路径失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为正确的类型
|
||||
pathItems := make([]responses.CategorySimpleResponse, len(path))
|
||||
for i, item := range path {
|
||||
pathItems[i] = *item
|
||||
}
|
||||
|
||||
return &responses.CategoryPathResponse{
|
||||
Path: pathItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCategoryStats 获取分类统计
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoryStats(ctx context.Context) (*responses.CategoryStatsResponse, error) {
|
||||
// 使用正确的CountOptions
|
||||
total, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{})
|
||||
if err != nil {
|
||||
s.logger.Error("获取分类总数失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取分类总数失败: %w", err)
|
||||
}
|
||||
|
||||
enabled, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{
|
||||
Filters: map[string]interface{}{"is_enabled": true},
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("获取启用分类数失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取启用分类数失败: %w", err)
|
||||
}
|
||||
|
||||
visible, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{
|
||||
Filters: map[string]interface{}{"is_visible": true},
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("获取可见分类数失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取可见分类数失败: %w", err)
|
||||
}
|
||||
|
||||
return &responses.CategoryStatsResponse{
|
||||
TotalCategories: total,
|
||||
EnabledCategories: enabled,
|
||||
VisibleCategories: visible,
|
||||
RootCategories: total - enabled, // 简化计算,实际应该查询根分类数量
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) validateCreateCategory(cmd *commands.CreateCategoryCommand) error {
|
||||
if cmd.Name == "" {
|
||||
return fmt.Errorf("分类名称不能为空")
|
||||
}
|
||||
if cmd.Level <= 0 {
|
||||
return fmt.Errorf("分类层级必须大于0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) convertToCategoryInfoResponse(category *entities.ProductCategory) *responses.CategoryInfoResponse {
|
||||
return &responses.CategoryInfoResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Code: category.Code,
|
||||
Description: category.Description,
|
||||
ParentID: category.ParentID,
|
||||
Level: category.Level,
|
||||
Sort: category.Sort,
|
||||
IsEnabled: category.IsEnabled,
|
||||
IsVisible: category.IsVisible,
|
||||
CreatedAt: category.CreatedAt,
|
||||
UpdatedAt: category.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
|
||||
return &responses.CategorySimpleResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Code: category.Code,
|
||||
ParentID: category.ParentID,
|
||||
Level: category.Level,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) buildCategoryTree(categories []*entities.ProductCategory, includeDisabled, includeHidden bool) []responses.CategoryInfoResponse {
|
||||
// 构建ID到分类的映射
|
||||
categoryMap := make(map[string]*entities.ProductCategory)
|
||||
for _, category := range categories {
|
||||
// 根据过滤条件决定是否包含
|
||||
if !includeDisabled && !category.IsEnabled {
|
||||
continue
|
||||
}
|
||||
if !includeHidden && !category.IsVisible {
|
||||
continue
|
||||
}
|
||||
categoryMap[category.ID] = category
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
var roots []responses.CategoryInfoResponse
|
||||
for _, category := range categoryMap {
|
||||
if category.ParentID == nil || *category.ParentID == "" {
|
||||
// 根节点
|
||||
root := *s.convertToCategoryInfoResponse(category)
|
||||
root.Children = s.findChildren(category.ID, categoryMap)
|
||||
roots = append(roots, root)
|
||||
}
|
||||
}
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) findChildren(parentID string, categoryMap map[string]*entities.ProductCategory) []responses.CategoryInfoResponse {
|
||||
var children []responses.CategoryInfoResponse
|
||||
for _, category := range categoryMap {
|
||||
if category.ParentID != nil && *category.ParentID == parentID {
|
||||
child := *s.convertToCategoryInfoResponse(category)
|
||||
child.Children = s.findChildren(category.ID, categoryMap)
|
||||
children = append(children, child)
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) buildCategoryPath(ctx context.Context, categoryID string) ([]*responses.CategorySimpleResponse, error) {
|
||||
var path []*responses.CategorySimpleResponse
|
||||
|
||||
currentID := categoryID
|
||||
for currentID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, currentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取分类失败: %w", err)
|
||||
}
|
||||
|
||||
path = append([]*responses.CategorySimpleResponse{
|
||||
s.convertToCategorySimpleResponse(&category),
|
||||
}, path...)
|
||||
|
||||
if category.ParentID != nil {
|
||||
currentID = *category.ParentID
|
||||
} else {
|
||||
currentID = ""
|
||||
}
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package commands
|
||||
|
||||
// CreateCategoryCommand 创建分类命令
|
||||
type CreateCategoryCommand struct {
|
||||
Name string `json:"name" binding:"required" comment:"分类名称"`
|
||||
Code string `json:"code" binding:"required" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" binding:"min=1" comment:"分类层级"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
}
|
||||
|
||||
// UpdateCategoryCommand 更新分类命令
|
||||
type UpdateCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" binding:"min=1" comment:"分类层级"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled *bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `json:"is_visible" comment:"是否展示"`
|
||||
}
|
||||
|
||||
// DeleteCategoryCommand 删除分类命令
|
||||
type DeleteCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// EnableCategoryCommand 启用分类命令
|
||||
type EnableCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// DisableCategoryCommand 禁用分类命令
|
||||
type DisableCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// ShowCategoryCommand 显示分类命令
|
||||
type ShowCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// HideCategoryCommand 隐藏分类命令
|
||||
type HideCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// MoveCategoryCommand 移动分类命令
|
||||
type MoveCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
ParentID *string `json:"parent_id" comment:"新的父分类ID"`
|
||||
Sort int `json:"sort" comment:"新的排序"`
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package commands
|
||||
|
||||
// CreateProductCommand 创建产品命令
|
||||
type CreateProductCommand struct {
|
||||
Name string `json:"name" binding:"required" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
}
|
||||
|
||||
// UpdateProductCommand 更新产品命令
|
||||
type UpdateProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"产品价格"`
|
||||
IsEnabled *bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
}
|
||||
|
||||
// DeleteProductCommand 删除产品命令
|
||||
type DeleteProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// EnableProductCommand 启用产品命令
|
||||
type EnableProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// DisableProductCommand 禁用产品命令
|
||||
type DisableProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// ShowProductCommand 显示产品命令
|
||||
type ShowProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// HideProductCommand 隐藏产品命令
|
||||
type HideProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// UpdateProductSEOCommand 更新产品SEO信息命令
|
||||
type UpdateProductSEOCommand struct {
|
||||
ID string `json:"-"`
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package commands
|
||||
|
||||
// CreateSubscriptionCommand 创建订阅命令
|
||||
type CreateSubscriptionCommand struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户ID"`
|
||||
ProductID string `json:"product_id" binding:"required" comment:"产品ID"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"订阅价格"`
|
||||
APILimit int64 `json:"api_limit" comment:"API调用限制"`
|
||||
AutoRenew bool `json:"auto_renew" comment:"是否自动续费"`
|
||||
Duration string `json:"duration" comment:"订阅时长"`
|
||||
}
|
||||
|
||||
// UpdateSubscriptionCommand 更新订阅命令
|
||||
type UpdateSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"订阅价格"`
|
||||
APILimit int64 `json:"api_limit" comment:"API调用限制"`
|
||||
AutoRenew *bool `json:"auto_renew" comment:"是否自动续费"`
|
||||
}
|
||||
|
||||
// CancelSubscriptionCommand 取消订阅命令
|
||||
type CancelSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// RenewSubscriptionCommand 续费订阅命令
|
||||
type RenewSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
Duration string `json:"duration" binding:"required" comment:"续费时长"`
|
||||
}
|
||||
|
||||
// ActivateSubscriptionCommand 激活订阅命令
|
||||
type ActivateSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// DeactivateSubscriptionCommand 停用订阅命令
|
||||
type DeactivateSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// UpdateAPIUsageCommand 更新API使用量命令
|
||||
type UpdateAPIUsageCommand struct {
|
||||
ID string `json:"-"`
|
||||
APIUsed int64 `json:"api_used" binding:"min=0" comment:"API使用量"`
|
||||
}
|
||||
|
||||
// ResetAPIUsageCommand 重置API使用量命令
|
||||
type ResetAPIUsageCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// SetAPILimitCommand 设置API限制命令
|
||||
type SetAPILimitCommand struct {
|
||||
ID string `json:"-"`
|
||||
APILimit int64 `json:"api_limit" binding:"min=0" comment:"API调用限制"`
|
||||
}
|
||||
35
internal/application/product/dto/queries/category_queries.go
Normal file
35
internal/application/product/dto/queries/category_queries.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package queries
|
||||
|
||||
// ListCategoriesQuery 分类列表查询
|
||||
type ListCategoriesQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
ParentID *string `form:"parent_id" comment:"父分类ID"`
|
||||
Level *int `form:"level" comment:"分类层级"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// GetCategoryQuery 获取分类详情查询
|
||||
type GetCategoryQuery struct {
|
||||
ID string `uri:"id" comment:"分类ID"`
|
||||
Code string `form:"code" comment:"分类编号"`
|
||||
}
|
||||
|
||||
// GetCategoryTreeQuery 获取分类树查询
|
||||
type GetCategoryTreeQuery struct {
|
||||
IncludeDisabled bool `form:"include_disabled" comment:"是否包含禁用分类"`
|
||||
IncludeHidden bool `form:"include_hidden" comment:"是否包含隐藏分类"`
|
||||
}
|
||||
|
||||
// GetCategoriesByLevelQuery 根据层级获取分类查询
|
||||
type GetCategoriesByLevelQuery struct {
|
||||
Level int `form:"level" binding:"min=1" comment:"分类层级"`
|
||||
}
|
||||
|
||||
// GetCategoryPathQuery 获取分类路径查询
|
||||
type GetCategoryPathQuery struct {
|
||||
CategoryID string `uri:"category_id" binding:"required" comment:"分类ID"`
|
||||
}
|
||||
47
internal/application/product/dto/queries/product_queries.go
Normal file
47
internal/application/product/dto/queries/product_queries.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package queries
|
||||
|
||||
// ListProductsQuery 产品列表查询
|
||||
type ListProductsQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
Keyword string `form:"keyword" comment:"搜索关键词"`
|
||||
CategoryID string `form:"category_id" comment:"分类ID"`
|
||||
MinPrice *float64 `form:"min_price" comment:"最低价格"`
|
||||
MaxPrice *float64 `form:"max_price" comment:"最高价格"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `form:"is_package" comment:"是否组合包"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// SearchProductsQuery 产品搜索查询
|
||||
type SearchProductsQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
Keyword string `form:"keyword" comment:"搜索关键词"`
|
||||
CategoryID string `form:"category_id" comment:"分类ID"`
|
||||
MinPrice *float64 `form:"min_price" comment:"最低价格"`
|
||||
MaxPrice *float64 `form:"max_price" comment:"最高价格"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `form:"is_package" comment:"是否组合包"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// GetProductQuery 获取产品详情查询
|
||||
type GetProductQuery struct {
|
||||
ID string `uri:"id" comment:"产品ID"`
|
||||
Code string `form:"code" comment:"产品编号"`
|
||||
}
|
||||
|
||||
// GetProductsByIDsQuery 根据ID列表获取产品查询
|
||||
type GetProductsByIDsQuery struct {
|
||||
IDs []string `form:"ids" binding:"required" comment:"产品ID列表"`
|
||||
}
|
||||
|
||||
// GetSubscribableProductsQuery 获取可订阅产品查询
|
||||
type GetSubscribableProductsQuery struct {
|
||||
UserID string `form:"user_id" binding:"required" comment:"用户ID"`
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package queries
|
||||
|
||||
import "tyapi-server/internal/domains/product/entities"
|
||||
|
||||
// ListSubscriptionsQuery 订阅列表查询
|
||||
type ListSubscriptionsQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
UserID string `form:"user_id" comment:"用户ID"`
|
||||
ProductID string `form:"product_id" comment:"产品ID"`
|
||||
Status entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// GetSubscriptionQuery 获取订阅详情查询
|
||||
type GetSubscriptionQuery struct {
|
||||
ID string `uri:"id" binding:"required" comment:"订阅ID"`
|
||||
}
|
||||
|
||||
// GetUserSubscriptionsQuery 获取用户订阅查询
|
||||
type GetUserSubscriptionsQuery struct {
|
||||
UserID string `form:"user_id" binding:"required" comment:"用户ID"`
|
||||
Status *entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
|
||||
}
|
||||
|
||||
// GetProductSubscriptionsQuery 获取产品订阅查询
|
||||
type GetProductSubscriptionsQuery struct {
|
||||
ProductID string `form:"product_id" binding:"required" comment:"产品ID"`
|
||||
Status *entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
|
||||
}
|
||||
|
||||
// GetActiveSubscriptionsQuery 获取活跃订阅查询
|
||||
type GetActiveSubscriptionsQuery struct {
|
||||
UserID string `form:"user_id" comment:"用户ID"`
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// CategoryInfoResponse 分类详情响应
|
||||
type CategoryInfoResponse struct {
|
||||
ID string `json:"id" comment:"分类ID"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" comment:"分类层级"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
|
||||
// 关联信息
|
||||
Parent *CategoryInfoResponse `json:"parent,omitempty" comment:"父分类"`
|
||||
Children []CategoryInfoResponse `json:"children,omitempty" comment:"子分类"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// CategoryListResponse 分类列表响应
|
||||
type CategoryListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []CategoryInfoResponse `json:"items" comment:"分类列表"`
|
||||
}
|
||||
|
||||
// CategoryTreeResponse 分类树响应
|
||||
type CategoryTreeResponse struct {
|
||||
Categories []CategoryInfoResponse `json:"categories" comment:"分类树"`
|
||||
}
|
||||
|
||||
// CategorySimpleResponse 分类简单信息响应
|
||||
type CategorySimpleResponse struct {
|
||||
ID string `json:"id" comment:"分类ID"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" comment:"分类层级"`
|
||||
}
|
||||
|
||||
// CategoryPathResponse 分类路径响应
|
||||
type CategoryPathResponse struct {
|
||||
Path []CategorySimpleResponse `json:"path" comment:"分类路径"`
|
||||
}
|
||||
|
||||
// CategoryStatsResponse 分类统计响应
|
||||
type CategoryStatsResponse struct {
|
||||
TotalCategories int64 `json:"total_categories" comment:"分类总数"`
|
||||
RootCategories int64 `json:"root_categories" comment:"根分类数"`
|
||||
EnabledCategories int64 `json:"enabled_categories" comment:"启用分类数"`
|
||||
VisibleCategories int64 `json:"visible_categories" comment:"可见分类数"`
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// ProductInfoResponse 产品详情响应
|
||||
type ProductInfoResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
|
||||
// 关联信息
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// ProductListResponse 产品列表响应
|
||||
type ProductListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []ProductInfoResponse `json:"items" comment:"产品列表"`
|
||||
}
|
||||
|
||||
// ProductSearchResponse 产品搜索响应
|
||||
type ProductSearchResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []ProductInfoResponse `json:"items" comment:"产品列表"`
|
||||
}
|
||||
|
||||
// ProductSimpleResponse 产品简单信息响应
|
||||
type ProductSimpleResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
}
|
||||
|
||||
// ProductStatsResponse 产品统计响应
|
||||
type ProductStatsResponse struct {
|
||||
TotalProducts int64 `json:"total_products" comment:"产品总数"`
|
||||
EnabledProducts int64 `json:"enabled_products" comment:"启用产品数"`
|
||||
VisibleProducts int64 `json:"visible_products" comment:"可见产品数"`
|
||||
PackageProducts int64 `json:"package_products" comment:"组合包产品数"`
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SubscriptionInfoResponse 订阅详情响应
|
||||
type SubscriptionInfoResponse struct {
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
UserID string `json:"user_id" comment:"用户ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
Price float64 `json:"price" comment:"订阅价格"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
|
||||
// 关联信息
|
||||
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// SubscriptionListResponse 订阅列表响应
|
||||
type SubscriptionListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []SubscriptionInfoResponse `json:"items" comment:"订阅列表"`
|
||||
}
|
||||
|
||||
// SubscriptionSimpleResponse 订阅简单信息响应
|
||||
type SubscriptionSimpleResponse struct {
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
Price float64 `json:"price" comment:"订阅价格"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
}
|
||||
|
||||
// SubscriptionUsageResponse 订阅使用情况响应
|
||||
type SubscriptionUsageResponse struct {
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
}
|
||||
|
||||
// SubscriptionStatsResponse 订阅统计响应
|
||||
type SubscriptionStatsResponse struct {
|
||||
TotalSubscriptions int64 `json:"total_subscriptions" comment:"订阅总数"`
|
||||
TotalRevenue float64 `json:"total_revenue" comment:"总收入"`
|
||||
}
|
||||
78
internal/application/product/product_application_service.go
Normal file
78
internal/application/product/product_application_service.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
)
|
||||
|
||||
// ProductApplicationService 产品应用服务接口
|
||||
type ProductApplicationService interface {
|
||||
// 产品管理
|
||||
CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error
|
||||
UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error
|
||||
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
|
||||
GetProductByID(ctx context.Context, query *queries.GetProductQuery) (*responses.ProductInfoResponse, error)
|
||||
ListProducts(ctx context.Context, query *queries.ListProductsQuery) (*responses.ProductListResponse, error)
|
||||
GetProductsByIDs(ctx context.Context, query *queries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error)
|
||||
|
||||
// 产品状态管理
|
||||
EnableProduct(ctx context.Context, cmd *commands.EnableProductCommand) error
|
||||
DisableProduct(ctx context.Context, cmd *commands.DisableProductCommand) error
|
||||
ShowProduct(ctx context.Context, cmd *commands.ShowProductCommand) error
|
||||
HideProduct(ctx context.Context, cmd *commands.HideProductCommand) error
|
||||
|
||||
// SEO管理
|
||||
UpdateProductSEO(ctx context.Context, cmd *commands.UpdateProductSEOCommand) error
|
||||
|
||||
// 业务查询
|
||||
GetSubscribableProducts(ctx context.Context, query *queries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error)
|
||||
GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error)
|
||||
}
|
||||
|
||||
// CategoryApplicationService 分类应用服务接口
|
||||
type CategoryApplicationService interface {
|
||||
// 分类管理
|
||||
CreateCategory(ctx context.Context, cmd *commands.CreateCategoryCommand) error
|
||||
UpdateCategory(ctx context.Context, cmd *commands.UpdateCategoryCommand) error
|
||||
DeleteCategory(ctx context.Context, cmd *commands.DeleteCategoryCommand) error
|
||||
GetCategoryByID(ctx context.Context, query *queries.GetCategoryQuery) (*responses.CategoryInfoResponse, error)
|
||||
ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) (*responses.CategoryListResponse, error)
|
||||
|
||||
// 分类状态管理
|
||||
EnableCategory(ctx context.Context, cmd *commands.EnableCategoryCommand) error
|
||||
DisableCategory(ctx context.Context, cmd *commands.DisableCategoryCommand) error
|
||||
ShowCategory(ctx context.Context, cmd *commands.ShowCategoryCommand) error
|
||||
HideCategory(ctx context.Context, cmd *commands.HideCategoryCommand) error
|
||||
|
||||
// 分类结构管理
|
||||
MoveCategory(ctx context.Context, cmd *commands.MoveCategoryCommand) error
|
||||
GetCategoryTree(ctx context.Context, query *queries.GetCategoryTreeQuery) (*responses.CategoryTreeResponse, error)
|
||||
GetCategoriesByLevel(ctx context.Context, query *queries.GetCategoriesByLevelQuery) ([]*responses.CategoryInfoResponse, error)
|
||||
GetCategoryPath(ctx context.Context, query *queries.GetCategoryPathQuery) (*responses.CategoryPathResponse, error)
|
||||
|
||||
// 统计
|
||||
GetCategoryStats(ctx context.Context) (*responses.CategoryStatsResponse, error)
|
||||
}
|
||||
|
||||
// SubscriptionApplicationService 订阅应用服务接口
|
||||
type SubscriptionApplicationService interface {
|
||||
// 订阅管理
|
||||
CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error
|
||||
UpdateSubscription(ctx context.Context, cmd *commands.UpdateSubscriptionCommand) error
|
||||
GetSubscriptionByID(ctx context.Context, query *queries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error)
|
||||
ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error)
|
||||
|
||||
// API使用管理
|
||||
UpdateAPIUsage(ctx context.Context, cmd *commands.UpdateAPIUsageCommand) error
|
||||
ResetAPIUsage(ctx context.Context, cmd *commands.ResetAPIUsageCommand) error
|
||||
|
||||
// 业务查询
|
||||
GetUserSubscriptions(ctx context.Context, query *queries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
|
||||
GetProductSubscriptions(ctx context.Context, query *queries.GetProductSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
|
||||
GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error)
|
||||
|
||||
// 统计
|
||||
GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error)
|
||||
}
|
||||
452
internal/application/product/product_application_service_impl.go
Normal file
452
internal/application/product/product_application_service_impl.go
Normal file
@@ -0,0 +1,452 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProductApplicationServiceImpl 产品应用服务实现
|
||||
type ProductApplicationServiceImpl struct {
|
||||
productRepo repositories.ProductRepository
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
productService *services.ProductService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductApplicationService 创建产品应用服务
|
||||
func NewProductApplicationService(
|
||||
productRepo repositories.ProductRepository,
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
productService *services.ProductService,
|
||||
logger *zap.Logger,
|
||||
) ProductApplicationService {
|
||||
return &ProductApplicationServiceImpl{
|
||||
productRepo: productRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
productService: productService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateProduct 创建产品
|
||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateCreateProduct(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 验证产品编号唯一性
|
||||
if err := s.productService.ValidateProductCode(cmd.Code, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 创建产品实体
|
||||
product := &entities.Product{
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: cmd.Price,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
}
|
||||
|
||||
// 4. 设置SEO信息
|
||||
product.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords)
|
||||
|
||||
// 5. 调用领域服务验证
|
||||
if err := s.productService.ValidateProduct(product); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 6. 保存到仓储
|
||||
if _, err := s.productRepo.Create(ctx, *product); err != nil {
|
||||
s.logger.Error("创建产品失败", zap.Error(err))
|
||||
return fmt.Errorf("创建产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("创建产品成功", zap.String("id", product.ID), zap.String("name", product.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProduct 更新产品
|
||||
func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error {
|
||||
// 1. 获取现有产品
|
||||
existingProduct, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 参数验证
|
||||
if err := s.validateUpdateProduct(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 验证产品编号唯一性(如果修改了编号)
|
||||
if cmd.Code != "" && cmd.Code != existingProduct.Code {
|
||||
if err := s.productService.ValidateProductCode(cmd.Code, cmd.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
existingProduct.Code = cmd.Code
|
||||
}
|
||||
|
||||
// 4. 更新字段
|
||||
if cmd.Name != "" {
|
||||
existingProduct.Name = cmd.Name
|
||||
}
|
||||
if cmd.Description != "" {
|
||||
existingProduct.Description = cmd.Description
|
||||
}
|
||||
if cmd.Content != "" {
|
||||
existingProduct.Content = cmd.Content
|
||||
}
|
||||
if cmd.CategoryID != "" {
|
||||
existingProduct.CategoryID = cmd.CategoryID
|
||||
}
|
||||
if cmd.Price >= 0 {
|
||||
existingProduct.Price = cmd.Price
|
||||
}
|
||||
if cmd.IsEnabled != nil {
|
||||
existingProduct.IsEnabled = *cmd.IsEnabled
|
||||
}
|
||||
if cmd.IsVisible != nil {
|
||||
existingProduct.IsVisible = *cmd.IsVisible
|
||||
}
|
||||
if cmd.IsPackage != nil {
|
||||
existingProduct.IsPackage = *cmd.IsPackage
|
||||
}
|
||||
|
||||
// 5. 更新SEO信息
|
||||
if cmd.SEOTitle != "" || cmd.SEODescription != "" || cmd.SEOKeywords != "" {
|
||||
existingProduct.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords)
|
||||
}
|
||||
|
||||
// 6. 调用领域服务验证
|
||||
if err := s.productService.ValidateProduct(&existingProduct); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7. 保存到仓储
|
||||
if err := s.productRepo.Update(ctx, existingProduct); err != nil {
|
||||
s.logger.Error("更新产品失败", zap.Error(err))
|
||||
return fmt.Errorf("更新产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新产品成功", zap.String("id", existingProduct.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProduct 删除产品
|
||||
func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error {
|
||||
// 1. 检查产品是否存在
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查是否有活跃订阅
|
||||
subscriptions, err := s.subscriptionRepo.FindByProductID(ctx, cmd.ID)
|
||||
if err == nil && len(subscriptions) > 0 {
|
||||
return errors.New("产品存在订阅,无法删除")
|
||||
}
|
||||
|
||||
// 3. 删除产品
|
||||
if err := s.productRepo.Delete(ctx, cmd.ID); err != nil {
|
||||
s.logger.Error("删除产品失败", zap.Error(err))
|
||||
return fmt.Errorf("删除产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("删除产品成功", zap.String("id", cmd.ID), zap.String("name", product.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListProducts 获取产品列表
|
||||
func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query *appQueries.ListProductsQuery) (*responses.ProductListResponse, error) {
|
||||
// 构建仓储查询
|
||||
repoQuery := &repoQueries.ListProductsQuery{
|
||||
Keyword: query.Keyword,
|
||||
CategoryID: query.CategoryID,
|
||||
MinPrice: query.MinPrice,
|
||||
MaxPrice: query.MaxPrice,
|
||||
IsEnabled: query.IsEnabled,
|
||||
IsVisible: query.IsVisible,
|
||||
IsPackage: query.IsPackage,
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
}
|
||||
|
||||
// 调用仓储
|
||||
products, total, err := s.productRepo.ListProducts(ctx, repoQuery)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]responses.ProductInfoResponse, len(products))
|
||||
for i := range products {
|
||||
items[i] = *s.convertToProductInfoResponse(products[i])
|
||||
}
|
||||
|
||||
return &responses.ProductListResponse{
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetProductsByIDs 根据ID列表获取产品
|
||||
func (s *ProductApplicationServiceImpl) GetProductsByIDs(ctx context.Context, query *appQueries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error) {
|
||||
products, err := s.productRepo.GetByIDs(ctx, query.IDs)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.ProductInfoResponse, len(products))
|
||||
for i := range products {
|
||||
items[i] = s.convertToProductInfoResponse(&products[i])
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetSubscribableProducts 获取可订阅的产品
|
||||
func (s *ProductApplicationServiceImpl) GetSubscribableProducts(ctx context.Context, query *appQueries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error) {
|
||||
products, err := s.productRepo.FindSubscribableProducts(ctx, query.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取可订阅产品失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取可订阅产品失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.ProductInfoResponse, len(products))
|
||||
for i := range products {
|
||||
items[i] = s.convertToProductInfoResponse(products[i])
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetProductByID 根据ID获取产品
|
||||
func (s *ProductApplicationServiceImpl) GetProductByID(ctx context.Context, query *appQueries.GetProductQuery) (*responses.ProductInfoResponse, error) {
|
||||
var product *entities.Product
|
||||
var err error
|
||||
|
||||
if query.ID != "" {
|
||||
p, err := s.productRepo.GetByID(ctx, query.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
product = &p
|
||||
} else if query.Code != "" {
|
||||
product, err = s.productRepo.FindByCode(ctx, query.Code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("产品ID或编号不能为空")
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
response := s.convertToProductInfoResponse(product)
|
||||
|
||||
// 加载分类信息
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, product.CategoryID)
|
||||
if err == nil {
|
||||
response.Category = s.convertToCategoryInfoResponse(&category)
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// EnableProduct 启用产品
|
||||
func (s *ProductApplicationServiceImpl) EnableProduct(ctx context.Context, cmd *commands.EnableProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.Enable()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("启用产品失败", zap.Error(err))
|
||||
return fmt.Errorf("启用产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("启用产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableProduct 禁用产品
|
||||
func (s *ProductApplicationServiceImpl) DisableProduct(ctx context.Context, cmd *commands.DisableProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.Disable()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("禁用产品失败", zap.Error(err))
|
||||
return fmt.Errorf("禁用产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("禁用产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowProduct 显示产品
|
||||
func (s *ProductApplicationServiceImpl) ShowProduct(ctx context.Context, cmd *commands.ShowProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.Show()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("显示产品失败", zap.Error(err))
|
||||
return fmt.Errorf("显示产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("显示产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// HideProduct 隐藏产品
|
||||
func (s *ProductApplicationServiceImpl) HideProduct(ctx context.Context, cmd *commands.HideProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.Hide()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("隐藏产品失败", zap.Error(err))
|
||||
return fmt.Errorf("隐藏产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("隐藏产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProductSEO 更新产品SEO信息
|
||||
func (s *ProductApplicationServiceImpl) UpdateProductSEO(ctx context.Context, cmd *commands.UpdateProductSEOCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords)
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("更新产品SEO失败", zap.Error(err))
|
||||
return fmt.Errorf("更新产品SEO失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新产品SEO成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计
|
||||
func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error) {
|
||||
stats, err := s.productService.GetProductStats()
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品统计失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品统计失败: %w", err)
|
||||
}
|
||||
|
||||
return &responses.ProductStatsResponse{
|
||||
TotalProducts: stats["total"],
|
||||
EnabledProducts: stats["enabled"],
|
||||
VisibleProducts: stats["visible"],
|
||||
PackageProducts: 0, // 需要单独统计
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
func (s *ProductApplicationServiceImpl) validateCreateProduct(cmd *commands.CreateProductCommand) error {
|
||||
if cmd.Name == "" {
|
||||
return errors.New("产品名称不能为空")
|
||||
}
|
||||
if cmd.Code == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
if cmd.CategoryID == "" {
|
||||
return errors.New("产品分类不能为空")
|
||||
}
|
||||
if cmd.Price < 0 {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProductApplicationServiceImpl) validateUpdateProduct(cmd *commands.UpdateProductCommand) error {
|
||||
if cmd.ID == "" {
|
||||
return errors.New("产品ID不能为空")
|
||||
}
|
||||
if cmd.Price < 0 {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||
return &responses.ProductInfoResponse{
|
||||
ID: product.ID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible,
|
||||
IsPackage: product.IsPackage,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *entities.ProductCategory) *responses.CategoryInfoResponse {
|
||||
return &responses.CategoryInfoResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Code: category.Code,
|
||||
Description: category.Description,
|
||||
ParentID: category.ParentID,
|
||||
Level: category.Level,
|
||||
Sort: category.Sort,
|
||||
IsEnabled: category.IsEnabled,
|
||||
IsVisible: category.IsVisible,
|
||||
CreatedAt: category.CreatedAt,
|
||||
UpdatedAt: category.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// SubscriptionApplicationServiceImpl 订阅应用服务实现
|
||||
type SubscriptionApplicationServiceImpl struct {
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
productRepo repositories.ProductRepository
|
||||
productService *services.ProductService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSubscriptionApplicationService 创建订阅应用服务
|
||||
func NewSubscriptionApplicationService(
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
productRepo repositories.ProductRepository,
|
||||
productService *services.ProductService,
|
||||
logger *zap.Logger,
|
||||
) SubscriptionApplicationService {
|
||||
return &SubscriptionApplicationServiceImpl{
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
productRepo: productRepo,
|
||||
productService: productService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSubscription 创建订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateCreateSubscription(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 检查产品是否存在且可订阅
|
||||
canSubscribe, err := s.productService.CanUserSubscribeProduct(cmd.UserID, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !canSubscribe {
|
||||
return errors.New("产品不可订阅或用户已有活跃订阅")
|
||||
}
|
||||
|
||||
// 3. 检查产品是否存在
|
||||
_, err = s.productRepo.GetByID(ctx, cmd.ProductID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 5. 创建订阅实体
|
||||
subscription := entities.Subscription{
|
||||
UserID: cmd.UserID,
|
||||
ProductID: cmd.ProductID,
|
||||
Status: entities.SubscriptionStatusActive,
|
||||
Price: cmd.Price,
|
||||
APIUsed: 0,
|
||||
}
|
||||
|
||||
// 6. 保存到仓储
|
||||
createdSubscription, err := s.subscriptionRepo.Create(ctx, subscription)
|
||||
if err != nil {
|
||||
s.logger.Error("创建订阅失败", zap.Error(err))
|
||||
return fmt.Errorf("创建订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("创建订阅成功", zap.String("id", createdSubscription.ID), zap.String("user_id", cmd.UserID), zap.String("product_id", cmd.ProductID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSubscription 更新订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) UpdateSubscription(ctx context.Context, cmd *commands.UpdateSubscriptionCommand) error {
|
||||
// 1. 获取现有订阅
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 更新字段
|
||||
if cmd.Price >= 0 {
|
||||
subscription.Price = cmd.Price
|
||||
}
|
||||
|
||||
// 3. 保存到仓储
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("更新订阅失败", zap.Error(err))
|
||||
return fmt.Errorf("更新订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新订阅成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptionByID 根据ID获取订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Context, query *appQueries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error) {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, query.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
response := s.convertToSubscriptionInfoResponse(&subscription)
|
||||
|
||||
// 加载产品信息
|
||||
product, err := s.productRepo.GetByID(ctx, subscription.ProductID)
|
||||
if err == nil {
|
||||
response.Product = s.convertToProductSimpleResponse(&product)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ListSubscriptions 获取订阅列表
|
||||
func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Context, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
|
||||
// 构建仓储查询
|
||||
repoQuery := &repoQueries.ListSubscriptionsQuery{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
UserID: query.UserID,
|
||||
ProductID: query.ProductID,
|
||||
Status: query.Status,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
}
|
||||
|
||||
// 调用仓储
|
||||
subscriptions, total, err := s.subscriptionRepo.ListSubscriptions(ctx, repoQuery)
|
||||
if err != nil {
|
||||
s.logger.Error("获取订阅列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取订阅列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i, subscription := range subscriptions {
|
||||
items[i] = *s.convertToSubscriptionInfoResponse(subscription)
|
||||
}
|
||||
|
||||
return &responses.SubscriptionListResponse{
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
// UpdateAPIUsage 更新API使用量
|
||||
func (s *SubscriptionApplicationServiceImpl) UpdateAPIUsage(ctx context.Context, cmd *commands.UpdateAPIUsageCommand) error {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
subscription.IncrementAPIUsage(cmd.APIUsed)
|
||||
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("更新API使用量失败", zap.Error(err))
|
||||
return fmt.Errorf("更新API使用量失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新API使用量成功", zap.String("id", cmd.ID), zap.Int64("api_used", cmd.APIUsed))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetAPIUsage 重置API使用量
|
||||
func (s *SubscriptionApplicationServiceImpl) ResetAPIUsage(ctx context.Context, cmd *commands.ResetAPIUsageCommand) error {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
subscription.ResetAPIUsage()
|
||||
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("重置API使用量失败", zap.Error(err))
|
||||
return fmt.Errorf("重置API使用量失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("重置API使用量成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// GetUserSubscriptions 获取用户订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) GetUserSubscriptions(ctx context.Context, query *appQueries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
|
||||
subscriptions, err := s.subscriptionRepo.FindByUserID(ctx, query.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户订阅失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取用户订阅失败: %w", err)
|
||||
}
|
||||
|
||||
// 过滤状态
|
||||
if query.Status != nil {
|
||||
filtered := make([]*entities.Subscription, 0)
|
||||
for _, sub := range subscriptions {
|
||||
if sub.Status == *query.Status {
|
||||
filtered = append(filtered, sub)
|
||||
}
|
||||
}
|
||||
subscriptions = filtered
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i, subscription := range subscriptions {
|
||||
items[i] = s.convertToSubscriptionInfoResponse(subscription)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetProductSubscriptions 获取产品订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context.Context, query *appQueries.GetProductSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
|
||||
subscriptions, err := s.subscriptionRepo.FindByProductID(ctx, query.ProductID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品订阅失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品订阅失败: %w", err)
|
||||
}
|
||||
|
||||
// 过滤状态
|
||||
if query.Status != nil {
|
||||
filtered := make([]*entities.Subscription, 0)
|
||||
for _, sub := range subscriptions {
|
||||
if sub.Status == *query.Status {
|
||||
filtered = append(filtered, sub)
|
||||
}
|
||||
}
|
||||
subscriptions = filtered
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i, subscription := range subscriptions {
|
||||
items[i] = s.convertToSubscriptionInfoResponse(subscription)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetSubscriptionUsage 获取订阅使用情况
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
return &responses.SubscriptionUsageResponse{
|
||||
ID: subscription.ID,
|
||||
ProductID: subscription.ProductID,
|
||||
APIUsed: subscription.APIUsed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSubscriptionStats 获取订阅统计
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error) {
|
||||
// 获取各种状态的订阅数量
|
||||
total, err := s.subscriptionRepo.CountActive(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取订阅统计失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取订阅统计失败: %w", err)
|
||||
}
|
||||
|
||||
// TODO: 计算总收入,需要从订单系统获取
|
||||
totalRevenue := 0.0
|
||||
|
||||
return &responses.SubscriptionStatsResponse{
|
||||
TotalSubscriptions: total,
|
||||
TotalRevenue: totalRevenue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) validateCreateSubscription(cmd *commands.CreateSubscriptionCommand) error {
|
||||
if cmd.UserID == "" {
|
||||
return errors.New("用户ID不能为空")
|
||||
}
|
||||
if cmd.ProductID == "" {
|
||||
return errors.New("产品ID不能为空")
|
||||
}
|
||||
if cmd.Price < 0 {
|
||||
return errors.New("订阅价格不能为负数")
|
||||
}
|
||||
if cmd.APILimit < 0 {
|
||||
return errors.New("API调用限制不能为负数")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) calculateEndDate(duration string) (*time.Time, error) {
|
||||
if duration == "" {
|
||||
return nil, nil // 永久订阅
|
||||
}
|
||||
|
||||
d, err := s.parseDuration(duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endDate := time.Now().Add(d)
|
||||
return &endDate, nil
|
||||
}
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) parseDuration(duration string) (time.Duration, error) {
|
||||
switch duration {
|
||||
case "7d":
|
||||
return 7 * 24 * time.Hour, nil
|
||||
case "30d":
|
||||
return 30 * 24 * time.Hour, nil
|
||||
case "90d":
|
||||
return 90 * 24 * time.Hour, nil
|
||||
case "365d":
|
||||
return 365 * 24 * time.Hour, nil
|
||||
default:
|
||||
return time.ParseDuration(duration)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
||||
return &responses.SubscriptionInfoResponse{
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(product *entities.Product) *responses.ProductSimpleResponse {
|
||||
return &responses.ProductSimpleResponse{
|
||||
ID: product.ID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Price: product.Price,
|
||||
IsPackage: product.IsPackage,
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,15 @@ type ChangePasswordCommand struct {
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// ResetPasswordCommand 重置密码命令
|
||||
// @Description 重置用户密码请求参数(忘记密码时使用)
|
||||
type ResetPasswordCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=6,max=128" example:"newpassword123"`
|
||||
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"newpassword123"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// SendCodeCommand 发送验证码命令
|
||||
// @Description 发送短信验证码请求参数
|
||||
type SendCodeCommand struct {
|
||||
|
||||
@@ -43,6 +43,7 @@ type UserProfileResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
EnterpriseInfo *EnterpriseInfoResponse `json:"enterprise_info,omitempty"`
|
||||
IsCertified bool `json:"is_certified" example:"false"`
|
||||
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
|
||||
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ type UserApplicationService interface {
|
||||
LoginWithPassword(ctx context.Context, cmd *commands.LoginWithPasswordCommand) (*responses.LoginUserResponse, error)
|
||||
LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error)
|
||||
ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error
|
||||
ResetPassword(ctx context.Context, cmd *commands.ResetPasswordCommand) error
|
||||
GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error)
|
||||
SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error
|
||||
}
|
||||
|
||||
@@ -178,6 +178,34 @@ func (s *UserApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *co
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
func (s *UserApplicationServiceImpl) ResetPassword(ctx context.Context, cmd *commands.ResetPasswordCommand) error {
|
||||
user, err := s.userRepo.GetByPhone(ctx, cmd.Phone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneResetPassword); err != nil {
|
||||
return fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
if err := user.ResetPassword(cmd.NewPassword, cmd.ConfirmNewPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.userRepo.Update(ctx, *user); err != nil {
|
||||
return fmt.Errorf("密码更新失败: %w", err)
|
||||
}
|
||||
|
||||
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, "")
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布密码重置事件失败", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("密码重置成功", zap.String("user_id", user.ID), zap.String("phone", user.Phone))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserProfile 获取用户信息
|
||||
func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error) {
|
||||
if userID == "" {
|
||||
@@ -200,6 +228,7 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Debug("用户暂无企业信息", zap.String("user_id", userID))
|
||||
response.IsCertified = false
|
||||
} else {
|
||||
response.EnterpriseInfo = &responses.EnterpriseInfoResponse{
|
||||
ID: enterpriseInfo.ID,
|
||||
@@ -214,6 +243,7 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
|
||||
CreatedAt: enterpriseInfo.CreatedAt,
|
||||
UpdatedAt: enterpriseInfo.UpdatedAt,
|
||||
}
|
||||
response.IsCertified = enterpriseInfo.IsCertified
|
||||
}
|
||||
|
||||
return response, nil
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"tyapi-server/internal/application/admin"
|
||||
"tyapi-server/internal/application/certification"
|
||||
"tyapi-server/internal/application/finance"
|
||||
"tyapi-server/internal/application/product"
|
||||
"tyapi-server/internal/application/user"
|
||||
"tyapi-server/internal/config"
|
||||
domain_admin_repo "tyapi-server/internal/domains/admin/repositories"
|
||||
@@ -19,6 +20,8 @@ import (
|
||||
certification_service "tyapi-server/internal/domains/certification/services"
|
||||
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||
finance_service "tyapi-server/internal/domains/finance/services"
|
||||
domain_product_repo "tyapi-server/internal/domains/product/repositories"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
domain_user_repo "tyapi-server/internal/domains/user/repositories"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/infrastructure/cache"
|
||||
@@ -26,6 +29,7 @@ import (
|
||||
admin_repo "tyapi-server/internal/infrastructure/database/repositories/admin"
|
||||
certification_repo "tyapi-server/internal/infrastructure/database/repositories/certification"
|
||||
finance_repo "tyapi-server/internal/infrastructure/database/repositories/finance"
|
||||
product_repo "tyapi-server/internal/infrastructure/database/repositories/product"
|
||||
user_repo "tyapi-server/internal/infrastructure/database/repositories/user"
|
||||
"tyapi-server/internal/infrastructure/external/ocr"
|
||||
"tyapi-server/internal/infrastructure/external/sms"
|
||||
@@ -286,6 +290,25 @@ func NewContainer() *Container {
|
||||
),
|
||||
),
|
||||
|
||||
// 仓储层 - 产品域
|
||||
fx.Provide(
|
||||
// 产品仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
product_repo.NewGormProductRepository,
|
||||
fx.As(new(domain_product_repo.ProductRepository)),
|
||||
),
|
||||
// 产品分类仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
product_repo.NewGormProductCategoryRepository,
|
||||
fx.As(new(domain_product_repo.ProductCategoryRepository)),
|
||||
),
|
||||
// 订阅仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
product_repo.NewGormSubscriptionRepository,
|
||||
fx.As(new(domain_product_repo.SubscriptionRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// 领域服务
|
||||
fx.Provide(
|
||||
user_service.NewUserService,
|
||||
@@ -295,6 +318,7 @@ func NewContainer() *Container {
|
||||
certification_service.NewCertificationService,
|
||||
certification_service.NewCertificationStateMachine,
|
||||
finance_service.NewFinanceService,
|
||||
product_service.NewProductService,
|
||||
),
|
||||
|
||||
// 应用服务
|
||||
@@ -319,6 +343,21 @@ func NewContainer() *Container {
|
||||
finance.NewFinanceApplicationService,
|
||||
fx.As(new(finance.FinanceApplicationService)),
|
||||
),
|
||||
// 产品应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewProductApplicationService,
|
||||
fx.As(new(product.ProductApplicationService)),
|
||||
),
|
||||
// 分类应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewCategoryApplicationService,
|
||||
fx.As(new(product.CategoryApplicationService)),
|
||||
),
|
||||
// 订阅应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewSubscriptionApplicationService,
|
||||
fx.As(new(product.SubscriptionApplicationService)),
|
||||
),
|
||||
),
|
||||
|
||||
// HTTP处理器
|
||||
@@ -331,14 +370,22 @@ func NewContainer() *Container {
|
||||
handlers.NewCertificationHandler,
|
||||
// 财务HTTP处理器
|
||||
handlers.NewFinanceHandler,
|
||||
// 产品HTTP处理器
|
||||
handlers.NewProductHandler,
|
||||
),
|
||||
|
||||
// 路由注册器
|
||||
// 路由注册
|
||||
fx.Provide(
|
||||
// 用户路由
|
||||
routes.NewUserRoutes,
|
||||
// 管理员路由
|
||||
routes.NewAdminRoutes,
|
||||
// 认证路由
|
||||
routes.NewCertificationRoutes,
|
||||
// 财务路由
|
||||
routes.NewFinanceRoutes,
|
||||
// 产品路由
|
||||
routes.NewProductRoutes,
|
||||
),
|
||||
|
||||
// 应用生命周期
|
||||
@@ -416,6 +463,7 @@ func RegisterRoutes(
|
||||
adminRoutes *routes.AdminRoutes,
|
||||
certificationRoutes *routes.CertificationRoutes,
|
||||
financeRoutes *routes.FinanceRoutes,
|
||||
productRoutes *routes.ProductRoutes,
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
) {
|
||||
@@ -426,6 +474,7 @@ func RegisterRoutes(
|
||||
adminRoutes.Register(router)
|
||||
certificationRoutes.Register(router)
|
||||
financeRoutes.Register(router)
|
||||
productRoutes.Register(router)
|
||||
|
||||
// 打印注册的路由信息
|
||||
router.PrintRoutes()
|
||||
|
||||
93
internal/domains/product/entities/product.go
Normal file
93
internal/domains/product/entities/product.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Product 产品实体
|
||||
type Product struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"产品ID"`
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
|
||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
|
||||
Description string `gorm:"type:text" comment:"产品简介"`
|
||||
Content string `gorm:"type:longtext" comment:"产品内容"`
|
||||
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
|
||||
Price float64 `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
|
||||
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `gorm:"type:varchar(200)" comment:"SEO标题"`
|
||||
SEODescription string `gorm:"type:text" comment:"SEO描述"`
|
||||
SEOKeywords string `gorm:"type:text" comment:"SEO关键词"`
|
||||
|
||||
// 关联关系
|
||||
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"产品分类"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// IsValid 检查产品是否有效
|
||||
func (p *Product) IsValid() bool {
|
||||
return p.DeletedAt.Time.IsZero() && p.IsEnabled
|
||||
}
|
||||
|
||||
// IsVisibleToUser 检查产品是否对用户可见
|
||||
func (p *Product) IsVisibleToUser() bool {
|
||||
return p.IsValid() && p.IsVisible
|
||||
}
|
||||
|
||||
// CanBeSubscribed 检查产品是否可以订阅
|
||||
func (p *Product) CanBeSubscribed() bool {
|
||||
return p.IsValid() && p.IsVisible
|
||||
}
|
||||
|
||||
// GetDisplayPrice 获取显示价格
|
||||
func (p *Product) GetDisplayPrice() float64 {
|
||||
if p.Price < 0 {
|
||||
return 0
|
||||
}
|
||||
return p.Price
|
||||
}
|
||||
|
||||
// UpdateSEO 更新SEO信息
|
||||
func (p *Product) UpdateSEO(title, description, keywords string) {
|
||||
p.SEOTitle = title
|
||||
p.SEODescription = description
|
||||
p.SEOKeywords = keywords
|
||||
}
|
||||
|
||||
// Enable 启用产品
|
||||
func (p *Product) Enable() {
|
||||
p.IsEnabled = true
|
||||
}
|
||||
|
||||
// Disable 禁用产品
|
||||
func (p *Product) Disable() {
|
||||
p.IsEnabled = false
|
||||
}
|
||||
|
||||
// Show 显示产品
|
||||
func (p *Product) Show() {
|
||||
p.IsVisible = true
|
||||
}
|
||||
|
||||
// Hide 隐藏产品
|
||||
func (p *Product) Hide() {
|
||||
p.IsVisible = false
|
||||
}
|
||||
|
||||
// SetAsPackage 设置为组合包
|
||||
func (p *Product) SetAsPackage() {
|
||||
p.IsPackage = true
|
||||
}
|
||||
|
||||
// SetAsSingle 设置为单个产品
|
||||
func (p *Product) SetAsSingle() {
|
||||
p.IsPackage = false
|
||||
}
|
||||
80
internal/domains/product/entities/product_category.go
Normal file
80
internal/domains/product/entities/product_category.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductCategory 产品分类实体
|
||||
type ProductCategory struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"分类ID"`
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"分类名称"`
|
||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"分类编号"`
|
||||
Description string `gorm:"type:text" comment:"分类描述"`
|
||||
ParentID *string `gorm:"type:varchar(36)" comment:"父分类ID"`
|
||||
Level int `gorm:"default:1" comment:"分类层级"`
|
||||
Sort int `gorm:"default:0" comment:"排序"`
|
||||
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||
|
||||
// 关联关系
|
||||
Parent *ProductCategory `gorm:"foreignKey:ParentID" comment:"父分类"`
|
||||
Children []ProductCategory `gorm:"foreignKey:ParentID" comment:"子分类"`
|
||||
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// IsValid 检查分类是否有效
|
||||
func (pc *ProductCategory) IsValid() bool {
|
||||
return pc.DeletedAt.Time.IsZero() && pc.IsEnabled
|
||||
}
|
||||
|
||||
// IsVisibleToUser 检查分类是否对用户可见
|
||||
func (pc *ProductCategory) IsVisibleToUser() bool {
|
||||
return pc.IsValid() && pc.IsVisible
|
||||
}
|
||||
|
||||
// IsRoot 检查是否为根分类
|
||||
func (pc *ProductCategory) IsRoot() bool {
|
||||
return pc.ParentID == nil || *pc.ParentID == ""
|
||||
}
|
||||
|
||||
// IsLeaf 检查是否为叶子分类
|
||||
func (pc *ProductCategory) IsLeaf() bool {
|
||||
return len(pc.Children) == 0
|
||||
}
|
||||
|
||||
// GetFullPath 获取完整分类路径
|
||||
func (pc *ProductCategory) GetFullPath() string {
|
||||
if pc.IsRoot() {
|
||||
return pc.Name
|
||||
}
|
||||
if pc.Parent != nil {
|
||||
return pc.Parent.GetFullPath() + " > " + pc.Name
|
||||
}
|
||||
return pc.Name
|
||||
}
|
||||
|
||||
// Enable 启用分类
|
||||
func (pc *ProductCategory) Enable() {
|
||||
pc.IsEnabled = true
|
||||
}
|
||||
|
||||
// Disable 禁用分类
|
||||
func (pc *ProductCategory) Disable() {
|
||||
pc.IsEnabled = false
|
||||
}
|
||||
|
||||
// Show 显示分类
|
||||
func (pc *ProductCategory) Show() {
|
||||
pc.IsVisible = true
|
||||
}
|
||||
|
||||
// Hide 隐藏分类
|
||||
func (pc *ProductCategory) Hide() {
|
||||
pc.IsVisible = false
|
||||
}
|
||||
69
internal/domains/product/entities/product_documentation.go
Normal file
69
internal/domains/product/entities/product_documentation.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductDocumentation 产品文档实体
|
||||
type ProductDocumentation struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"文档ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;uniqueIndex" comment:"产品ID"`
|
||||
Title string `gorm:"type:varchar(200);not null" comment:"文档标题"`
|
||||
Content string `gorm:"type:longtext;not null" comment:"文档内容"`
|
||||
UsageGuide string `gorm:"type:longtext" comment:"使用指南"`
|
||||
APIDocs string `gorm:"type:longtext" comment:"API文档"`
|
||||
Examples string `gorm:"type:longtext" comment:"使用示例"`
|
||||
FAQ string `gorm:"type:longtext" comment:"常见问题"`
|
||||
Version string `gorm:"type:varchar(20);default:'1.0'" comment:"文档版本"`
|
||||
Published bool `gorm:"default:false" comment:"是否已发布"`
|
||||
|
||||
// 关联关系
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// IsValid 检查文档是否有效
|
||||
func (pd *ProductDocumentation) IsValid() bool {
|
||||
return pd.DeletedAt.Time.IsZero()
|
||||
}
|
||||
|
||||
// IsPublished 检查文档是否已发布
|
||||
func (pd *ProductDocumentation) IsPublished() bool {
|
||||
return pd.Published
|
||||
}
|
||||
|
||||
// Publish 发布文档
|
||||
func (pd *ProductDocumentation) Publish() {
|
||||
pd.Published = true
|
||||
}
|
||||
|
||||
// Unpublish 取消发布文档
|
||||
func (pd *ProductDocumentation) Unpublish() {
|
||||
pd.Published = false
|
||||
}
|
||||
|
||||
// UpdateContent 更新文档内容
|
||||
func (pd *ProductDocumentation) UpdateContent(title, content, usageGuide, apiDocs, examples, faq string) {
|
||||
pd.Title = title
|
||||
pd.Content = content
|
||||
pd.UsageGuide = usageGuide
|
||||
pd.APIDocs = apiDocs
|
||||
pd.Examples = examples
|
||||
pd.FAQ = faq
|
||||
}
|
||||
|
||||
// IncrementVersion 增加版本号
|
||||
func (pd *ProductDocumentation) IncrementVersion() {
|
||||
// 简单的版本号递增逻辑,实际项目中可能需要更复杂的版本管理
|
||||
if pd.Version == "" {
|
||||
pd.Version = "1.0"
|
||||
} else {
|
||||
// 这里可以实现更复杂的版本号递增逻辑
|
||||
pd.Version = pd.Version + ".1"
|
||||
}
|
||||
}
|
||||
59
internal/domains/product/entities/subscription.go
Normal file
59
internal/domains/product/entities/subscription.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SubscriptionStatus 订阅状态枚举
|
||||
type SubscriptionStatus string
|
||||
|
||||
const (
|
||||
SubscriptionStatusActive SubscriptionStatus = "active" // 活跃
|
||||
SubscriptionStatusInactive SubscriptionStatus = "inactive" // 非活跃
|
||||
SubscriptionStatusExpired SubscriptionStatus = "expired" // 已过期
|
||||
SubscriptionStatusCanceled SubscriptionStatus = "canceled" // 已取消
|
||||
)
|
||||
|
||||
// Subscription 订阅实体
|
||||
type Subscription struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"订阅ID"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||
Status SubscriptionStatus `gorm:"type:varchar(20);not null;default:'active'" comment:"订阅状态"`
|
||||
Price float64 `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
|
||||
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
|
||||
|
||||
// 关联关系
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// IsValid 检查订阅是否有效
|
||||
func (s *Subscription) IsValid() bool {
|
||||
return s.DeletedAt.Time.IsZero()
|
||||
}
|
||||
|
||||
// IncrementAPIUsage 增加API使用次数
|
||||
func (s *Subscription) IncrementAPIUsage(count int64) {
|
||||
s.APIUsed += count
|
||||
}
|
||||
|
||||
// Activate 激活订阅
|
||||
func (s *Subscription) Activate() {
|
||||
s.Status = SubscriptionStatusActive
|
||||
}
|
||||
|
||||
// Deactivate 停用订阅
|
||||
func (s *Subscription) Deactivate() {
|
||||
s.Status = SubscriptionStatusInactive
|
||||
}
|
||||
|
||||
// ResetAPIUsage 重置API使用次数
|
||||
func (s *Subscription) ResetAPIUsage() {
|
||||
s.APIUsed = 0
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ProductCategoryRepository 产品分类仓储接口
|
||||
type ProductCategoryRepository interface {
|
||||
interfaces.Repository[entities.ProductCategory]
|
||||
|
||||
// 基础查询方法
|
||||
FindByCode(ctx context.Context, code string) (*entities.ProductCategory, error)
|
||||
FindByParentID(ctx context.Context, parentID *string) ([]*entities.ProductCategory, error)
|
||||
FindRootCategories(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
FindVisible(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
FindEnabled(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
|
||||
// 复杂查询方法
|
||||
ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) ([]*entities.ProductCategory, int64, error)
|
||||
GetCategoryTree(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
|
||||
// 业务查询方法
|
||||
FindCategoriesByLevel(ctx context.Context, level int) ([]*entities.ProductCategory, error)
|
||||
FindCategoryPath(ctx context.Context, categoryID string) ([]*entities.ProductCategory, error)
|
||||
|
||||
// 统计方法
|
||||
CountByParent(ctx context.Context, parentID *string) (int64, error)
|
||||
CountEnabled(ctx context.Context) (int64, error)
|
||||
CountVisible(ctx context.Context) (int64, error)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ProductRepository 产品仓储接口
|
||||
type ProductRepository interface {
|
||||
interfaces.Repository[entities.Product]
|
||||
|
||||
// 基础查询方法
|
||||
FindByCode(ctx context.Context, code string) (*entities.Product, error)
|
||||
FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.Product, error)
|
||||
FindVisible(ctx context.Context) ([]*entities.Product, error)
|
||||
FindEnabled(ctx context.Context) ([]*entities.Product, error)
|
||||
|
||||
// 复杂查询方法
|
||||
ListProducts(ctx context.Context, query *queries.ListProductsQuery) ([]*entities.Product, int64, error)
|
||||
|
||||
// 业务查询方法
|
||||
FindSubscribableProducts(ctx context.Context, userID string) ([]*entities.Product, error)
|
||||
FindProductsByIDs(ctx context.Context, ids []string) ([]*entities.Product, error)
|
||||
|
||||
// 统计方法
|
||||
CountByCategory(ctx context.Context, categoryID string) (int64, error)
|
||||
CountEnabled(ctx context.Context) (int64, error)
|
||||
CountVisible(ctx context.Context) (int64, error)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package queries
|
||||
|
||||
// ListCategoriesQuery 分类列表查询
|
||||
type ListCategoriesQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
ParentID *string `json:"parent_id"`
|
||||
Level *int `json:"level"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
|
||||
// GetCategoryQuery 获取分类详情查询
|
||||
type GetCategoryQuery struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GetCategoryTreeQuery 获取分类树查询
|
||||
type GetCategoryTreeQuery struct {
|
||||
IncludeDisabled bool `json:"include_disabled"`
|
||||
IncludeHidden bool `json:"include_hidden"`
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package queries
|
||||
|
||||
// ListProductsQuery 产品列表查询
|
||||
type ListProductsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Keyword string `json:"keyword"`
|
||||
CategoryID string `json:"category_id"`
|
||||
MinPrice *float64 `json:"min_price"`
|
||||
MaxPrice *float64 `json:"max_price"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
IsPackage *bool `json:"is_package"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
|
||||
// SearchProductsQuery 产品搜索查询
|
||||
type SearchProductsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Keyword string `json:"keyword"`
|
||||
CategoryID string `json:"category_id"`
|
||||
MinPrice *float64 `json:"min_price"`
|
||||
MaxPrice *float64 `json:"max_price"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
IsPackage *bool `json:"is_package"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
|
||||
// GetProductQuery 获取产品详情查询
|
||||
type GetProductQuery struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GetProductsByIDsQuery 根据ID列表获取产品查询
|
||||
type GetProductsByIDsQuery struct {
|
||||
IDs []string `json:"ids"`
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package queries
|
||||
|
||||
import "tyapi-server/internal/domains/product/entities"
|
||||
|
||||
// ListSubscriptionsQuery 订阅列表查询
|
||||
type ListSubscriptionsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id"`
|
||||
ProductID string `json:"product_id"`
|
||||
Status entities.SubscriptionStatus `json:"status"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
|
||||
// GetSubscriptionQuery 获取订阅详情查询
|
||||
type GetSubscriptionQuery struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// GetUserSubscriptionsQuery 获取用户订阅查询
|
||||
type GetUserSubscriptionsQuery struct {
|
||||
UserID string `json:"user_id"`
|
||||
Status *entities.SubscriptionStatus `json:"status"`
|
||||
}
|
||||
|
||||
// GetProductSubscriptionsQuery 获取产品订阅查询
|
||||
type GetProductSubscriptionsQuery struct {
|
||||
ProductID string `json:"product_id"`
|
||||
Status *entities.SubscriptionStatus `json:"status"`
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// SubscriptionRepository 订阅仓储接口
|
||||
type SubscriptionRepository interface {
|
||||
interfaces.Repository[entities.Subscription]
|
||||
|
||||
// 基础查询方法
|
||||
FindByUserID(ctx context.Context, userID string) ([]*entities.Subscription, error)
|
||||
FindByProductID(ctx context.Context, productID string) ([]*entities.Subscription, error)
|
||||
FindByUserAndProduct(ctx context.Context, userID, productID string) (*entities.Subscription, error)
|
||||
FindActive(ctx context.Context) ([]*entities.Subscription, error)
|
||||
|
||||
// 复杂查询方法
|
||||
ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error)
|
||||
FindUserActiveSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error)
|
||||
FindExpiredSubscriptions(ctx context.Context) ([]*entities.Subscription, error)
|
||||
|
||||
// 业务查询方法
|
||||
FindSubscriptionsByStatus(ctx context.Context, status entities.SubscriptionStatus) ([]*entities.Subscription, error)
|
||||
FindSubscriptionsByDateRange(ctx context.Context, startDate, endDate string) ([]*entities.Subscription, error)
|
||||
|
||||
// 统计方法
|
||||
CountByUser(ctx context.Context, userID string) (int64, error)
|
||||
CountByProduct(ctx context.Context, productID string) (int64, error)
|
||||
CountByStatus(ctx context.Context, status entities.SubscriptionStatus) (int64, error)
|
||||
CountActive(ctx context.Context) (int64, error)
|
||||
}
|
||||
151
internal/domains/product/services/product_service.go
Normal file
151
internal/domains/product/services/product_service.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
)
|
||||
|
||||
// ProductService 产品领域服务
|
||||
type ProductService struct {
|
||||
productRepo repositories.ProductRepository
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
}
|
||||
|
||||
// NewProductService 创建产品领域服务
|
||||
func NewProductService(
|
||||
productRepo repositories.ProductRepository,
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
) *ProductService {
|
||||
return &ProductService{
|
||||
productRepo: productRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateProduct 验证产品
|
||||
func (s *ProductService) ValidateProduct(product *entities.Product) error {
|
||||
if product == nil {
|
||||
return errors.New("产品不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Name) == "" {
|
||||
return errors.New("产品名称不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
if product.Price < 0 {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
|
||||
// 验证分类是否存在
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(nil, product.CategoryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品分类不存在: %w", err)
|
||||
}
|
||||
if !category.IsValid() {
|
||||
return errors.New("产品分类已禁用或删除")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateProductCode 验证产品编号唯一性
|
||||
func (s *ProductService) ValidateProductCode(code string, excludeID string) error {
|
||||
if strings.TrimSpace(code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
existingProduct, err := s.productRepo.FindByCode(nil, code)
|
||||
if err == nil && existingProduct != nil && existingProduct.ID != excludeID {
|
||||
return errors.New("产品编号已存在")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanUserSubscribeProduct 检查用户是否可以订阅产品
|
||||
func (s *ProductService) CanUserSubscribeProduct(userID string, productID string) (bool, error) {
|
||||
// 检查产品是否存在且可订阅
|
||||
product, err := s.productRepo.GetByID(nil, productID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
if !product.CanBeSubscribed() {
|
||||
return false, errors.New("产品不可订阅")
|
||||
}
|
||||
|
||||
// 检查用户是否已有该产品的订阅
|
||||
existingSubscription, err := s.subscriptionRepo.FindByUserAndProduct(nil, userID, productID)
|
||||
if err == nil && existingSubscription != nil {
|
||||
return false, errors.New("用户已有该产品的订阅")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetProductWithCategory 获取产品及其分类信息
|
||||
func (s *ProductService) GetProductWithCategory(productID string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.GetByID(nil, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 加载分类信息
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(nil, product.CategoryID)
|
||||
if err == nil {
|
||||
product.Category = &category
|
||||
}
|
||||
}
|
||||
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// GetVisibleProducts 获取可见产品列表
|
||||
func (s *ProductService) GetVisibleProducts() ([]*entities.Product, error) {
|
||||
return s.productRepo.FindVisible(nil)
|
||||
}
|
||||
|
||||
// GetEnabledProducts 获取启用产品列表
|
||||
func (s *ProductService) GetEnabledProducts() ([]*entities.Product, error) {
|
||||
return s.productRepo.FindEnabled(nil)
|
||||
}
|
||||
|
||||
// GetProductsByCategory 根据分类获取产品
|
||||
func (s *ProductService) GetProductsByCategory(categoryID string) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindByCategoryID(nil, categoryID)
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计信息
|
||||
func (s *ProductService) GetProductStats() (map[string]int64, error) {
|
||||
stats := make(map[string]int64)
|
||||
|
||||
total, err := s.productRepo.CountByCategory(nil, "")
|
||||
if err == nil {
|
||||
stats["total"] = total
|
||||
}
|
||||
|
||||
enabled, err := s.productRepo.CountEnabled(nil)
|
||||
if err == nil {
|
||||
stats["enabled"] = enabled
|
||||
}
|
||||
|
||||
visible, err := s.productRepo.CountVisible(nil)
|
||||
if err == nil {
|
||||
stats["visible"] = visible
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -134,6 +134,28 @@ func (u *User) SetPassword(password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码(忘记密码时使用)
|
||||
func (u *User) ResetPassword(newPassword, confirmPassword string) error {
|
||||
// 1. 验证确认密码
|
||||
if newPassword != confirmPassword {
|
||||
return NewValidationError("新密码和确认新密码不匹配")
|
||||
}
|
||||
|
||||
// 2. 验证新密码强度
|
||||
if err := u.validatePasswordStrength(newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 更新密码
|
||||
hashedPassword, err := u.hashPassword(newPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
u.Password = hashedPassword
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanLogin 检查用户是否可以登录
|
||||
func (u *User) CanLogin() bool {
|
||||
// 检查用户是否被删除
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
"tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormProductCategoryRepository GORM产品分类仓储实现
|
||||
type GormProductCategoryRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.ProductCategoryRepository = (*GormProductCategoryRepository)(nil)
|
||||
|
||||
// NewGormProductCategoryRepository 创建GORM产品分类仓储
|
||||
func NewGormProductCategoryRepository(db *gorm.DB, logger *zap.Logger) repositories.ProductCategoryRepository {
|
||||
return &GormProductCategoryRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建产品分类
|
||||
func (r *GormProductCategoryRepository) Create(ctx context.Context, entity entities.ProductCategory) (entities.ProductCategory, error) {
|
||||
r.logger.Info("创建产品分类", zap.String("id", entity.ID), zap.String("name", entity.Name))
|
||||
err := r.db.WithContext(ctx).Create(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取产品分类
|
||||
func (r *GormProductCategoryRepository) GetByID(ctx context.Context, id string) (entities.ProductCategory, error) {
|
||||
var entity entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
|
||||
// Update 更新产品分类
|
||||
func (r *GormProductCategoryRepository) Update(ctx context.Context, entity entities.ProductCategory) error {
|
||||
r.logger.Info("更新产品分类", zap.String("id", entity.ID))
|
||||
return r.db.WithContext(ctx).Save(&entity).Error
|
||||
}
|
||||
|
||||
// Delete 删除产品分类
|
||||
func (r *GormProductCategoryRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除产品分类", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.ProductCategory{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// FindByCode 根据编号查找产品分类
|
||||
func (r *GormProductCategoryRepository) FindByCode(ctx context.Context, code string) (*entities.ProductCategory, error) {
|
||||
var entity entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("code = ?", code).First(&entity).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &entity, nil
|
||||
}
|
||||
|
||||
// FindByParentID 根据父级ID查找产品分类
|
||||
func (r *GormProductCategoryRepository) FindByParentID(ctx context.Context, parentID *string) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
query := r.db.WithContext(ctx)
|
||||
|
||||
if parentID == nil {
|
||||
query = query.Where("parent_id IS NULL")
|
||||
} else {
|
||||
query = query.Where("parent_id = ?", *parentID)
|
||||
}
|
||||
|
||||
err := query.Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindRootCategories 查找根分类
|
||||
func (r *GormProductCategoryRepository) FindRootCategories(ctx context.Context) ([]*entities.ProductCategory, error) {
|
||||
return r.FindByParentID(ctx, nil)
|
||||
}
|
||||
|
||||
// FindVisible 查找可见分类
|
||||
func (r *GormProductCategoryRepository) FindVisible(ctx context.Context) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("is_visible = ? AND is_enabled = ?", true, true).Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindEnabled 查找启用分类
|
||||
func (r *GormProductCategoryRepository) FindEnabled(ctx context.Context) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("is_enabled = ?", true).Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListCategories 获取分类列表
|
||||
func (r *GormProductCategoryRepository) ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) ([]*entities.ProductCategory, int64, error) {
|
||||
var categories []entities.ProductCategory
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.ProductCategory{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.ParentID != nil {
|
||||
dbQuery = dbQuery.Where("parent_id = ?", *query.ParentID)
|
||||
}
|
||||
if query.IsEnabled != nil {
|
||||
dbQuery = dbQuery.Where("is_enabled = ?", *query.IsEnabled)
|
||||
}
|
||||
if query.IsVisible != nil {
|
||||
dbQuery = dbQuery.Where("is_visible = ?", *query.IsVisible)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if query.SortBy != "" {
|
||||
order := query.SortBy
|
||||
if query.SortOrder == "desc" {
|
||||
order += " DESC"
|
||||
} else {
|
||||
order += " ASC"
|
||||
}
|
||||
dbQuery = dbQuery.Order(order)
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("sort_order ASC, created_at DESC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if query.Page > 0 && query.PageSize > 0 {
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
if err := dbQuery.Find(&categories).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
// GetCategoryTree 获取分类树
|
||||
func (r *GormProductCategoryRepository) GetCategoryTree(ctx context.Context) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("is_enabled = ?", true).Order("sort_order ASC, created_at ASC").Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindCategoriesByLevel 根据层级查找分类
|
||||
func (r *GormProductCategoryRepository) FindCategoriesByLevel(ctx context.Context, level int) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("level = ? AND is_enabled = ?", level, true).Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindCategoryPath 查找分类路径
|
||||
func (r *GormProductCategoryRepository) FindCategoryPath(ctx context.Context, categoryID string) ([]*entities.ProductCategory, error) {
|
||||
// 这里需要递归查找父级分类,简化实现
|
||||
var entity entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("id = ?", categoryID).First(&entity).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []*entities.ProductCategory{&entity}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CountByParent 统计父级下的分类数量
|
||||
func (r *GormProductCategoryRepository) CountByParent(ctx context.Context, parentID *string) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.ProductCategory{})
|
||||
|
||||
if parentID == nil {
|
||||
query = query.Where("parent_id IS NULL")
|
||||
} else {
|
||||
query = query.Where("parent_id = ?", *parentID)
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountEnabled 统计启用分类数量
|
||||
func (r *GormProductCategoryRepository) CountEnabled(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.ProductCategory{}).Where("is_enabled = ?", true).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountVisible 统计可见分类数量
|
||||
func (r *GormProductCategoryRepository) CountVisible(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.ProductCategory{}).Where("is_visible = ? AND is_enabled = ?", true, true).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// 基础Repository接口方法
|
||||
|
||||
// Count 返回分类总数
|
||||
func (r *GormProductCategoryRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.ProductCategory{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("name LIKE ? OR description LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取分类
|
||||
func (r *GormProductCategoryRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&categories).Error
|
||||
return categories, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建分类
|
||||
func (r *GormProductCategoryRepository) CreateBatch(ctx context.Context, categories []entities.ProductCategory) error {
|
||||
return r.db.WithContext(ctx).Create(&categories).Error
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新分类
|
||||
func (r *GormProductCategoryRepository) UpdateBatch(ctx context.Context, categories []entities.ProductCategory) error {
|
||||
return r.db.WithContext(ctx).Save(&categories).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除分类
|
||||
func (r *GormProductCategoryRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.ProductCategory{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取分类列表(基础方法)
|
||||
func (r *GormProductCategoryRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
query := r.db.WithContext(ctx).Model(&entities.ProductCategory{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("name LIKE ? OR description LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := options.Sort
|
||||
if options.Order == "desc" {
|
||||
order += " DESC"
|
||||
} else {
|
||||
order += " ASC"
|
||||
}
|
||||
query = query.Order(order)
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
query = query.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
err := query.Find(&categories).Error
|
||||
return categories, err
|
||||
}
|
||||
|
||||
// Exists 检查分类是否存在
|
||||
func (r *GormProductCategoryRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.ProductCategory{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// SoftDelete 软删除分类
|
||||
func (r *GormProductCategoryRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.ProductCategory{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复软删除的分类
|
||||
func (r *GormProductCategoryRepository) Restore(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.ProductCategory{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormProductCategoryRepository) WithTx(tx interface{}) interfaces.Repository[entities.ProductCategory] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormProductCategoryRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
"tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormProductRepository GORM产品仓储实现
|
||||
type GormProductRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.ProductRepository = (*GormProductRepository)(nil)
|
||||
|
||||
// NewGormProductRepository 创建GORM产品仓储
|
||||
func NewGormProductRepository(db *gorm.DB, logger *zap.Logger) repositories.ProductRepository {
|
||||
return &GormProductRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建产品
|
||||
func (r *GormProductRepository) Create(ctx context.Context, entity entities.Product) (entities.Product, error) {
|
||||
r.logger.Info("创建产品", zap.String("id", entity.ID), zap.String("name", entity.Name))
|
||||
err := r.db.WithContext(ctx).Create(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取产品
|
||||
func (r *GormProductRepository) GetByID(ctx context.Context, id string) (entities.Product, error) {
|
||||
var entity entities.Product
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
|
||||
// Update 更新产品
|
||||
func (r *GormProductRepository) Update(ctx context.Context, entity entities.Product) error {
|
||||
r.logger.Info("更新产品", zap.String("id", entity.ID))
|
||||
return r.db.WithContext(ctx).Save(&entity).Error
|
||||
}
|
||||
|
||||
// Delete 删除产品
|
||||
func (r *GormProductRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除产品", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Product{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// FindByCode 根据编号查找产品
|
||||
func (r *GormProductRepository) FindByCode(ctx context.Context, code string) (*entities.Product, error) {
|
||||
var entity entities.Product
|
||||
err := r.db.WithContext(ctx).Where("code = ?", code).First(&entity).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &entity, nil
|
||||
}
|
||||
|
||||
// FindByCategoryID 根据分类ID查找产品
|
||||
func (r *GormProductRepository) FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.Product, error) {
|
||||
var productEntities []entities.Product
|
||||
err := r.db.WithContext(ctx).Where("category_id = ?", categoryID).Find(&productEntities).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Product, len(productEntities))
|
||||
for i := range productEntities {
|
||||
result[i] = &productEntities[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindVisible 查找可见产品
|
||||
func (r *GormProductRepository) FindVisible(ctx context.Context) ([]*entities.Product, error) {
|
||||
var productEntities []entities.Product
|
||||
err := r.db.WithContext(ctx).Where("is_visible = ? AND is_enabled = ?", true, true).Find(&productEntities).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Product, len(productEntities))
|
||||
for i := range productEntities {
|
||||
result[i] = &productEntities[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindEnabled 查找启用产品
|
||||
func (r *GormProductRepository) FindEnabled(ctx context.Context) ([]*entities.Product, error) {
|
||||
var productEntities []entities.Product
|
||||
err := r.db.WithContext(ctx).Where("is_enabled = ?", true).Find(&productEntities).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Product, len(productEntities))
|
||||
for i := range productEntities {
|
||||
result[i] = &productEntities[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListProducts 获取产品列表
|
||||
func (r *GormProductRepository) ListProducts(ctx context.Context, query *queries.ListProductsQuery) ([]*entities.Product, int64, error) {
|
||||
var productEntities []entities.Product
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Product{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.Keyword != "" {
|
||||
dbQuery = dbQuery.Where("name LIKE ? OR description LIKE ? OR code LIKE ?",
|
||||
"%"+query.Keyword+"%", "%"+query.Keyword+"%", "%"+query.Keyword+"%")
|
||||
}
|
||||
if query.CategoryID != "" {
|
||||
dbQuery = dbQuery.Where("category_id = ?", query.CategoryID)
|
||||
}
|
||||
if query.MinPrice != nil {
|
||||
dbQuery = dbQuery.Where("price >= ?", *query.MinPrice)
|
||||
}
|
||||
if query.MaxPrice != nil {
|
||||
dbQuery = dbQuery.Where("price <= ?", *query.MaxPrice)
|
||||
}
|
||||
if query.IsEnabled != nil {
|
||||
dbQuery = dbQuery.Where("is_enabled = ?", *query.IsEnabled)
|
||||
}
|
||||
if query.IsVisible != nil {
|
||||
dbQuery = dbQuery.Where("is_visible = ?", *query.IsVisible)
|
||||
}
|
||||
if query.IsPackage != nil {
|
||||
dbQuery = dbQuery.Where("is_package = ?", *query.IsPackage)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if query.SortBy != "" {
|
||||
order := query.SortBy
|
||||
if query.SortOrder == "desc" {
|
||||
order += " DESC"
|
||||
} else {
|
||||
order += " ASC"
|
||||
}
|
||||
dbQuery = dbQuery.Order(order)
|
||||
} 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)
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
if err := dbQuery.Find(&productEntities).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Product, len(productEntities))
|
||||
for i := range productEntities {
|
||||
result[i] = &productEntities[i]
|
||||
}
|
||||
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
|
||||
// FindSubscribableProducts 查找可订阅产品
|
||||
func (r *GormProductRepository) FindSubscribableProducts(ctx context.Context, userID string) ([]*entities.Product, error) {
|
||||
var productEntities []entities.Product
|
||||
err := r.db.WithContext(ctx).Where("is_enabled = ? AND is_visible = ?", true, true).Find(&productEntities).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Product, len(productEntities))
|
||||
for i := range productEntities {
|
||||
result[i] = &productEntities[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindProductsByIDs 根据ID列表查找产品
|
||||
func (r *GormProductRepository) FindProductsByIDs(ctx context.Context, ids []string) ([]*entities.Product, error) {
|
||||
var productEntities []entities.Product
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&productEntities).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Product, len(productEntities))
|
||||
for i := range productEntities {
|
||||
result[i] = &productEntities[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CountByCategory 统计分类下的产品数量
|
||||
func (r *GormProductRepository) CountByCategory(ctx context.Context, categoryID string) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.Product{})
|
||||
if categoryID != "" {
|
||||
query = query.Where("category_id = ?", categoryID)
|
||||
}
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountEnabled 统计启用产品数量
|
||||
func (r *GormProductRepository) CountEnabled(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Product{}).Where("is_enabled = ?", true).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountVisible 统计可见产品数量
|
||||
func (r *GormProductRepository) CountVisible(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Product{}).Where("is_visible = ? AND is_enabled = ?", true, true).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Count 返回产品总数
|
||||
func (r *GormProductRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.Product{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("name LIKE ? OR description LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取产品
|
||||
func (r *GormProductRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Product, error) {
|
||||
var products []entities.Product
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&products).Error
|
||||
return products, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建产品
|
||||
func (r *GormProductRepository) CreateBatch(ctx context.Context, products []entities.Product) error {
|
||||
return r.db.WithContext(ctx).Create(&products).Error
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新产品
|
||||
func (r *GormProductRepository) UpdateBatch(ctx context.Context, products []entities.Product) error {
|
||||
return r.db.WithContext(ctx).Save(&products).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除产品
|
||||
func (r *GormProductRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Product{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取产品列表(基础方法)
|
||||
func (r *GormProductRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Product, error) {
|
||||
var products []entities.Product
|
||||
query := r.db.WithContext(ctx).Model(&entities.Product{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("name LIKE ? OR description LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := options.Sort
|
||||
if options.Order == "desc" {
|
||||
order += " DESC"
|
||||
} else {
|
||||
order += " ASC"
|
||||
}
|
||||
query = query.Order(order)
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
query = query.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
err := query.Find(&products).Error
|
||||
return products, err
|
||||
}
|
||||
|
||||
// Exists 检查产品是否存在
|
||||
func (r *GormProductRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Product{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// SoftDelete 软删除产品
|
||||
func (r *GormProductRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Product{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复软删除的产品
|
||||
func (r *GormProductRepository) Restore(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.Product{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormProductRepository) WithTx(tx interface{}) interfaces.Repository[entities.Product] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormProductRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
"tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormSubscriptionRepository GORM订阅仓储实现
|
||||
type GormSubscriptionRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.SubscriptionRepository = (*GormSubscriptionRepository)(nil)
|
||||
|
||||
// NewGormSubscriptionRepository 创建GORM订阅仓储
|
||||
func NewGormSubscriptionRepository(db *gorm.DB, logger *zap.Logger) repositories.SubscriptionRepository {
|
||||
return &GormSubscriptionRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建订阅
|
||||
func (r *GormSubscriptionRepository) Create(ctx context.Context, entity entities.Subscription) (entities.Subscription, error) {
|
||||
r.logger.Info("创建订阅", zap.String("id", entity.ID), zap.String("user_id", entity.UserID))
|
||||
err := r.db.WithContext(ctx).Create(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取订阅
|
||||
func (r *GormSubscriptionRepository) GetByID(ctx context.Context, id string) (entities.Subscription, error) {
|
||||
var entity entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error
|
||||
return entity, err
|
||||
}
|
||||
|
||||
// Update 更新订阅
|
||||
func (r *GormSubscriptionRepository) Update(ctx context.Context, entity entities.Subscription) error {
|
||||
r.logger.Info("更新订阅", zap.String("id", entity.ID))
|
||||
return r.db.WithContext(ctx).Save(&entity).Error
|
||||
}
|
||||
|
||||
// Delete 删除订阅
|
||||
func (r *GormSubscriptionRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除订阅", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Subscription{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// FindByUserID 根据用户ID查找订阅
|
||||
func (r *GormSubscriptionRepository) FindByUserID(ctx context.Context, userID string) ([]*entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&subscriptions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindByProductID 根据产品ID查找订阅
|
||||
func (r *GormSubscriptionRepository) FindByProductID(ctx context.Context, productID string) ([]*entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("product_id = ?", productID).Find(&subscriptions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindByUserAndProduct 根据用户和产品查找订阅
|
||||
func (r *GormSubscriptionRepository) FindByUserAndProduct(ctx context.Context, userID, productID string) (*entities.Subscription, error) {
|
||||
var entity entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("user_id = ? AND product_id = ?", userID, productID).First(&entity).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &entity, nil
|
||||
}
|
||||
|
||||
// FindActive 查找活跃订阅
|
||||
func (r *GormSubscriptionRepository) FindActive(ctx context.Context) ([]*entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("status = ?", entities.SubscriptionStatusActive).Find(&subscriptions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListSubscriptions 获取订阅列表
|
||||
func (r *GormSubscriptionRepository) ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Subscription{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.UserID != "" {
|
||||
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
|
||||
}
|
||||
if query.ProductID != "" {
|
||||
dbQuery = dbQuery.Where("product_id = ?", query.ProductID)
|
||||
}
|
||||
if query.Status != "" {
|
||||
dbQuery = dbQuery.Where("status = ?", query.Status)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if query.SortBy != "" {
|
||||
order := query.SortBy
|
||||
if query.SortOrder == "desc" {
|
||||
order += " DESC"
|
||||
} else {
|
||||
order += " ASC"
|
||||
}
|
||||
dbQuery = dbQuery.Order(order)
|
||||
} 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)
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
if err := dbQuery.Find(&subscriptions).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
// FindUserActiveSubscriptions 查找用户活跃订阅
|
||||
func (r *GormSubscriptionRepository) FindUserActiveSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("user_id = ? AND status = ?", userID, entities.SubscriptionStatusActive).Find(&subscriptions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindExpiredSubscriptions 查找过期订阅
|
||||
func (r *GormSubscriptionRepository) FindExpiredSubscriptions(ctx context.Context) ([]*entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
now := time.Now()
|
||||
err := r.db.WithContext(ctx).Where("end_date IS NOT NULL AND end_date < ? AND status = ?", now, entities.SubscriptionStatusActive).Find(&subscriptions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindSubscriptionsByStatus 根据状态查找订阅
|
||||
func (r *GormSubscriptionRepository) FindSubscriptionsByStatus(ctx context.Context, status entities.SubscriptionStatus) ([]*entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("status = ?", status).Find(&subscriptions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindSubscriptionsByDateRange 根据日期范围查找订阅
|
||||
func (r *GormSubscriptionRepository) FindSubscriptionsByDateRange(ctx context.Context, startDate, endDate string) ([]*entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("created_at BETWEEN ? AND ?", startDate, endDate).Find(&subscriptions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.Subscription, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
result[i] = &subscriptions[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CountByUser 统计用户订阅数量
|
||||
func (r *GormSubscriptionRepository) CountByUser(ctx context.Context, userID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Subscription{}).Where("user_id = ?", userID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountByProduct 统计产品订阅数量
|
||||
func (r *GormSubscriptionRepository) CountByProduct(ctx context.Context, productID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Subscription{}).Where("product_id = ?", productID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountByStatus 根据状态统计订阅数量
|
||||
func (r *GormSubscriptionRepository) CountByStatus(ctx context.Context, status entities.SubscriptionStatus) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Subscription{}).Where("status = ?", status).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountActive 统计活跃订阅数量
|
||||
func (r *GormSubscriptionRepository) CountActive(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Subscription{}).Where("status = ?", entities.SubscriptionStatusActive).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// 基础Repository接口方法
|
||||
|
||||
// Count 返回订阅总数
|
||||
func (r *GormSubscriptionRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.Subscription{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("user_id LIKE ? OR product_id LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取订阅
|
||||
func (r *GormSubscriptionRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&subscriptions).Error
|
||||
return subscriptions, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建订阅
|
||||
func (r *GormSubscriptionRepository) CreateBatch(ctx context.Context, subscriptions []entities.Subscription) error {
|
||||
return r.db.WithContext(ctx).Create(&subscriptions).Error
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新订阅
|
||||
func (r *GormSubscriptionRepository) UpdateBatch(ctx context.Context, subscriptions []entities.Subscription) error {
|
||||
return r.db.WithContext(ctx).Save(&subscriptions).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除订阅
|
||||
func (r *GormSubscriptionRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Subscription{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取订阅列表(基础方法)
|
||||
func (r *GormSubscriptionRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Subscription, error) {
|
||||
var subscriptions []entities.Subscription
|
||||
query := r.db.WithContext(ctx).Model(&entities.Subscription{})
|
||||
|
||||
// 应用筛选条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("user_id LIKE ? OR product_id LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := options.Sort
|
||||
if options.Order == "desc" {
|
||||
order += " DESC"
|
||||
} else {
|
||||
order += " ASC"
|
||||
}
|
||||
query = query.Order(order)
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
query = query.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
err := query.Find(&subscriptions).Error
|
||||
return subscriptions, err
|
||||
}
|
||||
|
||||
// Exists 检查订阅是否存在
|
||||
func (r *GormSubscriptionRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Subscription{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// SoftDelete 软删除订阅
|
||||
func (r *GormSubscriptionRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Subscription{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复软删除的订阅
|
||||
func (r *GormSubscriptionRepository) Restore(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.Subscription{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormSubscriptionRepository) WithTx(tx interface{}) interfaces.Repository[entities.Subscription] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormSubscriptionRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func NewAdminHandler(
|
||||
// Login 管理员登录
|
||||
// @Summary 管理员登录
|
||||
// @Description 使用用户名和密码进行管理员登录,返回JWT令牌
|
||||
// @Tags 管理员认证
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.AdminLoginCommand true "管理员登录请求"
|
||||
|
||||
444
internal/infrastructure/http/handlers/product_handler.go
Normal file
444
internal/infrastructure/http/handlers/product_handler.go
Normal file
@@ -0,0 +1,444 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"tyapi-server/internal/application/product"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProductHandler 产品相关HTTP处理器
|
||||
type ProductHandler struct {
|
||||
appService product.ProductApplicationService
|
||||
categoryService product.CategoryApplicationService
|
||||
subAppService product.SubscriptionApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductHandler 创建产品HTTP处理器
|
||||
func NewProductHandler(
|
||||
appService product.ProductApplicationService,
|
||||
categoryService product.CategoryApplicationService,
|
||||
subAppService product.SubscriptionApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *ProductHandler {
|
||||
return &ProductHandler{
|
||||
appService: appService,
|
||||
categoryService: categoryService,
|
||||
subAppService: subAppService,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ListProducts 获取产品列表(数据大厅)
|
||||
// @Summary 获取产品列表
|
||||
// @Description 分页获取可用的产品列表,支持筛选
|
||||
// @Tags 数据大厅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param keyword query string false "搜索关键词"
|
||||
// @Param category_id query string false "分类ID"
|
||||
// @Param min_price query number false "最低价格"
|
||||
// @Param max_price query number false "最高价格"
|
||||
// @Param is_enabled query bool false "是否启用"
|
||||
// @Param is_visible query bool false "是否可见"
|
||||
// @Param is_package query bool false "是否组合包"
|
||||
// @Param sort_by query string false "排序字段"
|
||||
// @Param sort_order query string false "排序方向" Enums(asc, desc)
|
||||
// @Success 200 {object} responses.ProductListResponse "获取产品列表成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/products [get]
|
||||
func (h *ProductHandler) ListProducts(c *gin.Context) {
|
||||
var query queries.ListProductsQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize <= 0 {
|
||||
query.PageSize = 10
|
||||
}
|
||||
if query.PageSize > 100 {
|
||||
query.PageSize = 100
|
||||
}
|
||||
|
||||
result, err := h.appService.ListProducts(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取产品列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取产品列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取产品列表成功")
|
||||
}
|
||||
|
||||
// GetProductDetail 获取产品详情
|
||||
// @Summary 获取产品详情
|
||||
// @Description 根据产品ID获取产品详细信息
|
||||
// @Tags 数据大厅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "产品ID"
|
||||
// @Success 200 {object} responses.ProductInfoResponse "获取产品详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 404 {object} map[string]interface{} "产品不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/products/{id} [get]
|
||||
func (h *ProductHandler) GetProductDetail(c *gin.Context) {
|
||||
var query queries.GetProductQuery
|
||||
query.ID = c.Param("id")
|
||||
|
||||
if query.ID == "" {
|
||||
h.responseBuilder.BadRequest(c, "产品ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.GetProductByID(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取产品详情失败", zap.Error(err), zap.String("product_id", query.ID))
|
||||
h.responseBuilder.NotFound(c, "产品不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取产品详情成功")
|
||||
}
|
||||
|
||||
|
||||
// SubscribeProduct 订阅产品
|
||||
// @Summary 订阅产品
|
||||
// @Description 用户订阅指定产品
|
||||
// @Tags 数据大厅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "产品ID"
|
||||
// @Param request body commands.CreateSubscriptionCommand true "订阅请求"
|
||||
// @Success 200 {object} map[string]interface{} "订阅成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "产品不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/products/{id}/subscribe [post]
|
||||
func (h *ProductHandler) SubscribeProduct(c *gin.Context) {
|
||||
userID := c.GetString("user_id") // 从JWT中间件获取
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
productID := c.Param("id")
|
||||
if productID == "" {
|
||||
h.responseBuilder.BadRequest(c, "产品ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.CreateSubscriptionCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置用户ID和产品ID
|
||||
cmd.UserID = userID
|
||||
cmd.ProductID = productID
|
||||
|
||||
// 设置默认值
|
||||
if cmd.APILimit <= 0 {
|
||||
cmd.APILimit = 1000 // 默认API调用限制
|
||||
}
|
||||
if cmd.Duration == "" {
|
||||
cmd.Duration = "30d" // 默认订阅30天
|
||||
}
|
||||
|
||||
if err := h.subAppService.CreateSubscription(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("订阅产品失败", zap.Error(err), zap.String("user_id", userID), zap.String("product_id", productID))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "订阅成功")
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计信息
|
||||
// @Summary 获取产品统计
|
||||
// @Description 获取产品相关的统计信息
|
||||
// @Tags 数据大厅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} responses.ProductStatsResponse "获取统计信息成功"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/products/stats [get]
|
||||
func (h *ProductHandler) GetProductStats(c *gin.Context) {
|
||||
result, err := h.appService.GetProductStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取产品统计失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取产品统计失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取产品统计成功")
|
||||
}
|
||||
|
||||
// ================ 分类相关方法 ================
|
||||
|
||||
// ListCategories 获取分类列表
|
||||
// @Summary 获取分类列表
|
||||
// @Description 获取产品分类列表,支持层级筛选
|
||||
// @Tags 数据大厅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param parent_id query string false "父级分类ID"
|
||||
// @Param level query int false "分类层级"
|
||||
// @Success 200 {object} responses.CategoryListResponse "获取分类列表成功"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/categories [get]
|
||||
func (h *ProductHandler) ListCategories(c *gin.Context) {
|
||||
// 解析查询参数
|
||||
parentID := c.Query("parent_id")
|
||||
levelStr := c.Query("level")
|
||||
|
||||
// 构建查询命令
|
||||
query := &queries.ListCategoriesQuery{
|
||||
Page: 1,
|
||||
PageSize: 100,
|
||||
SortBy: "sort_order",
|
||||
SortOrder: "asc",
|
||||
}
|
||||
|
||||
// 设置父级分类ID
|
||||
if parentID != "" {
|
||||
query.ParentID = &parentID
|
||||
}
|
||||
|
||||
// 设置分类层级
|
||||
if levelStr != "" {
|
||||
if level, err := strconv.Atoi(levelStr); err == nil {
|
||||
query.Level = &level
|
||||
}
|
||||
}
|
||||
|
||||
// 调用应用服务
|
||||
categories, err := h.categoryService.ListCategories(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取分类列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取分类列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
h.responseBuilder.Success(c, categories, "获取分类列表成功")
|
||||
}
|
||||
|
||||
// GetCategoryDetail 获取分类详情
|
||||
// @Summary 获取分类详情
|
||||
// @Description 根据分类ID获取分类详细信息
|
||||
// @Tags 数据大厅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "分类ID"
|
||||
// @Success 200 {object} responses.CategoryInfoResponse "获取分类详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 404 {object} map[string]interface{} "分类不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/categories/{id} [get]
|
||||
func (h *ProductHandler) GetCategoryDetail(c *gin.Context) {
|
||||
categoryID := c.Param("id")
|
||||
if categoryID == "" {
|
||||
h.responseBuilder.BadRequest(c, "分类ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 构建查询命令
|
||||
query := &queries.GetCategoryQuery{
|
||||
ID: categoryID,
|
||||
}
|
||||
|
||||
// 调用应用服务
|
||||
category, err := h.categoryService.GetCategoryByID(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取分类详情失败", zap.String("category_id", categoryID), zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, "分类不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
h.responseBuilder.Success(c, category, "获取分类详情成功")
|
||||
}
|
||||
|
||||
// ================ 我的订阅相关方法 ================
|
||||
|
||||
// ListMySubscriptions 获取我的订阅列表
|
||||
// @Summary 获取我的订阅列表
|
||||
// @Description 获取当前用户的订阅列表
|
||||
// @Tags 我的订阅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param status query string false "订阅状态"
|
||||
// @Param sort_by query string false "排序字段"
|
||||
// @Param sort_order query string false "排序方向" Enums(asc, desc)
|
||||
// @Success 200 {object} responses.SubscriptionListResponse "获取订阅列表成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/my/subscriptions [get]
|
||||
func (h *ProductHandler) ListMySubscriptions(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var query queries.ListSubscriptionsQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize <= 0 {
|
||||
query.PageSize = 10
|
||||
}
|
||||
if query.PageSize > 100 {
|
||||
query.PageSize = 100
|
||||
}
|
||||
|
||||
// 设置用户ID
|
||||
query.UserID = userID
|
||||
|
||||
result, err := h.subAppService.ListSubscriptions(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取我的订阅列表失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.responseBuilder.InternalError(c, "获取我的订阅列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取我的订阅列表成功")
|
||||
}
|
||||
|
||||
// GetMySubscriptionStats 获取我的订阅统计
|
||||
// @Summary 获取我的订阅统计
|
||||
// @Description 获取当前用户的订阅统计信息
|
||||
// @Tags 我的订阅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.SubscriptionStatsResponse "获取订阅统计成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/my/subscriptions/stats [get]
|
||||
func (h *ProductHandler) GetMySubscriptionStats(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.subAppService.GetSubscriptionStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取我的订阅统计失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.responseBuilder.InternalError(c, "获取我的订阅统计失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取我的订阅统计成功")
|
||||
}
|
||||
|
||||
// GetMySubscriptionDetail 获取我的订阅详情
|
||||
// @Summary 获取我的订阅详情
|
||||
// @Description 获取指定订阅的详细信息
|
||||
// @Tags 我的订阅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "订阅ID"
|
||||
// @Success 200 {object} responses.SubscriptionInfoResponse "获取订阅详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "订阅不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/my/subscriptions/{id} [get]
|
||||
func (h *ProductHandler) GetMySubscriptionDetail(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionID := c.Param("id")
|
||||
if subscriptionID == "" {
|
||||
h.responseBuilder.BadRequest(c, "订阅ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var query queries.GetSubscriptionQuery
|
||||
query.ID = subscriptionID
|
||||
|
||||
result, err := h.subAppService.GetSubscriptionByID(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取我的订阅详情失败", zap.Error(err), zap.String("user_id", userID), zap.String("subscription_id", subscriptionID))
|
||||
h.responseBuilder.NotFound(c, "订阅不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取我的订阅详情成功")
|
||||
}
|
||||
|
||||
// GetMySubscriptionUsage 获取我的订阅使用情况
|
||||
// @Summary 获取我的订阅使用情况
|
||||
// @Description 获取指定订阅的使用情况统计
|
||||
// @Tags 我的订阅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "订阅ID"
|
||||
// @Success 200 {object} responses.SubscriptionUsageResponse "获取使用情况成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "订阅不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/my/subscriptions/{id}/usage [get]
|
||||
func (h *ProductHandler) GetMySubscriptionUsage(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionID := c.Param("id")
|
||||
if subscriptionID == "" {
|
||||
h.responseBuilder.BadRequest(c, "订阅ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.subAppService.GetSubscriptionUsage(c.Request.Context(), subscriptionID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取我的订阅使用情况失败", zap.Error(err), zap.String("user_id", userID), zap.String("subscription_id", subscriptionID))
|
||||
h.responseBuilder.NotFound(c, "订阅不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取我的订阅使用情况成功")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -213,6 +213,33 @@ func (h *UserHandler) ChangePassword(c *gin.Context) {
|
||||
h.response.Success(c, nil, "密码修改成功")
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
// @Summary 重置密码
|
||||
// @Description 使用手机号、验证码和新密码重置用户密码(忘记密码时使用)
|
||||
// @Tags 用户认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.ResetPasswordCommand true "重置密码请求"
|
||||
// @Success 200 {object} map[string]interface{} "密码重置成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效"
|
||||
// @Failure 404 {object} map[string]interface{} "用户不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/reset-password [post]
|
||||
func (h *UserHandler) ResetPassword(c *gin.Context) {
|
||||
var cmd commands.ResetPasswordCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.ResetPassword(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("重置密码失败", zap.Error(err))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, nil, "密码重置成功")
|
||||
}
|
||||
|
||||
// getCurrentUserID 获取当前用户ID
|
||||
func (h *UserHandler) getCurrentUserID(c *gin.Context) string {
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
|
||||
81
internal/infrastructure/http/routes/product_routes.go
Normal file
81
internal/infrastructure/http/routes/product_routes.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"tyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "tyapi-server/internal/shared/http"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProductRoutes 产品路由
|
||||
type ProductRoutes struct {
|
||||
productHandler *handlers.ProductHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductRoutes 创建产品路由
|
||||
func NewProductRoutes(
|
||||
productHandler *handlers.ProductHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *ProductRoutes {
|
||||
return &ProductRoutes{
|
||||
productHandler: productHandler,
|
||||
auth: auth,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册产品相关路由
|
||||
func (r *ProductRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// 数据大厅 - 公开接口
|
||||
products := engine.Group("/api/v1/products")
|
||||
{
|
||||
// 获取产品列表(分页+筛选)
|
||||
products.GET("", r.productHandler.ListProducts)
|
||||
|
||||
// 获取产品详情
|
||||
products.GET("/:id", r.productHandler.GetProductDetail)
|
||||
|
||||
// 获取产品统计
|
||||
products.GET("/stats", r.productHandler.GetProductStats)
|
||||
|
||||
// 订阅产品(需要认证)
|
||||
products.POST("/:id/subscribe", r.auth.Handle(), r.productHandler.SubscribeProduct)
|
||||
}
|
||||
|
||||
// 分类 - 公开接口
|
||||
categories := engine.Group("/api/v1/categories")
|
||||
{
|
||||
// 获取分类列表
|
||||
categories.GET("", r.productHandler.ListCategories)
|
||||
|
||||
// 获取分类详情
|
||||
categories.GET("/:id", r.productHandler.GetCategoryDetail)
|
||||
}
|
||||
|
||||
// 我的订阅 - 需要认证
|
||||
my := engine.Group("/api/v1/my", r.auth.Handle())
|
||||
{
|
||||
subscriptions := my.Group("/subscriptions")
|
||||
{
|
||||
// 获取我的订阅列表
|
||||
subscriptions.GET("", r.productHandler.ListMySubscriptions)
|
||||
|
||||
// 获取我的订阅统计
|
||||
subscriptions.GET("/stats", r.productHandler.GetMySubscriptionStats)
|
||||
|
||||
// 获取订阅详情
|
||||
subscriptions.GET("/:id", r.productHandler.GetMySubscriptionDetail)
|
||||
|
||||
// 获取订阅使用情况
|
||||
subscriptions.GET("/:id/usage", r.productHandler.GetMySubscriptionUsage)
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("产品路由注册完成")
|
||||
}
|
||||
@@ -39,6 +39,7 @@ func (r *UserRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
usersGroup.POST("/register", r.handler.Register) // 用户注册
|
||||
usersGroup.POST("/login-password", r.handler.LoginWithPassword) // 密码登录
|
||||
usersGroup.POST("/login-sms", r.handler.LoginWithSMS) // 短信验证码登录
|
||||
usersGroup.POST("/reset-password", r.handler.ResetPassword) // 重置密码
|
||||
|
||||
// 需要认证的路由
|
||||
authenticated := usersGroup.Group("")
|
||||
|
||||
Reference in New Issue
Block a user