Files
tyapi-server/internal/application/product/product_application_service_impl.go
2025-11-13 20:43:35 +08:00

1190 lines
40 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 创建产品
// 业务流程<E6B581>?. 构建产品实体 2. 创建产品
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) 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. 创建产品
_, err := s.productManagementService.CreateProduct(ctx, product)
return err
}
// UpdateProduct 更新产品
// 业务流程<E6B581>?. 获取现有产品 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 删除产品
// 业务流程<E6B581>?. 删除产品
func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error {
return s.productManagementService.DeleteProduct(ctx, cmd.ID)
}
// ListProducts 获取产品列表
// 业务流程<E6B581>?. 获取产品列表 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 获取产品列表(包含订阅状态)
// 业务流程<E6B581>?. 获取产品列表和订阅状<E99885>?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列表获取产品
// 业务流程<E6B581>?. 获取产品列表 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获取产品
// 业务流程<E6B581>?. 获取产品信息 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 获取产品统计信息
// 业务流程<E6B581>?. 获取产品统计 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.ProductListResponse, 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.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
}
// 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(),
}
}
}
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(),
}
}
}
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] = &paramField{
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{},
"JRZQDBCE": &dto.JRZQDBCEReq{},
"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] = &paramField{
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"
}
}