Merge pull request '支持openapi文档的解析' (#20) from feature/support_parser into master

Reviewed-on: #20
This commit is contained in:
白茶清欢 2025-04-11 22:10:01 +08:00
commit 66e5b00562
3 changed files with 730 additions and 8 deletions

56
define/parser_api.go Normal file
View File

@ -0,0 +1,56 @@
// Package parser ...
//
// Description : parser ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-04-11 20:21
package define
// ApiConfig 解析出的接口配置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:21 2025/4/11
type ApiConfig struct {
Tag string `json:"tag" dc:"接口所属标签"` // 接口所属标签
Name string `json:"name" binding:"required,min=1" dc:"接口名称" err:"项目接口名称必须存在,且不能为空"` // 接口名称
Uri string `json:"uri" binding:"required,min=1,max=512" dc:"项目接口uri" err:"项目接口路径必须存在,且长度在[1,512]之间"` // 项目接口路由
MethodID int64 `json:"method_id" dc:"请求方法ID"` // 请求方法
Method string `json:"method" binding:"required_without=MethodID" dc:"请求方法" err:"请求方法与请求方法ID至少存在一个"`
ContentType string `json:"content_type" dc:"请求类型" binding:"required_without=ContentTypeID" err:"请求类型ID与请求类型至少存在一个"`
Description string `json:"description" dc:"项目接口描述"` // 接口描述
ParamList []*ApiParamItem `json:"param_list" dc:"项目接口参数列表"` // 参数列表
ResultList []*ApiResultItem `json:"result_list" dc:"项目接口返回值列表"` // 返回值列表
}
// ApiParamItem 接口参数配置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:23 2025/4/11
type ApiParamItem struct {
Title string `json:"title" binding:"required,min=1" dc:"参数标题" err:"参数标题必须存在,且不能为空"` // 参数标题
Name string `json:"name" binding:"required,min=1" dc:"参数名称" err:"参数名称必须存在,且不能为空"` // 参数名称
Location string `json:"location" binding:"required,oneof=HEADER COOKIE BODY QUERY PATH" dc:"参数位置" err:"参数位置必须存在,且需要是一个有效合法值"` // 参数位置
ParamType string `json:"param_type" binding:"required,validate_data_type" dc:"参数类型" err:"参数类型必须存在,且需要是一个有效的合法值"` // 参数类型
IsRequired bool `json:"is_required" binding:"required" dc:"参数是否必传" err:"参数是否必传必须存在,且需要是一个有效合法值"` // 是否必传
DefaultValue string `json:"default_value" dc:"默认值"` // 默认值
ExampleValue string `json:"example_value" dc:"示例值"` // 示例值
Description string `json:"description" dc:"参数描述"` // 参数描述
}
// ApiResultItem 接口返回值配置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:23 2025/4/11
type ApiResultItem struct {
Title string `json:"title" binding:"required,min=1,max=64" dc:"返回值标题" err:"返回值标题必须存在,且长度在[1,64]之间"` // 返回值标题
DataPath string `json:"data_path" binding:"required,min=1,max=512" dc:"返回值路径" err:"返回值路径必须存在,且长度在[1,512]之间"` // 返回值路径
DataLocation string `json:"data_location" binding:"required,oneof=BODY HEADER COOKIE" dc:"返回值位置" err:"返回值位置必须存在,其需要时一个有效的合法值"` // 返回值位置
DataType string `json:"data_type" binding:"required,validate_data_type" dc:"返回值类型" err:"返回值类型必须存在,且需要是一个有效的合法值"` // 返回值类型
DefaultValue string `json:"default_value" dc:"默认值"` // 默认值
ExampleValue string `json:"example_value" dc:"示例值"` // 示例值
Description string `json:"description" dc:"返回值描述"` // 返回值描述
}

View File

@ -6,3 +6,643 @@
//
// Date : 2025-04-11 20:07
package swagger
import (
"errors"
"fmt"
apiDocDefine "git.zhangdeman.cn/gateway/api-doc/define"
"git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/wrapper"
"net/http"
"sort"
"strings"
)
// HandleOpenapiDocRes ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 18:00 2025/2/26
func HandleOpenapiDocRes(docRes *apiDocDefine.OpenapiDoc, ignoreApiTable map[string]bool, dataField string) ([]*apiDocDefine.ApiConfig, error) {
if nil == ignoreApiTable {
ignoreApiTable = make(map[string]bool)
}
hod := &handleOpenapiDoc{
docRes: docRes,
parseRes: make([]*apiDocDefine.ApiConfig, 0),
ignoreApiTable: ignoreApiTable,
dataField: dataField,
}
if err := hod.Parse(); nil != err {
return nil, err
}
return hod.parseRes, nil
}
type handleOpenapiDoc struct {
docRes *apiDocDefine.OpenapiDoc
parseRes []*apiDocDefine.ApiConfig
ignoreApiTable map[string]bool
dataField string
}
func (hod *handleOpenapiDoc) Parse() error {
// 排序, 保证输出顺序一致性
uriList := make([]string, 0)
for uri := range hod.docRes.Paths {
uriList = append(uriList, uri)
}
sort.Strings(uriList)
for _, uri := range uriList {
itemPath := hod.docRes.Paths[uri]
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodGet, itemPath.Get); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodPut, itemPath.Put); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodPost, itemPath.Post); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodHead, itemPath.Head); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodPatch, itemPath.Patch); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodDelete, itemPath.Delete); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodConnect, itemPath.Connect); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodOptions, itemPath.Options); nil != err {
return err
}
if err := hod.apiPathConfigToProjectConfig(uri, http.MethodTrace, itemPath.Trace); nil != err {
return err
}
}
return nil
}
// apiPathConfigToProjectConfig 解析请求方法
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 18:48 2025/2/26
func (hod *handleOpenapiDoc) apiPathConfigToProjectConfig(uri string, method string, apiPathConfig *apiDocDefine.PathItemOperationConfig) error {
if hod.ignoreApiTable[fmt.Sprintf("%v_%v", strings.ToUpper(method), uri)] {
// 接口已存在
return nil
}
if apiPathConfig == nil {
return nil
}
requestContentTypeList := make([]string, 0)
if nil != apiPathConfig.RequestBody {
for contentType := range apiPathConfig.RequestBody.Content {
requestContentTypeList = append(requestContentTypeList, contentType)
}
}
if len(requestContentTypeList) == 0 {
requestContentTypeList = append(requestContentTypeList, consts.MimeTypeXWWWFormUrlencoded)
}
sort.Strings(requestContentTypeList)
tag := "未知分组"
if len(apiPathConfig.Tags) > 0 {
tag = apiPathConfig.Tags[0]
}
importUriConfig := &apiDocDefine.ApiConfig{
Tag: tag,
Name: apiPathConfig.Summary,
Uri: uri,
Method: strings.ToUpper(method),
ContentType: requestContentTypeList[0],
Description: apiPathConfig.Description,
ParamList: make([]*apiDocDefine.ApiParamItem, 0),
ResultList: make([]*apiDocDefine.ApiResultItem, 0),
}
// 解析公共的 Parameters , 任意请求方法都可能有 // TODO: 数组&对象递归处理
for _, itemParam := range apiPathConfig.Parameters {
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: itemParam.Name,
Name: itemParam.Name,
Location: hod.openapiDocLocationFormat(itemParam.In),
ParamType: hod.openapiDocType2GoType(fmt.Sprintf("%v", itemParam.Schema.Type), itemParam.Schema.Format),
IsRequired: itemParam.Required,
DefaultValue: "∂",
ExampleValue: "",
Description: itemParam.Description,
})
}
// post 类请求 body
if err := hod.handleRequestBody(importUriConfig, apiPathConfig.RequestBody, requestContentTypeList[0]); nil != err {
return err
}
// 处理response
if err := hod.handleResponseBody(importUriConfig, apiPathConfig.Responses); nil != err {
return err
}
// 解析response
hod.parseRes = append(hod.parseRes, importUriConfig)
return nil
}
// handleRequestBody 解析request body
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 12:17 2025/2/27
func (hod *handleOpenapiDoc) handleRequestBody(importUriConfig *apiDocDefine.ApiConfig, requestBodyData *apiDocDefine.RequestBody, selectRequestContentType string) error {
if nil == requestBodyData || nil == requestBodyData.Content {
return nil
}
if nil == requestBodyData.Content[selectRequestContentType] {
return fmt.Errorf("select content_type = %v is not found in requestBodyData.Content", selectRequestContentType)
}
requestBody := requestBodyData.Content[selectRequestContentType].Schema
if len(requestBody.Ref) == 0 {
return nil
}
requestBodyRequiredParamTable := map[string]bool{}
for _, itemParamName := range requestBody.Required {
requestBodyRequiredParamTable[itemParamName] = true
}
if len(requestBody.Ref) > 0 {
refCfg, err := hod.getResComponentsConfig(requestBody.Ref)
if nil != err {
return err
}
// ref 配置合并
if nil == requestBodyData.Content[selectRequestContentType].Schema.Properties {
requestBodyData.Content[selectRequestContentType].Schema.Properties = make(map[string]*apiDocDefine.Property)
}
for paramName, itemParam := range refCfg.Properties {
requestBodyData.Content[selectRequestContentType].Schema.Properties[paramName] = itemParam
}
for _, itemParamName := range refCfg.Required {
requestBodyRequiredParamTable[itemParamName] = true
}
}
for requestBodyParamName, requestBodyParamConfig := range requestBody.Properties {
// 对象、数组、引用递归解析
if err := hod.expendObjectOrArrayRequest(importUriConfig, requestBodyRequiredParamTable, "", "", requestBodyParamName, requestBodyParamConfig); nil != err {
return err
}
}
return nil
}
func (hod *handleOpenapiDoc) expendObjectOrArrayRequest(importUriConfig *apiDocDefine.ApiConfig, requestBodyRequiredParamTable map[string]bool, rootRef string, parentPath string, currentPropertyName string, currentProperty *apiDocDefine.Property) error {
if len(parentPath) > 0 {
currentPropertyName = parentPath + "." + currentPropertyName
}
defaultValue := wrapper.AnyDataType(currentProperty.Default).ToString().Value()
// 基础数据类型
if len(currentProperty.Properties) == 0 && currentProperty.Type != consts.SwaggerDataTypeObject && currentProperty.Type != consts.SwaggerDataTypeArray && len(currentProperty.Ref) == 0 {
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: currentPropertyName,
Name: currentPropertyName,
Location: consts.RequestDataLocationBody.String(),
ParamType: hod.openapiDocType2GoType(fmt.Sprintf("%v", currentProperty.Type), currentProperty.Format),
IsRequired: requestBodyRequiredParamTable[currentPropertyName],
DefaultValue: defaultValue,
ExampleValue: "",
Description: currentProperty.Description,
})
return nil
}
// 处理数组(注意循环引用导致的递归死循环)
if currentProperty.Type == consts.SwaggerDataTypeArray {
if nil == currentProperty.Items {
if strings.HasSuffix(parentPath, ".[]") {
// 说明已经set过,不处理
return nil
}
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: currentPropertyName,
Name: currentPropertyName,
Location: consts.RequestDataLocationBody.String(),
ParamType: consts.DataTypeSliceAny.String(),
IsRequired: requestBodyRequiredParamTable[currentPropertyName],
DefaultValue: "",
ExampleValue: "",
Description: currentProperty.Description,
})
return nil
}
// 数组类型, 如果是基础类型的数组, 直接返回, map 数组或嵌套数组层层展开
if currentProperty.Items.Type == consts.SwaggerDataTypeObject || currentProperty.Items.Type == consts.SwaggerDataTypeArray || len(currentProperty.Items.Ref) > 0 {
if !strings.HasSuffix(parentPath, ".[]") {
dataType := hod.openapiDocType2GoType(fmt.Sprintf("%v", currentProperty.Type), currentProperty.Format)
// 数组根key没设置过进行set
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: currentPropertyName,
Name: currentPropertyName,
Location: consts.RequestDataLocationBody.String(),
ParamType: "[]" + dataType,
IsRequired: requestBodyRequiredParamTable[currentPropertyName],
DefaultValue: defaultValue,
ExampleValue: "",
Description: currentProperty.Description,
})
}
if len(currentProperty.Items.Ref) == 0 {
return nil
}
// 解析ref内容
refDefine, err := hod.getResComponentsConfig(currentProperty.Items.Ref)
if nil != err {
return err
}
if strings.Contains(rootRef, currentProperty.Items.Ref) {
dataType := consts.DataTypeAny.String()
// 循环引用
if refDefine.Type == consts.SwaggerDataTypeObject {
dataType = consts.DataTypeSliceMapStringAny.String()
} else if refDefine.Type == consts.SwaggerDataTypeArray {
dataType = consts.DataTypeSliceSlice.String()
}
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: currentPropertyName,
Name: currentPropertyName,
Location: consts.RequestDataLocationBody.String(),
ParamType: dataType,
IsRequired: requestBodyRequiredParamTable[currentPropertyName],
DefaultValue: defaultValue,
ExampleValue: "",
Description: currentProperty.Description,
})
return nil
}
// 数组子项是数组或者对象
currentPropertyName = currentPropertyName + ".[]"
newRequiredTable := make(map[string]bool)
for _, item := range refDefine.Required {
newRequiredTable[item] = true
}
for itemName, itemVal := range refDefine.Properties {
if err = hod.expendObjectOrArrayRequest(importUriConfig, newRequiredTable, rootRef+currentProperty.Items.Ref, currentPropertyName, itemName, itemVal); nil != err {
return err
}
}
return nil
} else {
// 数组子项是基础类型
dataType := hod.openapiDocType2GoType(fmt.Sprintf("%v", currentProperty.Type), currentProperty.Format)
if len(currentProperty.Format) == 0 {
// 未指定format dataType 添加 [] 前缀
dataType = "[]" + dataType
}
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: currentPropertyName,
Name: currentPropertyName,
Location: consts.RequestDataLocationBody.String(),
ParamType: dataType,
IsRequired: requestBodyRequiredParamTable[currentPropertyName],
DefaultValue: defaultValue,
ExampleValue: "",
Description: currentProperty.Description,
})
return nil
}
}
if currentProperty.Type == consts.SwaggerDataTypeObject && currentProperty.Ref == "" && len(currentProperty.Properties) == 0 {
// 对象类型, 且未设置引用类型, 并且属性也为空, 对应any数据类型
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: currentPropertyName,
Name: currentPropertyName,
Location: consts.RequestDataLocationBody.String(),
ParamType: consts.DataTypeAny.String(),
IsRequired: requestBodyRequiredParamTable[currentPropertyName],
DefaultValue: defaultValue,
ExampleValue: "",
Description: currentProperty.Description,
})
return nil
}
// 遍历 Properties
for fieldName, fieldVal := range currentProperty.Properties {
importUriConfig.ParamList = append(importUriConfig.ParamList, &apiDocDefine.ApiParamItem{
Title: fieldName,
Name: fieldName,
Location: consts.RequestDataLocationBody.String(),
ParamType: hod.openapiDocType2GoType(fmt.Sprintf("%v", fieldVal.Type), fieldVal.Format),
IsRequired: requestBodyRequiredParamTable[currentPropertyName],
DefaultValue: defaultValue,
Description: fieldVal.Description,
})
// 对象或者数组, 深度递归
if fieldVal.Type == consts.SwaggerDataTypeObject || fieldVal.Type == consts.SwaggerDataTypeArray {
if err := hod.expendObjectOrArrayRequest(importUriConfig, requestBodyRequiredParamTable, rootRef, currentPropertyName, fieldName, fieldVal); nil != err {
return err
}
}
if len(fieldVal.Ref) > 0 {
// 引用的结构体定义, 递归解析
if propertyResDefine, err := hod.getResComponentsConfig(fieldVal.Ref); nil != err {
return err
} else {
newRequiredTable := make(map[string]bool)
for _, item := range propertyResDefine.Required {
newRequiredTable[item] = true
}
for _, itemProperty := range propertyResDefine.Properties {
if err = hod.expendObjectOrArrayRequest(importUriConfig, newRequiredTable, rootRef+fieldVal.Ref, currentPropertyName, fieldName, itemProperty); nil != err {
return err
}
}
}
}
}
return nil
}
// handleResponseBody 解析响应body
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:44 2025/2/27
func (hod *handleOpenapiDoc) handleResponseBody(importUriConfig *apiDocDefine.ApiConfig, requestBodyTable map[string]*apiDocDefine.Response) error {
if nil == requestBodyTable || nil == requestBodyTable["200"] {
return nil
}
if len(requestBodyTable["200"].Content) == 0 {
// 无返回值
return nil
}
contentTypeList := []string{}
for itemContentType := range requestBodyTable["200"].Content {
contentTypeList = append(contentTypeList, itemContentType)
}
if len(contentTypeList) == 0 {
contentTypeList = append(contentTypeList, consts.MimeTypeJson)
}
sort.Strings(contentTypeList)
selectResponseContentType := contentTypeList[0]
responseBodyData := requestBodyTable["200"].Content[selectResponseContentType].Schema
// 处理properties
if hod.dataField == "" {
// 未指定数据字段
for resultName, itemResult := range responseBodyData.Properties {
if err := hod.expendObjectOrArrayPath(importUriConfig, "", "", resultName, itemResult); nil != err {
return err
}
}
} else {
// 指定数据字段
fieldConfig := responseBodyData.Properties[hod.dataField]
if nil == fieldConfig {
return fmt.Errorf("data_field=%v 在相应body中不存在, uri=%v", hod.dataField, importUriConfig.Uri)
}
for fieldName, fieldVal := range fieldConfig.Properties {
// 引用的结构体定义, 递归解析
if err := hod.expendObjectOrArrayPath(importUriConfig, "", "", fieldName, fieldVal); nil != err {
return err
}
}
}
if len(responseBodyData.Ref) == 0 {
return nil
}
refCfg, err := hod.getResComponentsConfig(responseBodyData.Ref)
if nil != err {
return err
}
// ref 配置合并
for resultName, itemResult := range refCfg.Properties {
if err = hod.expendObjectOrArrayPath(importUriConfig, responseBodyData.Ref, "", resultName, itemResult); nil != err {
return err
}
}
return nil
}
// expendObjectOrArrayPath 展开ref对象路径
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:28 2025/4/8
func (hod *handleOpenapiDoc) expendObjectOrArrayPath(importUriConfig *apiDocDefine.ApiConfig, rootRef string, parentPath string, currentPropertyName string, currentProperty *apiDocDefine.Property) error {
if len(parentPath) > 0 {
currentPropertyName = parentPath + "." + currentPropertyName
}
defaultValue := wrapper.AnyDataType(currentProperty.Default).ToString().Value()
// 基础数据类型
if len(currentProperty.Properties) == 0 && currentProperty.Type != consts.SwaggerDataTypeObject && currentProperty.Type != consts.SwaggerDataTypeArray && len(currentProperty.Ref) == 0 {
importUriConfig.ResultList = append(importUriConfig.ResultList, &apiDocDefine.ApiResultItem{
Title: currentPropertyName,
DataPath: currentPropertyName,
DataLocation: consts.RequestDataLocationBody.String(),
DataType: hod.openapiDocType2GoType(fmt.Sprintf("%v", currentProperty.Type), currentProperty.Format),
DefaultValue: defaultValue,
ExampleValue: defaultValue,
Description: currentProperty.Description,
})
return nil
}
// 处理数组(注意循环引用导致的递归死循环)
if currentProperty.Type == consts.SwaggerDataTypeArray {
if nil == currentProperty.Items {
if strings.HasSuffix(parentPath, ".[]") {
// 说明已经set过,不处理
return nil
}
importUriConfig.ResultList = append(importUriConfig.ResultList, &apiDocDefine.ApiResultItem{
Title: currentPropertyName,
DataPath: currentPropertyName,
DataLocation: consts.ResponseDataLocationBody.String(),
DataType: consts.DataTypeSliceAny.String(),
DefaultValue: defaultValue,
ExampleValue: defaultValue,
Description: currentProperty.Description,
})
return nil
}
// 数组类型, 如果是基础类型的数组, 直接返回, map 数组或嵌套数组层层展开
if currentProperty.Items.Type == consts.SwaggerDataTypeObject || currentProperty.Items.Type == consts.SwaggerDataTypeArray || len(currentProperty.Items.Ref) > 0 {
if !strings.HasSuffix(parentPath, ".[]") {
// 数组根key没设置过进行set
importUriConfig.ResultList = append(importUriConfig.ResultList, &apiDocDefine.ApiResultItem{
Title: currentPropertyName,
DataPath: currentPropertyName,
DataLocation: consts.RequestDataLocationBody.String(),
DataType: consts.DataTypeSliceAny.String(),
DefaultValue: defaultValue,
ExampleValue: defaultValue,
Description: currentProperty.Description,
})
}
if len(currentProperty.Items.Ref) == 0 {
return nil
}
// 解析ref内容
refDefine, err := hod.getResComponentsConfig(currentProperty.Items.Ref)
if nil != err {
return err
}
if strings.Contains(rootRef, currentProperty.Items.Ref) {
dataType := consts.DataTypeAny.String()
// 循环引用
if refDefine.Type == consts.SwaggerDataTypeObject {
dataType = consts.DataTypeSliceMapStringAny.String()
} else if refDefine.Type == consts.SwaggerDataTypeArray {
dataType = consts.DataTypeSliceSlice.String()
}
importUriConfig.ResultList = append(importUriConfig.ResultList, &apiDocDefine.ApiResultItem{
Title: currentPropertyName,
DataPath: currentPropertyName,
DataLocation: consts.RequestDataLocationBody.String(),
DataType: dataType,
DefaultValue: defaultValue,
ExampleValue: defaultValue,
Description: currentProperty.Description,
})
return nil
}
// 数组子项是数组或者对象
currentPropertyName = currentPropertyName + ".[]"
for itemName, itemVal := range refDefine.Properties {
if err = hod.expendObjectOrArrayPath(importUriConfig, rootRef+currentProperty.Items.Ref, currentPropertyName, itemName, itemVal); nil != err {
return err
}
}
return nil
} else {
// 数组子项是基础类型
dataType := hod.openapiDocType2GoType(fmt.Sprintf("%v", currentProperty.Type), currentProperty.Format)
if len(currentProperty.Format) == 0 {
// 未指定format dataType 添加 [] 前缀
dataType = "[]" + dataType
}
importUriConfig.ResultList = append(importUriConfig.ResultList, &apiDocDefine.ApiResultItem{
Title: currentPropertyName,
DataPath: currentPropertyName,
DataLocation: consts.RequestDataLocationBody.String(),
DataType: dataType,
DefaultValue: defaultValue,
ExampleValue: defaultValue,
Description: currentProperty.Description,
})
return nil
}
}
if currentProperty.Type == consts.SwaggerDataTypeObject && currentProperty.Ref == "" && len(currentProperty.Properties) == 0 {
// 对象类型, 且未设置引用类型, 并且属性也为空, 对应any数据类型
importUriConfig.ResultList = append(importUriConfig.ResultList, &apiDocDefine.ApiResultItem{
Title: currentPropertyName,
DataPath: currentPropertyName,
DataLocation: consts.RequestDataLocationBody.String(),
DataType: consts.DataTypeAny.String(),
DefaultValue: defaultValue,
ExampleValue: defaultValue,
Description: currentProperty.Description,
})
return nil
}
// 遍历 Properties
for fieldName, fieldVal := range currentProperty.Properties {
importUriConfig.ResultList = append(importUriConfig.ResultList, &apiDocDefine.ApiResultItem{
Title: fieldName,
DataPath: fieldName,
DataLocation: consts.RequestDataLocationBody.String(),
DataType: hod.openapiDocType2GoType(fmt.Sprintf("%v", fieldVal.Type), fieldVal.Format),
Description: fieldVal.Description,
DefaultValue: defaultValue,
ExampleValue: defaultValue,
})
// 对象或者数组, 深度递归
if fieldVal.Type == consts.SwaggerDataTypeObject || fieldVal.Type == consts.SwaggerDataTypeArray {
if err := hod.expendObjectOrArrayPath(importUriConfig, rootRef, currentPropertyName, fieldName, fieldVal); nil != err {
return err
}
}
if len(fieldVal.Ref) > 0 {
// 引用的结构体定义, 递归解析
if propertyResDefine, err := hod.getResComponentsConfig(fieldVal.Ref); nil != err {
return err
} else {
for _, itemProperty := range propertyResDefine.Properties {
if err = hod.expendObjectOrArrayPath(importUriConfig, rootRef+fieldVal.Ref, currentPropertyName, fieldName, itemProperty); nil != err {
return err
}
}
}
}
}
return nil
}
// getResComponentsConfig 获取ref指向components的定义
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 11:17 2025/2/27
func (hod *handleOpenapiDoc) getResComponentsConfig(ref string) (*apiDocDefine.Schema, error) {
refKey := strings.TrimPrefix(ref, "#/components/schemas/")
if _, exist := hod.docRes.Components.Schemas[refKey]; exist {
return hod.docRes.Components.Schemas[refKey], nil
}
// apifox 导出的文档, ref 部分, 空格被转义, 但是 components 定义中空格未被转义
refKey = strings.ReplaceAll(refKey, "%20", " ")
if _, exist := hod.docRes.Components.Schemas[refKey]; exist {
return hod.docRes.Components.Schemas[refKey], nil
}
return nil, errors.New("components not found : " + refKey)
}
// openapiDocLocationFormat 文档数据位置转为网关的数据位置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:06 2025/2/26
func (hod *handleOpenapiDoc) openapiDocLocationFormat(openapiDocLocation string) string {
openapiDocLocation = strings.ToUpper(openapiDocLocation)
for _, itemLocation := range consts.RequestDataLocationList {
if strings.ToUpper(itemLocation.Value.String()) == openapiDocLocation {
return itemLocation.Value.String()
}
}
// 没有明确配置参数位置query/header/cookie/body等, 会自动按照请求尝试获取
return consts.RequestDataLocationAny.String()
}
// openapiDocType2GoType 文档数据类型转为网关数据类型
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:07 2025/2/26
func (hod *handleOpenapiDoc) openapiDocType2GoType(openapiDocType string, formatType string) string {
openapiDocType = strings.ToLower(openapiDocType)
if formatType == "" {
formatType = openapiDocType
}
formatType = strings.ReplaceAll(strings.ReplaceAll(formatType, " ", ""), "interface{}", "any")
for _, itemType := range consts.DataTypeList {
if itemType.Value.String() == formatType {
return itemType.Value.String()
}
}
switch openapiDocType {
case consts.SwaggerDataTypeString:
return consts.DataTypeString.String()
case consts.SwaggerDataTypeInteger:
return consts.DataTypeInt.String()
case consts.SwaggerDataTypeNumber:
return consts.DataTypeFloat64.String()
case consts.SwaggerDataTypeArray:
return consts.DataTypeSliceAny.String()
case consts.SwaggerDataTypeObject:
return consts.DataTypeMapStrAny.String()
case consts.SwaggerDataTypeBoolean:
return consts.DataTypeBool.String()
}
// 兜底数据类型any
return consts.DataTypeAny.String()
}

View File

@ -50,15 +50,41 @@ func GetParameterDefaultLocation(requestMethod string) string {
//
// Date : 12:23 2024/4/22
func GetSwaggerType(inputType string) string {
convertTable := map[string]string{
consts.DataTypeString.String(): "string",
consts.DataTypeInt.String(): "integer",
consts.DataTypeUint.String(): "integer",
consts.DataTypeFloat.String(): "number",
consts.DataTypeBool.String(): "boolean",
for _, itemType := range consts.DataTypeBaseInt {
if itemType.String() == inputType {
return consts.SwaggerDataTypeInteger
}
}
if _, exist := convertTable[inputType]; exist {
return convertTable[inputType]
for _, itemType := range consts.DataTypeBaseUint {
if itemType.String() == inputType {
return consts.SwaggerDataTypeInteger
}
}
for _, itemType := range consts.DataTypeBaseFloat {
if itemType.String() == inputType {
return consts.SwaggerDataTypeDouble
}
}
for _, itemType := range consts.DataTypeBaseString {
if itemType.String() == inputType {
return consts.SwaggerDataTypeString
}
}
for _, itemType := range consts.DataTypeBaseString {
if itemType.String() == inputType {
return consts.SwaggerDataTypeString
}
}
for _, itemType := range consts.DataTypeBaseBool {
if itemType.String() == inputType {
return consts.SwaggerDataTypeBoolean
}
}
if strings.HasPrefix(inputType, "[]") {
return consts.SwaggerDataTypeArray
}
if strings.HasPrefix(inputType, "map") {
return consts.SwaggerDataTypeObject
}
return inputType
}