package product import ( "context" "fmt" "reflect" "strings" "github.com/shopspring/decimal" "go.uber.org/zap" "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/api/dto" api_services "tyapi-server/internal/domains/api/services" "tyapi-server/internal/domains/product/entities" product_service "tyapi-server/internal/domains/product/services" "tyapi-server/internal/shared/interfaces" ) // ProductApplicationServiceImpl 产品应用服务实现 // 负责业务流程编排、事务管理、数据转换,不直接操作仓库 type ProductApplicationServiceImpl struct { productManagementService *product_service.ProductManagementService productSubscriptionService *product_service.ProductSubscriptionService productApiConfigAppService ProductApiConfigApplicationService documentationAppService DocumentationApplicationServiceInterface formConfigService api_services.FormConfigService logger *zap.Logger } // NewProductApplicationService 创建产品应用服务 func NewProductApplicationService( productManagementService *product_service.ProductManagementService, productSubscriptionService *product_service.ProductSubscriptionService, productApiConfigAppService ProductApiConfigApplicationService, documentationAppService DocumentationApplicationServiceInterface, formConfigService api_services.FormConfigService, logger *zap.Logger, ) ProductApplicationService { return &ProductApplicationServiceImpl{ productManagementService: productManagementService, productSubscriptionService: productSubscriptionService, productApiConfigAppService: productApiConfigAppService, documentationAppService: documentationAppService, formConfigService: formConfigService, logger: logger, } } // CreateProduct 创建产品 // 业务流程�?. 构建产品实体 2. 创建产品 func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) { // 1. 构建产品实体 product := &entities.Product{ Name: cmd.Name, Code: cmd.Code, Description: cmd.Description, Content: cmd.Content, CategoryID: cmd.CategoryID, Price: decimal.NewFromFloat(cmd.Price), CostPrice: decimal.NewFromFloat(cmd.CostPrice), Remark: cmd.Remark, IsEnabled: cmd.IsEnabled, IsVisible: cmd.IsVisible, IsPackage: cmd.IsPackage, SEOTitle: cmd.SEOTitle, SEODescription: cmd.SEODescription, SEOKeywords: cmd.SEOKeywords, } // 2. 创建产品 createdProduct, err := s.productManagementService.CreateProduct(ctx, product) if err != nil { return nil, err } // 3. 转换为响应对象 return s.convertToProductAdminInfoResponse(createdProduct), nil } // UpdateProduct 更新产品 // 业务流程�?. 获取现有产品 2. 更新产品信息 3. 保存产品 func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error { // 1. 获取现有产品 existingProduct, err := s.productManagementService.GetProductByID(ctx, cmd.ID) if err != nil { return err } // 2. 更新产品信息 existingProduct.Name = cmd.Name existingProduct.Code = cmd.Code existingProduct.Description = cmd.Description existingProduct.Content = cmd.Content existingProduct.CategoryID = cmd.CategoryID existingProduct.Price = decimal.NewFromFloat(cmd.Price) existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice) existingProduct.Remark = cmd.Remark existingProduct.IsEnabled = cmd.IsEnabled existingProduct.IsVisible = cmd.IsVisible existingProduct.IsPackage = cmd.IsPackage existingProduct.SEOTitle = cmd.SEOTitle existingProduct.SEODescription = cmd.SEODescription existingProduct.SEOKeywords = cmd.SEOKeywords // 3. 保存产品 return s.productManagementService.UpdateProduct(ctx, existingProduct) } // DeleteProduct 删除产品 // 业务流程�?. 删除产品 func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error { return s.productManagementService.DeleteProduct(ctx, cmd.ID) } // ListProducts 获取产品列表 // 业务流程�?. 获取产品列表 2. 构建响应数据 func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductListResponse, error) { // 检查是否有用户ID,如果有则使用带订阅状态的方法 if userID, ok := filters["user_id"].(string); ok && userID != "" { return s.ListProductsWithSubscriptionStatus(ctx, filters, options) } // 调用领域服务获取产品列表 products, total, err := s.productManagementService.ListProducts(ctx, filters, options) if err != nil { return nil, err } // 转换为响应对象 items := make([]responses.ProductInfoResponse, len(products)) for i := range products { items[i] = *s.convertToProductInfoResponse(products[i]) } return &responses.ProductListResponse{ Total: total, Page: options.Page, Size: options.PageSize, Items: items, }, nil } // ListProductsWithSubscriptionStatus 获取产品列表(包含订阅状态) // 业务流程�?. 获取产品列表和订阅状�?2. 构建响应数据 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 } // GetProductsByIDs 根据ID列表获取产品 // 业务流程�?. 获取产品列表 2. 构建响应数据 func (s *ProductApplicationServiceImpl) GetProductsByIDs(ctx context.Context, query *appQueries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error) { // 这里需要扩展领域服务来支持批量获取 // 暂时返回空列表 return []*responses.ProductInfoResponse{}, nil } // GetSubscribableProducts 获取可订阅产品列表 // 业务流程:1. 获取启用产品 2. 过滤可订阅产品 3. 构建响应数据 func (s *ProductApplicationServiceImpl) GetSubscribableProducts(ctx context.Context, query *appQueries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error) { products, err := s.productManagementService.GetEnabledProducts(ctx) if err != nil { return nil, err } // 过滤可订阅的产品 var subscribableProducts []*entities.Product for _, product := range products { if product.CanBeSubscribed() { subscribableProducts = append(subscribableProducts, product) } } // 转换为响应对象 items := make([]*responses.ProductInfoResponse, len(subscribableProducts)) for i := range subscribableProducts { items[i] = s.convertToProductInfoResponse(subscribableProducts[i]) } return items, nil } // GetProductByID 根据ID获取产品 // 业务流程�?. 获取产品信息 2. 构建响应数据 func (s *ProductApplicationServiceImpl) GetProductByID(ctx context.Context, query *appQueries.GetProductQuery) (*responses.ProductInfoResponse, error) { product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID) if err != nil { return nil, err } return s.convertToProductInfoResponse(product), nil } // GetProductStats 获取产品统计信息 // 业务流程�?. 获取产品统计 2. 构建响应数据 func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error) { stats, err := s.productSubscriptionService.GetProductStats(ctx) if err != nil { return nil, err } return &responses.ProductStatsResponse{ TotalProducts: stats["total"], EnabledProducts: stats["enabled"], VisibleProducts: stats["visible"], PackageProducts: 0, // 需要单独统计 }, nil } // 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) } // GetAvailableProducts 获取可选子产品列表(管理员端,返回包含成本价的数据) // 业务流程:1. 获取启用产品 2. 过滤可订阅产品 3. 构建管理员响应数据 func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context, query *appQueries.GetAvailableProductsQuery) (*responses.ProductAdminListResponse, error) { // 构建筛选条件 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 } // 转换为管理员响应对象(包含成本价,用于组合包配置) 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 } // 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. 构建管理员响应数据 func (s *ProductApplicationServiceImpl) GetProductByIDForAdmin(ctx context.Context, query *appQueries.GetProductDetailQuery) (*responses.ProductAdminInfoResponse, error) { product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID) if err != nil { return nil, err } 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 } // GetProductByIDForUser 根据ID获取产品(用户端专用) // 业务流程:1. 获取产品信息 2. 构建用户响应数据 // 注意:详情接口不受 is_visible 字段影响,可通过直接访问详情接口查看任何产品 func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Context, query *appQueries.GetProductDetailQuery) (*responses.ProductInfoWithDocumentResponse, error) { // 首先尝试通过新ID查找产品 product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID) if err != nil { // 如果通过新ID找不到,尝试通过旧ID查找 product, err = s.productManagementService.GetProductByOldIDWithCategory(ctx, query.ID) if err != nil { return nil, err } } response := &responses.ProductInfoWithDocumentResponse{ ProductInfoResponse: *s.convertToProductInfoResponse(product), } // 如果需要包含文档信息 if query.WithDocument != nil && *query.WithDocument { doc, err := s.documentationAppService.GetDocumentationByProductID(ctx, product.ID) if err == nil && doc != nil { response.Documentation = doc } else if product.IsPackage && len(response.PackageItems) > 0 { // 如果是组合包且没有自己的文档,尝试合并子产品的文档 mergedDoc := s.mergePackageItemsDocumentation(ctx, product.Code, response.PackageItems) if mergedDoc != nil { response.Documentation = mergedDoc } } } return response, nil } // convertToProductInfoResponse 转换为产品信息响应 func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse { response := &responses.ProductInfoResponse{ ID: product.ID, OldID: product.OldID, 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, SEODescription: product.SEODescription, SEOKeywords: product.SEOKeywords, CreatedAt: product.CreatedAt, UpdatedAt: product.UpdatedAt, } // 添加分类信息 if product.Category != nil { response.Category = s.convertToCategoryInfoResponse(product.Category) } // 转换组合包项目信息 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(), CostPrice: item.Product.CostPrice.InexactFloat64(), } } } return response } // convertToProductAdminInfoResponse 转换为管理员产品信息响应 func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse { response := &responses.ProductAdminInfoResponse{ ID: product.ID, OldID: product.OldID, Name: product.Name, Code: product.Code, Description: product.Description, Content: product.Content, CategoryID: product.CategoryID, Price: product.Price.InexactFloat64(), CostPrice: product.CostPrice.InexactFloat64(), Remark: product.Remark, 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) } // 转换组合包项目信息 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(), CostPrice: item.Product.CostPrice.InexactFloat64(), } } } return response } // convertToCategoryInfoResponse 转换为分类信息响应 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, } } // 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) } // 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作为标识 RequestMethod: "POST", // 默认方法 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)) // 第一个示例显示成功,其他可能显示部分失败 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") 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{}, "JRZQDCBE": &dto.JRZQDCBEReq{}, "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{}, "IVYZ0B03": &dto.IVYZ0B03Req{}, "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{}, "IVYZ3P9M": &dto.IVYZ3P9MReq{}, "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位)", "name": "姓名", "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": "我的", } 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" } }