多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
有了开发规范及后台的数据支持,我们前台的对接就更加简单了。 上一节中,我们已经新建了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> ```