背景
大部分测试dubbo接口,都是使用Jmeter工具进行测试,需要把jar包下载下来,再根据dubbo接口的传值方法进行测试,对于这一过程及其不方便,有时候会因为dubbo版本与Jmeter插件版本不兼容,导致测试无法进行下去,踩坑较多测试效率较低;为此,本文分享一个dubbo接口测试小工具,大大提高效率!
实现方案
Python + fastapi web框架 + Telnet库
方案原理
dubbo提供了telent命令查看服务,脚本模拟Telnet命令进行dubbo接口测试,通过web框架对Telnet命令进行包装,发送Telnet命令之后,解析数据,通过http接口返回
源码分析
- Telnet分解(总结了dubbo接口大致传参类型,如遇特殊的,可联系我)
def invoke(self, service_name, method_name, arg):
#自定义对象传参
if isinstance(arg, dict) and arg:
command_str = "invoke {0}.{1}({2})".format(
service_name, method_name, json.dumps(arg))
#集合对象传参
elif isinstance(arg, list) and arg:
command_str = "invoke {0}.{1}({2})".format(
service_name, method_name, json.dumps(arg))
#无需对象传值
elif isinstance(arg, dict) and not arg:
command_str = "invoke {0}.{1}()".format(
service_name, method_name)
#枚举值类型传参
else:
command_str = "invoke {0}.{1}({2})".format(
service_name, method_name, arg)
data = self.command(command_str)
try:
# 字节数据解码 utf8
data = data.decode("utf-8").split('\n')[0].strip()
except BaseException:
# 字节数据解码 gbk
data = data.decode("gbk").split('\n')[0].strip()
return data
- 模拟command控制台提交
def command(self, str_=""):
# 模拟cmd控制台 dubbo>invoke ...
if self.conn :
self.conn.write(str_.encode() + b'\n')
data = self.conn.read_until(self.prompt.encode())
return data
else:
return False
3.支持dubbo接口查询(根据IP地址、端口、服务名查询到对应方法名下的传参类型)
def ls_invoke(self, service_name):
command_str = "ls -l {0}".format(service_name)
data = self.command(command_str)
if "No such service" in data.decode("utf-8"):
return False
else:
data = data.decode("utf-8").split('\n')
key = ['methodName', 'paramType','type']
dubbo_list = []
for i in range(0, len(data) - 1):
value = []
dubbo_name = data[i].strip().split(' ')[1]
method_name = re.findall(r"(.*?)[(]", dubbo_name)[0]
value.append(method_name)
paramType = re.findall(r"[(](.*?)[)]", dubbo_name)[0]
paramTypeList = paramType.split(',')
if len(paramTypeList) ==1:
paramTypeList = paramTypeList[0]
value.append(paramTypeList)
if 'java.lang' in paramType or 'java.math' in paramType:
value.append(0)
elif not paramType:
value.append(1)
elif 'List' in paramType:
value.append(2)
else:
value.append(3)
dubbo_list.append(dict(zip(key, value)))
return dubbo_list
4.view源码--dubboList
@router.post('/dubboList', name='dubbo列表接口')
async def dubboList(data: DubboListBody):
host,port = data.url.split(":")
service_name = data.serviceName
method_name = data.methodName
conn = BmDubbo(host, port)
status = conn.command("")
#判断是否连接成功
if status:
#传入方法名,查询对应方法名的传值类型
if method_name:
param_data = conn.param_data(service_name, method_name)
#判断方法是否存在
if param_data:
res_data = {'responseCode': 200, 'responseMsg': "请求成功"}
dubbo_list = {'responseData': param_data}
res_data.update(dubbo_list)
return res_data
#不存在返回报错
else:
return {'responseCode': 301, 'responseMsg': "找不到对应的serviceName"}
#不传,直接返回服务下所有的数据
else:
response_data = conn.ls_invoke(service_name)
if response_data:
res_data = {'responseCode': 200, 'responseMsg': "请求成功"}
dubbo_list = {'responseData':response_data}
res_data.update(dubbo_list)
return res_data
else:
return {'responseCode': 301, 'responseMsg': "找不到对应的serviceName"}
#连接不成功返回报错
else:
return {'responseCode': 302, 'responseMsg': "dubbo服务连接出错"}
5.view源码--dubboInvoke
@router.post('/dubbo', name='dubbo业务请求接口')
async def dubboInvoke(data: DubboInvokeBody):
host,port = data.url.split(":")
service_name = data.serviceName
method_name = data.methodName
boby = data.data
conn = BmDubbo(host, port)
status = conn.command("")
if status:
# 根据服务名和方法名,返回param方法名和类型
param_data = conn.param_data(service_name, method_name)
if param_data:
type = param_data['type']
param = param_data['paramType']
# 传参类型为枚举值方法
if type == 0:
l_data = [v for v in boby.values()]
l_data = str(l_data)
boby = l_data[1:-1]
# 无需传参
elif type == 1:
boby = boby
# 传参类型为集合对象
elif type == 2:
for k, v in boby.items():
if isinstance(v, list):
boby = v
break
# 传参类型为自定义对象
else:
boby.update({"class": param})
response_data = conn.invoke(service_name, method_name, boby)
try:
response_data = json.loads(response_data)
except Exception as e:
res_data = {'responseCode': 207, 'responseMsg': "dubbo接口请求异常"}
res_data.update({'responseData':response_data})
return res_data
return response_data
else:
return {'responseCode': 301, 'responseMsg': "找不到对应的serviceName"}
else:
return {'responseCode': 302, 'responseMsg': "dubbo服务连接出错"}
使用教程
- pip install requirements.txt
- 右键运行main文件
- 访问http://127.0.0.1:5000/api/xxx 即可开始测试dubbo接口
- 具体传参可看dubbo接口文档
- 基于fastapi,将dubbo接口转换成便捷的http接口测试
- 优点:等你来发掘
- 缺点/bug:等你来发掘
dubbo接口文档
1.查询服务名下的所有方法
接口地址
- 说明:根据dubbo接口地址和dubbo接口服务名,查询服务名下的所有方法
- 地址:
/api/dubboList
- 方法:
POST
请求头
序号 | 类型 | 值 | 说明 |
---|---|---|---|
1 | Content-Type | application/json | JSON 格式 |
请求体
序号 | 键值 | 类型 | 说明 |
---|---|---|---|
1 | url | String | dubbo接口地址,IP:端口 |
2 | serviceName | String | 对应的服务名 |
3 | methodName | String | 服务名下对应的方法名 |
请求体示例
{
"url": "xxx.xxx.xx.xx:20880",
"serviceName": "cn.com.xxx.mallerp.api.xxxx.xxxx"
}
返回体
序号 | 键值 | 类型 | 说明 |
---|---|---|---|
1 | responseCode | Int | 返回code |
2 | responseMsg | String | 返回信息 |
3 | responseData | Array | data数组 |
4 | - type | int | 0-枚举值,1-无需传参 2- 集合对象,3-自定义对象 |
- paramType | string | Java传值类型 | |
- methodName | string | 方法名 |
返回值示例(成功)
{
"responseCode": 200,
"responseMsg": "请求成功",
"responseData": [
{
"methodName": "xxxxxx",
"paramType": "java.util.HashMap",
"type": 3
},
{
"methodName": "xxxxxx",
"paramType": [
"java.lang.String",
"java.lang.String",
"java.lang.String",
"java.lang.Integer",
"java.lang.Integer"
],
"type": 0
},
{
"methodName": "xxxxxx",
"paramType": "",
"type": 1
},
{
"methodName": "xxxxxx",
"paramType": "java.util.List",
"type": 2
}
]
}
返回值示例(失败)
{
"responseCode": 500,
"responseMsg": "相应的报错信息"
}
2.dubbo接口-业务接口
接口地址
- 说明:根据dubbo接口地址和dubbo接口服务名,方法名,参数值实现dubbo接口逻辑
- 地址:
/api/dubbo
- 方法:
POST
请求头
序号 | 类型 | 值 | 说明 |
---|---|---|---|
1 | Content-Type | application/json | JSON 格式 |
请求体
序号 | 键值 | 类型 | 说明 |
---|---|---|---|
1 | url | string | dubbo接口地址,IP:端口 |
2 | serviceName | string | 服务名 |
3 | methodName | string | 方法名 |
4 | data | object | 传值4种情况,具体看示例 |
请求体示例 -- 原生对象或者自定义对象传参
{
"url": "xxx.xxx.xx.xx:20880",
"serviceName": "cn.com.xxx.mallerp.api.xxxx.xxxx",
"methodName": "xxxxxx",
"data": { //data传入对应的业务json数据
"productStoreQueryDTOS": [
{
"productNoNumDTOList": [
{
"num": 13,
"productNo": "10000620"
},
{
"num": 13,
"productNo": "10000014"
}
],
"storeCode": "4401S1389"
}
]
}
}
请求体示例 -- 枚举值类型传参
{
"url": "xxx.xxx.xx.xx:20880",
"serviceName": "cn.com.xxx.mallerp.api.xxxx.xxxx",
"methodName": "login",
"data": { //格式为json,顺序必须按照dubbo接口枚举值传参顺序,注意是否为int还是string
"account":"80563855",
"password":"3fd6ebe43dab8b6ce6d033a5da6e6ac5"
}
}
请求体示例 -- 方法名无需传参
{
"url": "xxx.xxx.xx.xx:20880",
"serviceName": "cn.com.xxx.mallerp.api.xxxx.xxxx",
"methodName": "xxxxxx",
"data":{} //传入空对象
}
请求体示例 --集合对象传参
{
"url": "xxx.xxx.xx.xx:20880",
"serviceName": "cn.com.xxx.mallerp.api.xxxx.xxxx",
"methodName": "xxxxxx",
"data":{
"empList": [
"30000445",
"30000444"
]
} //传入对象,里面嵌套数组
}
返回值示例(成功)只展示其中一种
{
"responseData": "dubbo接口返回什么,就返回什么"
}
返回值示例(失败)
{
"responseCode": 500,
"responseMsg": "相应的报错信息"
}
项目地址:dubbo_fastapi