相關背景:
老項目重構,后端返回xml格式數據。
前端有現有的vue項目底層框架可以復用,現有框架支持對后端返回的json數據進行解析,需要調整的就是對xml格式數據的解析。
前端對后端接口的請求使用axios進行封裝,且有mock數據方便本地聯調開發。
解決方案:
封裝xml解析相關方法;
根據后端接口返回數據格式邊寫xml文件進行后端數據mock;
mock數據的實現:
json格式的數據可以直接編寫json格式的數據進行模擬,可以很方便的進行解析。
xml格式的數據如果直接寫成字符串格式的,更新管理起來會比較麻煩,因此可以直接編寫xml文件進行模擬。
對於mock接口數據的xml文件,可以在mock數據請求封裝中直接對xml文件進行讀取解析。
xml解析相關函數封裝:
xmlLoad.js
1 /** 2 * 加載xml文件 3 * @param {Object} dname 4 */ 5 function loadXMLDoc(dname) { 6 let xhttp 7 if (window.XMLHttpRequest) { 8 xhttp = new XMLHttpRequest(); 9 } else { 10 xhttp = new ActiveXObject("Microsoft.XMLHTTP"); 11 } 12 xhttp.open("GET", dname, false); 13 xhttp.send(); 14 return xhttp.responseXML; 15 } 16 17 /** 18 * xml字符串轉換xml對象數據 19 * @param {Object} xmlStr 20 */ 21 function xmlStr2XmlObj(xmlStr) { 22 var xmlObj = {}; 23 if (document.all) { 24 var xmlDom = new ActiveXObject("Microsoft.XMLDOM"); 25 xmlDom.loadXML(xmlStr); 26 xmlObj = xmlDom; 27 } else { 28 xmlObj = new DOMParser().parseFromString(xmlStr, "text/xml"); 29 } 30 return xmlObj; 31 } 32 33 /** 34 * xml直接轉換json數據 35 * @param {Object} xml 36 */ 37 function xmlObj2json(xml) { 38 try { 39 var obj = {}; 40 if (xml.children.length > 0) { 41 for (var i = 0; i < xml.children.length; i++) { 42 var item = xml.children.item(i); 43 var nodeName = item.nodeName; 44 if (typeof(obj[nodeName]) == "undefined") { 45 obj[nodeName] = xmlObj2json(item); 46 } else { 47 if (typeof(obj[nodeName].push) == "undefined") { 48 var old = obj[nodeName]; 49 obj[nodeName] = []; 50 obj[nodeName].push(old); 51 } 52 obj[nodeName].push(xmlObj2json(item)); 53 } 54 } 55 } else { 56 obj = xml.textContent; 57 } 58 return obj; 59 } catch (e) { 60 console.log(e.message); 61 } 62 } 63 64 /** 65 * xml字符串轉換json數據 66 * @param {Object} xml 67 */ 68 function xmlStr2json(xml) { 69 var xmlObj = xmlStr2XmlObj(xml); 70 var jsonObj = {}; 71 if (xmlObj.childNodes.length > 0) { 72 jsonObj = xmlObj2json(xmlObj); 73 } 74 return jsonObj; 75 } 76 77 export default { 78 loadXMLDoc, 79 xmlStr2XmlObj, 80 xmlObj2json, 81 xmlStr2json 82 }
mock后端返回數據:
mocklist.xml
<?xml version="1.0" encoding="utf-8"?> <Apilist> <!--首頁banner--> <searchBanners> <ApiResult> <ret_code>000000</ret_code> <ret_msg>請求成功</ret_msg> <datas> <Banner> <banner_id>001</banner_id> <set_id>001</set_id> <num>0</num> <pic_url>http://xxx.com.jpg</pic_url> <link_type>0</link_type> <link_id>http://xxx.com</link_id> <description>描述</description> </Banner> <Banner> <banner_id>002</banner_id> <set_id>002</set_id> <num>0</num> <pic_url>http://xxx.com.jpg</pic_url> <link_type>1</link_type> <link_id>/myTask</link_id> <description>描述</description> </Banner> </datas> </ApiResult> </searchBanners> ... </Apilist>
如上,高亮部分的標簽可以看做是mock接口的名稱,與前端對接口請求的封裝函數名對應,如下:
request.js
import request from '@api/request' const { errorToast, http } = request /** * 導出接口請求 * noNeedLogin: true 無需登錄 * noPreError: true 無需預處理錯誤信息 */ export default { errorToast, //檢驗用戶登錄有效性 searchBanners(params) { return http({ api: "searchBanners", data: params }) }, ......
注:經驗證,xml文件需要放在static文件夾中,本地編譯vue項目后,能直接在瀏覽器中訪問該文件,以便於xml文件的讀取。
xml文檔中mock數據的讀取:
request.js
1 import axios from "axios" 2 ...... 3 import xmlLoad from '@assets/js/xmlLoad' 4 /** 5 * 封裝mock數據模擬接口請求 6 */ 7 let http2; 8 if (isLocal) http2 = (params) => { 9 var self = this 10 let dname = location.origin + '/static/mocklist.xml' 11 return new Promise((resolve, reject) => { 12 //讀取xml中的數據 13 let xmlNodes = xmlLoad.loadXMLDoc(dname) //xml數據讀取 14 if(!xmlNodes) return; 15 let xmlJson = xmlLoad.xmlObj2json(xmlNodes) //xml轉換為json格式數據 16 let mocklist = xmlJson['Apilist'] //mock數據列表 17 setTimeout(() => { 18 //獲取當前訪問接口名及mock數據 19 let key = params.api 20 var data = mocklist[key].ApiResult 21 resolve(data) 22 }) 23 } 24 25 export default { 26 errorToast, 27 http: isLocal ? http2 : http 28 }
后端接口真實數據的讀取:
request.js
1 import axios from "axios" 2 import xmlLoad from '@assets/js/xmlLoad' 3 ...... 4 5 /** 6 * 封裝服務器端接口請求方法 7 */ 8 const http = (params) => { 9 return new Promise((resolve, reject) => { 10 axios.request({ 11 url: '/xxxx.do',//后端訪問接口地址 12 params: params.data, 13 method: params.method || 'POST' 14 }).then(function(res) { 15 //解析接口返回的xml字符串數據 16 let xmlStr = res.data; 17 let xmlJson = xmlLoad.xmlStr2json(xmlStr) 18 let data = xmlJson && xmlJson["ApiResult"] 19 if (res.status == 200 && data) { 20 ...... 21 resolve(data) 22 } else { 23 errorToast(); 24 console.log(data) 25 } 26 }).catch(function(e) { 27 errorToast(); 28 reject(e) 29 }); 30 }) 31 } 32 33 /** 34 * 封裝mock數據模擬接口請求 35 */ 36 let http2; 37 ...... 38 39 export default { 40 errorToast, 41 http: isLocal ? http2 : http 42 }
如上,即實現了對后端接口數據的模擬,方便本地開發,同時對xml格式數據處理方面也只是在請求封裝中進行,頁面中的實現未受影響。
注意事項:
對於xml到json的轉換,因xml本身並沒有數組的概念,所以需要注意我們需要的“數組”如果只有一條數據的異常處理。
1 <datas> 2 <UserTask> 3 <task_tag>001</task_tag> 4 <task_term>001</task_term> 5 <task_title>xxxx</task_title> 6 <task_status>0</task_status> 7 <task_extra_info>{"tag_name":"xxxx","tag_pic_url":"https://xxxx.jpg","task_begin_time":"2020.06.20","task_end_time":"2020.07.20"}</task_extra_info> 8 </UserTask> 9 </datas>
如上面代碼,如果只有一個 UserTask ,那么解析出來將是一個Object對象:
//只有一個UserTask data:{ UserTask:{ ...... } }
如果有多個,則是數組:
//有多個UserTask data:{ UserTask:[{ ...... },{ ...... },{ ...... }] }
對於這個情況,可以封裝一個函數進行轉換,以方便前端進行頁面布局與數據渲染。代碼如下:
1 /** 2 * xml解析后的列表轉換為js的數組 3 * @param {Object} xmlList 4 */ 5 function xmlListToArr(xmlList) { 6 let targetArr = [] 7 if (xmlList.constructor == Array) { 8 targetArr = xmlList 9 } else { 10 targetArr[0] = xmlList 11 } 12 return targetArr 13 }
如上,可以封裝在公共函數里邊,在頁面中有從后端請求的列表數據需要渲染時,就可以調用這個函數進行數據處理。
這里還需要注意這些問題:
- 大部分列表數據都需要進行數組化處理:畢竟大部分列表的數據都當然,如果是幾個簡單的圖片列表的展示,那可以考慮單個圖片的展示,不必要把單個圖片也轉換成數組;
- 數組化處理操作在具體頁面具體業務代碼中進行:列表數據的處理由於xml中沒有數組的概念,所以沒辦法很好的進行集中封裝處理,因此可以在具體頁面的具體數據進行處理;
- 可以集中封裝處理的情況:如果知道后端返回接口所有列表類數據都嚴格包含某個字符串,如list,那么可以直接在xml解析函數中進行攔截處理就好了。