zoukankan      html  css  js  c++  java
  • trackr: An AngularJS app with a Java 8 backend – Part II

    该系列文章来自techdev

    The Frontend

    在本系列的第一部分我们已经描述RESTful端建立在Java 8和Spring。这一部分将介绍我们的第一个用 AngularJS建造的客户端应用程序。

    我们使用trackr跟踪我们的工作时间,旅行费用和假期请求。因此我们需要两个基本的管理界面为所有实体以及高级输入表单。我们也想扩展中定义的安全端到我们的前端——如果REST服务禁止用户查看或编辑一个实体这些行为不应该是可见的。

    测试和自动化的很大一部分我们的Java后端前端的我们想要这些功能。除了测试框架和测试运行器这意味着某种形式的构建和依赖关系管理。这应该是我们Gradle构建的一部分。

    Bower

    我将使用Bower来完成依赖关系管理。虽然使用相当简单,但我们不得不吐槽Bower,因为它失败构建的问题。一次一个git命令就失败了,还有一次我无法删除一些缓存文件夹。我真的希望他们改善一下在未来。每次我更改或添加一个依赖Gradle构建都容易失败。

    另一个非常具体的问题是jQuery的依赖问题。jQuery仓库里jquery和jQuery两个,Bootstrap 依赖jquery,但是Bower 随机下载jquery和jQuery,文件夹结构的不一致导致构建失败不能运行,这个问题:https://github.com/bower/bower/issues/1118。现在我不想抱怨太多,也许是合理的,也许不是,但它打破了我的建立,一个依赖关系管理系统,是用于生产目的不能打破构建。

    还有一个,不是Bower的问题,但使用它的时候出现了。增加错误的包。当一个包的build 目录是空的只有项目源码在里面。我们最终使用主分支和不是一个发布版本使用这种依赖性。

    用Maven的依赖管理我从来没有这些问题。你添加一个依赖项,Maven下载它,它的工作原理。在我看来Bower需要更多的发展。

    Grunt

    我们使用Grunt来完成3个任务。执行JSHint,构建生产文件的源代码(比如最小化js文件)和执行测试。

    Grunt工作真的很好。唯一的问题可能是任务的配置文档时不够的。但大多数任务都有一个良好的文档和/或一个示例配置文件,描述了所有的选项。

    Running Bower and Grunt from within Gradle

    在前一章我们用gradle配置过这个例子build.gradle

    task gruntTest(type: Exec) {
        workingDir 'src/main/webapp/WEB-INF/app'
        executable = 'grunt'
        args = ['test']
    }
    

    我们有一些这样的任务,只有执行命令,如grunt testnpm install等等。我真的很喜欢它,其他开发人员checkout项目(当然所有工具已经安装好了)只需要运行./gradlew build就有了一个WAR文件(node工具已经安装)。  

    The architecture

    我们希望前端非常模块化,这样我们就可以与其他应用程序扩展它。这意味着我们实际上想要构建一个平台trackr仅仅是第一个应用程序。AngularJS已经附带支持模块。此外我们使用 RequireJS 来做Javascript依赖。这允许模块被集成在平台很少变化。因此我们的目录结构如下:

    folder_structure

    下面是一些示例代码展示应用程序加载:

    /* app.js */
    define(['angular', 'modules/shared/shared', 'modules/module1/module1', 'modules/module2/module2', function(angular) {
        var app = angular.module('app', 'shared', 'module1', 'module2');
        angular.element(document).ready(function() {
            angular.bootstrap(document, ['app']); //If there's setup needed before the app runs.
        });
    });
     
    /* modules/module1/module1.js */
    define(['angular'], function(angular) {
       var module1 = angular.module('module1', []);
       //add controllers loaded by require, define routes, states etc.
       return module1;
    });
    

    不是标准AngularJS路由通过$routeProvider我们使用https://github.com/angular-ui/ui-router 的$stateProvider。它允许嵌套状态和视图的应用程序真正有利于模块化的方法。每个模块可以定义自己的状态有自己的URL前缀,而不需要到处重写。  

    这是管理模块的状态配置摘录:

    $stateProvider
           .state('trackr.administration', {
               url: '/administration',
               views: {
                   'center@': {
                       templateUrl: 'src/modules/trackr/administration/administration.tpl.html'
                   }
               },
               needsAuthority: 'ROLE_SUPERVISOR'
           })
           .state('trackr.administration.projects', {
               url: '/projects',
               views: {
                   'center@': {
                       templateUrl: 'src/modules/trackr/administration/projects/list.tpl.html',
                       controller: 'trackr.administration.controllers.projects.list'
                   }
               },
               needsAuthority: 'ROLE_SUPERVISOR'
           })
           .state('trackr.administration.projects.edit', {
               url: '/{id:[\w\.]+}',
               views: {
                   'project': {
                       templateUrl: 'src/modules/trackr/administration/projects/edit.tpl.html',
                       controller: 'trackr.administration.controllers.projects.edit'
                   }
               },
               needsAuthority: 'ROLE_SUPERVISOR'
           })
    

    它定义了3个内部状态,最后一个trackr.administration.projects.edit将作为一个父状态的template的一个view,在url (/trackr)/administration/projects/projectId下被访问。

    projects

    这个就是演示效果,点进project的时候,才会有编辑。您还可以看到该模块选择左边。早期阶段的布局是当我主要是发展的特性。

    RequireJS方法可以把大量的文本的Javascript文件,特别是如果目录结构越来越复杂。起初,我发现这很讨厌,但我看了看一个典型的Java类。

    你可能不认识他们了,因为IDE隐藏了他们,更有可能的是,你不用写他们,但他们在那,就是:import语句。唯一的差别就是RequireJS的import你的IDE没法帮你做。

    我们的构建将append和最小化所有Javascript文件和加速加载在我们的生产环境。

    Restangular

    Restangular是一个angular module,它隐藏了$http和rest Service的交互。url的字符串操作可以使用流利的API,链接在一起的方法。下面是一个例子从我们employee-editing Controller。

    var employeeBase = Restangular.one('employees', $stateParams.id);
     
    employeeBase.get().then(function(employee) {
        $scope.employee = employee;
        employee.one('credentials').get().then(function(credentials) { /* do something with credentials */ });
    }
     
    $scope.deleteEmployee = function() {
        employeeBase.remove().then(function() { /* redirect to some other page */ });
    }
    

    可以看到它的工作$http的promises一样。真的很直观和Spring-Data-Rest工作得很好。唯一的映射列表结果必须被配置在Restangular Spring-Data-Rest相匹配的方法。  

    Internationalization

    我们使用angular-translate 来实现国际化,很直接,在大多数用例使用翻译过滤器。翻译可以异步服务或可以硬编码。

    <thead>
        <tr>
            <th translate="VACATION_REQUEST.START_DATE"></th>
            <th translate="VACATION_REQUEST.END_DATE"></th>
            <th translate="VACATION_REQUEST.NUMBER_OF_DAYS"></th>
            <th translate="VACATION_REQUEST.STATUE"></th>
            <th translate="ACTIONS.ACTIONS"></th>
        </tr>
    </thead>
    

    编辑:帕斯卡普雷在评论中指出最好尽量使用指令而不是过滤器。过滤器可以有负面影响性能。更改代码示例使用指令。

    Security

    后端实现一个非常严格的安全方法。trackr每个用户的角色分配和REST服务处理相关的权限。现在,在前端用户访问的所有代码。我想我们可以为员工和管理员提供不同的应用程序,但似乎太多的开销。毕竟前端不包含密码,只有必须的数据,最后将保护后端。

    尽管如此,我们不希望用户看到的链接页面无法访问或能够编辑一个实体他们可以看到但例如PUT请求将被拒绝。所以我在一些地方添加额外的安全检查。

    • 状态切换. A state can require a role to be accessed. Currently, if the user does not have the required role nothing will happen, but an error page is completely possible. (You can see this in the code for the $stateProviderabove)
    • 用 has-authority 指令来隐藏元素. This can be used e.g. to hide the link to administration in the top menu for employees.
    • 我们使用click-to-edit的方法替换表单. The directive for that takes the role of the user into account, too.

    如果正确使用这应该足以显示只允许trackr的内容给用户。如果用户想恶意行为他们肯定能够切换到一些state不允许,但最终剩下的后端将阻止他们访问数据。

    这是一个has-authority的指令的例子

    <ul class="nav navbar-nav">
        <li ui-sref-active="active"><a href ui-sref="trackr.employee">{{ 'PAGES.EMPLOYEE.TITLE' | translate }}</a></li>
        <li ui-sref-active="active" has-authority="ROLE_SUPERVISOR"><a href ui-sref="trackr.supervisor">{{ 'PAGES.SUPERVISOR.TITLE' | translate }}</a></li>
        <li ui-sref-active="active" has-authority="ROLE_SUPERVISOR"><a href ui-sref="trackr.administration">{{ 'PAGES.ADMINISTRATION.TITLE' | translate }}</a></li>
    </ul>
    

    Testing

    我们的后端有一个不错的测试覆盖率。虽然我已经写了一些较小的AngularJS应用我从未真正建立测试对他们来说,这是相当新的给我。我决定使用Karma 作为测试运行器,Jasmine作为一个测试框架。设置Karma与RequireJS管理文件是一个任务,需要一些时间和研究。

    能够编写和执行测试后,下一个问题是:如何测试一个AngularJS应用程序,测试什么和如何编写代码让代码是可测试的。

    让我们从第一个问题开始。AngularJS提供angular-mocks模拟一个真正的AngularJS环境测试。它允许您启动应用程序,使用测试和模拟中注入$http调用。

    define(['angular-mocks', 'app'], function() {
        describe('some test', function() {
            beforeEach(module('app'));
            beforeEach(inject(function($httpBackend) {
                $httpBackend.whenGET(...);
            }));
            it('should do something', inject(function($httpBackend) { ... }));
        });
    });
    

    这只是一个例子,没有真正的考验。很快,显然我必须做某些任务在每个测试中,像启动应用程序。因为我发现没有办法编写一些测试基类我想到不同的方法。有RequireJS证明是非常有用的。我只是写了一个文件,返回一个函数将执行所有基本的测试设置。在实际测试我不得不需要文件,执行功能和将设立我的测试。我做这个启动应用程序和模拟$httpBackend和一些数据。  

    接下来是:我们测什么?我还不是完全清楚。我开始与一些非常基本的测试,检查一些$scope变量的定义。我的想法是,禁止有人删除$scope仍可能需要。然后我开始测试检查如果HTTP请求时执行;angular-mocks帮助这里。我没有多少业务代码所以主要是集成测试。

    接着是如何编写测试代码的问题。很容易访问$scope在测试,所以这可能是一个想法把$scope和测试它的一切。但是这个破坏了封装。有时你在一个控制器有一个helper函数,不该在$scope。所以我用这个模式:

    /* controller.js */
    angular.controller(['$scope', function($scope) {
        var controller = this;
        controller.foo = function(bar) {
            //do stuff 
        };
     
        $scope.baz = function() {
            controller.foo('baz');
        };
    }]);
     
    /* controllerSpec.js */
    describe('controller', function() {
        var controller;
        beforeEach(function() { /* set up controller and app */ });
        it('should foo', function() {
            var foo = controller.foo('bar');
            expect(foo).toBe('bar');
        });
    });
    

    我想时间会告诉我们如何提高我们的实际测试,但是一个好的基本设置。

    Code coverage

    IntelliJ IDEA有很大的代码覆盖率工具,我可以用Java运行测试。这对于Javascript也是可能的。我用Istanbul通过雅虎karma-coverage 任务。它不需要任何特殊的设置(只是激活和配置的输出目录报告)。我发现它很晚,这是一个很好的令人惊异的事物去发现我的覆盖率有多糟糕。经过一天的编写更多的测试我从这里

    JS-Tests-before

    到这里

    JS-Tests-after

    所以我仍然需要一个好方法来测试指令,但它确实暂时看起来很不错。

    Istanbul甚至每个Javascript文件创建一个页面来指明多少行,分支和方法被覆盖。

    coverage

    Conclusion

    RequireJS和AngularJS模块的组合允许一个真正模块化的程序是很好。与我们的Spring后端Restangular集成非常好。Karma,Jasmine和angular-mocks(和一些RequireJS)帮助建立一个测试环境中有许多方便的助手,让您专注于实际的测试。      

    我没有这么多像Bower与我所描述的经历。一个工具,break我的build而且没有明显的原因是很糟糕的。

    请在评论中与我们分享你的经验!

    我们刚刚开源了trackr所以你可以随便蹂躏它,你懂的。阅读完整的声明:http://blog.techdev.de/trackr-is-now-open-source/

    next 

      

      

      

  • 相关阅读:
    变量定义方法
    动态编译
    函数
    过程
    触发器
    高级聚合函数rollup(),cube(),grouping sets()
    高级函数-decode
    高级函数-sign
    js 保留两位小数 javascript
    js 发红包
  • 原文地址:https://www.cnblogs.com/mignet/p/trackr-an-angularjs-app-with-a-java-8-backend-part-ii.html
Copyright © 2011-2022 走看看