fix
This commit is contained in:
@@ -3,6 +3,8 @@ package product
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
@@ -10,6 +12,8 @@ import (
|
||||
"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"
|
||||
@@ -22,6 +26,7 @@ type ProductApplicationServiceImpl struct {
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
productApiConfigAppService ProductApiConfigApplicationService
|
||||
documentationAppService DocumentationApplicationServiceInterface
|
||||
formConfigService api_services.FormConfigService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
@@ -31,6 +36,7 @@ func NewProductApplicationService(
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
productApiConfigAppService ProductApiConfigApplicationService,
|
||||
documentationAppService DocumentationApplicationServiceInterface,
|
||||
formConfigService api_services.FormConfigService,
|
||||
logger *zap.Logger,
|
||||
) ProductApplicationService {
|
||||
return &ProductApplicationServiceImpl{
|
||||
@@ -38,12 +44,13 @@ func NewProductApplicationService(
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
productApiConfigAppService: productApiConfigAppService,
|
||||
documentationAppService: documentationAppService,
|
||||
formConfigService: formConfigService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateProduct 创建产品
|
||||
// 业务流程:1. 构建产品实体 2. 创建产品
|
||||
// 业务流程<EFBFBD>?. 构建产品实体 2. 创建产品
|
||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error {
|
||||
// 1. 构建产品实体
|
||||
product := &entities.Product{
|
||||
@@ -67,7 +74,7 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
|
||||
}
|
||||
|
||||
// UpdateProduct 更新产品
|
||||
// 业务流程:1. 获取现有产品 2. 更新产品信息 3. 保存产品
|
||||
// 业务流程<EFBFBD>?. 获取现有产品 2. 更新产品信息 3. 保存产品
|
||||
func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error {
|
||||
// 1. 获取现有产品
|
||||
existingProduct, err := s.productManagementService.GetProductByID(ctx, cmd.ID)
|
||||
@@ -94,13 +101,13 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
||||
}
|
||||
|
||||
// DeleteProduct 删除产品
|
||||
// 业务流程:1. 删除产品
|
||||
// 业务流程<EFBFBD>?. 删除产品
|
||||
func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error {
|
||||
return s.productManagementService.DeleteProduct(ctx, cmd.ID)
|
||||
}
|
||||
|
||||
// ListProducts 获取产品列表
|
||||
// 业务流程:1. 获取产品列表 2. 构建响应数据
|
||||
// 业务流程<EFBFBD>?. 获取产品列表 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 != "" {
|
||||
@@ -128,7 +135,7 @@ func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, filter
|
||||
}
|
||||
|
||||
// ListProductsWithSubscriptionStatus 获取产品列表(包含订阅状态)
|
||||
// 业务流程:1. 获取产品列表和订阅状态 2. 构建响应数据
|
||||
// 业务流程<EFBFBD>?. 获取产品列表和订阅状<EFBFBD>?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)
|
||||
@@ -158,7 +165,7 @@ func (s *ProductApplicationServiceImpl) ListProductsWithSubscriptionStatus(ctx c
|
||||
}
|
||||
|
||||
// GetProductsByIDs 根据ID列表获取产品
|
||||
// 业务流程:1. 获取产品列表 2. 构建响应数据
|
||||
// 业务流程<EFBFBD>?. 获取产品列表 2. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetProductsByIDs(ctx context.Context, query *appQueries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error) {
|
||||
// 这里需要扩展领域服务来支持批量获取
|
||||
// 暂时返回空列表
|
||||
@@ -191,7 +198,7 @@ func (s *ProductApplicationServiceImpl) GetSubscribableProducts(ctx context.Cont
|
||||
}
|
||||
|
||||
// GetProductByID 根据ID获取产品
|
||||
// 业务流程:1. 获取产品信息 2. 构建响应数据
|
||||
// 业务流程<EFBFBD>?. 获取产品信息 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 {
|
||||
@@ -202,7 +209,7 @@ func (s *ProductApplicationServiceImpl) GetProductByID(ctx context.Context, quer
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计信息
|
||||
// 业务流程:1. 获取产品统计 2. 构建响应数据
|
||||
// 业务流程<EFBFBD>?. 获取产品统计 2. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error) {
|
||||
stats, err := s.productSubscriptionService.GetProductStats(ctx)
|
||||
if err != nil {
|
||||
@@ -353,7 +360,6 @@ func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context
|
||||
filters := make(map[string]interface{})
|
||||
filters["is_package"] = false // 只获取非组合包产品
|
||||
filters["is_enabled"] = true // 只获取启用产品
|
||||
|
||||
if query.Keyword != "" {
|
||||
filters["keyword"] = query.Keyword
|
||||
}
|
||||
@@ -434,7 +440,8 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForAdmin(ctx context.Conte
|
||||
}
|
||||
|
||||
// GetProductByIDForUser 根据ID获取产品(用户端专用)
|
||||
// 业务流程:1. 获取产品信息 2. 验证产品可见性 3. 构建用户响应数据
|
||||
// 业务流程: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)
|
||||
@@ -446,11 +453,6 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
|
||||
}
|
||||
}
|
||||
|
||||
// 用户端只能查看可见的产品
|
||||
if !product.IsVisible {
|
||||
return nil, fmt.Errorf("产品不存在或不可见")
|
||||
}
|
||||
|
||||
response := &responses.ProductInfoWithDocumentResponse{
|
||||
ProductInfoResponse: *s.convertToProductInfoResponse(product),
|
||||
}
|
||||
@@ -460,6 +462,12 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,4 +593,594 @@ func (s *ProductApplicationServiceImpl) DeleteProductApiConfig(ctx context.Conte
|
||||
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{},
|
||||
"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{},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -875,7 +875,23 @@ func NewContainer() *Container {
|
||||
),
|
||||
// 产品应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewProductApplicationService,
|
||||
func(
|
||||
productManagementService *product_services.ProductManagementService,
|
||||
productSubscriptionService *product_services.ProductSubscriptionService,
|
||||
productApiConfigAppService product.ProductApiConfigApplicationService,
|
||||
documentationAppService product.DocumentationApplicationServiceInterface,
|
||||
formConfigService api_services.FormConfigService,
|
||||
logger *zap.Logger,
|
||||
) product.ProductApplicationService {
|
||||
return product.NewProductApplicationService(
|
||||
productManagementService,
|
||||
productSubscriptionService,
|
||||
productApiConfigAppService,
|
||||
documentationAppService,
|
||||
formConfigService,
|
||||
logger,
|
||||
)
|
||||
},
|
||||
fx.As(new(product.ProductApplicationService)),
|
||||
),
|
||||
// 产品API配置应用服务 - 绑定到接口
|
||||
|
||||
@@ -176,7 +176,7 @@ func (h *ProductHandler) getCurrentUserID(c *gin.Context) string {
|
||||
|
||||
// GetProductDetail 获取产品详情
|
||||
// @Summary 获取产品详情
|
||||
// @Description 获取产品详细信息,用户端只能查看可见的产品
|
||||
// @Description 获取产品详细信息,详情接口不受 is_visible 字段影响,可通过直接访问查看任何产品
|
||||
// @Tags 数据大厅
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
@@ -184,7 +184,7 @@ func (h *ProductHandler) getCurrentUserID(c *gin.Context) string {
|
||||
// @Param with_document query bool false "是否包含文档信息"
|
||||
// @Success 200 {object} responses.ProductInfoWithDocumentResponse "获取产品详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 404 {object} map[string]interface{} "产品不存在或不可见"
|
||||
// @Failure 404 {object} map[string]interface{} "产品不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/products/{id} [get]
|
||||
func (h *ProductHandler) GetProductDetail(c *gin.Context) {
|
||||
@@ -205,7 +205,7 @@ func (h *ProductHandler) GetProductDetail(c *gin.Context) {
|
||||
result, err := h.appService.GetProductByIDForUser(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取产品详情失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, "产品不存在或不可见")
|
||||
h.responseBuilder.NotFound(c, "产品不存在")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user