有了开发规范及后台的数据支持,我们前台的对接就更加简单了。
上一节中,我们已经新建了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. 姓名为空
![https://box.kancloud.cn/6425424320f6fb631a336d77a21d100c_1000x294.png](https://box.kancloud.cn/6425424320f6fb631a336d77a21d100c_1000x294.png)
2. 姓名为一位字符串
![https://box.kancloud.cn/ffb9e4f7d5027029209543cb848c5d4e_994x282.png](https://box.kancloud.cn/ffb9e4f7d5027029209543cb848c5d4e_994x282.png)
没错,正如你看到的,并没有出现我们想看到的"名称必须介于2-8之间"的提示。这是哪出了问题呢?
下面,我们一步步的排查问题。
排查问题前,我们需要非常明确的清楚数据的传输流。
前台 -> 后台 -> 前台
具体是这样:
V -> C -> M -> 后台 -> M -> C -> V
我们先找到控制台的网络选项卡,来查看前台传给后台的数据是否出现了问题。
![https://box.kancloud.cn/933bcc61db39debc61d1c431fed58c14_952x231.gif](https://box.kancloud.cn/933bcc61db39debc61d1c431fed58c14_952x231.gif)
通过观察网络,我们发现,数据成功的发送给了后台。然后得到了非我们预期的结果。但我们在上一小节中,明明的使用postman对后台进行过测试,怎么现在发送过去数据就得到了和使用postman不一样的结果呢?
问题的根本在于:使用postman发送的数据,与使用anguarjs发送的数据虽然相同,但是`文件头`定义却有区别。
我们再详细查看一下文件头:
angularjs:
![https://box.kancloud.cn/24a8fdcc8a83d518dd28bbd4df8a73dd_822x348.png](https://box.kancloud.cn/24a8fdcc8a83d518dd28bbd4df8a73dd_822x348.png)
postman:
![https://box.kancloud.cn/ec10b6ca8f8dbb2d9da0a044bfc5a587_969x232.gif](https://box.kancloud.cn/ec10b6ca8f8dbb2d9da0a044bfc5a587_969x232.gif)
没错,无论是选择`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进行设置:
![https://box.kancloud.cn/2bb78ed023ab2e9260b49b0803e260c9_969x232.gif](https://box.kancloud.cn/2bb78ed023ab2e9260b49b0803e260c9_969x232.gif)
当我们进行如上设置时,最终`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)
![https://box.kancloud.cn/1e6f8b40126003253093c216b09c1049_1362x788.png](https://box.kancloud.cn/1e6f8b40126003253093c216b09c1049_1362x788.png)
<hr />
看来问题出现了后台的测试上,本来应该使用`application/json`,我们却使用了其它的方式。
# 修正后台
我们将postman进行正确的设置后,发现也出现了如前台集成测试的错误。
![https://box.kancloud.cn/63a6ecc52188fd5f7e60064a177dd48e_886x517.gif](https://box.kancloud.cn/63a6ecc52188fd5f7e60064a177dd48e_886x517.gif)
无论,我们输入任何的内容,都会提示我们『名称不能为空』
这是由于:我们在接收数据时,使用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立即生效.
此时,我们再进行请求,验证很顺利的生效了。现在,我们在进行前台的测试,数据也被成功的返回了。
![https://box.kancloud.cn/63aa13c53c5e8ffc8f8c2bc6a06fe50b_723x139.gif](https://box.kancloud.cn/63aa13c53c5e8ffc8f8c2bc6a06fe50b_723x139.gif)
> 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
- 第十四章:重构服务层