apipost 8.x 腳本循環調用接口
- 背景
- 實現
- 先說整體邏輯:
- 最后
背景
上周為了找某OA 偶爾出現的詭異現象,需要用測試工具來壓測,看看這個問題能否重現。以前用過Jmeter,但是沒有裝,正好有個國產的apipost看看如何?剛開始使用界面調用接口,還挺順利。但是到腳本調用接口,就是token校驗不通過,這還是問deepseek,這里還有有趣地方:百度查詢apipost 腳本給出例子,然后給deepseek發過去,它說那是postman 例子,隨后給我“正確”的apipost 腳本,我暗自贊嘆,真厲害!但是執行就是校驗不通過,老報301,重定向到登錄界面。后來我就問APIPOST客服,它apipost 里面ai助手,扔給它搞定就行。結果它給我就是postman腳本。我就詫異,我問客服,為何不給apipost 的腳本,她回答:兼容postman。結果一試,真能跑通。太神奇了!這是不是側面說明:apipost自身腳本還不夠成熟。
實現
先說整體邏輯:
前面1-4 就是模擬瀏覽器操作,發起流程一個過程。因為這個只做一遍,不需要循環。
第5步:因為需要循環多個接口,不能使用界面定義接口。它里面又分為
5.1 查詢當前流程最后處理人員
5.2登錄
5.3 保存流程
5.4 移動到下一個操作人員
5.5 提交流程
5.6 登出
重復5.1-5.5 直到5.1 查詢不到最后處理人員為止
主函數如下:
// 定義一個異步函數來處理循環
async function loopRequests() {try {let baseUrl = pm.environment.get("base_url");if (!baseUrl) {throw new Error('base_url 環境變量未設置');}let jsessionId = pm.environment.get("JSESSIONID");let cwbbsAuth = pm.environment.get("cwbbs.auth");let flowId = pm.environment.get('flowId');console.log('jsessionId:' + jsessionId)if (!jsessionId) {throw new Error('jsessionId 環境變量未設置');}if (!flowId) {throw new Error('flowId 環境變量未設置');} else {console.info('開始處理流程flowId:' + flowId)}let hasMore = true;let count = 0;while (hasMore) {console.log(`正在發起第 ${count + 1} 次請求`);try {// 1. 查詢當前流程最后處理人員const url = baseUrl + '/flow/getLastHandlingPersonnel';const headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8','Cookie': 'JSESSIONID=' + jsessionId + ';cwbbs.auth=' + cwbbsAuth};const body = `flowId=${flowId}`;const res = await pm.sendRequest({url,method: 'POST',header: headers,body: {mode: 'raw',raw: body}});const responseText = String(res.stream);console.log('responseText:' + responseText)const responseData = JSON5.parse(responseText);console.log(`第 ${count + 1} 次請求成功:`, responseData);// 4. 根據最后處理人登錄->保存->提交->登出if (responseData.ret == 1) {console.log('開始處理流程...');await login(responseData);await saveFlow(responseData);let nextInternalName = await moveNextPerson(responseData);await finish(responseData, nextInternalName);await logout();console.log('流程處理完成');} else {console.log('ret不為1,停止循環。響應:', responseData);hasMore = false;}count++;if (count >= 20) {console.log('達到最大循環次數20,停止循環');hasMore = false;}} catch (innerError) {console.error(`第 ${count + 1} 次請求失敗:`, innerError.message);console.error('錯誤堆棧:', innerError.stack);hasMore = false; // 出錯時停止循環}}} catch (outerError) {console.error('loopRequests 外部錯誤:', outerError.message);console.error('外部錯誤堆棧:', outerError.stack);throw outerError;}
}
5.2 登錄接口:
async function login(loginData) {try {console.log('開始登錄,用戶:', loginData.userName);const baseUrl = pm.environment.get("base_url");if (!baseUrl) {throw new Error('base_url 環境變量未設置');}if (!loginData.userName) {throw new Error('loginData 中缺少 userName');}const url = baseUrl + '/doLogin.do';const headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'};const body = `name=${loginData.userName}&wdmm=TY%2BD6EZV1gPtH3dQJJkwrA%3D%3D&signature=&op=&mainTitle=&mainPage=&form_token=17560851885711572739&isSavePwd=on`;const res = await pm.sendRequest({url,method: 'POST',header: headers,body: {mode: 'raw',raw: body}});const responseText = String(res.stream);console.log('responseText:' + responseText)const responseData = JSON5.parse(responseText);console.log('after JSON5.parse')if (responseData.ret != '1') {throw new Error(`登錄失敗,ret: ${responseData.ret}, msg: ${responseData.msg}`);}// cookies 保存到環境變量const headers1 = res.headers;// 遍歷所有響應頭headers1.each(header1 => {//console.log(header1.key + ': ' + header1.value);// 專門處理Set-Cookie頭if (header1.key.toLowerCase() === 'set-cookie') {const setCookieValue = header1.value;console.log('Set-Cookie原始值:', setCookieValue);// 解析JSESSIONIDconst jsessionIdMatch = setCookieValue.match(/JSESSIONID=([^;]+)/);if (jsessionIdMatch && jsessionIdMatch[1]) {const jsessionId = jsessionIdMatch[1];pm.environment.set("JSESSIONID", jsessionId);console.log('? 已設置JSESSIONID:', jsessionId);}// 解析cwbbs.authconst cwbbsAuthMatch = setCookieValue.match(/cwbbs\.auth=([^;]*)/);if (cwbbsAuthMatch) {const cwbbsAuth = cwbbsAuthMatch[1] || ''; // 如果是空值也保存pm.environment.set("cwbbs.auth", cwbbsAuth);console.log('? 已設置cwbbs.auth:', cwbbsAuth);}}});} catch (error) {console.error('登錄過程中出錯:', error.message);console.error('登錄錯誤堆棧:', error.stack);throw error;}
}
5.3 保存流程
async function saveFlow(flowResponse) { // 重命名參數避免沖突try {let jsessionId = pm.environment.get("JSESSIONID"); // 獲取cookie值let cwbbsAuth = pm.environment.get("cwbbs.auth"); // 獲取cookie值let flowId = pm.environment.get('flowId');let baseUrl = pm.environment.get("base_url"); // 獲取base_urlif (!baseUrl) {throw new Error('base_url 環境變量未設置');}console.log('---saveFlow開始-----')console.log('jessionId:' + jsessionId + ',cwbbsAuth:' + cwbbsAuth)const url = baseUrl + '/flow/finishAction.do';// 使用FormData對象,讓Postman自動處理multipart格式// 構建URL編碼的表單數據// 定義邊界符const boundary = '----WebKitFormBoundary' + Date.now().toString(16);const crlf = '\r\n';// 構建multipart/form-data bodylet multipartBody = '';// 輔助函數:添加表單字段const addFormField = (name, value) => {multipartBody += `--${boundary}${crlf}`;multipartBody += `Content-Disposition: form-data; name="${name}"${crlf}${crlf}`;multipartBody += `${value}${crlf}`;};// 添加所有字段addFormField('isUseMsg', 'true');addFormField('flowAction', '');addFormField('cwsWorkflowTitle', 'xxx發布test');addFormField('cwsWorkflowResult', '');addFormField('att1', '');addFormField('flowId', flowId);addFormField('actionId', flowResponse.actionId);addFormField('myActionId', flowResponse.myActionId);addFormField('XorNextActionInternalNames', '');addFormField('op', 'saveformvalueBeforeXorCondSelect');addFormField('isAfterSaveformvalueBeforeXorCondSelect', '');addFormField('formReportContent', '');addFormField('gzhlx', 'xxx平臺');addFormField('wzbt', '111');addFormField('tgr', 'test');addFormField('ngr', 'test');addFormField('wzlx', '一般信息');addFormField('tgbm', flowResponse.deptCode); // 不需要手動encodeURIComponent,邊界符會自動處理addFormField('zzlj', '');addFormField('bqsm', '1');addFormField('mgxxjc', '1');addFormField('wznr', '<p>test</p>');addFormField('bmjlyj', '同意');addFormField('gzfzryj', '');addFormField('jycldyj', '');addFormField('dgbfgldyj', '');addFormField('zjlyj', '');addFormField('dszyj', '');addFormField('cws_textarea_sf', '是');addFormField('cws_textarea_ngr', 'test');addFormField('cws_textarea_tgr', 'test');addFormField('cws_textarea_bqsm', '1');addFormField('cws_textarea_tgbm', flowResponse.deptCode);addFormField('cws_textarea_wzbt', '111');addFormField('cws_textarea_wzlx', '一般信息');addFormField('cws_textarea_wznr', '<p>test</p>');addFormField('cws_textarea_zzlj', '');addFormField('cws_textarea_dszyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_gzhlx', 'xxx平臺');addFormField('cws_textarea_zjlyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_bmjlyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_mgxxjc', '1');addFormField('cws_textarea_gzfzryj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_jycldyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_dgbfgldyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('myReplyTextareaContent', '');addFormField('isSecret0', '0');addFormField('myActionId0', flowResponse.myActionId);addFormField('discussId0', '0');addFormField('flow_id0', flowId);addFormField('action_id0', '0');addFormField('user_name0', flowResponse.userName);addFormField('userRealName0', flowResponse.userRealName);addFormField('reply_name0', flowResponse.userName);addFormField('parent_id0', '-1');addFormField('returnBack', 'true');// 添加結束邊界multipartBody += `--${boundary}--${crlf}`;const headers = {'Content-Type': `multipart/form-data; boundary=${boundary}`,'Cookie': 'JSESSIONID=' + jsessionId + ';cwbbs.auth=' + cwbbsAuth};console.log('headers:' + headers);const res = await pm.sendRequest({url,method: 'POST',header: headers,body: {mode: 'raw',raw: multipartBody}});const responseText = String(res.stream);console.log('保存流程,請求:' + multipartBody);console.log('responseText:' + responseText)const responseData = JSON5.parse(responseText);if (responseData.ret == '1') {console.log('? 保存成功');return responseData;} else {console.warn('? 保存失敗,返回值:', responseData.ret);throw new Error(`保存失敗: ${responseData.msg || '未知錯誤'}`);}} catch (error) {console.error('保存流程時出錯:', error.message);throw error; // 重新拋出錯誤}
}
5.4 移動到下一個操作人員
async function moveNextPerson(flowResponse) {try {console.info('開始處理移動到下一個操作人員')const baseUrl = pm.environment.get("base_url");if (!baseUrl) {throw new Error('base_url 環境變量未設置');}const url = baseUrl + '/flow/moveNextPersonnels';const headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'};const body = `op=matchNextBranch&actionId=${flowResponse.actionId}&myActionId=${flowResponse.myActionId}`;console.log('moveNextPerson 請求:' + body);const res = await pm.sendRequest({url,method: 'POST',header: headers,body: {mode: 'raw',raw: body}});const responseText = String(res.stream);console.log('responseText:' + responseText)const responseData = JSON5.parse(responseText);console.log('after JSON5.parse')if (responseData.ret != '1') {throw new Error(`移動下一個操作人員失敗,ret: ${responseData.ret}, msg: ${responseData.msg}`);} else {return responseData.nextInternalName;}} catch (error) {console.error('移動到下一個操作人員出錯:', error.message);console.error('移動到下一個操作人員錯誤堆棧:', error.stack);throw error;}
}
5.5 提交流程
async function finish(flowResponse,nextInternalName) {try {let jsessionId = pm.environment.get("JSESSIONID"); // 獲取cookie值let cwbbsAuth = pm.environment.get("cwbbs.auth"); // 獲取cookie值let flowId = pm.environment.get('flowId');let baseUrl = pm.environment.get("base_url"); // 獲取base_urlif (!baseUrl) {throw new Error('base_url 環境變量未設置');}const boundary = '----WebKitFormBoundaryuUyGuZA3IOGaIBch' + Date.now().toString(16);const crlf = '\r\n';let multipartBody = '';// 定義添加表單字段的方法const addFormField = (name, value) => {multipartBody += `--${boundary}${crlf}`;multipartBody += `Content-Disposition: form-data; name="${name}"${crlf}${crlf}`;multipartBody += `${value}${crlf}`;};// 添加所有字段addFormField('deptOfUserWithMultiDept0', '');addFormField('XorActionSelected', nextInternalName);addFormField(`WorkflowAction_${flowResponse.actionId}`, flowResponse.userName);addFormField('isUseMsg', 'true');addFormField('flowAction', '');addFormField('cwsWorkflowTitle', 'xxx發布test');addFormField('cwsWorkflowResult', '');addFormField('att1', '');addFormField('flowId', flowId);addFormField('actionId', flowResponse.actionId);addFormField('myActionId', flowResponse.myActionId);addFormField('XorNextActionInternalNames', nextInternalName);addFormField('op', 'finish');addFormField('isAfterSaveformvalueBeforeXorCondSelect', 'true');addFormField('formReportContent', '');addFormField('gzhlx', 'xxx平臺');addFormField('wzbt', '111');addFormField('tgr', 'test');addFormField('ngr', 'test');addFormField('wzlx', '一般信息');addFormField('tgbm', flowResponse.deptCode);addFormField('zzlj', '');addFormField('bqsm', '1');addFormField('mgxxjc', '1');addFormField('wznr', '<p>test</p>');addFormField('bmjlyj', '同意');addFormField('gzfzryj', '');addFormField('jycldyj', '');addFormField('dgbfgldyj', '');addFormField('zjlyj', '');addFormField('dszyj', '');addFormField('cws_textarea_sf', '是');addFormField('cws_textarea_ngr', 'test');addFormField('cws_textarea_tgr', 'test');addFormField('cws_textarea_bqsm', '1');addFormField('cws_textarea_tgbm', flowResponse.deptCode);addFormField('cws_textarea_wzbt', '111');addFormField('cws_textarea_wzlx', '一般信息');addFormField('cws_textarea_wznr', '<p>test</p>');addFormField('cws_textarea_zzlj', '');addFormField('cws_textarea_dszyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_gzhlx', 'xxx平臺');addFormField('cws_textarea_zjlyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_bmjlyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_mgxxjc', '1');addFormField('cws_textarea_gzfzryj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_jycldyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('cws_textarea_dgbfgldyj', '<?xml version="1.0" encoding="utf-8"?><myactions></myactions>');addFormField('myReplyTextareaContent', '');addFormField('isSecret0', '0');addFormField('myActionId0', flowResponse.myActionId);addFormField('discussId0', '0');addFormField('flow_id0', flowId);addFormField('action_id0', '0');addFormField('user_name0', flowResponse.userName);addFormField('userRealName0', flowResponse.userRealName);addFormField('reply_name0', flowResponse.userName);addFormField('parent_id0', '-1');addFormField('returnBack', 'true');// 添加結束邊界multipartBody += `--${boundary}--${crlf}`;const url = baseUrl + '/flow/finishAction.do';const headers = {'Content-Type': `multipart/form-data; boundary=${boundary}`,'Cookie': 'JSESSIONID=' + jsessionId + ';cwbbs.auth=' + cwbbsAuth};const res = await pm.sendRequest({url,method: 'POST',header: headers,body: {mode: 'raw',raw: multipartBody}});const responseText = String(res.stream);console.log('提交流程,請求:' + multipartBody);console.log('responseText:' + responseText)const responseData = JSON5.parse(responseText);if (responseData.ret == '1') {console.log('? 提交成功');return responseData;} else {console.warn('? 提交失敗,返回值:', responseData.ret);throw new Error(`提交失敗: ${responseData.msg || '未知錯誤'}`);}} catch (error) {console.error('提交流程時出錯:', error.message);throw error; // 重新拋出錯誤}
}
5.6 登出
async function logout() {try {let jsessionId = pm.environment.get("JSESSIONID"); // 獲取cookie值let cwbbsAuth = pm.environment.get("cwbbs.auth"); // 獲取cookie值 let baseUrl = pm.environment.get("base_url"); // 獲取base_urlif (!baseUrl) {throw new Error('base_url 環境變量未設置');}const url = baseUrl + '/logout?skincode=lte';const headers = {'Cookie': 'JSESSIONID=' + jsessionId + ';cwbbs.auth=' + cwbbsAuth};const res = await pm.sendRequest({url,method: 'GET',header: headers});const responseText = String(res.stream);console.log('登出成功:' + responseText)} catch (error) {console.error('登出出錯:', error.message);throw error; // 重新拋出錯誤}
}
// 執行函數
try {console.log('開始執行主循環...');loopRequests().then(() => {console.log('主循環執行完成');}).catch(error => {console.error('主循環執行失敗:', error.message);console.error('主循環錯誤堆棧:', error.stack);});
} catch (error) {console.error('執行過程中出錯:', error.message);console.error('執行錯誤堆棧:', error.stack);
因為一個流程發起后,經過多少人處理是動態的,不能指定循環多少次。
最后
apipost 腳本寫到這里,這個工具感覺比國外的postman,jemeter 要輕量一些。
另外,它的寫接口和用例分開的
如需溝通,聯系:lita2lz