• swagger 文档自动生成接口代码+ts类型


    递归获取 interface 中指定类型

    interface Path {
      "/v1/user/update": {
        post: {
          a: string;
          response: {
            "200": {
              data: string;
            };
          };
        };
      };
    }
    
    type GetResponseType<T extends keyof Path, K extends keyof Path[T]> = K extends string
      ? Path[T][K]
      : never;
    
    // 递归定义类型
    type GetPathType<T extends keyof Path, P extends string[]> = P extends [infer Head, ...infer Tail]
      ? Head extends keyof Path
        ? GetPathType<Path[Head], Tail>
        : never
      : T extends keyof Path
      ? Path[T]
      : never;
    
    // getPath 方法
    function getPath<T extends keyof Path, P extends string[]>(path: T, ...keys: P): GetPathType<T, P> {
      return keys.reduce((acc, key) => acc[key], path) as GetPathType<T, P>;
    }
    
    // 测试
    const pathType: GetPathType<"/v1/user/update", ["post", "response", "200"]> = {
      data: 'example',
    }; // 此时 pathType 的类型即为 Path["/v1/user/update"]["post"]["response"]["200"]
    console.log(pathType);
    
    const specificResponseType: GetResponseType<"/v1/user/update", "200"> = {
      data: 'example',
    }; // 此时 specificResponseType 的类型即为 Path["/v1/user/update"]["post"]["response"]["200"]
    console.log(specificResponseType);
    
    const dynamicPathType = getPath("/v1/user/update", "post", "response", "200");
    console.log(dynamicPathType);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    实现

    const fs = require('fs');
    const axios = require('axios');
    // const apiJson = require('./testDemo/openapi.json');
    // const apiJson = require('./api.json');
    
    const { spawn } = require('child_process');
    
    // 命令和参数
    const command = 'npx';
    const args = ['openapi-typescript', './testDemo/openapi.json', '-o', './testDemo/schema.d.ts'];
    
    // 输出文件夹
    const outputDir = './testDemo';
    
    console.log('Command executing......');
    
    if (!fs.existsSync(outputDir)) {
        // 如果不存在,则创建文件夹
        fs.mkdirSync(outputDir);
        console.log(`Folder "${outputDir}" created.`);
    }
    
    async function fetchSwagger() {
        const url = 'swagger文档地址';
    
        const res = await axios.get(url);
        const data = JSON.stringify(res.data);
        fs.writeFile(`./${outputDir}/openapi.json`, data, (err) => {
            if (err) {
                console.error('Failed to generate file:', err);
            } else {
                console.log('File generated successfully.');
                // 执行命令
                const process = spawn(command, args, { stdio: 'inherit' });
                // 监听命令的退出事件
                process.on('close', (code) => {
                    if (code === 0) {
                        console.log('Command executed successfully.');
                        // 生成所有axios方法内容
    
                        collectTagsAndGenerateCode(res.data);
                    } else {
                        console.error(`Command failed with code ${code}.`);
                    }
                });
            }
        });
    }
    
    async function collectTagsAndGenerateCode(apiJson) {
        const tagsMap = collectTags(apiJson);
    
        for (const modelKey in tagsMap) {
            const model = tagsMap[modelKey];
            const axiosFunctionsContent = generateAxiosFunctions(model.name, model.paths);
            if (fs.existsSync(outputDir)) {
                fs.writeFile(`./${outputDir}/${model.name}.ts`, axiosFunctionsContent, (err) => {
                    if (err) {
                        console.error('Failed to generate file:', err);
                    } else {
                        console.log('File generated successfully.');
                    }
                });
            } else {
                console.log(`Folder "${outputDir}" created.`);
            }
        }
    }
    
    // 接口代码生成,按照文件为维度
    function generateAxiosFunctions(modelName, apiPaths) {
        let axiosFunctions = '';
    
        const header = 'import axios from "../src/service/tools.ts" \n';
        const dts = "import type {paths} from '../schema.d.ts' \n";
    
        // const ReturnValidType = 'type ReturnValidType = T extends { data?: infer D } ? D : T; \n';
        const ReturnValidType = `type ReturnValidType = T[keyof T] extends { content: infer D }
        ? D extends { '*/*': infer U }
            ? U
            : unknown
        : unknown;
        `;
        axiosFunctions += header;
        axiosFunctions += dts;
        axiosFunctions += ReturnValidType;
    
        for (const path of apiPaths) {
            const method = path.method;
            const url = path.path;
    
            // for (const method of methods) {
            const tags = '';
            const axiosFunction = generateAxiosFunction(url, method, tags);
    
            axiosFunctions += axiosFunction + '\n\n';
            // }
        }
    
        return axiosFunctions;
    }
    
    function generateFunctionName(url) {
        // 可根据需要自定义函数名称的生成逻辑,这里使用简单的处理方式
        const parts = url.split('/');
        const functionName = parts
            .filter((part) => part !== '')
            .map((part) => part.replace(/[{}]/g, ''))
            .map((part) => part.replace(/(\b\w)/gi, (match) => match.toUpperCase()))
            .join('');
    
        return functionName;
    }
    
    function generateAxiosFunction(url, method, tags) {
        const functionName = generateFunctionName(url);
    
        return `
            export type IProps_${functionName} = paths['${url}']['${method}']['requestBody']['content']['application/json']['data']
            // export type IRes_${functionName}_prefix =paths['${url}']['${method}']['responses'][200]['content']['*/*']
            export type IRes_${functionName}_prefix =paths['${url}']['${method}']['responses']
            export type IRes_${functionName} =ReturnValidType${functionName}_prefix>
    
            export async function ${functionName}(requestData: IProps_${functionName}) {
            try {
                const response = await axios({
                method: '${method}',
                url: '${url}',
                data: requestData
                });
    
                console.log('API response:', response.data);
                return response.data as IRes_${functionName} ;
            } catch (error) {
                console.error('API error:', error);
                throw error;
            }
            }
        `;
    }
    
    function collectTags(apiJson) {
        const tagsMap = {};
        const tags = apiJson.tags;
        const paths = apiJson.paths;
    
        for (const pathKey in paths) {
            const path = paths[pathKey];
    
            for (const method in path) {
                const pathTags = path[method]?.tags || [];
    
                pathTags.forEach((tag) => {
                    const item = {
                        [pathKey]: {
                            [method]: path[method],
                        },
                        method,
                        path: pathKey,
                    };
    
                    if (tagsMap[tag]) {
                        tagsMap[tag].paths.push(item);
                    } else {
                        tagsMap[tag] = {
                            name: tag,
                            paths: [item],
                        };
                    }
                });
            }
        }
    
        return tagsMap;
    }
    fetchSwagger();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
  • 相关阅读:
    信号类型(雷达)——雷达波形认识(一)
    企业架构LNMP学习笔记37
    c++::作用域符解析
    win 10 mstsc连接 RemoteApp
    ffmpeg 中av_rescale_rnd 的含义
    Java计算Date类相距天数、月数、年数、直接获取年月日
    相机的常见参数分析
    【微服务】spring webflux响应式编程使用详解
    macOS安装brew和使用brew
    HTTP协议和HTTPS协议
  • 原文地址:https://blog.csdn.net/weixin_43294560/article/details/134435593