起因
目前媽媽送房采用的是經(jīng)典的MVC架構(gòu),前端模版使用vm,對于即會java又懂前端的人來說,這種方式?jīng)]有什么不好,反而開發(fā)會很快。
但是,現(xiàn)在的互聯(lián)網(wǎng)是分工協(xié)作的,我們現(xiàn)在的方式是前端開發(fā)人員負責靜態(tài)頁面樣式及交互的開發(fā),后端人員拿到靜態(tài)頁面進行數(shù)據(jù)的綁定,如果一次能搞定,那當然沒什么問題,但是一旦涉及到bug的修改,功能的調(diào)整,那就是折磨了。再加上幾經(jīng)人手,前端代碼已經(jīng)丑陋不堪。所以,我萌生出了對項目進行重構(gòu)并進行前后端分離的想法。為什么要前后端分離,這篇文章講得不錯
剛好最近學(xué)習(xí)了react,實戰(zhàn)中也嘗試了下,并且我們的這個項目特別適合做成一個SPA(single page application,單頁面應(yīng)用),所以就決定利用其進行逐步重構(gòu)。
項目路徑規(guī)劃
因為SPA的代碼最后都需要編譯打包成一個文件,所以我把我的前端工程跟java工程放在一起,方便文件直接生成到j(luò)ava工程中

webpack打包路徑設(shè)置
...
var staticPath = '../../webapp/assets'; // java工程靜態(tài)文件路徑
var viewFilename= '../../webapp/view/main/mmsf/mmsfIndex.vm' // java工程試圖文件路徑
...
// 文件打包后直接生成到j(luò)ava工程下
entry: {
bundle:path.resolve(__dirname, 'app/main.js')
},
output: {
path: path.resolve(__dirname ,staticPath ),
publicPath: '/',
filename: '/js/mmsf/[name].js'
},
...
// 圖片
module: {
loaders:[
...
{ test: /\.(png|jpg)$/, loader: 'file-loader?limit=8192&name=i/mmsf/[name].[ext]'},
]
},
...
// html文件及配置文件處理
plugins: [
...
new CopyWebpackPlugin([
{ from: './app/mmsfIndex.html', to: path.resolve(__dirname, viewFilename) },
{ from: './app/config.js', to: path.resolve(__dirname ,staticPath + '/js/mmsf/config.js') },
]),
]
如何方便的與后端進行接口聯(lián)調(diào)
前后端分離時,數(shù)據(jù)交換一般采取http的方式。開發(fā)時由于前后端不在同一個域,需要進行一些處理,以解決js跨越的限制。
常見的方法有:
- 瀏覽器設(shè)置(chrome),原來確實是一個最方便的方式,在新版的chrome瀏覽器上試過,但是沒成功
- jsonp的方式,有點麻煩,放棄
- 后端設(shè)置http頭(Access-Allow-Control-Origin),本公司后端不會,懶得去求他們,還是自己想辦法吧。
- ngix,最后決定用這個
ngix配置
如果我將ngix配置成如下所示:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
#access_log logs/host.access.log main;
location /api {
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
#rewrite (.*) site-web-personal/$1 permanent;
proxy_set_header Host $host;
proxy_set_header Remote_Addr $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.10.171:8282/api;
}
location / {
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
#rewrite (.*) site-web-personal/$1 permanent;
proxy_set_header Host $host;
proxy_set_header Remote_Addr $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.10.169:8888/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
這樣當我訪問http://localhost/*時會轉(zhuǎn)發(fā)到我本地起的前端調(diào)試web服務(wù),當我訪問http://localhost/api時會轉(zhuǎn)發(fā)到后端接口服務(wù)。
但是,我們的后端一開始并沒有統(tǒng)一規(guī)劃,所以也就沒有區(qū)別接口的url地址和訪問頁面的url地址。
最后想到的辦法是,利用eclipse->server可以給工程配置一個統(tǒng)一的url根目錄來解決:

然后ngix配置改成:
...
location /house-web-web {
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
#rewrite (.*) site-web-personal/$1 permanent;
proxy_set_header Host $host;
proxy_set_header Remote_Addr $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.10.171:8282/house-web-web;
}
...
此時,訪問http://localhost/house-web-web的請求會轉(zhuǎn)發(fā)給后臺接口,當然前端也需要進行配置:
// 上線需要改這兩個參數(shù)
window.debug = true;
// baseurl
window.apiBase = window.debug ? '/house-web-web' : ''
// 前端路由
window.url = {
'discoverIndex' : '/discover/index.htm', // 發(fā)現(xiàn)首頁
'discoverDetail' : '/discover/detail.htm', // 發(fā)現(xiàn)詳情
'searchIndex' : '/search/index.htm', // 搜索首頁
'searchDetail' : '/search/detail.htm' // 搜索詳情
}
// api
window.api = {
'discoverIndex': window.apiBase + '/discover/getIndexJson.htm', //發(fā)現(xiàn)首頁
'discoverDetail': window.apiBase + '/discover/detailPage.htm', // 發(fā)現(xiàn)詳情
}
這樣,此時在瀏覽器中訪問http://localhost/#/discover/index.htm則會到本地前端調(diào)試的服務(wù),在該頁面中訪問http://localhost/house-web-web/discover/getIndexJson.htm則會訪問后臺的接口
前端路由
react的前端路由有兩種方式hashHistory和browserHistory
由于開發(fā)時webpack-dev-server啟動的服務(wù)不支持browserHistory,所以開發(fā)時用hashHistory,生產(chǎn)上用browserHistory
<Router history={window.debug ? hashHistory : browserHistory}>
<Route path="/">
<Route path={window.url.discoverIndex} component={DiscoverList}/>
<Route path={window.url.discoverDetail} component={DiscoverDetail}/>
<Route path={window.url.searchIndex} component={SearchIndex}/>
<Route path={window.url.searchDetail} component={SearchDetail}/>
</Route>
</Router>
圖片處理
圖片有點不好弄,因為react中引入圖片是相對于工程的路徑,而到瀏覽器中訪問時是相對于java靜態(tài)文件目錄的路徑,所以這里的處理方式是:
- 對于小圖片,統(tǒng)一使用base64格式,這樣不僅優(yōu)化了訪問速度,也解決了圖片路徑問題
- 對于大圖片,保證前端圖片路徑與java靜態(tài)文件圖片路徑一致,且圖片的訪問路徑用絕對路徑
例子:
background-image: url('/assets/i/mmsf/search-banner.png')
為了確保webpack對圖片進行處理,在react中對用到的圖片均import
// 這里引入只是為了使webpack將圖片拷貝到正確的路徑
import bannerElderly from '../../assets/i/mmsf/discover-detail-banner.png'
import iconShare from '../../assets/i/mmsf/icon-share.png'
后端url處理
由于生產(chǎn)上還是需要使用真實路徑,所以需要后端進行配合,所有用前端路由的url地址均渲染mmsfIndex.vm

總結(jié)
總的下來,實現(xiàn)這一方案,用了很多非主流的方法,也讓我知道了做技術(shù)只要掌握了原理,是可以根據(jù)自己的需要進行變通的。接下來就是逐步把原來的頁面都遷移到新項目中來了,可以跟原來丑陋的代碼說拜拜咯。
代碼地址