2025-07-15 13:21:34 +08:00
|
|
|
|
package product
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"fmt"
|
2025-11-03 13:32:05 +08:00
|
|
|
|
"reflect"
|
|
|
|
|
|
"strings"
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"github.com/shopspring/decimal"
|
2025-07-20 20:53:26 +08:00
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
2025-07-15 13:21:34 +08:00
|
|
|
|
"tyapi-server/internal/application/product/dto/commands"
|
|
|
|
|
|
appQueries "tyapi-server/internal/application/product/dto/queries"
|
|
|
|
|
|
"tyapi-server/internal/application/product/dto/responses"
|
2025-11-03 13:32:05 +08:00
|
|
|
|
"tyapi-server/internal/domains/api/dto"
|
|
|
|
|
|
api_services "tyapi-server/internal/domains/api/services"
|
2025-07-15 13:21:34 +08:00
|
|
|
|
"tyapi-server/internal/domains/product/entities"
|
2025-07-20 20:53:26 +08:00
|
|
|
|
product_service "tyapi-server/internal/domains/product/services"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/shared/interfaces"
|
2025-07-15 13:21:34 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ProductApplicationServiceImpl 产品应用服务实现
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
2025-07-15 13:21:34 +08:00
|
|
|
|
type ProductApplicationServiceImpl struct {
|
2025-07-29 00:30:32 +08:00
|
|
|
|
productManagementService *product_service.ProductManagementService
|
|
|
|
|
|
productSubscriptionService *product_service.ProductSubscriptionService
|
|
|
|
|
|
productApiConfigAppService ProductApiConfigApplicationService
|
2025-07-31 15:41:00 +08:00
|
|
|
|
documentationAppService DocumentationApplicationServiceInterface
|
2025-11-03 13:32:05 +08:00
|
|
|
|
formConfigService api_services.FormConfigService
|
2025-07-29 00:30:32 +08:00
|
|
|
|
logger *zap.Logger
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewProductApplicationService 创建产品应用服务
|
|
|
|
|
|
func NewProductApplicationService(
|
2025-07-20 20:53:26 +08:00
|
|
|
|
productManagementService *product_service.ProductManagementService,
|
|
|
|
|
|
productSubscriptionService *product_service.ProductSubscriptionService,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
productApiConfigAppService ProductApiConfigApplicationService,
|
2025-07-31 15:41:00 +08:00
|
|
|
|
documentationAppService DocumentationApplicationServiceInterface,
|
2025-11-03 13:32:05 +08:00
|
|
|
|
formConfigService api_services.FormConfigService,
|
2025-07-15 13:21:34 +08:00
|
|
|
|
logger *zap.Logger,
|
|
|
|
|
|
) ProductApplicationService {
|
|
|
|
|
|
return &ProductApplicationServiceImpl{
|
2025-07-29 00:30:32 +08:00
|
|
|
|
productManagementService: productManagementService,
|
|
|
|
|
|
productSubscriptionService: productSubscriptionService,
|
|
|
|
|
|
productApiConfigAppService: productApiConfigAppService,
|
2025-07-31 15:41:00 +08:00
|
|
|
|
documentationAppService: documentationAppService,
|
2025-11-03 13:32:05 +08:00
|
|
|
|
formConfigService: formConfigService,
|
2025-07-29 00:30:32 +08:00
|
|
|
|
logger: logger,
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateProduct 创建产品
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 构建产品实体 2. 创建产品
|
2025-12-09 16:42:37 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 1. 构建产品实体
|
2025-07-15 13:21:34 +08:00
|
|
|
|
product := &entities.Product{
|
2025-07-20 20:53:26 +08:00
|
|
|
|
Name: cmd.Name,
|
|
|
|
|
|
Code: cmd.Code,
|
|
|
|
|
|
Description: cmd.Description,
|
|
|
|
|
|
Content: cmd.Content,
|
|
|
|
|
|
CategoryID: cmd.CategoryID,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
Price: decimal.NewFromFloat(cmd.Price),
|
2025-11-13 20:43:35 +08:00
|
|
|
|
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
|
|
|
|
|
Remark: cmd.Remark,
|
2025-07-20 20:53:26 +08:00
|
|
|
|
IsEnabled: cmd.IsEnabled,
|
|
|
|
|
|
IsVisible: cmd.IsVisible,
|
|
|
|
|
|
IsPackage: cmd.IsPackage,
|
|
|
|
|
|
SEOTitle: cmd.SEOTitle,
|
|
|
|
|
|
SEODescription: cmd.SEODescription,
|
|
|
|
|
|
SEOKeywords: cmd.SEOKeywords,
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 2. 创建产品
|
2025-12-09 16:42:37 +08:00
|
|
|
|
createdProduct, err := s.productManagementService.CreateProduct(ctx, product)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 转换为响应对象
|
|
|
|
|
|
return s.convertToProductAdminInfoResponse(createdProduct), nil
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdateProduct 更新产品
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 获取现有产品 2. 更新产品信息 3. 保存产品
|
2025-07-15 13:21:34 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error {
|
|
|
|
|
|
// 1. 获取现有产品
|
2025-07-20 20:53:26 +08:00
|
|
|
|
existingProduct, err := s.productManagementService.GetProductByID(ctx, cmd.ID)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 2. 更新产品信息
|
|
|
|
|
|
existingProduct.Name = cmd.Name
|
|
|
|
|
|
existingProduct.Code = cmd.Code
|
|
|
|
|
|
existingProduct.Description = cmd.Description
|
|
|
|
|
|
existingProduct.Content = cmd.Content
|
|
|
|
|
|
existingProduct.CategoryID = cmd.CategoryID
|
2025-07-28 01:46:39 +08:00
|
|
|
|
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
2025-11-13 20:43:35 +08:00
|
|
|
|
existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice)
|
|
|
|
|
|
existingProduct.Remark = cmd.Remark
|
2025-07-20 20:53:26 +08:00
|
|
|
|
existingProduct.IsEnabled = cmd.IsEnabled
|
|
|
|
|
|
existingProduct.IsVisible = cmd.IsVisible
|
|
|
|
|
|
existingProduct.IsPackage = cmd.IsPackage
|
|
|
|
|
|
existingProduct.SEOTitle = cmd.SEOTitle
|
|
|
|
|
|
existingProduct.SEODescription = cmd.SEODescription
|
|
|
|
|
|
existingProduct.SEOKeywords = cmd.SEOKeywords
|
2025-07-15 13:21:34 +08:00
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 3. 保存产品
|
|
|
|
|
|
return s.productManagementService.UpdateProduct(ctx, existingProduct)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DeleteProduct 删除产品
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 删除产品
|
2025-07-15 13:21:34 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return s.productManagementService.DeleteProduct(ctx, cmd.ID)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListProducts 获取产品列表
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 获取产品列表 2. 构建响应数据
|
2025-07-28 01:46:39 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductListResponse, error) {
|
2025-07-29 00:30:32 +08:00
|
|
|
|
// 检查是否有用户ID,如果有则使用带订阅状态的方法
|
|
|
|
|
|
if userID, ok := filters["user_id"].(string); ok && userID != "" {
|
|
|
|
|
|
return s.ListProductsWithSubscriptionStatus(ctx, filters, options)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 调用领域服务获取产品列表
|
|
|
|
|
|
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
if err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return nil, err
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应对象
|
|
|
|
|
|
items := make([]responses.ProductInfoResponse, len(products))
|
|
|
|
|
|
for i := range products {
|
|
|
|
|
|
items[i] = *s.convertToProductInfoResponse(products[i])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.ProductListResponse{
|
2025-07-28 01:46:39 +08:00
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
2025-07-15 13:21:34 +08:00
|
|
|
|
Items: items,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-29 00:30:32 +08:00
|
|
|
|
// ListProductsWithSubscriptionStatus 获取产品列表(包含订阅状态)
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 获取产品列表和订阅状<E99885>?2. 构建响应数据
|
2025-07-29 00:30:32 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) ListProductsWithSubscriptionStatus(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductListResponse, error) {
|
|
|
|
|
|
// 调用领域服务获取产品列表(包含订阅状态)
|
|
|
|
|
|
products, subscriptionStatusMap, total, err := s.productManagementService.ListProductsWithSubscriptionStatus(ctx, filters, options)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应对象
|
|
|
|
|
|
items := make([]responses.ProductInfoResponse, len(products))
|
|
|
|
|
|
for i := range products {
|
|
|
|
|
|
item := s.convertToProductInfoResponse(products[i])
|
|
|
|
|
|
|
|
|
|
|
|
// 设置订阅状态
|
|
|
|
|
|
if isSubscribed, exists := subscriptionStatusMap[products[i].ID]; exists {
|
|
|
|
|
|
item.IsSubscribed = &isSubscribed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items[i] = *item
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.ProductListResponse{
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-15 13:21:34 +08:00
|
|
|
|
// GetProductsByIDs 根据ID列表获取产品
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 获取产品列表 2. 构建响应数据
|
2025-07-15 13:21:34 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) GetProductsByIDs(ctx context.Context, query *appQueries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error) {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 这里需要扩展领域服务来支持批量获取
|
|
|
|
|
|
// 暂时返回空列表
|
|
|
|
|
|
return []*responses.ProductInfoResponse{}, nil
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// GetSubscribableProducts 获取可订阅产品列表
|
|
|
|
|
|
// 业务流程:1. 获取启用产品 2. 过滤可订阅产品 3. 构建响应数据
|
2025-07-15 13:21:34 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) GetSubscribableProducts(ctx context.Context, query *appQueries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error) {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
products, err := s.productManagementService.GetEnabledProducts(ctx)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
if err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return nil, err
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 过滤可订阅的产品
|
|
|
|
|
|
var subscribableProducts []*entities.Product
|
|
|
|
|
|
for _, product := range products {
|
|
|
|
|
|
if product.CanBeSubscribed() {
|
|
|
|
|
|
subscribableProducts = append(subscribableProducts, product)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应对象
|
2025-07-20 20:53:26 +08:00
|
|
|
|
items := make([]*responses.ProductInfoResponse, len(subscribableProducts))
|
|
|
|
|
|
for i := range subscribableProducts {
|
|
|
|
|
|
items[i] = s.convertToProductInfoResponse(subscribableProducts[i])
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return items, nil
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// GetProductByID 根据ID获取产品
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 获取产品信息 2. 构建响应数据
|
2025-07-20 20:53:26 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) GetProductByID(ctx context.Context, query *appQueries.GetProductQuery) (*responses.ProductInfoResponse, error) {
|
|
|
|
|
|
product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
if err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return nil, err
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return s.convertToProductInfoResponse(product), nil
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// GetProductStats 获取产品统计信息
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程<E6B581>?. 获取产品统计 2. 构建响应数据
|
2025-07-15 13:21:34 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error) {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
stats, err := s.productSubscriptionService.GetProductStats(ctx)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
if err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return nil, err
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.ProductStatsResponse{
|
|
|
|
|
|
TotalProducts: stats["total"],
|
|
|
|
|
|
EnabledProducts: stats["enabled"],
|
|
|
|
|
|
VisibleProducts: stats["visible"],
|
|
|
|
|
|
PackageProducts: 0, // 需要单独统计
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// AddPackageItem 添加组合包子产品
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) AddPackageItem(ctx context.Context, packageID string, cmd *commands.AddPackageItemCommand) error {
|
|
|
|
|
|
// 验证组合包是否存在
|
|
|
|
|
|
packageProduct, err := s.productManagementService.GetProductByID(ctx, packageID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if !packageProduct.IsPackage {
|
|
|
|
|
|
return fmt.Errorf("产品不是组合包")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证子产品是否存在且不是组合包
|
|
|
|
|
|
subProduct, err := s.productManagementService.GetProductByID(ctx, cmd.ProductID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if subProduct.IsPackage {
|
|
|
|
|
|
return fmt.Errorf("不能将组合包作为子产品")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已经存在
|
|
|
|
|
|
existingItems, err := s.productManagementService.GetPackageItems(ctx, packageID)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
for _, item := range existingItems {
|
|
|
|
|
|
if item.ProductID == cmd.ProductID {
|
|
|
|
|
|
return fmt.Errorf("该产品已在组合包中")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前最大排序号
|
|
|
|
|
|
maxSortOrder := 0
|
|
|
|
|
|
if existingItems != nil {
|
|
|
|
|
|
for _, item := range existingItems {
|
|
|
|
|
|
if item.SortOrder > maxSortOrder {
|
|
|
|
|
|
maxSortOrder = item.SortOrder
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建组合包项目
|
|
|
|
|
|
packageItem := &entities.ProductPackageItem{
|
|
|
|
|
|
PackageID: packageID,
|
|
|
|
|
|
ProductID: cmd.ProductID,
|
|
|
|
|
|
SortOrder: maxSortOrder + 1,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return s.productManagementService.CreatePackageItem(ctx, packageItem)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdatePackageItem 更新组合包子产品
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) UpdatePackageItem(ctx context.Context, packageID, itemID string, cmd *commands.UpdatePackageItemCommand) error {
|
|
|
|
|
|
// 验证组合包项目是否存在
|
|
|
|
|
|
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if packageItem.PackageID != packageID {
|
|
|
|
|
|
return fmt.Errorf("组合包项目不属于指定组合包")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新项目
|
|
|
|
|
|
packageItem.SortOrder = cmd.SortOrder
|
|
|
|
|
|
|
|
|
|
|
|
return s.productManagementService.UpdatePackageItem(ctx, packageItem)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RemovePackageItem 移除组合包子产品
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) RemovePackageItem(ctx context.Context, packageID, itemID string) error {
|
|
|
|
|
|
// 验证组合包项目是否存在
|
|
|
|
|
|
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if packageItem.PackageID != packageID {
|
|
|
|
|
|
return fmt.Errorf("组合包项目不属于指定组合包")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return s.productManagementService.DeletePackageItem(ctx, itemID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ReorderPackageItems 重新排序组合包子产品
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) ReorderPackageItems(ctx context.Context, packageID string, cmd *commands.ReorderPackageItemsCommand) error {
|
|
|
|
|
|
// 验证所有项目是否属于该组合包
|
|
|
|
|
|
for i, itemID := range cmd.ItemIDs {
|
|
|
|
|
|
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if packageItem.PackageID != packageID {
|
|
|
|
|
|
return fmt.Errorf("组合包项目不属于指定组合包")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新排序
|
|
|
|
|
|
packageItem.SortOrder = i + 1
|
|
|
|
|
|
if err := s.productManagementService.UpdatePackageItem(ctx, packageItem); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdatePackageItems 批量更新组合包子产品
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) UpdatePackageItems(ctx context.Context, packageID string, cmd *commands.UpdatePackageItemsCommand) error {
|
|
|
|
|
|
// 验证组合包是否存在
|
|
|
|
|
|
packageProduct, err := s.productManagementService.GetProductByID(ctx, packageID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if !packageProduct.IsPackage {
|
|
|
|
|
|
return fmt.Errorf("产品不是组合包")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证所有子产品是否存在且不是组合包
|
|
|
|
|
|
for _, item := range cmd.Items {
|
|
|
|
|
|
subProduct, err := s.productManagementService.GetProductByID(ctx, item.ProductID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if subProduct.IsPackage {
|
|
|
|
|
|
return fmt.Errorf("不能将组合包作为子产品")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用事务进行批量更新
|
|
|
|
|
|
return s.productManagementService.UpdatePackageItemsBatch(ctx, packageID, cmd.Items)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 22:13:05 +08:00
|
|
|
|
// GetAvailableProducts 获取可选子产品列表(管理员端,返回包含成本价的数据)
|
|
|
|
|
|
// 业务流程:1. 获取启用产品 2. 过滤可订阅产品 3. 构建管理员响应数据
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context, query *appQueries.GetAvailableProductsQuery) (*responses.ProductAdminListResponse, error) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 构建筛选条件
|
|
|
|
|
|
filters := make(map[string]interface{})
|
|
|
|
|
|
filters["is_package"] = false // 只获取非组合包产品
|
|
|
|
|
|
filters["is_enabled"] = true // 只获取启用产品
|
|
|
|
|
|
if query.Keyword != "" {
|
|
|
|
|
|
filters["keyword"] = query.Keyword
|
|
|
|
|
|
}
|
|
|
|
|
|
if query.CategoryID != "" {
|
|
|
|
|
|
filters["category_id"] = query.CategoryID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置分页选项
|
|
|
|
|
|
options := interfaces.ListOptions{
|
|
|
|
|
|
Page: query.Page,
|
|
|
|
|
|
PageSize: query.PageSize,
|
|
|
|
|
|
Sort: "created_at",
|
|
|
|
|
|
Order: "desc",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取产品列表
|
|
|
|
|
|
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 22:13:05 +08:00
|
|
|
|
// 转换为管理员响应对象(包含成本价,用于组合包配置)
|
|
|
|
|
|
items := make([]responses.ProductAdminInfoResponse, len(products))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
for i := range products {
|
2025-11-13 22:13:05 +08:00
|
|
|
|
items[i] = *s.convertToProductAdminInfoResponse(products[i])
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 22:13:05 +08:00
|
|
|
|
return &responses.ProductAdminListResponse{
|
2025-07-28 01:46:39 +08:00
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-30 00:51:22 +08:00
|
|
|
|
// ListProductsForAdmin 获取产品列表(管理员专用)
|
|
|
|
|
|
// 业务流程:1. 获取所有产品列表(包括隐藏的) 2. 构建管理员响应数据
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) ListProductsForAdmin(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductAdminListResponse, error) {
|
|
|
|
|
|
// 调用领域服务获取产品列表(管理员可以看到所有产品)
|
|
|
|
|
|
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为管理员响应对象
|
|
|
|
|
|
items := make([]responses.ProductAdminInfoResponse, len(products))
|
|
|
|
|
|
for i := range products {
|
|
|
|
|
|
items[i] = *s.convertToProductAdminInfoResponse(products[i])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.ProductAdminListResponse{
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetProductByIDForAdmin 根据ID获取产品(管理员专用)
|
|
|
|
|
|
// 业务流程:1. 获取产品信息 2. 构建管理员响应数据
|
2025-07-31 15:41:00 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) GetProductByIDForAdmin(ctx context.Context, query *appQueries.GetProductDetailQuery) (*responses.ProductAdminInfoResponse, error) {
|
2025-07-30 00:51:22 +08:00
|
|
|
|
product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-31 15:41:00 +08:00
|
|
|
|
response := s.convertToProductAdminInfoResponse(product)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果需要包含文档信息
|
|
|
|
|
|
if query.WithDocument != nil && *query.WithDocument {
|
|
|
|
|
|
doc, err := s.documentationAppService.GetDocumentationByProductID(ctx, query.ID)
|
|
|
|
|
|
if err == nil && doc != nil {
|
|
|
|
|
|
response.Documentation = doc
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
2025-07-30 00:51:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetProductByIDForUser 根据ID获取产品(用户端专用)
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 业务流程:1. 获取产品信息 2. 构建用户响应数据
|
|
|
|
|
|
// 注意:详情接口不受 is_visible 字段影响,可通过直接访问详情接口查看任何产品
|
2025-07-31 15:41:00 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Context, query *appQueries.GetProductDetailQuery) (*responses.ProductInfoWithDocumentResponse, error) {
|
2025-08-02 20:44:09 +08:00
|
|
|
|
// 首先尝试通过新ID查找产品
|
2025-07-30 00:51:22 +08:00
|
|
|
|
product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID)
|
|
|
|
|
|
if err != nil {
|
2025-08-02 20:44:09 +08:00
|
|
|
|
// 如果通过新ID找不到,尝试通过旧ID查找
|
|
|
|
|
|
product, err = s.productManagementService.GetProductByOldIDWithCategory(ctx, query.ID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-07-30 00:51:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-31 15:41:00 +08:00
|
|
|
|
response := &responses.ProductInfoWithDocumentResponse{
|
|
|
|
|
|
ProductInfoResponse: *s.convertToProductInfoResponse(product),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果需要包含文档信息
|
|
|
|
|
|
if query.WithDocument != nil && *query.WithDocument {
|
2025-08-02 20:44:09 +08:00
|
|
|
|
doc, err := s.documentationAppService.GetDocumentationByProductID(ctx, product.ID)
|
2025-07-31 15:41:00 +08:00
|
|
|
|
if err == nil && doc != nil {
|
|
|
|
|
|
response.Documentation = doc
|
2025-11-03 13:32:05 +08:00
|
|
|
|
} else if product.IsPackage && len(response.PackageItems) > 0 {
|
|
|
|
|
|
// 如果是组合包且没有自己的文档,尝试合并子产品的文档
|
|
|
|
|
|
mergedDoc := s.mergePackageItemsDocumentation(ctx, product.Code, response.PackageItems)
|
|
|
|
|
|
if mergedDoc != nil {
|
|
|
|
|
|
response.Documentation = mergedDoc
|
|
|
|
|
|
}
|
2025-07-31 15:41:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
2025-07-30 00:51:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// convertToProductInfoResponse 转换为产品信息响应
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
|
|
|
|
|
response := &responses.ProductInfoResponse{
|
2025-07-29 00:30:32 +08:00
|
|
|
|
ID: product.ID,
|
2025-08-02 20:44:09 +08:00
|
|
|
|
OldID: product.OldID,
|
2025-07-29 00:30:32 +08:00
|
|
|
|
Name: product.Name,
|
|
|
|
|
|
Code: product.Code,
|
|
|
|
|
|
Description: product.Description,
|
|
|
|
|
|
Content: product.Content,
|
|
|
|
|
|
CategoryID: product.CategoryID,
|
|
|
|
|
|
Price: product.Price.InexactFloat64(),
|
|
|
|
|
|
IsEnabled: product.IsEnabled,
|
|
|
|
|
|
IsPackage: product.IsPackage,
|
|
|
|
|
|
SEOTitle: product.SEOTitle,
|
2025-07-20 20:53:26 +08:00
|
|
|
|
SEODescription: product.SEODescription,
|
2025-07-29 00:30:32 +08:00
|
|
|
|
SEOKeywords: product.SEOKeywords,
|
|
|
|
|
|
CreatedAt: product.CreatedAt,
|
|
|
|
|
|
UpdatedAt: product.UpdatedAt,
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 添加分类信息
|
|
|
|
|
|
if product.Category != nil {
|
|
|
|
|
|
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 转换组合包项目信息
|
2025-07-30 00:51:22 +08:00
|
|
|
|
if product.IsPackage && len(product.PackageItems) > 0 {
|
|
|
|
|
|
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
|
|
|
|
|
for i, item := range product.PackageItems {
|
|
|
|
|
|
response.PackageItems[i] = &responses.PackageItemResponse{
|
|
|
|
|
|
ID: item.ID,
|
|
|
|
|
|
ProductID: item.ProductID,
|
|
|
|
|
|
ProductCode: item.Product.Code,
|
|
|
|
|
|
ProductName: item.Product.Name,
|
|
|
|
|
|
SortOrder: item.SortOrder,
|
|
|
|
|
|
Price: item.Product.Price.InexactFloat64(),
|
2025-11-13 22:13:05 +08:00
|
|
|
|
CostPrice: item.Product.CostPrice.InexactFloat64(),
|
2025-07-30 00:51:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
|
|
|
|
|
response := &responses.ProductAdminInfoResponse{
|
|
|
|
|
|
ID: product.ID,
|
2025-08-02 20:44:09 +08:00
|
|
|
|
OldID: product.OldID,
|
2025-07-30 00:51:22 +08:00
|
|
|
|
Name: product.Name,
|
|
|
|
|
|
Code: product.Code,
|
|
|
|
|
|
Description: product.Description,
|
|
|
|
|
|
Content: product.Content,
|
|
|
|
|
|
CategoryID: product.CategoryID,
|
|
|
|
|
|
Price: product.Price.InexactFloat64(),
|
2025-11-13 20:43:35 +08:00
|
|
|
|
CostPrice: product.CostPrice.InexactFloat64(),
|
|
|
|
|
|
Remark: product.Remark,
|
2025-07-30 00:51:22 +08:00
|
|
|
|
IsEnabled: product.IsEnabled,
|
|
|
|
|
|
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
|
|
|
|
|
IsPackage: product.IsPackage,
|
|
|
|
|
|
SEOTitle: product.SEOTitle,
|
|
|
|
|
|
SEODescription: product.SEODescription,
|
|
|
|
|
|
SEOKeywords: product.SEOKeywords,
|
|
|
|
|
|
CreatedAt: product.CreatedAt,
|
|
|
|
|
|
UpdatedAt: product.UpdatedAt,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加分类信息
|
|
|
|
|
|
if product.Category != nil {
|
|
|
|
|
|
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换组合包项目信息
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if product.IsPackage && len(product.PackageItems) > 0 {
|
|
|
|
|
|
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
|
|
|
|
|
for i, item := range product.PackageItems {
|
|
|
|
|
|
response.PackageItems[i] = &responses.PackageItemResponse{
|
|
|
|
|
|
ID: item.ID,
|
|
|
|
|
|
ProductID: item.ProductID,
|
|
|
|
|
|
ProductCode: item.Product.Code,
|
|
|
|
|
|
ProductName: item.Product.Name,
|
|
|
|
|
|
SortOrder: item.SortOrder,
|
|
|
|
|
|
Price: item.Product.Price.InexactFloat64(),
|
2025-11-13 22:13:05 +08:00
|
|
|
|
CostPrice: item.Product.CostPrice.InexactFloat64(),
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return response
|
2025-07-15 13:21:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// convertToCategoryInfoResponse 转换为分类信息响应
|
2025-07-15 13:21:34 +08:00
|
|
|
|
func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *entities.ProductCategory) *responses.CategoryInfoResponse {
|
|
|
|
|
|
return &responses.CategoryInfoResponse{
|
|
|
|
|
|
ID: category.ID,
|
|
|
|
|
|
Name: category.Name,
|
|
|
|
|
|
Description: category.Description,
|
|
|
|
|
|
IsEnabled: category.IsEnabled,
|
|
|
|
|
|
CreatedAt: category.CreatedAt,
|
|
|
|
|
|
UpdatedAt: category.UpdatedAt,
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetProductApiConfig 获取产品API配置
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
|
|
|
|
|
|
return s.productApiConfigAppService.GetProductApiConfig(ctx, productID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateProductApiConfig 创建产品API配置
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error {
|
|
|
|
|
|
return s.productApiConfigAppService.CreateProductApiConfig(ctx, productID, config)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdateProductApiConfig 更新产品API配置
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error {
|
|
|
|
|
|
return s.productApiConfigAppService.UpdateProductApiConfig(ctx, configID, config)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DeleteProductApiConfig 删除产品API配置
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) DeleteProductApiConfig(ctx context.Context, configID string) error {
|
|
|
|
|
|
return s.productApiConfigAppService.DeleteProductApiConfig(ctx, configID)
|
2025-07-29 00:30:32 +08:00
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// mergePackageItemsDocumentation 合并组合包子产品的文档
|
|
|
|
|
|
// packageCode: 组合包的产品编号,用于生成请求地址
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) mergePackageItemsDocumentation(ctx context.Context, packageCode string, packageItems []*responses.PackageItemResponse) *responses.DocumentationResponse {
|
|
|
|
|
|
if len(packageItems) == 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有子产品的ID
|
|
|
|
|
|
productIDs := make([]string, 0, len(packageItems))
|
|
|
|
|
|
for _, item := range packageItems {
|
|
|
|
|
|
productIDs = append(productIDs, item.ProductID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量获取子产品的文档
|
|
|
|
|
|
docs, err := s.documentationAppService.GetDocumentationsByProductIDs(ctx, productIDs)
|
|
|
|
|
|
if err != nil || len(docs) == 0 {
|
|
|
|
|
|
s.logger.Debug("组合包子产品文档获取失败或为空", zap.Error(err))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建文档映射,方便按产品ID查找
|
|
|
|
|
|
docMap := make(map[string]*responses.DocumentationResponse)
|
|
|
|
|
|
for i := range docs {
|
|
|
|
|
|
docMap[docs[i].ProductID] = &docs[i]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合并文档内容
|
|
|
|
|
|
mergedDoc := &responses.DocumentationResponse{
|
|
|
|
|
|
ProductID: packageItems[0].ProductID, // 使用第一个子产品的ID作为标识
|
2025-11-09 16:08:58 +08:00
|
|
|
|
RequestMethod: "POST", // 默认方法
|
2025-11-03 13:32:05 +08:00
|
|
|
|
Version: "1.0",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集子产品的文档数据,用于构建组合包响应结构
|
|
|
|
|
|
subProductDocs := make([]subProductDocInfo, 0)
|
|
|
|
|
|
|
|
|
|
|
|
// 按packageItems的顺序合并文档
|
|
|
|
|
|
for _, item := range packageItems {
|
|
|
|
|
|
doc, exists := docMap[item.ProductID]
|
|
|
|
|
|
if !exists || doc == nil {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存子产品文档信息,用于构建组合包响应结构
|
|
|
|
|
|
subProductDocs = append(subProductDocs, subProductDocInfo{
|
|
|
|
|
|
item: item,
|
|
|
|
|
|
doc: doc,
|
|
|
|
|
|
responseFields: doc.ResponseFields,
|
|
|
|
|
|
responseExample: doc.ResponseExample,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建组合包的返回字段说明
|
|
|
|
|
|
if len(subProductDocs) > 0 {
|
|
|
|
|
|
mergedDoc.ResponseFields = s.buildCombPackageResponseFields(subProductDocs)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建组合包的响应示例
|
|
|
|
|
|
if len(subProductDocs) > 0 {
|
|
|
|
|
|
mergedDoc.ResponseExample = s.buildCombPackageResponseExample(subProductDocs)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合并请求参数(从DTO结构体中提取所有子产品的参数,去重后生成统一文档)
|
|
|
|
|
|
if len(packageItems) > 0 {
|
|
|
|
|
|
mergedDoc.RequestParams = s.mergeRequestParamsFromDTOs(ctx, packageItems)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置请求地址和方法(使用组合包的产品编号)
|
|
|
|
|
|
mergedDoc.RequestURL = fmt.Sprintf("https://api.tianyuanapi.com/api/v1/%s?t=13位时间戳", packageCode)
|
|
|
|
|
|
mergedDoc.RequestMethod = "POST"
|
|
|
|
|
|
|
|
|
|
|
|
// 错误代码和请求方式部分设置为空,前端会显示默认内容
|
|
|
|
|
|
mergedDoc.ErrorCodes = ""
|
|
|
|
|
|
mergedDoc.BasicInfo = ""
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有任何有意义的内容(请求参数、返回字段、响应示例都没有),返回nil
|
|
|
|
|
|
if mergedDoc.RequestParams == "" && mergedDoc.ResponseFields == "" && mergedDoc.ResponseExample == "" {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return mergedDoc
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// joinParts 使用分隔符连接文档部分
|
|
|
|
|
|
func joinParts(parts []string) string {
|
|
|
|
|
|
if len(parts) == 0 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(parts) == 1 {
|
|
|
|
|
|
return parts[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用分隔线分隔不同产品的文档
|
|
|
|
|
|
return strings.Join(parts, "\n\n---\n\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// subProductDocInfo 子产品文档信息
|
|
|
|
|
|
type subProductDocInfo struct {
|
|
|
|
|
|
item *responses.PackageItemResponse
|
|
|
|
|
|
doc *responses.DocumentationResponse
|
|
|
|
|
|
responseFields string
|
|
|
|
|
|
responseExample string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// buildCombPackageResponseFields 构建组合包的返回字段说明
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) buildCombPackageResponseFields(subProductDocs []subProductDocInfo) string {
|
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
|
|
|
|
|
|
|
// 组合包响应结构说明
|
|
|
|
|
|
builder.WriteString("## 组合包响应结构\n\n")
|
|
|
|
|
|
builder.WriteString("组合包API返回的是一个包含所有子产品响应结果的数组。\n\n")
|
|
|
|
|
|
builder.WriteString("| 字段名 | 类型 | 说明 |\n")
|
|
|
|
|
|
builder.WriteString("|--------|------|------|\n")
|
|
|
|
|
|
builder.WriteString("| responses | array | 子产品响应列表 |\n")
|
|
|
|
|
|
builder.WriteString("| responses[].api_code | string | 子产品代码 |\n")
|
|
|
|
|
|
builder.WriteString("| responses[].success | boolean | 该子产品调用是否成功 |\n")
|
|
|
|
|
|
builder.WriteString("| responses[].data | object/null | 子产品的响应数据(成功时返回数据,失败时为null) |\n")
|
|
|
|
|
|
builder.WriteString("| responses[].error | string | 错误信息(仅在失败时返回) |\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
// 各个子产品的data字段说明
|
|
|
|
|
|
builder.WriteString("## 子产品响应数据说明\n\n")
|
|
|
|
|
|
for i, spDoc := range subProductDocs {
|
|
|
|
|
|
if i > 0 {
|
|
|
|
|
|
builder.WriteString("\n---\n\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
productTitle := fmt.Sprintf("### %s (%s) 的 data 字段", spDoc.item.ProductName, spDoc.item.ProductCode)
|
|
|
|
|
|
builder.WriteString(productTitle + "\n\n")
|
|
|
|
|
|
if spDoc.responseFields != "" {
|
|
|
|
|
|
// 移除子产品文档中可能存在的标题,只保留字段说明
|
|
|
|
|
|
fields := spDoc.responseFields
|
|
|
|
|
|
// 如果包含标题,尝试提取data字段相关的部分
|
|
|
|
|
|
if strings.Contains(fields, "data") || strings.Contains(fields, "返回字段") {
|
|
|
|
|
|
builder.WriteString(fields)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
builder.WriteString(fields)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
builder.WriteString("该子产品的 data 字段结构请参考该产品的单独文档。")
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return builder.String()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// buildCombPackageResponseExample 构建组合包的响应示例
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) buildCombPackageResponseExample(subProductDocs []subProductDocInfo) string {
|
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
|
|
|
|
|
|
|
builder.WriteString("## 组合包响应示例\n\n")
|
|
|
|
|
|
builder.WriteString("### 成功响应(部分子产品成功)\n\n")
|
|
|
|
|
|
builder.WriteString("```json\n")
|
|
|
|
|
|
builder.WriteString("{\n")
|
|
|
|
|
|
builder.WriteString(" \"responses\": [\n")
|
|
|
|
|
|
|
|
|
|
|
|
// 构建示例JSON - 第一个成功,第二个可能失败
|
|
|
|
|
|
for i, spDoc := range subProductDocs {
|
|
|
|
|
|
if i > 0 {
|
|
|
|
|
|
builder.WriteString(",\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString(" {\n")
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" \"api_code\": \"%s\",\n", spDoc.item.ProductCode))
|
2025-11-09 16:08:58 +08:00
|
|
|
|
|
2025-11-03 13:32:05 +08:00
|
|
|
|
// 第一个示例显示成功,其他可能显示部分失败
|
|
|
|
|
|
if i == 0 {
|
|
|
|
|
|
builder.WriteString(" \"success\": true,\n")
|
|
|
|
|
|
builder.WriteString(" \"data\": {\n")
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" // %s 的实际响应数据\n", spDoc.item.ProductName))
|
|
|
|
|
|
builder.WriteString(" // 具体字段结构请参考下方该子产品的详细响应示例\n")
|
|
|
|
|
|
builder.WriteString(" }\n")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 第二个可能成功也可能失败,展示成功的情况
|
|
|
|
|
|
builder.WriteString(" \"success\": true,\n")
|
|
|
|
|
|
builder.WriteString(" \"data\": {}\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString(" }")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
builder.WriteString("\n ]\n")
|
|
|
|
|
|
builder.WriteString("}\n")
|
|
|
|
|
|
builder.WriteString("```\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
// 添加失败示例
|
|
|
|
|
|
builder.WriteString("### 部分失败响应示例\n\n")
|
|
|
|
|
|
builder.WriteString("```json\n")
|
|
|
|
|
|
builder.WriteString("{\n")
|
|
|
|
|
|
builder.WriteString(" \"responses\": [\n")
|
|
|
|
|
|
builder.WriteString(" {\n")
|
|
|
|
|
|
if len(subProductDocs) > 0 {
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" \"api_code\": \"%s\",\n", subProductDocs[0].item.ProductCode))
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString(" \"success\": false,\n")
|
|
|
|
|
|
builder.WriteString(" \"data\": null,\n")
|
|
|
|
|
|
builder.WriteString(" \"error\": \"参数校验不正确\"\n")
|
|
|
|
|
|
builder.WriteString(" }")
|
|
|
|
|
|
if len(subProductDocs) > 1 {
|
|
|
|
|
|
builder.WriteString(",\n")
|
|
|
|
|
|
builder.WriteString(" {\n")
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" \"api_code\": \"%s\",\n", subProductDocs[1].item.ProductCode))
|
|
|
|
|
|
builder.WriteString(" \"success\": true,\n")
|
|
|
|
|
|
builder.WriteString(" \"data\": {}\n")
|
|
|
|
|
|
builder.WriteString(" }")
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString("\n ]\n")
|
|
|
|
|
|
builder.WriteString("}\n")
|
|
|
|
|
|
builder.WriteString("```\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
// 添加各子产品的详细响应示例说明
|
|
|
|
|
|
if len(subProductDocs) > 0 {
|
|
|
|
|
|
builder.WriteString("## 各子产品详细响应示例\n\n")
|
|
|
|
|
|
builder.WriteString("以下为各个子产品的详细响应示例,组合包响应中的 `data` 字段即为对应子产品的完整响应数据。\n\n")
|
2025-11-09 16:08:58 +08:00
|
|
|
|
|
2025-11-03 13:32:05 +08:00
|
|
|
|
for i, spDoc := range subProductDocs {
|
|
|
|
|
|
if i > 0 {
|
|
|
|
|
|
builder.WriteString("\n---\n\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
productTitle := fmt.Sprintf("### %s (%s)", spDoc.item.ProductName, spDoc.item.ProductCode)
|
|
|
|
|
|
builder.WriteString(productTitle + "\n\n")
|
|
|
|
|
|
if spDoc.responseExample != "" {
|
|
|
|
|
|
builder.WriteString(spDoc.responseExample)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
builder.WriteString("该子产品的响应示例请参考该产品的单独文档。")
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return builder.String()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// paramField 参数字段信息
|
|
|
|
|
|
type paramField struct {
|
|
|
|
|
|
Name string
|
|
|
|
|
|
Type string
|
|
|
|
|
|
Required string
|
|
|
|
|
|
Description string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// mergeRequestParamsFromDTOs 从DTO结构体中提取并合并组合包的请求参数
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) mergeRequestParamsFromDTOs(ctx context.Context, packageItems []*responses.PackageItemResponse) string {
|
|
|
|
|
|
if len(packageItems) == 0 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 用于存储所有参数字段,key为字段名(json tag)
|
|
|
|
|
|
paramMap := make(map[string]*paramField)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取DTO映射(复用form_config_service的逻辑)
|
|
|
|
|
|
dtoMap := s.getDTOMap()
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历每个子产品,从DTO中提取字段
|
|
|
|
|
|
for _, item := range packageItems {
|
|
|
|
|
|
// 从DTO映射中获取子产品的DTO结构体
|
|
|
|
|
|
if dtoStruct, exists := dtoMap[item.ProductCode]; exists {
|
|
|
|
|
|
// 通过反射解析DTO字段
|
|
|
|
|
|
s.extractFieldsFromDTO(dtoStruct, paramMap)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有找到DTO,尝试通过FormConfigService获取
|
|
|
|
|
|
if s.formConfigService != nil {
|
|
|
|
|
|
formConfig, err := s.formConfigService.GetFormConfig(ctx, item.ProductCode)
|
|
|
|
|
|
if err == nil && formConfig != nil {
|
|
|
|
|
|
// 从表单配置中提取字段
|
|
|
|
|
|
for _, field := range formConfig.Fields {
|
|
|
|
|
|
if _, exists := paramMap[field.Name]; !exists {
|
|
|
|
|
|
requiredStr := "否"
|
|
|
|
|
|
if field.Required {
|
|
|
|
|
|
requiredStr = "是"
|
|
|
|
|
|
}
|
|
|
|
|
|
paramMap[field.Name] = ¶mField{
|
|
|
|
|
|
Name: field.Name,
|
|
|
|
|
|
Type: s.mapFieldTypeToDocType(field.Type),
|
|
|
|
|
|
Required: requiredStr,
|
|
|
|
|
|
Description: field.Description,
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果字段已存在,保留更详细的描述,如果新字段是必填则更新
|
|
|
|
|
|
existing := paramMap[field.Name]
|
|
|
|
|
|
if field.Description != "" && existing.Description == "" {
|
|
|
|
|
|
existing.Description = field.Description
|
|
|
|
|
|
}
|
|
|
|
|
|
if field.Required && existing.Required != "是" {
|
|
|
|
|
|
existing.Required = "是"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有提取到任何参数,返回空字符串
|
|
|
|
|
|
if len(paramMap) == 0 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建合并后的请求参数文档
|
|
|
|
|
|
var result strings.Builder
|
|
|
|
|
|
result.WriteString("## 请求参数\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
// 构建JSON示例
|
|
|
|
|
|
result.WriteString("```json\n{\n")
|
|
|
|
|
|
first := true
|
|
|
|
|
|
for fieldName := range paramMap {
|
|
|
|
|
|
if !first {
|
|
|
|
|
|
result.WriteString(",\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
result.WriteString(fmt.Sprintf(` "%s": "string"`, fieldName))
|
|
|
|
|
|
first = false
|
|
|
|
|
|
}
|
|
|
|
|
|
result.WriteString("\n}\n```\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
// 构建表格
|
|
|
|
|
|
result.WriteString("| 字段名 | 类型 | 必填 | 描述 |\n")
|
|
|
|
|
|
result.WriteString("|--------|------|------|------|\n")
|
|
|
|
|
|
|
|
|
|
|
|
// 按字段名排序输出
|
|
|
|
|
|
fieldNames := make([]string, 0, len(paramMap))
|
|
|
|
|
|
for fieldName := range paramMap {
|
|
|
|
|
|
fieldNames = append(fieldNames, fieldName)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 简单排序
|
|
|
|
|
|
for i := 0; i < len(fieldNames)-1; i++ {
|
|
|
|
|
|
for j := i + 1; j < len(fieldNames); j++ {
|
|
|
|
|
|
if fieldNames[i] > fieldNames[j] {
|
|
|
|
|
|
fieldNames[i], fieldNames[j] = fieldNames[j], fieldNames[i]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, fieldName := range fieldNames {
|
|
|
|
|
|
field := paramMap[fieldName]
|
|
|
|
|
|
result.WriteString(fmt.Sprintf("| %s | %s | %s | %s |\n",
|
|
|
|
|
|
field.Name, field.Type, field.Required, field.Description))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result.WriteString("\n通过加密后得到 Base64 字符串,将其放入到请求体中,字段名为 `data`。\n\n")
|
|
|
|
|
|
result.WriteString("```json\n{\n \"data\": \"xxxx(base64)\"\n}\n```")
|
|
|
|
|
|
|
|
|
|
|
|
return result.String()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getDTOMap 获取API代码到DTO结构体的映射(复用form_config_service的逻辑)
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
|
|
|
|
|
|
return map[string]interface{}{
|
|
|
|
|
|
"IVYZ9363": &dto.IVYZ9363Req{},
|
|
|
|
|
|
"IVYZ385E": &dto.IVYZ385EReq{},
|
|
|
|
|
|
"IVYZ5733": &dto.IVYZ5733Req{},
|
|
|
|
|
|
"FLXG3D56": &dto.FLXG3D56Req{},
|
|
|
|
|
|
"FLXG75FE": &dto.FLXG75FEReq{},
|
|
|
|
|
|
"FLXG0V3B": &dto.FLXG0V3BReq{},
|
|
|
|
|
|
"FLXG0V4B": &dto.FLXG0V4BReq{},
|
|
|
|
|
|
"FLXG54F5": &dto.FLXG54F5Req{},
|
|
|
|
|
|
"FLXG162A": &dto.FLXG162AReq{},
|
|
|
|
|
|
"FLXG0687": &dto.FLXG0687Req{},
|
|
|
|
|
|
"FLXGBC21": &dto.FLXGBC21Req{},
|
|
|
|
|
|
"FLXG970F": &dto.FLXG970FReq{},
|
|
|
|
|
|
"FLXG5876": &dto.FLXG5876Req{},
|
|
|
|
|
|
"FLXG9687": &dto.FLXG9687Req{},
|
|
|
|
|
|
"FLXGC9D1": &dto.FLXGC9D1Req{},
|
|
|
|
|
|
"FLXGCA3D": &dto.FLXGCA3DReq{},
|
|
|
|
|
|
"FLXGDEC7": &dto.FLXGDEC7Req{},
|
|
|
|
|
|
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
|
|
|
|
|
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
|
|
|
|
|
"JRZQ8203": &dto.JRZQ8203Req{},
|
2025-11-26 20:33:11 +08:00
|
|
|
|
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
2025-11-03 13:32:05 +08:00
|
|
|
|
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
|
|
|
|
|
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
|
|
|
|
|
"QYGL45BD": &dto.QYGL45BDReq{},
|
|
|
|
|
|
"QYGL8261": &dto.QYGL8261Req{},
|
|
|
|
|
|
"QYGL8271": &dto.QYGL8271Req{},
|
|
|
|
|
|
"QYGLB4C0": &dto.QYGLB4C0Req{},
|
|
|
|
|
|
"QYGL23T7": &dto.QYGL23T7Req{},
|
|
|
|
|
|
"QYGL5A3C": &dto.QYGL5A3CReq{},
|
|
|
|
|
|
"QYGL8B4D": &dto.QYGL8B4DReq{},
|
|
|
|
|
|
"QYGL9E2F": &dto.QYGL9E2FReq{},
|
|
|
|
|
|
"QYGL7C1A": &dto.QYGL7C1AReq{},
|
|
|
|
|
|
"QYGL3F8E": &dto.QYGL3F8EReq{},
|
|
|
|
|
|
"YYSY4B37": &dto.YYSY4B37Req{},
|
|
|
|
|
|
"YYSY4B21": &dto.YYSY4B21Req{},
|
|
|
|
|
|
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
|
|
|
|
|
"YYSY09CD": &dto.YYSY09CDReq{},
|
2025-11-29 15:23:57 +08:00
|
|
|
|
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
2025-11-03 13:32:05 +08:00
|
|
|
|
"YYSYBE08": &dto.YYSYBE08Req{},
|
|
|
|
|
|
"YYSYD50F": &dto.YYSYD50FReq{},
|
|
|
|
|
|
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
|
|
|
|
|
"IVYZ9A2B": &dto.IVYZ9A2BReq{},
|
|
|
|
|
|
"IVYZ7F2A": &dto.IVYZ7F2AReq{},
|
|
|
|
|
|
"IVYZ4E8B": &dto.IVYZ4E8BReq{},
|
|
|
|
|
|
"IVYZ1C9D": &dto.IVYZ1C9DReq{},
|
|
|
|
|
|
"IVYZGZ08": &dto.IVYZGZ08Req{},
|
|
|
|
|
|
"FLXG8A3F": &dto.FLXG8A3FReq{},
|
|
|
|
|
|
"FLXG5B2E": &dto.FLXG5B2EReq{},
|
|
|
|
|
|
"COMB298Y": &dto.COMB298YReq{},
|
|
|
|
|
|
"COMB86PM": &dto.COMB86PMReq{},
|
|
|
|
|
|
"QCXG7A2B": &dto.QCXG7A2BReq{},
|
|
|
|
|
|
"COMENT01": &dto.COMENT01Req{},
|
|
|
|
|
|
"JRZQ09J8": &dto.JRZQ09J8Req{},
|
|
|
|
|
|
"FLXGDEA8": &dto.FLXGDEA8Req{},
|
|
|
|
|
|
"FLXGDEA9": &dto.FLXGDEA9Req{},
|
|
|
|
|
|
"JRZQ1D09": &dto.JRZQ1D09Req{},
|
|
|
|
|
|
"IVYZ2A8B": &dto.IVYZ2A8BReq{},
|
|
|
|
|
|
"IVYZ7C9D": &dto.IVYZ7C9DReq{},
|
|
|
|
|
|
"IVYZ5E3F": &dto.IVYZ5E3FReq{},
|
|
|
|
|
|
"YYSY4F2E": &dto.YYSY4F2EReq{},
|
|
|
|
|
|
"YYSY8B1C": &dto.YYSY8B1CReq{},
|
|
|
|
|
|
"YYSY6D9A": &dto.YYSY6D9AReq{},
|
|
|
|
|
|
"YYSY3E7F": &dto.YYSY3E7FReq{},
|
|
|
|
|
|
"FLXG5A3B": &dto.FLXG5A3BReq{},
|
|
|
|
|
|
"FLXG9C1D": &dto.FLXG9C1DReq{},
|
|
|
|
|
|
"FLXG2E8F": &dto.FLXG2E8FReq{},
|
|
|
|
|
|
"JRZQ3C7B": &dto.JRZQ3C7BReq{},
|
|
|
|
|
|
"JRZQ8A2D": &dto.JRZQ8A2DReq{},
|
|
|
|
|
|
"JRZQ5E9F": &dto.JRZQ5E9FReq{},
|
|
|
|
|
|
"JRZQ4B6C": &dto.JRZQ4B6CReq{},
|
|
|
|
|
|
"JRZQ7F1A": &dto.JRZQ7F1AReq{},
|
|
|
|
|
|
"DWBG6A2C": &dto.DWBG6A2CReq{},
|
|
|
|
|
|
"DWBG8B4D": &dto.DWBG8B4DReq{},
|
|
|
|
|
|
"FLXG8B4D": &dto.FLXG8B4DReq{},
|
|
|
|
|
|
"IVYZ81NC": &dto.IVYZ81NCReq{},
|
|
|
|
|
|
"IVYZ7F3A": &dto.IVYZ7F3AReq{},
|
2025-11-09 16:08:58 +08:00
|
|
|
|
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
2025-11-03 13:32:05 +08:00
|
|
|
|
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
|
|
|
|
|
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
|
|
|
|
|
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
|
|
|
|
|
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
|
|
|
|
|
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
|
|
|
|
|
"JRZQ9E2A": &dto.JRZQ9E2AReq{},
|
|
|
|
|
|
"YYSY9A1B": &dto.YYSY9A1BReq{},
|
|
|
|
|
|
"YYSY8C2D": &dto.YYSY8C2DReq{},
|
|
|
|
|
|
"YYSY7D3E": &dto.YYSY7D3EReq{},
|
|
|
|
|
|
"YYSY9E4A": &dto.YYSY9E4AReq{},
|
|
|
|
|
|
"JRZQ6F2A": &dto.JRZQ6F2AReq{},
|
|
|
|
|
|
"JRZQ8B3C": &dto.JRZQ8B3CReq{},
|
|
|
|
|
|
"JRZQ9D4E": &dto.JRZQ9D4EReq{},
|
|
|
|
|
|
"FLXG7E8F": &dto.FLXG7E8FReq{},
|
|
|
|
|
|
"QYGL5F6A": &dto.QYGL5F6AReq{},
|
|
|
|
|
|
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
|
|
|
|
|
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
|
|
|
|
|
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// extractFieldsFromDTO 从DTO结构体中提取字段信息
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) extractFieldsFromDTO(dtoStruct interface{}, paramMap map[string]*paramField) {
|
|
|
|
|
|
if dtoStruct == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
t := reflect.TypeOf(dtoStruct).Elem()
|
|
|
|
|
|
if t == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
|
|
|
|
field := t.Field(i)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取JSON标签
|
|
|
|
|
|
jsonTag := field.Tag.Get("json")
|
|
|
|
|
|
if jsonTag == "" || jsonTag == "-" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 去除omitempty等选项
|
|
|
|
|
|
jsonTag = strings.Split(jsonTag, ",")[0]
|
|
|
|
|
|
|
|
|
|
|
|
// 跳过加密相关的字段(不应该出现在业务参数文档中)
|
|
|
|
|
|
if jsonTag == "data" || jsonTag == "encrypt" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取验证标签
|
|
|
|
|
|
validateTag := field.Tag.Get("validate")
|
|
|
|
|
|
required := strings.Contains(validateTag, "required")
|
|
|
|
|
|
requiredStr := "否"
|
|
|
|
|
|
if required {
|
|
|
|
|
|
requiredStr = "是"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取字段类型
|
|
|
|
|
|
fieldType := s.getGoTypeName(field.Type)
|
|
|
|
|
|
|
|
|
|
|
|
// 生成字段描述
|
|
|
|
|
|
description := s.generateFieldDescription(jsonTag, validateTag)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果字段已存在,保留更详细的描述,如果新字段是必填则更新
|
|
|
|
|
|
if existing, exists := paramMap[jsonTag]; exists {
|
|
|
|
|
|
if description != "" && existing.Description == "" {
|
|
|
|
|
|
existing.Description = description
|
|
|
|
|
|
}
|
|
|
|
|
|
if required && existing.Required != "是" {
|
|
|
|
|
|
existing.Required = "是"
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
paramMap[jsonTag] = ¶mField{
|
|
|
|
|
|
Name: jsonTag,
|
|
|
|
|
|
Type: fieldType,
|
|
|
|
|
|
Required: requiredStr,
|
|
|
|
|
|
Description: description,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getGoTypeName 获取Go类型的名称(转换为文档中的类型描述)
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) getGoTypeName(fieldType reflect.Type) string {
|
|
|
|
|
|
switch fieldType.Kind() {
|
|
|
|
|
|
case reflect.String:
|
|
|
|
|
|
return "string"
|
|
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
|
|
|
return "int"
|
|
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
|
|
|
|
return "int"
|
|
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
|
|
|
|
return "float"
|
|
|
|
|
|
case reflect.Bool:
|
|
|
|
|
|
return "boolean"
|
|
|
|
|
|
case reflect.Slice, reflect.Array:
|
|
|
|
|
|
return "array"
|
|
|
|
|
|
case reflect.Map:
|
|
|
|
|
|
return "object"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "string"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// generateFieldDescription 生成字段描述
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) generateFieldDescription(jsonTag string, validateTag string) string {
|
|
|
|
|
|
// 基础字段描述映射
|
|
|
|
|
|
descMap := map[string]string{
|
|
|
|
|
|
"mobile_no": "手机号码(11位)",
|
|
|
|
|
|
"id_card": "身份证号码(18位)",
|
2025-11-09 16:08:58 +08:00
|
|
|
|
"name": "姓名",
|
2025-11-03 13:32:05 +08:00
|
|
|
|
"man_name": "男方姓名",
|
|
|
|
|
|
"woman_name": "女方姓名",
|
|
|
|
|
|
"man_id_card": "男方身份证号码",
|
|
|
|
|
|
"woman_id_card": "女方身份证号码",
|
|
|
|
|
|
"ent_name": "企业名称",
|
|
|
|
|
|
"legal_person": "法人姓名",
|
|
|
|
|
|
"ent_code": "统一社会信用代码",
|
|
|
|
|
|
"auth_date": "授权日期范围(格式:YYYYMMDD-YYYYMMDD)",
|
|
|
|
|
|
"time_range": "时间范围(格式:HH:MM-HH:MM)",
|
|
|
|
|
|
"authorized": "是否授权(0-未授权,1-已授权)",
|
|
|
|
|
|
"authorization_url": "授权书URL地址(支持格式:pdf/jpg/jpeg/png/bmp)",
|
|
|
|
|
|
"unique_id": "唯一标识",
|
|
|
|
|
|
"return_url": "返回链接",
|
|
|
|
|
|
"mobile_type": "手机类型",
|
|
|
|
|
|
"start_date": "开始日期",
|
|
|
|
|
|
"years": "查询年数(0-100)",
|
|
|
|
|
|
"bank_card": "银行卡号",
|
|
|
|
|
|
"user_type": "关系类型(1-ETC开户人;2-车辆所有人;3-ETC经办人)",
|
|
|
|
|
|
"vehicle_type": "车辆类型(0-客车;1-货车;2-全部)",
|
|
|
|
|
|
"page_num": "页码(从1开始)",
|
|
|
|
|
|
"page_size": "每页数量(1-100)",
|
|
|
|
|
|
"use_scenario": "使用场景(1-信贷审核;2-保险评估;3-招聘背景调查;4-其他业务场景;99-其他)",
|
|
|
|
|
|
"wo": "我的",
|
|
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
2025-11-03 13:32:05 +08:00
|
|
|
|
if desc, exists := descMap[jsonTag]; exists {
|
|
|
|
|
|
return desc
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有预定义,根据验证规则生成描述
|
|
|
|
|
|
if strings.Contains(validateTag, "required") {
|
|
|
|
|
|
return "必填字段"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// mapFieldTypeToDocType 将前端字段类型映射为文档类型
|
|
|
|
|
|
func (s *ProductApplicationServiceImpl) mapFieldTypeToDocType(frontendType string) string {
|
|
|
|
|
|
switch frontendType {
|
|
|
|
|
|
case "tel", "text":
|
|
|
|
|
|
return "string"
|
|
|
|
|
|
case "number":
|
|
|
|
|
|
return "int"
|
|
|
|
|
|
case "checkbox":
|
|
|
|
|
|
return "boolean"
|
|
|
|
|
|
case "select":
|
|
|
|
|
|
return "string"
|
|
|
|
|
|
case "date":
|
|
|
|
|
|
return "string"
|
|
|
|
|
|
case "url":
|
|
|
|
|
|
return "string"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "string"
|
|
|
|
|
|
}
|
2025-11-09 16:08:58 +08:00
|
|
|
|
}
|