swagger2api-v3 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/core/generator.d.ts +13 -0
- package/dist/core/generator.js +85 -23
- package/dist/core/parser.js +16 -1
- package/dist/utils/index.js +27 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,18 +128,20 @@ export interface UserInfo {
|
|
|
128
128
|
### 生成的 API 接口
|
|
129
129
|
|
|
130
130
|
```typescript
|
|
131
|
-
//
|
|
131
|
+
// authController/index.ts
|
|
132
132
|
import { request } from '@/utils/request';
|
|
133
|
-
import type { LoginDto,
|
|
133
|
+
import type { LoginDto, LoginRespDto } from '../types';
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
136
|
+
* 登录
|
|
137
137
|
* @param data 登录参数
|
|
138
|
+
* @param config 可选的请求配置
|
|
138
139
|
*/
|
|
139
|
-
export const
|
|
140
|
-
return request.post<
|
|
141
|
-
url: '/auth/login',
|
|
142
|
-
data
|
|
140
|
+
export const authControllerLoginPost = (data: LoginDto, config?: any) => {
|
|
141
|
+
return request.post<LoginRespDto>({
|
|
142
|
+
url: '/admin/auth/login',
|
|
143
|
+
data,
|
|
144
|
+
...config
|
|
143
145
|
});
|
|
144
146
|
};
|
|
145
147
|
```
|
package/dist/core/generator.d.ts
CHANGED
|
@@ -67,12 +67,25 @@ export declare class CodeGenerator {
|
|
|
67
67
|
* @returns 使用的类型名称数组
|
|
68
68
|
*/
|
|
69
69
|
private collectUsedTypes;
|
|
70
|
+
/**
|
|
71
|
+
* 从类型字符串中提取所有类型名称(包括泛型参数)
|
|
72
|
+
* @param typeStr 类型字符串,如 "ResOp<UserListRespDto>"
|
|
73
|
+
* @returns 类型名称数组,如 ["ResOp", "UserListRespDto"]
|
|
74
|
+
*/
|
|
75
|
+
private extractTypeNames;
|
|
70
76
|
/**
|
|
71
77
|
* 判断是否为基础类型
|
|
72
78
|
* @param type 类型名称
|
|
73
79
|
* @returns 是否为基础类型
|
|
74
80
|
*/
|
|
75
81
|
private isPrimitiveType;
|
|
82
|
+
/**
|
|
83
|
+
* 检测是否为通用响应容器类型
|
|
84
|
+
* @param type 类型信息
|
|
85
|
+
* @param definition 类型定义
|
|
86
|
+
* @returns 是否为通用响应容器类型
|
|
87
|
+
*/
|
|
88
|
+
private isGenericResponseContainer;
|
|
76
89
|
/**
|
|
77
90
|
* 生成请求配置
|
|
78
91
|
* @param api API接口信息
|
package/dist/core/generator.js
CHANGED
|
@@ -102,11 +102,19 @@ class CodeGenerator {
|
|
|
102
102
|
''
|
|
103
103
|
].join('\n');
|
|
104
104
|
const typeDefinitions = types
|
|
105
|
-
.map(type => {
|
|
105
|
+
.map((type) => {
|
|
106
106
|
const comment = type.description
|
|
107
107
|
? `/**\n * ${type.description}\n */\n`
|
|
108
108
|
: '';
|
|
109
|
-
|
|
109
|
+
// 通用处理:检测通用响应容器类型并转换为泛型接口
|
|
110
|
+
let definition = type.definition;
|
|
111
|
+
if (this.isGenericResponseContainer(type, definition)) {
|
|
112
|
+
const typeName = type.name;
|
|
113
|
+
definition = definition
|
|
114
|
+
.replace(`export interface ${typeName} {`, `export interface ${typeName}<T = Record<string, any>> {`)
|
|
115
|
+
.replace('data: Record<string, any>;', 'data: T;');
|
|
116
|
+
}
|
|
117
|
+
return `${comment}${definition}`;
|
|
110
118
|
})
|
|
111
119
|
.join('\n\n');
|
|
112
120
|
return `${header}${typeDefinitions}\n`;
|
|
@@ -163,7 +171,9 @@ class CodeGenerator {
|
|
|
163
171
|
header.push(`import type { ${typeNames} } from '${typesPath}';`);
|
|
164
172
|
}
|
|
165
173
|
header.push('');
|
|
166
|
-
const apiImplementations = apis
|
|
174
|
+
const apiImplementations = apis
|
|
175
|
+
.map((api) => this.generateApiFunction(api))
|
|
176
|
+
.join('\n\n');
|
|
167
177
|
return `${header.join('\n')}${apiImplementations}\n`;
|
|
168
178
|
}
|
|
169
179
|
/**
|
|
@@ -175,7 +185,7 @@ class CodeGenerator {
|
|
|
175
185
|
const parts = [];
|
|
176
186
|
// 生成注释
|
|
177
187
|
if (this.config.options?.addComments !== false) {
|
|
178
|
-
const swaggerParams = api.parameters.map(p => ({
|
|
188
|
+
const swaggerParams = api.parameters.map((p) => ({
|
|
179
189
|
name: p.name,
|
|
180
190
|
in: p.in,
|
|
181
191
|
required: p.required,
|
|
@@ -186,7 +196,7 @@ class CodeGenerator {
|
|
|
186
196
|
parts.push(comment);
|
|
187
197
|
}
|
|
188
198
|
// 生成函数签名
|
|
189
|
-
const swaggerParameters = api.parameters.map(p => ({
|
|
199
|
+
const swaggerParameters = api.parameters.map((p) => ({
|
|
190
200
|
name: p.name,
|
|
191
201
|
in: p.in,
|
|
192
202
|
required: p.required,
|
|
@@ -211,34 +221,36 @@ class CodeGenerator {
|
|
|
211
221
|
*/
|
|
212
222
|
generateDirectParameters(parameters) {
|
|
213
223
|
const params = [];
|
|
214
|
-
const queryParams = parameters.filter(p => p.in === 'query');
|
|
215
|
-
const pathParams = parameters.filter(p => p.in === 'path');
|
|
216
|
-
const bodyParams = parameters.filter(p => p.in === 'body');
|
|
217
|
-
const formParams = parameters.filter(p => p.in === 'formData');
|
|
224
|
+
const queryParams = parameters.filter((p) => p.in === 'query');
|
|
225
|
+
const pathParams = parameters.filter((p) => p.in === 'path');
|
|
226
|
+
const bodyParams = parameters.filter((p) => p.in === 'body');
|
|
227
|
+
const formParams = parameters.filter((p) => p.in === 'formData');
|
|
218
228
|
// 合并路径参数和查询参数为一个params对象
|
|
219
229
|
const allParams = [...pathParams, ...queryParams];
|
|
220
230
|
if (allParams.length > 0) {
|
|
221
231
|
const paramType = allParams
|
|
222
|
-
.map(p => {
|
|
232
|
+
.map((p) => {
|
|
223
233
|
const optional = p.required ? '' : '?';
|
|
224
234
|
return `${p.name}${optional}: ${p.type}`;
|
|
225
235
|
})
|
|
226
236
|
.join(', ');
|
|
227
237
|
// 检查是否所有参数都是可选的
|
|
228
|
-
const allOptional = allParams.every(p => !p.required);
|
|
238
|
+
const allOptional = allParams.every((p) => !p.required);
|
|
229
239
|
const optionalModifier = allOptional ? '?' : '';
|
|
230
240
|
params.push(`params${optionalModifier}: { ${paramType} }`);
|
|
231
241
|
}
|
|
232
242
|
// 请求体参数
|
|
233
243
|
if (bodyParams.length > 0) {
|
|
234
244
|
const bodyParam = bodyParams[0];
|
|
235
|
-
const bodyType = bodyParam.schema
|
|
245
|
+
const bodyType = bodyParam.schema
|
|
246
|
+
? this.getTypeFromSchema(bodyParam.schema)
|
|
247
|
+
: bodyParam.type;
|
|
236
248
|
params.push(`data: ${bodyType}`);
|
|
237
249
|
}
|
|
238
250
|
// 表单参数
|
|
239
251
|
if (formParams.length > 0) {
|
|
240
252
|
const formType = formParams
|
|
241
|
-
.map(p => {
|
|
253
|
+
.map((p) => {
|
|
242
254
|
const optional = p.required ? '' : '?';
|
|
243
255
|
return `${p.name}${optional}: ${p.type}`;
|
|
244
256
|
})
|
|
@@ -267,17 +279,22 @@ class CodeGenerator {
|
|
|
267
279
|
*/
|
|
268
280
|
collectUsedTypes(apis) {
|
|
269
281
|
const usedTypes = new Set();
|
|
270
|
-
apis.forEach(api => {
|
|
282
|
+
apis.forEach((api) => {
|
|
271
283
|
// 收集响应类型
|
|
272
284
|
if (api.responseType && api.responseType !== 'any') {
|
|
273
|
-
|
|
285
|
+
// 提取泛型类型中的所有类型名称
|
|
286
|
+
this.extractTypeNames(api.responseType).forEach((typeName) => {
|
|
287
|
+
usedTypes.add(typeName);
|
|
288
|
+
});
|
|
274
289
|
}
|
|
275
290
|
// 收集参数类型
|
|
276
|
-
api.parameters.forEach(param => {
|
|
291
|
+
api.parameters.forEach((param) => {
|
|
277
292
|
if (param.schema) {
|
|
278
293
|
const type = this.getTypeFromSchema(param.schema);
|
|
279
294
|
if (type && type !== 'any' && !this.isPrimitiveType(type)) {
|
|
280
|
-
|
|
295
|
+
this.extractTypeNames(type).forEach((typeName) => {
|
|
296
|
+
usedTypes.add(typeName);
|
|
297
|
+
});
|
|
281
298
|
}
|
|
282
299
|
}
|
|
283
300
|
else if (param.type && !this.isPrimitiveType(param.type)) {
|
|
@@ -287,15 +304,60 @@ class CodeGenerator {
|
|
|
287
304
|
});
|
|
288
305
|
return Array.from(usedTypes).sort();
|
|
289
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* 从类型字符串中提取所有类型名称(包括泛型参数)
|
|
309
|
+
* @param typeStr 类型字符串,如 "ResOp<UserListRespDto>"
|
|
310
|
+
* @returns 类型名称数组,如 ["ResOp", "UserListRespDto"]
|
|
311
|
+
*/
|
|
312
|
+
extractTypeNames(typeStr) {
|
|
313
|
+
const typeNames = new Set();
|
|
314
|
+
// 匹配所有标识符(类型名称)
|
|
315
|
+
const matches = typeStr.match(/[A-Za-z_][A-Za-z0-9_]*/g);
|
|
316
|
+
if (matches) {
|
|
317
|
+
matches.forEach((match) => {
|
|
318
|
+
if (!this.isPrimitiveType(match)) {
|
|
319
|
+
typeNames.add(match);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return Array.from(typeNames);
|
|
324
|
+
}
|
|
290
325
|
/**
|
|
291
326
|
* 判断是否为基础类型
|
|
292
327
|
* @param type 类型名称
|
|
293
328
|
* @returns 是否为基础类型
|
|
294
329
|
*/
|
|
295
330
|
isPrimitiveType(type) {
|
|
296
|
-
const primitiveTypes = [
|
|
331
|
+
const primitiveTypes = [
|
|
332
|
+
'string',
|
|
333
|
+
'number',
|
|
334
|
+
'boolean',
|
|
335
|
+
'object',
|
|
336
|
+
'array',
|
|
337
|
+
'any',
|
|
338
|
+
'void',
|
|
339
|
+
'null',
|
|
340
|
+
'undefined'
|
|
341
|
+
];
|
|
297
342
|
return primitiveTypes.includes(type.toLowerCase());
|
|
298
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* 检测是否为通用响应容器类型
|
|
346
|
+
* @param type 类型信息
|
|
347
|
+
* @param definition 类型定义
|
|
348
|
+
* @returns 是否为通用响应容器类型
|
|
349
|
+
*/
|
|
350
|
+
isGenericResponseContainer(type, definition) {
|
|
351
|
+
// 检查是否为接口定义
|
|
352
|
+
if (!definition.includes(`export interface ${type.name} {`)) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
// 检查是否包含 data 字段且类型为 Record<string, any>
|
|
356
|
+
const hasDataField = definition.includes('data: Record<string, any>;');
|
|
357
|
+
// 检查是否包含其他常见的响应容器字段
|
|
358
|
+
const hasCommonFields = ['code', 'message', 'success', 'status'].some((field) => definition.includes(`${field}:`));
|
|
359
|
+
return hasDataField && hasCommonFields;
|
|
360
|
+
}
|
|
299
361
|
/**
|
|
300
362
|
* 生成请求配置
|
|
301
363
|
* @param api API接口信息
|
|
@@ -309,11 +371,11 @@ class CodeGenerator {
|
|
|
309
371
|
if (this.config.prefix) {
|
|
310
372
|
url = this.config.prefix + url;
|
|
311
373
|
}
|
|
312
|
-
const pathParams = api.parameters.filter(p => p.in === 'path');
|
|
313
|
-
const queryParams = api.parameters.filter(p => p.in === 'query');
|
|
374
|
+
const pathParams = api.parameters.filter((p) => p.in === 'path');
|
|
375
|
+
const queryParams = api.parameters.filter((p) => p.in === 'query');
|
|
314
376
|
if (pathParams.length > 0) {
|
|
315
377
|
// 替换路径参数,从params对象中获取
|
|
316
|
-
pathParams.forEach(param => {
|
|
378
|
+
pathParams.forEach((param) => {
|
|
317
379
|
url = url.replace(`{${param.name}}`, `\${params.${param.name}}`);
|
|
318
380
|
});
|
|
319
381
|
config.push(`url: \`${url}\``);
|
|
@@ -327,8 +389,8 @@ class CodeGenerator {
|
|
|
327
389
|
config.push('params');
|
|
328
390
|
}
|
|
329
391
|
// 请求体数据
|
|
330
|
-
const bodyParams = api.parameters.filter(p => p.in === 'body');
|
|
331
|
-
const formParams = api.parameters.filter(p => p.in === 'formData');
|
|
392
|
+
const bodyParams = api.parameters.filter((p) => p.in === 'body');
|
|
393
|
+
const formParams = api.parameters.filter((p) => p.in === 'formData');
|
|
332
394
|
if (bodyParams.length > 0) {
|
|
333
395
|
config.push('data');
|
|
334
396
|
}
|
package/dist/core/parser.js
CHANGED
|
@@ -161,7 +161,22 @@ class SwaggerParser {
|
|
|
161
161
|
else if (schema.enum) {
|
|
162
162
|
// 枚举类型
|
|
163
163
|
const enumValues = schema.enum
|
|
164
|
-
.map((value) =>
|
|
164
|
+
.map((value, index) => {
|
|
165
|
+
let key = value;
|
|
166
|
+
// 优先使用 x-enum-varnames 或 x-enumNames 扩展字段
|
|
167
|
+
if ((schema['x-enum-varnames'] && schema['x-enum-varnames'][index]) ||
|
|
168
|
+
(schema['x-enumNames'] && schema['x-enumNames'][index])) {
|
|
169
|
+
key = schema['x-enum-varnames']?.[index] || schema['x-enumNames']?.[index];
|
|
170
|
+
}
|
|
171
|
+
else if (/^\d+$/.test(value)) {
|
|
172
|
+
// 对于数字枚举,使用 VALUE_ 前缀
|
|
173
|
+
key = `VALUE_${value}`;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
key = value.toUpperCase();
|
|
177
|
+
}
|
|
178
|
+
return ` ${key} = '${value}'`;
|
|
179
|
+
})
|
|
165
180
|
.join(',\n');
|
|
166
181
|
definition = `export enum ${typeName} {\n${enumValues}\n}`;
|
|
167
182
|
}
|
package/dist/utils/index.js
CHANGED
|
@@ -122,10 +122,35 @@ function swaggerTypeToTsType(schema) {
|
|
|
122
122
|
}
|
|
123
123
|
// 处理 allOf 类型组合
|
|
124
124
|
if (schema.allOf && schema.allOf.length > 0) {
|
|
125
|
-
//
|
|
125
|
+
// 检查是否为泛型模式:第一个是引用类型,第二个定义了扩展属性
|
|
126
126
|
const firstSchema = schema.allOf[0];
|
|
127
|
-
if (firstSchema.$ref) {
|
|
127
|
+
if (firstSchema.$ref && schema.allOf.length > 1) {
|
|
128
128
|
const refName = firstSchema.$ref.split('/').pop();
|
|
129
|
+
const secondSchema = schema.allOf[1];
|
|
130
|
+
// 检查第二个 schema 是否定义了对象属性(可能没有 type 字段)
|
|
131
|
+
if (secondSchema.properties) {
|
|
132
|
+
// 获取所有扩展属性的类型
|
|
133
|
+
const propertyTypes = [];
|
|
134
|
+
for (const [propName, propSchema] of Object.entries(secondSchema.properties)) {
|
|
135
|
+
const propType = swaggerTypeToTsType(propSchema);
|
|
136
|
+
propertyTypes.push(propType);
|
|
137
|
+
}
|
|
138
|
+
// 如果只有一个属性,直接作为泛型参数
|
|
139
|
+
if (propertyTypes.length === 1) {
|
|
140
|
+
return `${refName}<${propertyTypes[0]}>`;
|
|
141
|
+
}
|
|
142
|
+
// 如果有多个属性,组合成联合类型或对象类型
|
|
143
|
+
else if (propertyTypes.length > 1) {
|
|
144
|
+
const combinedType = `{ ${Object.entries(secondSchema.properties)
|
|
145
|
+
.map(([key, value]) => {
|
|
146
|
+
const optional = secondSchema.required?.includes(key) ? '' : '?';
|
|
147
|
+
const type = swaggerTypeToTsType(value);
|
|
148
|
+
return `${key}${optional}: ${type}`;
|
|
149
|
+
})
|
|
150
|
+
.join('; ')} }`;
|
|
151
|
+
return `${refName}<${combinedType}>`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
129
154
|
return refName || 'any';
|
|
130
155
|
}
|
|
131
156
|
// 如果不是引用,尝试合并所有类型
|