postman高阶脚本
一、postman常用脚本之断言
1、状态码及说明
==(1)状态代码是200==
pm.test("Status code is 200", function () {
pm.response.to.have.status(200); # 200是int类型
});
(2)状态码名称(OK、Not Found……)
pm.test("Status code name has string", function () {
pm.response.to.have.status("Created"); # Created:string类型
});
(3)实际状态码在预期几个状态码其中
pm.test("状态码是200或201", function () {
pm.expect(pm.response.code).to.be.oneOf([201,202]);
});
2、响应中字符串断言
(1)检查响应主体是否 等于字符串
pm.test("响应数据 = 某个string", function () {
pm.response.to.have.body("response_body_string");
});
==(2)检查响应主体是否 包含字符串==
pm.test("Body matches string", function () {
pm.expect(pm.response.text()).to.include("string_you_want_to_search");
});
3、检查JSON值(※极其重要※)
==(1)检查响应数据某 字段的值是否等于 xxx==
pm.test("Your test name", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.student.count).to.eql(100);
});
==(2)判断响应中 某字段下是否包含某字段(用js语言)==
hasOwnProperty
pm.test("检查响应中choose_subject字段是否包含name字段",function(){
var jsonData = pm.response.json();
pm.expect(jsonData.choose_subject.hasOwnProperty("name")).to.eql(true);
});
jsonData.choose_subject.containsKey.every(item => "name" in item); //(这个不用看)
下面这是加了js循环的(可以不看):
pm.test("检查响应中choose_subject字段是否包含name字段",function(){
var jsonData = pm.response.json();
for (i=0; i<jsonData.choose_subject.length; i++ ){
oneJson = jsonData.choose_subject[i];
pm.expect(oneJson.hasOwnProperty("name")).to.eql(true);
console.log(bool);
}
});
// jsonData.choose_subject.containsKey.every(item => "name" in item);
==(3)判断响应中某字段下某字段的值是否包含某字符串(用js语言)==
to.include
pm.test("响应中包含buy_status字段,其下subject_name字段值包含字符串语文、数学、英语",
function () {
var jsonData = pm.response.json();
pm.expect(jsonData.buy_status[0].subject_name).to.include('语文');
});
==(4)判断响应中某字段的值不为空(不为0、“”、[]、{}等)==
pm.test("响应content字段的值不为空", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.content ? true:false).to.eql(true);
});
4、header中某key存在
pm.test("Content-Type is present", function () {
pm.response.to.have.header("Content-Type");
});
5、判断响应中字段数据类型
我大概整理一下几种类型的:
number 、string 、object 、array 、boolean 、undefind(空)
pm.test("term_type 是整型", function () {
pm.expect(typeof(jsonData.return_list[0].term_type) === "number").to.eql(true);
});
pm.test("id 是字符串", function () {
pm.expect(typeof(jsonData.return_list[0].id) === "string").to.eql(true);
});
pm.test("success 是布尔值", function () {
pm.expect(typeof(jsonData.success) === "boolean").to.eql(true);
});
pm.test("return_list 是 array数组", function () {
pm.expect(jsonData.return_list instanceof Array).to.eql(true);
});
pm.test("name字段未定义(或者说不存在)", function () {
var jsonData = pm.response.json();
pm.expect(typeof(jsonData.name) === "undefined").to.eql(true);
});
扩展一下js语言(for循环):
var jsonData = pm.response.json();
for (i=0; i<jsonData.choose_subject.length; i++ ){
oneJson = jsonData.choose_subject[i];
console.log(oneJson );
}
6、响应时间
- 响应时间小于200毫秒
pm.test("Response time is less than 200ms", function () {
pm.expect(pm.response.responseTime).to.be.below(200);
});
- 响应时间大于10毫秒
pm.test("响应时间小于200毫秒", function () {
pm.expect(pm.response.responseTime).to.be.above(10);
});
7、json高级
(1)使用TinyValidator获取JSON数据
var schema = {
"items": {
"type": "boolean"
}
};
var data1 = [true, false];
var data2 = [true, 123];
pm.test('Schema is valid', function() {
pm.expect(tv4.validate(data1, schema)).to.be.true;
pm.expect(tv4.validate(data2, schema)).to.be.true;
});
(2)JSON模式验证器
var Ajv = require('ajv'),
ajv = new Ajv({logger: console}),
schema = {
"properties": {
"alpha": {
"type": "boolean"
}
}
};
pm.test('Schema is valid', function() {
pm.expect(ajv.validate(schema, {alpha: true})).to.be.true;
pm.expect(ajv.validate(schema, {alpha: 123})).to.be.false;
});
(3)将XML主体转换为JSON对象
var jsonObject = xml2Json(responseBody);
8、解码base64编码数据
var intermediate,
base64Content, // assume this has a base64 encoded value
rawContent = base64Content.slice('data:application/octet-stream;base64,'.length);
intermediate = CryptoJS.enc.Base64.parse(base64content); // CryptoJS is an inbuilt object, documented here: https://www.npmjs.com/package/crypto-js
pm.test('Contents are valid', function() {
pm.expect(CryptoJS.enc.Utf8.stringify(intermediate)).to.be.true; // a check for non- emptiness
});
二、postman进阶脚本
1、发送请求(pm.sendRequest)
(1)发送get请求
//const 是定义常量
const url = 'http://115.28.108.130:5000/api/user/getToken/?appid=136425';
// 发送get请求
pm.sendRequest(url, function (err, res) { //err, res这两个命名随便,但是必须第一个表示异常,第二个表示响应
console.log(err ? err : res.text()); // 控制台打印请求文本。res.text()、res.json()
});
(2)发送表单式post请求
//构造一个登录请求
const loginRequest = {
url: 'http://115.28.108.130:5000/api/user/login/',
method: "POST",
body: {
mode: 'urlencoded', // 模式为表单url编码模式
urlencoded: 'name=张三&password=123456'
}
};
// 发送请求
pm.sendRequest(loginRequest, function (err, res) {
console.log(err ? err : res.text());
});
(3)发送JSON格式请求
// 构造一个注册请求
const regRequest = {
url: 'http://115.28.108.130:5000/api/user/reg/',
method: 'POST',
header: 'Content-Type: application/json', //注意要在Header中声明内容使用的类型
body: {
mode: 'raw', // 使用raw(原始)格式
raw: JSON.stringify({ name: '小小', password: '123456' }) //要将JSON对象转为文本发送
}
};
// 发送请求
pm.sendRequest(regRequest, function (err, res) {
console.log(err ? err : res.json()); // 响应为JSON格式可以使用res.json()获取到JSON对象
});
(4)发送XML格式请求
发送XML格式和发送JSON格式差不多, 只要指定内容格式并发送相应的内容即可
//构造请求
const demoRequest = {
url: 'http://httpbin.org/post',
method: 'POST',
header: 'Content-Type: application/xml', // 请求头种指定内容格式
body: {
mode: 'raw',
raw: '<xml>hello</xml>' // 按文本格式发送xml
}
};
//发送请求
pm.sendRequest(demoRequest, function (err, res) {
console.log(err ? err : res.json());
});
2、更改接口请求顺序
用处:有时候我们不希望集合按照严格顺序执行接口。这时候就可以更改接口请求的顺序。
举例:
假如你的集合下有三个接口A、B、C,其中A接口返回字段有一个content_type,
当content_type=1时,我们期望执行B接口,再执行C接口结束;
当content_type=2时,我们期望直接执行C接口结束;
(1)一般模式
- postman.setNextRequest('接口请求的名称');
//更改接口执行顺序
postman.setNextRequest("用脚本发送post请求");
(2)分支模式
var jsonData = pm.response.json()
if(jsonData.args.a == 'a'){
postman.setNextRequest("D");
}
else{
postman.setNextRequest("B");
}
(3)轮询模式
场景:当本接口响应数据为 "{}\n"时,再一次执行本接口,直到响应数据改变
if(pm.response.text() == "{}\n"){
postman.setNextRequest("自身接口的名称");
}
3、脚本中打印请求url、入参、响应信息
(1)打印请求所有信息(包括url、入参、headers等)
console.log(request);
(2)打印请求的 url
console.log(request.url);
get请求的url是带参数的;
post请求的url不带参数
(3)打印get请求的所有入参
如get请求的url是:
https://pMobile/recite_word/tlist.vpage?clazz_level={{tuobi_qiaoSuan_grade}}&sid={{student_id}}
console.log(request.url.split('?')[1]);
(4)打印get请求的所有入参某个入参
如get请求的url是:
https://pMobile/recite_word/tlist.vpage?clazz_level={{tuobi_qiaoSuan_grade}}&sid={{student_id}}
我想打印 sid 这个入参的值:
- 第一种方法:
console.log(request.url.split('?')[1].split('&')[1].split('=')[1]);
- 第二种方法:
上面这个方法,当入参顺序变化时,会取错值,为了取正确,优化成这样写:
var keys = request.url.split('?')[1].split('&');//获取到所有的参数,组成数组
for(index in keys){
key = keys[index].split('=')[0]; //获取到参数对的key
value = keys[index].split('=')[1]; //获取到参数对的value
if(key === 'sid'){ //当参数对的key=我要的sid时
let sid = value; 自定义一个变量叫sid,值等于上面参数对的value
console.log(sid);
}
}
(5)获取post参数的所有入参
console.log(request.data);
(6)获取post参数的入参中“mobile”这个参数
console.log(request.data.mobile);
(7)获取请求的headers
console.log(request.headers);
4、脚本中打印响应信息
(1)获取响应所有信息
console.log(pm.response);
(2)获取响应的headers
console.log(pm.response.headers.members[0]['key']);
console.log(pm.response.headers.members[2]['value']);
(3)获取响应状态码及说明
console.log(pm.response.code);
console.log(pm.response.status);
5、计算数组长度
function count(o){
var t = typeof o;
if(t == 'string'){ //计算字符串的长度
return o.length;
}else if(t == 'object'){//计算数组的长度
var n = 0;
for(var i in o){
n++;
}
return n;
}
return false;
}
var lesson = jsonData.lessons[0];
console.log(count(lesson));//调用count方法
6、获取一个返回的HTML文件进行校验
var html, titleText;
// load the response body as HTML using cheerio
// and using cheerio's jQuery like .find API, get the H1 tag
html = cheerio(responseBody);
titleText = html.find('h1').text();
// add a test that ensures that there is some H1 content
tests["page must have h1 heading"] = Boolean(titleText);
三、常用的postman测试脚本
1、获取数据列表
// 获取数据列表
//orgData-->JSON数组
function GetList(orgData,key){
var dataList = [];
for(var i=0; i<orgData.length; i++){
dataList[i] = orgData[i][key]; //数据列表添加值
}
return dataList;
}
2、获取JSON数组中的某个值
//获取某个具体的值
//myDataList --> JSON数组
//myValue --> 唯一值
//keyIn --> myValue对应的key
//keyOut --> 需要获取的值对应的key
function GetValue(orgData,myValue,keyIn,keyOut){
var mydata = "";
for(var i=0; i<myDataList.length; i++){
if(myValue == myDataList[i][keyIn]){
var mydata = myDataList[i][keyOut];
}
}
return mydata;
}
3、生成随机代码
var codeStr = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var myProjectCode = getRandomCode(pm.environment.get("orgProjectIDs"),codeStr,1,codeStr.length);
//生成随机代码,且长度大于min小于max
//orgDataList --> 原始代码列表(用来生成唯一代码)
//codeStr --> 代码字符数组
//min --> 生成的代码最小长度
//max --> 生成的代码最大长度
function getRandomCode(orgDataList,codeStr,min,max){
var myCode = "";
for(var i=0; i<getRandomNum(codeStr,min,max); i++){
myCode +=codeStr[getRandomIndex(codeStr)];
}
if(orgDataList.indexOf(myCode)===-1){ //如果账户名称不存在原来账户名中,则执行下一步
return myCode;
}else{
return getRandomCode(orgDataList,codeStr,min,max);
}
}
//生成随机下标
function getRandomIndex(codeStr){
return Math.floor(Math.random()*codeStr.length);
}
//生成代码长度num
//min ≤ num ≤ max
function getRandomNum(codeStr,min,max){
var num = Math.floor(Math.random()*codeStr.length);
if(num>=min && num<=max) return num;
else return getRandomNum(codeStr,min,max);
}
4、生成随机页码、页面显示长度
//生成随机页码、页面显示长度
//orgDataLen --> 页面数据总长度
var orgDataLen = pm.environment.get("orgJSONData").length;
var pageSize = GetPageSize();
var pageNo = GetPageNo(orgDataLen,pageSize);
pm.environment.set("pageSize", pageSize); console.log("当前获取数据为第"+pageNo+"页");
pm.environment.set("pageNo", pageNo); console.log("每页最多显示"+pageSize+"条数据");
//获取页码
function GetPageNo(orgDataLen,pageSize){
var maxNo = Math.ceil(orgDataLen/pageSize);
var pageNo = Math.ceil(Math.random() * maxNo);
return pageNo;
}
//获取页面显示长度
function GetPageSize(){
return (Math.ceil(Math.random() * 10))*10;
}
5、判断实际页面显示列表和预期是否一样
//判断实际页面显示列表和预期是否一样
//orgJSONData --> 数据列表
var testStr = "当前页面获取数据:第"+pm.environment.get("pageNo")+"页,共"+pm.response.json().data.list.length+"条";
pm.test(testStr, function () {
var curDataListLen = pm.response.json().data.list.length;
var orgDataListLen = pm.environment.get("orgJSONData").length;
var pageSize = pm.environment.get("pageSize");
var pageNo = pm.environment.get("pageNo");
if(orgDataListLen <= pageSize){
pm.expect(curDataListLen).to.eql(orgDataListLen);
}else if((orgDataListLen % pageSize) == 0){
pm.expect(curDataListLen).to.eql(pageSize);
}else if(pageNo != (Math.ceil(orgDataListLen/pageSize))){
pm.expect(curDataListLen).to.eql(pageSize);
}else{
pm.expect(curDataListLen).to.eql(orgDataListLen % pageSize);
}
console.log("第"+pageNo+"页获取数据:"+curDataListLen+"条");
});
6.获取当前时间的时间戳
1、引入moment.js库,引入方式有两种
// require 方式
var moment = require('moment'); 亲测在postman可用
// import 方式 ,这种方式会 报错
import moment from 'moment';
2、格式化时间来获取时间戳
var timestemp = moment().format('YYYY年MM月DD日');
var timestemp = moment().format('YYYYMMDDHHmmss');
3、获取时间戳
const timestamp = Date.now();
console.log(timestamp)
// 在postman请求体body中直接使用
{
"time":{{$sys.DateFormat("YYYY-MM-DD HH:mm:ss")}}
}
7.MD5加密
1、引入一个库,crypto-js
// import CryptoJs from crypto-js;(亲测 不引入也是可以的 直接使用)
2、获取MD5加密信息
var jiami = CryptoJS.MD5(str要加密的字符串).toString();
8.获取请求参数
1、获取查询字符串 参数(url 问号后边的参数):const getParams = pm.request.url.query;
2、获取body:var body = pm.request.body.raw
3、获取header:pm.request.headers.get(“Cookie”)
4、获取响应中的值:var response = responseBody // 获取到的响应,若想取其中的某个值,需要 转换为JSON对象格式
5、获取响应中具体某个字段的值:var response_info_name = JSON.parse(responseBody).name // 获取响应体中的 name值
9.JSON字符串 和 JSON对象之间相互转化
1、JSON 字符串 转换为 JSON 对象,使用JSON.parse(字符串), 使用这个 方法
使用场景:
在postman中,想要 在前置条件中 获取请求体中 某个字段的值:
请求体:
{
"name":"xiaoxing",
"sex":"man"
}
我们可以获取 在前置条件中 获取 请求体 var body = pm.request.body.raw。body中的值则上边的请求体,
但是如果 想获取 name 的 值,就需要转化了,如下:
var name = JSON.parse(pm.request.body.raw).name
console.log(name) 则为 xiaoxing 的值。
2、JSON 对象 转化为 JSON 字符串,使用 JSON.stringify(对象),使用这个方法,
使用场景:
在postman中 想对 在 前置条件中 对members的 值进行 MD5加密,方法如下:
请求体:
{
”name“:"xiaoxing",
"sex":"man",
"members":["1", "2", "3"]
}
var name = JSON.parse(pm.request.body.raw).members // 取出 members值
var members_list = JSON.stringify(name) // 将 对象 转化为 list字符串
var md5_str = CryptoJS.MD5(members_list).toString(); // 将结果进行md5加密
console.log(md5_str) // 将结果打印出来
10.无需将参数保存到 环境变量/全局变量,应用参数
var a = 12;
pm.variables.set("b",a);
在请求body、headers 可以使用“{{b}}” 进行使用
11.判断数组是否为空
function isEmpty(value) {
return (Array.isArray(value) && value.length === 0) || (Object.prototype.isPrototypeOf(value) && Object.keys(value).length === 0);
12.时间等待-阻塞等待-会卡住
function sleep(numberMillis) {
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return;
}
}
sleep(4000);
13.时间等待-非阻塞等待-不会卡住
const sleep = time => {
return new Promise(resolve => setTimeout(resolve, time)
)
}
console.log('等待2秒钟')
sleep(2000)
14.Json转String
pm.globals.set("access_token", pm.response.json().access_token);
15.设置全局变量
pm.globals.set("access_token", pm.response.json().access_token);
16.正则表达式
var s = pm.response.text().match(/(.)?csrf_token" value="(.)?"/>/gi,1);
var t = s[0].replace(/.*value="|"/>/g,"");
console.log(t);
17.用代码发送请求
- 先使用 Postman 创建一个请求
- 把该请求 Export 到本地
- 打开 json 文件,找到 item 下的想要发送的请求的 request 信息,如:
"request": {
"method": "POST",
"header": [
{
"key": "Referer",
"value": "https://{{host}}/login"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{"UserName":"{{username}}","PassWd":"{{password}}","pattern":null}"
},
"url": "https://{{host}}/api/V1/login"
}
18. 把 request 的值赋值给变量 req
req = {
"method": "POST",
"header": [
{
"key": "Referer",
"value": "https://{{host}}/login"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{"UserName":"{{username}}","PassWd":"{{password}}","pattern":null}"
},
"url": "https://{{host}}/api/V1/login"
}
19.通过 Postman 的帮助生成发送请求的代码并修改请求地址为 req
pm.sendRequest("https://postman-echo.com/get", function (err, response) {
console.log(response.json());
});
修改为:
pm.sendRequest(req, function (err, response) {
//response为该接口的返回值
console.log(response.json());
});
20.异常捕获
pm.test("Your test name", function () {
var jsonData = pm.response.json();
try{
var id = jsonData.data[0].parcelId;
pm.environment.set("parcelId", id);
}catch(err){
console.log("parcelId is null");
console.log(err)
console.log(err.message);
}
});
21.Tests脚本
const res = JSON.parse(responseBody)
console.log('res:',res)
pm.environment.set('Authorization', 'Bearer '+ res.data.token)
pm.test("登录成功", () => {
pm.expect(res.errorMessage).to.eql("success");
})
22.设置获取、获取变量
函数名 描述
pm.globals.set("variable_key", "variable_value"); 设置一个全局变量
pm.environment.set("variable_key", "variable_value"); 设置一个环境变量
pm.globals.get("variable_key"); 获取一个全局变量(的值)
pm.environment.get("variable_key"); 获取一个局部变量(的值)
pm.variables.get("variable_key"); 获取一个变量(的值)
23.编辑预请求脚本
//设置变量
pm.globals.set("doctype","json");
pm.environment.set("type","AUTO");
pm.environment.set("i","心有猛虎,细嗅蔷薇");
//获取变量
global_doctype_value = pm.globals.get("doctype");
variable_doctype = pm.variables.get("doctype");
//获取变量
environment_type_value = pm.environment.get("type");
variable_type = pm.variables.get("type");
//获取变量
variable_name = pm.variables.get("name");
//打印变量
console.log("通过获取全局变量函数获取全局变量",global_doctype_value)
console.log("通过获取变量函数获取全局变量",variable_doctype)
console.log("通过获取环境变量函数获取环境变量",environment_type_value)
console.log("通过获取变量函数获取环境变量",variable_type)
console.log("通过获取变量函数获取变量",variable_name)//name变量也是一个环境变量,只是说不是脚本设置的
24.清除变量
**函数名 描述pm.globals.unset("variable_key");清除一个全局变量pm.environment.unset("variable_key"); **清除一个环境变量
25.脚本发送post请求
// 构建一个请求
const echoPostRequest = {
url: 'https://postman-echo.com/post',
method: 'POST',//请求方法
header: 'headername1:value1',//请求头部
body: {
mode: 'raw',//数据类型
raw: JSON.stringify({ key: 'this is json' })
}
};
//发送请求
pm.sendRequest(echoPostRequest, function (err, res) {
console.log(err ? err : res.json());
});
四、接口依赖
在遇到有依赖的接口时,比如需要登录或者需要从前一个接口的结果中获取参数时,我们往往需要在该请求前先发送一下所依赖的请求, 我们可以在Pre-request script中使用pm.sendRequest实现
例:发送GET请求
1、构建请求
构建一个GET请求:请求使用到了name,该变量的值是另外一个请求返回来的数据
2、编辑脚本
⑴在脚本区域编辑脚本:在脚本中构建一个get请求,并使用该请求返回的数据作为构建器中的请求的参数
⑵需要注意下预请求脚本中获取响应是res.json()等(获取预请求的响应),而在测试脚本中获取响应是pm.response.json
pm.sendRequest("https://postman-echo.com/get?name=mouseA",function(err,res){
if (err){
console.log(err);
}
else {
console.log(res.json())//打印响应
console.log(typeof(res.json()))//查看响应类型
name = res.json()["args"]["name"]//提取响应中的有效信息
console.log(name)
pm.environment.set("name",name)//将提取出来的内容设为环境变量
}
})
3、查看结果
在console中查看请求的运行结果,并查看环境变量集中变量的变化(将变量存到变量集中的好处在于其他请求可以用到这个变量)
例:发送表单格式Post请求
1、构建请求
构建了一个get请求:该请求就没有选择环境变量集(在脚本中设置的环境变量就不会被存到环境变量集中)
2、编辑脚本
在脚本区域编辑脚本:在脚本中构建一个post请求,并使用该请求返回的数据作为构建器中的请求的参数
//构建请求
const SendRequest = {
url: "https://postman-echo.com/post",
method: "POST",//请求方法
body: {
mode: 'urlencoded', // 模式为表单url编码模式
urlencoded: 'name=mouse&password=123456'
}
};
// 发送请求
pm.sendRequest(SendRequest, function (err, res) {
console.log(err ? err : res.json());
name = res.json()["form"]["name"]
console.log(name)
pm.environment.set("name",name)//设置环境变量
});
例:发送JSON格式Post请求
编辑脚本
这里需要注意的是:需要将Content-Type:application/json定义在请求头中,否则会识别不了传入的json参数
//需要发送的数据
var data = {
"name":"小小",
"password":"123456",}
// 构造一个post请求
const regRequest = {
url: "https://postman-echo.com/post",
method: 'POST',//请求方法
header: 'Content-Type: application/json', //注意要在Header中声明内容使用的类型,使用表单时可以不声明
body: {
mode: 'raw', // 使用raw(原始)格式
raw: JSON.stringify(data)//也可以使用下面那种写法
//raw: JSON.stringify({ name: '小小', password: '123456' })
}
};
//发送请求
pm.sendRequest(regRequest, function (err, res) {
console.log(err ? err : res.json()); // 响应为JSON格式可以使用res.json()获取到JSON对象
console.log(err ? err : res.text());//也可以使用text()方法返回响应内容
name = res.json()["data"]["name"]
pm.environment.set("name",name)
});
例:发送XML格式请求
发送XML格式和发送JSON格式差不多, 只要指定内容格式并发送相应的内容即可
//构造请求
const demoRequest = {
url: 'http://httpbin.org/post',
method: 'POST',
header: 'Content-Type: application/xml', // 请求头种指定内容格式
body: {
mode: 'raw',
raw: '<xml>hello</xml>' // 按文本格式发送xml
}
};
//发送请求
pm.sendRequest(demoRequest, function (err, res) {
console.log(err ? err : res.json());
});
【持续更新中,需要请收藏】
版权归原作者 幸福的达哥 所有, 如有侵权,请联系我们删除。