├── .gitignore ├── README.md ├── _example ├── cs │ ├── api.cs │ ├── client.cs │ └── model.cs ├── golang │ ├── api.go │ └── client.go ├── java │ ├── ApiClient.java │ ├── api │ │ ├── AnimalController.java │ │ ├── OtherApi.java │ │ └── UserApi.java │ └── dto │ │ ├── ApiResult.java │ │ ├── Cat.java │ │ ├── Dog.java │ │ ├── MapResult.java │ │ ├── PageData.java │ │ └── User.java ├── kotlin │ ├── Api.kt │ └── Client.kt ├── python │ ├── client.py │ └── demo.py ├── rust │ ├── api.rs │ ├── client.rs │ └── model.rs ├── swift │ ├── Api.swift │ └── Client.swift └── ts │ ├── client.ts │ ├── demo.ts │ └── index.ts ├── _images ├── api_cs.jpg ├── api_go.jpg ├── api_java.jpg ├── api_kotlin.jpg ├── api_python.jpg ├── api_rust.jpg ├── api_swift.jpg ├── api_ts.jpg ├── home_api.png └── home_struct.png ├── cmd ├── init.go ├── reload.go ├── root.go ├── start.go └── upgrade.go ├── go.mod ├── go.sum ├── internal ├── cmd.go ├── generics.go └── writer.go ├── lang └── doc.go ├── main.go ├── openapi.json ├── tmpl ├── cs │ ├── api.tmpl │ ├── client.tmpl │ ├── cs.go │ └── model.tmpl ├── doc.go ├── golang │ ├── api.tmpl │ ├── client.tmpl │ ├── golang.go │ └── model.tmpl ├── java │ ├── api.tmpl │ ├── client.tmpl │ ├── java.go │ └── model.tmpl ├── kotlin │ ├── api.tmpl │ ├── client.tmpl │ ├── kotlin.go │ └── model.tmpl ├── python │ ├── api.tmpl │ ├── client.tmpl │ ├── model.tmpl │ └── python.go ├── rust │ ├── api.tmpl │ ├── client.tmpl │ ├── model.tmpl │ └── rust.go ├── swift │ ├── api.tmpl │ ├── client.tmpl │ ├── model.tmpl │ └── swift.go ├── template.go ├── ts │ ├── api.tmpl │ ├── client.tmpl │ ├── model.tmpl │ └── ts.go └── types.go ├── v2 ├── doc.go ├── parser.go └── schema.go └── v3 ├── doc.go ├── parser.go └── schema.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .demo 3 | .example -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenApi (Swagger)接口代码生成器 2 | 3 | This is a tool that generates API call code in various programming languages based on the content of an OpenAPI document. 4 | 5 | 根据openapi (swagger) 文档生成不同编程语言客户端接口代码 6 | 7 | | Language | Demo | 8 | |------------|-------------------------------------------------------------------------------------------| 9 | | Java | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_java.jpg) | 10 | | Typescript | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_ts.jpg) | 11 | | Go | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_go.jpg) | 12 | | Swift | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_swift.jpg) | 13 | | Python | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_python.jpg) | 14 | | Kotlin | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_kotlin.jpg) | 15 | | C# | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_cs.jpg) | 16 | | Rust | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/api_rust.jpg) | 17 | 18 | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/home_api.png) 19 | 20 | ![image](https://github.com/otk-final/openapi-codegen/blob/master/_images/home_struct.png) 21 | 22 | ## Feature 23 | 24 | - 支持`v2`/`v3`文档格式 25 | - 支持语言:`ts`,`swift`,`kotlin`,`java`,`python`,`go`, `rust`, `c#` 26 | - ***支持泛型*** 27 | - 支持自定义模版 28 | 29 | ## Install 30 | 31 | 源码编译 32 | 33 | ``` 34 | go build -o openapi 35 | ``` 36 | 37 | ## Download - 1.0.2 38 | 39 | - [mac](https://github.com/otk-final/openapi-codegen/releases/download/v1.0.2/openapi_darwin.zip) 40 | - [windows](https://github.com/otk-final/openapi-codegen/releases/download/v1.0.2/openapi_windows.zip) 41 | - [linux](https://github.com/otk-final/openapi-codegen/releases/download/v1.0.2/openapi_linux.zip) 42 | 43 | 安装并添加到环境变量 44 | 45 | ## Tutorial 46 | 47 | ### start 48 | 49 | ```shell 50 | openapi start -h 51 | 52 | Quick start 53 | 54 | Usage: 55 | start [flags] 56 | 57 | Flags: 58 | -e, --endpoint string example:https://{server}:{port}/v3/api-docs 59 | -h, --help help for start 60 | -l, --lang string kotlin,python,ts,typescript,swift,java,go,golang,rust,cs(c#) 61 | -v, --version string openapi version (default "v3") 62 | 63 | ``` 64 | 65 | 例子 66 | 67 | ```shell 68 | openapi -l ts -v v3 -e http://localhost:8080/v3/api_docs 69 | ``` 70 | 71 | ``` 72 | openapi -l kotlin -v v2 -e http://localhost:8080/v2/api_docs 73 | ``` 74 | 75 | 76 | 77 | ### init 78 | 79 | ```shell 80 | openapi init 81 | ``` 82 | 83 | 在当前目录下生成[openapi.json](https://github.com/otk-final/openapi-codegen/openapi.json)配置文件 84 | 85 | 86 | ### reload 87 | 88 | ```shell 89 | #默认当前目录下 openapi.json 90 | openapi reload 91 | 92 | #自定义文件路径 93 | openapi reload -f /app/openapi.json 94 | ``` 95 | 96 | 根据`openapi.json`配置文件重新生成接口代码,默认全部 97 | 98 | ```shell 99 | #指定name 或者 language 100 | openapi reload -f /app/openapi.json -n ts 101 | ``` 102 | 103 | 104 | 105 | ### Configuration File 106 | 107 | ```json 108 | [{ 109 | "name:"server_name", 110 | //openapi 文档地址 111 | "endpoint": "http://localhost:8083/v3/api-docs", 112 | //目标语言 113 | "lang": "ts", 114 | //openapi 版本 115 | "version": "v3", 116 | //输出文件 - 可自定义,系统仅内置(api,model,client) 117 | "output": { 118 | // api 模版 119 | "api": { 120 | //输出文件 文件头 121 | "header": [ 122 | "package demo.api;", 123 | "", 124 | "import demo.model.*;", 125 | "import demo.ApiClient;" 126 | ], 127 | //自定义模版路径 128 | "template": "custom.tmpl", 129 | //输出文件路径 130 | "file": "src/main/java/demo/api/{api}.java", 131 | // 自定义变量 - 继承全局变量 132 | "variables": { 133 | "k1": "k2" 134 | } 135 | }, 136 | // api 模版 137 | "client": { 138 | "file": "src/main/java/demo/ApiClient.java" 139 | }, 140 | "model": { 141 | "file": "src/main/java/demo/model/{model}.java" 142 | } 143 | }, 144 | //忽略路径 145 | "ignore": ["/error","/v3/"], 146 | //匹配路径 147 | "filter": ["/user/"], 148 | //别名 - 当目标语言遇到语法关键词冲突时使用 149 | "alias": { 150 | //属性别名 151 | "properties": { 152 | "JsonNode":"any" 153 | }, 154 | //结构体别名 155 | "models": { 156 | "User":"People" 157 | }, 158 | //类型别名 159 | "types": { 160 | //自定义数据类型 161 | "string": "CustomString", 162 | //format 自定义数据类型 163 | "interge+int64": "CustomLong" 164 | }, 165 | //参数别名 166 | "parameters": { 167 | "export": "output" 168 | } 169 | }, 170 | //模版全局变量 171 | "variables": { 172 | "name": "value" 173 | }, 174 | //泛型 175 | "generics": { 176 | //是否开启 177 | "enable": true, 178 | //是否展开Root包装类型 179 | "unfold": false, 180 | //泛型表达式 181 | "expressions": { 182 | //单一类型 - ApiResult{... T data ...} 183 | "ApiResult": [ 184 | "data" 185 | ], 186 | //集合类型 - PageData{... List entities ...} 187 | "PageData": [ 188 | "entities+" 189 | ], 190 | //Map类型 - Map{... T data ...} 191 | "MapResult": [ 192 | "data~" 193 | ], 194 | //复合泛型 - KVPair {... T key,V value ...} 195 | "KVPair": [ 196 | "key", 197 | "value" 198 | ] 199 | } 200 | }, 201 | // 解决 openapi重复operation_id 导出带数字的方法名: batch_12 => batch 202 | "repeatable_operation_id": true 203 | }] 204 | ``` 205 | 206 | - openapi文档规范中不支持泛型,泛型表达式需在配置文件中预声明 207 | - 当server端采用统一标准泛型结构返回时,如:`ApiResult`时,可开启`generics.unfold` 参数,业务方法可以直接获取`T` 208 | - 集合泛型声明:`属性+` ,采用 `+` 符号 209 | - Map泛型声明:`属性~` ,采用 `~` 符号 210 | - 内置模版文件:[模版文件](https://github.com/otk-final/openapi-codegen/tree/master/tmpl) 211 | 212 | ### Example 213 | 214 | #### server端 215 | 216 | > [openapi-server](https://github.com/otk-final/openapi-server) 217 | 218 | 219 | #### client端 220 | 221 | > [ts](https://github.com/otk-final/openapi-codegen/tree/master/_example/ts) 222 | > 223 | > [go](https://github.com/otk-final/openapi-codegen/tree/master/_example/golang) 224 | > 225 | > [java](https://github.com/otk-final/openapi-codegen/tree/master/_example/java) 226 | > 227 | > [kotlin](https://github.com/otk-final/openapi-codegen/tree/master/_example/kotlin) 228 | > 229 | > [swift](https://github.com/otk-final/openapi-codegen/tree/master/_example/swift) 230 | > 231 | > [python](https://github.com/otk-final/openapi-codegen/tree/master/_example/python) 232 | > 233 | > [c#](https://github.com/otk-final/openapi-codegen/tree/master/_example/cs) 234 | > 235 | > [rust](https://github.com/otk-final/openapi-codegen/tree/master/_example/rust) -------------------------------------------------------------------------------- /_example/cs/api.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /** 7 | * 动物接口 8 | */ 9 | public class AnimalController 10 | { 11 | 12 | private IApiClient client; 13 | 14 | public AnimalController(IApiClient client) { 15 | this.client = client; 16 | } 17 | 18 | 19 | /** 20 | * 响应long 21 | * 22 | */ 23 | public Task addCat( Cat body) { 24 | 25 | return client.Post<,>("/animal/cat/add",body); 26 | } 27 | 28 | 29 | /** 30 | * 路径+Body 31 | * 32 | */ 33 | public Task addDog( string kind, Dog body) { 34 | 35 | return client.Post<,>(string.Format("/animal/add/{0}/dog",kind),body); 36 | } 37 | 38 | 39 | /** 40 | * 响应list 41 | * 42 | */ 43 | public Task> cats() { 44 | var param = new Dictionary(); 45 | return client.Get>("/animal/cats",param); 46 | } 47 | 48 | 49 | /** 50 | * 响应对象 51 | * 52 | */ 53 | public Task dog( int id) { 54 | var param = new Dictionary(); 55 | return client.Get(string.Format("/animal/dog/{0}",id),param); 56 | } 57 | 58 | 59 | /** 60 | * 响应map 61 | * 其他 62 | */ 63 | public Task> mapCats() { 64 | var param = new Dictionary(); 65 | return client.Get>("/animal/map/cats",param); 66 | } 67 | 68 | 69 | /** 70 | * 响应Map泛型 71 | * 72 | */ 73 | public Task mapDog() { 74 | var param = new Dictionary(); 75 | return client.Get("/animal/map/dogs",param); 76 | } 77 | 78 | 79 | /** 80 | * 响应Array泛型 81 | * 82 | */ 83 | public Task pageDogs() { 84 | var param = new Dictionary(); 85 | return client.Get("/animal/page/dogs",param); 86 | } 87 | 88 | 89 | /** 90 | * 路径+Query 91 | * 92 | */ 93 | public Task searchDog( string color, string kind, string name) { 94 | var param = new Dictionary(); 95 | param["color"] = color; 96 | param["kind"] = kind; 97 | param["name"] = name; 98 | 99 | return client.Get(string.Format("/animal/query/{0}/dog",kind),param); 100 | } 101 | 102 | 103 | } 104 | 105 | 106 | 107 | /** 108 | * 其他接口 109 | */ 110 | public class OtherApi 111 | { 112 | 113 | private IApiClient client; 114 | 115 | public OtherApi(IApiClient client) { 116 | this.client = client; 117 | } 118 | 119 | 120 | /** 121 | * 响应基础类型 122 | * 123 | */ 124 | public Task get() { 125 | var param = new Dictionary(); 126 | return client.Get("/other/long",param); 127 | } 128 | 129 | 130 | /** 131 | * 响应基础类型 132 | * 133 | */ 134 | public Task getContent() { 135 | var param = new Dictionary(); 136 | return client.Get("/other/string",param); 137 | } 138 | 139 | 140 | /** 141 | * 多路径参数 142 | * 143 | */ 144 | public Task> multiplePathVariable( string id, string type) { 145 | var param = new Dictionary(); 146 | return client.Get>(string.Format("/other/boolean/{0}/{1}",type,id),param); 147 | } 148 | 149 | 150 | } 151 | 152 | 153 | 154 | /** 155 | * 用户接口 156 | */ 157 | public class UserApi 158 | { 159 | 160 | private IApiClient client; 161 | 162 | public UserApi(IApiClient client) { 163 | this.client = client; 164 | } 165 | 166 | 167 | /** 168 | * 添加用户 169 | * 170 | */ 171 | public Task add( User body) { 172 | 173 | return client.Post<,>("/user/add",body); 174 | } 175 | 176 | 177 | /** 178 | * 删除用户 179 | * 180 | */ 181 | public Task delete( long id) { 182 | var param = new Dictionary(); 183 | return client.Delete<,>(string.Format("/user/delete/{0}",id),param); 184 | } 185 | 186 | 187 | /** 188 | * 修改用户 189 | * 190 | */ 191 | public Task edit( long id, User body) { 192 | 193 | return client.Put<,>(string.Format("/user/edit/{0}",id),body); 194 | } 195 | 196 | 197 | /** 198 | * 查询用户 199 | * 200 | */ 201 | public Task edit_1( long id, string keyword) { 202 | var param = new Dictionary(); 203 | param["id"] = id; 204 | param["keyword"] = keyword; 205 | 206 | return client.Get(string.Format("/user/detail/{0}",id),param); 207 | } 208 | 209 | 210 | /** 211 | * 用户列表 212 | * 213 | */ 214 | public Task list() { 215 | var param = new Dictionary(); 216 | return client.Get("/user/list",param); 217 | } 218 | 219 | 220 | /** 221 | * 用户分页 222 | * 223 | */ 224 | public Task page( int page, int size) { 225 | var param = new Dictionary(); 226 | param["page"] = page; 227 | param["size"] = size; 228 | 229 | return client.Get("/user/page",param); 230 | } 231 | 232 | 233 | } 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /_example/cs/client.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using System; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Threading.Tasks; 8 | using System.Collections.Generic; 9 | using System.Web; 10 | 11 | public interface IApiClient 12 | { 13 | public Task Get(string path, Dictionary parameters) where T : class; 14 | 15 | public Task Post(string path, P parameters) where T : class where P : class; 16 | 17 | public Task Put(string path, P parameters) where T : class where P : class; 18 | 19 | public Task Delete(string path, P parameters) where T : class where P : class; 20 | } 21 | 22 | public class DefaultApiClient : IApiClient 23 | { 24 | private readonly HttpClient _httpClient; 25 | private readonly JsonSerializerOptions _jsonOptions; 26 | 27 | public DefaultApiClient(HttpClient httpClient) 28 | { 29 | _httpClient = httpClient; 30 | _jsonOptions = new JsonSerializerOptions 31 | { 32 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 33 | }; 34 | } 35 | 36 | public async Task Get(string path, Dictionary parameters) where T : class 37 | { 38 | var uriBuilder = new UriBuilder(_httpClient.BaseAddress + path); 39 | var query = HttpUtility.ParseQueryString(string.Empty); 40 | 41 | foreach (var kvp in parameters) 42 | { 43 | query[kvp.Key] = kvp.Value.ToString(); 44 | } 45 | 46 | uriBuilder.Query = query.ToString(); 47 | var response = await _httpClient.GetAsync(uriBuilder.ToString()); 48 | return await DeserializeResponse(response); 49 | } 50 | 51 | public async Task Post(string path, P parameters) where T : class where P : class 52 | { 53 | var content = SerializeContent(parameters); 54 | var response = await _httpClient.PostAsync(path, content); 55 | return await DeserializeResponse(response); 56 | } 57 | 58 | public async Task Put(string path, P parameters) where T : class where P : class 59 | { 60 | var content = SerializeContent(parameters); 61 | var response = await _httpClient.PutAsync(path, content); 62 | return await DeserializeResponse(response); 63 | } 64 | 65 | public async Task Delete(string path, P parameters) where T : class where P : class 66 | { 67 | var request = new HttpRequestMessage 68 | { 69 | Method = HttpMethod.Delete, 70 | RequestUri = new Uri(_httpClient.BaseAddress + path), 71 | Content = SerializeContent(parameters) 72 | }; 73 | var response = await _httpClient.SendAsync(request); 74 | return await DeserializeResponse(response); 75 | } 76 | 77 | private HttpContent SerializeContent(T obj) 78 | { 79 | var json = JsonSerializer.Serialize(obj, _jsonOptions); 80 | return new StringContent(json, Encoding.UTF8, "application/json"); 81 | } 82 | 83 | private async Task DeserializeResponse(HttpResponseMessage response) where T : class 84 | { 85 | response.EnsureSuccessStatusCode(); 86 | var json = await response.Content.ReadAsStringAsync(); 87 | return JsonSerializer.Deserialize(json, _jsonOptions); 88 | } 89 | } -------------------------------------------------------------------------------- /_example/cs/model.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /** 7 | * 接口标准响应 8 | * 9 | */ 10 | public class ApiResultBoolean 11 | { 12 | /** 13 | * 业务响应码 14 | * 15 | */ 16 | public string? Code { get; set; } 17 | /** 18 | * 数据 19 | * 20 | */ 21 | public bool? Data { get; set; } 22 | /** 23 | * 业务错误信息 24 | * 25 | */ 26 | public string? Err { get; set; } 27 | /** 28 | * 业务响应信息 29 | * 30 | */ 31 | public string? Msg { get; set; } 32 | 33 | } 34 | 35 | 36 | 37 | 38 | /** 39 | * 接口标准响应 40 | * 41 | */ 42 | public class ApiResultLong 43 | { 44 | /** 45 | * 业务响应码 46 | * 47 | */ 48 | public string? Code { get; set; } 49 | /** 50 | * 数据 51 | * 52 | */ 53 | public long? Data { get; set; } 54 | /** 55 | * 业务错误信息 56 | * 57 | */ 58 | public string? Err { get; set; } 59 | /** 60 | * 业务响应信息 61 | * 62 | */ 63 | public string? Msg { get; set; } 64 | 65 | } 66 | 67 | 68 | 69 | 70 | /** 71 | * 猫 72 | * 73 | */ 74 | public class Cat 75 | { 76 | /** 77 | * 食物 78 | * 79 | */ 80 | public List? Foods { get; set; } 81 | /** 82 | * 昵称 83 | * 84 | */ 85 | public string? Nickname { get; set; } 86 | 87 | } 88 | 89 | 90 | 91 | 92 | /** 93 | * 狗 94 | * 95 | */ 96 | public class Dog 97 | { 98 | /** 99 | * 颜色 100 | * 101 | */ 102 | public string? Color { get; set; } 103 | /** 104 | * ID 105 | * 106 | */ 107 | public long? Id { get; set; } 108 | /** 109 | * 昵称 110 | * 111 | */ 112 | public string? Nickname { get; set; } 113 | /** 114 | * 售价 115 | * 116 | */ 117 | public int? Price { get; set; } 118 | 119 | } 120 | 121 | 122 | 123 | 124 | /** 125 | * 接口标准响应 126 | * 127 | */ 128 | public class ApiResultListUser 129 | { 130 | /** 131 | * 业务响应码 132 | * 133 | */ 134 | public string? Code { get; set; } 135 | /** 136 | * 数据 137 | * 138 | */ 139 | public List? Data { get; set; } 140 | /** 141 | * 业务错误信息 142 | * 143 | */ 144 | public string? Err { get; set; } 145 | /** 146 | * 业务响应信息 147 | * 148 | */ 149 | public string? Msg { get; set; } 150 | 151 | } 152 | 153 | 154 | 155 | 156 | /** 157 | * 接口标准响应 158 | * 159 | */ 160 | public class ApiResultPageDataUser 161 | { 162 | /** 163 | * 业务响应码 164 | * 165 | */ 166 | public string? Code { get; set; } 167 | /** 168 | * 169 | * 170 | */ 171 | public PageDataUser? Data { get; set; } 172 | /** 173 | * 业务错误信息 174 | * 175 | */ 176 | public string? Err { get; set; } 177 | /** 178 | * 业务响应信息 179 | * 180 | */ 181 | public string? Msg { get; set; } 182 | 183 | } 184 | 185 | 186 | 187 | 188 | /** 189 | * 接口标准响应 190 | * 191 | */ 192 | public class ApiResultUser 193 | { 194 | /** 195 | * 业务响应码 196 | * 197 | */ 198 | public string? Code { get; set; } 199 | /** 200 | * 201 | * 202 | */ 203 | public User? Data { get; set; } 204 | /** 205 | * 业务错误信息 206 | * 207 | */ 208 | public string? Err { get; set; } 209 | /** 210 | * 业务响应信息 211 | * 212 | */ 213 | public string? Msg { get; set; } 214 | 215 | } 216 | 217 | 218 | 219 | 220 | /** 221 | * 接口标准响应 222 | * 223 | */ 224 | public class MapResultDog 225 | { 226 | /** 227 | * 响应码 228 | * 229 | */ 230 | public string? Code { get; set; } 231 | /** 232 | * 数据 233 | * 234 | */ 235 | public Dictionary? Data { get; set; } 236 | 237 | } 238 | 239 | 240 | 241 | 242 | /** 243 | * 标准分页数据 244 | * 245 | */ 246 | public class PageDataDog 247 | { 248 | /** 249 | * 数据 250 | * 251 | */ 252 | public List? Entities { get; set; } 253 | /** 254 | * 页码 255 | * 256 | */ 257 | public int? Page { get; set; } 258 | /** 259 | * 条数 260 | * 261 | */ 262 | public int? Size { get; set; } 263 | /** 264 | * 总数 265 | * 266 | */ 267 | public long? Total { get; set; } 268 | 269 | } 270 | 271 | 272 | 273 | 274 | /** 275 | * 标准分页数据 276 | * 277 | */ 278 | public class PageDataUser 279 | { 280 | /** 281 | * 数据 282 | * 283 | */ 284 | public List? Entities { get; set; } 285 | /** 286 | * 页码 287 | * 288 | */ 289 | public int? Page { get; set; } 290 | /** 291 | * 条数 292 | * 293 | */ 294 | public int? Size { get; set; } 295 | /** 296 | * 总数 297 | * 298 | */ 299 | public long? Total { get; set; } 300 | 301 | } 302 | 303 | 304 | 305 | 306 | /** 307 | * 用户 308 | * 309 | */ 310 | public class User 311 | { 312 | /** 313 | * 年龄 314 | * 315 | */ 316 | public int? Age { get; set; } 317 | /** 318 | * 生日 319 | * 320 | */ 321 | public string? Birthday { get; set; } 322 | /** 323 | * 集合 324 | * 325 | */ 326 | public List? Cats { get; set; } 327 | /** 328 | * 是否启用 329 | * 330 | */ 331 | public bool? Enable { get; set; } 332 | /** 333 | * 引用map 334 | * 335 | */ 336 | public Dictionary? Gods { get; set; } 337 | /** 338 | * ID 339 | * 340 | */ 341 | public long? Id { get; set; } 342 | /** 343 | * 基础map 344 | * 345 | */ 346 | public Dictionary? Kv { get; set; } 347 | /** 348 | * 名称 349 | * 350 | */ 351 | public string? Name { get; set; } 352 | /** 353 | * 354 | * 355 | */ 356 | public JsonNode? Profile { get; set; } 357 | /** 358 | * 标签 359 | * 360 | */ 361 | public List? Tags { get; set; } 362 | /** 363 | * 时间戳 364 | * 365 | */ 366 | public long? Timestamp { get; set; } 367 | 368 | } 369 | 370 | 371 | 372 | 373 | -------------------------------------------------------------------------------- /_example/golang/api.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import "fmt" 4 | 5 | // ApiResult ApiResult[T any] 6 | type ApiResult[T any] struct { 7 | Code string `json:"code,omitempty"` //业务响应码 8 | Data T `json:"data,omitempty"` //数据 9 | Err string `json:"err,omitempty"` //业务错误信息 10 | Msg string `json:"msg,omitempty"` //业务响应信息 11 | 12 | } 13 | 14 | // PageData PageData[T any] 15 | type PageData[T any] struct { 16 | Entities []T `json:"entities,omitempty"` //数据 17 | Page int32 `json:"page,omitempty"` //页码 18 | Size int32 `json:"size,omitempty"` //条数 19 | Total int64 `json:"total,omitempty"` //总数 20 | 21 | } 22 | 23 | // MapResult MapResult[T any] 24 | type MapResult[T any] struct { 25 | Code string `json:"code,omitempty"` //响应码 26 | Data map[string]T `json:"data,omitempty"` //数据 27 | 28 | } 29 | 30 | // Cat 猫 31 | type Cat struct { 32 | Foods []string `json:"foods,omitempty"` //食物 33 | Nickname string `json:"nickname,omitempty"` //昵称 34 | 35 | } 36 | 37 | // Dog 狗 38 | type Dog struct { 39 | Color string `json:"color,omitempty"` //颜色 40 | Id int64 `json:"id,omitempty"` //ID 41 | Nickname string `json:"nickname,omitempty"` //昵称 42 | Price int `json:"price,omitempty"` //售价 43 | 44 | } 45 | 46 | // User 用户 47 | type User struct { 48 | Age int32 `json:"age,omitempty"` //年龄 49 | Birthday string `json:"birthday,omitempty"` //生日 50 | Cats []*Cat `json:"cats,omitempty"` //集合 51 | Enable bool `json:"enable,omitempty"` //是否启用 52 | Gods map[string]*Dog `json:"gods,omitempty"` //引用map 53 | Id int64 `json:"id,omitempty"` //ID 54 | Kv map[string]string `json:"kv,omitempty"` //基础map 55 | Name string `json:"name,omitempty"` //名称 56 | Profile any `json:"profile,omitempty"` // 57 | Tags []string `json:"tags,omitempty"` //标签 58 | Timestamp int64 `json:"timestamp,omitempty"` //时间戳 59 | 60 | } 61 | 62 | // AnimalControllerAddCat "/animal/cat/add" 63 | type AnimalControllerAddCat struct { 64 | Client ApiClient[int] 65 | } 66 | 67 | // Call 响应long 68 | func (receiver *AnimalControllerAddCat) Call(body *Cat) (int, error) { 69 | 70 | return receiver.Client.Post("/animal/cat/add", body) 71 | } 72 | 73 | // AnimalControllerAddDog fmt.Sprintf("/animal/add/%v/dog",kind) 74 | type AnimalControllerAddDog struct { 75 | Client ApiClient[*Dog] 76 | } 77 | 78 | // Call 路径+Body 79 | func (receiver *AnimalControllerAddDog) Call(kind string, body *Dog) (*Dog, error) { 80 | 81 | return receiver.Client.Post(fmt.Sprintf("/animal/add/%v/dog", kind), body) 82 | } 83 | 84 | // AnimalControllerCats "/animal/cats" 85 | type AnimalControllerCats struct { 86 | Client ApiClient[[]*Cat] 87 | } 88 | 89 | // Call 响应list 90 | func (receiver *AnimalControllerCats) Call() ([]*Cat, error) { 91 | params := map[string]any{} 92 | return receiver.Client.Get("/animal/cats", params) 93 | } 94 | 95 | // AnimalControllerDog fmt.Sprintf("/animal/dog/%v",id) 96 | type AnimalControllerDog struct { 97 | Client ApiClient[*Dog] 98 | } 99 | 100 | // Call 响应对象 101 | func (receiver *AnimalControllerDog) Call(id int32) (*Dog, error) { 102 | params := map[string]any{} 103 | return receiver.Client.Get(fmt.Sprintf("/animal/dog/%v", id), params) 104 | } 105 | 106 | // AnimalControllerMapCats "/animal/map/cats" 107 | type AnimalControllerMapCats struct { 108 | Client ApiClient[map[string]*Cat] 109 | } 110 | 111 | // Call 响应map 112 | func (receiver *AnimalControllerMapCats) Call() (map[string]*Cat, error) { 113 | params := map[string]any{} 114 | return receiver.Client.Get("/animal/map/cats", params) 115 | } 116 | 117 | // AnimalControllerMapDog "/animal/map/dogs" 118 | type AnimalControllerMapDog struct { 119 | Client ApiClient[MapResult[*Dog]] 120 | } 121 | 122 | // Call 响应Map泛型 123 | func (receiver *AnimalControllerMapDog) Call() (MapResult[*Dog], error) { 124 | params := map[string]any{} 125 | return receiver.Client.Get("/animal/map/dogs", params) 126 | } 127 | 128 | // AnimalControllerPageDogs "/animal/page/dogs" 129 | type AnimalControllerPageDogs struct { 130 | Client ApiClient[PageData[*Dog]] 131 | } 132 | 133 | // Call 响应Array泛型 134 | func (receiver *AnimalControllerPageDogs) Call() (PageData[*Dog], error) { 135 | params := map[string]any{} 136 | return receiver.Client.Get("/animal/page/dogs", params) 137 | } 138 | 139 | // AnimalControllerSearchDog fmt.Sprintf("/animal/query/%v/dog",kind) 140 | type AnimalControllerSearchDog struct { 141 | Client ApiClient[*Dog] 142 | } 143 | 144 | // Call 路径+Query 145 | func (receiver *AnimalControllerSearchDog) Call(color string, kind string, name string) (*Dog, error) { 146 | 147 | params := map[string]any{} 148 | params["color"] = color 149 | params["name"] = name 150 | 151 | return receiver.Client.Get(fmt.Sprintf("/animal/query/%v/dog", kind), params) 152 | } 153 | 154 | // OtherApiGet "/other/long" 155 | type OtherApiGet struct { 156 | Client ApiClient[string] 157 | } 158 | 159 | // Call 响应基础类型 160 | func (receiver *OtherApiGet) Call() (string, error) { 161 | params := map[string]any{} 162 | return receiver.Client.Get("/other/long", params) 163 | } 164 | 165 | // OtherApiGetContent "/other/string" 166 | type OtherApiGetContent struct { 167 | Client ApiClient[int] 168 | } 169 | 170 | // Call 响应基础类型 171 | func (receiver *OtherApiGetContent) Call() (int, error) { 172 | params := map[string]any{} 173 | return receiver.Client.Get("/other/string", params) 174 | } 175 | 176 | // OtherApiMultiplePathVariable fmt.Sprintf("/other/boolean/%v/%v",type2,id) 177 | type OtherApiMultiplePathVariable struct { 178 | Client ApiClient[[]*Dog] 179 | } 180 | 181 | // Call 多路径参数 182 | func (receiver *OtherApiMultiplePathVariable) Call(id string, type2 string) ([]*Dog, error) { 183 | params := map[string]any{} 184 | return receiver.Client.Get(fmt.Sprintf("/other/boolean/%v/%v", type2, id), params) 185 | } 186 | 187 | // UserApiAdd "/user/add" 188 | type UserApiAdd struct { 189 | Client ApiClient[ApiResult[int64]] 190 | } 191 | 192 | // Call 添加用户 193 | func (receiver *UserApiAdd) Call(body *User) (ApiResult[int64], error) { 194 | 195 | return receiver.Client.Post("/user/add", body) 196 | } 197 | 198 | // UserApiDelete fmt.Sprintf("/user/delete/%v",id) 199 | type UserApiDelete struct { 200 | Client ApiClient[ApiResult[bool]] 201 | } 202 | 203 | // Call 删除用户 204 | func (receiver *UserApiDelete) Call(id int64) (ApiResult[bool], error) { 205 | params := map[string]any{} 206 | return receiver.Client.Delete(fmt.Sprintf("/user/delete/%v", id), params) 207 | } 208 | 209 | // UserApiEdit fmt.Sprintf("/user/edit/%v",id) 210 | type UserApiEdit struct { 211 | Client ApiClient[ApiResult[int64]] 212 | } 213 | 214 | // Call 修改用户 215 | func (receiver *UserApiEdit) Call(id int64, body *User) (ApiResult[int64], error) { 216 | 217 | return receiver.Client.Put(fmt.Sprintf("/user/edit/%v", id), body) 218 | } 219 | 220 | // UserApiEdit_1 fmt.Sprintf("/user/detail/%v",id) 221 | type UserApiEdit_1 struct { 222 | Client ApiClient[ApiResult[*User]] 223 | } 224 | 225 | // Call 查询用户 226 | func (receiver *UserApiEdit_1) Call(id int64, keyword string) (ApiResult[*User], error) { 227 | 228 | params := map[string]any{} 229 | params["keyword"] = keyword 230 | 231 | return receiver.Client.Get(fmt.Sprintf("/user/detail/%v", id), params) 232 | } 233 | 234 | // UserApiList "/user/list" 235 | type UserApiList struct { 236 | Client ApiClient[ApiResult[[]*User]] 237 | } 238 | 239 | // Call 用户列表 240 | func (receiver *UserApiList) Call() (ApiResult[[]*User], error) { 241 | params := map[string]any{} 242 | return receiver.Client.Get("/user/list", params) 243 | } 244 | 245 | // UserApiPage "/user/page" 246 | type UserApiPage struct { 247 | Client ApiClient[ApiResult[PageData[*User]]] 248 | } 249 | 250 | // Call 用户分页 251 | func (receiver *UserApiPage) Call(page int32, size int32) (ApiResult[PageData[*User]], error) { 252 | 253 | params := map[string]any{} 254 | params["page"] = page 255 | params["size"] = size 256 | 257 | return receiver.Client.Get("/user/page", params) 258 | } 259 | -------------------------------------------------------------------------------- /_example/golang/client.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | // ApiClient TODO You need to implement the current interface 13 | type ApiClient[T any] interface { 14 | Get(name string, params map[string]any) (T, error) 15 | 16 | Post(name string, params any) (T, error) 17 | 18 | Put(name string, params any) (T, error) 19 | 20 | Delete(name string, params any) (T, error) 21 | } 22 | 23 | type DefaultClient[T any] struct { 24 | Host string 25 | Headers http.Header 26 | } 27 | 28 | func NewClient[T any](host string, headers http.Header) ApiClient[T] { 29 | return &DefaultClient[T]{host, headers} 30 | } 31 | 32 | func (t *DefaultClient[T]) Get(name string, params map[string]any) (T, error) { 33 | 34 | uri, _ := url.Parse(t.Host) 35 | uri.Path = name 36 | 37 | if params != nil { 38 | query := url.Values{} 39 | for k, v := range params { 40 | query.Set(k, fmt.Sprintf("%v", v)) 41 | } 42 | uri.RawQuery = query.Encode() 43 | } 44 | 45 | req := &http.Request{ 46 | Method: http.MethodGet, 47 | URL: uri, 48 | Header: t.Headers, 49 | } 50 | 51 | return t.request(req) 52 | } 53 | 54 | func (t *DefaultClient[T]) Post(name string, params any) (T, error) { 55 | return t.doBody(http.MethodPost, name, params) 56 | } 57 | 58 | func (t *DefaultClient[T]) Put(name string, params any) (T, error) { 59 | return t.doBody(http.MethodPut, name, params) 60 | } 61 | 62 | func (t *DefaultClient[T]) Delete(name string, params any) (T, error) { 63 | return t.doBody(http.MethodDelete, name, params) 64 | } 65 | 66 | func (t *DefaultClient[T]) doBody(method string, name string, params any) (T, error) { 67 | uri, _ := url.Parse(t.Host) 68 | uri.JoinPath(name) 69 | 70 | req := &http.Request{ 71 | Method: method, 72 | URL: uri, 73 | Header: t.Headers, 74 | } 75 | 76 | if params != nil { 77 | body, _ := json.Marshal(params) 78 | req.Body = io.NopCloser(bytes.NewBuffer(body)) 79 | } 80 | 81 | return t.request(req) 82 | } 83 | func (t *DefaultClient[T]) request(req *http.Request) (T, error) { 84 | 85 | var ret T 86 | 87 | resp, err := http.DefaultClient.Do(req) 88 | if err != nil { 89 | return ret, err 90 | } 91 | 92 | defer func() { 93 | _ = resp.Body.Close() 94 | }() 95 | body, err := io.ReadAll(resp.Body) 96 | if err != nil { 97 | return ret, err 98 | } 99 | 100 | err = json.Unmarshal(body, &ret) 101 | if err != nil { 102 | return ret, err 103 | } 104 | return ret, nil 105 | } 106 | -------------------------------------------------------------------------------- /_example/java/ApiClient.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package example; 4 | 5 | import com.fasterxml.jackson.core.type.TypeReference; 6 | 7 | import java.util.Map; 8 | 9 | public interface ApiClient { 10 | 11 | T get(String path, Map params, TypeReference resultType); 12 | 13 | T put(String path, P body, TypeReference resultType); 14 | 15 | T post(String path, P params, TypeReference resultType); 16 | 17 | T delete(String path, P params, TypeReference resultType); 18 | } 19 | -------------------------------------------------------------------------------- /_example/java/api/AnimalController.java: -------------------------------------------------------------------------------- 1 | 2 | package example.api; 3 | 4 | import example.dto.*; 5 | import example.ApiClient; 6 | import com.fasterxml.jackson.core.type.TypeReference; 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.HashMap; 11 | 12 | /** 13 | * 动物接口 14 | */ 15 | public class AnimalController { 16 | 17 | private final ApiClient client; 18 | 19 | public AnimalController(ApiClient client) { 20 | this.client = client; 21 | } 22 | 23 | 24 | 25 | public static final TypeReference addCatResultType = new TypeReference() { 26 | }; 27 | 28 | /** 29 | * 响应long 30 | * 31 | */ 32 | public Integer addCat( Cat body) { 33 | 34 | return client.post("/animal/cat/add",body, addCatResultType); 35 | } 36 | 37 | 38 | 39 | public static final TypeReference addDogResultType = new TypeReference() { 40 | }; 41 | 42 | /** 43 | * 路径+Body 44 | * 45 | */ 46 | public Dog addDog( String kind, Dog body) { 47 | 48 | return client.post(String.format("/animal/add/%s/dog",kind),body, addDogResultType); 49 | } 50 | 51 | 52 | 53 | public static final TypeReference> catsResultType = new TypeReference>() { 54 | }; 55 | 56 | /** 57 | * 响应list 58 | * 59 | */ 60 | public List cats() { 61 | Map params = new HashMap(); 62 | return client.get("/animal/cats",params, catsResultType); 63 | } 64 | 65 | 66 | 67 | public static final TypeReference dogResultType = new TypeReference() { 68 | }; 69 | 70 | /** 71 | * 响应对象 72 | * 73 | */ 74 | public Dog dog( Integer id) { 75 | Map params = new HashMap(); 76 | return client.get(String.format("/animal/dog/%s",id),params, dogResultType); 77 | } 78 | 79 | 80 | 81 | public static final TypeReference> mapCatsResultType = new TypeReference>() { 82 | }; 83 | 84 | /** 85 | * 响应map 86 | * 其他 87 | */ 88 | public Map mapCats() { 89 | Map params = new HashMap(); 90 | return client.get("/animal/map/cats",params, mapCatsResultType); 91 | } 92 | 93 | 94 | 95 | public static final TypeReference mapDogResultType = new TypeReference() { 96 | }; 97 | 98 | /** 99 | * 响应Map泛型 100 | * 101 | */ 102 | public Dog mapDog() { 103 | Map params = new HashMap(); 104 | return client.get("/animal/map/dogs",params, mapDogResultType); 105 | } 106 | 107 | 108 | 109 | public static final TypeReference pageDogsResultType = new TypeReference() { 110 | }; 111 | 112 | /** 113 | * 响应Array泛型 114 | * 115 | */ 116 | public Dog pageDogs() { 117 | Map params = new HashMap(); 118 | return client.get("/animal/page/dogs",params, pageDogsResultType); 119 | } 120 | 121 | 122 | 123 | public static final TypeReference searchDogResultType = new TypeReference() { 124 | }; 125 | 126 | /** 127 | * 路径+Query 128 | * 129 | */ 130 | public Dog searchDog( String color, String kind, String name) { 131 | Map params = new HashMap(); 132 | params.put("color",color); 133 | params.put("kind",kind); 134 | params.put("name",name); 135 | 136 | return client.get(String.format("/animal/query/%s/dog",kind),params, searchDogResultType); 137 | } 138 | 139 | 140 | } 141 | -------------------------------------------------------------------------------- /_example/java/api/OtherApi.java: -------------------------------------------------------------------------------- 1 | 2 | package example.api; 3 | 4 | import example.dto.*; 5 | import example.ApiClient; 6 | import com.fasterxml.jackson.core.type.TypeReference; 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.HashMap; 11 | 12 | /** 13 | * 其他接口 14 | */ 15 | public class OtherApi { 16 | 17 | private final ApiClient client; 18 | 19 | public OtherApi(ApiClient client) { 20 | this.client = client; 21 | } 22 | 23 | 24 | 25 | public static final TypeReference getResultType = new TypeReference() { 26 | }; 27 | 28 | /** 29 | * 响应基础类型 30 | * 31 | */ 32 | public String get() { 33 | Map params = new HashMap(); 34 | return client.get("/other/long",params, getResultType); 35 | } 36 | 37 | 38 | 39 | public static final TypeReference getContentResultType = new TypeReference() { 40 | }; 41 | 42 | /** 43 | * 响应基础类型 44 | * 45 | */ 46 | public Integer getContent() { 47 | Map params = new HashMap(); 48 | return client.get("/other/string",params, getContentResultType); 49 | } 50 | 51 | 52 | 53 | public static final TypeReference> multiplePathVariableResultType = new TypeReference>() { 54 | }; 55 | 56 | /** 57 | * 多路径参数 58 | * 59 | */ 60 | public List multiplePathVariable( String id, String type) { 61 | Map params = new HashMap(); 62 | return client.get(String.format("/other/boolean/%s/%s",type,id),params, multiplePathVariableResultType); 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /_example/java/api/UserApi.java: -------------------------------------------------------------------------------- 1 | 2 | package example.api; 3 | 4 | import example.dto.*; 5 | import example.ApiClient; 6 | import com.fasterxml.jackson.core.type.TypeReference; 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.HashMap; 11 | 12 | /** 13 | * 用户接口 14 | */ 15 | public class UserApi { 16 | 17 | private final ApiClient client; 18 | 19 | public UserApi(ApiClient client) { 20 | this.client = client; 21 | } 22 | 23 | 24 | 25 | public static final TypeReference addResultType = new TypeReference() { 26 | }; 27 | 28 | /** 29 | * 添加用户 30 | * 31 | */ 32 | public Long add( User body) { 33 | 34 | return client.post("/user/add",body, addResultType); 35 | } 36 | 37 | 38 | 39 | public static final TypeReference deleteResultType = new TypeReference() { 40 | }; 41 | 42 | /** 43 | * 删除用户 44 | * 45 | */ 46 | public Boolean delete( Long id) { 47 | Map params = new HashMap(); 48 | return client.delete(String.format("/user/delete/%s",id),params, deleteResultType); 49 | } 50 | 51 | 52 | 53 | public static final TypeReference editResultType = new TypeReference() { 54 | }; 55 | 56 | /** 57 | * 修改用户 58 | * 59 | */ 60 | public Long edit( Long id, User body) { 61 | 62 | return client.put(String.format("/user/edit/%s",id),body, editResultType); 63 | } 64 | 65 | 66 | 67 | public static final TypeReference edit_1ResultType = new TypeReference() { 68 | }; 69 | 70 | /** 71 | * 查询用户 72 | * 73 | */ 74 | public User edit_1( Long id, String keyword) { 75 | Map params = new HashMap(); 76 | params.put("id",id); 77 | params.put("keyword",keyword); 78 | 79 | return client.get(String.format("/user/detail/%s",id),params, edit_1ResultType); 80 | } 81 | 82 | 83 | 84 | public static final TypeReference> listResultType = new TypeReference>() { 85 | }; 86 | 87 | /** 88 | * 用户列表 89 | * 90 | */ 91 | public List list() { 92 | Map params = new HashMap(); 93 | return client.get("/user/list",params, listResultType); 94 | } 95 | 96 | 97 | 98 | public static final TypeReference> pageResultType = new TypeReference>() { 99 | }; 100 | 101 | /** 102 | * 用户分页 103 | * 104 | */ 105 | public PageData page( Integer page, Integer size) { 106 | Map params = new HashMap(); 107 | params.put("page",page); 108 | params.put("size",size); 109 | 110 | return client.get("/user/page",params, pageResultType); 111 | } 112 | 113 | 114 | } 115 | -------------------------------------------------------------------------------- /_example/java/dto/ApiResult.java: -------------------------------------------------------------------------------- 1 | 2 | package example.dto; 3 | 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | /** 15 | * 16 | * 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class ApiResult implements Serializable { 22 | /** 23 | * 业务响应码 24 | * 25 | */ 26 | private String code; 27 | /** 28 | * 数据 29 | * 30 | */ 31 | private T data; 32 | /** 33 | * 业务错误信息 34 | * 35 | */ 36 | private String err; 37 | /** 38 | * 业务响应信息 39 | * 40 | */ 41 | private String msg; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /_example/java/dto/Cat.java: -------------------------------------------------------------------------------- 1 | 2 | package example.dto; 3 | 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | /** 15 | * 猫 16 | * 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class Cat implements Serializable { 22 | /** 23 | * 食物 24 | * 25 | */ 26 | private List foods; 27 | /** 28 | * 昵称 29 | * 30 | */ 31 | private String nickname; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /_example/java/dto/Dog.java: -------------------------------------------------------------------------------- 1 | 2 | package example.dto; 3 | 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | /** 15 | * 狗 16 | * 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class Dog implements Serializable { 22 | /** 23 | * 颜色 24 | * 25 | */ 26 | private String color; 27 | /** 28 | * ID 29 | * 30 | */ 31 | private Long id; 32 | /** 33 | * 昵称 34 | * 35 | */ 36 | private String nickname; 37 | /** 38 | * 售价 39 | * 40 | */ 41 | private Integer price; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /_example/java/dto/MapResult.java: -------------------------------------------------------------------------------- 1 | 2 | package example.dto; 3 | 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | /** 15 | * 16 | * 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class MapResult implements Serializable { 22 | /** 23 | * 响应码 24 | * 25 | */ 26 | private String code; 27 | /** 28 | * 数据 29 | * 30 | */ 31 | private Map data; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /_example/java/dto/PageData.java: -------------------------------------------------------------------------------- 1 | 2 | package example.dto; 3 | 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | /** 15 | * 16 | * 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class PageData implements Serializable { 22 | /** 23 | * 数据 24 | * 25 | */ 26 | private List entities; 27 | /** 28 | * 页码 29 | * 30 | */ 31 | private Integer page; 32 | /** 33 | * 条数 34 | * 35 | */ 36 | private Integer size; 37 | /** 38 | * 总数 39 | * 40 | */ 41 | private Long total; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /_example/java/dto/User.java: -------------------------------------------------------------------------------- 1 | 2 | package example.dto; 3 | 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | /** 15 | * 用户 16 | * 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class User implements Serializable { 22 | /** 23 | * 年龄 24 | * 25 | */ 26 | private Integer age; 27 | /** 28 | * 生日 29 | * 30 | */ 31 | private String birthday; 32 | /** 33 | * 集合 34 | * 35 | */ 36 | private List cats; 37 | /** 38 | * 是否启用 39 | * 40 | */ 41 | private Boolean enable; 42 | /** 43 | * 引用map 44 | * 45 | */ 46 | private Map gods; 47 | /** 48 | * ID 49 | * 50 | */ 51 | private Long id; 52 | /** 53 | * 基础map 54 | * 55 | */ 56 | private Map kv; 57 | /** 58 | * 名称 59 | * 60 | */ 61 | private String name; 62 | /** 63 | * 64 | * 65 | */ 66 | private JsonNode profile; 67 | /** 68 | * 标签 69 | * 70 | */ 71 | private List tags; 72 | /** 73 | * 时间戳 74 | * 75 | */ 76 | private Long timestamp; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /_example/kotlin/Api.kt: -------------------------------------------------------------------------------- 1 | package com.demo; 2 | 3 | import com.demo.ApiClient; 4 | import java.io.Serializable 5 | import com.google.gson.reflect.TypeToken 6 | 7 | 8 | /** 9 | * 10 | */ 11 | data class ApiResult( 12 | //业务响应码 13 | var code: String, 14 | //数据 15 | var data: T, 16 | //业务错误信息 17 | var err: String, 18 | //业务响应信息 19 | var msg: String, 20 | 21 | ) : Serializable 22 | 23 | /** 24 | * 25 | */ 26 | data class PageData( 27 | //数据 28 | var entities: List, 29 | //页码 30 | var page: Int, 31 | //条数 32 | var size: Int, 33 | //总数 34 | var total: Long, 35 | 36 | ) : Serializable 37 | 38 | /** 39 | * 40 | */ 41 | data class MapResult( 42 | //响应码 43 | var code: String, 44 | //数据 45 | var data: Map, 46 | 47 | ) : Serializable 48 | 49 | /** 50 | * 猫 51 | */ 52 | data class Cat( 53 | //食物 54 | var foods: List, 55 | //昵称 56 | var nickname: String, 57 | 58 | ) : Serializable 59 | 60 | /** 61 | * 狗 62 | */ 63 | data class Dog( 64 | //颜色 65 | var color: String, 66 | //ID 67 | var id: Long, 68 | //昵称 69 | var nickname: String, 70 | //售价 71 | var price: Int, 72 | 73 | ) : Serializable 74 | 75 | /** 76 | * 用户 77 | */ 78 | data class User( 79 | //年龄 80 | var age: Int, 81 | //生日 82 | var birthday: String, 83 | //集合 84 | var cats: List, 85 | //是否启用 86 | var enable: Boolean, 87 | //引用map 88 | var gods: Map, 89 | //ID 90 | var id: Long, 91 | //基础map 92 | var kv: Map, 93 | //名称 94 | var name: String, 95 | // 96 | var profile: Any, 97 | //标签 98 | var tags: List, 99 | //时间戳 100 | var timestamp: Long, 101 | 102 | ) : Serializable 103 | 104 | /** 105 | * 动物接口 106 | */ 107 | class AnimalController(private var client: ApiClient) { 108 | 109 | 110 | var addCatResultType = object : TypeToken() {}.type 111 | 112 | /** 113 | * 响应long 114 | * 115 | */ 116 | suspend fun addCat(body: Cat): Result { 117 | return client.post("/animal/cat/add", addCatResultType, body) 118 | } 119 | 120 | 121 | var addDogResultType = object : TypeToken() {}.type 122 | 123 | /** 124 | * 路径+Body 125 | * 126 | */ 127 | suspend fun addDog(kind: String, body: Dog): Result { 128 | return client.post("/animal/add/%s/dog".format(kind), addDogResultType, body) 129 | } 130 | 131 | 132 | var catsResultType = object : TypeToken>() {}.type 133 | 134 | /** 135 | * 响应list 136 | * 137 | */ 138 | suspend fun cats(): Result> { 139 | val params = HashMap() 140 | return client.get("/animal/cats", catsResultType, params) 141 | } 142 | 143 | 144 | var dogResultType = object : TypeToken() {}.type 145 | 146 | /** 147 | * 响应对象 148 | * 149 | */ 150 | suspend fun dog(id: Int): Result { 151 | val params = HashMap() 152 | return client.get("/animal/dog/%s".format(id), dogResultType, params) 153 | } 154 | 155 | 156 | var mapCatsResultType = object : TypeToken>() {}.type 157 | 158 | /** 159 | * 响应map 160 | * 其他 161 | */ 162 | suspend fun mapCats(): Result> { 163 | val params = HashMap() 164 | return client.get("/animal/map/cats", mapCatsResultType, params) 165 | } 166 | 167 | 168 | var mapDogResultType = object : TypeToken() {}.type 169 | 170 | /** 171 | * 响应Map泛型 172 | * 173 | */ 174 | suspend fun mapDog(): Result { 175 | val params = HashMap() 176 | return client.get("/animal/map/dogs", mapDogResultType, params) 177 | } 178 | 179 | 180 | var pageDogsResultType = object : TypeToken() {}.type 181 | 182 | /** 183 | * 响应Array泛型 184 | * 185 | */ 186 | suspend fun pageDogs(): Result { 187 | val params = HashMap() 188 | return client.get("/animal/page/dogs", pageDogsResultType, params) 189 | } 190 | 191 | 192 | var searchDogResultType = object : TypeToken() {}.type 193 | 194 | /** 195 | * 路径+Query 196 | * 197 | */ 198 | suspend fun searchDog(color: String, kind: String, name: String): Result { 199 | val params = HashMap() 200 | params["color"] = color 201 | params["kind"] = kind 202 | params["name"] = name 203 | 204 | return client.get("/animal/query/%s/dog".format(kind), searchDogResultType, params) 205 | } 206 | 207 | 208 | } 209 | 210 | /** 211 | * 其他接口 212 | */ 213 | class OtherApi(private var client: ApiClient) { 214 | 215 | 216 | var getResultType = object : TypeToken() {}.type 217 | 218 | /** 219 | * 响应基础类型 220 | * 221 | */ 222 | suspend fun get(): Result { 223 | val params = HashMap() 224 | return client.get("/other/long", getResultType, params) 225 | } 226 | 227 | 228 | var getContentResultType = object : TypeToken() {}.type 229 | 230 | /** 231 | * 响应基础类型 232 | * 233 | */ 234 | suspend fun getContent(): Result { 235 | val params = HashMap() 236 | return client.get("/other/string", getContentResultType, params) 237 | } 238 | 239 | 240 | var multiplePathVariableResultType = object : TypeToken>() {}.type 241 | 242 | /** 243 | * 多路径参数 244 | * 245 | */ 246 | suspend fun multiplePathVariable(id: String, type: String): Result> { 247 | val params = HashMap() 248 | return client.get( 249 | "/other/boolean/%s/%s".format(type, id), multiplePathVariableResultType, params 250 | ) 251 | } 252 | 253 | 254 | } 255 | 256 | /** 257 | * 用户接口 258 | */ 259 | class UserApi(private var client: ApiClient) { 260 | 261 | 262 | var addResultType = object : TypeToken() {}.type 263 | 264 | /** 265 | * 添加用户 266 | * 267 | */ 268 | suspend fun add(body: User): Result { 269 | return client.post("/user/add", addResultType, body) 270 | } 271 | 272 | 273 | var deleteResultType = object : TypeToken() {}.type 274 | 275 | /** 276 | * 删除用户 277 | * 278 | */ 279 | suspend fun delete(id: Long): Result { 280 | val params = HashMap() 281 | return client.delete("/user/delete/%s".format(id), deleteResultType, params) 282 | } 283 | 284 | 285 | var editResultType = object : TypeToken() {}.type 286 | 287 | /** 288 | * 修改用户 289 | * 290 | */ 291 | suspend fun edit(id: Long, body: User): Result { 292 | return client.put("/user/edit/%s".format(id), editResultType, body) 293 | } 294 | 295 | 296 | var edit_1ResultType = object : TypeToken() {}.type 297 | 298 | /** 299 | * 查询用户 300 | * 301 | */ 302 | suspend fun edit_1(id: Long, keyword: String): Result { 303 | val params = HashMap() 304 | params["id"] = id 305 | params["keyword"] = keyword 306 | 307 | return client.get("/user/detail/%s".format(id), edit_1ResultType, params) 308 | } 309 | 310 | 311 | var listResultType = object : TypeToken>() {}.type 312 | 313 | /** 314 | * 用户列表 315 | * 316 | */ 317 | suspend fun list(): Result> { 318 | val params = HashMap() 319 | return client.get("/user/list", listResultType, params) 320 | } 321 | 322 | 323 | var pageResultType = object : TypeToken>() {}.type 324 | 325 | /** 326 | * 用户分页 327 | * 328 | */ 329 | suspend fun page(page: Int, size: Int): Result> { 330 | val params = HashMap() 331 | params["page"] = page 332 | params["size"] = size 333 | 334 | return client.get("/user/page", pageResultType, params) 335 | } 336 | 337 | 338 | } 339 | -------------------------------------------------------------------------------- /_example/kotlin/Client.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.demo; 4 | 5 | import com.google.gson.Gson 6 | import okhttp3.MediaType 7 | import okhttp3.RequestBody 8 | import okhttp3.ResponseBody 9 | import retrofit2.Response 10 | import retrofit2.Retrofit 11 | import retrofit2.http.Body 12 | import retrofit2.http.DELETE 13 | import retrofit2.http.GET 14 | import retrofit2.http.POST 15 | import retrofit2.http.PUT 16 | import retrofit2.http.QueryMap 17 | import retrofit2.http.Url 18 | import java.lang.reflect.Type 19 | 20 | /** 21 | * TODO You need to implement the current interface, which can refer to Retorfit or OkHttp 22 | */ 23 | class ApiClient { 24 | 25 | suspend fun get(path: String, retType: Type, params: Map? = emptyMap()): Result { 26 | return toResult(defaultClient.get(path, params), retType) 27 | } 28 | 29 | suspend fun

post(path: String, retType: Type, params: P?): Result { 30 | return toResult(defaultClient.post(path, toBody(params)), retType) 31 | } 32 | 33 | suspend fun

put(path: String, retType: Type, params: P?): Result { 34 | return toResult(defaultClient.put(path, toBody(params)), retType) 35 | } 36 | 37 | suspend fun

delete(path: String, retType: Type, params: P?): Result { 38 | return toResult(defaultClient.delete(path, toBody(params)), retType) 39 | } 40 | 41 | private fun toResult(resp: Response, retType: Type): Result { 42 | return if (resp.isSuccessful) { 43 | Result.success(defaultGson.fromJson(resp.body()!!.charStream(), retType)) 44 | } else { 45 | Result.failure(Exception("Request failed with status code: ${resp.code()}")) 46 | } 47 | } 48 | 49 | private fun

toBody(params: P?): RequestBody? { 50 | return RequestBody.create(MediaType.parse("application/json"), defaultGson.toJson(params)) 51 | } 52 | } 53 | 54 | 55 | var defaultGson = Gson() 56 | private var defaultClient = 57 | Retrofit.Builder().baseUrl("http://localhost:8083").build().create(RetrofitClient::class.java) 58 | 59 | interface RetrofitClient { 60 | 61 | @GET 62 | suspend fun get(@Url path: String, @QueryMap(encoded = true) params: Map?): Response 63 | 64 | @POST 65 | suspend fun post(@Url path: String, @Body params: RequestBody?): Response 66 | 67 | @PUT 68 | suspend fun put(@Url path: String, @Body params: RequestBody?): Response 69 | 70 | @DELETE 71 | suspend fun delete(@Url path: String, @Body params: RequestBody?): Response 72 | } 73 | 74 | -------------------------------------------------------------------------------- /_example/python/client.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar, Optional, Any, Type 2 | import requests 3 | from dataclasses import asdict 4 | 5 | T = TypeVar('T') 6 | P = TypeVar('P') 7 | 8 | 9 | class ApiClient: 10 | 11 | def get(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 12 | ... 13 | 14 | def post(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 15 | ... 16 | 17 | def put(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 18 | ... 19 | 20 | def delete(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 21 | ... 22 | 23 | 24 | class HttClient(ApiClient): 25 | BASE_URL = "http://localhost:8083/v3/api-docs" 26 | 27 | def get(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 28 | url = f"{self.BASE_URL}{path}" 29 | return requests.get(url=url, params=params).json() 30 | 31 | def post(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 32 | url = f"{self.BASE_URL}{path}" 33 | return requests.post(url=url, json=asdict(params)).json() 34 | 35 | def put(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 36 | url = f"{self.BASE_URL}{path}" 37 | return requests.put(url=url, json=asdict(params)).json() 38 | 39 | def delete(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 40 | url = f"{self.BASE_URL}{path}" 41 | return requests.delete(url=url, json=asdict(params)).json() 42 | -------------------------------------------------------------------------------- /_example/python/demo.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Generic, TypeVar, Optional, Any, Type, List, Dict 3 | 4 | from client import ApiClient 5 | 6 | T = TypeVar('T') 7 | A = TypeVar('A') 8 | B = TypeVar('B') 9 | C = TypeVar('C') 10 | P = TypeVar('P') 11 | 12 | 13 | @dataclass 14 | class Dog: 15 | """ 16 | Dog 狗 17 | """ 18 | color: str = None 19 | id: int = None 20 | nickname: str = None 21 | price: int = None 22 | 23 | 24 | @dataclass 25 | class PageData(Generic[T]): 26 | """ 27 | PageData 28 | """ 29 | entities: List[T] = None 30 | page: int = None 31 | size: int = None 32 | total: int = None 33 | 34 | 35 | @dataclass 36 | class ApiResult(Generic[T]): 37 | """ 38 | ApiResult 39 | """ 40 | code: str = None 41 | data: T = None 42 | err: str = None 43 | msg: str = None 44 | 45 | 46 | @dataclass 47 | class Cat: 48 | """ 49 | Cat 猫 50 | """ 51 | foods: List[str] = None 52 | nickname: str = None 53 | 54 | 55 | @dataclass 56 | class MapResult(Generic[T]): 57 | """ 58 | MapResult 59 | """ 60 | code: str = None 61 | data: Dict[str, T] = None 62 | 63 | 64 | @dataclass 65 | class User: 66 | """ 67 | User 用户 68 | """ 69 | age: int = None 70 | birthday: str = None 71 | cats: List[Cat] = None 72 | enable: bool = None 73 | gods: Dict[str, Dog] = None 74 | id: int = None 75 | kv: Dict[str, str] = None 76 | name: str = None 77 | profile: JsonNode = None 78 | tags: List[str] = None 79 | timestamp: int = None 80 | 81 | 82 | class AnimalController: 83 | """ 84 | 动物接口 85 | """ 86 | client: ApiClient = None 87 | 88 | def __init__(self, client: ApiClient): 89 | self.client = client 90 | 91 | def addCat(self, body: Cat) -> Optional[int]: 92 | """ 93 | 响应long 94 | """ 95 | 96 | return self.client.post("/animal/cat/add", int, body) 97 | 98 | def addDog(self, kind: str, body: Dog) -> Optional[Dog]: 99 | """ 100 | 路径+Body 101 | """ 102 | 103 | return self.client.post("/animal/add/%s/dog".format(kind), Dog, body) 104 | 105 | def cats(self, ) -> Optional[List[Cat]]: 106 | """ 107 | 响应list 108 | """ 109 | params = {} 110 | return self.client.get("/animal/cats", List[Cat], params) 111 | 112 | def dog(self, id: int) -> Optional[Dog]: 113 | """ 114 | 响应对象 115 | """ 116 | params = {} 117 | return self.client.get("/animal/dog/%s".format(id), Dog, params) 118 | 119 | def mapCats(self, ) -> Optional[Dict[str, Cat]]: 120 | """ 121 | 响应map 122 | """ 123 | params = {} 124 | return self.client.get("/animal/map/cats", Dict[str, Cat], params) 125 | 126 | def mapDog(self, ) -> Optional[Dog]: 127 | """ 128 | 响应Map泛型 129 | """ 130 | params = {} 131 | return self.client.get("/animal/map/dogs", Dog, params) 132 | 133 | def pageDogs(self, ) -> Optional[Dog]: 134 | """ 135 | 响应Array泛型 136 | """ 137 | params = {} 138 | return self.client.get("/animal/page/dogs", Dog, params) 139 | 140 | def searchDog(self, color: str, kind: str, name: str) -> Optional[Dog]: 141 | """ 142 | 路径+Query 143 | """ 144 | 145 | params = {"color": color, "kind": kind, "name": name, } 146 | return self.client.get("/animal/query/%s/dog".format(kind), Dog, params) 147 | 148 | 149 | class OtherApi: 150 | """ 151 | 其他接口 152 | """ 153 | client: ApiClient = None 154 | 155 | def __init__(self, client: ApiClient): 156 | self.client = client 157 | 158 | def get(self, ) -> Optional[str]: 159 | """ 160 | 响应基础类型 161 | """ 162 | params = {} 163 | return self.client.get("/other/long", str, params) 164 | 165 | def getContent(self, ) -> Optional[int]: 166 | """ 167 | 响应基础类型 168 | """ 169 | params = {} 170 | return self.client.get("/other/string", int, params) 171 | 172 | def multiplePathVariable(self, id: str, type: str) -> Optional[List[Dog]]: 173 | """ 174 | 多路径参数 175 | """ 176 | params = {} 177 | return self.client.get("/other/boolean/%s/%s".format(type, id), List[Dog], params) 178 | 179 | 180 | class UserApi: 181 | """ 182 | 用户接口 183 | """ 184 | client: ApiClient = None 185 | 186 | def __init__(self, client: ApiClient): 187 | self.client = client 188 | 189 | def add(self, body: User) -> Optional[int]: 190 | """ 191 | 添加用户 192 | """ 193 | 194 | return self.client.post("/user/add", int, body) 195 | 196 | def delete(self, id: int) -> Optional[bool]: 197 | """ 198 | 删除用户 199 | """ 200 | params = {} 201 | return self.client.delete("/user/delete/%s".format(id), bool, params) 202 | 203 | def edit(self, id: int, body: User) -> Optional[int]: 204 | """ 205 | 修改用户 206 | """ 207 | 208 | return self.client.put("/user/edit/%s".format(id), int, body) 209 | 210 | def edit_1(self, id: int, keyword: str) -> Optional[User]: 211 | """ 212 | 查询用户 213 | """ 214 | 215 | params = {"id": id, "keyword": keyword, } 216 | return self.client.get("/user/detail/%s".format(id), User, params) 217 | 218 | def list(self, ) -> Optional[List[User]]: 219 | """ 220 | 用户列表 221 | """ 222 | params = {} 223 | return self.client.get("/user/list", List[User], params) 224 | 225 | def page(self, page: int, size: int) -> Optional[PageData[User]]: 226 | """ 227 | 用户分页 228 | """ 229 | 230 | params = {"page": page, "size": size, } 231 | return self.client.get("/user/page", PageData[User], params) 232 | -------------------------------------------------------------------------------- /_example/rust/api.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use std::collections::HashMap; 4 | use std::io::Error; 5 | use std::iter::Map; 6 | use crate::Client::ApiClient; 7 | use crate::Model::*; 8 | 9 | 10 | 11 | 12 | /** 13 | * 动物接口 14 | */ 15 | pub struct AnimalController { 16 | client: dyn ApiClient, 17 | } 18 | 19 | impl AnimalController { 20 | 21 | 22 | 23 | /** 24 | * 响应long 25 | * 26 | */ 27 | async fn addCat(&self, body: Cat) -> Result { 28 | 29 | self.client.post(String::from("/animal/cat/add"),body) 30 | } 31 | 32 | 33 | 34 | /** 35 | * 路径+Body 36 | * 37 | */ 38 | async fn addDog(&self, kind: String,body: Dog) -> Result { 39 | 40 | self.client.post(String::from(format!("/animal/add/${kind}/dog",kind)),body) 41 | } 42 | 43 | 44 | 45 | /** 46 | * 响应list 47 | * 48 | */ 49 | async fn cats(&self, ) -> Result,Error> { 50 | let mut params = HashMap::new(); 51 | self.client.get(String::from("/animal/cats"),params) 52 | } 53 | 54 | 55 | 56 | /** 57 | * 响应对象 58 | * 59 | */ 60 | async fn dog(&self, id: usize) -> Result { 61 | let mut params = HashMap::new(); 62 | self.client.get(String::from(format!("/animal/dog/${id}",id)),params) 63 | } 64 | 65 | 66 | 67 | /** 68 | * 响应map 69 | * 其他 70 | */ 71 | async fn mapCats(&self, ) -> Result,Error> { 72 | let mut params = HashMap::new(); 73 | self.client.get(String::from("/animal/map/cats"),params) 74 | } 75 | 76 | 77 | 78 | /** 79 | * 响应Map泛型 80 | * 81 | */ 82 | async fn mapDog(&self, ) -> Result { 83 | let mut params = HashMap::new(); 84 | self.client.get(String::from("/animal/map/dogs"),params) 85 | } 86 | 87 | 88 | 89 | /** 90 | * 响应Array泛型 91 | * 92 | */ 93 | async fn pageDogs(&self, ) -> Result { 94 | let mut params = HashMap::new(); 95 | self.client.get(String::from("/animal/page/dogs"),params) 96 | } 97 | 98 | 99 | 100 | /** 101 | * 路径+Query 102 | * 103 | */ 104 | async fn searchDog(&self, color: String,kind: String,name: String) -> Result { 105 | let mut params = HashMap::new(); 106 | params.insert("color",color.to_string()); 107 | params.insert("kind",kind.to_string()); 108 | params.insert("name",name.to_string()); 109 | 110 | self.client.get(String::from(format!("/animal/query/${kind}/dog",kind)),params) 111 | } 112 | 113 | 114 | } 115 | 116 | 117 | 118 | /** 119 | * 其他接口 120 | */ 121 | pub struct OtherApi { 122 | client: dyn ApiClient, 123 | } 124 | 125 | impl OtherApi { 126 | 127 | 128 | 129 | /** 130 | * 响应基础类型 131 | * 132 | */ 133 | async fn get(&self, ) -> Result { 134 | let mut params = HashMap::new(); 135 | self.client.get(String::from("/other/long"),params) 136 | } 137 | 138 | 139 | 140 | /** 141 | * 响应基础类型 142 | * 143 | */ 144 | async fn getContent(&self, ) -> Result { 145 | let mut params = HashMap::new(); 146 | self.client.get(String::from("/other/string"),params) 147 | } 148 | 149 | 150 | 151 | /** 152 | * 多路径参数 153 | * 154 | */ 155 | async fn multiplePathVariable(&self, id: String,type: String) -> Result,Error> { 156 | let mut params = HashMap::new(); 157 | self.client.get(String::from(format!("/other/boolean/${type}/${id}",type,id)),params) 158 | } 159 | 160 | 161 | } 162 | 163 | 164 | 165 | /** 166 | * 用户接口 167 | */ 168 | pub struct UserApi { 169 | client: dyn ApiClient, 170 | } 171 | 172 | impl UserApi { 173 | 174 | 175 | 176 | /** 177 | * 添加用户 178 | * 179 | */ 180 | async fn add(&self, body: User) -> Result { 181 | 182 | self.client.post(String::from("/user/add"),body) 183 | } 184 | 185 | 186 | 187 | /** 188 | * 删除用户 189 | * 190 | */ 191 | async fn delete(&self, id: u64) -> Result { 192 | let mut params = HashMap::new(); 193 | self.client.delete(String::from(format!("/user/delete/${id}",id)),params) 194 | } 195 | 196 | 197 | 198 | /** 199 | * 修改用户 200 | * 201 | */ 202 | async fn edit(&self, id: u64,body: User) -> Result { 203 | 204 | self.client.put(String::from(format!("/user/edit/${id}",id)),body) 205 | } 206 | 207 | 208 | 209 | /** 210 | * 查询用户 211 | * 212 | */ 213 | async fn edit_1(&self, id: u64,keyword: String) -> Result { 214 | let mut params = HashMap::new(); 215 | params.insert("id",id.to_string()); 216 | params.insert("keyword",keyword.to_string()); 217 | 218 | self.client.get(String::from(format!("/user/detail/${id}",id)),params) 219 | } 220 | 221 | 222 | 223 | /** 224 | * 用户列表 225 | * 226 | */ 227 | async fn list(&self, ) -> Result { 228 | let mut params = HashMap::new(); 229 | self.client.get(String::from("/user/list"),params) 230 | } 231 | 232 | 233 | 234 | /** 235 | * 用户分页 236 | * 237 | */ 238 | async fn page(&self, page: usize,size: usize) -> Result { 239 | let mut params = HashMap::new(); 240 | params.insert("page",page.to_string()); 241 | params.insert("size",size.to_string()); 242 | 243 | self.client.get(String::from("/user/page"),params) 244 | } 245 | 246 | 247 | } 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /_example/rust/client.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use std::any::Any; 4 | use std::collections::HashMap; 5 | use std::io::Error; 6 | 7 | pub trait ApiClient { 8 | fn get(&self, path: String, params: HashMap) -> Result; 9 | fn post(&self, path: String, params: P) -> Result; 10 | fn put(&self, path: String, params: P) -> Result; 11 | fn delete(&self, path: String, params: P) -> Result; 12 | } -------------------------------------------------------------------------------- /_example/rust/model.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use std::iter::Map; 5 | 6 | 7 | 8 | 9 | 10 | /** 11 | * 接口标准响应 12 | * 13 | */ 14 | #[derive(Debug, Clone)] 15 | pub struct ApiResultBoolean { 16 | /** 17 | * 业务响应码 18 | * 19 | */ 20 | pub code: Option, 21 | /** 22 | * 数据 23 | * 24 | */ 25 | pub data: Option, 26 | /** 27 | * 业务错误信息 28 | * 29 | */ 30 | pub err: Option, 31 | /** 32 | * 业务响应信息 33 | * 34 | */ 35 | pub msg: Option, 36 | 37 | } 38 | 39 | 40 | 41 | 42 | /** 43 | * 接口标准响应 44 | * 45 | */ 46 | #[derive(Debug, Clone)] 47 | pub struct ApiResultLong { 48 | /** 49 | * 业务响应码 50 | * 51 | */ 52 | pub code: Option, 53 | /** 54 | * 数据 55 | * 56 | */ 57 | pub data: Option, 58 | /** 59 | * 业务错误信息 60 | * 61 | */ 62 | pub err: Option, 63 | /** 64 | * 业务响应信息 65 | * 66 | */ 67 | pub msg: Option, 68 | 69 | } 70 | 71 | 72 | 73 | 74 | /** 75 | * 猫 76 | * 77 | */ 78 | #[derive(Debug, Clone)] 79 | pub struct Cat { 80 | /** 81 | * 食物 82 | * 83 | */ 84 | pub foods: Option>, 85 | /** 86 | * 昵称 87 | * 88 | */ 89 | pub nickname: Option, 90 | 91 | } 92 | 93 | 94 | 95 | 96 | /** 97 | * 狗 98 | * 99 | */ 100 | #[derive(Debug, Clone)] 101 | pub struct Dog { 102 | /** 103 | * 颜色 104 | * 105 | */ 106 | pub color: Option, 107 | /** 108 | * ID 109 | * 110 | */ 111 | pub id: Option, 112 | /** 113 | * 昵称 114 | * 115 | */ 116 | pub nickname: Option, 117 | /** 118 | * 售价 119 | * 120 | */ 121 | pub price: Option, 122 | 123 | } 124 | 125 | 126 | 127 | 128 | /** 129 | * 接口标准响应 130 | * 131 | */ 132 | #[derive(Debug, Clone)] 133 | pub struct ApiResultListUser { 134 | /** 135 | * 业务响应码 136 | * 137 | */ 138 | pub code: Option, 139 | /** 140 | * 数据 141 | * 142 | */ 143 | pub data: Option>, 144 | /** 145 | * 业务错误信息 146 | * 147 | */ 148 | pub err: Option, 149 | /** 150 | * 业务响应信息 151 | * 152 | */ 153 | pub msg: Option, 154 | 155 | } 156 | 157 | 158 | 159 | 160 | /** 161 | * 接口标准响应 162 | * 163 | */ 164 | #[derive(Debug, Clone)] 165 | pub struct ApiResultPageDataUser { 166 | /** 167 | * 业务响应码 168 | * 169 | */ 170 | pub code: Option, 171 | /** 172 | * 173 | * 174 | */ 175 | pub data: Option, 176 | /** 177 | * 业务错误信息 178 | * 179 | */ 180 | pub err: Option, 181 | /** 182 | * 业务响应信息 183 | * 184 | */ 185 | pub msg: Option, 186 | 187 | } 188 | 189 | 190 | 191 | 192 | /** 193 | * 接口标准响应 194 | * 195 | */ 196 | #[derive(Debug, Clone)] 197 | pub struct ApiResultUser { 198 | /** 199 | * 业务响应码 200 | * 201 | */ 202 | pub code: Option, 203 | /** 204 | * 205 | * 206 | */ 207 | pub data: Option, 208 | /** 209 | * 业务错误信息 210 | * 211 | */ 212 | pub err: Option, 213 | /** 214 | * 业务响应信息 215 | * 216 | */ 217 | pub msg: Option, 218 | 219 | } 220 | 221 | 222 | 223 | 224 | /** 225 | * 接口标准响应 226 | * 227 | */ 228 | #[derive(Debug, Clone)] 229 | pub struct MapResultDog { 230 | /** 231 | * 响应码 232 | * 233 | */ 234 | pub code: Option, 235 | /** 236 | * 数据 237 | * 238 | */ 239 | pub data: Option>, 240 | 241 | } 242 | 243 | 244 | 245 | 246 | /** 247 | * 标准分页数据 248 | * 249 | */ 250 | #[derive(Debug, Clone)] 251 | pub struct PageDataDog { 252 | /** 253 | * 数据 254 | * 255 | */ 256 | pub entities: Option>, 257 | /** 258 | * 页码 259 | * 260 | */ 261 | pub page: Option, 262 | /** 263 | * 条数 264 | * 265 | */ 266 | pub size: Option, 267 | /** 268 | * 总数 269 | * 270 | */ 271 | pub total: Option, 272 | 273 | } 274 | 275 | 276 | 277 | 278 | /** 279 | * 标准分页数据 280 | * 281 | */ 282 | #[derive(Debug, Clone)] 283 | pub struct PageDataUser { 284 | /** 285 | * 数据 286 | * 287 | */ 288 | pub entities: Option>, 289 | /** 290 | * 页码 291 | * 292 | */ 293 | pub page: Option, 294 | /** 295 | * 条数 296 | * 297 | */ 298 | pub size: Option, 299 | /** 300 | * 总数 301 | * 302 | */ 303 | pub total: Option, 304 | 305 | } 306 | 307 | 308 | 309 | 310 | /** 311 | * 用户 312 | * 313 | */ 314 | #[derive(Debug, Clone)] 315 | pub struct User { 316 | /** 317 | * 年龄 318 | * 319 | */ 320 | pub age: Option, 321 | /** 322 | * 生日 323 | * 324 | */ 325 | pub birthday: Option, 326 | /** 327 | * 集合 328 | * 329 | */ 330 | pub cats: Option>, 331 | /** 332 | * 是否启用 333 | * 334 | */ 335 | pub enable: Option, 336 | /** 337 | * 引用map 338 | * 339 | */ 340 | pub gods: Option>, 341 | /** 342 | * ID 343 | * 344 | */ 345 | pub id: Option, 346 | /** 347 | * 基础map 348 | * 349 | */ 350 | pub kv: Option>, 351 | /** 352 | * 名称 353 | * 354 | */ 355 | pub name: Option, 356 | /** 357 | * 358 | * 359 | */ 360 | pub profile: Option, 361 | /** 362 | * 标签 363 | * 364 | */ 365 | pub tags: Option>, 366 | /** 367 | * 时间戳 368 | * 369 | */ 370 | pub timestamp: Option, 371 | 372 | } 373 | 374 | 375 | 376 | 377 | -------------------------------------------------------------------------------- /_example/swift/Api.swift: -------------------------------------------------------------------------------- 1 | 2 | import Alamofire 3 | import Foundation 4 | 5 | /** 6 | * 接口标准响应 7 | */ 8 | public struct ApiResultBoolean: Codable { 9 | // 业务响应码 10 | var code: String? 11 | // 数据 12 | var data: Bool? 13 | // 业务错误信息 14 | var err: String? 15 | // 业务响应信息 16 | var msg: String? 17 | } 18 | 19 | /** 20 | * 接口标准响应 21 | */ 22 | public struct ApiResultListUser: Codable { 23 | // 业务响应码 24 | var code: String? 25 | // 数据 26 | var data: [User]? 27 | // 业务错误信息 28 | var err: String? 29 | // 业务响应信息 30 | var msg: String? 31 | } 32 | 33 | /** 34 | * 接口标准响应 35 | */ 36 | public struct ApiResultLong: Codable { 37 | // 业务响应码 38 | var code: String? 39 | // 数据 40 | var data: Int64? 41 | // 业务错误信息 42 | var err: String? 43 | // 业务响应信息 44 | var msg: String? 45 | } 46 | 47 | /** 48 | * 接口标准响应 49 | */ 50 | public struct ApiResultPageDataUser: Codable { 51 | // 业务响应码 52 | var code: String? 53 | // 54 | var data: PageDataUser? 55 | // 业务错误信息 56 | var err: String? 57 | // 业务响应信息 58 | var msg: String? 59 | } 60 | 61 | /** 62 | * 接口标准响应 63 | */ 64 | public struct ApiResultUser: Codable { 65 | // 业务响应码 66 | var code: String? 67 | // 68 | var data: User? 69 | // 业务错误信息 70 | var err: String? 71 | // 业务响应信息 72 | var msg: String? 73 | } 74 | 75 | /** 76 | * 猫 77 | */ 78 | public struct Cat: Codable { 79 | // 食物 80 | var foods: [String]? 81 | // 昵称 82 | var nickname: String? 83 | } 84 | 85 | /** 86 | * 狗 87 | */ 88 | public struct Dog: Codable { 89 | // 颜色 90 | var color: String? 91 | // ID 92 | var id: Int64? 93 | // 昵称 94 | var nickname: String? 95 | // 售价 96 | var price: Int? 97 | } 98 | 99 | /** 100 | * 接口标准响应 101 | */ 102 | public struct MapResultDog: Codable { 103 | // 响应码 104 | var code: String? 105 | // 数据 106 | var data: [String: Dog]? 107 | } 108 | 109 | /** 110 | * 标准分页数据 111 | */ 112 | public struct PageDataDog: Codable { 113 | // 数据 114 | var entities: [Dog]? 115 | // 页码 116 | var page: Int? 117 | // 条数 118 | var size: Int? 119 | // 总数 120 | var total: Int64? 121 | } 122 | 123 | /** 124 | * 标准分页数据 125 | */ 126 | public struct PageDataUser: Codable { 127 | // 数据 128 | var entities: [User]? 129 | // 页码 130 | var page: Int? 131 | // 条数 132 | var size: Int? 133 | // 总数 134 | var total: Int64? 135 | } 136 | 137 | /** 138 | * 用户 139 | */ 140 | public struct User: Codable { 141 | // 年龄 142 | var age: Int? 143 | // 生日 144 | var birthday: String? 145 | // 集合 146 | var cats: [Cat]? 147 | // 是否启用 148 | var enable: Bool? 149 | // 引用map 150 | var gods: [String: Dog]? 151 | // ID 152 | var id: Int64? 153 | // 基础map 154 | var kv: [String: String]? 155 | // 名称 156 | var name: String? 157 | // 158 | var profile: any? 159 | // 标签 160 | var tags: [String]? 161 | // 时间戳 162 | var timestamp: Int64? 163 | } 164 | 165 | /** 166 | * 动物接口 167 | */ 168 | class AnimalController { 169 | var client: ApiClient 170 | 171 | init(client: ApiClient) { 172 | self.client = client 173 | } 174 | 175 | /** 176 | * 响应long 177 | */ 178 | func addCat(body: Cat) async throws -> Int { 179 | return try await client.post("/animal/cat/add", body, Int.self) 180 | } 181 | 182 | /** 183 | * 路径+Body 184 | */ 185 | func addDog(kind: String, body: Dog) async throws -> Dog { 186 | return try await client.post(String(format: "/animal/add/%@/dog", kind), body, Dog.self) 187 | } 188 | 189 | /** 190 | * 响应list 191 | */ 192 | func cats() async throws -> [Cat] { 193 | var params: [String: Any?] = [:] 194 | return try await client.get("/animal/cats", params, [Cat].self) 195 | } 196 | 197 | /** 198 | * 响应对象 199 | */ 200 | func dog(id: Int) async throws -> Dog { 201 | var params: [String: Any?] = [:] 202 | return try await client.get(String(format: "/animal/dog/%@", id), params, Dog.self) 203 | } 204 | 205 | /** 206 | * 响应map 207 | */ 208 | func mapCats() async throws -> [String: Cat] { 209 | var params: [String: Any?] = [:] 210 | return try await client.get("/animal/map/cats", params, [String: Cat].self) 211 | } 212 | 213 | /** 214 | * 响应Map泛型 215 | */ 216 | func mapDog() async throws -> MapResultDog { 217 | var params: [String: Any?] = [:] 218 | return try await client.get("/animal/map/dogs", params, MapResultDog.self) 219 | } 220 | 221 | /** 222 | * 响应Array泛型 223 | */ 224 | func pageDogs() async throws -> PageDataDog { 225 | var params: [String: Any?] = [:] 226 | return try await client.get("/animal/page/dogs", params, PageDataDog.self) 227 | } 228 | 229 | /** 230 | * 路径+Query 231 | */ 232 | func searchDog(color: String, kind: String, name: String) async throws -> Dog { 233 | var params: [String: Any?] = [:] 234 | params["color"] = color 235 | params["name"] = name 236 | 237 | return try await client.get(String(format: "/animal/query/%@/dog", kind), params, Dog.self) 238 | } 239 | } 240 | 241 | /** 242 | * 其他接口 243 | */ 244 | class OtherApi { 245 | var client: ApiClient 246 | 247 | init(client: ApiClient) { 248 | self.client = client 249 | } 250 | 251 | /** 252 | * 响应基础类型 253 | */ 254 | func get() async throws -> String { 255 | var params: [String: Any?] = [:] 256 | return try await client.get("/other/long", params, String.self) 257 | } 258 | 259 | /** 260 | * 响应基础类型 261 | */ 262 | func getContent() async throws -> Int { 263 | var params: [String: Any?] = [:] 264 | return try await client.get("/other/string", params, Int.self) 265 | } 266 | 267 | /** 268 | * 多路径参数 269 | */ 270 | func multiplePathVariable(id: String, type: String) async throws -> [Dog] { 271 | var params: [String: Any?] = [:] 272 | return try await client.get(String(format: "/other/boolean/%@/%@", type, id), params, [Dog].self) 273 | } 274 | } 275 | 276 | /** 277 | * 用户接口 278 | */ 279 | class UserApi { 280 | var client: ApiClient 281 | 282 | init(client: ApiClient) { 283 | self.client = client 284 | } 285 | 286 | /** 287 | * 添加用户 288 | */ 289 | func add(body: User) async throws -> ApiResultLong { 290 | return try await client.post("/user/add", body, ApiResultLong.self) 291 | } 292 | 293 | /** 294 | * 删除用户 295 | */ 296 | func delete(id: Int64) async throws -> ApiResultBoolean { 297 | var params: [String: Any?] = [:] 298 | return try await client.delete(String(format: "/user/delete/%@", id), params, ApiResultBoolean.self) 299 | } 300 | 301 | /** 302 | * 修改用户 303 | */ 304 | func edit(id: Int64, body: User) async throws -> ApiResultLong { 305 | return try await client.put(String(format: "/user/edit/%@", id), body, ApiResultLong.self) 306 | } 307 | 308 | /** 309 | * 查询用户 310 | */ 311 | func edit_1(id: Int64, keyword: String) async throws -> ApiResultUser { 312 | var params: [String: Any?] = [:] 313 | params["keyword"] = keyword 314 | 315 | return try await client.get(String(format: "/user/detail/%@", id), params, ApiResultUser.self) 316 | } 317 | 318 | /** 319 | * 用户列表 320 | */ 321 | func list() async throws -> ApiResultListUser { 322 | var params: [String: Any?] = [:] 323 | return try await client.get("/user/list", params, ApiResultListUser.self) 324 | } 325 | 326 | /** 327 | * 用户分页 328 | */ 329 | func page(page: Int, size: Int) async throws -> ApiResultPageDataUser { 330 | var params: [String: Any?] = [:] 331 | params["page"] = page 332 | params["size"] = size 333 | 334 | return try await client.get("/user/page", params, ApiResultPageDataUser.self) 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /_example/swift/Client.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | import Alamofire 4 | import Foundation 5 | 6 | /** 7 | * TODO You need to implement the current protocol, which can refer to Alamofire or URLSession 8 | */ 9 | protocol ApiClient { 10 | func get(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 11 | 12 | func post(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 13 | 14 | func put(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 15 | 16 | func delete(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 17 | } 18 | 19 | var BASE_URL = "http://localhost:8083/v3/api-docs" 20 | 21 | struct AlamofireApiClient: ApiClient { 22 | var headers: HTTPHeaders? 23 | 24 | func get(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 25 | let reqParams: Parameters? = params as? [String: Any] 26 | return try await AF.request("\(BASE_URL)\(path)", 27 | method: .get, 28 | parameters: reqParams, 29 | encoding: URLEncoding.default, 30 | headers: headers) 31 | .serializingDecodable(resultType.self).value 32 | } 33 | 34 | func post(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 35 | let reqParams: Parameters? = params as? Dictionary 36 | return try await AF.request("\(BASE_URL)\(path)", 37 | method: .post, 38 | parameters: reqParams, 39 | encoding: JSONEncoding.default, 40 | headers: headers) 41 | .serializingDecodable(resultType.self).value 42 | } 43 | 44 | func put(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 45 | let reqParams: Parameters? = params as? Dictionary 46 | return try await AF.request("\(BASE_URL)\(path)", 47 | method: .put, 48 | parameters: reqParams, 49 | encoding: JSONEncoding.default, 50 | headers: headers) 51 | .serializingDecodable(resultType.self).value 52 | } 53 | 54 | func delete(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 55 | let reqParams: Parameters? = params as? Dictionary 56 | return try await AF.request("\(BASE_URL)\(path)", 57 | method: .delete, 58 | parameters: reqParams, 59 | encoding: JSONEncoding.default, 60 | headers: headers) 61 | .serializingDecodable(resultType.self).value 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /_example/ts/client.ts: -------------------------------------------------------------------------------- 1 | 2 | import axios, {AxiosError, AxiosResponse} from "axios"; 3 | 4 | export const axiosClient = axios.create({ 5 | baseURL: "http://localhost:8083", 6 | headers: { 7 | 'Content-Type': 'application/json;charset=utf-8' 8 | }, 9 | timeout: Number(60000), 10 | withCredentials: false 11 | }) 12 | 13 | axiosClient.interceptors.response.use((response: AxiosResponse) => { 14 | return response.data 15 | }, (error: AxiosError) => { 16 | return Promise.reject(error) 17 | }) 18 | 19 | export const ApiClient = { 20 | 21 | get: async (path: string, params?: any) => { 22 | return await axiosClient.get(path,{params:params}) as any 23 | }, 24 | 25 | post: async (path: string, body?: any) => { 26 | return await axiosClient.post(path, body) as any 27 | }, 28 | 29 | put: async (path: string, body?: any) => { 30 | return await axiosClient.put(path, body) as any 31 | }, 32 | 33 | delete: async (path: string, body?: any) => { 34 | return await axiosClient.delete(path, body) as any 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /_example/ts/demo.ts: -------------------------------------------------------------------------------- 1 | import {ApiClient} from "./client" 2 | 3 | /** 4 | * 5 | */ 6 | export interface ApiResult { 7 | //业务响应码 8 | code?: string 9 | //数据 10 | data?: T 11 | //业务错误信息 12 | err?: string 13 | //业务响应信息 14 | msg?: string 15 | 16 | } 17 | 18 | /** 19 | * 20 | */ 21 | export interface PageData { 22 | //数据 23 | entities?: T[] 24 | //页码 25 | page?: number 26 | //条数 27 | size?: number 28 | //总数 29 | total?: number 30 | 31 | } 32 | 33 | /** 34 | * 35 | */ 36 | export interface MapResult { 37 | //响应码 38 | code?: string 39 | //数据 40 | data?: Record 41 | 42 | } 43 | 44 | /** 45 | * 猫 46 | */ 47 | export interface Cat { 48 | //食物 49 | foods?: string[] 50 | //昵称 51 | nickname?: string 52 | 53 | } 54 | 55 | /** 56 | * 狗 57 | */ 58 | export interface Dog { 59 | //颜色 60 | color?: string 61 | //ID 62 | id?: number 63 | //昵称 64 | nickname?: string 65 | //售价 66 | price?: number 67 | 68 | } 69 | 70 | /** 71 | * 用户 72 | */ 73 | export interface User { 74 | //年龄 75 | age?: number 76 | //生日 77 | birthday?: string 78 | //集合 79 | cats?: Cat[] 80 | //是否启用 81 | enable?: boolean 82 | //引用map 83 | gods?: Record 84 | //ID 85 | id?: number 86 | //基础map 87 | kv?: Record 88 | //名称 89 | name?: string 90 | // 91 | profile?: any 92 | //标签 93 | tags?: string[] 94 | //时间戳 95 | timestamp?: number 96 | 97 | } 98 | 99 | /** 100 | * 动物接口 101 | */ 102 | export const AnimalController = { 103 | 104 | /** 105 | * 106 | * 响应long 107 | */ 108 | addCat: async (body: Cat) => { 109 | return await ApiClient.post("/animal/cat/add", body) as number 110 | }, 111 | 112 | 113 | /** 114 | * 115 | * 路径+Body 116 | */ 117 | addDog: async (kind: string, body: Dog) => { 118 | return await ApiClient.post(`/animal/add/${kind}/dog`, body) as Dog 119 | }, 120 | 121 | 122 | /** 123 | * 124 | * 响应list 125 | */ 126 | cats: async () => { 127 | let params = {}; 128 | return await ApiClient.get("/animal/cats", params) as Cat[] 129 | }, 130 | 131 | 132 | /** 133 | * 134 | * 响应对象 135 | */ 136 | dog: async (id: number) => { 137 | let params = {}; 138 | return await ApiClient.get(`/animal/dog/${id}`, params) as Dog 139 | }, 140 | 141 | 142 | /** 143 | * 其他 144 | * 响应map 145 | */ 146 | mapCats: async () => { 147 | let params = {}; 148 | return await ApiClient.get("/animal/map/cats", params) as Record 149 | }, 150 | 151 | 152 | /** 153 | * 154 | * 响应Map泛型 155 | */ 156 | mapDog: async () => { 157 | let params = {}; 158 | return await ApiClient.get("/animal/map/dogs", params) as MapResult 159 | }, 160 | 161 | 162 | /** 163 | * 164 | * 响应Array泛型 165 | */ 166 | pageDogs: async () => { 167 | let params = {}; 168 | return await ApiClient.get("/animal/page/dogs", params) as PageData 169 | }, 170 | 171 | 172 | /** 173 | * 174 | * 路径+Query 175 | */ 176 | searchDog: async (color: string, kind: string, name: string) => { 177 | let params = {color: color, name: name}; 178 | return await ApiClient.get(`/animal/query/${kind}/dog`, params) as Dog 179 | }, 180 | 181 | 182 | } 183 | 184 | /** 185 | * 其他接口 186 | */ 187 | export const OtherApi = { 188 | 189 | /** 190 | * 191 | * 响应基础类型 192 | */ 193 | get: async () => { 194 | let params = {}; 195 | return await ApiClient.get("/other/long", params) as string 196 | }, 197 | 198 | 199 | /** 200 | * 201 | * 响应基础类型 202 | */ 203 | getContent: async () => { 204 | let params = {}; 205 | return await ApiClient.get("/other/string", params) as number 206 | }, 207 | 208 | 209 | /** 210 | * 211 | * 多路径参数 212 | */ 213 | multiplePathVariable: async (id: string, type: string) => { 214 | let params = {}; 215 | return await ApiClient.get(`/other/boolean/${type}/${id}`, params) as Dog[] 216 | }, 217 | 218 | 219 | } 220 | 221 | /** 222 | * 用户接口 223 | */ 224 | export const UserApi = { 225 | 226 | /** 227 | * 228 | * 添加用户 229 | */ 230 | add: async (body: User) => { 231 | return await ApiClient.post("/user/add", body) as ApiResult 232 | }, 233 | 234 | 235 | /** 236 | * 237 | * 删除用户 238 | */ 239 | delete: async (id: number) => { 240 | let params = {}; 241 | return await ApiClient.delete(`/user/delete/${id}`, params) as ApiResult 242 | }, 243 | 244 | 245 | /** 246 | * 247 | * 修改用户 248 | */ 249 | edit: async (id: number, body: User) => { 250 | return await ApiClient.put(`/user/edit/${id}`, body) as ApiResult 251 | }, 252 | 253 | 254 | /** 255 | * 256 | * 查询用户 257 | */ 258 | edit_1: async (id: number, keyword: string) => { 259 | let params = {keyword: keyword}; 260 | return await ApiClient.get(`/user/detail/${id}`, params) as ApiResult 261 | }, 262 | 263 | 264 | /** 265 | * 266 | * 用户列表 267 | */ 268 | list: async () => { 269 | let params = {}; 270 | return await ApiClient.get("/user/list", params) as ApiResult 271 | }, 272 | 273 | 274 | /** 275 | * 276 | * 用户分页 277 | */ 278 | page: async (page: number, size: number) => { 279 | let params = {page: page, size: size}; 280 | return await ApiClient.get("/user/page", params) as ApiResult> 281 | }, 282 | 283 | 284 | } 285 | -------------------------------------------------------------------------------- /_example/ts/index.ts: -------------------------------------------------------------------------------- 1 | import {Dog, OtherApi, User, UserApi} from "./demo"; 2 | 3 | console.log('Happy developing ✨') 4 | 5 | 6 | OtherApi.multiplePathVariable("12", "abc").then((e) => { 7 | console.info(e) 8 | }) 9 | 10 | let dog: Dog = {color: "", id: 0, nickname: "", price: 0} 11 | let user: User = { 12 | age: 12, 13 | birthday: "2023-10-10", 14 | cats: [], 15 | enable: false, 16 | gods: { 17 | "d1": dog, 18 | "d2": dog 19 | }, 20 | id: 0, 21 | kv: { 22 | "k1": "v1", 23 | "k2": "v2" 24 | }, 25 | name: "dev", 26 | profile: undefined, 27 | tags: ["t1", "t2", "t3"], 28 | timestamp: 120344 29 | } 30 | 31 | 32 | UserApi.add(user).then(res => { 33 | console.log(res.data) 34 | }) 35 | -------------------------------------------------------------------------------- /_images/api_cs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_cs.jpg -------------------------------------------------------------------------------- /_images/api_go.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_go.jpg -------------------------------------------------------------------------------- /_images/api_java.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_java.jpg -------------------------------------------------------------------------------- /_images/api_kotlin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_kotlin.jpg -------------------------------------------------------------------------------- /_images/api_python.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_python.jpg -------------------------------------------------------------------------------- /_images/api_rust.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_rust.jpg -------------------------------------------------------------------------------- /_images/api_swift.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_swift.jpg -------------------------------------------------------------------------------- /_images/api_ts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/api_ts.jpg -------------------------------------------------------------------------------- /_images/home_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/home_api.png -------------------------------------------------------------------------------- /_images/home_struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otk-final/openapi-codegen/b66a4a7afcd47c296e7a0487bc99b686aa4ac74e/_images/home_struct.png -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "codegen/internal" 5 | "codegen/tmpl" 6 | "encoding/json" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | var initArgs = &internal.Args{ 13 | Name: "swagger", 14 | Endpoint: "http://localhost:8080/v3/api-docs", 15 | Lang: "ts", 16 | Version: defaultVersion, 17 | } 18 | 19 | func init() { 20 | //init 21 | initCmd.Flags().StringVarP(&initArgs.Version, "version", "v", initArgs.Version, "openapi version") 22 | initCmd.Flags().StringVarP(&initArgs.Endpoint, "endpoint", "e", initArgs.Endpoint, "example:https://{server}:{port}/v3/api-docs") 23 | initCmd.Flags().StringVarP(&initArgs.Lang, "lang", "l", initArgs.Lang, strings.Join(tmpl.LangNames(), ",")) 24 | } 25 | 26 | // create env file 27 | var initCmd = &cobra.Command{ 28 | 29 | Use: "init", 30 | Short: "Initialize environment variable configuration file", 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | 33 | //创建openapi.json 34 | defaultEnv := &internal.Env{ 35 | Args: initArgs, 36 | Output: tmpl.NewOutputs(initArgs.Lang), 37 | Ignore: []string{}, 38 | Filter: []string{}, 39 | Alias: internal.Alias{ 40 | Properties: make(map[string]string), 41 | Models: make(map[string]string), 42 | Types: make(map[string]string), 43 | Parameters: make(map[string]string), 44 | }, 45 | 46 | Variables: map[string]string{}, 47 | Generics: &internal.Generics{ 48 | Enable: false, 49 | Unfold: false, 50 | Expressions: map[string][]string{ 51 | "ApiResult": {"data"}, 52 | }, 53 | }, 54 | RepeatableOperationId: true, 55 | } 56 | //当前执行目录 57 | envFile := tmpl.PwdJoinPath(defaultEnvFileName) 58 | 59 | envs := make([]*internal.Env, 0) 60 | envs = append(envs, defaultEnv) 61 | 62 | data, _ := json.MarshalIndent(envs, "", " ") 63 | return os.WriteFile(envFile, data, os.ModePerm) 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /cmd/reload.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "codegen/internal" 5 | "codegen/tmpl" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/samber/lo" 9 | "github.com/spf13/cobra" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | var envFile string 15 | var envName string 16 | var defaultEnvFileName = "openapi.json" 17 | 18 | func init() { 19 | 20 | //reload 21 | reloadCmd.Flags().StringVarP(&envFile, "file", "f", "", "customize env file") 22 | reloadCmd.Flags().StringVarP(&envName, "name", "n", "", "env name") 23 | } 24 | 25 | // reload from env 26 | var reloadCmd = &cobra.Command{ 27 | Use: "reload", 28 | Short: "Reload with environment variable configuration file", 29 | Run: func(cmd *cobra.Command, args []string) { 30 | 31 | //当前执行目录 32 | if envFile == "" { 33 | envFile = defaultEnvFileName 34 | } 35 | envFile = tmpl.PwdJoinPath(envFile) 36 | 37 | data, err := os.ReadFile(envFile) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | 43 | envs := make([]*internal.Env, 0) 44 | 45 | //当前配置文件 46 | err = json.Unmarshal(data, &envs) 47 | if err != nil { 48 | fmt.Println(err) 49 | return 50 | } 51 | 52 | if envName != "" { 53 | envs = lo.Filter(envs, func(item *internal.Env, idx int) bool { 54 | return strings.EqualFold(envName, item.Name) 55 | }) 56 | } 57 | 58 | if len(envs) == 0 { 59 | fmt.Println("Not found current env") 60 | return 61 | } 62 | 63 | for _, env := range envs { 64 | 65 | fmt.Printf("[%s] Begin Generate\n", env.Name) 66 | 67 | exe, err := internal.New(env, args) 68 | if err != nil { 69 | fmt.Println(err) 70 | return 71 | } 72 | 73 | err = exe.Run(env.Args) 74 | if err != nil { 75 | fmt.Println(err) 76 | return 77 | } 78 | 79 | fmt.Printf("[%s] Generate SUCCESS\n", env.Name) 80 | } 81 | 82 | fmt.Println("\nFinished") 83 | }, 84 | } 85 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const version = "1.0.2" 8 | 9 | var rootCmd = &cobra.Command{ 10 | Use: "openapi", 11 | Version: version, 12 | Short: "openapi codegen tool", 13 | Long: "This is a tool that generates API call code in various programming languages based on the content of an OpenAPI document.", 14 | Example: "openapi start -e http://localhost:8080/v3/api-doc -l ts -o src/api.ts", 15 | } 16 | 17 | var defaultVersion = "v3" 18 | 19 | func init() { 20 | // 根据openapi 生成接口文档 21 | rootCmd.AddCommand(startCmd, initCmd, reloadCmd, upgradeCmd) 22 | } 23 | 24 | func Run() error { 25 | return rootCmd.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /cmd/start.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "codegen/internal" 5 | "codegen/tmpl" 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | "strings" 9 | ) 10 | 11 | func init() { 12 | 13 | //init 14 | startCmd.Flags().StringVarP(&initArgs.Version, "version", "v", initArgs.Version, "openapi version") 15 | startCmd.Flags().StringVarP(&initArgs.Endpoint, "endpoint", "e", initArgs.Endpoint, "example:https://{server}:{port}/v3/api-docs") 16 | startCmd.Flags().StringVarP(&initArgs.Lang, "lang", "l", initArgs.Lang, strings.Join(tmpl.LangNames(), ",")) 17 | } 18 | 19 | var startCmd = &cobra.Command{ 20 | Use: "start", 21 | Short: "Quick start", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | 24 | env := &internal.Env{ 25 | Args: initArgs, 26 | Ignore: make([]string, 0), 27 | Filter: make([]string, 0), 28 | Output: tmpl.NewOutputs(initArgs.Lang), 29 | Alias: internal.Alias{ 30 | Properties: make(map[string]string), 31 | Models: make(map[string]string), 32 | Types: make(map[string]string), 33 | Parameters: make(map[string]string), 34 | }, 35 | Variables: map[string]string{}, 36 | Generics: &internal.Generics{ 37 | Enable: false, 38 | Unfold: false, 39 | Expressions: map[string][]string{}, 40 | }, 41 | } 42 | 43 | exe, err := internal.New(env, args) 44 | if err != nil { 45 | fmt.Println(err) 46 | return 47 | } 48 | 49 | err = exe.Run(env.Args) 50 | if err != nil { 51 | fmt.Println(err) 52 | return 53 | } 54 | fmt.Println("SUCCESS") 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /cmd/upgrade.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "archive/zip" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/samber/lo" 9 | "github.com/schollz/progressbar/v3" 10 | "github.com/spf13/cobra" 11 | "io" 12 | "net/http" 13 | "os" 14 | "path/filepath" 15 | "runtime" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | var upgradeCmd = &cobra.Command{ 21 | Use: "upgrade", 22 | Short: "Upgrade to the latest version or specified version", 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | if repoUrl != "" { 25 | return downloadInstall(repoUrl) 26 | } 27 | 28 | //查询 github 29 | assets, err := downloadCheck(repoVersion) 30 | if err != nil { 31 | return err 32 | } 33 | return downloadInstall(assets.Url) 34 | }, 35 | } 36 | 37 | func init() { 38 | upgradeCmd.Flags().StringVarP(&repoUrl, "download url", "d", repoUrl, "repo download url") 39 | upgradeCmd.Flags().StringVarP(&repoVersion, "repo version", "v", repoVersion, "repo version") 40 | } 41 | 42 | const repo = "https://api.github.com/repos/otk-final/openapi-codegen/releases" 43 | 44 | var repoUrl = "" 45 | var repoVersion = "latest" 46 | var repoName = "openapi" 47 | 48 | type Assets struct { 49 | Name string `json:"name"` 50 | Url string `json:"browser_download_url"` 51 | } 52 | type RepoInfo struct { 53 | Tag string `json:"tag"` 54 | Name string `json:"name"` 55 | CreatedAt string `json:"created_at"` 56 | PublishedAt string `json:"published_at"` 57 | Assets []*Assets `json:"assets"` 58 | } 59 | 60 | func downloadCheck(version string) (*Assets, error) { 61 | 62 | //查询 63 | resp, err := http.Get(repo + "/" + version) 64 | if err != nil { 65 | return nil, err 66 | } 67 | defer func() { 68 | _ = resp.Body.Close() 69 | }() 70 | 71 | body, _ := io.ReadAll(resp.Body) 72 | 73 | //解码 74 | var info RepoInfo 75 | _ = json.Unmarshal(body, &info) 76 | 77 | //获取当前os 安装包 78 | assets, ok := lo.Find(info.Assets, func(item *Assets) bool { 79 | return strings.Contains(item.Name, runtime.GOOS) 80 | }) 81 | if ok { 82 | return assets, nil 83 | } 84 | return nil, errors.New("not found assets") 85 | } 86 | 87 | func downloadInstall(url string) error { 88 | fmt.Printf("start download %s\n", url) 89 | 90 | //下载 91 | resp, err := http.Get(url) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | defer func() { 97 | _ = resp.Body.Close() 98 | }() 99 | 100 | //临时目录 文件 101 | tempDir, _ := os.UserCacheDir() 102 | tempDir = filepath.Join(tempDir, repoName) 103 | _ = os.MkdirAll(tempDir, os.ModePerm) 104 | 105 | tempFile, err := os.OpenFile(filepath.Join(tempDir, fmt.Sprintf("%d.zip", time.Now().UnixMilli())), os.O_CREATE|os.O_WRONLY, 0644) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | //写入文件 111 | bar := progressbar.DefaultBytes(resp.ContentLength, "downloading") 112 | _, err = io.Copy(io.MultiWriter(tempFile, bar), resp.Body) 113 | if err != nil { 114 | return err 115 | } 116 | fmt.Printf("download completed\n") 117 | 118 | //解压文件 119 | tempZip, _ := zip.OpenReader(tempFile.Name()) 120 | defer func() { 121 | _ = tempZip.Close() 122 | _ = os.Remove(tempFile.Name()) 123 | }() 124 | 125 | //获取指定文件 126 | newFile, ok := lo.Find(tempZip.File, func(item *zip.File) bool { 127 | return strings.Contains(item.Name, repoName) 128 | }) 129 | if !ok { 130 | return errors.New("not found install file from zip") 131 | } 132 | 133 | newReader, _ := newFile.Open() 134 | newBytes, _ := io.ReadAll(newReader) 135 | 136 | //获取真实安装路径 137 | exePath, _ := os.Executable() 138 | realPath, _ := filepath.EvalSymlinks(exePath) 139 | 140 | //替换文件 覆盖 141 | return os.WriteFile(realPath, newBytes, os.ModePerm) 142 | } 143 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module codegen 2 | 3 | go 1.24.2 4 | 5 | require github.com/samber/lo v1.49.1 6 | 7 | require ( 8 | github.com/fsnotify/fsnotify v1.8.0 // indirect 9 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 10 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 11 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect 12 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 13 | github.com/rivo/uniseg v0.4.7 // indirect 14 | github.com/sagikazarmark/locafero v0.7.0 // indirect 15 | github.com/samber/do v1.6.0 // indirect 16 | github.com/schollz/progressbar/v3 v3.18.0 // indirect 17 | github.com/sourcegraph/conc v0.3.0 // indirect 18 | github.com/spf13/afero v1.12.0 // indirect 19 | github.com/spf13/cast v1.7.1 // indirect 20 | github.com/spf13/cobra v1.9.1 // indirect 21 | github.com/spf13/pflag v1.0.6 // indirect 22 | github.com/spf13/viper v1.20.1 // indirect 23 | github.com/subosito/gotenv v1.6.0 // indirect 24 | go.uber.org/atomic v1.9.0 // indirect 25 | go.uber.org/multierr v1.9.0 // indirect 26 | golang.org/x/sys v0.29.0 // indirect 27 | golang.org/x/term v0.28.0 // indirect 28 | golang.org/x/text v0.21.0 // indirect 29 | gopkg.in/yaml.v3 v3.0.1 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 5 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 6 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 7 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 8 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 9 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 10 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 11 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 12 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 13 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 16 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 17 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 18 | github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= 19 | github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= 20 | github.com/samber/do v1.6.0/go.mod h1:DWqBvumy8dyb2vEnYZE7D7zaVEB64J45B0NjTlY/M4k= 21 | github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= 22 | github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= 23 | github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= 24 | github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= 25 | github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA= 26 | github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= 27 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 28 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 29 | github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= 30 | github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= 31 | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= 32 | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 33 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 34 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 35 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 36 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 37 | github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= 38 | github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 39 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 40 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 41 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 42 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 43 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 44 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 45 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 46 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 47 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 48 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 49 | golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= 50 | golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 51 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 52 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 55 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 56 | -------------------------------------------------------------------------------- /internal/cmd.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "cmp" 5 | "codegen/lang" 6 | "codegen/tmpl" 7 | v2 "codegen/v2" 8 | v3 "codegen/v3" 9 | "errors" 10 | "fmt" 11 | "github.com/samber/lo" 12 | "slices" 13 | "strings" 14 | ) 15 | 16 | type Args struct { 17 | Name string `json:"name"` 18 | Endpoint string `json:"endpoint"` 19 | Lang string `json:"lang"` 20 | Version string `json:"version"` 21 | } 22 | 23 | type Env struct { 24 | *Args 25 | Output map[string]*tmpl.Output `json:"output"` 26 | Ignore []string `json:"ignore"` 27 | Filter []string `json:"filter"` 28 | Alias Alias `json:"alias"` 29 | Variables map[string]string `json:"variables"` 30 | Generics *Generics `json:"generics"` 31 | RepeatableOperationId bool `json:"repeatable_operation_id"` 32 | } 33 | 34 | type Alias struct { 35 | Properties map[string]string `json:"properties"` 36 | Models map[string]string `json:"models"` 37 | Types map[string]string `json:"types"` 38 | Parameters map[string]string `json:"parameters"` 39 | } 40 | 41 | type Generics struct { 42 | Enable bool `json:"enable"` 43 | Unfold bool `json:"unfold"` 44 | Expressions map[string][]string `json:"expressions"` 45 | } 46 | 47 | type Executor struct { 48 | env *Env 49 | convert lang.TypeConvert 50 | } 51 | 52 | func New(env *Env, args []string) (*Executor, error) { 53 | return &Executor{ 54 | env: env, 55 | convert: tmpl.NewLangConvert(env.Lang, env.Alias.Types), 56 | }, nil 57 | } 58 | 59 | func (e *Executor) Run(cmd *Args) error { 60 | 61 | var outRefs []*tmpl.Ref 62 | var outApis []*tmpl.Api 63 | var err error 64 | 65 | if e.env.Version == "v2" { 66 | outRefs, outApis, err = v2.LoadParse(e.env.Endpoint) 67 | } else if e.env.Version == "v3" { 68 | outRefs, outApis, err = v3.LoadParse(e.env.Endpoint) 69 | } else { 70 | return errors.New("invalid version") 71 | } 72 | if err != nil { 73 | return err 74 | } 75 | 76 | outApis = e.filterApis(outApis) 77 | 78 | slices.SortFunc(outRefs, func(a, b *tmpl.Ref) int { 79 | return cmp.Compare(a.Name, b.Name) 80 | }) 81 | 82 | slices.SortFunc(outApis, func(a, b *tmpl.Api) int { 83 | return cmp.Compare(a.Name, b.Name) 84 | }) 85 | 86 | outRefs, err = e.buildRefs(outRefs) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | outApis, err = e.buildApis(outRefs, outApis) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | for name, output := range e.env.Output { 97 | 98 | //合并 Variables 99 | for k, v := range e.env.Variables { 100 | if _, ok := output.Variables[k]; ok { 101 | continue 102 | } 103 | output.Variables[k] = v 104 | } 105 | 106 | writer := newWriter(name, e.env) 107 | engine, err := tmpl.NewEngine(e.env.Lang, name, output.Template, output.Variables) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | err = writer.write(output, outApis, outRefs, engine) 113 | if err != nil { 114 | return err 115 | } 116 | } 117 | return nil 118 | } 119 | 120 | func (e *Executor) buildRefs(outRefs []*tmpl.Ref) ([]*tmpl.Ref, error) { 121 | typeConvert := e.convert 122 | 123 | //是否需要生成泛型 124 | if e.env.Generics.Enable { 125 | 126 | expressions := e.env.Generics.Expressions 127 | 128 | generics := resolvingGenerics(e.convert, expressions, outRefs) 129 | 130 | mgr := &nestingGenericManage{ 131 | generics: generics, 132 | refs: outRefs, 133 | } 134 | 135 | newRefs := make([]*tmpl.Ref, 0) 136 | newRefs = append(newRefs, generics...) 137 | 138 | for _, ref := range outRefs { 139 | 140 | refName := ref.Name 141 | //是否为泛型,前缀是否一致 142 | matched, ok := lo.Find(generics, func(item *tmpl.Ref) bool { 143 | return strings.HasPrefix(refName, item.Name) 144 | }) 145 | 146 | if ok { 147 | //递归识别泛型 148 | val := &nestingGeneric{ 149 | Kind: tmpl.GenericType, 150 | Expression: matched.Name, 151 | Subs: make([]*nestingGeneric, 0), 152 | } 153 | mgr.recursion(val, matched, ref) 154 | 155 | //是否展开泛型,只获取子类型,方便业务直接使用 156 | if e.env.Generics.Unfold { 157 | val.unfold() 158 | } 159 | 160 | //设置泛型>不可变类型 161 | ref.Type.Kind = tmpl.GenericType | tmpl.ImmutableType 162 | ref.Type.Expression = val.discriminator(typeConvert) 163 | } 164 | 165 | newRefs = append(newRefs, ref) 166 | } 167 | outRefs = newRefs 168 | } 169 | 170 | //根据类型 生成属性表达式 171 | for _, ref := range outRefs { 172 | 173 | //排序 174 | properties := ref.Properties 175 | slices.SortFunc(properties, func(a, b *tmpl.Property) int { 176 | return cmp.Compare(a.Name, b.Name) 177 | }) 178 | 179 | //属性转换 180 | for _, property := range properties { 181 | 182 | if property.Type != nil { 183 | property.Type.GenerateExpression(property.Format, typeConvert) 184 | } 185 | 186 | property.Alias = cmp.Or(e.env.Alias.Properties[property.Name], property.Name) 187 | } 188 | 189 | //是否确定导出 190 | kind := ref.Type.Kind 191 | if kind&tmpl.ImmutableType != 0 && kind&tmpl.GenericType != 0 { 192 | ref.Ignore = true 193 | } 194 | if len(ref.Properties) == 0 { 195 | ref.Ignore = true 196 | } 197 | 198 | //别名 199 | ref.Alias = cmp.Or(e.env.Alias.Models[ref.Name], ref.Name) 200 | } 201 | 202 | //有顺序要求,重新排序 203 | slices.SortFunc(outRefs, func(a, b *tmpl.Ref) int { 204 | return a.ReferenceLevel() - b.ReferenceLevel() 205 | }) 206 | 207 | return outRefs, nil 208 | } 209 | 210 | func (e *Executor) filterApis(outApis []*tmpl.Api) []*tmpl.Api { 211 | // 过滤 path 212 | filter := e.env.Filter 213 | ignore := e.env.Ignore 214 | lo.ForEach(outApis, func(item *tmpl.Api, index int) { 215 | item.Paths = lo.Filter(item.Paths, func(p *tmpl.Path, index int) bool { 216 | path := p.Path 217 | _, ignoreMatch := lo.Find(ignore, func(p string) bool { 218 | return strings.HasPrefix(path, p) 219 | }) 220 | if ignoreMatch { 221 | return false 222 | } 223 | 224 | if len(filter) > 0 { 225 | _, filterMatch := lo.Find(filter, func(p string) bool { 226 | return strings.HasPrefix(path, p) 227 | }) 228 | return filterMatch 229 | } 230 | return true 231 | }) 232 | }) 233 | 234 | return lo.Filter(outApis, func(item *tmpl.Api, index int) bool { 235 | return len(item.Paths) > 0 236 | }) 237 | } 238 | 239 | func (e *Executor) buildApis(outRefs []*tmpl.Ref, outApis []*tmpl.Api) ([]*tmpl.Api, error) { 240 | 241 | LANG := e.env.Lang 242 | typeConvert := e.convert 243 | 244 | //查询 245 | findType := func(currenType *tmpl.NamedType) *tmpl.NamedType { 246 | 247 | currenName := currenType.Expression 248 | match, ok := lo.Find(outRefs, func(item *tmpl.Ref) bool { 249 | return item.Name == currenName 250 | }) 251 | 252 | if ok && match.Type.Kind&tmpl.GenericType != 0 { 253 | return match.Type 254 | } 255 | 256 | //默认类型 257 | currenType.GenerateExpression("", e.convert) 258 | return currenType 259 | } 260 | 261 | //匹配路径参数 262 | joinPath := func(path *tmpl.Path) string { 263 | parameters := path.Parameters 264 | 265 | //存在路径参数 266 | parameters = lo.Filter(parameters, func(item *tmpl.Parameter, idx int) bool { 267 | return item.In == "path" 268 | }) 269 | 270 | if len(parameters) > 0 { 271 | return lang.Format(LANG, path.OriginalPath, e.env.Alias.Parameters) 272 | } 273 | return fmt.Sprintf(`"%s"`, path.Path) 274 | } 275 | 276 | //根据类型 生成表达式 277 | for _, api := range outApis { 278 | 279 | paths := api.Paths 280 | slices.SortFunc(paths, func(a, b *tmpl.Path) int { 281 | return cmp.Compare(a.Name, b.Name) 282 | }) 283 | 284 | for _, path := range paths { 285 | 286 | //参数转换 287 | for _, parameter := range path.Parameters { 288 | parameter.Type.GenerateExpression(parameter.Format, typeConvert) 289 | //别名 290 | parameter.Alias = cmp.Or(e.env.Alias.Parameters[parameter.Name], parameter.Name) 291 | } 292 | 293 | //路径 294 | path.Path = joinPath(path) 295 | 296 | //全量参数 297 | slices.SortFunc(path.Parameters, func(a, b *tmpl.Parameter) int { 298 | return cmp.Compare(a.Name, b.Name) 299 | }) 300 | 301 | if path.Request != nil { 302 | path.Parameters = append(path.Parameters, &tmpl.Parameter{ 303 | Name: "body", 304 | Alias: "body", 305 | In: "body", 306 | Type: findType(path.Request), 307 | }) 308 | } 309 | 310 | //query参数 311 | queries := lo.Filter(path.Parameters, func(item *tmpl.Parameter, index int) bool { 312 | return item.In == "query" 313 | }) 314 | slices.SortFunc(queries, func(a, b *tmpl.Parameter) int { 315 | return cmp.Compare(a.Name, b.Name) 316 | }) 317 | path.Queries = queries 318 | 319 | if path.Response != nil { 320 | path.Response = findType(path.Response) 321 | } 322 | 323 | //方法名 不同Tag下 方法重名下容易生成 add_1,update_39 324 | path.Name = lo.TernaryF(e.env.RepeatableOperationId, func() string { 325 | return strings.FieldsFunc(path.Name, func(r rune) bool { 326 | return r == '_' || r == '-' 327 | })[0] 328 | }, func() string { 329 | return path.Name 330 | }) 331 | 332 | } 333 | 334 | api.Paths = paths 335 | } 336 | 337 | return outApis, nil 338 | } 339 | -------------------------------------------------------------------------------- /internal/generics.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "cmp" 5 | "codegen/lang" 6 | "codegen/tmpl" 7 | "fmt" 8 | "github.com/samber/lo" 9 | "slices" 10 | "strings" 11 | ) 12 | 13 | type nestingGeneric struct { 14 | Kind tmpl.NamedTypeKind 15 | Expression string 16 | Format string 17 | Subs []*nestingGeneric 18 | } 19 | 20 | func (n *nestingGeneric) discriminator(convert lang.TypeConvert) string { 21 | lines := make([]string, 0) 22 | for _, sub := range n.Subs { 23 | var expression = sub.Expression 24 | if sub.Kind&tmpl.GenericType != 0 { 25 | expression = sub.discriminator(convert) 26 | } else { 27 | expression = sub.Kind.Parse(expression, sub.Format, convert) 28 | } 29 | lines = append(lines, expression) 30 | } 31 | 32 | if len(lines) == 0 { 33 | return n.Kind.Parse(n.Expression, n.Format, convert) 34 | } 35 | 36 | return convert.Generic(n.Expression, lang.ActualGenericMode, lines...) 37 | } 38 | 39 | func (n *nestingGeneric) unfold() { 40 | //多类型忽略 41 | if len(n.Subs) != 1 { 42 | return 43 | } 44 | *n = *n.Subs[0] 45 | } 46 | 47 | type nestingGenericManage struct { 48 | generics []*tmpl.Ref 49 | refs []*tmpl.Ref 50 | } 51 | 52 | func (n *nestingGenericManage) recursion(current *nestingGeneric, generic, example *tmpl.Ref) { 53 | 54 | //同序 替换占位符 55 | genericProperties := generic.Properties 56 | slices.SortFunc(genericProperties, func(t1, t2 *tmpl.Property) int { 57 | return cmp.Compare(t1.Name, t2.Name) 58 | }) 59 | exampleProperties := example.Properties 60 | slices.SortFunc(exampleProperties, func(t1, t2 *tmpl.Property) int { 61 | return cmp.Compare(t1.Name, t2.Name) 62 | }) 63 | 64 | for idx, property := range genericProperties { 65 | 66 | //过滤非泛型属性 67 | if property.Type.Kind&tmpl.GenericType == 0 { 68 | continue 69 | } 70 | 71 | //同序 获取属性 72 | exampleProperty := exampleProperties[idx] 73 | nextKind := exampleProperty.Type.Kind 74 | nextFormat := exampleProperty.Format 75 | nextExpression := exampleProperty.Type.Expression 76 | 77 | //判断 属性类型是否为嵌套泛型 78 | nextGeneric, genericOk := lo.Find(n.generics, func(item *tmpl.Ref) bool { 79 | return strings.HasPrefix(nextExpression, item.Name) 80 | }) 81 | 82 | nextExample, exampleOk := lo.Find(n.refs, func(item *tmpl.Ref) bool { 83 | return item.Name == nextExpression 84 | }) 85 | 86 | next := &nestingGeneric{ 87 | Subs: make([]*nestingGeneric, 0), 88 | } 89 | 90 | if genericOk && exampleOk { 91 | //泛型 92 | next.Expression = nextGeneric.Name 93 | next.Kind = property.Type.Kind 94 | next.Format = property.Format 95 | 96 | n.recursion(next, nextGeneric, nextExample) 97 | } else { 98 | //基础属性类型 99 | next.Kind = nextKind 100 | 101 | //防止数组类型冲突,排除 102 | if property.Type.Kind&tmpl.ArrayType != 0 { 103 | next.Kind = nextKind ^ tmpl.ArrayType 104 | } 105 | 106 | //防止数组类型冲突,排除 107 | if property.Type.Kind&tmpl.MapType != 0 { 108 | next.Kind = nextKind ^ tmpl.MapType 109 | } 110 | 111 | next.Format = nextFormat 112 | next.Expression = nextExpression 113 | } 114 | current.Subs = append(current.Subs, next) 115 | } 116 | 117 | } 118 | 119 | var placeholders = []string{"T", "A", "B", "C", "D", "E", "F", "G", "H"} 120 | 121 | // 匹配泛型 122 | func resolvingGenerics(convert lang.TypeConvert, expressions map[string][]string, refs []*tmpl.Ref) []*tmpl.Ref { 123 | 124 | var defines = make([]*tmpl.Ref, 0) 125 | for wrapper, discriminators := range expressions { 126 | 127 | //查询已声明数据 128 | match, ok := lo.Find(refs, func(item *tmpl.Ref) bool { 129 | return strings.HasPrefix(item.Name, wrapper) 130 | }) 131 | if !ok { 132 | continue 133 | } 134 | 135 | properties := make(tmpl.Properties, 0) 136 | idx := 0 137 | //替换泛型属性的 类型 138 | for _, property := range match.Properties { 139 | 140 | var expression string 141 | var kind tmpl.NamedTypeKind 142 | // 泛型 使用自定义的属性 143 | if lo.Contains(discriminators, property.Name) { 144 | //对象泛型 145 | expression = placeholders[idx] 146 | kind = tmpl.ImmutableType | tmpl.GenericType //属性设置不可变 147 | } else if lo.Contains(discriminators, fmt.Sprintf("%s+", property.Name)) { //集合泛型 148 | //集合泛型 149 | expression = convert.Array(placeholders[idx]) 150 | kind = tmpl.ImmutableType | tmpl.ArrayType | tmpl.GenericType //属性设置不可变 151 | } else if lo.Contains(discriminators, fmt.Sprintf("%s~", property.Name)) { //Map泛型 152 | //集合泛型 153 | expression = convert.Map(placeholders[idx]) 154 | kind = tmpl.ImmutableType | tmpl.MapType | tmpl.GenericType //属性设置不可变 155 | } else { 156 | //其他类型 157 | properties = append(properties, property) 158 | continue 159 | } 160 | 161 | //生成表达式 162 | property = &tmpl.Property{ 163 | Name: property.Name, 164 | Description: property.Description, 165 | Type: &tmpl.NamedType{ 166 | Kind: kind, 167 | Expression: expression, 168 | }, 169 | } 170 | properties = append(properties, property) 171 | 172 | idx++ 173 | } 174 | 175 | placeholders := placeholders[:idx] 176 | tp := &tmpl.NamedType{ 177 | Kind: tmpl.ImmutableType, //类型设置不可变 178 | Expression: convert.Generic(wrapper, lang.TypeGenericMode, placeholders...), 179 | } 180 | 181 | ref := &tmpl.Ref{ 182 | Name: wrapper, 183 | Type: tp, 184 | Properties: properties, 185 | Description: "", 186 | } 187 | defines = append(defines, ref) 188 | } 189 | 190 | return defines 191 | } 192 | -------------------------------------------------------------------------------- /internal/writer.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "codegen/tmpl" 6 | "fmt" 7 | "github.com/samber/lo" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "text/template" 12 | ) 13 | 14 | type writer interface { 15 | write(output *tmpl.Output, apis []*tmpl.Api, models []*tmpl.Ref, engine *template.Template) error 16 | } 17 | 18 | func newWriter(name string, env *Env) writer { 19 | 20 | //标准输入 21 | std := &stdWriter{name, env} 22 | 23 | //java 需要输出多个文件 24 | if env.Lang == "java" { 25 | return &javaWriter{name, env, std} 26 | } 27 | return std 28 | } 29 | 30 | type stdWriter struct { 31 | name string 32 | env *Env 33 | } 34 | 35 | func (w *stdWriter) write(output *tmpl.Output, apis []*tmpl.Api, models []*tmpl.Ref, engine *template.Template) error { 36 | buf := &bytes.Buffer{} 37 | 38 | if w.name == "model" { 39 | 40 | models = lo.Filter(models, func(item *tmpl.Ref, index int) bool { 41 | return !item.Ignore 42 | }) 43 | 44 | if len(models) == 0 { 45 | return nil 46 | } 47 | } 48 | 49 | data := struct { 50 | Output *tmpl.Output 51 | Env *Env 52 | Apis []*tmpl.Api 53 | Models []*tmpl.Ref 54 | }{ 55 | Output: output, 56 | Apis: apis, 57 | Models: models, 58 | Env: w.env, 59 | } 60 | 61 | //写入 header 62 | err := engine.Execute(buf, data) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | //格式化路径参数 68 | textFile := output.File 69 | if len(apis) == 1 { 70 | textFile = strings.ReplaceAll(textFile, "{api}", apis[0].Name) 71 | } 72 | if len(models) == 1 { 73 | textFile = strings.ReplaceAll(textFile, "{model}", models[0].Name) 74 | } 75 | 76 | file := tmpl.PwdJoinPath(textFile) 77 | dir := filepath.Dir(file) 78 | err = os.MkdirAll(dir, os.ModePerm) 79 | if err != nil { 80 | return err 81 | } 82 | fmt.Printf("Output %s \n", file) 83 | 84 | return os.WriteFile(file, buf.Bytes(), os.ModePerm) 85 | } 86 | 87 | type javaWriter struct { 88 | name string 89 | env *Env 90 | std writer 91 | } 92 | 93 | // java 一个文件只能有一个model 或者 api 需写入多个文件 94 | func (w *javaWriter) write(output *tmpl.Output, apis []*tmpl.Api, models []*tmpl.Ref, engine *template.Template) error { 95 | if w.name == "api" { 96 | for _, api := range apis { 97 | err := w.std.write(output, []*tmpl.Api{api}, models, engine) 98 | if err != nil { 99 | return err 100 | } 101 | } 102 | return nil 103 | } else if w.name == "model" { 104 | for _, model := range models { 105 | err := w.std.write(output, apis, []*tmpl.Ref{model}, engine) 106 | if err != nil { 107 | return err 108 | } 109 | } 110 | return nil 111 | } 112 | return w.std.write(output, apis, models, engine) 113 | } 114 | -------------------------------------------------------------------------------- /lang/doc.go: -------------------------------------------------------------------------------- 1 | package lang 2 | 3 | import ( 4 | "cmp" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | type GenericMode int 11 | 12 | const ( 13 | // ActualGenericMode 实参类型 14 | ActualGenericMode GenericMode = iota + 1 15 | 16 | // TypeGenericMode 行参类型 17 | TypeGenericMode 18 | ) 19 | 20 | type TypeConvert interface { 21 | 22 | // Reference 引用类型 23 | Reference(name string) string 24 | // Foundation 基础类型 25 | Foundation(name string, format string) string 26 | // Array 数组 27 | Array(sub string) string 28 | // Map 字典 29 | Map(sub string) string 30 | // Generic 泛型 31 | Generic(parentType string, mode GenericMode, subTypes ...string) string 32 | } 33 | 34 | var reg = regexp.MustCompile(`\{([^/]+?)\}`) 35 | 36 | func Format(lang string, path string, alias map[string]string) string { 37 | 38 | var segments []string 39 | text := reg.ReplaceAllStringFunc(path, func(seg string) string { 40 | 41 | segment := reg.FindStringSubmatch(seg)[1] 42 | segment = cmp.Or(alias[segment], segment) 43 | 44 | segments = append(segments, segment) 45 | 46 | switch lang { 47 | case "ts": 48 | return fmt.Sprintf("${%s}", segment) 49 | case "go": 50 | return "%v" 51 | case "swift": 52 | return "%@" 53 | case "rust": 54 | return fmt.Sprintf("${%s}", segment) 55 | case "cs": 56 | return fmt.Sprintf("{%d}", len(segments)-1) 57 | default: 58 | return "%s" 59 | } 60 | }) 61 | 62 | switch lang { 63 | case "ts": 64 | return fmt.Sprintf("`%s`", text) 65 | case "rust": 66 | return fmt.Sprintf(`format!("%s",%s)`, text, strings.Join(segments, ",")) 67 | case "cs": 68 | return fmt.Sprintf(`string.Format("%s",%s)`, text, strings.Join(segments, ",")) 69 | case "go": 70 | return fmt.Sprintf(`fmt.Sprintf("%s",%s)`, text, strings.Join(segments, ",")) 71 | case "java": 72 | return fmt.Sprintf(`String.format("%s",%s)`, text, strings.Join(segments, ",")) 73 | case "swift": 74 | return fmt.Sprintf(`String(format: "%s", %s)`, text, strings.Join(segments, ",")) 75 | case "kotlin", "python": 76 | return fmt.Sprintf(`"%s".format(%s)`, text, strings.Join(segments, ",")) 77 | } 78 | return text 79 | } 80 | 81 | type TextConvert interface { 82 | Placeholder(name string) string 83 | Format(name string, values ...string) string 84 | } 85 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "codegen/cmd" 4 | 5 | func main() { 6 | _ = cmd.Run() 7 | } 8 | -------------------------------------------------------------------------------- /openapi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "ts", 4 | "endpoint": "http://localhost:8080/v3/api-docs/api", 5 | "lang": "ts", 6 | "version": "v3", 7 | "output": { 8 | "api": { 9 | "file": "client-ts/src/api.ts" 10 | }, 11 | "client": { 12 | "file": "client-ts/src/client.ts" 13 | }, 14 | "model": { 15 | "file": "client-ts/src/model.ts" 16 | } 17 | }, 18 | "ignore": [], 19 | "filter": [], 20 | "alias": { 21 | "properties": {}, 22 | "models": {}, 23 | "types": { 24 | "JsonNode": "any" 25 | }, 26 | "parameters": {} 27 | }, 28 | "variables": {}, 29 | "generics": { 30 | "enable": true, 31 | "unfold": false, 32 | "expressions": { 33 | "ApiResult": [ 34 | "data" 35 | ] 36 | } 37 | }, 38 | "repeatable_operation_id": false 39 | }, 40 | { 41 | "name": "swift", 42 | "endpoint": "http://localhost:8080/v3/api-docs/api", 43 | "lang": "swift", 44 | "version": "v3", 45 | "output": { 46 | "api": { 47 | "file": "client-swift/api.swift" 48 | }, 49 | "client": { 50 | "file": "client-swift/client.swift" 51 | }, 52 | "model": { 53 | "file": "client-swift/model.swift" 54 | } 55 | }, 56 | "ignore": [], 57 | "filter": [], 58 | "alias": { 59 | "properties": {}, 60 | "models": {}, 61 | "types": { 62 | "JsonNode": "String" 63 | }, 64 | "parameters": {} 65 | }, 66 | "variables": {}, 67 | "generics": { 68 | "enable": true, 69 | "unfold": false, 70 | "expressions": { 71 | "ApiResult": [ 72 | "data" 73 | ] 74 | } 75 | }, 76 | "repeatable_operation_id": false 77 | }, 78 | { 79 | "name": "python", 80 | "endpoint": "http://localhost:8080/v3/api-docs/api", 81 | "lang": "python", 82 | "version": "v3", 83 | "output": { 84 | "api": { 85 | "file": "client-python/api.py" 86 | }, 87 | "client": { 88 | "file": "client-python/client.py" 89 | }, 90 | "model": { 91 | "file": "client-python/model.py" 92 | } 93 | }, 94 | "ignore": [], 95 | "filter": [], 96 | "alias": { 97 | "properties": {}, 98 | "models": {}, 99 | "types": { 100 | "JsonNode": "Any" 101 | }, 102 | "parameters": {} 103 | }, 104 | "variables": {}, 105 | "generics": { 106 | "enable": false, 107 | "unfold": false, 108 | "expressions": { 109 | "ApiResult": [ 110 | "data" 111 | ] 112 | } 113 | }, 114 | "repeatable_operation_id": false 115 | }, 116 | { 117 | "name": "kotlin", 118 | "endpoint": "http://localhost:8080/v3/api-docs/api", 119 | "lang": "kotlin", 120 | "version": "v3", 121 | "output": { 122 | "api": { 123 | "header": [ 124 | "package com.demo;" 125 | ], 126 | "file": "client-kotlin/app/src/main/java/com/demo/Api.kt" 127 | }, 128 | "client": { 129 | "header": [ 130 | "package com.demo;" 131 | ], 132 | "file": "client-kotlin/app/src/main/java/com/demo/Client.kt" 133 | }, 134 | "model": { 135 | "header": [ 136 | "package com.demo;" 137 | ], 138 | "file": "client-kotlin/app/src/main/java/com/demo/Model.kt" 139 | } 140 | }, 141 | "ignore": [], 142 | "filter": [], 143 | "alias": { 144 | "properties": {}, 145 | "models": {}, 146 | "types": { 147 | "JsonNode": "Any" 148 | }, 149 | "parameters": {} 150 | }, 151 | "variables": {}, 152 | "generics": { 153 | "enable": true, 154 | "unfold": false, 155 | "expressions": { 156 | "ApiResult": [ 157 | "data" 158 | ] 159 | } 160 | }, 161 | "repeatable_operation_id": false 162 | }, 163 | { 164 | "name": "java", 165 | "endpoint": "http://localhost:8080/v3/api-docs/api", 166 | "lang": "java", 167 | "version": "v3", 168 | "output": { 169 | "api": { 170 | "header": [ 171 | "package demo.api;", 172 | "", 173 | "import demo.model.*;", 174 | "import demo.ApiClient;" 175 | ], 176 | "file": "client-java/src/main/java/demo/api/{api}.java" 177 | }, 178 | "client": { 179 | "header": [ 180 | "package demo;" 181 | ], 182 | "file": "client-java/src/main/java/demo/ApiClient.java" 183 | }, 184 | "model": { 185 | "header": [ 186 | "package demo.model;" 187 | ], 188 | "file": "client-java/src/main/java/demo/model/{model}.java" 189 | } 190 | }, 191 | "ignore": [], 192 | "filter": [], 193 | "alias": { 194 | "properties": {}, 195 | "models": {}, 196 | "types": {}, 197 | "parameters": {} 198 | }, 199 | "variables": {}, 200 | "generics": { 201 | "enable": true, 202 | "unfold": false, 203 | "expressions": { 204 | "ApiResult": [ 205 | "data" 206 | ] 207 | } 208 | }, 209 | "repeatable_operation_id": false 210 | }, 211 | { 212 | "name": "go", 213 | "endpoint": "http://localhost:8080/v3/api-docs/api", 214 | "lang": "go", 215 | "version": "v3", 216 | "output": { 217 | "api": { 218 | "header": [ 219 | "package api;" 220 | ], 221 | "file": "client-go/api/api.go" 222 | }, 223 | "client": { 224 | "header": [ 225 | "package api;" 226 | ], 227 | "file": "client-go/api/client.go" 228 | }, 229 | "model": { 230 | "header": [ 231 | "package api;" 232 | ], 233 | "file": "client-go/api/model.go" 234 | } 235 | }, 236 | "ignore": [], 237 | "filter": [], 238 | "alias": { 239 | "properties": {}, 240 | "models": {}, 241 | "types": { 242 | "JsonNode": "any" 243 | }, 244 | "parameters": { 245 | "type": "kind" 246 | } 247 | }, 248 | "variables": {}, 249 | "generics": { 250 | "enable": true, 251 | "unfold": false, 252 | "expressions": { 253 | "ApiResult": [ 254 | "data" 255 | ] 256 | } 257 | }, 258 | "repeatable_operation_id": false 259 | }, 260 | { 261 | "name": "cs", 262 | "endpoint": "http://localhost:8080/v3/api-docs/api", 263 | "lang": "cs", 264 | "version": "v3", 265 | "output": { 266 | "api": { 267 | "header": [ 268 | "namespace demo;" 269 | ], 270 | "file": "client-cs/Api.cs" 271 | }, 272 | "client": { 273 | "header": [ 274 | "namespace demo;" 275 | ], 276 | "file": "client-cs/Client.cs" 277 | }, 278 | "model": { 279 | "header": [ 280 | "namespace demo;" 281 | ], 282 | "file": "client-cs/Model.cs" 283 | } 284 | }, 285 | "ignore": [], 286 | "filter": [], 287 | "alias": { 288 | "properties": {}, 289 | "models": {}, 290 | "types": { 291 | }, 292 | "parameters": { 293 | } 294 | }, 295 | "variables": {}, 296 | "generics": { 297 | "enable": true, 298 | "unfold": false, 299 | "expressions": { 300 | "ApiResult": [ 301 | "data" 302 | ] 303 | } 304 | }, 305 | "repeatable_operation_id": false 306 | }, 307 | { 308 | "name": "rust", 309 | "endpoint": "http://localhost:8080/v3/api-docs/api", 310 | "lang": "rust", 311 | "version": "v3", 312 | "output": { 313 | "api": { 314 | "file": "client-rs/src/api.rs" 315 | }, 316 | "client": { 317 | "file": "client-rs/src/client.rs" 318 | }, 319 | "model": { 320 | "file": "client-rs/src/model.rs" 321 | } 322 | }, 323 | "ignore": [], 324 | "filter": [], 325 | "alias": { 326 | "properties": { 327 | "type": "kind" 328 | }, 329 | "models": {}, 330 | "types": { 331 | "JsonNode": "String" 332 | }, 333 | "parameters": { 334 | "type": "kind" 335 | } 336 | }, 337 | "variables": {}, 338 | "generics": { 339 | "enable": true, 340 | "unfold": false, 341 | "expressions": { 342 | "ApiResult": [ 343 | "data" 344 | ] 345 | } 346 | }, 347 | "repeatable_operation_id": false 348 | }, 349 | { 350 | "name": "dart", 351 | "endpoint": "http://localhost:8080/v3/api-docs/api", 352 | "lang": "dart", 353 | "version": "v3", 354 | "output": { 355 | "api": { 356 | "file": "client-dart/src/api.dart" 357 | }, 358 | "client": { 359 | "file": "client-dart/src/client.dart" 360 | }, 361 | "model": { 362 | "file": "client-dart/src/model.dart" 363 | } 364 | }, 365 | "ignore": [], 366 | "filter": [], 367 | "alias": { 368 | "properties": { 369 | "type": "kind" 370 | }, 371 | "models": {}, 372 | "types": { 373 | "JsonNode": "String" 374 | }, 375 | "parameters": { 376 | "type": "kind" 377 | } 378 | }, 379 | "variables": {}, 380 | "generics": { 381 | "enable": true, 382 | "unfold": false, 383 | "expressions": { 384 | "ApiResult": [ 385 | "data" 386 | ] 387 | } 388 | }, 389 | "repeatable_operation_id": false 390 | } 391 | ] -------------------------------------------------------------------------------- /tmpl/cs/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | 6 | {{range $val :=.Apis}} 7 | {{template "api" $val}} 8 | {{end}} 9 | 10 | 11 | {{define "api"}} 12 | /** 13 | * {{.Description}} 14 | */ 15 | public class {{Capitalize .Name}} 16 | { 17 | 18 | private IApiClient client; 19 | 20 | public {{Capitalize .Name}}(IApiClient client) { 21 | this.client = client; 22 | } 23 | 24 | {{range $val :=.Paths -}} 25 | {{template "path" $val}} 26 | {{end}} 27 | } 28 | {{end}} 29 | 30 | 31 | {{define "path"}} 32 | /** 33 | * {{.Description}} 34 | * {{.Summary}} 35 | */ 36 | public Task<{{.Response.Expression}}> {{.Name}}({{template "parameters" .}}) { 37 | {{ if .Queries -}} 38 | var param = new Dictionary(); 39 | {{range $idx,$val := .Parameters -}} 40 | param["{{$val.Name}}"] = {{$val.Alias}}; 41 | {{end}} 42 | {{- else -}} 43 | {{if .Request}}{{else}}var param = new Dictionary();{{end}} 44 | {{- end}} 45 | return client.{{Capitalize .Method}}<{{if eq .Method "get"}}{{.Response.Expression}}{{else}},{{end}}>({{.Path}}, {{- if .Request}}body{{else}}param{{end}}); 46 | } 47 | {{end}} 48 | 49 | 50 | {{define "parameters"}} 51 | {{- range $idx,$val := .Parameters -}}{{if gt $idx 0}},{{end}} {{$val.Type.Expression}} {{$val.Alias}}{{- end -}} 52 | {{end}} 53 | -------------------------------------------------------------------------------- /tmpl/cs/client.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | using System; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Threading.Tasks; 10 | using System.Collections.Generic; 11 | using System.Web; 12 | 13 | public interface IApiClient 14 | { 15 | public Task Get(string path, Dictionary parameters) where T : class; 16 | 17 | public Task Post(string path, P parameters) where T : class where P : class; 18 | 19 | public Task Put(string path, P parameters) where T : class where P : class; 20 | 21 | public Task Delete(string path, P parameters) where T : class where P : class; 22 | } 23 | 24 | public class DefaultApiClient : IApiClient 25 | { 26 | private readonly HttpClient _httpClient; 27 | private readonly JsonSerializerOptions _jsonOptions; 28 | 29 | public DefaultApiClient(HttpClient httpClient) 30 | { 31 | _httpClient = httpClient; 32 | _jsonOptions = new JsonSerializerOptions 33 | { 34 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 35 | }; 36 | } 37 | 38 | public async Task Get(string path, Dictionary parameters) where T : class 39 | { 40 | var uriBuilder = new UriBuilder(_httpClient.BaseAddress + path); 41 | var query = HttpUtility.ParseQueryString(string.Empty); 42 | 43 | foreach (var kvp in parameters) 44 | { 45 | query[kvp.Key] = kvp.Value.ToString(); 46 | } 47 | 48 | uriBuilder.Query = query.ToString(); 49 | var response = await _httpClient.GetAsync(uriBuilder.ToString()); 50 | return await DeserializeResponse(response); 51 | } 52 | 53 | public async Task Post(string path, P parameters) where T : class where P : class 54 | { 55 | var content = SerializeContent(parameters); 56 | var response = await _httpClient.PostAsync(path, content); 57 | return await DeserializeResponse(response); 58 | } 59 | 60 | public async Task Put(string path, P parameters) where T : class where P : class 61 | { 62 | var content = SerializeContent(parameters); 63 | var response = await _httpClient.PutAsync(path, content); 64 | return await DeserializeResponse(response); 65 | } 66 | 67 | public async Task Delete(string path, P parameters) where T : class where P : class 68 | { 69 | var request = new HttpRequestMessage 70 | { 71 | Method = HttpMethod.Delete, 72 | RequestUri = new Uri(_httpClient.BaseAddress + path), 73 | Content = SerializeContent(parameters) 74 | }; 75 | var response = await _httpClient.SendAsync(request); 76 | return await DeserializeResponse(response); 77 | } 78 | 79 | private HttpContent SerializeContent(T obj) 80 | { 81 | var json = JsonSerializer.Serialize(obj, _jsonOptions); 82 | return new StringContent(json, Encoding.UTF8, "application/json"); 83 | } 84 | 85 | private async Task DeserializeResponse(HttpResponseMessage response) where T : class 86 | { 87 | response.EnsureSuccessStatusCode(); 88 | var json = await response.Content.ReadAsStringAsync(); 89 | return JsonSerializer.Deserialize(json, _jsonOptions); 90 | } 91 | } -------------------------------------------------------------------------------- /tmpl/cs/cs.go: -------------------------------------------------------------------------------- 1 | package cs 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | //go:embed api.tmpl 11 | var templateApi string 12 | 13 | //go:embed client.tmpl 14 | var templateClient string 15 | 16 | //go:embed model.tmpl 17 | var templateModel string 18 | 19 | var Templates = map[string]string{ 20 | "api": templateApi, 21 | "model": templateModel, 22 | "client": templateClient, 23 | } 24 | 25 | type Convert struct { 26 | } 27 | 28 | func (j *Convert) Reference(name string) string { 29 | return name 30 | } 31 | 32 | func (j *Convert) Foundation(name string, format string) string { 33 | switch name { 34 | case "integer", "number": 35 | if format == "int64" { 36 | return "long" 37 | } 38 | return "int" 39 | case "string": 40 | return "string" 41 | case "boolean": 42 | return "bool" 43 | default: 44 | return name 45 | } 46 | } 47 | 48 | func (j *Convert) Map(sub string) string { 49 | return fmt.Sprintf("Dictionary", sub) 50 | } 51 | 52 | func (j *Convert) Array(sub string) string { 53 | return fmt.Sprintf("List<%s>", sub) 54 | } 55 | 56 | func (j *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 57 | return fmt.Sprintf("%s<%s>", parentType, strings.Join(subTypes, ",")) 58 | } 59 | -------------------------------------------------------------------------------- /tmpl/cs/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | {{range $val :=.Models}} 6 | {{template "model" $val}} 7 | {{end}} 8 | 9 | 10 | {{define "model"}} 11 | 12 | /** 13 | * {{.Description}} 14 | * {{.Summary}} 15 | */ 16 | public class {{.Type.Expression}} 17 | { 18 | {{range $val :=.Properties -}} 19 | /** 20 | * {{.Description}} 21 | * {{.Summary}} 22 | */ 23 | public {{$val.Type.Expression}}? {{Capitalize $val.Alias}} { get; set; } 24 | {{end}} 25 | } 26 | {{end}} -------------------------------------------------------------------------------- /tmpl/doc.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "codegen/lang" 5 | "github.com/samber/lo" 6 | ) 7 | 8 | type Api struct { 9 | Name string 10 | Description string 11 | Paths []*Path 12 | } 13 | 14 | type Path struct { 15 | Tag string 16 | Name string 17 | Description string 18 | Summary string 19 | OriginalPath string 20 | Path string 21 | Method string 22 | Parameters Parameters 23 | Queries Parameters 24 | Request *NamedType 25 | Response *NamedType 26 | } 27 | 28 | type Output struct { 29 | Header []string `json:"header,omitempty"` //文件头信息 30 | File string `json:"file,omitempty"` //文件地址 31 | Template string `json:"template,omitempty"` //模版文件 32 | Variables map[string]string `json:"variables,omitempty"` //变量集 33 | } 34 | 35 | type Parameter struct { 36 | Name string 37 | Alias string 38 | Required bool 39 | In string 40 | Type *NamedType 41 | Format string 42 | Description string 43 | Default string 44 | } 45 | 46 | type NamedTypeKind uint 47 | 48 | const ( 49 | ImmutableType NamedTypeKind = 1 << iota 50 | 51 | FoundationType 52 | 53 | MapType 54 | 55 | ArrayType 56 | 57 | ReferenceType 58 | 59 | GenericType 60 | ) 61 | 62 | type NamedType struct { 63 | Kind NamedTypeKind 64 | Expression string 65 | } 66 | 67 | type Parameters = []*Parameter 68 | 69 | func (nt *NamedType) GenerateExpression(format string, convert lang.TypeConvert) { 70 | expression := nt.Expression 71 | 72 | if nt.Kind&ImmutableType != 0 { 73 | return 74 | } 75 | nt.Expression = nt.Kind.Parse(expression, format, convert) 76 | } 77 | 78 | func (nk NamedTypeKind) Parse(expression string, format string, convert lang.TypeConvert) string { 79 | 80 | if nk&FoundationType != 0 { 81 | expression = convert.Foundation(expression, format) 82 | } 83 | if nk&ReferenceType != 0 { 84 | expression = convert.Reference(expression) 85 | } 86 | if nk&ArrayType != 0 { 87 | expression = convert.Array(expression) 88 | } 89 | if nk&MapType != 0 { 90 | expression = convert.Map(expression) 91 | } 92 | return expression 93 | } 94 | 95 | type Ref struct { 96 | Name string 97 | Alias string 98 | Type *NamedType 99 | Properties Properties 100 | Description string 101 | Summary string 102 | Ignore bool 103 | } 104 | 105 | func (r *Ref) ReferenceLevel() int { 106 | 107 | return lo.Reduce(r.Properties, func(agg int, item *Property, index int) int { 108 | if item.Type.Kind&ReferenceType != 0 { 109 | return agg + 1 110 | } 111 | return agg 112 | }, 0) 113 | 114 | } 115 | 116 | type Properties []*Property 117 | 118 | func (p Properties) Find(name string) (*Property, bool) { 119 | return lo.Find(p, func(item *Property) bool { 120 | return item.Name == name 121 | }) 122 | } 123 | 124 | type Generic struct { 125 | Expression string 126 | Property string 127 | } 128 | 129 | type Property struct { 130 | Name string 131 | Alias string 132 | Description string 133 | Summary string 134 | Type *NamedType 135 | Format string 136 | Enums []string 137 | } 138 | -------------------------------------------------------------------------------- /tmpl/golang/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import "fmt" 6 | 7 | {{range $val :=.Apis}} 8 | {{template "api" $val}} 9 | {{end}} 10 | 11 | 12 | {{define "path"}} 13 | 14 | // {{Capitalize .Tag}}{{Capitalize .Name}} {{.Path}} 15 | type {{Capitalize .Tag}}{{Capitalize .Name}} struct { 16 | Client ApiClient[{{.Response.Expression}}] 17 | } 18 | 19 | // Call {{.Description}} 20 | func (receiver *{{Capitalize .Tag}}{{Capitalize .Name}}) Call({{template "parameters" .}}) ({{.Response.Expression}}, error) { 21 | {{if .Queries}} 22 | params := map[string]any{} 23 | {{range $idx,$val := .Queries -}} 24 | params["{{$val.Name}}"] = {{$val.Alias}} 25 | {{end -}} 26 | {{- else -}} 27 | {{if .Request}}{{else}}params := map[string]any{}{{end}} 28 | {{- end}} 29 | return receiver.Client.{{Capitalize .Method}}({{.Path}}, {{- if .Request}}body{{else}}params{{end}}) 30 | } 31 | {{end}} 32 | 33 | 34 | {{define "parameters"}} 35 | {{- range $idx,$val := .Parameters -}}{{if gt $idx 0}},{{end}} {{$val.Alias}} {{$val.Type.Expression}}{{- end -}} 36 | {{end}} 37 | 38 | {{define "api"}} 39 | {{range $val :=.Paths -}} 40 | {{template "path" $val}} 41 | {{end}} 42 | {{end}} -------------------------------------------------------------------------------- /tmpl/golang/client.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/url" 12 | ) 13 | 14 | 15 | // ApiClient TODO You need to implement the current interface 16 | type ApiClient[T any] interface { 17 | Get(name string, params map[string]any) (T, error) 18 | 19 | Post(name string, params any) (T, error) 20 | 21 | Put(name string, params any) (T, error) 22 | 23 | Delete(name string, params any) (T, error) 24 | } 25 | 26 | type DefaultClient[T any] struct { 27 | Host string 28 | Headers http.Header 29 | } 30 | 31 | func NewClient[T any](host string, headers http.Header) ApiClient[T] { 32 | return &DefaultClient[T]{host, headers} 33 | } 34 | 35 | func (t *DefaultClient[T]) Get(name string, params map[string]any) (T, error) { 36 | 37 | uri, _ := url.Parse(t.Host) 38 | uri.Path = name 39 | 40 | if params != nil { 41 | query := url.Values{} 42 | for k, v := range params { 43 | query.Set(k, fmt.Sprintf("%v", v)) 44 | } 45 | uri.RawQuery = query.Encode() 46 | } 47 | 48 | req := &http.Request{ 49 | Method: http.MethodGet, 50 | URL: uri, 51 | Header: t.Headers, 52 | } 53 | 54 | return t.request(req) 55 | } 56 | 57 | func (t *DefaultClient[T]) Post(name string, params any) (T, error) { 58 | return t.doBody(http.MethodPost, name, params) 59 | } 60 | 61 | func (t *DefaultClient[T]) Put(name string, params any) (T, error) { 62 | return t.doBody(http.MethodPut, name, params) 63 | } 64 | 65 | func (t *DefaultClient[T]) Delete(name string, params any) (T, error) { 66 | return t.doBody(http.MethodDelete, name, params) 67 | } 68 | 69 | func (t *DefaultClient[T]) doBody(method string, name string, params any) (T, error) { 70 | uri, _ := url.Parse(t.Host) 71 | uri.JoinPath(name) 72 | 73 | req := &http.Request{ 74 | Method: method, 75 | URL: uri, 76 | Header: t.Headers, 77 | } 78 | 79 | if params != nil { 80 | body, _ := json.Marshal(params) 81 | req.Body = io.NopCloser(bytes.NewBuffer(body)) 82 | } 83 | 84 | return t.request(req) 85 | } 86 | func (t *DefaultClient[T]) request(req *http.Request) (T, error) { 87 | 88 | var ret T 89 | 90 | 91 | resp, err := http.DefaultClient.Do(req) 92 | if err != nil { 93 | return ret, err 94 | } 95 | 96 | 97 | defer func() { 98 | _ = resp.Body.Close() 99 | }() 100 | body, err := io.ReadAll(resp.Body) 101 | if err != nil { 102 | return ret, err 103 | } 104 | 105 | 106 | err = json.Unmarshal(body, &ret) 107 | if err != nil { 108 | return ret, err 109 | } 110 | return ret, nil 111 | } -------------------------------------------------------------------------------- /tmpl/golang/golang.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "github.com/samber/lo" 8 | "strings" 9 | ) 10 | 11 | //go:embed api.tmpl 12 | var templateApi string 13 | 14 | //go:embed client.tmpl 15 | var templateClient string 16 | 17 | //go:embed model.tmpl 18 | var templateModel string 19 | 20 | var Templates = map[string]string{ 21 | "api": templateApi, 22 | "model": templateModel, 23 | "client": templateClient, 24 | } 25 | 26 | type Convert struct { 27 | } 28 | 29 | func (g *Convert) Reference(name string) string { 30 | return fmt.Sprintf("*%s", name) 31 | } 32 | 33 | func (g *Convert) Foundation(name string, format string) string { 34 | switch name { 35 | case "integer", "number": 36 | if format != "" { 37 | return format 38 | } 39 | return "int" 40 | case "boolean": 41 | return "bool" 42 | case "object": 43 | return "interface{}" 44 | default: 45 | return name 46 | } 47 | } 48 | 49 | func (g *Convert) Map(sub string) string { 50 | return fmt.Sprintf("map[string]%s", sub) 51 | } 52 | 53 | func (g *Convert) Array(sub string) string { 54 | return fmt.Sprintf("[]%s", sub) 55 | } 56 | 57 | func (g *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 58 | if mode == lang.TypeGenericMode { 59 | subTypes = lo.Map(subTypes, func(item string, index int) string { 60 | return fmt.Sprintf("%s any", item) 61 | }) 62 | } 63 | return fmt.Sprintf("%s[%s]", parentType, strings.Join(subTypes, ",")) 64 | } 65 | -------------------------------------------------------------------------------- /tmpl/golang/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | {{range $val :=.Models}} 6 | {{template "model" $val}} 7 | {{end}} 8 | 9 | 10 | {{define "model"}} 11 | // {{.Name}} {{if .Description}}{{.Description}}{{else}}{{.Type.Expression}}{{end}} 12 | type {{.Type.Expression}} struct { 13 | {{range $val :=.Properties -}} 14 | {{Capitalize $val.Alias}} {{$val.Type.Expression}} `json:"{{$val.Name}},omitempty"` //{{$val.Description}} 15 | {{end}} 16 | } 17 | {{end}} -------------------------------------------------------------------------------- /tmpl/java/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import com.fasterxml.jackson.core.type.TypeReference; 6 | import com.fasterxml.jackson.databind.JsonNode; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.HashMap; 10 | 11 | {{range $val :=.Apis}} 12 | {{template "api" $val}} 13 | {{end}} 14 | 15 | 16 | {{define "api"}} 17 | /** 18 | * {{.Description}} 19 | */ 20 | public class {{Capitalize .Name}} { 21 | 22 | private final ApiClient client; 23 | 24 | public {{Capitalize .Name}}(ApiClient client) { 25 | this.client = client; 26 | } 27 | 28 | {{range $val :=.Paths -}} 29 | {{template "path" $val}} 30 | {{end}} 31 | } 32 | {{end}} 33 | 34 | 35 | {{define "path"}} 36 | 37 | public static final TypeReference<{{.Response.Expression}}> {{.Name}}ResultType = new TypeReference<{{.Response.Expression}}>() { 38 | }; 39 | 40 | /** 41 | * {{.Description}} 42 | * {{.Summary}} 43 | */ 44 | public {{.Response.Expression}} {{.Name}}({{template "parameters" .}}) { 45 | {{ if .Queries -}} 46 | Map params = new HashMap(); 47 | {{range $idx,$val := .Parameters -}} 48 | params.put("{{$val.Name}}",{{$val.Alias}}); 49 | {{end}} 50 | {{- else -}} 51 | {{if .Request}}{{else}}Map params = new HashMap();{{end}} 52 | {{- end}} 53 | return client.{{.Method}}({{.Path}}, {{- if .Request}}body{{else}}params{{end}}, {{.Name}}ResultType); 54 | } 55 | {{end}} 56 | 57 | 58 | {{define "parameters"}} 59 | {{- range $idx,$val := .Parameters -}}{{if gt $idx 0}},{{end}} {{$val.Type.Expression}} {{$val.Alias}}{{- end -}} 60 | {{end}} 61 | -------------------------------------------------------------------------------- /tmpl/java/client.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import com.fasterxml.jackson.core.type.TypeReference; 6 | 7 | import java.util.Map; 8 | 9 | public interface ApiClient { 10 | 11 | T get(String path, Map params, TypeReference resultType); 12 | 13 | T put(String path, P body, TypeReference resultType); 14 | 15 | T post(String path, P params, TypeReference resultType); 16 | 17 | T delete(String path, P params, TypeReference resultType); 18 | } -------------------------------------------------------------------------------- /tmpl/java/java.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | //go:embed api.tmpl 11 | var templateApi string 12 | 13 | //go:embed client.tmpl 14 | var templateClient string 15 | 16 | //go:embed model.tmpl 17 | var templateModel string 18 | 19 | var Templates = map[string]string{ 20 | "api": templateApi, 21 | "model": templateModel, 22 | "client": templateClient, 23 | } 24 | 25 | type Convert struct { 26 | } 27 | 28 | func (j *Convert) Reference(name string) string { 29 | return name 30 | } 31 | 32 | func (j *Convert) Foundation(name string, format string) string { 33 | switch name { 34 | case "integer", "number": 35 | if format == "int64" { 36 | return "Long" 37 | } 38 | return "Integer" 39 | case "string": 40 | return "String" 41 | case "boolean": 42 | return "Boolean" 43 | case "object": 44 | return "JsonNode" 45 | default: 46 | return name 47 | } 48 | } 49 | 50 | func (j *Convert) Map(sub string) string { 51 | return fmt.Sprintf("Map", sub) 52 | } 53 | 54 | func (j *Convert) Array(sub string) string { 55 | return fmt.Sprintf("List<%s>", sub) 56 | } 57 | 58 | func (j *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 59 | return fmt.Sprintf("%s<%s>", parentType, strings.Join(subTypes, ",")) 60 | } 61 | -------------------------------------------------------------------------------- /tmpl/java/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | 7 | import java.io.Serializable; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | {{range $val :=.Models}} 12 | {{template "model" $val}} 13 | {{end}} 14 | 15 | 16 | {{define "model"}} 17 | 18 | /** 19 | * {{.Description}} 20 | * {{.Summary}} 21 | */ 22 | public class {{.Type.Expression}} implements Serializable { 23 | {{range $val :=.Properties -}} 24 | /** 25 | * {{.Description}} 26 | * {{.Summary}} 27 | */ 28 | private {{$val.Type.Expression}} {{$val.Alias}}; 29 | {{end}} 30 | 31 | {{range $val :=.Properties}} 32 | public {{$val.Type.Expression}} get{{Capitalize $val.Alias}}() { 33 | return {{$val.Alias}}; 34 | } 35 | 36 | public void set{{Capitalize $val.Name}}({{$val.Type.Expression}} {{$val.Alias}}) { 37 | this.{{$val.Alias}} = {{$val.Alias}}; 38 | } 39 | {{end}} 40 | } 41 | {{end}} -------------------------------------------------------------------------------- /tmpl/kotlin/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import java.io.Serializable 6 | import com.google.gson.reflect.TypeToken 7 | 8 | {{range $val :=.Apis}} 9 | {{template "api" $val}} 10 | {{end}} 11 | 12 | {{define "path"}} 13 | 14 | 15 | var {{.Name}}ResultType = object : TypeToken<{{.Response.Expression}}>() {}.type 16 | /** 17 | * {{.Description}} 18 | * {{.Summary}} 19 | */ 20 | suspend fun {{.Name}}({{template "parameters" .}}): Result<{{.Response.Expression}}> { 21 | {{if .Queries}} 22 | val params = HashMap() 23 | {{range $idx,$val := .Parameters -}} 24 | params["{{$val.Name}}"] = {{$val.Alias}} 25 | {{end}} 26 | {{- else -}} 27 | {{if .Request}}{{else}}val params = HashMap(){{end}} 28 | {{- end}} 29 | return client.{{.Method}}({{.Path}},{{.Name}}ResultType, {{- if .Request}}body{{else}}params{{end}}) 30 | } 31 | {{end}} 32 | 33 | 34 | {{define "parameters"}} 35 | {{- range $idx,$val := .Parameters -}}{{if gt $idx 0}},{{end}} {{$val.Alias}}: {{$val.Type.Expression}}{{- end -}} 36 | {{end}} 37 | 38 | 39 | {{define "api"}} 40 | /** 41 | * {{.Description}} 42 | */ 43 | class {{.Name}}(private var client: ApiClient) { 44 | {{range $val :=.Paths -}} 45 | {{template "path" $val}} 46 | {{end}} 47 | } 48 | {{end}} -------------------------------------------------------------------------------- /tmpl/kotlin/client.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import com.google.gson.Gson 6 | import okhttp3.MediaType 7 | import okhttp3.RequestBody 8 | import okhttp3.ResponseBody 9 | import retrofit2.Response 10 | import retrofit2.Retrofit 11 | import retrofit2.http.Body 12 | import retrofit2.http.DELETE 13 | import retrofit2.http.GET 14 | import retrofit2.http.POST 15 | import retrofit2.http.PUT 16 | import retrofit2.http.QueryMap 17 | import retrofit2.http.Url 18 | import java.lang.reflect.Type 19 | 20 | /** 21 | * TODO You need to implement the current interface, which can refer to Retorfit or OkHttp 22 | */ 23 | class ApiClient { 24 | 25 | suspend fun get(path: String, retType: Type, params: Map? = emptyMap()): Result { 26 | return toResult(defaultClient.get(path, params), retType) 27 | } 28 | 29 | suspend fun

post(path: String, retType: Type, params: P?): Result { 30 | return toResult(defaultClient.post(path, toBody(params)), retType) 31 | } 32 | 33 | suspend fun

put(path: String, retType: Type, params: P?): Result { 34 | return toResult(defaultClient.put(path, toBody(params)), retType) 35 | } 36 | 37 | suspend fun

delete(path: String, retType: Type, params: P?): Result { 38 | return toResult(defaultClient.delete(path, toBody(params)), retType) 39 | } 40 | 41 | private fun toResult(resp: Response, retType: Type): Result { 42 | return if (resp.isSuccessful) { 43 | Result.success(defaultGson.fromJson(resp.body()!!.charStream(), retType)) 44 | } else { 45 | Result.failure(Exception("Request failed with status code: ${resp.code()}")) 46 | } 47 | } 48 | 49 | private fun

toBody(params: P?): RequestBody? { 50 | return RequestBody.create(MediaType.parse("application/json"), defaultGson.toJson(params)) 51 | } 52 | } 53 | 54 | 55 | var defaultGson = Gson() 56 | private var defaultClient = 57 | Retrofit.Builder().baseUrl("{{.Env.Endpoint}}").build().create(RetrofitClient::class.java) 58 | 59 | interface RetrofitClient { 60 | 61 | @GET 62 | suspend fun get(@Url path: String, @QueryMap(encoded = true) params: Map?): Response 63 | 64 | @POST 65 | suspend fun post(@Url path: String, @Body params: RequestBody?): Response 66 | 67 | @PUT 68 | suspend fun put(@Url path: String, @Body params: RequestBody?): Response 69 | 70 | @DELETE 71 | suspend fun delete(@Url path: String, @Body params: RequestBody?): Response 72 | } 73 | -------------------------------------------------------------------------------- /tmpl/kotlin/kotlin.go: -------------------------------------------------------------------------------- 1 | package kotlin 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | //go:embed api.tmpl 11 | var templateApi string 12 | 13 | //go:embed client.tmpl 14 | var templateClient string 15 | 16 | //go:embed model.tmpl 17 | var templateModel string 18 | 19 | var Templates = map[string]string{ 20 | "api": templateApi, 21 | "model": templateModel, 22 | "client": templateClient, 23 | } 24 | 25 | type Convert struct { 26 | } 27 | 28 | func (k *Convert) Reference(name string) string { 29 | return name 30 | } 31 | 32 | func (k *Convert) Foundation(name string, format string) string { 33 | switch name { 34 | case "integer", "number": 35 | if format == "int64" { 36 | return "Long" 37 | } 38 | return "Int" 39 | case "string": 40 | return "String" 41 | case "boolean": 42 | return "Boolean" 43 | default: 44 | return name 45 | } 46 | } 47 | 48 | func (k *Convert) Map(sub string) string { 49 | return fmt.Sprintf("Map", sub) 50 | } 51 | 52 | func (k *Convert) Array(sub string) string { 53 | return fmt.Sprintf("List<%s>", sub) 54 | } 55 | 56 | func (k *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 57 | return fmt.Sprintf("%s<%s>", parentType, strings.Join(subTypes, ",")) 58 | } 59 | -------------------------------------------------------------------------------- /tmpl/kotlin/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import java.io.Serializable 6 | import com.google.gson.reflect.TypeToken 7 | 8 | {{range $val :=.Models}} 9 | {{template "model" $val}} 10 | {{end}} 11 | 12 | 13 | {{define "model"}} 14 | /** 15 | * {{.Description}} 16 | */ 17 | data class {{.Type.Expression}} ( 18 | {{range $val :=.Properties -}} 19 | //{{$val.Description}} 20 | {{- if $val.Enums -}}// Enums: {{$val.Enums}} {{- end}} 21 | var {{$val.Alias}}: {{$val.Type.Expression}}, 22 | {{end}} 23 | ) : Serializable 24 | {{end}} -------------------------------------------------------------------------------- /tmpl/python/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | from client import * 6 | from model import * 7 | 8 | from dataclasses import dataclass 9 | from typing import Generic, TypeVar, Optional, Any, Type, List, Dict 10 | 11 | from client import ApiClient 12 | 13 | T = TypeVar('T') 14 | A = TypeVar('A') 15 | B = TypeVar('B') 16 | C = TypeVar('C') 17 | P = TypeVar('P') 18 | 19 | 20 | {{range $val :=.Apis}} 21 | {{template "api" $val}} 22 | {{end}} 23 | 24 | 25 | {{define "api"}} 26 | class {{.Name}}: 27 | """ 28 | {{.Description}} 29 | """ 30 | client: ApiClient = None 31 | 32 | def __init__(self, client: ApiClient): 33 | self.client = client 34 | 35 | {{range $val :=.Paths -}} 36 | {{template "path" $val}} 37 | {{end}} 38 | 39 | {{end}} 40 | 41 | 42 | {{define "path"}} 43 | 44 | def {{.Name}}(self, {{template "parameters" .}}) -> Optional[{{.Response.Expression}}]: 45 | """ 46 | {{.Description}} 47 | """ 48 | {{if .Queries}} 49 | params = { {{range $idx,$val := .Parameters -}}"{{$val.Name}}": {{$val.Alias}},{{end}} } 50 | {{- else -}} 51 | {{if .Request}}{{else}}params = {}{{end}} 52 | {{- end}} 53 | return self.client.{{.Method}}({{.Path}}, {{.Response.Expression}}, {{- if .Request}}body{{else}}params{{end}}) 54 | {{end}} 55 | 56 | 57 | {{define "parameters"}} 58 | {{- range $idx,$val := .Parameters -}} {{if gt $idx 0}}, {{end}}{{$val.Alias}}:{{$val.Type.Expression}}{{end -}} 59 | {{end}} -------------------------------------------------------------------------------- /tmpl/python/client.tmpl: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar, Optional, Any, Type 2 | import requests 3 | from dataclasses import asdict 4 | 5 | T = TypeVar('T') 6 | P = TypeVar('P') 7 | 8 | 9 | class ApiClient: 10 | 11 | def get(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 12 | ... 13 | 14 | def post(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 15 | ... 16 | 17 | def put(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 18 | ... 19 | 20 | def delete(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 21 | ... 22 | 23 | 24 | 25 | 26 | class HttClient(ApiClient): 27 | BASE_URL = "{{.Env.Endpoint}}" 28 | 29 | def get(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 30 | url = f"{self.BASE_URL}{path}" 31 | return requests.get(url=url, params=params).json() 32 | 33 | def post(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 34 | url = f"{self.BASE_URL}{path}" 35 | return requests.post(url=url, json=asdict(params)).json() 36 | 37 | def put(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 38 | url = f"{self.BASE_URL}{path}" 39 | return requests.put(url=url, json=asdict(params)).json() 40 | 41 | def delete(self, path: str, mode: Type[T], params: P = None) -> Optional[T]: 42 | url = f"{self.BASE_URL}{path}" 43 | return requests.delete(url=url, json=asdict(params)).json() 44 | -------------------------------------------------------------------------------- /tmpl/python/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | 6 | from dataclasses import dataclass 7 | from typing import Generic, TypeVar, Optional, Any, Type, List, Dict 8 | 9 | from client import ApiClient 10 | 11 | T = TypeVar('T') 12 | A = TypeVar('A') 13 | B = TypeVar('B') 14 | C = TypeVar('C') 15 | P = TypeVar('P') 16 | 17 | {{range $val :=.Models}} 18 | {{template "model" $val}} 19 | {{end}} 20 | 21 | {{define "model"}} 22 | @dataclass 23 | class {{.Type.Expression}}: 24 | """ 25 | {{.Name}} {{.Description}} 26 | """ 27 | {{range $val :=.Properties -}} 28 | {{$val.Alias}}: {{$val.Type.Expression}} = None 29 | {{end}} 30 | {{end}} -------------------------------------------------------------------------------- /tmpl/python/python.go: -------------------------------------------------------------------------------- 1 | package python 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | //go:embed api.tmpl 11 | var templateApi string 12 | 13 | //go:embed client.tmpl 14 | var templateClient string 15 | 16 | //go:embed model.tmpl 17 | var templateModel string 18 | 19 | var Templates = map[string]string{ 20 | "api": templateApi, 21 | "model": templateModel, 22 | "client": templateClient, 23 | } 24 | 25 | type Convert struct { 26 | } 27 | 28 | func (p *Convert) Reference(name string) string { 29 | return name 30 | } 31 | 32 | func (p *Convert) Foundation(name string, format string) string { 33 | switch name { 34 | case "integer", "number": 35 | return "int" 36 | case "boolean": 37 | return "bool" 38 | case "object": 39 | return "Any" 40 | case "string": 41 | return "str" 42 | default: 43 | return name 44 | } 45 | } 46 | 47 | func (p *Convert) Map(sub string) string { 48 | return fmt.Sprintf("Dict[str,%s]", sub) 49 | } 50 | 51 | func (p *Convert) Array(sub string) string { 52 | return fmt.Sprintf("List[%s]", sub) 53 | } 54 | 55 | func (p *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 56 | if mode == lang.TypeGenericMode { 57 | return fmt.Sprintf("%s(Generic[%s])", parentType, strings.Join(subTypes, ", ")) 58 | } 59 | return fmt.Sprintf("%s[%s]", parentType, strings.Join(subTypes, ", ")) 60 | } 61 | -------------------------------------------------------------------------------- /tmpl/rust/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | use std::collections::HashMap; 6 | use std::io::Error; 7 | use std::iter::Map; 8 | use crate::Client::ApiClient; 9 | use crate::Model::*; 10 | 11 | 12 | {{range $val :=.Apis}} 13 | {{template "api" $val}} 14 | {{end}} 15 | 16 | 17 | {{define "api"}} 18 | /** 19 | * {{.Description}} 20 | */ 21 | pub struct {{Capitalize .Name}} { 22 | client: dyn ApiClient, 23 | } 24 | 25 | impl {{Capitalize .Name}} { 26 | 27 | {{range $val :=.Paths -}} 28 | {{template "path" $val}} 29 | {{end}} 30 | } 31 | {{end}} 32 | 33 | 34 | {{define "path"}} 35 | 36 | /** 37 | * {{.Description}} 38 | * {{.Summary}} 39 | */ 40 | async fn {{.Name}}(&self, {{template "parameters" .}}) -> Result<{{.Response.Expression}},Error> { 41 | {{ if .Queries -}} 42 | let mut params = HashMap::new(); 43 | {{range $idx,$val := .Parameters -}} 44 | params.insert("{{$val.Name}}",{{$val.Alias}}.to_string()); 45 | {{end}} 46 | {{- else -}} 47 | {{if .Request}}{{else}}let mut params = HashMap::new();{{end}} 48 | {{- end}} 49 | self.client.{{.Method}}(String::from({{.Path}}), {{- if .Request}}body{{else}}params{{end}}) 50 | } 51 | {{end}} 52 | 53 | 54 | {{define "parameters"}} 55 | {{- range $idx,$val := .Parameters -}}{{if gt $idx 0}},{{end}}{{$val.Alias}}: {{$val.Type.Expression}}{{- end -}} 56 | {{end}} 57 | -------------------------------------------------------------------------------- /tmpl/rust/client.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | use std::any::Any; 6 | use std::collections::HashMap; 7 | use std::io::Error; 8 | 9 | pub trait ApiClient { 10 | fn get(&self, path: String, params: HashMap) -> Result; 11 | fn post(&self, path: String, params: P) -> Result; 12 | fn put(&self, path: String, params: P) -> Result; 13 | fn delete(&self, path: String, params: P) -> Result; 14 | } -------------------------------------------------------------------------------- /tmpl/rust/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | use serde::{Deserialize, Serialize}; 6 | use std::iter::Map; 7 | 8 | 9 | {{range $val :=.Models}} 10 | {{template "model" $val}} 11 | {{end}} 12 | 13 | 14 | {{define "model"}} 15 | 16 | /** 17 | * {{.Description}} 18 | * {{.Summary}} 19 | */ 20 | #[derive(Debug, Clone)] 21 | pub struct {{.Type.Expression}} { 22 | {{range $val :=.Properties -}} 23 | /** 24 | * {{.Description}} 25 | * {{.Summary}} 26 | */ 27 | pub {{$val.Alias}}: Option<{{$val.Type.Expression}}>, 28 | {{end}} 29 | } 30 | {{end}} -------------------------------------------------------------------------------- /tmpl/rust/rust.go: -------------------------------------------------------------------------------- 1 | package rust 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | //go:embed api.tmpl 11 | var templateApi string 12 | 13 | //go:embed client.tmpl 14 | var templateClient string 15 | 16 | //go:embed model.tmpl 17 | var templateModel string 18 | 19 | var Templates = map[string]string{ 20 | "api": templateApi, 21 | "model": templateModel, 22 | "client": templateClient, 23 | } 24 | 25 | type Convert struct { 26 | } 27 | 28 | func (j *Convert) Reference(name string) string { 29 | return name 30 | } 31 | 32 | func (j *Convert) Foundation(name string, format string) string { 33 | switch name { 34 | case "integer", "number": 35 | if format == "int64" { 36 | return "u64" 37 | } 38 | return "usize" 39 | case "string": 40 | return "String" 41 | case "boolean": 42 | return "bool" 43 | default: 44 | return name 45 | } 46 | } 47 | 48 | func (j *Convert) Map(sub string) string { 49 | return fmt.Sprintf("Map", sub) 50 | } 51 | 52 | func (j *Convert) Array(sub string) string { 53 | return fmt.Sprintf("Vec<%s>", sub) 54 | } 55 | 56 | func (j *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 57 | return fmt.Sprintf("%s<%s>", parentType, strings.Join(subTypes, ",")) 58 | } 59 | -------------------------------------------------------------------------------- /tmpl/swift/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | {{range $val :=.Apis}} 6 | {{template "api" $val}} 7 | {{end}} 8 | 9 | 10 | 11 | {{define "parameters"}} 12 | {{- range $idx,$val := .Parameters -}}{{if gt $idx 0}},{{end}} {{$val.Alias}}: {{$val.Type.Expression}}{{- end -}} 13 | {{end}} 14 | 15 | {{define "path"}} 16 | /** 17 | * {{.Description}} 18 | */ 19 | func {{.Name}}({{template "parameters" .}}) async throws -> {{.Response.Expression}} { 20 | {{if .Queries}} 21 | var params: [String: Any?] = [:] 22 | {{range $idx,$val := .Queries -}} 23 | params["{{$val.Name}}"] = {{$val.Alias}} 24 | {{end -}} 25 | {{- else -}} 26 | {{if .Request}}{{else}}var params: [String: Any?] = [:]{{end}} 27 | {{- end}} 28 | return try await client.{{.Method}}({{.Path}}, {{- if .Request}}body{{else}}params{{end}} ,{{.Response.Expression}}.self) 29 | } 30 | {{end}} 31 | 32 | {{define "api"}} 33 | /** 34 | * {{.Description}} 35 | */ 36 | class {{.Name}} { 37 | 38 | var client: ApiClient 39 | 40 | init(client: ApiClient) { 41 | self.client = client 42 | } 43 | 44 | {{range $val :=.Paths -}} 45 | {{template "path" $val}} 46 | {{end}} 47 | } 48 | {{end}} 49 | 50 | 51 | -------------------------------------------------------------------------------- /tmpl/swift/client.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import Alamofire 6 | import Foundation 7 | 8 | /** 9 | * TODO You need to implement the current protocol, which can refer to Alamofire or URLSession 10 | */ 11 | protocol ApiClient { 12 | func get(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 13 | 14 | func post(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 15 | 16 | func put(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 17 | 18 | func delete(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T 19 | } 20 | 21 | var BASE_URL = "{{.Env.Endpoint}}" 22 | 23 | struct AlamofireApiClient: ApiClient { 24 | var headers: HTTPHeaders? 25 | 26 | func get(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 27 | let reqParams: Parameters? = params as? [String: Any] 28 | return try await AF.request("\(BASE_URL)\(path)", 29 | method: .get, 30 | parameters: reqParams, 31 | encoding: URLEncoding.default, 32 | headers: headers) 33 | .serializingDecodable(resultType.self).value 34 | } 35 | 36 | func post(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 37 | let reqParams: Parameters? = params as? Dictionary 38 | return try await AF.request("\(BASE_URL)\(path)", 39 | method: .post, 40 | parameters: reqParams, 41 | encoding: JSONEncoding.default, 42 | headers: headers) 43 | .serializingDecodable(resultType.self).value 44 | } 45 | 46 | func put(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 47 | let reqParams: Parameters? = params as? Dictionary 48 | return try await AF.request("\(BASE_URL)\(path)", 49 | method: .put, 50 | parameters: reqParams, 51 | encoding: JSONEncoding.default, 52 | headers: headers) 53 | .serializingDecodable(resultType.self).value 54 | } 55 | 56 | func delete(_ path: String, _ params: Any?, _ resultType: T.Type) async throws -> T where T: Decodable, T: Encodable { 57 | let reqParams: Parameters? = params as? Dictionary 58 | return try await AF.request("\(BASE_URL)\(path)", 59 | method: .delete, 60 | parameters: reqParams, 61 | encoding: JSONEncoding.default, 62 | headers: headers) 63 | .serializingDecodable(resultType.self).value 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tmpl/swift/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | {{range $val :=.Models}} 6 | {{template "model" $val}} 7 | {{end}} 8 | 9 | {{define "model"}} 10 | /** 11 | * {{.Description}} 12 | */ 13 | public struct {{.Type.Expression}}:Codable { 14 | {{range $val :=.Properties -}} 15 | //{{$val.Description}} 16 | {{- if $val.Enums -}}// Enums: {{$val.Enums}} {{- end}} 17 | var {{$val.Alias}}: {{$val.Type.Expression}}? 18 | {{end}} 19 | } 20 | {{end}} -------------------------------------------------------------------------------- /tmpl/swift/swift.go: -------------------------------------------------------------------------------- 1 | package swift 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "github.com/samber/lo" 8 | "strings" 9 | ) 10 | 11 | //go:embed api.tmpl 12 | var templateApi string 13 | 14 | //go:embed client.tmpl 15 | var templateClient string 16 | 17 | //go:embed model.tmpl 18 | var templateModel string 19 | 20 | var Templates = map[string]string{ 21 | "api": templateApi, 22 | "model": templateModel, 23 | "client": templateClient, 24 | } 25 | 26 | type Convert struct { 27 | } 28 | 29 | func (s *Convert) Reference(name string) string { 30 | return name 31 | } 32 | 33 | func (s *Convert) Foundation(name string, format string) string { 34 | switch name { 35 | case "integer", "number": 36 | if format == "int64" { 37 | return "Int64" 38 | } 39 | return "Int" 40 | case "string": 41 | return "String" 42 | case "boolean": 43 | return "Bool" 44 | default: 45 | return name 46 | } 47 | } 48 | 49 | func (s *Convert) Array(sub string) string { 50 | return fmt.Sprintf("[%s]", sub) 51 | } 52 | 53 | func (s *Convert) Map(sub string) string { 54 | return fmt.Sprintf("[String:%s]", sub) 55 | } 56 | 57 | func (s *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 58 | if mode == lang.TypeGenericMode { 59 | subTypes = lo.Map(subTypes, func(item string, index int) string { 60 | return fmt.Sprintf("%s: Codable", item) 61 | }) 62 | } 63 | return fmt.Sprintf("%s<%s>", parentType, strings.Join(subTypes, ",")) 64 | } 65 | -------------------------------------------------------------------------------- /tmpl/template.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "codegen/tmpl/cs" 5 | "codegen/tmpl/golang" 6 | "codegen/tmpl/java" 7 | "codegen/tmpl/kotlin" 8 | "codegen/tmpl/python" 9 | "codegen/tmpl/rust" 10 | "codegen/tmpl/swift" 11 | "codegen/tmpl/ts" 12 | _ "embed" 13 | "errors" 14 | "fmt" 15 | "os" 16 | "path/filepath" 17 | "text/template" 18 | "unicode" 19 | ) 20 | 21 | var register = map[string]map[string]string{ 22 | "ts": ts.Templates, 23 | "go": golang.Templates, 24 | "swift": swift.Templates, 25 | "python": python.Templates, 26 | "kotlin": kotlin.Templates, 27 | "java": java.Templates, 28 | "cs": cs.Templates, //TODO 29 | "rust": rust.Templates, //TODO 30 | } 31 | 32 | func Capitalize(s string) string { 33 | if s == "" { 34 | return "" 35 | } 36 | runes := []rune(s) 37 | runes[0] = unicode.ToUpper(runes[0]) 38 | return string(runes) 39 | } 40 | 41 | func NewEngine(lang string, name string, style string, variable map[string]string) (*template.Template, error) { 42 | tp := template.New(lang) 43 | 44 | var tmplFunc = template.FuncMap{ 45 | "Capitalize": Capitalize, 46 | "Variable": func(key string) string { 47 | return variable[key] 48 | }, 49 | } 50 | tp.Funcs(tmplFunc) 51 | 52 | //自定义模版 53 | if style != "" { 54 | return tp.ParseFiles(PwdJoinPath(style)) 55 | } 56 | 57 | mapping, ok := register[lang] 58 | if !ok { 59 | return nil, errors.New("invalid language") 60 | } 61 | define, ok := mapping[name] 62 | if !ok { 63 | return nil, errors.New("invalid language template name") 64 | } 65 | return tp.Parse(define) 66 | } 67 | 68 | func PwdJoinPath(name string) string { 69 | if filepath.IsAbs(name) { 70 | return filepath.Clean(name) 71 | } 72 | pwd, _ := os.Getwd() 73 | return filepath.Join(pwd, name) 74 | } 75 | 76 | var langFileSuffix = map[string]string{ 77 | "python": "py", 78 | "golang": "go", 79 | "kotlin": "kt", 80 | "rust": "rs", 81 | } 82 | 83 | func NewOutputs(lang string) map[string]*Output { 84 | 85 | suffix, ok := langFileSuffix[lang] 86 | if !ok { 87 | suffix = lang 88 | } 89 | 90 | return map[string]*Output{ 91 | "api": { 92 | Header: []string{}, 93 | File: fmt.Sprintf("api.%s", suffix), 94 | Variables: map[string]string{}, 95 | }, 96 | "model": { 97 | Header: []string{}, 98 | File: fmt.Sprintf("model.%s", suffix), 99 | Variables: map[string]string{}, 100 | }, 101 | "client": { 102 | Header: []string{}, 103 | File: fmt.Sprintf("client.%s", suffix), 104 | Variables: map[string]string{}, 105 | }, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tmpl/ts/api.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import {ApiClient} from './client'; 6 | import { 7 | {{range $val :=.Models}} 8 | {{- $val.Alias}}, 9 | {{end}} 10 | } from './model'; 11 | 12 | 13 | {{range $val :=.Apis}} 14 | {{template "api" $val}} 15 | {{end}} 16 | 17 | 18 | {{define "api"}} 19 | /** 20 | * {{.Description}} 21 | */ 22 | export const {{.Name}} = { 23 | {{range $val :=.Paths -}} 24 | {{template "path" $val}} 25 | {{end}} 26 | } 27 | {{end}} 28 | 29 | 30 | {{define "parameters"}} 31 | {{- range $idx,$val := .Parameters -}}{{if gt $idx 0}},{{end}} {{$val.Alias}}: {{$val.Type.Expression}}{{- end -}} 32 | {{end}} 33 | 34 | 35 | {{define "path"}} 36 | /** 37 | * {{.Summary}} 38 | * {{.Description}} 39 | */ 40 | {{.Name}}: async ({{template "parameters" .}}) => { 41 | {{if .Queries}} 42 | let params = { {{- range $idx,$val := .Queries -}}{{if gt $idx 0}},{{end}} {{$val.Name}}: {{$val.Alias}}{{- end -}} }; 43 | {{- else -}} 44 | {{if .Request}}{{else}}let params = {};{{end}} 45 | {{- end}} 46 | return await ApiClient.{{.Method}}({{.Path}},{{- if .Request}}body{{else}}params{{end}}) as {{.Response.Expression}} 47 | }, 48 | {{end}} -------------------------------------------------------------------------------- /tmpl/ts/client.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | import axios, {AxiosError, AxiosResponse} from "axios"; 6 | 7 | export const axiosClient = axios.create({ 8 | baseURL: "{{.Env.Endpoint}}", 9 | headers: { 10 | 'Content-Type': 'application/json;charset=utf-8' 11 | }, 12 | timeout: Number(60000), 13 | withCredentials: false 14 | }) 15 | 16 | axiosClient.interceptors.response.use((response: AxiosResponse) => { 17 | return response.data 18 | }, (error: AxiosError) => { 19 | return Promise.reject(error) 20 | }) 21 | 22 | export const ApiClient = { 23 | 24 | get: async (path: string, params?: any) => { 25 | return await axiosClient.get(path,{params:params}) as any 26 | }, 27 | 28 | post: async (path: string, body?: any) => { 29 | return await axiosClient.post(path, body) as any 30 | }, 31 | 32 | put: async (path: string, body?: any) => { 33 | return await axiosClient.put(path, body) as any 34 | }, 35 | 36 | delete: async (path: string, body?: any) => { 37 | return await axiosClient.delete(path, body) as any 38 | }, 39 | } -------------------------------------------------------------------------------- /tmpl/ts/model.tmpl: -------------------------------------------------------------------------------- 1 | {{range $val :=.Output.Header}} 2 | {{- $val}} 3 | {{end}} 4 | 5 | {{define "model"}} 6 | /** 7 | * {{.Description}} 8 | */ 9 | export interface {{.Type.Expression}} { 10 | {{range $val :=.Properties -}} 11 | //{{$val.Description}} 12 | {{- if $val.Enums -}}// Enums: {{$val.Enums}} {{- end}} 13 | {{$val.Alias}}?: {{$val.Type.Expression}} 14 | {{end}} 15 | } 16 | {{end}} 17 | 18 | 19 | {{range $val :=.Models}} 20 | {{template "model" $val}} 21 | {{end}} 22 | 23 | -------------------------------------------------------------------------------- /tmpl/ts/ts.go: -------------------------------------------------------------------------------- 1 | package ts 2 | 3 | import ( 4 | "codegen/lang" 5 | _ "embed" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | //go:embed api.tmpl 11 | var templateApi string 12 | 13 | //go:embed client.tmpl 14 | var templateClient string 15 | 16 | //go:embed model.tmpl 17 | var templateModel string 18 | 19 | var Templates = map[string]string{ 20 | "api": templateApi, 21 | "model": templateModel, 22 | "client": templateClient, 23 | } 24 | 25 | type Convert struct { 26 | } 27 | 28 | func (t *Convert) Reference(name string) string { 29 | return name 30 | } 31 | 32 | func (t *Convert) Foundation(name string, format string) string { 33 | switch name { 34 | case "integer", "int", "long": 35 | return "number" 36 | case "object": 37 | return "any" 38 | default: 39 | return name 40 | } 41 | } 42 | 43 | func (t *Convert) Array(sub string) string { 44 | return fmt.Sprintf("%s[]", sub) 45 | } 46 | 47 | func (t *Convert) Map(sub string) string { 48 | return fmt.Sprintf("Record", sub) 49 | } 50 | 51 | func (t *Convert) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 52 | return fmt.Sprintf("%s<%s>", parentType, strings.Join(subTypes, ",")) 53 | } 54 | -------------------------------------------------------------------------------- /tmpl/types.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "codegen/lang" 5 | "codegen/tmpl/cs" 6 | "codegen/tmpl/golang" 7 | "codegen/tmpl/java" 8 | "codegen/tmpl/kotlin" 9 | "codegen/tmpl/python" 10 | "codegen/tmpl/rust" 11 | "codegen/tmpl/swift" 12 | "codegen/tmpl/ts" 13 | "fmt" 14 | "github.com/samber/lo" 15 | ) 16 | 17 | var handlers = map[string]lang.TypeConvert{} 18 | 19 | func init() { 20 | handlers["ts"] = &ts.Convert{} 21 | handlers["swift"] = &swift.Convert{} 22 | handlers["java"] = &java.Convert{} 23 | handlers["go"] = &golang.Convert{} 24 | handlers["kotlin"] = &kotlin.Convert{} 25 | handlers["python"] = &python.Convert{} 26 | handlers["cs"] = &cs.Convert{} 27 | handlers["rust"] = &rust.Convert{} 28 | } 29 | 30 | func LangNames() []string { 31 | return lo.Keys(handlers) 32 | } 33 | 34 | func NewLangConvert(targetLang string, extendTypes map[string]string) lang.TypeConvert { 35 | return &extendConverts{ 36 | handler: handlers, 37 | mapping: extendTypes, 38 | target: targetLang, 39 | } 40 | } 41 | 42 | type extendConverts struct { 43 | handler map[string]lang.TypeConvert 44 | mapping map[string]string 45 | target string 46 | } 47 | 48 | func (l *extendConverts) Reference(name string) string { 49 | v, ok := l.mapping[name] 50 | if ok { 51 | return v 52 | } 53 | return l.handler[l.target].Reference(name) 54 | } 55 | 56 | func (l *extendConverts) Foundation(name string, format string) string { 57 | 58 | v, ok := l.mapping[name] 59 | if ok { 60 | return v 61 | } 62 | 63 | //组合类型 type + format 64 | combination := fmt.Sprintf("%s+%s", name, format) 65 | v, ok = l.mapping[combination] 66 | if ok { 67 | return v 68 | } 69 | 70 | return l.handler[l.target].Foundation(name, format) 71 | } 72 | 73 | func (l *extendConverts) Array(sub string) string { 74 | return l.handler[l.target].Array(sub) 75 | } 76 | 77 | func (l *extendConverts) Map(sub string) string { 78 | return l.handler[l.target].Map(sub) 79 | } 80 | 81 | func (l *extendConverts) Generic(parentType string, mode lang.GenericMode, subTypes ...string) string { 82 | return l.handler[l.target].Generic(parentType, mode, subTypes...) 83 | } 84 | -------------------------------------------------------------------------------- /v2/doc.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | type Doc struct { 4 | Swagger string `json:"openapi,omitempty"` 5 | Info struct { 6 | Title string `json:"title,omitempty"` 7 | Version string `json:"version,omitempty"` 8 | } `json:"info,omitempty"` 9 | Host string 10 | BasePath string 11 | Tags []Tag `json:"tags,omitempty"` 12 | Paths map[string]Path `json:"paths,omitempty"` 13 | Definitions map[string]Mode `json:"definitions,omitempty"` 14 | } 15 | -------------------------------------------------------------------------------- /v2/parser.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "codegen/tmpl" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/samber/lo" 8 | "io" 9 | "net/http" 10 | ) 11 | 12 | func LoadParse(addr string) ([]*tmpl.Ref, []*tmpl.Api, error) { 13 | 14 | resp, err := http.Get(addr) 15 | if err != nil { 16 | return nil, nil, err 17 | } 18 | if resp.StatusCode != http.StatusOK { 19 | return nil, nil, fmt.Errorf("%s %s", http.StatusText(resp.StatusCode), addr) 20 | } 21 | 22 | defer func() { 23 | _ = resp.Body.Close() 24 | }() 25 | 26 | body, _ := io.ReadAll(resp.Body) 27 | var doc = &Doc{} 28 | err = json.Unmarshal(body, doc) 29 | if err != nil { 30 | return nil, nil, err 31 | } 32 | 33 | refs := make([]*tmpl.Ref, 0) 34 | 35 | toPropertyType := func(info Property) *tmpl.NamedType { 36 | return schemaType(info.Schema).Parse() 37 | } 38 | 39 | toProperties := func(info Mode) tmpl.Properties { 40 | 41 | array := make(tmpl.Properties, 0) 42 | for name, property := range info.Properties { 43 | 44 | array = append(array, &tmpl.Property{ 45 | Name: name, 46 | Description: property.Description, 47 | Type: toPropertyType(property), 48 | Format: property.Format, 49 | Enums: []string{}, 50 | }) 51 | } 52 | return array 53 | } 54 | 55 | schemas := doc.Definitions 56 | for name, mode := range schemas { 57 | 58 | ref := &tmpl.Ref{ 59 | Name: name, 60 | Type: &tmpl.NamedType{ 61 | Kind: tmpl.ReferenceType, 62 | Expression: name, 63 | }, 64 | Properties: toProperties(mode), 65 | Description: mode.Description, 66 | } 67 | 68 | refs = append(refs, ref) 69 | } 70 | 71 | toParameters := func(method string, info Method) tmpl.Parameters { 72 | parameters := make([]*tmpl.Parameter, 0) 73 | for _, item := range info.Parameters { 74 | 75 | if item.In == "body" { 76 | continue 77 | } 78 | 79 | tp := schemaType(item.Schema).Parse() 80 | 81 | parameters = append(parameters, &tmpl.Parameter{ 82 | Name: item.Name, 83 | Required: item.Required, 84 | Type: tp, 85 | In: item.In, 86 | Format: item.Schema.Format, 87 | Description: item.Description, 88 | }) 89 | } 90 | return parameters 91 | } 92 | 93 | toRequest := func(method string, info Method) (nt *tmpl.NamedType) { 94 | 95 | req, ok := lo.Find(info.Parameters, func(item Parameter) bool { 96 | return item.In == "body" 97 | }) 98 | 99 | if !ok { 100 | return nil 101 | } 102 | 103 | return schemaType(req.Schema).Parse() 104 | } 105 | 106 | toResponse := func(method string, info Method) *tmpl.NamedType { 107 | response := info.Responses["200"] 108 | return schemaType(response.Schema).Parse() 109 | } 110 | 111 | paths := make([]*tmpl.Path, 0) 112 | for path, item := range doc.Paths { 113 | for method, fn := range item { 114 | 115 | p := &tmpl.Path{ 116 | Tag: lo.Ternary(len(fn.Tags) > 0, fn.Tags[0], "UnnamedApi"), 117 | Name: fn.OperationId, 118 | Description: fn.Description, 119 | Summary: fn.Summary, 120 | Path: path, 121 | OriginalPath: path, 122 | Method: method, 123 | Parameters: toParameters(method, fn), 124 | Request: toRequest(method, fn), 125 | Response: toResponse(method, fn), 126 | } 127 | paths = append(paths, p) 128 | } 129 | } 130 | 131 | groups := lo.GroupBy(paths, func(item *tmpl.Path) string { 132 | return item.Tag 133 | }) 134 | 135 | apis := make([]*tmpl.Api, 0) 136 | for _, tag := range doc.Tags { 137 | apis = append(apis, &tmpl.Api{ 138 | Name: tag.Name, 139 | Description: tag.Description, 140 | Paths: groups[tag.Name], 141 | }) 142 | } 143 | 144 | return refs, apis, nil 145 | } 146 | -------------------------------------------------------------------------------- /v2/schema.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "cmp" 5 | "codegen/tmpl" 6 | "github.com/samber/lo" 7 | "strings" 8 | ) 9 | 10 | type Tag struct { 11 | Name string `json:"name,omitempty"` 12 | Description string `json:"description,omitempty"` 13 | } 14 | 15 | type Path = map[string]Method 16 | 17 | type Method struct { 18 | Tags []string `json:"tags,omitempty"` 19 | Summary string `json:"summary,omitempty"` 20 | Description string `json:"description,omitempty"` 21 | OperationId string `json:"operationId,omitempty"` 22 | Responses map[string]Response `json:"responses,omitempty"` 23 | Parameters []Parameter `json:"parameters,omitempty"` 24 | } 25 | 26 | type Response struct { 27 | Description string `json:"description,omitempty"` 28 | Schema Schema `json:"schema,omitempty"` 29 | } 30 | 31 | type Schema struct { 32 | Type string `json:"type,omitempty"` 33 | Format string `json:"format,omitempty"` 34 | Ref string `json:"$ref,omitempty"` 35 | Items *struct { 36 | Type string `json:"type,omitempty"` 37 | Ref string `json:"$ref,omitempty"` 38 | } `json:"items,omitempty"` 39 | AdditionalProperties *struct { 40 | Type string `json:"type,omitempty"` 41 | Ref string `json:"$ref,omitempty"` 42 | } `json:"additionalProperties,omitempty"` 43 | Default interface{} `json:"default,omitempty"` 44 | } 45 | 46 | type Parameter struct { 47 | Schema 48 | Name string `json:"name,omitempty"` 49 | In string `json:"in,omitempty"` 50 | Required bool `json:"required,omitempty"` 51 | Description string `json:"description,omitempty"` 52 | } 53 | 54 | type Mode struct { 55 | Type string `json:"type,omitempty"` 56 | Properties map[string]Property `json:"properties,omitempty"` 57 | Description string `json:"description,omitempty"` 58 | } 59 | 60 | type Property struct { 61 | Schema 62 | Description string `json:"description,omitempty"` 63 | Enum []any `json:"enum,omitempty"` 64 | } 65 | 66 | type schemaType Schema 67 | 68 | func (s schemaType) Parse() *tmpl.NamedType { 69 | 70 | var expression string 71 | var kind tmpl.NamedTypeKind 72 | 73 | if s.Ref != "" { 74 | kind = tmpl.ReferenceType 75 | expression = s.Ref 76 | } else { 77 | if s.Items != nil { 78 | 79 | expression = cmp.Or(s.Items.Type, s.Items.Ref) 80 | kind = lo.Ternary(expression == s.Items.Type, tmpl.ArrayType|tmpl.FoundationType, tmpl.ArrayType|tmpl.ReferenceType) 81 | } else if s.AdditionalProperties != nil { 82 | 83 | expression = cmp.Or(s.AdditionalProperties.Type, s.AdditionalProperties.Ref) 84 | kind = lo.Ternary(expression == s.AdditionalProperties.Type, tmpl.MapType|tmpl.FoundationType, tmpl.MapType|tmpl.ReferenceType) 85 | } else { 86 | expression = cmp.Or(s.Type, s.Ref) 87 | kind = lo.Ternary(expression == s.Type, tmpl.FoundationType, tmpl.ReferenceType) 88 | } 89 | } 90 | 91 | if expression == "" { 92 | return nil 93 | } 94 | 95 | return &tmpl.NamedType{Kind: kind, Expression: ModePath(expression).BaseName()} 96 | } 97 | 98 | type ModePath string 99 | 100 | func (r ModePath) BaseName() string { 101 | x := r[strings.LastIndex(string(r), "/")+1:] 102 | return string(x) 103 | } 104 | -------------------------------------------------------------------------------- /v3/doc.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | type Doc struct { 4 | Openapi string `json:"openapi,omitempty"` 5 | Info struct { 6 | Title string `json:"title,omitempty"` 7 | Version string `json:"version,omitempty"` 8 | } `json:"info,omitempty"` 9 | Servers []struct { 10 | Url string `json:"url,omitempty"` 11 | Description string `json:"description,omitempty"` 12 | } `json:"servers,omitempty"` 13 | Tags []Tag `json:"tags,omitempty"` 14 | Paths map[string]Path `json:"paths,omitempty"` 15 | Components struct { 16 | Schemas map[string]Mode `json:"schemas,omitempty"` 17 | } `json:"components,omitempty"` 18 | } 19 | -------------------------------------------------------------------------------- /v3/parser.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import ( 4 | "cmp" 5 | "codegen/tmpl" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/samber/lo" 9 | "io" 10 | "net/http" 11 | ) 12 | 13 | func LoadParse(addr string) ([]*tmpl.Ref, []*tmpl.Api, error) { 14 | 15 | resp, err := http.Get(addr) 16 | if err != nil { 17 | return nil, nil, err 18 | } 19 | if resp.StatusCode != http.StatusOK { 20 | return nil, nil, fmt.Errorf("%s %s", addr, http.StatusText(resp.StatusCode)) 21 | } 22 | 23 | defer func() { 24 | _ = resp.Body.Close() 25 | }() 26 | 27 | body, _ := io.ReadAll(resp.Body) 28 | var doc = &Doc{} 29 | err = json.Unmarshal(body, doc) 30 | if err != nil { 31 | return nil, nil, err 32 | } 33 | 34 | refs := make([]*tmpl.Ref, 0) 35 | 36 | toPropertyType := func(info Property) *tmpl.NamedType { 37 | return schemaType(info.Schema).Parse() 38 | } 39 | 40 | toProperties := func(info Mode) tmpl.Properties { 41 | 42 | array := make(tmpl.Properties, 0) 43 | for name, property := range info.Properties { 44 | 45 | array = append(array, &tmpl.Property{ 46 | Name: name, 47 | Description: property.Description, 48 | Type: toPropertyType(property), 49 | Format: property.Format, 50 | Enums: []string{}, 51 | }) 52 | } 53 | return array 54 | } 55 | 56 | schemas := doc.Components.Schemas 57 | for name, mode := range schemas { 58 | 59 | ref := &tmpl.Ref{ 60 | Name: name, 61 | Type: &tmpl.NamedType{ 62 | Kind: tmpl.ReferenceType, 63 | Expression: name, 64 | }, 65 | Properties: toProperties(mode), 66 | Description: mode.Description, 67 | } 68 | 69 | refs = append(refs, ref) 70 | } 71 | 72 | toParameters := func(method string, info Method) tmpl.Parameters { 73 | parameters := make([]*tmpl.Parameter, 0) 74 | for _, item := range info.Parameters { 75 | 76 | tp := schemaType(item.Schema).Parse() 77 | 78 | parameters = append(parameters, &tmpl.Parameter{ 79 | Name: item.Name, 80 | Required: item.Required, 81 | Type: tp, 82 | In: item.In, 83 | Format: item.Schema.Format, 84 | Description: item.Description, 85 | }) 86 | } 87 | return parameters 88 | } 89 | 90 | toRequest := func(method string, info Method) (nt *tmpl.NamedType) { 91 | 92 | schema := info.RequestBody.Content["application/json"].Schema 93 | return schemaType(schema).Parse() 94 | } 95 | 96 | toResponse := func(method string, info Method) *tmpl.NamedType { 97 | response := info.Responses["200"] 98 | return schemaType(response.Content["*/*"].Schema).Parse() 99 | } 100 | 101 | paths := make([]*tmpl.Path, 0) 102 | for path, item := range doc.Paths { 103 | for method, fn := range item { 104 | 105 | p := &tmpl.Path{ 106 | Tag: lo.Ternary(len(fn.Tags) > 0, fn.Tags[0], "UnnamedApi"), 107 | Name: fn.OperationId, 108 | Description: fn.Description, 109 | Summary: fn.Summary, 110 | Path: path, 111 | OriginalPath: path, 112 | Method: method, 113 | Parameters: toParameters(method, fn), 114 | Request: toRequest(method, fn), 115 | Response: toResponse(method, fn), 116 | } 117 | paths = append(paths, p) 118 | } 119 | } 120 | 121 | groups := lo.GroupBy(paths, func(item *tmpl.Path) string { 122 | return item.Tag 123 | }) 124 | 125 | apis := make([]*tmpl.Api, 0) 126 | for _, tag := range doc.Tags { 127 | apis = append(apis, &tmpl.Api{ 128 | Name: tag.Name, 129 | Description: tag.Description, 130 | Paths: groups[tag.Name], 131 | }) 132 | } 133 | 134 | return refs, apis, nil 135 | } 136 | 137 | type schemaType Schema 138 | 139 | func (s schemaType) Parse() *tmpl.NamedType { 140 | 141 | var expression string 142 | var kind tmpl.NamedTypeKind 143 | 144 | if s.Ref != "" { 145 | kind = tmpl.ReferenceType 146 | expression = s.Ref 147 | } else { 148 | if s.Items != nil { 149 | 150 | expression = cmp.Or(s.Items.Type, s.Items.Ref) 151 | kind = lo.Ternary(expression == s.Items.Type, tmpl.ArrayType|tmpl.FoundationType, tmpl.ArrayType|tmpl.ReferenceType) 152 | } else if s.AdditionalProperties != nil { 153 | 154 | expression = cmp.Or(s.AdditionalProperties.Type, s.AdditionalProperties.Ref) 155 | kind = lo.Ternary(expression == s.AdditionalProperties.Type, tmpl.MapType|tmpl.FoundationType, tmpl.MapType|tmpl.ReferenceType) 156 | } else { 157 | expression = cmp.Or(s.Type, s.Ref) 158 | kind = lo.Ternary(expression == s.Type, tmpl.FoundationType, tmpl.ReferenceType) 159 | } 160 | } 161 | 162 | if expression == "" { 163 | return nil 164 | } 165 | 166 | return &tmpl.NamedType{Kind: kind, Expression: RefPath(expression).BaseName()} 167 | } 168 | -------------------------------------------------------------------------------- /v3/schema.go: -------------------------------------------------------------------------------- 1 | package v3 2 | 3 | import "strings" 4 | 5 | type Tag struct { 6 | Name string `json:"name,omitempty"` 7 | Description string `json:"description,omitempty"` 8 | } 9 | 10 | type Path = map[string]Method 11 | 12 | type Method struct { 13 | Tags []string `json:"tags,omitempty"` 14 | Summary string `json:"summary,omitempty"` 15 | Description string `json:"description,omitempty"` 16 | OperationId string `json:"operationId,omitempty"` 17 | Responses map[string]Response `json:"responses,omitempty"` 18 | RequestBody RequestBody `json:"requestBody,omitempty"` 19 | Parameters []Parameter `json:"parameters,omitempty"` 20 | } 21 | 22 | type Response struct { 23 | Description string `json:"description,omitempty"` 24 | Content Content `json:"content,omitempty"` 25 | Ref string `json:"$ref,omitempty"` 26 | } 27 | 28 | type RequestBody struct { 29 | Description string `json:"description,omitempty"` 30 | Content Content `json:"content,omitempty"` 31 | Ref string `json:"$ref,omitempty"` 32 | } 33 | 34 | type Content = map[string]struct { 35 | Schema Schema `json:"schema,omitempty"` 36 | } 37 | 38 | type Parameter struct { 39 | Name string `json:"name,omitempty"` 40 | In string `json:"in,omitempty"` 41 | Required bool `json:"required,omitempty"` 42 | Schema Schema `json:"schema,omitempty"` 43 | Description string `json:"description,omitempty"` 44 | } 45 | 46 | type Schema struct { 47 | Type string `json:"type,omitempty"` 48 | Format string `json:"format,omitempty"` 49 | Ref string `json:"$ref,omitempty"` 50 | Items *struct { 51 | Type string `json:"type,omitempty"` 52 | Ref string `json:"$ref,omitempty"` 53 | } `json:"items,omitempty"` 54 | AdditionalProperties *struct { 55 | Type string `json:"type,omitempty"` 56 | Ref string `json:"$ref,omitempty"` 57 | } `json:"additionalProperties,omitempty"` 58 | Default interface{} `json:"default,omitempty"` 59 | } 60 | 61 | type Mode struct { 62 | Type string `json:"type,omitempty"` 63 | Properties map[string]Property `json:"properties,omitempty"` 64 | Description string `json:"description,omitempty"` 65 | } 66 | 67 | type Property struct { 68 | Schema 69 | Description string `json:"description,omitempty"` 70 | Enum []any `json:"enum,omitempty"` 71 | } 72 | 73 | type RefPath string 74 | 75 | func (r RefPath) BaseName() string { 76 | x := r[strings.LastIndex(string(r), "/")+1:] 77 | return string(x) 78 | } 79 | --------------------------------------------------------------------------------