根据swagger文档生成ts接口文件

369 阅读4分钟

前言

ts接口文件每次都去写非常麻烦,特别是使用ts定义后端请求或者响应的数据类型很费时间,这里使用python写个小工具来解决这个繁琐的问题,你需要先准备python环境

代码

我这里根据config.json配置文件来生成具体文件,放到根目录

{
  "api": "后端ip地址",
  "dir_dict": {
    "模块名字": "生成的文件路径"
  }
}
例如
{
  "api": "http://......",
  "dir_dict": {
    "用户管理": "/api/user/manage.ts"
  }
}

python代码

准备好环境后使用命令pyinstaller -F index.py打包成一个exe文件 我们可以直接把这个文件放到前端工程的根目录。

我这里打包后的文件名是api.exe,我在前端项目的根目录执行 api 用户管理

image.png 生成结果

image.png

# 导入 requests 包
import json
import os
import re
import string
from pathlib import Path

import requests


# 获取所有接口路径
def get_all_path():
    return pathDict.keys()


def dict_keys_to_arr(keys):
    res_val = []
    for item in keys:
        res_val.append(item)
    return res_val


# 获取口路径下所有请求方式
def get_all_req_methods(path):
    y = pathDict[path]
    return dict_keys_to_arr(y.keys())


# 接口描述
def get_api_des(path, method):
    return pathDict[path][method]['summary']


# 获取ref执行类型-名字
def get_type_name(ref):
    return ref.split('#/definitions/')[1]


# 获取返回值的ref执行类型-名字
def get_api_res_type(path, method):
    return get_type_name(pathDict[path][method]['responses'][200]['schema']['$ref'])


# 拿到请求ref指向的类型对象
def get_api_ref_type(res_type_name):
    return typeDict[res_type_name]['properties']


def get_require_list(res_type_name):
    tar = typeDict[res_type_name].get('required')
    return tar if tar else []


def create_interface(name, content):
    print(f'类型名称:{name}')
    resType = typeNameDict.get(name)
    if resType:
        return name
    resType = f'export interface {name}' + '{' + content + '}'
    typeNameDict[name] = resType
    typeList.append(resType)
    return name


# 返回interface的名称
def create_ref_type(name, path_str, is_req):
    p = 'ReqType' if is_req else 'ResType'
    # name = get_type_name(ref_name)
    print(name)
    type_info = typeDict[name].get('properties')
    res_val = ''
    if type_info:
        for item in dict_keys_to_arr(type_info.keys()):
            r = judge_required(item in get_require_list(name) if is_req else True)
            if type_info[item].get('type') == 'array':
                cur = type_info[item]['items']
                if cur.get('$ref'):
                    res_val += f'{item}{r}{create_ref_type(get_type_name(cur["$ref"]), name + "/" + item, is_req)}[]'
                elif cur.get('items'):
                    items = cur.get('items')
                    res_val += f'{item}{r}{items.get("type")}[]'
                else:
                    res_val += f'{item}{r}{get_symbl_type(cur["type"])}[]'
            elif type_info[item].get('$ref'):
                # 复杂数据类型
                res_val += f'{item}{r}{create_ref_type(get_type_name(type_info[item].get("$ref")), name + "/" + item, is_req)}'

            else:
                # 请求参数才校验是否必传,响应参数都是必传
                res_val += f"{item}{r}{get_symbl_type(type_info[item]['type'])}"
            res_val += ';'
    else:
        res_val = 'object'
    return create_interface(f'{create_hump_name(path_str)}{p}', res_val)


def get_symbl_type(type_name):
    return typeMapDict.get(type_name, type_name)


# query path body
# api函数的参数获取
def get_parameters_type(path_str):
    method = get_all_req_methods(path_str)[0]
    res_val = []
    info = pathDict[path_str][method].get('parameters')
    if info is None:
        return ''
    query_list = []
    for item in info:
        t = item['in']
        if t == 'body':
            # 创建interface类型
            inter = create_ref_type(get_type_name(item['schema']['$ref']), path_str + '/type', True)
            # 将创建好的类型标注给参数
            # res_val += f'params:{inter}'
            res_val.append(f'params:{inter}')
            # res_val += ','
        elif t == 'query':
            query_list.append(item)
        else:
            res_val.append(f'id{judge_required(item["required"])}{get_symbl_type(item["type"])}')
    if len(query_list) > 0:
        s = ''
        for item in query_list:
            s += f'{item["name"]}{judge_required(item["required"])}{get_symbl_type(item["type"])};'
        res_val.append(f'params:{create_interface(create_hump_name(path_str + "/type"), s)}')
    # return ','.join(res_val)
    return res_val


def judge_required(judge_str):
    return no_require if not judge_str else require


def get_responses_type(path_str):
    method = get_all_req_methods(path_str)[0]
    res_val = ''
    info = pathDict[path_str][method]['responses']['200']['schema']['$ref']
    name = get_type_name(info)
    if 'IHttpResult' in name:
        type_struct = name.replace('«', ",").replace("»", ",").split(",")
        symbol_type = ['boolean', 'int', 'long', 'string', 'Void']
        fan_type = ''.join(re.findall(r'«([^«»]*)»', name))  # 拿到最里层类型
        if 'List' in type_struct or 'IPageResp' in type_struct:
            if fan_type in symbol_type:
                return f'{get_symbl_type(fan_type)}[]'
            else:
                return f'{create_ref_type(fan_type, path_str, False)}[]'
            # 完整类型需要放开
            # return f'IHttpResult<{create_ref_type(fan_type, path_str, False)}[]>["data"]'
        # elif 'IPageResp' in type_struct:
        # 完整类型需要放开
        # return f'IHttpResult<IPageResp<{create_ref_type(fan_type, path_str, False)}[]>>["data"]'
        elif fan_type in symbol_type:
            # 完整类型需要放开
            # return f'IHttpResult<{get_symbl_type(fan_type)}>'
            return f'{get_symbl_type(fan_type)}'
        return f'{create_ref_type(fan_type, path_str, False)}'
    else:
        return create_ref_type(get_type_name(info), path_str, False)


def remove_non_english_chars(input_string):
    # 创建一个包含所有英文字符的集合
    english_chars = set(string.ascii_letters)

    # 遍历输入字符串,只保留英文字符
    cleaned_string = ''.join([char for char in input_string if char in english_chars])

    return cleaned_string


# 驼峰命名
def create_hump_name(path_str):
    hs = has_url_id(path_str)
    new_path_ = id_url_to_path(path_str) if hs else path_str
    r = ''
    for index, val in enumerate(new_path_.split('/')[1:]):
        # print(index, item)
        cleaned_string = remove_non_english_chars(val)
        if len(cleaned_string) == 0:
            continue
        if index > 0:
            r += cleaned_string.capitalize()
        else:
            r += cleaned_string
    return r


# 判断是否含有动态参数 path/{id}
def has_url_id(path_str):
    return '}' in path_str


# 将含有动态参数 的地址,去掉参数 保留path
def id_url_to_path(path_str):
    return re.sub(r'/{[^{}]*}', '', path_str)


# 创建类型文件
def create_type_file(api_enum, api_func_content, types):
    import_head = 'import { defHttp } from "/@/utils/http/axios"'
    str_ = f'''
       {import_head}
       {types}
       {api_enum}
       {api_func_content}
    '''
    return str_


# 创建一项路径枚举
def create_enum_api_item(path_str):
    # s = path_str.split('/')
    name = f'{create_hump_name(path_str)}Path'
    return name, f'{name}="{path_str}"'


def create_api_func_item(path_str, has_id_p, has_body, method, body_type, enum_path_name, res_type='any'):
    api_can = {
        'has_id': False,
        'val': ''
    }
    if has_id_p:
        api_can['has_id'] = True
        api_can['val'] = 'url:' + '`${Api.' + enum_path_name + '}/' + '${id}`'
    if has_body:
        api_can['val'] = api_can['val'] + f',params' if api_can[
            'has_id'] else 'url:' + '`${Api.' + enum_path_name + '}`' + f',params'
    if not has_id_p and not has_body:
        api_can['val'] = 'url:' + '`${Api.' + enum_path_name + '}`'
    can2 = "{" + api_can['val'] + '}'
    func = f'defHttp.{method}<{res_type}>({can2})'
    param_list = get_parameters_type(path_str)
    p = []
    for item in param_list:
        cur = item.split(':')
        t = cur[1]
        k = cur[0]
        p.append(f'* @param {{{t}}} {k}')
        # p += f'* @param {{{t}}} {k}' + '\n' if len(p) > 0 else f'* @param {{{t}}} {k}'
    ss = '\n'.join(p)
    return f'''
    /**
     * @description: {get_api_des(path_str, method)}
     {ss}
     * @return {{{res_type}}}
     */
    export const {create_hump_name(path_str)}Api=({','.join(param_list)})=>{func}
    '''
    # 开始生成单个接口类型


# 根据tag 找到所有的接口路径
def find_path_by_tag(tag_name):
    path_list = get_all_path()
    res_val = []
    for path_item in path_list:
        method = get_all_req_methods(path_item)[0]
        # print(method)
        if tag_name in pathDict[path_item][method]['tags']:
            res_val.append(path_item)
    return res_val


def list_to_content(str_list, split_str=''):
    res_val = ''
    for item in str_list:
        res_val += f'{item}{split_str}\n'
    return res_val


# 判断接口是否携带对象参数
def has_body_func(path_str):
    m = get_all_req_methods(path_str)[0]
    p = pathDict[path_str]
    if p[m].get('parameters'):
        for item in p[m]['parameters']:
            if item['in'] == 'body' or item['in'] == 'query':
                return True
        return False
    else:
        return False


def main(dir_dict):
    # args = sys.argv
    # print(args)
    # if len(args) == 1 or len(args) > 2 or not dir_dict.get(args[1]):
    #     input('参数错误')
    #     return
    # t_name = args[1]
    t_name = '任务报告获取'#写死测试
    path_list = find_path_by_tag(t_name)
    r = []
    c = []
    for item in path_list:
        has_id = has_url_id(item)
        new_path = id_url_to_path(item) if has_id else item
        enum_name, content = create_enum_api_item(new_path)
        c.append(content)
        r.append(
            create_api_func_item(
                f'{item}',
                has_id,
                has_body_func(item),
                get_all_req_methods(item)[0],
                None,
                enum_name,
                get_responses_type(item)))
    func_content = list_to_content(r)
    api_enum_content = 'enum Api {' + list_to_content(c, ',') + '}'
    # create_interface('IHttpResult<T>', '''
    #            code:string;
    #            data:T;
    #            errorLevel:string;
    #            message:string;
    #            subCode:string;
    #            subMessage:string
    #        ''')
    # create_interface('IPageResp<T>', '''
    #            pageCount:number;
    #            pageRecords:T;
    #            pageNum:number;
    #            pageSize:number;
    #            totalCount:number;
    #        ''')
    interface_content = list_to_content(typeList)
    # create_type_file(api_enum_content, func_content, interface_content)
    content = create_type_file(api_enum_content, func_content, interface_content)
    p = f'{Path.cwd()}{dir_dict[t_name]}'
    create_dir(dir_dict[t_name])
    with open(p, 'w', encoding='utf-8') as f:
        f.write(content)
    input(f'{t_name}接口文件已更新到{p}')


def create_dir(dir_path):
    cur_path = Path.cwd()
    arr = dir_path.split('/')
    for item in arr[0:len(arr) - 1]:
        if len(item) == 0:
            continue
        cur_path = os.path.join(cur_path, item)
        if not os.path.exists(cur_path):
            os.makedirs(cur_path)


if __name__ == "__main__":
    #     需要生成类型的接口路径,没有则生成全部

    # 打开文件并读取内容
    with open('config.json', 'r', encoding='utf-8') as file:
        data = json.load(file)
    api_path = f'{data["api"]}/v2/api-docs'
    write_dir = data["dir_dict"]
    # 发送请求
    x = requests.get(api_path)
    res = x.json()
    res.keys()
    typeList = []  # 声明的类型
    typeNameDict = {}
    typeDict = res['definitions']
    pathDict = res['paths']
    complexTypes = ['array', 'object']  # 复杂类型
    typeMapDict = {  # 需要转换为ts类型的
        'integer': 'number',
        'int': 'number',
        'long': 'number',
        'Void': 'null'
    }
    require = ':'
    no_require = '?:'
    enum_path_list = []
    main(data["dir_dict"])