Vue前後端分離運用實踐中遇到的坑
Vue是一套構建使用者介面的漸進式框架,只關注檢視層,採用自底向上增量開發的設計,目標是通過儘可能簡單的API實現響應的資料繫結和組合的檢視元件,易於學習上手,主流前端框架。以下是博主在一次從學習到專案運用過程中的經驗總結。
一、多環境打包部署配置-測試環境
一般使用vue-cli腳手架建立的vue工程中都會有一個打包的配置目錄build,其中對開發環境編譯執行和生產環境打包做了相關配置,如果需要自定義測試環境的打包,我們把build.js複製一份,改名為bulid.test.js作為測試環境打包的總入口,同時複製一份webpack.prod.conf.js---->webpack.test.conf.js,修改成測試環境的相應配置即可。最後在config/index.js中增加如下配置
buildtest: {
index:path.resolve(__dirname, '../dist/index.html'),
assetsRoot:path.resolve(__dirname, '../dist'),
assetsSubDirectory:'static',
assetsPublicPath:'./',
devtool:'#source-map',
productionGzip:false,
productionGzipExtensions: ['js', 'css'],
bundleAnalyzerReport:process.env.npm_config_report
},
這裡對幾個比較重要的配置做下說明:
1. assetsPublicPath:打包後引用的靜態資源位置,這裡使用的是相對路徑,生產環境部署的時候如果需要把靜態資源部署到cdn上,這個配置改為所在cdn的絕對路徑即可。
2. productionSourceMap:打包以後是否保留原始碼map,測試環境為了除錯方便,這裡選擇true,生產環境為了安全需要,建議配置成false。最終打包結果的區別在於是否有下圖中的map檔案
測試環境配置完成以後,在package.json的scripts物件中增加
"buildtest": "nodebuild/build.test.js"
然後就可以使用npmrun buildtest命令來進行測試環境的打包了。
二、打包靜態資源快取問題
修改下圖中的HtmlWebpackPlugin中的hash屬性為true
newHtmlWebpackPlugin({
filename:process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template:'index.html',
inject:true,
hash:true,
minify: {
removeComments:true,
collapseWhitespace:true,
removeAttributeQuotes:true
},
chunksSortMode:'dependency'
}),
hash:true|false,是否為所有注入的靜態資源新增webpack每次編譯產生的唯一hash值,新增hash形式如下所示:
<scriptsrc=./static/js/app.a1c0214fd81a8bf81b0c.js?e5fd6f1bf690011ababd></script>
三、IE相容性問題
Vue官方文件有對IE相容性的說明,因為 Vue 使用了IE8 無法模擬的 ECMAScript 5 特性。所以Vue不支援 IE8 及以下版本,它支援所有相容ECMAScript 5 的瀏覽器。
在專案開發完成後,卻出現了IE11無法訪問的情況,具體報錯資訊:
SCRIPT5022: [vuex] vuex requires a Promisepolyfill inthis browser.
造成這種現象的原因歸根究底就是瀏覽器對ES6中的promise無法支援,因此需要通過引入babel-polyfill來使我們的瀏覽器正常使用es6的功能,解決方案:
1.首先通過npm來安裝polyfill:
npm install babel-polyfill --save-dev
2.修改webpack.base.conf.js檔案module.exports的配置:
entry: {
app: ['babel-polyfill','./src/main.js']
},
四、登入狀態路由攔截
一般系統都會有對所有需要許可權控制的路由進行攔截,確保登入狀態下才能訪問某些路由這類需求。利用vue-router提供的鉤子函式beforeEach()可以很簡單的實現。
第一步:定義路由是否需要攔截
首先在定義路由的時候就需要多新增一個自定義欄位requireAuth,用於判斷該路由的訪問是否需要登入。
{
path:'/xxx,
meta: {
requireAuth:true, // 新增該欄位,表示進入這個路由是需要登入的
},
component:xxx,
redirect:'/xxx/xxx,
children: [{
meta: {
requireAuth:true,
},
path:xxx/',
component:xxx
}
}
第二步:使用鉤子函式beforeEach()攔截路由
// 全域性導航鉤子
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
getCurrentUser().then((curUser)=>{//getCurrentUser是封裝的呼叫後臺http介面的promise介面
if (curUser) {
next();
} else {
next({
path:'/login',
query: {
redirect:to.fullPath //登入頁面獲取該引數,在重新登入後重定向到該路由
}
})
}
}).catch((err)=>{
next({
path:'/login',
query: {
redirect:to.fullPath
}
})
});
} else {
next();
}
})
每個鉤子方法接收三個引數:
* to: Route: 即將要進入的目標 路由物件
* from: Route: 當前導航正要離開的路由
* next: Function: 一定要呼叫該方法來 resolve 這個鉤子。執行效果依賴 next 方法的呼叫引數。
* next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
* next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了(可能是使用者手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址。
* next(‘/’) 或者 next({ path: ‘/’ }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。
這裡為了實現開發環境、測試環境、生產環境都能夠跨域訪問,而且考慮到jsonp方式的跨域無法支援post請求,所以我選擇在服務端新增filter允許跨域請求的方案。(網上一搜一大堆,就是在response里加一些頭資訊)
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; HttpServletRequest httpServletRequest = (HttpServletRequest) request; String origin = httpServletRequest.getHeader("Origin"); if (StringUtils.isNotEmpty(origin)) { httpServletResponse.setHeader("Access-Control-Allow-Origin", origin); httpServletResponse.setHeader("Access-Control-Allow-Headers","Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); httpServletResponse.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); } chain.doFilter(request, response); } |
經過以上過濾器對請求的跨域處理以後,所有的get方式的跨域請求都可以正常使用。後來,因業務需要提交表單資料,需要發post請求,結果並沒有像想象的那樣正常返回結果。事實上,這裡的post請求不是一個嚴格意義上的簡單請求,所以會先發起一個“PreFlight”(也就是Option請求),用來讓服務端返回允許的方法(如get、post),被跨域訪問的Origin(來源,或者域),還有是否需要Credentials(認證資訊),查閱SpringMVC的原始碼發現
之前在response的header中新增的Access-Control-Allow-Origin,在springmvc自己關於跨域攔截器的處理中沒有獲取到(程式碼1),導致程式執行到程式碼2處,拒絕請求。繼續研究ServletServerHttpResponse的構造方法發現,這裡要求Servlet版本達到3.0以上。
修改ServletAPI2.4àServletAPI3.0,完美解決問題。
六、SpringMVC 接收 Axios POST請求引數
1.傳遞簡單引數
和get請求類似,如果想避開IE瀏覽器預設快取的策略,可以在引數中追加一個時間戳,timestamp:Date.now()
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.post("/user", {
params: {
userName:userName,
password:password,
timestamp:Date.now()
}
})
2.傳遞物件引數
對比axios和jquery ajax傳送的請求,發現Form Data內的引數形式有差異,導致controller無法獲取引數並對映到物件。
使用URLSearchParams來處理引數,可以解決這個問題,但是URLSearchParams的相容性並不高,在IE瀏覽器直接報錯。
繼續查閱資料,瞭解到可以使用Qs模組來格式化引數,這個模組在安裝axios的時候就已經安裝了,不需要另外安裝,
importqsfrom'qs'
id:student.id,
name:student.name,
isGood:student.isGood ? 1 : 0,
sex:student.sex,
age:student.age
}
returnaxios.post("/updateStudent", qs.stringify(userInfo))
.then((response) => {
returnresponse.data;
})
},
3.傳遞陣列引數
axios傳遞陣列引數時,發現SpringMVC 的@RequestParam(value= "roleIds[]", required=false) List<String> roleIds無法接收引數,而jqueryajax的卻正常。
下圖是jquery ajax傳遞陣列引數roleIds時,Form Data內的資料形式,陣列不帶下標
下圖是axios 經過Qs格式化後roleIds後,Form Data內的資料形式,陣列帶下標
最終在Qs模組的api中發現
Qs對資料的格式化是有格式引數的,如果沒有指定則使用預設值indices,即上面提到的帶陣列下標的資料形式,這裡將格式化引數加上,
addOrUpdateMenu(param) {
returnaxios.post("/update",
qs.stringify(param,{arrayFormat:'brackets'}))
.then((response) => {
returnresponse.data;
});
},
問題解決。