最近難得公司業務稍微緩解一些,我們終於有時間靜下心總結下技術,對於之前的項目去其糟粕取其精華,我們的目的是:
- 解決后台管理系統的開發效率問題,封裝常用功能,將技術性強的內容分離出去;
- 將重復性高的開發工作統一技術規范,避免各自研究;
- 采用最佳實踐,參考優秀項目,制定最佳方法,至少是最適合當前團隊的。
這里我先總結下我這塊針對數據列表頁的做法,后續再補充其它模塊的做法,先看下列表頁的需求:
- 搜索條件支持動態條件查詢,后端不需要干預相關條件的組裝;即不能出現類似如下的代碼(注:此做法只針對單表的查詢,如果是非常復雜的多表關聯,此方案也許不是最佳的)
if(!StringUtils.isEmpty(employeeEnityRequest.getEmployeeName())){ criteria.andEmployeeNameEqualTo(employeeEnityRequest.getEmployeeName()); } if(!StringUtils.isEmpty(employeeEnityRequest.getEmployeeStatus())){ criteria.andEmployeeStatusEqualTo(Integer.valueOf(employeeEnityRequest.getEmployeeStatus())); }
- 查詢異步,用戶點擊下一頁時如果需要刷新整個頁面體驗性不太好。
上面這兩需求非常常規,有很多種實現方式,我分享下我的做法(我的環境是eclipse,tomcat,maven,spring mvc, mybatise,mysql):
針對動態查詢,我們通過約定規則來實現,比如View中我們可以這樣寫
<input type="text" name="WHERE.storeName.LIKE" class="form-control" style="width: 180px;" " />
它的意思是查詢email字段,操作符是=號。WHERE是固定的,后台解析收集條件時做識別作用,中間的是字段名稱,后面是操作符,操作符比如有EQ,LIKE等常規的數據庫查詢操作符。這樣我們可以在前端任意增加修改條件,而后台的邏輯是不需要有任務變更的,詳細的收集過程請看本文后面的介紹。
針對異步查詢,我采用了angularjs相關技術,當時遇到一個問題:angularjs在查詢時一般都會指定一個寫好的model傳遞到后台,但由於上面動態查詢的條件是變動的(字段名稱不固定,字段數量不固定,操作類型不固定),所以沒有辦法去定義這樣的model。第一直覺是將整個表單傳遞到后台,后台根據表單的值來解決特定的條件,第二個問題來了,既然是將表單傳遞到后台,那么后台要用什么參數來接收這個表單呢,於時想到HttpServletRequest,但經過測試,這個參數始終取不到值,當時的代碼如下:
java
@RequestMapping(value = "/getStoreByPage", method = RequestMethod.POST) @ResponseBody public PageInfo<BcStore> getStoreByPage(HttpServletRequest request,int pageNum, int pageSize) {
js
$.ajax({ type : "POST", url : url, dataType : 'json', data:$("#searchForm").serialize(), async : false, success : function(data) { $scopeLocal.pageResponse = data; $scopeLocal.content=data.list; } });
后來和同事討論說是需要設置ajax的contentType為application/x-www-form-urlencoded,但設置后直接報錯,請求無法到達服務端,說明參數類型匹配錯誤,將后台controller方法中的參數HttpServletRequest刪除后順利通過。但這個參數刪除了,表單值從哪取呢?好在后端也可以取到當前請求,RequestContextHolder可以幫助我們,於是下面的代碼就水到渠成了,通過這個幫助類我們可以從請求中根據我們制定的規定來解析條件,至於條件對象的格式,主要看數據訪問端的使用情況,這里先不貼代碼了,我們主要采用的是通用mapper那套方案,網上可去搜索。到此,問題解決了,數據也可以順利查到了。
private static String DEFAULT_PRE_WHERE = "WHERE."; private String preWhere = DEFAULT_PRE_WHERE; List<String> searchFilterStrings = Lists.newArrayList(); Map<String, String[]> map = request.getParameterMap(); for (Map.Entry<String, String[]> entry : map.entrySet()) { String strKey = entry.getKey(); for (String value : entry.getValue()) { if (!Strings.isNullOrEmpty(value) && !"none".equals(value) && strKey.startsWith(preWhere)) { String filedAndOp = strKey.substring(preWhere.length()); searchFilterStrings.add(String.format("%s.%s", filedAndOp, value)); } } }
列表數據的展示,我沒有采用jquery datatable之類的控件,我感覺需要寫JS代碼,看起來比較復雜,采用angularjs的ng-repeat非常直觀,且容易控制細節。
<table id="datatable1" cellpadding="0" cellspacing="0" border="0" class="datatable table table-striped table-bordered table-hover"> <thead> <tr> <th>門店編號</th> <th>名稱</th> <th>類型</th> <th>店長</th> <th>電話</th> <th>郵箱</th> <th>狀態</th> <th>創建時間</th> <th>操作</th> </tr> </thead> <tbody> <tr ng-repeat="store in content"> <td>{{store.storeCode}}</td> <td>{{store.storeName}}</td> <td> <div ng-show="store.storeType=='1'"> <span class="label label-success">自營店</span> </div> <div ng-show="store.storeType=='0'"> <span class="label label-danger">加盟店</span> </div> </td>
分頁控制我們采用了angularjs與boostrap的一個插件完成,需要引用ui-bootstrap-tpls.min.js以及boostrap-ui相關的代碼才行:
<pagination class="pagination-sm" ng-model="pageRequest.pageNum" total-items="pageResponse.total" max-size="4" ng-change="pageRequest.getResponse()" items-per-page="pageRequest.pageSize" rotate="false" previous-text="上一頁" next-text="下一頁" ></pagination>
js代碼,為了使前端調用方式,我們盡量做了封裝,使得查詢邏輯只需要寫最少的代碼:注入一個$listService,然后傳一個請求地址給它就可以了,當然這里面有些固定寫法,比如一個request對象的屬性,需要前后台配置一起完成才行,不能隨意寫。
var mainApp = angular.module('storeManageApp',['ui.bootstrap']); $.initListService(mainApp); mainApp.controller('storeManageCtrl', function ($scope, $http,$listService) { var listUrl="<c:url value="/store/getAllByPage"/>"; $listService.init($scope,listUrl); $listService.get(); });
angularjs的注入做的不錯,我們封裝的js也參考了angularjs提供的service模式來完成:由於這個service是需要angular對象的,所以做了一個jquery的擴展函數,便於調用,函數里面的代碼就比較簡單的,常規的service寫法,這里指出下,ajax提交后台的參數我沒有采用data參數,而是直接拼接在url上,如果放在data上應該加了那個contentType。
jQuery.extend({ initListService: function(mainApp) { mainApp.service('$listService', function(){ var $scopeLocal={}; this.init = function($scope,listUrl) { $scopeLocal=$scope; $scopeLocal.pageRequest = { "pageNum": 1, "pageSize": "5" }; $scopeLocal.pageRequest.getResponse = function () { var requestData = $("#searchForm").serialize(); var url = listUrl+"?"+requestData+"&pageNum="+$scopeLocal.pageRequest.pageNum; $.ajax({ type : "POST", url : url, dataType : 'json', async : false, success : function(data) { $scopeLocal.pageResponse = data; $scopeLocal.content=data.list; } }); } this.get = function() { $scopeLocal.pageRequest.getResponse(); }; } }); } });
最后上一個列表頁的效果圖:
功能看起來不錯,但還有一些不完善的,比如應該提供幾個數據加載事件便於在數據加載前后做些特殊的處理操作。但是一個好的開始,后續團隊成員只要參考這個模板來做效率上會提升一部分,當然提升效率不光是這篇文章中介紹的,我們還有權限過濾的集成,各類控件的封裝等等功能。