有了開發規范及后臺的數據支持,我們前臺的對接就更加簡單了。
上一節中,我們已經新建了klassServer, 本節的數據增加,我們同樣寫在這個文件中.
# services/klass.js
```
// 新增數據
var save = function(name, teacherId, callback) {
server.http({
method: 'POST',
url: 'klass.Save.json',
data : {
name: name,
teacherId: teacherId
}
}, function(response) {
callback(response);
});
}
// Public API here
return {
// 獲取全部教師信息
paginate: function(name, page, pageSize, callback) {
return paginate(name, page, pageSize, callback);
},
save: save
};
```
## 單元測試
```
'use strict';
describe('Service: klass', function() {
// load the service's module
beforeEach(module('webAppApp'));
// instantiate service
var klass, $httpBackend, config;
beforeEach(inject(function(_klass_, _$httpBackend_, _config_) {
klass = _klass_;
config = _config_; // 引用項目配置
$httpBackend = _$httpBackend_;
// 定義請求 URL
var url = config.apiRootPath + '/Klass.json';
// 定義返回數據, 僅定義正確的返回碼。
var data = {code:200};
// 進行模似數據請求配置.當請求方法為post,資源名為url時, 返回data數據.
$httpBackend.when('POST', url).respond(data);
// 定義請求 URL
url = config.apiRootPath + '/klass.Save.json';
// 定義返回數據, 僅定義正確的返回碼。
// 進行模似數據請求配置.當請求方法為post,資源名為url時, 返回data數據.
$httpBackend.when('POST', url).respond(data);
}));
it('檢測語法是否出現錯誤', function() {
// 調用方法
klass.paginate('', 1, 2, function() {
console.log('klass paginate passed');
});
// 調用保存
klass.save('name', 3, function(){
console.log('klass save 通過');
});
// 模擬數據請求
$httpBackend.flush();
});
});
```
## 控制臺信息
```
LOG: 'klass paginate passed'
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 0.069 secsLOG: Object{data: Object{code: 200}, status: 200, headers: function (name) { ... }, config: Object{method: 'POST', transformRequest: [...], transformResponse: [...], paramSerializer: function ngParamSerializer(params) { ... }, jsonpCallbackParam: 'callback', url: 'http://127.0.0.1:8080/javaee/klass.Save.json', data: Object{name: ..., teacherId: ...}, header: Object{contentType: ...}, headers: Object{Accept: ..., Content-Type: ...}}, statusText: ''}
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 0.069 secsLOG: 'klass save 通過'
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 0.069 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 11 of 14 SUCCESS (0 secs / 0.082 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 12 of 14 SUCCESS (0 secs / 0.086 secsLOG: Object{data: Object{code: 200, teachers: []}, status: 200, headers: function (name) { ... }, config: Object{method: 'GET', transformRequest: [...], transformResponse: [...], paramSerializer: function ngParamSerializer(params) { ... }, jsonpCallbackParam: 'callback', url: 'http://127.0.0.1:8080/javaee/Teacher_all.json', header: Object{contentType: ...}, headers: Object{Accept: ...}}, statusText: ''}
```
我們發現,控制臺打印了大量的數據返回信息。為了單元測試中,減少打印資源請求信息給我們帶來的干擾。我們對代碼進行如下重構,以達到凈化控制臺的目的。
1. 設置:僅在開發模式下,在控制臺打印資源請求信息。
services/server.js
```
$http(param).then(function successCallback(response) {
// 開發者模式下,打印資源請求結果
if (config.isDebug === true) {
console.log(response);
}
data = response.data;
```
2.在單元測試時,關閉開發模式
```
beforeEach(inject(function(_klass_, _$httpBackend_, _config_) {
klass = _klass_;
config = _config_; // 引用項目配置
config.isDebug = false; // 關閉開發模式
$httpBackend = _$httpBackend_;
```
此時,再進行單元測試時,我們發現控制臺干凈多了。
```
09 02 2017 11:45:08.727:INFO [launcher]: Starting browser PhantomJS
09 02 2017 11:45:09.435:INFO [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket Oxpj8Xe-i4QI1QPnAAAA with id 77693093
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 0.131 secsLOG: 'klass paginate passed'
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 0.131 secsLOG: 'klass save 通過'
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 0.131 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 11 of 14 SUCCESS (0 secs / 0.147 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 12 of 14 SUCCESS (0 secs / 0.153 secsLOG: []
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 12 of 14 SUCCESS (0 secs / 0.153 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 13 of 14 SUCCESS (0 secs / 0.158 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 14 of 14 SUCCESS (0 secs / 0.161 secsPhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 14 of 14 SUCCESS (0.028 secs / 0.161 secs)
Done, without errors.
```
## CM對接
M層開發測試完畢后,我們繼續回到C層,使其成功與M層進行對接。
```
'use strict';
/**
* @ngdoc function
* @name webAppApp.controller:KlassAddCtrl
* @description
* # KlassAddCtrl
* Controller of the webAppApp
*/
angular.module('webAppApp')
.controller('KlassAddCtrl', function($scope, config, teacher, klass) {
$scope.name = ''; // 名稱
// 教師列表
$scope.teachers = [];
// 選中的教師
$scope.teacher = 0;
$scope.isDebug = config.isDebug;
$scope.isError = false; // 是否發生錯誤
$scope.errors = {}; // 錯誤信息
$scope.message = ''; // 提示信息
// 數據提交
var submit = function() {
klass.save($scope.name, $scope.teacher, function(response){
if (!angular.equals({}, response.errors)) {
// 發生錯誤
$scope.errors = response.errors;
$scope.isError = true; // 發生錯誤
$scope.message = ''; // 清空消息
} else {
// 添加成功
$scope.message = '添加成功';
$scope.isError = false;
}
});
};
// 獲取教師列表
var getTeachers = function() {
teacher.all(function(response){
$scope.teachers = response; // 獲取到的所有教師
$scope.teacher = $scope.teachers[0].id; // 初始化選中的教師
});
};
// 初始化
var int = function () {
getTeachers();
};
int();
$scope.submit = submit;
});
```
上述代碼,為了顯示成功與錯誤的信息,我們增加了isError, errors, message 三個字段。并且重新寫了submit()方法。并根據返回信息,來定制前臺的顯示。
### 完善V層
在V層中,增加成功與錯誤的信息。
```
<div class="row">
<div class="col-md-12">
<div ng-show="message" class="alert alert-success" role="alert">{{message}}</div>
<div ng-show="isError" class="alert alert-danger" role="alert">{{errors}}</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form ng-submit="submit()">
<label>名稱:
<input type="text" ng-model="name">
</label>
<label>輔導員:
<select ng-model="teacher">
<option ng-repeat="teacher in teachers" ng-value="teacher.id">{{teacher.name}}</option>
</select>
<button type="submit">submit</button>
</form>
</div>
</div>
<div class="debug" ng-show="isDebug">
{{name}}
<br />
{{teachers}}
<br />
{{teacher}}
</div>
```
## 測試
我們進行數據模擬添加。
1. 姓名為空

2. 姓名為一位字符串

沒錯,正如你看到的,并沒有出現我們想看到的"名稱必須介于2-8之間"的提示。這是哪出了問題呢?
下面,我們一步步的排查問題。
排查問題前,我們需要非常明確的清楚數據的傳輸流。
前臺 -> 后臺 -> 前臺
具體是這樣:
V -> C -> M -> 后臺 -> M -> C -> V
我們先找到控制臺的網絡選項卡,來查看前臺傳給后臺的數據是否出現了問題。

通過觀察網絡,我們發現,數據成功的發送給了后臺。然后得到了非我們預期的結果。但我們在上一小節中,明明的使用postman對后臺進行過測試,怎么現在發送過去數據就得到了和使用postman不一樣的結果呢?
問題的根本在于:使用postman發送的數據,與使用anguarjs發送的數據雖然相同,但是`文件頭`定義卻有區別。
我們再詳細查看一下文件頭:
angularjs:

postman:

沒錯,無論是選擇`form-data`還是`x-www-form-urlencoded`,它們的`Content-Type:`都以angularjs的`application/json`發送的不一樣。
事實上,后臺的服務端在接收到數據后,也是先去判斷用戶發送的`Content-Type`,從而確定用戶發送的數據類型。
> content-type延伸閱讀: [https://segmentfault.com/a/1190000003002851](https://segmentfault.com/a/1190000003002851)
找到了問題的關鍵后,讓我們在進行數據請求時,重新對postman進行設置:

當我們進行如上設置時,最終`Content-Type`的值,便以angularjs發送請求的值完全相同了。
<hr />
其實angularjs和postman一樣,在進行數據發送時,也是可以設置`Content-Type`值的。只是在前后臺分離的設計中,我們更愿意使用`application/json`,以表示發送的數據為JSON字符串。
> 官方文檔: [https://docs.angularjs.org/api/ng/service/$http](https://docs.angularjs.org/api/ng/service/$http)

<hr />
看來問題出現了后臺的測試上,本來應該使用`application/json`,我們卻使用了其它的方式。
# 修正后臺
我們將postman進行正確的設置后,發現也出現了如前臺集成測試的錯誤。

無論,我們輸入任何的內容,都會提示我們『名稱不能為空』
這是由于:我們在接收數據時,使用struts-json插件進行了處理;但在進行數據驗證時,卻仍然使用的是struts的默認驗證機制。這導致了將發送的`Content-Type`為`application/json`時, struts-json對數據進行攔截,最終使得struts在進行驗證時,沒有驗證到任何的數據。為了解決這個問題,我們需要增加另一個攔截器,將struts-json攔截后的數據,轉發至struts的驗證器,從而達到字段的驗證的目的。
為此,我們對struts.xml進行更改:
修改前 :
```
<!-- 配置攔截器,使傳入的json數據能夠成功的通過setXXX()方法來傳值 -->
<interceptor-ref name="defaultStack" />
<interceptor-ref name="json">
<param name="enableSMD">true</param>
</interceptor-ref>
```
修改后:
```
<!-- 配置攔截器,使傳入的json數據能夠成功的通過setXXX()方法來傳值 -->
<interceptor-ref name="json">
<param name="enableSMD">true</param>
</interceptor-ref>
<!-- 增加jsonValidationWorkflowStack攔截器,用于JSON字段驗證 -->
<interceptor-ref name="jsonValidationWorkflowStack" />
```
我們刪除產生沖突的默認攔截器`defaultStack`在`json`攔截器下,增加`jsonValidationWorkflowStack`攔截器。配置好后,保存struts.xml文件,并重啟server以使struts立即生效.
此時,我們再進行請求,驗證很順利的生效了。現在,我們在進行前臺的測試,數據也被成功的返回了。

> git checkout -f step12.3.3
<hr />
記不太清是什么時候引入的log4j2這個日志記錄的工具了。由于引它的引用,我們控制臺上會生成很多條日志記錄,如果前期你并不想看到它們,可以對log4j2.xml做如下配置:
```
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<!-- 設置日志級別為error -->
<Logger name="com.opensymphony.xwork2" level="error"/>
<Logger name="org.apache.struts2" level="error"/>
<Root level="warn">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
```
- README
- 第一章:準備
- 第二章:Hello World!
- 第一節:查看工程文件
- 第二節:JDK、JRE與環境變量
- 第三節:index.jsp
- 第三章:Hello Struts
- 第一節:Web.xml
- 第二節:單入口
- 第三節:Hello Struts
- 第四節:觸發C層
- 第四章:建立數據表
- 第一節:建立實體類
- 第二節:測試一
- 第三節:測試二
- 第四節:引入Hibernate
- 第五節:配置Hibernate
- 第六節:建立連接
- 第七節:實體類映射數據表
- 第八節:完善數據表
- 第五章:教師管理
- 第一節:增加數據--add
- 第二節:增加數據--save
- 1 獲取傳入數據數據
- 2 數據寫入測試
- 3 對接C層
- 第三節:數據列表
- 1 獲取數據
- 2 重構代碼
- 3 C層對接--初始化
- 4 C層添加數據
- 5 V層顯示數據
- 6 獲取數據庫中數據
- 7 顯示性別
- 8 分頁
- 9 條件查詢
- 第四節:修改數據
- 1 edit
- 2 update
- 第五節:刪除數據
- 第六節:總結
- 第六章:重構C層
- 第一節:繼承ActionSupport類
- 第二節:數據驗證
- 第七章:前臺分離(前臺)
- 第一節:環境搭建
- 第二節:運行環境
- 第三節:共享開發環境
- 第四節:生產環境
- 第八章:前臺開發(前臺)
- 第一節:本地化
- 第二節:教師列表
- 1 引入M層
- 2 模擬后臺返回數據
- 3 C與M對接
- 4 C與V對接
- 第九章:前后臺對接(前后臺)
- 第一節:后臺輸出json(后臺)
- 第二節:對接前臺(全棧)
- 第二節:對接API(前臺)
- 第二節:跨域請求(后臺)
- 第三節:重構代碼(前臺)
- 第十章:重構后臺M層
- 第一節:數據訪問DAO層
- 第二節:項目整體重構
- 第十一章:用戶登陸(前后臺)
- 第一節:制定規范
- 第二節:定制測試用例
- 第三節:后臺輸入測試代碼(后臺)
- 第四節:postman(后臺)
- 第五節:新建用戶登陸模塊(前臺)
- 第六節:代碼重構(前臺)
- 第十二章:班級管理(前后臺)
- 第一節:班級列表
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第二節:Add
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第三節:Save
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第四節:Edit
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第五節:Update
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第六節:Delete
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第七節:小結
- 第十三章:班級管理(API)
- 第一節:ER圖
- 第二節:create
- 1 實體層
- 2 dao層
- 3 service(server)層
- 4 action層
- 第三節:ManyToOne
- 第四節:Read
- 1 service(server)層
- 2 action層
- 第五節:update
- 1 service(server)層
- 2 action層
- 第六節:update
- 第十四章:重構服務層