Compare commits

...

48 Commits

Author SHA1 Message Date
e02098e643 Merge pull request 'feature/fix_bug' (#21) from feature/fix_bug into master
Reviewed-on: #21
2025-05-28 21:05:17 +08:00
de49c7e1d6 fix both modify 2025-05-28 21:04:21 +08:00
9a881e3bf2 update go mod 2025-05-28 21:01:08 +08:00
66e5b00562 Merge pull request '支持openapi文档的解析' (#20) from feature/support_parser into master
Reviewed-on: #20
2025-04-11 22:10:01 +08:00
b919c9f485 优化GOType -> swagger type转换 2025-04-11 22:00:05 +08:00
4b9d44296d 优化location处理 2025-04-11 21:39:57 +08:00
a736cff687 优化类型处理 2025-04-11 21:38:11 +08:00
0da14a1492 增加openapi文档解析 2025-04-11 21:10:18 +08:00
0b18c69e6e update go mod 2025-04-11 20:08:38 +08:00
5afe9bed09 文件路径拼接使用filepath.Join 2025-04-07 12:34:01 +08:00
0899ad9fe6 优化文档数据类型 -> Golang数据类型的转换 2025-04-07 12:12:21 +08:00
cef0969b3e update go mod 2025-04-07 12:00:51 +08:00
2363b2aa83 update consts 2025-03-24 12:08:46 +08:00
130c302ad2 Merge pull request '优化文档数据结构体' (#19) from feature/parse_openapi into master
Reviewed-on: #19
2025-02-27 20:45:35 +08:00
b5dbc330fc 优化文档数据结构图 2025-02-26 11:10:47 +08:00
22550f9ac0 增加解析openapi文档的方法, 待完善 2025-02-25 22:21:17 +08:00
71d6db967d Merge pull request '优化示例值类型' (#18) from feature/fix_example_value_type into master
Reviewed-on: #18
2025-02-20 18:51:01 +08:00
1538b52cf7 优化示例值类型 2025-02-20 18:50:16 +08:00
22d937a4b1 Merge pull request '修复设置属性可能出现的NPE问题' (#17) from feature/fix_npe into master
Reviewed-on: #17
2025-02-20 17:58:51 +08:00
2fe0d11b46 修复设置属性可能出现的NPE问题 2025-02-20 17:56:12 +08:00
6987b4829b Merge pull request '增加示例值解析 + 优化schema属性配置' (#16) from feature/support_example_value into master
Reviewed-on: #16
2025-02-20 16:22:50 +08:00
d7cbf81799 增加示例值解析 + 优化schema属性配置 2025-02-20 16:22:23 +08:00
bd7f6d2cfd 修复最大值、最小值处理翻了的BUG 2025-02-19 22:25:45 +08:00
f76641346f Merge pull request '修复类型为字符串时,最大值 最小值的配置' (#15) from feature/fix_str_length into master
Reviewed-on: #15
2025-02-19 21:46:33 +08:00
d248c1a093 修复类型为字符串时,最大值 最小值的配置 2025-02-19 21:46:05 +08:00
9f915c779d Merge pull request '修复部分场景确实min/max解析丢失的问题' (#14) from feature/fix_min_max into master
Reviewed-on: #14
2025-02-19 18:15:35 +08:00
58ba52f919 修复部分场景确实min/max解析丢失的问题 2025-02-19 18:15:10 +08:00
0e1ceaf613 Merge pull request '增加对any数据类型解析的支持' (#13) from feature/fix_type_interface into master
Reviewed-on: #13
2025-02-19 15:36:13 +08:00
998c1613e4 增加对any数据类型解析的支持 2025-02-19 15:35:47 +08:00
e2d0efe09e Merge pull request '增加枚举值描述解析' (#12) from feature/support_enum_desc into master
Reviewed-on: #12
2025-02-18 23:03:08 +08:00
f6c3e0380d 增加枚举值描述解析 2025-02-18 23:02:46 +08:00
e1191b8c05 Merge pull request '文档支持解析最小值、最大值、默认值' (#11) from feature/support_min_max into master
Reviewed-on: #11
2025-02-18 22:29:41 +08:00
32186500f5 redoc-free主题配置标题 2025-02-18 22:29:07 +08:00
57dfd12aa5 文档支持解析最小值、最大值、默认值 2025-02-18 22:21:53 +08:00
339b7aca77 解析字段描述, 支持 ### 代替 ` 2025-02-18 21:26:20 +08:00
b8a7f0585a Merge pull request '支持免费版redoc swagger文档渲染' (#10) from feature/support_free_redoc into master
Reviewed-on: #10
2025-02-18 17:07:36 +08:00
7aa4d3c417 修复get类请求匿名字段处理的BUG 2025-02-18 17:04:47 +08:00
93d399d845 整合redoc免费版swagger文档主题 2025-02-18 16:50:37 +08:00
ab8f68c799 增加redoc free版本定义 2025-02-18 16:10:40 +08:00
8cd00367e4 描述解析支持dc标签,适配gf框架风格习惯 2025-02-18 14:56:18 +08:00
db15e16f99 Merge pull request '支持匿名字段' (#9) from feature/fix_base_arr_anonymity_struct into master
Reviewed-on: #9
2025-02-18 14:07:24 +08:00
4e54b5fe33 修复匿名字段中基础数据类型指针解析错误问题 2025-02-18 12:36:41 +08:00
d407826f88 完善数据每一项数据类型解析 2025-02-18 12:23:51 +08:00
848adb5b29 修复基础数据类型的数组处理 2025-02-18 11:48:39 +08:00
f0f15a4df3 优化 summary/operationId处理 2025-02-18 10:51:24 +08:00
1ff465b953 code upgrade 2025-02-18 10:08:45 +08:00
0fc52fe51b 增加你名字端处理 2025-02-17 22:04:23 +08:00
f6810bafbd 匿名字段处理, 待完善 2025-02-17 18:56:48 +08:00
16 changed files with 1491 additions and 360 deletions

View File

@ -47,24 +47,34 @@ func GetUriPathParamList(uriPath string) []*define.ParamConfig {
// Date : 11:52 2024/12/24 // Date : 11:52 2024/12/24
func GetDataType(docParamType string, formatType string) string { func GetDataType(docParamType string, formatType string) string {
docParamType = strings.ToLower(docParamType) docParamType = strings.ToLower(docParamType)
formatType = strings.ToLower(formatType) formatType = strings.ReplaceAll(strings.ToLower(formatType), "interface{}", "any") // 内置支持的类型统一使用any, 不使用 interface{}
if len(formatType) == 0 { if len(formatType) == 0 {
formatType = docParamType formatType = docParamType
} }
// format type 和内置的支持数据类型一致, 直接返回对应类型
for _, itemType := range consts.DataTypeList {
if itemType.Value.String() == formatType {
return itemType.Value.String()
}
}
// format type 和内置的支持数据类型不一致, 根据文档类型进行转换
switch docParamType { switch docParamType {
case consts.SwaggerDataTypeInteger: case consts.SwaggerDataTypeInteger:
if formatType == "int64" { if formatType == consts.DataTypeInt.String() {
return consts.DataTypeInt.String() return consts.DataTypeInt.String()
} }
return consts.DataTypeUint.String() return consts.DataTypeInt.String()
case "string", "apikey": case "string", "apikey":
return consts.DataTypeString.String() return consts.DataTypeString.String()
case "object": case "object":
return consts.DataTypeMapStrAny.String() return consts.DataTypeMapStrAny.String()
case "boolean": case "boolean":
return consts.DataTypeBool.String() return consts.DataTypeBool.String()
case "number", "float", "double", "float32", "float64": case "number", "float", "double", "float64":
return consts.DataTypeFloat.String() return consts.DataTypeFloat64.String()
case "float32":
return consts.DataTypeFloat32.String()
case "array": case "array":
if formatType == "integer" { if formatType == "integer" {
return consts.DataTypeSliceInt.String() return consts.DataTypeSliceInt.String()

View File

@ -119,21 +119,29 @@ type PathConfigParameter struct {
// //
// Date : 12:32 2024/7/19 // Date : 12:32 2024/7/19
type Schema struct { type Schema struct {
Nullable bool `json:"nullable,omitempty"` // 对于定义的schema允许发送 null 值。默认值是 false. Nullable bool `json:"nullable,omitempty"` // 对于定义的schema允许发送 null 值。默认值是 false.
Discriminator *SchemaDiscriminator `json:"discriminator,omitempty"` // 说白了, 就是一个字段可能是不同的数据结构。。。 Discriminator *SchemaDiscriminator `json:"discriminator,omitempty"` // 说白了, 就是一个字段可能是不同的数据结构。。。
ReadOnly bool `json:"readOnly,omitempty"` // 仅与 Schema "properties" 定义有关。 声明此属性是 "readonly" 的。这意味着它可以作为 response 的一部分但不应该作为 request 的一部分被发送。如果一个 property 的 readOnly 被标记为 true 且在 required 列表中required 将只作用于 response。一个 property 的 readOnly 和 writeOnly 不允许同时被标记为 true。默认值是 false。 ReadOnly bool `json:"readOnly,omitempty"` // 仅与 Schema "properties" 定义有关。 声明此属性是 "readonly" 的。这意味着它可以作为 response 的一部分但不应该作为 request 的一部分被发送。如果一个 property 的 readOnly 被标记为 true 且在 required 列表中required 将只作用于 response。一个 property 的 readOnly 和 writeOnly 不允许同时被标记为 true。默认值是 false。
WriteOnly bool `json:"writeOnly,omitempty"` // 仅与 Schema "properties" 定义有关。声明此 property 为 "write only"。所以它可以作为 request 的一部分而不应该作为 response 的一部分被发送。如果一个 propertywriteOnly 被标记为 true 且在 required 列表中required 将只作用于 request。一个 property 的 readOnly 和 writeOnly 不能同时被标记为 true。默认值是 false。 WriteOnly bool `json:"writeOnly,omitempty"` // 仅与 Schema "properties" 定义有关。声明此 property 为 "write only"。所以它可以作为 request 的一部分而不应该作为 response 的一部分被发送。如果一个 propertywriteOnly 被标记为 true 且在 required 列表中required 将只作用于 request。一个 property 的 readOnly 和 writeOnly 不能同时被标记为 true。默认值是 false。
Xml *XML `json:"xml,omitempty"` // 这只能用于 properties schemas在root schemas 中没有效果。 Xml *XML `json:"xml,omitempty"` // 这只能用于 properties schemas在root schemas 中没有效果。
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` // 此 schema 附加的外部文档。 ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` // 此 schema 附加的外部文档。
Example string `json:"example,omitempty"` // 一个用于示范此 schema实例的示例可以是任意格式。为了表达无法用 JSON 或 YAML 格式呈现的示例,可以使用 string 类型的值,且在必要的地方需要使用字符转义。 Example any `json:"example,omitempty"` // 一个用于示范此 schema实例的示例可以是任意格式。为了表达无法用 JSON 或 YAML 格式呈现的示例,可以使用 string 类型的值,且在必要的地方需要使用字符转义。
Deprecated bool `json:"deprecated,omitempty"` // 表示一个 schema 是废弃的,应该逐渐被放弃使用。默认值是 false. Description string `json:"description,omitempty"` // 一个用于示范此 schema实例的示例可以是任意格式。为了表达无法用 JSON 或 YAML 格式呈现的示例,可以使用 string 类型的值,且在必要的地方需要使用字符转义。
Properties map[string]*Property `json:"properties,omitempty"` // 数据字段 => 数据规则 Deprecated bool `json:"deprecated,omitempty"` // 表示一个 schema 是废弃的,应该逐渐被放弃使用。默认值是 false.
Required []string `json:"required,omitempty"` // 必传属性列表 Properties map[string]*Property `json:"properties,omitempty"` // 数据字段 => 数据规则
Enum []any `json:"enum,omitempty"` // 枚举值列表 Required []string `json:"required,omitempty"` // 必传属性列表
Type string `json:"type,omitempty"` // 类型 Enum []any `json:"enum,omitempty"` // 枚举值列表
Items *PropertyXOf `json:"items,omitempty"` // items 必须存在如果 type 的值是 array。 XEnumDescription map[string]string `json:"x-enumDescriptions,omitempty"` // 枚举值描述的扩展, redoc-free支持
Ref string `json:"$ref,omitempty"` // 类型引用 Type any `json:"type,omitempty"` // 类型 (string | []string)
Format string `json:"format,omitempty"` // 格式化类型 Items *PropertyXOf `json:"items,omitempty"` // items 必须存在如果 type 的值是 array。
OneOf []*PropertyXOf `json:"oneOf,omitempty"` // type 是一个对象, allOf 指向对象描述
Ref string `json:"$ref,omitempty"` // 类型引用
Format string `json:"format,omitempty"` // 格式化类型
Maximum *int64 `json:"maximum,omitempty"` // 最大值
Minimum *int64 `json:"minimum,omitempty"` // 最小值
MinLength *int64 `json:"minLength,omitempty"` // 字符串最小长度
MaxLength *int64 `json:"maxLength,omitempty"` // 字符串最大长度
Default any `json:"default,omitempty"` // 默认值
} }
// Property 是从 JSON Schema 提取出来的,但是做了一些调整以适应 OpenAPI Specification。 // Property 是从 JSON Schema 提取出来的,但是做了一些调整以适应 OpenAPI Specification。
@ -142,16 +150,22 @@ type Schema struct {
// //
// Date : 17:05 2024/7/19 // Date : 17:05 2024/7/19
type Property struct { type Property struct {
Type string `json:"type,omitempty"` // 数据类型, swagger本身的定义 Type any `json:"type,omitempty"` // 数据类型(string | []string), swagger本身的定义
Format string `json:"format,omitempty"` // 对应编程语言中的数据类型描述 Format string `json:"format,omitempty"` // 对应编程语言中的数据类型描述
Enum []any `json:"enum,omitempty"` // 枚举值列表 Enum []any `json:"enum,omitempty"` // 枚举值列表
XEnumDescription map[string]string `json:"x-enumDescriptions,omitempty"` // 枚举值描述的扩展, redoc-free支持
Default any `json:"default,omitempty"` // 默认值 : 不同于 JSON Schema这个值必须符合定义与相同级别的 Schema 对象 中定义的类型,比如 type 是 string那么 default 可以是 "foo" 但不能是 1。 Default any `json:"default,omitempty"` // 默认值 : 不同于 JSON Schema这个值必须符合定义与相同级别的 Schema 对象 中定义的类型,比如 type 是 string那么 default 可以是 "foo" 但不能是 1。
Description string `json:"description,omitempty"` // 数据描述, CommonMark syntax可以被用来呈现富文本格式. Description string `json:"description,omitempty"` // 数据描述, CommonMark syntax可以被用来呈现富文本格式.
Example any `json:"example,omitempty"` // 媒体类型的示例。示例对象应该符合此媒体类型的格式, 这里指定的example对象 object is mutually exclusive of the examples object. 而且如果引用的schema也包含示例在这里指定的example值将会覆盖schema提供的示例。
Maximum *int64 `json:"maximum,omitempty"` // 最大值
Minimum *int64 `json:"minimum,omitempty"` // 最小值
MinLength *int64 `json:"minLength,omitempty"` // 字符串最小长度
MaxLength *int64 `json:"maxLength,omitempty"` // 字符串最大长度
AllOf []*PropertyXOf `json:"allOf,omitempty"` // type 是一个对象, allOf 指向对象描述 AllOf []*PropertyXOf `json:"allOf,omitempty"` // type 是一个对象, allOf 指向对象描述
OneOf []*PropertyXOf `json:"oneOf,omitempty"` // type 是一个对象, allOf 指向对象描述 OneOf []*PropertyXOf `json:"oneOf,omitempty"` // type 是一个对象, allOf 指向对象描述
AnyOf []*PropertyXOf `json:"anyOf,omitempty"` // type 是一个对象, allOf 指向对象描述 AnyOf []*PropertyXOf `json:"anyOf,omitempty"` // type 是一个对象, allOf 指向对象描述
Items *PropertyXOf `json:"items,omitempty"` // items 必须存在如果 type 的值是 array。 Items *PropertyXOf `json:"items,omitempty"` // items 必须存在如果 type 的值是 array。
AdditionalProperties *PropertyXOf `json:"additionalProperties,omitempty"` // additionalProperties 是一个用于描述模型中包含未在属性列表中定义的额外属性的选项。它允许接受任意的一个或多个键值对。它的作用是为了在模型定义中包含未知或动态属性。通常,在设计 API 时,我们无法预先知道 API 用户会传递什么样的额外属性,这时就可以使用 additionalProperties 功能来灵活地处理这些未知属性。 AdditionalProperties any `json:"additionalProperties,omitempty"` // additionalProperties(PropertyXOf | bool) 是一个用于描述模型中包含未在属性列表中定义的额外属性的选项。它允许接受任意的一个或多个键值对。它的作用是为了在模型定义中包含未知或动态属性。通常,在设计 API 时,我们无法预先知道 API 用户会传递什么样的额外属性,这时就可以使用 additionalProperties 功能来灵活地处理这些未知属性。
Properties map[string]*Property `json:"properties,omitempty"` // type = object 时, 定义对象属性 Properties map[string]*Property `json:"properties,omitempty"` // type = object 时, 定义对象属性
Ref string `json:"$ref,omitempty"` // 对描述的引用 Ref string `json:"$ref,omitempty"` // 对描述的引用
} }
@ -162,7 +176,12 @@ type Property struct {
// //
// Date : 17:13 2024/7/19 // Date : 17:13 2024/7/19
type PropertyXOf struct { type PropertyXOf struct {
Ref string `json:"$ref"` // 引用的结构描述 Type string `json:"type,omitempty"` // 基础类型
Format string `json:"format,omitempty"` // 真实类型
Maximum *int64 `json:"maximum,omitempty"` // 最大值
Minimum *int64 `json:"minimum,omitempty"` // 最小值
Ref string `json:"$ref,omitempty"` // 引用的结构描述
// Items *PropertyXOf `json:"items,omitempty"` // 数据每一项已用类型
} }
// SchemaDiscriminator 当一个 request bodies 或 response payloads 可以是多种 schemas 时,可以使用一个 discriminator 对象来帮助序列化、反序列化和校验 // SchemaDiscriminator 当一个 request bodies 或 response payloads 可以是多种 schemas 时,可以使用一个 discriminator 对象来帮助序列化、反序列化和校验
@ -208,7 +227,7 @@ type RequestBody struct {
// Date : 17:21 2024/7/19 // Date : 17:21 2024/7/19
type Media struct { type Media struct {
Schema *Schema `json:"schema,omitempty"` // 定义此媒体类型的结构。 Schema *Schema `json:"schema,omitempty"` // 定义此媒体类型的结构。
Example string `json:"example,omitempty"` // 媒体类型的示例。示例对象应该符合此媒体类型的格式, 这里指定的example对象 object is mutually exclusive of the examples object. 而且如果引用的schema也包含示例在这里指定的example值将会覆盖schema提供的示例。 Example map[string]any `json:"example,omitempty"` // 媒体类型的示例。示例对象应该符合此媒体类型的格式, 这里指定的example对象 object is mutually exclusive of the examples object. 而且如果引用的schema也包含示例在这里指定的example值将会覆盖schema提供的示例。
Examples map[string]*Example `json:"examples,omitempty"` // 媒体类型的示例,每个媒体对象的值都应该匹配它对应的媒体类型的格式。 The examples object is mutually exclusive of the example object. 而且如果引用的schema也包含示例在这里指定的example值将会覆盖schema提供的示例。 Examples map[string]*Example `json:"examples,omitempty"` // 媒体类型的示例,每个媒体对象的值都应该匹配它对应的媒体类型的格式。 The examples object is mutually exclusive of the example object. 而且如果引用的schema也包含示例在这里指定的example值将会覆盖schema提供的示例。
Encoding map[string]*Encoding `json:"encoding,omitempty"` // 属性名与编码信息的映射。每个属性名必须存在于schema属性的key中当媒体类型等于multipart或application/x-www-form-urlencoded时编码对象信息仅适用于requestBody。 Encoding map[string]*Encoding `json:"encoding,omitempty"` // 属性名与编码信息的映射。每个属性名必须存在于schema属性的key中当媒体类型等于multipart或application/x-www-form-urlencoded时编码对象信息仅适用于requestBody。
} }

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

@ -11,4 +11,5 @@ const (
SwaggerUIThemeDefault = "swaggerUI" // 文档默认主题 SwaggerUIThemeDefault = "swaggerUI" // 文档默认主题
SwaggerUIThemeKnife4go = "knife4go" // knife4go 主题 SwaggerUIThemeKnife4go = "knife4go" // knife4go 主题
SwaggerUIThemeYDocLucky = "ydoc-lucky-ui" // YDoc Lucky UI 主题: https://github.com/NoBugBoy/LuckyUI SwaggerUIThemeYDocLucky = "ydoc-lucky-ui" // YDoc Lucky UI 主题: https://github.com/NoBugBoy/LuckyUI
SwaggerUIThemeRedocFree = "redoc-free" // redoc UI 主题, 开源免费版: https://github.com/Redocly/redoc
) )

View File

@ -8,27 +8,31 @@
package define package define
const ( const (
TagJson = "json" TagJson = "json"
TagXml = "xml" TagXml = "xml"
TagYaml = "yaml" TagYaml = "yaml"
TagYml = "yml" TagYml = "yml"
TagForm = "form" TagForm = "form"
TagBinding = "binding" TagBinding = "binding"
TagValidate = "validate" TagValidate = "validate"
TagErr = "err" TagErr = "err"
TagMsg = "msg" TagMsg = "msg"
TagDesc = "desc" TagDc = "dc"
TagDescription = "description" TagDesc = "desc"
TagD = "d" TagDescription = "description"
TagDefault = "default" TagD = "d"
TagDeprecated = "deprecated" TagDefault = "default"
TagSummary = "summary" TagDeprecated = "deprecated"
TagPath = "path" // 接口的请求路径 TagSummary = "summary"
TagMethod = "method" // 接口的请求方法 TagPath = "path" // 接口的请求路径
TagUriTag = "tag" // 接口的tag TagMethod = "method" // 接口的请求方法
TagOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map非严格模式返回任意值 TagUriTag = "tag" // 接口的tag
TagErrMsg = "err" // 验证失败错误信息tag TagOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map非严格模式返回任意值
TagContentType = "content_type" TagErrMsg = "err" // 验证失败错误信息tag
TagOutputContentType = "output_content_type" TagExample = "example" // 示例值
TagNameOmitempty = "omitempty" TagEg = "eg" // 示例值
TagContentType = "content_type"
TagOutputContentType = "output_content_type"
TagNameOmitempty = "omitempty"
TagNameEnumDescription = "enum-desc" // 枚举值描述: enum1:enum1-desc||enum2:enum2-desc
) )

View File

@ -207,7 +207,7 @@ func (g *Generate) AddApiFromInAndOut(uriPrefix string, paramType reflect.Type,
Schema: &define.Schema{ Schema: &define.Schema{
Ref: g.getSchemaRef(paramSchemaName), Ref: g.getSchemaRef(paramSchemaName),
}, },
Example: "", Example: nil,
Examples: nil, Examples: nil,
Encoding: nil, Encoding: nil,
} }
@ -220,7 +220,7 @@ func (g *Generate) AddApiFromInAndOut(uriPrefix string, paramType reflect.Type,
Schema: &define.Schema{ Schema: &define.Schema{
Ref: g.getSchemaRef(resultSchemaName), Ref: g.getSchemaRef(resultSchemaName),
}, },
Example: "", Example: nil,
Examples: nil, Examples: nil,
Encoding: nil, Encoding: nil,
} }
@ -270,7 +270,7 @@ func (g *Generate) getApiDocBaseCfg(baseCfg *define.UriBaseConfig, paramType ref
Summary: baseCfg.Summary, Summary: baseCfg.Summary,
Description: baseCfg.Description, Description: baseCfg.Description,
ExternalDocs: nil, ExternalDocs: nil,
OperationID: baseCfg.Summary + "(" + strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "-") + ")", OperationID: baseCfg.Summary + "(" + baseCfg.Method + "-" + strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "-") + ")",
Parameters: make([]*define.PathConfigParameter, 0), Parameters: make([]*define.PathConfigParameter, 0),
RequestBody: &define.RequestBody{ RequestBody: &define.RequestBody{
Required: true, Required: true,
@ -326,7 +326,12 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe
baseReqCfg.Parameters = make([]*define.PathConfigParameter, 0) baseReqCfg.Parameters = make([]*define.PathConfigParameter, 0)
} }
for i := 0; i < inputType.NumField(); i++ { for i := 0; i < inputType.NumField(); i++ {
propertyName := ParseStructField.GetParamName(inputType.Field(i)) if inputType.Field(i).Anonymous {
// 匿名字段, 直接对齐到当前的父级
g.ParseReadConfigParam(requestCfg, baseReqCfg, inputType.Field(i).Type)
continue
}
propertyName := ParseStructFieldTag.GetParamName(inputType.Field(i))
if propertyName == "-" { if propertyName == "-" {
continue continue
} }
@ -334,11 +339,46 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe
// 空Meta字段认为是用来描述元信息的, 忽略 // 空Meta字段认为是用来描述元信息的, 忽略
continue continue
} }
convertBaseType, isBaseType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String())
realInputTypeFormat := inputType.Field(i).Type.String() realInputTypeFormat := inputType.Field(i).Type.String()
fieldType := inputType.Field(i).Type fieldType := inputType.Field(i).Type
/*if inputType.Field(i).Type.Kind() == reflect.Ptr { if isBaseType {
fieldType = inputType.Field(i).Type.Elem() // 当做默认基础类型, 默认不会出现 *map *[]
}*/ itemParam := &define.PathConfigParameter{
Name: propertyName,
In: consts.SwaggerParameterInQuery,
Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)),
Schema: &define.Schema{
Type: convertBaseType,
Format: realInputTypeFormat,
},
AllowEmptyValue: false,
Style: "",
Explode: false,
AllowReserved: false,
}
g.setStructFieldProperty(itemParam.Schema, inputType.Field(i))
baseReqCfg.Parameters = append(baseReqCfg.Parameters, itemParam)
continue
}
if inputType.Field(i).Type.Kind() == reflect.Interface {
itemParam := &define.PathConfigParameter{
Name: ParseStructFieldTag.GetParamName(inputType.Field(i)),
In: consts.SwaggerParameterInQuery,
Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)),
Schema: &define.Schema{
OneOf: g.anyTypeConfig(inputType.Field(i)).OneOf,
Format: realInputTypeFormat,
},
}
g.setStructFieldProperty(itemParam.Schema, inputType.Field(i))
baseReqCfg.Parameters = append(baseReqCfg.Parameters, itemParam)
continue
}
if inputType.Field(i).Type.Kind() == reflect.Ptr { if inputType.Field(i).Type.Kind() == reflect.Ptr {
// 处理指针 // 处理指针
if inputType.Field(i).Type.Elem().Kind() == reflect.Struct { if inputType.Field(i).Type.Elem().Kind() == reflect.Struct {
@ -347,9 +387,9 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe
baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{
Name: propertyName, Name: propertyName,
In: consts.SwaggerParameterInQuery, In: consts.SwaggerParameterInQuery,
Description: ParseStructField.GetParamDesc(inputType.Field(i)), Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)), Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructField.Deprecated(inputType.Field(i)), Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)),
Schema: &define.Schema{ Schema: &define.Schema{
// Format: realInputTypeFormat, // Format: realInputTypeFormat,
Ref: g.getSchemaRef(schemaNameNext), Ref: g.getSchemaRef(schemaNameNext),
@ -359,22 +399,7 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe
AllowReserved: false, AllowReserved: false,
}) })
} else { } else {
// 当做默认基础类型, 默认不会出现 *map *[]
baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{
Name: propertyName,
In: consts.SwaggerParameterInQuery,
Description: ParseStructField.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructField.Deprecated(inputType.Field(i)),
Schema: &define.Schema{
Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()),
Format: realInputTypeFormat,
},
AllowEmptyValue: false,
Style: "",
Explode: false,
AllowReserved: false,
})
} }
continue continue
} }
@ -382,40 +407,20 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe
fieldType.Kind() == reflect.Map || fieldType.Kind() == reflect.Map ||
fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Array ||
fieldType.Kind() == reflect.Slice { fieldType.Kind() == reflect.Slice {
if convertType := g.realBaseType2SwaggerType(fieldType.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() {
// 针对基础类型指针
baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{
Name: ParseStructField.GetParamName(inputType.Field(i)),
In: consts.SwaggerParameterInQuery,
Description: ParseStructField.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructField.Deprecated(inputType.Field(i)),
Schema: &define.Schema{
Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()),
Items: nil,
Ref: "",
Format: realInputTypeFormat,
},
AllowEmptyValue: false,
Style: "",
Explode: false,
AllowReserved: false,
Ref: "",
})
continue
}
} else {
baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{
Name: ParseStructField.GetParamName(inputType.Field(i)), Name: ParseStructFieldTag.GetParamName(inputType.Field(i)),
In: consts.SwaggerParameterInQuery, In: consts.SwaggerParameterInQuery,
Description: ParseStructField.GetParamDesc(inputType.Field(i)), Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)), Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructField.Deprecated(inputType.Field(i)), Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)),
Schema: &define.Schema{ Schema: &define.Schema{
Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), Type: convertBaseType,
Items: nil, Items: nil,
Ref: "", Ref: "",
Format: realInputTypeFormat, Format: realInputTypeFormat,
Enum: ValidateRule.Enum(inputType.Field(i)),
XEnumDescription: ParseStructFieldTag.EnumDescription(inputType.Field(i)),
Example: ParseStructFieldTag.GetExampleValue(inputType.Field(i)),
}, },
AllowEmptyValue: false, AllowEmptyValue: false,
Style: "", Style: "",
@ -482,45 +487,66 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in
} }
// 数组 // 数组
if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array {
sliceItemType, itemIsBaseType := g.parseSliceItem(schemaName, inputType)
propertyXOf := &define.PropertyXOf{}
if itemIsBaseType {
propertyXOf.Type, _ = g.realBaseType2SwaggerType(sliceItemType)
propertyXOf.Format = sliceItemType
propertyXOf.Ref = ""
} else {
propertyXOf.Type = ""
propertyXOf.Format = ""
propertyXOf.Ref = g.getSchemaRef(sliceItemType)
}
if len(rootSchemaName) == 0 { if len(rootSchemaName) == 0 {
g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray
sliceItemType := g.parseSliceItem(schemaName, inputType) g.docData.Components.Schemas[schemaName].Items = propertyXOf
g.docData.Components.Schemas[schemaName].Items = &define.PropertyXOf{Ref: g.getSchemaRef(sliceItemType)}
} else { } else {
sliceItemType := g.parseSliceItem(schemaName, inputType)
g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{
Type: consts.SwaggerDataTypeArray, Type: consts.SwaggerDataTypeArray,
Format: inputType.String(), Format: inputType.String(),
Items: &define.PropertyXOf{Ref: g.getSchemaRef(sliceItemType)}, Items: propertyXOf,
} }
} }
return schemaName return schemaName
} }
// 结构体 // 结构体
if inputType.Kind() == reflect.Struct { if inputType.Kind() == reflect.Struct {
for i := 0; i < inputType.NumField(); i++ { for i := 0; i < inputType.NumField(); i++ {
propertyName := ParseStructField.GetParamName(inputType.Field(i)) propertyName := ParseStructFieldTag.GetParamName(inputType.Field(i))
if propertyName == "-" { if propertyName == "-" {
continue continue
} }
if inputType.Field(i).Anonymous {
// 处理匿名字段
g.handleAnonymousField(schemaName, inputType.Field(i))
continue
}
if inputType.Kind() == reflect.Interface {
// 处理interface{}类型参数
g.docData.Components.Schemas[schemaName].Properties[propertyName] = g.anyTypeConfig(inputType.Field(i))
continue
}
if inputType.Field(i).Type.Kind() == reflect.Ptr { if inputType.Field(i).Type.Kind() == reflect.Ptr {
// 处理指针 // 处理指针
if inputType.Field(i).Type.Elem().Kind() == reflect.Struct { if inputType.Field(i).Type.Elem().Kind() == reflect.Struct {
// 结构体指针 // 结构体指针
schemaNameNext := g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type.Elem()) schemaNameNext := g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type.Elem())
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
// Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)),
Ref: g.getSchemaRef(schemaNameNext), Ref: g.getSchemaRef(schemaNameNext),
} }
} else { } else {
// 当做默认基础类型, 默认不会出现 *map *[] // 当做默认基础类型, 默认不会出现 *map *[]
convertBaseType, _ := g.realBaseType2SwaggerType(inputType.Field(i).Type.String())
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
Type: g.realBaseType2SwaggerType(g.realBaseType2SwaggerType(inputType.Field(i).Type.String())), Type: convertBaseType,
Format: inputType.Field(i).Type.String(), Format: inputType.Field(i).Type.String(),
Default: ParseStructField.GetDefaultValue(inputType.Field(i)),
Description: ParseStructField.GetParamDesc(inputType.Field(i)),
} }
} }
// 设置参数各种属性
g.setStructFieldProperty(g.docData.Components.Schemas[schemaName], inputType.Field(i))
continue continue
} }
if inputType.Field(i).Type.Kind() == reflect.Struct || if inputType.Field(i).Type.Kind() == reflect.Struct ||
@ -530,36 +556,45 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in
if inputType.Field(i).Type.Kind() == reflect.Struct || if inputType.Field(i).Type.Kind() == reflect.Struct ||
inputType.Field(i).Type.Kind() == reflect.Map { inputType.Field(i).Type.Kind() == reflect.Map {
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
Type: consts.SwaggerDataTypeObject, Type: consts.SwaggerDataTypeObject,
Format: inputType.Field(i).Type.String(), Format: inputType.Field(i).Type.String(),
Description: ParseStructField.GetParamDesc(inputType.Field(i)), Properties: map[string]*define.Property{},
Properties: map[string]*define.Property{},
} }
} else if inputType.Field(i).Type.Kind() == reflect.Array || } else if inputType.Field(i).Type.Kind() == reflect.Array ||
inputType.Field(i).Type.Kind() == reflect.Slice { inputType.Field(i).Type.Kind() == reflect.Slice {
sliceItemType, itemIsBaseType := g.parseSliceItem(schemaName, inputType.Field(i).Type)
propertyXOf := &define.PropertyXOf{}
if itemIsBaseType {
propertyXOf.Type, _ = g.realBaseType2SwaggerType(sliceItemType)
propertyXOf.Format = sliceItemType
propertyXOf.Ref = ""
} else {
propertyXOf.Type = ""
propertyXOf.Format = ""
propertyXOf.Ref = g.getSchemaRef(sliceItemType)
}
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
Type: consts.SwaggerDataTypeArray, Type: consts.SwaggerDataTypeArray,
Format: inputType.Field(i).Type.String(), Format: inputType.Field(i).Type.String(),
Description: ParseStructField.GetParamDesc(inputType.Field(i)), Items: propertyXOf,
Items: &define.PropertyXOf{
Ref: g.getSchemaRef(g.parseSliceItem(schemaName, inputType.Field(i).Type)),
},
Properties: map[string]*define.Property{},
} }
} else { } else {
g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type) g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type)
} }
} else { } else {
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ if inputType.Field(i).Type.Kind() == reflect.Interface {
Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), g.docData.Components.Schemas[schemaName].Properties[propertyName] = g.anyTypeConfig(inputType.Field(i))
Format: inputType.Field(i).Type.String(), g.docData.Components.Schemas[schemaName].Properties[propertyName].Example = ParseStructFieldTag.GetExampleValue(inputType.Field(i))
Default: ParseStructField.GetDefaultValue(inputType.Field(i)), } else {
Description: ParseStructField.GetParamDesc(inputType.Field(i)), convertBaseType, _ := g.realBaseType2SwaggerType(inputType.Field(i).Type.String())
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
Type: convertBaseType,
Format: inputType.Field(i).Type.String(),
}
} }
} }
// 设置参数各种属性 // 设置参数各种属性
g.setStructFieldProperty(schemaName, inputType.Field(i)) g.setStructFieldProperty(g.docData.Components.Schemas[schemaName], inputType.Field(i))
} }
return schemaName return schemaName
} }
@ -569,7 +604,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in
// 非基础数据类型 // 非基础数据类型
return g.AddComponentsSchema(schemaName, inputType.Elem().String(), inputType.Elem()) return g.AddComponentsSchema(schemaName, inputType.Elem().String(), inputType.Elem())
} else { } else {
convertType := g.realBaseType2SwaggerType(inputType.String()) convertType, _ := g.realBaseType2SwaggerType(inputType.String())
g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{
Type: convertType, Type: convertType,
Format: inputType.String(), Format: inputType.String(),
@ -580,26 +615,79 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in
return schemaName return schemaName
} }
// handleAnonymousField 处理匿名字段
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 18:43 2025/2/17
func (g *Generate) handleAnonymousField(schemaName string, field reflect.StructField) {
if !field.Anonymous {
// 不是匿名字段
return
}
handleType := field.Type
if handleType.Kind() == reflect.Ptr {
handleType = handleType.Elem()
}
for i := 0; i < handleType.NumField(); i++ {
itemField := handleType.Field(i)
if itemField.Anonymous {
// 递归处理多层嵌套匿名字段
g.handleAnonymousField(schemaName, itemField)
continue
} else {
baseConvertType, isBaseType := g.realBaseType2SwaggerType(itemField.Type.String())
if !isBaseType {
paramName := ParseStructFieldTag.GetParamName(itemField)
if itemField.Type.Kind() == reflect.Interface {
g.docData.Components.Schemas[schemaName].Properties[paramName] = g.anyTypeConfig(itemField)
}
g.AddComponentsSchema(schemaName, handleType.Field(i).Type.PkgPath(), handleType.Field(i).Type)
continue
} else {
paramName := ParseStructFieldTag.GetParamName(itemField)
g.docData.Components.Schemas[schemaName].Properties[paramName] = &define.Property{
Type: baseConvertType,
Format: itemField.Type.String(),
Example: ParseStructFieldTag.GetExampleValue(itemField),
Enum: ValidateRule.Enum(itemField),
XEnumDescription: ParseStructFieldTag.EnumDescription(itemField),
Default: ParseStructFieldTag.GetDefaultValue(handleType.Field(i)),
Description: ParseStructFieldTag.GetParamDesc(handleType.Field(i)),
}
if ValidateRule.IsRequired(itemField) {
g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, paramName)
}
}
}
}
}
// parseSliceItem 解析数组每一项 // parseSliceItem 解析数组每一项
// //
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 21:33 2025/2/8 // Date : 21:33 2025/2/8
func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) string { func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) (string, bool) {
if inputType.Kind() != reflect.Slice && inputType.Kind() != reflect.Array { if inputType.Kind() != reflect.Slice && inputType.Kind() != reflect.Array {
// 不是数组 // 不是数组
return "" return "", false
} }
sliceValue := reflect.MakeSlice(inputType, 1, 1) sliceValue := reflect.MakeSlice(inputType, 1, 1)
sliceItemType := sliceValue.Index(0).Type() sliceItemType := sliceValue.Index(0).Type()
realSliceItemType := sliceItemType.String()
if sliceItemType.Kind() == reflect.Ptr { if sliceItemType.Kind() == reflect.Ptr {
sliceItemType = sliceItemType.Elem() sliceItemType = sliceItemType.Elem()
} }
_, isBaseType := g.realBaseType2SwaggerType(sliceItemType.String())
if isBaseType {
return realSliceItemType, true
}
g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType) g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType)
if len(sliceItemType.PkgPath()) == 0 { if len(sliceItemType.PkgPath()) == 0 {
return sliceItemType.String() return realSliceItemType, false
} }
return sliceItemType.PkgPath() + "." + sliceItemType.Name() return sliceItemType.PkgPath() + "." + sliceItemType.Name(), false
} }
// getSchemaRef 获取引用的类型 // getSchemaRef 获取引用的类型
@ -612,7 +700,11 @@ func (g *Generate) getSchemaRef(schemaName string) string {
return "" return ""
} }
schemaName = strings.ReplaceAll(schemaName, "*", "") // 去除指针类型 * schemaName = strings.ReplaceAll(schemaName, "*", "") // 去除指针类型 *
return "#/components/schemas/" + strings.ReplaceAll(schemaName, "/", ".") convertType, isBaseType := g.realBaseType2SwaggerType(schemaName)
if isBaseType {
return convertType
}
return "#/components/schemas/" + strings.ReplaceAll(convertType, "/", ".")
} }
// realType2SwaggerType golang 真实数据类型转换为golang数据类型 // realType2SwaggerType golang 真实数据类型转换为golang数据类型
@ -620,48 +712,75 @@ func (g *Generate) getSchemaRef(schemaName string) string {
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 20:25 2025/2/11 // Date : 20:25 2025/2/11
func (g *Generate) realBaseType2SwaggerType(realType string) string { func (g *Generate) realBaseType2SwaggerType(realType string) (string, bool) {
switch realType { switch realType {
case "bool", "*bool": case "bool", "*bool":
return consts.SwaggerDataTypeBoolean return consts.SwaggerDataTypeBoolean, true
case "string", "*string": case "string", "*string":
return consts.SwaggerDataTypeString return consts.SwaggerDataTypeString, true
case "byte", "*byte": case "byte", "*byte":
return consts.SwaggerDataTypeByte return consts.SwaggerDataTypeByte, true
case "float32", "*float32", "float64", "*float64": case "float32", "*float32", "float64", "*float64":
return consts.SwaggerDataTypeDouble return consts.SwaggerDataTypeDouble, true
case "int", "*int", "uint", "*uint", "int64", "*int64", "uint64", "*uint64": case "int", "*int", "uint", "*uint", "int64", "*int64", "uint64", "*uint64":
return consts.SwaggerDataTypeInteger return consts.SwaggerDataTypeInteger, true
case "int8", "*int8", "uint8", "*uint8", "int16", "*int16", "uint16", "*uint16", "int32", "*int32", "uint32", "*uint32": case "int8", "*int8", "uint8", "*uint8", "int16", "*int16", "uint16", "*uint16", "int32", "*int32", "uint32", "*uint32":
return consts.SwaggerDataTypeInteger return consts.SwaggerDataTypeInteger, true
default: default:
return realType if strings.HasPrefix(realType, "[]") {
return consts.SwaggerDataTypeArray, true
}
return realType, false
} }
} }
// realType2SwaggerType ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:20 2025/2/11
func (g *Generate) realType2SwaggerType(realType string) string {
if strings.HasPrefix(realType, "[]") {
return consts.SwaggerDataTypeArray
}
return g.realBaseType2SwaggerType(realType)
}
// setStructFieldProperty 添加struct_field各种属性 // setStructFieldProperty 添加struct_field各种属性
// //
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 16:13 2025/2/13 // Date : 16:13 2025/2/13
func (g *Generate) setStructFieldProperty(schemaName string, structField reflect.StructField) { func (g *Generate) setStructFieldProperty(schema *define.Schema, structField reflect.StructField) {
paramName := ParseStructField.GetParamName(structField) paramName := ParseStructFieldTag.GetParamName(structField)
if ValidateRule.IsRequired(structField) { if paramName == "" || paramName == "-" {
g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, paramName) return
}
isRequired := ValidateRule.IsRequired(structField)
enum := ValidateRule.Enum(structField)
xEnumDescription := ParseStructFieldTag.EnumDescription(structField)
example := ParseStructFieldTag.GetExampleValue(structField)
description := ParseStructFieldTag.GetParamDesc(structField)
maxVal := ValidateRule.Maximum(structField)
minVal := ValidateRule.Minimum(structField)
if nil != schema {
if isRequired {
schema.Required = append(schema.Required, paramName)
}
if nil == schema.Properties[paramName] {
if schema.Type == consts.SwaggerDataTypeString {
schema.MinLength = minVal
schema.MaxLength = maxVal
} else {
schema.Minimum = minVal
schema.Maximum = maxVal
}
schema.Enum = enum
schema.XEnumDescription = xEnumDescription
schema.Example = example
schema.Description = description
return
}
if schema.Properties[paramName].Type == consts.SwaggerDataTypeString {
schema.Properties[paramName].MinLength = minVal
schema.Properties[paramName].MaxLength = maxVal
} else {
schema.Properties[paramName].Minimum = minVal
schema.Properties[paramName].Maximum = maxVal
}
schema.Properties[paramName].Enum = enum
schema.Properties[paramName].XEnumDescription = xEnumDescription
schema.Properties[paramName].Example = example
schema.Properties[paramName].Description = description
} }
g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(structField)].Enum = ValidateRule.Enum(structField)
} }
// parseBaseUriConfig 通过Meta字段解析Uri基础配置信息 // parseBaseUriConfig 通过Meta字段解析Uri基础配置信息
@ -718,7 +837,10 @@ func (g *Generate) parseBaseUriConfig(uriPrefix string, paramType reflect.Type)
responseContentType = consts.MimeTypeJson responseContentType = consts.MimeTypeJson
} }
res.OutputContentType = strings.Split(responseContentType, ",") res.OutputContentType = strings.Split(responseContentType, ",")
res.Summary = ParseStructField.Summary(metaField) res.Summary = ParseStructFieldTag.Summary(metaField)
if len(res.Summary) == 0 {
res.Summary = wrapper.String(strings.ReplaceAll(strings.TrimLeft(res.Uri, "/"), "/", "_")).SnakeCaseToCamel()
}
if res.Method == "" { if res.Method == "" {
return nil, errors.New("baseCfg.Method is empty") return nil, errors.New("baseCfg.Method is empty")
} }
@ -746,3 +868,44 @@ func (g *Generate) parseBaseUriConfig(uriPrefix string, paramType reflect.Type)
} }
return res, nil return res, nil
} }
// anyTypeConfig 任意类型数据配置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:33 2025/2/19
func (g *Generate) anyTypeConfig(structField reflect.StructField) *define.Property {
return &define.Property{
Description: ParseStructFieldTag.GetParamDesc(structField),
OneOf: []*define.PropertyXOf{
{
Type: consts.SwaggerDataTypeObject,
Format: "map[string]any",
},
/* {
Type: consts.SwaggerDataTypeArray,
Format: "[]any",
Items: &define.PropertyXOf{
Items: nil,
},
},*/
{
Type: consts.SwaggerDataTypeInteger,
Format: "int/uint",
},
{
Type: consts.SwaggerDataTypeNumber,
Format: "int/uint/float",
},
{
Type: consts.SwaggerDataTypeString,
Format: "string",
},
{
Type: consts.SwaggerDataTypeBoolean,
Format: "bool",
},
},
}
}

67
go.mod
View File

@ -1,63 +1,62 @@
module git.zhangdeman.cn/gateway/api-doc module git.zhangdeman.cn/gateway/api-doc
go 1.22.2 go 1.23.0
toolchain go1.24.1
require ( require (
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1 git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250124091620-c757e551a8c9 git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.1
github.com/go-webtools/knife4go v1.0.4 github.com/go-webtools/knife4go v1.0.4
github.com/swaggo/files v1.0.1 github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/gin-swagger v1.6.0
github.com/tidwall/gjson v1.18.0
) )
require ( require (
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e // indirect git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e // indirect
github.com/BurntSushi/toml v1.4.0 // indirect github.com/BurntSushi/toml v1.5.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/sbabiv/xml2map v1.2.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/swaggo/swag v1.8.12 // indirect github.com/swaggo/swag v1.16.4 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.14 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.17.0 // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.7.0 // indirect golang.org/x/tools v0.33.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

177
go.sum
View File

@ -1,125 +1,122 @@
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1 h1:vv4X72I6s6XcTi0ykj2v/cgMZyseFyE2LkS4WloICCs= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995 h1:LmPRAf0AsxRVFPibdpZR89ajlsz8hof2IvMMyTqiEq4=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y=
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0/go.mod h1:VHb9qmhaPDAQDcS6vUiDCamYjZ4R5lD1XtVsh55KsMI= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0/go.mod h1:VHb9qmhaPDAQDcS6vUiDCamYjZ4R5lD1XtVsh55KsMI=
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd h1:q7GG14qgXKB4MEXQFOe7/UYebsqMfPaSX80TcPdOosI= git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9 h1:/GLQaFoLb+ciHOtAS2BIyPNnf4O5ME3AC5PUaJY9kfs=
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd/go.mod h1:+D6uPSljwHywjVY5WSBY4TRVMj26TN5f5cFGEYMldjs= git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9/go.mod h1:ABJ655C5QenQNOzf7LjCe4sSB52CXvaWLX2Zg4uwDJY=
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e h1:Q973S6CcWr1ICZhFI1STFOJ+KUImCl2BaIXm6YppBqI= git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e h1:Q973S6CcWr1ICZhFI1STFOJ+KUImCl2BaIXm6YppBqI=
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e/go.mod h1:VpPjBlwz8U+OxZuxzHQBv1aEEZ3pStH6bZvT21ADEbI= git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e/go.mod h1:VpPjBlwz8U+OxZuxzHQBv1aEEZ3pStH6bZvT21ADEbI=
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250124091620-c757e551a8c9 h1:yF770WIDNwyiKL0nwmBGmjZvNCLXtHQL4xJyffPjTMU= git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740 h1:zPUoylfJTbc0EcxW+NEzOTBmoeFZ2I/rLFBnEzxb4Wk=
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250124091620-c757e551a8c9/go.mod h1:I76wxEsWq7KnMQ84elpwTjEqq4I49QFw60tp5h7iGBs= git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740/go.mod h1:1ct92dbVc49pmXusA/iGfcQUJzcYmJ+cjAhgc3sDv1I=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-webtools/knife4go v1.0.4 h1:p32SApmM0sx2/Y5p0QfeaGv5KD96R1mj2CaHdyH8jy8= github.com/go-webtools/knife4go v1.0.4 h1:p32SApmM0sx2/Y5p0QfeaGv5KD96R1mj2CaHdyH8jy8=
github.com/go-webtools/knife4go v1.0.4/go.mod h1:trOlXN1tqBJ7R44sHON3exGvzCwjbsVriIHEenry3d8= github.com/go-webtools/knife4go v1.0.4/go.mod h1:trOlXN1tqBJ7R44sHON3exGvzCwjbsVriIHEenry3d8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/sbabiv/xml2map v1.2.1 h1:1lT7t0hhUvXZCkdxqtq4n8/ZCnwLWGq4rDuDv5XOoFE=
github.com/sbabiv/xml2map v1.2.1/go.mod h1:2TPoAfcaM7+Sd4iriPvzyntb2mx7GY+kkQpB/GQa/eo=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -129,68 +126,58 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -8,34 +8,35 @@
package api_doc package api_doc
import ( import (
"errors"
"git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/gateway/api-doc/define"
"git.zhangdeman.cn/zhangdeman/serialize" "git.zhangdeman.cn/zhangdeman/serialize"
"github.com/tidwall/gjson"
"strings"
) )
// Parse 解析swagger文档 // ParseOpenapi3 解析openapi文档
// //
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 16:54 2024/12/23 // Date : 21:02 2025/2/25
func Parse(docUrl string) (*define.DocParseResult, error) { func ParseOpenapi3(docUrl string, docContent string) (*define.OpenapiDoc, error) {
var ( var (
err error docRes define.OpenapiDoc
docContent []byte err error
docByte []byte
) )
if docContent, err = serialize.File.ReadFromRemote(docUrl); nil != err { if len(docContent) < 2 {
if len(docUrl) == 0 {
return nil, errors.New("doc url and doc content all empty")
}
if docByte, err = serialize.File.ReadFromRemote(docUrl); nil != err {
return nil, err
}
} else {
docByte = []byte(docContent)
}
if err = serialize.JSON.UnmarshalWithNumber(docByte, &docRes); nil != err {
return nil, err return nil, err
} }
swaggerVersion := gjson.GetBytes(docContent, "swagger").String() return &docRes, nil
if "" == swaggerVersion || !strings.HasPrefix(swaggerVersion, "2.") {
// 未指定swagger版本或swagger版本3.x
return ParseOpenapi3(docContent)
}
return ParseSwagger2(docContent)
}
func ParseOpenapi3(docContent []byte) (*define.DocParseResult, error) {
return nil, nil
} }

View File

@ -11,7 +11,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/gateway/api-doc/define"
"git.zhangdeman.cn/zhangdeman/serialize"
"reflect" "reflect"
"testing" "testing"
) )
@ -25,10 +24,18 @@ type Meta struct {
// //
// Date : 17:55 2024/7/19 // Date : 17:55 2024/7/19
func Test_parser_Openapi3(t *testing.T) { func Test_parser_Openapi3(t *testing.T) {
type UserExt struct {
Job string `json:"job" dc:"job" binding:"required"`
JobPtr *string `json:"job_ptr" dc:"job_ptr" binding:"required"`
Height string `json:"height" dc:"height" binding:"required"`
}
type User struct { type User struct {
Meta `json:"-" deprecated:"false" path:"/user/detail" method:"POST" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` Meta `json:"-" deprecated:"false" path:"/user/detail" method:"POST" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"`
Name *string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` Name *string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"`
Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"`
IDList []int64 `json:"id_list" dc:"id_list...." binding:"required"`
IDPtrList []*int64 `json:"id_ptr_list" dc:"id_ptr_list...." binding:"required"`
UserExt
} }
type UserDelete struct { type UserDelete struct {
Meta `json:"-" deprecated:"false" path:"/user/detail" method:"DELETE" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` Meta `json:"-" deprecated:"false" path:"/user/detail" method:"DELETE" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"`
@ -88,8 +95,7 @@ func Test_parser_Openapi3(t *testing.T) {
fmt.Println(string(byteData)) fmt.Println(string(byteData))
} }
func TestParseForSwagger(t *testing.T) { func Test_parse_Openapi3_doc(t *testing.T) {
docUrl := "https://git.zhangdeman.cn/swagger.v1.json" res, err := ParseOpenapi3("http://localhost:10990/static-server/github-openapi.json", "")
res, _ := Parse(docUrl) fmt.Println(res, err)
serialize.JSON.ConsoleOutput(res)
} }

29
redoc-free/index.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>{{DOC_TITLE}}</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700 -->
<!-- 预留占位符, 可以通过外部替换 -->
<link href="{{CSS_FAMILY}}" rel="stylesheet">
<!--
Redoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- 预留占位符, 可以通过外部替换 -->
<!--doc.json, 相对于外部设置好的base_url-->
<redoc spec-url='{{DOC_PATH}}'></redoc>
<!--https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js-->
<script src="{{REDOC_STANDALONE_JS}}"> </script>
</body>
</html>

View File

@ -15,10 +15,10 @@ import (
) )
var ( var (
ParseStructField = parseStructField{} ParseStructFieldTag = parseStructFieldTag{}
) )
type parseStructField struct { type parseStructFieldTag struct {
} }
// GetParamName 获取参数名称 // GetParamName 获取参数名称
@ -26,7 +26,7 @@ type parseStructField struct {
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 21:58 2025/2/11 // Date : 21:58 2025/2/11
func (psf parseStructField) GetParamName(structField reflect.StructField) string { func (psf parseStructFieldTag) GetParamName(structField reflect.StructField) string {
paramNameTagList := []string{ paramNameTagList := []string{
define.TagJson, define.TagForm, define.TagJson, define.TagForm,
define.TagXml, define.TagYaml, define.TagXml, define.TagYaml,
@ -49,12 +49,12 @@ func (psf parseStructField) GetParamName(structField reflect.StructField) string
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 22:01 2025/2/11 // Date : 22:01 2025/2/11
func (psf parseStructField) GetParamDesc(structField reflect.StructField) string { func (psf parseStructFieldTag) GetParamDesc(structField reflect.StructField) string {
descTagList := []string{define.TagDesc, define.TagDescription} descTagList := []string{define.TagDc, define.TagDesc, define.TagDescription}
for _, tag := range descTagList { for _, tag := range descTagList {
tagVal := structField.Tag.Get(tag) tagVal := structField.Tag.Get(tag)
if tagVal != "" { if tagVal != "" {
return tagVal return strings.ReplaceAll(tagVal, "###", "`")
} }
} }
// 没有显示的设置参数描述, 则使用参数名作为参数描述 // 没有显示的设置参数描述, 则使用参数名作为参数描述
@ -66,35 +66,38 @@ func (psf parseStructField) GetParamDesc(structField reflect.StructField) string
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 22:05 2025/2/11 // Date : 22:05 2025/2/11
func (psf parseStructField) GetDefaultValue(structField reflect.StructField) any { func (psf parseStructFieldTag) GetDefaultValue(structField reflect.StructField) any {
defaultTagList := []string{define.TagD, define.TagDefault} defaultTagList := []string{define.TagD, define.TagDefault}
fieldType := structField.Type.Kind().String() fieldType := structField.Type.Kind().String()
for _, tag := range defaultTagList { for _, tag := range defaultTagList {
if val, exist := structField.Tag.Lookup(tag); exist && val != "" { val, exist := structField.Tag.Lookup(tag)
if strings.HasPrefix(fieldType, "int") { val = strings.TrimSpace(val)
i, _ := strconv.Atoi(val) if !exist || len(val) == 0 {
return i continue
} }
if strings.HasPrefix(fieldType, "uint") { if strings.HasPrefix(fieldType, "int") {
uintVal, _ := strconv.ParseUint(val, 10, 64) i, _ := strconv.Atoi(val)
return uintVal return i
} }
if strings.HasPrefix(fieldType, "float") { if strings.HasPrefix(fieldType, "uint") {
floatVal, _ := strconv.ParseFloat(val, 64) uintVal, _ := strconv.ParseUint(val, 10, 64)
return floatVal return uintVal
} }
if strings.HasPrefix(fieldType, "string") { if strings.HasPrefix(fieldType, "float") {
return val floatVal, _ := strconv.ParseFloat(val, 64)
} return floatVal
if strings.HasPrefix(fieldType, "bool") { }
if val == "true" { if strings.HasPrefix(fieldType, "string") {
return true
} else {
return false
}
}
return val return val
} }
if strings.HasPrefix(fieldType, "bool") {
if val == "true" {
return true
} else {
return false
}
}
return val
} }
return nil return nil
} }
@ -104,7 +107,7 @@ func (psf parseStructField) GetDefaultValue(structField reflect.StructField) any
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 15:30 2025/2/13 // Date : 15:30 2025/2/13
func (psf parseStructField) GetValidateRule(structField reflect.StructField) string { func (psf parseStructFieldTag) GetValidateRule(structField reflect.StructField) string {
defaultTagList := []string{define.TagValidate, define.TagBinding} defaultTagList := []string{define.TagValidate, define.TagBinding}
for _, tag := range defaultTagList { for _, tag := range defaultTagList {
if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 { if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 {
@ -119,7 +122,7 @@ func (psf parseStructField) GetValidateRule(structField reflect.StructField) str
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 21:12 2025/2/13 // Date : 21:12 2025/2/13
func (psf parseStructField) Deprecated(structField reflect.StructField) bool { func (psf parseStructFieldTag) Deprecated(structField reflect.StructField) bool {
defaultTagList := []string{define.TagDeprecated} defaultTagList := []string{define.TagDeprecated}
for _, tag := range defaultTagList { for _, tag := range defaultTagList {
if tagVal, exist := structField.Tag.Lookup(tag); exist && (tagVal == "1" || strings.ToLower(tagVal) == "true") { if tagVal, exist := structField.Tag.Lookup(tag); exist && (tagVal == "1" || strings.ToLower(tagVal) == "true") {
@ -134,7 +137,7 @@ func (psf parseStructField) Deprecated(structField reflect.StructField) bool {
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 15:15 2025/2/14 // Date : 15:15 2025/2/14
func (psf parseStructField) Summary(structField reflect.StructField) string { func (psf parseStructFieldTag) Summary(structField reflect.StructField) string {
defaultTagList := []string{define.TagSummary} defaultTagList := []string{define.TagSummary}
for _, tag := range defaultTagList { for _, tag := range defaultTagList {
if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 { if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 {
@ -147,3 +150,71 @@ func (psf parseStructField) Summary(structField reflect.StructField) string {
} }
return paramName return paramName
} }
// EnumDescription .枚举值详细描述
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 22:40 2025/2/18
func (psf parseStructFieldTag) EnumDescription(structField reflect.StructField) map[string]string {
defaultTagList := []string{define.TagNameEnumDescription}
res := map[string]string{}
for _, tag := range defaultTagList {
if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 {
tagVal = strings.ReplaceAll(tagVal, "###", "`")
enumList := strings.Split(tagVal, "||")
for _, enum := range enumList {
enumArr := strings.Split(enum, ":")
if len(enumArr) < 2 {
continue
}
res[enumArr[0]] = strings.Join(enumArr[1:], ":")
}
return res
}
}
if len(res) == 0 {
return nil
}
return res
}
// GetExampleValue 示例值
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:42 2025/2/20
func (psf parseStructFieldTag) GetExampleValue(structField reflect.StructField) any {
descTagList := []string{define.TagEg, define.TagExample}
fieldType := structField.Type.Kind().String()
for _, tag := range descTagList {
val := strings.TrimSpace(structField.Tag.Get(tag))
if val == "" {
continue
}
if strings.HasPrefix(fieldType, "int") {
i, _ := strconv.Atoi(val)
return i
}
if strings.HasPrefix(fieldType, "uint") {
uintVal, _ := strconv.ParseUint(val, 10, 64)
return uintVal
}
if strings.HasPrefix(fieldType, "float") {
floatVal, _ := strconv.ParseFloat(val, 64)
return floatVal
}
if strings.HasPrefix(fieldType, "string") {
return val
}
if strings.HasPrefix(fieldType, "bool") {
if val == "true" {
return true
} else {
return false
}
}
return val
}
return nil
}

648
swagger/parser.go Normal file
View File

@ -0,0 +1,648 @@
// Package swagger ...
//
// Description : swagger ...
//
// Author : go_developer@163.com<白茶清欢>
//
// 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

@ -9,6 +9,7 @@ package api_doc
import ( import (
"embed" "embed"
"fmt"
"git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/gateway/api-doc/define"
"git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/consts"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -17,12 +18,16 @@ import (
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
"net/http" "net/http"
"path/filepath"
"strings" "strings"
) )
//go:embed ydoc-lucky-ui/* //go:embed ydoc-lucky-ui/*
var ydocUIFiles embed.FS var ydocUIFiles embed.FS
//go:embed redoc-free/index.html
var redocFreeIndexContent string
// NewSwaggerUI ... // NewSwaggerUI ...
// //
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
@ -106,6 +111,11 @@ func (su *SwaggerUI) Handler() func(ctx *gin.Context) {
case define.SwaggerUIThemeYDocLucky: case define.SwaggerUIThemeYDocLucky:
// YDoc-Lucky-UI 主题处理 // YDoc-Lucky-UI 主题处理
return su.HandleLuckyUI() return su.HandleLuckyUI()
case define.SwaggerUIThemeDefault:
return su.HandleSwaggerUI()
case define.SwaggerUIThemeRedocFree:
// redoc免费版
return su.HandleRedocFreeUI()
default: default:
return su.HandleSwaggerUI() return su.HandleSwaggerUI()
} }
@ -120,7 +130,7 @@ func (su *SwaggerUI) HandleLuckyUI() func(ctx *gin.Context) {
// su.router.StaticFS(su.baseUri+"/assets", http.FS(ydocUIFiles)) // su.router.StaticFS(su.baseUri+"/assets", http.FS(ydocUIFiles))
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
fileRealPath := strings.TrimPrefix(ctx.Request.RequestURI, su.baseUri) fileRealPath := strings.TrimPrefix(ctx.Request.RequestURI, su.baseUri)
byteData, _ := ydocUIFiles.ReadFile(define.SwaggerUIThemeYDocLucky + fileRealPath) byteData, _ := ydocUIFiles.ReadFile(filepath.Join(define.SwaggerUIThemeYDocLucky, fileRealPath))
if strings.HasSuffix(ctx.Request.RequestURI, "html") { if strings.HasSuffix(ctx.Request.RequestURI, "html") {
byteData = []byte(strings.ReplaceAll(string(byteData), "{{BASE_URI}}", su.baseUri)) byteData = []byte(strings.ReplaceAll(string(byteData), "{{BASE_URI}}", su.baseUri))
} }
@ -160,3 +170,26 @@ func (su *SwaggerUI) HandleKnife4goUI() func(ctx *gin.Context) {
func (su *SwaggerUI) HandleSwaggerUI() func(ctx *gin.Context) { func (su *SwaggerUI) HandleSwaggerUI() func(ctx *gin.Context) {
return ginSwagger.WrapHandler(swaggerFiles.Handler) return ginSwagger.WrapHandler(swaggerFiles.Handler)
} }
// HandleRedocFreeUI 处理redoc_free主题
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:40 2025/2/18
func (su *SwaggerUI) HandleRedocFreeUI() func(ctx *gin.Context) {
return func(ctx *gin.Context) {
// TODO : 这部分数据支持外部传参替换
replaceTable := map[string]string{
"{{DOC_TITLE}}": fmt.Sprintf("【%v】%v", su.docInstance.Doc().Info.Version, su.docInstance.Doc().Info.Title),
"{{CSS_FAMILY}}": "https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700",
"{{DOC_PATH}}": "doc.json",
"{{REDOC_STANDALONE_JS}}": "https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js",
}
for k, v := range replaceTable {
redocFreeIndexContent = strings.ReplaceAll(redocFreeIndexContent, k, v)
}
ctx.Header("Content-Type", "text/html; charset=utf-8")
ctx.String(http.StatusOK, redocFreeIndexContent)
ctx.Abort()
}
}

View File

@ -50,15 +50,41 @@ func GetParameterDefaultLocation(requestMethod string) string {
// //
// Date : 12:23 2024/4/22 // Date : 12:23 2024/4/22
func GetSwaggerType(inputType string) string { func GetSwaggerType(inputType string) string {
convertTable := map[string]string{ for _, itemType := range consts.DataTypeBaseInt {
consts.DataTypeString.String(): "string", if itemType.String() == inputType {
consts.DataTypeInt.String(): "integer", return consts.SwaggerDataTypeInteger
consts.DataTypeUint.String(): "integer", }
consts.DataTypeFloat.String(): "number",
consts.DataTypeBool.String(): "boolean",
} }
if _, exist := convertTable[inputType]; exist { for _, itemType := range consts.DataTypeBaseUint {
return convertTable[inputType] 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 return inputType
} }

View File

@ -78,6 +78,84 @@ func (r validateRule) Enum(structField reflect.StructField) []any {
return anySlice return anySlice
} }
// Minimum 最小值
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:51 2025/2/18
func (r validateRule) Minimum(structField reflect.StructField) *int64 {
ruleTable := r.getValidateRuleTable(structField)
var (
err error
minVal, gteVal int64
minStr, gteStr string
minStrExist, gteStrExist bool
)
if minStr, minStrExist = ruleTable[consts.ValidatorRuleCommonMin.String()]; minStrExist && len(minStr) > 0 {
if minVal, err = strconv.ParseInt(minStr, 10, 64); nil != err {
panic("validate rule min val = " + minStr + " : " + err.Error())
}
}
if gteStr, gteStrExist = ruleTable[consts.ValidateRuleGte.String()]; gteStrExist && len(gteStr) > 0 {
if gteVal, err = strconv.ParseInt(gteStr, 10, 64); nil != err {
panic("validate rule gte val = " + gteStr + " : " + err.Error())
}
}
if !minStrExist && !gteStrExist {
// 未配置
return nil
}
if len(minStr) > 0 {
if len(gteStr) > 0 {
if gteVal > minVal {
return &minVal
}
return &gteVal
}
return &minVal
}
return &gteVal
}
// Maximum 最大值
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 22:00 2025/2/18
func (r validateRule) Maximum(structField reflect.StructField) *int64 {
ruleTable := r.getValidateRuleTable(structField)
var (
err error
maxVal, lteVal int64
maxStr, lteStr string
maxStrExist, gleStrExist bool
)
if maxStr, maxStrExist = ruleTable[consts.ValidatorRuleCommonMax.String()]; maxStrExist && len(maxStr) > 0 {
if maxVal, err = strconv.ParseInt(maxStr, 10, 64); nil != err {
panic("validate rule min val = " + maxStr + " : " + err.Error())
}
}
if lteStr, gleStrExist = ruleTable[consts.ValidateRuleLte.String()]; gleStrExist && len(lteStr) > 0 {
if lteVal, err = strconv.ParseInt(lteStr, 10, 64); nil != err {
panic("validate rule gte val = " + lteStr + " : " + err.Error())
}
}
if !maxStrExist && !gleStrExist {
// 未配置
return nil
}
if len(maxStr) > 0 {
if len(lteStr) > 0 {
if lteVal > maxVal {
return &lteVal
}
return &maxVal
}
return &maxVal
}
return &lteVal
}
// getValidateRuleTable 解析验证规则表 // getValidateRuleTable 解析验证规则表
// //
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
@ -85,7 +163,7 @@ func (r validateRule) Enum(structField reflect.StructField) []any {
// Date : 15:29 2025/2/13 // Date : 15:29 2025/2/13
func (r validateRule) getValidateRuleTable(structField reflect.StructField) map[string]string { func (r validateRule) getValidateRuleTable(structField reflect.StructField) map[string]string {
res := map[string]string{} res := map[string]string{}
ruleStr := ParseStructField.GetValidateRule(structField) ruleStr := ParseStructFieldTag.GetValidateRule(structField)
if len(ruleStr) == 0 { if len(ruleStr) == 0 {
return res return res
} }