1. 程式人生 > >Vue前後端分離運用實踐中遇到的坑

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;

    });

  },

問題解決。