跳到主要内容

源码阅读

因为已经有实现方案了,我们要做的是在此基础上进行改造,更符合自己使用,原项目地址https://github.com/chenshuai2144/openapi2typescript,

项目入口

我们从代码的测试文件test/test.js入手,看代码是如何启动的:

const openAPI = require('../dist/index');

const gen = async () => {
await openAPI.generateService({
schemaPath: `${__dirname}/example-files/swagger-empty.json`,
serversPath: './servers/empty',
});
}
gen()

这里说明会先将ts编译为js,然后再运行js文件,主要是调用了里面的generateService函数。

// 从 appName 生成 service 数据
export const generateService = async ({
requestLibPath,
schemaPath,
mockFolder,
nullable = false,
requestOptionsType = '{[key: string]: any}',
...rest
}: GenerateServiceProps) => {
const openAPI = await getOpenAPIConfig(schemaPath);
const requestImportStatement = getImportStatement(requestLibPath);
const serviceGenerator = new ServiceGenerator(
{
namespace: 'API',
requestOptionsType,
requestImportStatement,
enumStyle: 'string-literal',
nullable,
isCamelCase: true,
...rest,
},
openAPI,
);
serviceGenerator.genFile();

if (mockFolder) {
await mockGenerator({
openAPI,
mockFolder: mockFolder || './mocks/',
});
}
};

这段代码主要是从 OpenAPI 文件或 URL 中读取配置,并生成对应的 TypeScript 服务文件,主要逻辑如下:

  1. 读取 OpenAPI 配置:从指定路径加载 OpenAPI 文档。
  2. 获取请求库导入语句:根据给定路径生成请求库的导入语句。
  3. 初始化服务生成器:使用 OpenAPI 配置和相关选项初始化服务生成器对象。
  4. 生成服务代码:调用生成器的方法,生成对应的 TypeScript 服务层代码。
  5. 生成 Mock 数据(可选):如果指定了 mock 数据目录,则生成相应的 mock 数据文件。

getOpenAPIConfig

const getOpenAPIConfig = async (schemaPath: string) => {
const schema = await getSchema(schemaPath);
if (!schema) {
return null;
}
const openAPI = await converterSwaggerToOpenApi(schema);
return openAPI;
};
export const getSchema = async (schemaPath: string) => {
if (schemaPath.startsWith('http')) {
const protocol = schemaPath.startsWith('https:') ? https : http;
try {
const agent = new protocol.Agent({
rejectUnauthorized: false,
});
const json = await fetch(schemaPath, { agent }).then((rest) => rest.json());
return json;
} catch (error) {
// eslint-disable-next-line no-console
console.log('fetch openapi error:', error);
}
return null;
}
if (require.cache[schemaPath]) {
delete require.cache[schemaPath];
}
const schema = require(schemaPath);
return schema;
};
const converterSwaggerToOpenApi = (swagger: any) => {
if (!swagger.swagger) {
return swagger;
}
return new Promise((resolve, reject) => {
converter.convertObj(swagger, {}, (err, options) => {
Log(['💺 将 Swagger 转化为 openAPI']);
if (err) {
reject(err);
return;
}
resolve(options.openapi);
});
});
};

getOpenAPIConfig 函数的主要功能是根据提供的 schemaPath(OpenAPI 文档路径)获取并解析 OpenAPI 文档。如果文档是 Swagger 规范,还会将其转换为 OpenAPI 格式。具体逻辑如下:

  1. 获取 Schema:调用 getSchema(schemaPath),根据提供的路径加载 API 文档,支持远程(HTTP/HTTPS)或本地文件的加载。
  2. 转换为 OpenAPI:如果获取到的是 Swagger 规范文档,则调用 converterSwaggerToOpenApi 方法将 Swagger 转换为 OpenAPI 规范。

getImportStatement

const getImportStatement = (requestLibPath: string) => {
if (requestLibPath && requestLibPath.startsWith('import')) {
return requestLibPath;
}
if (requestLibPath) {
return `import request from '${requestLibPath}'`;
}
return `import { request } from "umi"`;
};

getImportStatement 函数的作用是根据传入的 requestLibPath 生成对应的请求库导入语句。

  • 如果 requestLibPath 是完整的导入语句,直接返回。
  • 否则,根据传入路径生成标准导入语句。
  • 如果没有路径,使用默认的 umirequest 方法。

ServiceGenerator

构造器

constructor(config: GenerateServiceProps, openAPIData: OpenAPIObject) {
this.finalPath = '';
this.config = {
projectName: 'api',
templatesFolder: join(__dirname, '../', 'templates'),
...config,
};
if (this.config.hook?.afterOpenApiDataInited) {
this.openAPIData =
this.config.hook.afterOpenApiDataInited(openAPIData) || openAPIData;
} else {
this.openAPIData = openAPIData;
}
const { info } = this.openAPIData;
const basePath = '';
this.version = info.version;
const hookCustomFileNames = this.config.hook?.customFileNames || defaultGetFileTag;
Object.keys(this.openAPIData.paths || {}).forEach((p) => {
const pathItem: PathItemObject = this.openAPIData.paths[p];
['get', 'put', 'post', 'delete', 'patch'].forEach((method) => {
const operationObject: OperationObject = pathItem[method];
if (!operationObject) {
return;
}

let tags = hookCustomFileNames(operationObject, p, method);
if (!tags) {
tags = defaultGetFileTag(operationObject, p, method);
}

tags.forEach((tagString) => {
const tag = this.config.isCamelCase
? camelCase(resolveTypeName(tagString))
: resolveTypeName(tagString);

if (!this.apiData[tag]) {
this.apiData[tag] = [];
}
this.apiData[tag].push({
path: `${basePath}${p}`,
method,
...operationObject,
});
});
});
});

}

这段代码是 ServiceGenerator 类的构造函数,主要负责初始化类的实例并处理 OpenAPI 数据。以下是主要逻辑的详细解释:

  1. 构造函数参数config: GenerateServiceProps:接收一个配置对象,包含生成服务所需的各种参数。openAPIData: OpenAPIObject:接收解析后的 OpenAPI 数据。

  2. 处理 OpenAPI 数据:检查是否存在自定义钩子 afterOpenApiDataInited,如果存在,则调用该钩子处理 openAPIData,否则直接使用传入的 openAPIData。

  3. 处理路径和操作:hookCustomFileNames:获取自定义文件名生成函数,如果未定义,则使用默认的 defaultGetFileTag 函数。

  4. 标签逻辑:遍历 openAPIData.paths 中的每个路径(p),对于每个路径,遍历其支持的 HTTP 方法(get、put、post、delete、patch)。对于每个方法,获取对应的 operationObject。如果 operationObject 存在,调用 hookCustomFileNames 函数生成标签(tags),如果未返回标签,则使用默认的标签生成逻辑,这里的标签也就是最后生成的接口文件名

  5. 生成 API 数据:遍历生成的标签(tags),将每个标签转换为符合配置的格式(如小驼峰命名)。如果 this.apiData 中不存在该标签,则初始化为一个空数组。将当前路径、HTTP 方法和操作对象(operationObject)添加到 this.apiData 中,形成一个以标签为键的 API 数据结构。相当于缓存起来,比如UserController中有哪些接口数据,就放到对应的Map中。

defaultGetFileTag函数,简单来说就是获取文件名:

function defaultGetFileTag(operationObject: OperationObject, apiPath: string, _apiMethod: string) {
return operationObject['x-swagger-router-controller']
? [operationObject['x-swagger-router-controller']]
: operationObject.tags || [operationObject.operationId] || [
apiPath.replace('/', '').split('/')[1],
];
}

defaultGetFileTag 函数的主要作用是根据给定的 API 操作对象(operationObject)生成与该操作相关的标签(tags)。这些标签用于在生成服务代码时标识和分类 API 操作。

genFile

介绍完上面的构造函数中做的事情,就到了关键的生成代码了。

先来看生成ts类型声明的:

const basePath = this.config.serversPath || './src/service';
try {
const finalPath = join(basePath, this.config.projectName);

this.finalPath = finalPath;
glob
.sync(`${finalPath}/**/*`)
.filter((ele) => !ele.includes('_deperated'))
.forEach((ele) => {
rimraf.sync(ele);
});
} catch (error) {
Log(`🚥 serves 生成失败: ${error}`);
}
// 生成 ts 类型声明
this.genFileFromTemplate('typings.d.ts', 'interface', {
namespace: this.config.namespace,
nullable: this.config.nullable,
// namespace: 'API',
list: this.getInterfaceTP(),
disableTypeCheck: false,
});
  • basePath是用户配置的生成文件位置路径,最终生成的文件位置为basePath+projectName
  • 然后就是使用glob去清理finalPath下面的所有文件,使用 rimraf 模块同步删除过滤后的所有文件和文件夹,以清理旧的生成文件。
  • 然后就到了真正生成文件的地方genFileFromTemplate, this.genFileFromTemplate:调用该方法生成 TypeScript 类型声明文件 typings.d.ts。当然还用来生成service等文件。
  • 这里的getInterfaceTP为我们实际的数据

生成controller文件:

这里会去拿到每个controller(getServiceTP函数),然后分别生成对应的接口,所以最后会生成很多文件

    // 生成 controller 文件
const prettierError = [];
// 生成 service 统计
this.getServiceTP().forEach((tp) => {
// 根据当前数据源类型选择恰当的 controller 模版
const template = 'serviceController';
const hasError = this.genFileFromTemplate(
this.getFinalFileName(`${tp.className}.ts`),
template,
{
namespace: this.config.namespace,
requestOptionsType: this.config.requestOptionsType,
requestImportStatement: this.config.requestImportStatement,
disableTypeCheck: false,
...tp,
},
);
prettierError.push(hasError);
});

if (prettierError.includes(true)) {
Log(`🚥 格式化失败,请检查 service 文件内可能存在的语法错误`);
}

生成index文件,只要把controller文件名列表传入模版:

    // 生成 index 文件
this.genFileFromTemplate(`index.ts`, 'serviceIndex', {
list: this.classNameList,
disableTypeCheck: false,
});

// 打印日志
Log(`✅ 成功生成 service 文件`);

genFileFromTemplate

  private genFileFromTemplate(
fileName: string,
type: TypescriptFileType,
params: Record<string, any>,
): boolean {
try {
const template = this.getTemplate(type);
// 设置输出不转义
nunjucks.configure({
autoescape: false,
});
return writeFile(this.finalPath, fileName, nunjucks.renderString(template, params));
} catch (error) {
// eslint-disable-next-line no-console
console.error('[GenSDK] file gen fail:', fileName, 'type:', type);
throw error;
}
}

private getTemplate(type: 'interface' | 'serviceController' | 'serviceIndex'): string {
return readFileSync(join(this.config.templatesFolder, `${type}.njk`), 'utf8');
}

genFileFromTemplate 用于生成 TypeScript 文件并从模板读取内容,使用指定的模板和参数渲染内容,并将结果写入文件系统。

  • fileName: string:要生成的文件名。
  • type: TypescriptFileType:文件类型,可能的值包括 'interface'、'serviceController' 和 'serviceIndex',用于确定使用哪个模板。
  • params: Record<string, any>:渲染模板时使用的参数,包含动态数据。

getTemplate 方法用来获取指定类型的模板内容

  • 配置 Nunjucks 模板引擎,设置 autoescapefalse,表示输出内容不进行 HTML 转义。
  • join(this.config.templatesFolder, ${type}.njk) 生成模板文件的完整路径,this.config.templatesFolder 是模板文件夹的路径,${type}.njk 是模板文件名。默认的this.config.templatesFolder是根目录下的this.config.templates目录

getInterfaceTP

getInterfaceTP 方法的主要功能是从 OpenAPI 数据中提取接口类型信息,并生成相应的 TypeScript 类型定义。

const { components } = this.openAPIData;
const data =
components &&
components.schemas &&
[components.schemas].map((defines) => {
if (!defines) {
return null;
}

return Object.keys(defines).map((typeName) => {
const result = this.resolveObject(defines[typeName]);

const getDefinesType = () => {
if (result.type) {
return (defines[typeName] as SchemaObject).type === 'object' || result.type;
}
return 'Record<string, any>';
};
return {
typeName: resolveTypeName(typeName),
type: getDefinesType(),
parent: result.parent,
props: result.props || [],
isEnum: result.isEnum,
};
});
});
  1. openAPIData 中提取 components,其中包含了 API 的各种定义,包括模式(schemas)。
  2. 检查 componentscomponents.schemas 是否存在。使用 map 遍历 schemas 中的每个定义(defines),并为每个模式生成一个对象,包含以下信息:
  • typeName:通过 resolveTypeName 函数处理的类型名称。
  • type:调用 getDefinesType 函数获取类型,判断是否为对象类型。
  • parent:从 result 中获取父类型信息。
  • props:从 result 中获取属性列表。
  • isEnum:指示该类型是否为枚举

处理请求参数:

Object.keys(this.openAPIData.paths || {}).forEach((p) => {
const pathItem: PathItemObject = this.openAPIData.paths[p];
['get', 'put', 'post', 'delete', 'patch'].forEach((method) => {
const operationObject: OperationObject = pathItem[method];
if (!operationObject) {
return;
}
operationObject.parameters = operationObject.parameters?.filter(
(item) => (item as ParameterObject)?.in !== 'header',
);
const props = [];
if (operationObject.parameters) {
operationObject.parameters.forEach((parameter: any) => {
props.push({
desc: parameter.description ?? '',
name: parameter.name,
required: parameter.required,
type: this.getType(parameter.schema),
});
});
}
// parameters may be in path
if (pathItem.parameters) {
pathItem.parameters.forEach((parameter: any) => {
props.push({
desc: parameter.description ?? '',
name: parameter.name,
required: parameter.required,
type: this.getType(parameter.schema),
});
});
}

if (props.length > 0 && data) {
data.push([
{
typeName: this.getTypeName({ ...operationObject, method, path: p }),
type: 'Record<string, any>',
parent: undefined,
props: [props],
isEnum: false,
},
]);
}
});
});

遍历 openAPIData.paths 中的每个路径(p),并对每个路径的 HTTP 方法(get、put、post、delete、patch)进行处理。对于每个 operationObject,过滤掉 header 中的参数。收集请求参数的描述、名称、是否必需和类型,并将其存储在 props 数组中。如果 props 数组不为空,则生成一个新的类型定义,包含请求参数的信息,并将其添加到 data 中。

返回结果:

return (
data &&
data
.reduce((p, c) => p && c && p.concat(c), [])
// 排序下,要不每次git都乱了
.sort((a, b) => a.typeName.localeCompare(b.typeName))
);

如果 data 存在,则将其扁平化(合并嵌套数组)并按 typeName 排序。返回最终的类型定义数组。

getServiceTP

getServiceTP 方法的主要功能是从 apiData 中提取服务的类型信息,并生成相应的参数和元数据。

1.初始化和过滤API数据

return Object.keys(this.apiData)
.map((tag, index) => {
const tmpFunctionRD: Record<string, number> = {};
const genParams = this.apiData[tag]
.filter(
(api) =>
// 暂不支持变量
!api.path.includes('${'),
).map((api) => {
xxx
}
xxx
}).filter((ele) => !!ele?.list?.length);

Object.keys(this.apiData):获取所有 API 标签的键。

  • map:遍历每个标签(tag),为每个标签生成相应的服务参数。

  • tmpFunctionRD:用于跟踪生成的函数名称,确保函数名称的唯一性。

  • filter:过滤掉路径中包含变量(${})的 API,暂时不支持这些 API。

2.生成API参数

.map((api) => {
const newApi = api;
try {
const allParams = this.getParamsTP(newApi.parameters, newApi.path);
const body = this.getBodyTP(newApi.requestBody);
const response = this.getResponseTP(newApi.responses);
  • getParamsTP:获取 API 的所有参数类型。

  • getBodyTP:获取请求体的类型。

  • getResponseTP:获取响应的类型。

3.处理文件和表单数据

const params = allParams || {};
const file = this.getFileTP(newApi.requestBody);

let formData = false;
if ((body && (body.mediaType || '').includes('form-data')) || file) {
formData = true;
}
  • params:获取所有参数,默认为空对象。

  • file:获取请求体中的文件类型。

  • formData:检查请求体是否为 form-data 类型,或是否存在文件。

4.生成函数名称

let functionName = this.getFuncationName(newApi);

if (functionName && tmpFunctionRD[functionName]) {
functionName = `${functionName}_${(tmpFunctionRD[functionName] += 1)}`;
} else if (functionName) {
tmpFunctionRD[functionName] = 1;
}

getFuncationName:生成函数名称。唯一性处理:如果函数名称已存在,则在名称后添加一个计数后缀,以确保唯一性。

  public getFuncationName(data: APIDataType) {
// 获取路径相同部分
const pathBasePrefix = this.getBasePrefix(Object.keys(this.openAPIData.paths));
return this.config.hook && this.config.hook.customFunctionName
? this.config.hook.customFunctionName(data)
: data.operationId
? this.resolveFunctionName(stripDot(data.operationId), data.method)
: data.method + this.genDefaultFunctionName(data.path, pathBasePrefix);
}

5.格式化路径

let formattedPath = newApi.path.replace(
/:([^/]*)|{([^}]*)}/gi,
(_, str, str2) => `$\{${str || str2}}`,
);

formattedPath:将路径中的参数格式化为 ${param} 的形式。

6.处理扩展信息

if (newApi.extensions && newApi.extensions['x-antTech-description']) {
const { extensions } = newApi;
const { apiName, antTechVersion, productCode, antTechApiName } = extensions['x-antTech-description'];
formattedPath = antTechApiName || formattedPath;
this.mappings.push({
antTechApi: formattedPath,
popAction: apiName,
popProduct: productCode,
antTechVersion,
});
newApi.antTechVersion = antTechVersion;
}

检查 API 是否有扩展信息 x-antTech-description,并提取相关信息。更新 formattedPath,并将映射信息存储在 this.mappings 中。

7.处理路径参数的别名

const escapedPathParams = ((params || {}).path || []).map((ele, index) => ({
...ele,
alias: `param${index}`,
}));
if (escapedPathParams.length) {
escapedPathParams.forEach((param) => {
formattedPath = formattedPath.replace(`$\{${param.name}}`, `$\{${param.alias}}`);
});
}

为路径参数添加别名(如 param0、param1),并在 formattedPath 中替换原有参数。

8.处理查询参数

 const finalParams =
escapedPathParams && escapedPathParams.length
? { ...params, path: escapedPathParams }
: params;

// 处理 query 中的复杂对象
if (finalParams && finalParams.query) {
finalParams.query = finalParams.query.map((ele) => ({
...ele,
isComplexType: ele.isObject,
}));
}

检查查询参数是否存在,并标记复杂类型。

9.生成前缀路径

  const getPrefixPath = () => {
if (!this.config.apiPrefix) {
return formattedPath;
}
// 静态 apiPrefix
const prefix =
typeof this.config.apiPrefix === 'function'
? `${this.config.apiPrefix({
path: formattedPath,
method: newApi.method,
namespace: tag,
functionName,
})}`.trim()
: this.config.apiPrefix.trim();

if (!prefix) {
return formattedPath;
}

if (prefix.startsWith("'") || prefix.startsWith('"') || prefix.startsWith('`')) {
const finalPrefix = prefix.slice(1, prefix.length - 1);
if (
formattedPath.startsWith(finalPrefix) ||
formattedPath.startsWith(`/${finalPrefix}`)
) {
return formattedPath;
}
return `${finalPrefix}${formattedPath}`;
}
// prefix 变量
return `$\{${prefix}}${formattedPath}`;
};

getPrefixPath:生成带前缀的路径。检查是否配置了 apiPrefix,如果是函数,则调用该函数生成前缀。

10.返回生成的 API 信息

  return {
...newApi,
functionName: this.config.isCamelCase ? camelCase(functionName) : functionName,
typeName: this.getTypeName(newApi),
path: getPrefixPath(),
pathInComment: formattedPath.replace(/\*/g, '&#42;'),
hasPathVariables: formattedPath.includes('{'),
hasApiPrefix: !!this.config.apiPrefix,
method: newApi.method,
// 如果 functionName 和 summary 相同,则不显示 summary
desc:
functionName === newApi.summary
? newApi.description
: [
newApi.summary,
newApi.description,
(newApi.responses?.default as ResponseObject)?.description
? `返回值: ${(newApi.responses?.default as ResponseObject).description}`
: '',
]
.filter((s) => s)
.join(' '),
hasHeader: !!(params && params.header) || !!(body && body.mediaType),
params: finalParams,
hasParams: Boolean(Object.keys(finalParams || {}).length),
options: this.config.hook?.customOptionsDefaultValue?.(newApi) || {},
body,
file,
hasFormData: formData,
response,
};

返回一个对象,包含生成的 API 信息,包括函数名称、类型名称、路径、描述、参数、请求体、文件、响应等。

12.处理类名

      const fileName = this.replaceDot(tag) || `api${index}`;

let className = fileName;
if (this.config.hook && this.config.hook.customClassName) {
className = this.config.hook.customClassName(tag);
}
if (genParams.length) {
this.classNameList.push({
fileName: className,
controllerName: className,
});
}
return {
genType: 'ts',
className,
instanceName: `${fileName[0]?.toLowerCase()}${fileName.substr(1)}`,
list: genParams,
};

生成文件名和类名,支持自定义类名。如果生成的参数列表不为空,则将类名添加到 classNameList 中。

getParamsTP

  public getParamsTP(
parameters: (ParameterObject | ReferenceObject)[] = [],
path: string = null,
): Record<string, ParameterObject[]> {
const templateParams: Record<string, ParameterObject[]> = {};

if (parameters && parameters.length) {
['query', 'path', 'cookie' /* , 'file' */].forEach((source) => {
// Possible values are "query", "header", "path" or "cookie". (https://swagger.io/specification/)
const params = parameters
.map((p) => this.resolveRefObject(p))
.filter((p: ParameterObject) => p.in === source)
.map((p) => {
const isDirectObject = ((p.schema || {}).type || p.type) === 'object';
const refList = ((p.schema || {}).$ref || p.$ref || '').split('/');
const ref = refList[refList.length - 1];
const deRefObj = (Object.entries(
(this.openAPIData.components && this.openAPIData.components.schemas) || {},
).find(([k]) => k === ref) || []) as any;
const isRefObject = (deRefObj[1] || {}).type === 'object';
return {
...p,
isObject: isDirectObject || isRefObject,
type: this.getType(p.schema || DEFAULT_SCHEMA, this.config.namespace),
};
});

if (params.length) {
templateParams[source] = params;
}
});
}

if (path && path.length > 0) {
const regex = /\{(\w+)\}/g;
templateParams.path = templateParams.path || [];
let match = null;
while ((match = regex.exec(path))) {
if (!templateParams.path.some((p) => p.name === match[1])) {
templateParams.path.push({
...DEFAULT_PATH_PARAM,
name: match[1],
});
}
}

// 如果 path 没有内容,则将删除 path 参数,避免影响后续的 hasParams 判断
if (!templateParams.path.length) delete templateParams.path;
}

return templateParams;
}

getParamsTP 方法从给定的参数列表中提取查询参数、路径参数和 cookie 参数,并返回一个结构化的对象。它通过解析参数引用、判断参数类型、处理路径中的参数等步骤,确保生成的参数信息完整且准确

getBodyTP

  public getBodyTP(requestBody: any = {}) {
const reqBody: RequestBodyObject = this.resolveRefObject(requestBody);
if (!reqBody) {
return null;
}
const reqContent: ContentObject = reqBody.content;
if (typeof reqContent !== 'object') {
return null;
}
let mediaType = Object.keys(reqContent)[0];

const schema: SchemaObject = reqContent[mediaType].schema || DEFAULT_SCHEMA;

if (mediaType === '*/*') {
mediaType = '';
}
// 如果 requestBody 有 required 属性,则正常展示;如果没有,默认非必填
const required = typeof requestBody.required === 'boolean' ? requestBody.required : false;
if (schema.type === 'object' && schema.properties) {
const propertiesList = Object.keys(schema.properties)
.map((p) => {
if (
schema.properties &&
schema.properties[p] &&
!['binary', 'base64'].includes((schema.properties[p] as SchemaObject).format || '') &&
!(
['string[]', 'array'].includes((schema.properties[p] as SchemaObject).type || '') &&
['binary', 'base64'].includes(
((schema.properties[p] as SchemaObject).items as SchemaObject).format || '',
)
)
) {
return {
key: p,
schema: {
...schema.properties[p],
type: this.getType(schema.properties[p], this.config.namespace),
required: schema.required?.includes(p) ?? false,
},
};
}
return undefined;
})
.filter((p) => p);
return {
mediaType,
...schema,
required,
propertiesList,
};
}
return {
mediaType,
required,
type: this.getType(schema, this.config.namespace),
};
}

getBodyTP 方法的主要功能是从请求体对象中提取和处理请求体的类型信息,并返回一个结构化的对象

getResponseTP

  public getResponseTP(responses: ResponsesObject = {}) {
const { components } = this.openAPIData;
const response: ResponseObject | undefined =
responses && this.resolveRefObject(responses.default || responses['200'] || responses['201']);
const defaultResponse = {
mediaType: '*/*',
type: 'any',
};
if (!response) {
return defaultResponse;
}
const resContent: ContentObject | undefined = response.content;
const resContentMediaTypes = Object.keys(resContent || {});
const mediaType = resContentMediaTypes.includes('application/json')
? 'application/json'
: resContentMediaTypes[0]; // 优先使用 application/json
if (typeof resContent !== 'object' || !mediaType) {
return defaultResponse;
}
let schema = (resContent[mediaType].schema || DEFAULT_SCHEMA) as SchemaObject;

if (schema.$ref) {
const refPaths = schema.$ref.split('/');
const refName = refPaths[refPaths.length - 1];
const childrenSchema = components.schemas[refName] as SchemaObject;
if (
childrenSchema?.type === 'object' &&
'properties' in childrenSchema &&
this.config.dataFields
) {
schema =
this.config.dataFields
.map((field) => childrenSchema.properties[field])
.filter(Boolean)?.[0] ||
resContent[mediaType].schema ||
DEFAULT_SCHEMA;
}
}

if ('properties' in schema) {
Object.keys(schema.properties).map((fieldName) => {
// eslint-disable-next-line @typescript-eslint/dot-notation
schema.properties[fieldName]['required'] = schema.required?.includes(fieldName) ?? false;
});
}
return {
mediaType,
type: this.getType(schema, this.config.namespace),
};
}

getResponseTP 方法的主要功能是从 OpenAPI 响应对象中提取和处理响应的类型信息,并返回一个结构化的对象。