前言
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 用户管理
生成结果
# 导入 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"])