JHipster中文文档(一)

介绍

技术栈

客户端技术栈

单页面应用:

  • Angular4 or AngularJS v1.x
  • Bootstrap
  • HTML5
  • 国际化支持
  • Sass
  • Spring Websocket

良好的开发流程:

  • 通过Yarn或Bower易于安装JavaScript类库
  • 通过webpack或gulp构建、优化、实时重载
  • 使用Karma headless Chrome 或者 Protractor进行测试
  • 支持Thtmeleaf模板引擎

服务端技术栈

一个完整的Spring应用:

  • Spring-Boot:易于配置
  • Maven或Gradle:构建、测试、运行应用
  • Spring Security
  • Spring MVC REST + Jackson
  • Spring Data JPA + Bean Validation
  • Liquibase: 数据库管理(数据库变更等记录)
  • Elasticsearch支持
  • MongoDB支持
  • Cassandra支持
  • Kafka 支持
  • Spring WebSocket支持

微服务技术栈

微服务是可选的并且完全支持服务端技术栈,除此之外还支持:

  • 使用Netflix Zuul或者Traefik进行HTTP路由
  • 使用Netflix Eureka或者Consul进行服务发现

生产环境支持

  • 监控:使用Metrics和ELK
  • 缓存:ehcache本地缓存、hazelcast等分布式缓存
  • 静态资源优化(gzip、http cache header)
  • 日志管理
  • 使用数据库连接池HikariCP提升性能
  • 可建立标准的war或者可执行的jar
  • Docker以及Docker Compose完全支持
  • 支持主流云服务提供商,如k8s docker aws 等

环境设置

安装JHipster

安装方式

Jhipster提供了6中方式以便使用JHipster,主要有:

  • 在线安装
  • 使用Yarn进行本地安装
  • 使用NPM进行本地安装
  • 使用包管理器安装,仅支持Max OS X和Windows
  • 基于ubuntu development box安装
  • 基于docker的轻量级容器安装

官方推荐选择第二种安装方式,即:使用Yarn进行本地安装。

使用Yarn进行本地安装

当使用Angular时的快速配置
  1. 安装JDK8
  2. 安装Node.js
  3. 安装Yarn(配置淘宝镜像:yarn config set registry https://registry.npm.taobao.org
  4. 如果要使用JHipster商城,需要安装Yeoman:yarn global add yo
  5. 安装JHipster:yarn global add generator-jhipster
当使用AngularJS 1.x时的快速配置
  1. 安装JDK8
  2. 安装Node.js
  3. 安装Yarn
  4. 安装Bower: yarn global add bower
  5. 安装gulp: yarn global add gulp-cli
  6. 如果需要使用JHipster商城,安装Yeoman:yarn global add yo
  7. 安装JHipster:yarn global add generator-jhipster
其他组件安装
  1. 安装Java构建工具:Maven或者Gradle,如果使用Maven需要配置环境变量 M2_HOME=/maven-home
  2. 安装git进行版本管理
其他问题

如果使用Yarn有问题,请检查$HOME/.config/yarn/global/node_modules/.bin 有没有此路径。 在Mac或者Linux下,需要
export PATH="$PATH:`yarn global bin`:$HOME/.config/yarn/global/node_modules/.bin"

jhipster使用Yeoman进行代码生成,需要查看更多信息、提示或者帮助,请移步 the Yeoman “getting starting” guide

Jhipster核心任务

创建一个应用

快速入门

开始之前为你的应用创建一个空文件夹:mkdir myapplication

进入到此文件夹中 cd myapplication

生成你的应用,执行jhipster

回答你看到的问题,这些问题用于定制你的应用,稍后详细解释每个问题。

当一个应用生成好之后,你可以使用./mvnw或者./mvnw.bat来启动,(也可以使用yarn && mvn spring-boot:run来启动)

应用将在http://localhost:8080可用

生成应用时的问题

你选择的问题答案不同可能会影响到接下来的问题显示,如:你不想使用hibernate的缓存那么就不能选择sql数据库。

Q1. Which type of application would you like to create?
  • Monolithic application: 单体应用
  • Microservice application: 微服务架构的service
  • Microservice gateway: 微服务网关,为微服务请求进行路由和安全
  • JHipster UAA server: 基于OAuth2的安全认证微服务,后文详细解释
Q2. What is the base name of your application?

你期望的应用的名称

Q3. What is your default Java package name?

应用使用的默认包名,使用Yeoman的时候此值会被存储,当下次使用的时候此值会成为默认值,可覆写此值

Q4. Do you want to use the JHipster Registry to configure, monitor and scale your application?

JHipster Registry是一个开源的工具,用于管理你正在运行的应用(微服务注册中心和统一配置中心),只有在微服务架构时才会使用

Q5. Which type of authentication would you like to use?

所有可能的答案:

  • JWT authentication: 使用JSON Web Token
  • HTTP Session Authentication:经典的基于session认证的机制
  • OAuth 2.0 / OIDC Authentication: 使用OpenID连接服务,类似于Keycloak或者Okta
  • Authentication with JHipster UAA server: 此种方式必须提前生成JHipster UAA Server (Q1的选项),它是基于OAuth2的验证服务
Q6. Which type of database would you like to use?

你可以选择的选项:

  • No database,仅使用微服务架构时可用
  • An SQL database: 使用关系型数据库,将会采用Spring data jpa
  • MongoDB
  • Cassandra
  • Couchbase
Q7. Which production database would you like to use?

选择你线上环境使用的数据库,此选项决定src/main/resources/config/application-prod.yml的配置

Q8. Which development database would you like to use?

此选项决定你src/main/resources/config/application-dev.ymlprofile的数据库配置项,你可以选择:

  • H2,running in-memory,数据存储在内存中,服务停掉数据消失
  • H2,with its data stored on disk,数据存储在硬盘,当前只是BETA测试且不能在Windows机器上工作
  • 可以和Q7的选项一致
Q9. Do you want to use the Spring cache abstraction?

由于Spring对于Cache的允许用户使用不同的cache实现,你可以使用chcache(本地缓存),Hazelcast(分布式缓存)或者Infinispan(另一种分布式缓存),此选项可以提升你的应用的性能

Q10. Do you want to use Hibernate 2nd level cache?

此选项仅当你选择SQL数据库并且在Q9选择了一个缓存实现。Hibernate使用二级缓存可以更好的提升它的性能

Q11. Would you like to use Maven or Gradle?

构建此项目时将要使用的工具,Maven或者Gradle

Q12. Which other technologies would you like to use?

多选,你可以为你的应用添加多种技术,如:

  • Social login,社交登录功能
  • API first development using swagger-codegen:通过成swagger-codegen而使你的应用采用API优先的开发模式
  • Search engine using ElasticSearch: 对于ES的支持(Spring Data Elasticsearch)
  • Clustered HTTP sessions using Hazelcast,默认情况下,JHipster只是用Http Session来存储Spring Security的身份验证和授权信息。如果你在集群中运行,使用HTTP Session将会导致一些数据一致的问题,如果你想在集群中复制session,请选择此项
Q13. WebSockets using Spring Websocket

启用Websockets支持,将使用Spring WebSocket,JHipster提供了简单的例子展示如和高效的使用

Q14. Asynchronous messages using Apache Kafka

是否使用Kafka来发布和订阅消息

Q15. Which Framework would you like to use for the client?

选择使用那种客户端技术:

  • Angular version 4+
  • AngularJS version 1.x
Q16. Would you like to use the LibSass stylesheet preprocessor for your CSS?

Node-sass对于设计CSS是一个优秀的解决方案,便于高效使用,你需要运行一个Gulp服务,jhipster会自动配置

Q17. Would you like to enable internationalization support?

JHipster对于国际支持非常友好,你可以在客户端和服务端使用。但一般对于国际化要求不多的场景,可以不选择。

Q18. Which testing frameworks would you like to use?

默认Jhipster提供了Java单元/集成测试(spring`s Junit)和JavaScript单元测试(Karma.js),你也可以选择:

  • Performance tests using Gatling,性能测试 Gatling
  • Behaviour tests using Cucumber,行为测试 Cucumber
  • Angular integration tests with Protractor, angular集成测试Protractor
Q19. Would you like to install other generators from the JHipster Marketplace?

是否需要去JHipster商城安装第三方插件模块

命令行选项

你可以使用JHipster的一些可选命令行选项,使用jhipster app --help查看详细说明。

  • --skip-cache 忽略之前记住的答案
  • --skip-git 不自动生成git项目
  • --skip-install不自动安装依赖
  • --skip-client 跳过客户端的生成,只生成后台服务,也可以使用jhipster server代替
  • --skip-client 跳过生成服务端代码,只生成客户端代码jhipster clinet代替
  • --skip-user-management跳过生成用户管理代码,包含前端和后端
  • --i18n 当跳过客户端代码生成时禁用或启用i18n,否则没应用
  • --auth 当跳过服务端代码生成时指定认证类型
  • --db 当跳过服务端代码生成时指定数据库
  • --with-entities 如果已经生成了实体则重新生成一次
  • --skip-checks 跳过需要工具的检查
  • --jhi-prefix 添加服务、组件或路由的前缀,默认jhi
  • --npm 使用npm代替Yarn
  • --eperimental 启用实验特性,请注意这些特性可能不稳定
  • --force 强制覆盖以存在的文件

创建一个实体

介绍

当你刚创建完应用时,你可能想要创建一些实体类。例如你也许想要创建一个Book和Author实体类。对于每个实体,你需要:

  • 一个数据库表
  • 一个Liquibase变更set
  • 一个JPA实体
  • 一个Spring data jpa repository
  • 一个带有基础增删改查的rest controller
  • 一个angular路由、组件以及服务
  • 一个HTML视图
  • 集成测试
  • 性能测试

如果你有多个实体,你可能还想创建他们之间的关系,比如这个例子,你需要:

  • 一个数据库外键
  • 指定Javascript和HTML代码管理此关系

实体sub-generator(子生成器)将会为每个实体创建所有需要的文件并且提供增删改查后端,sub-generator通过jhipster entity <entityName> --[options]运行

它支持的选项有:

  • --table-name <table_name> 通过JHipster会生成一个表,他的名称基于你的实体名称,如果你想要修改为不同的名称使用此选项
  • --angular-suffix <suffix> 如果你想所有的都带有自定义的后缀,可使用此选项
  • --regenerate 将会不做任何询问生成已存在的实体
  • --skip-server 不会服务端代码
  • --skip-client不会生成客户端代码
  • --db 跳过服务端代码生成时指定数据库

JHipster UML AND JDL Studio

此章节描述如何使用JHipster的标准命令行接口创建实体类。如果你想创建多个实体,你也许要使用一些图形工具。

如果你使用JDL Studio:

  • 你已使用import-jdl子生成器从JDL 文件生成实体,jhipster import-jdl your-jdl-file.jh

    • 当导入的时候如果你不想重新生成实体,你可以使用--json-only标志,跳过实体生成仅创建json文件于.jhipster文件夹中
    • 默认使用import-jdl仅会重新生成被改变的实体,如果你想所有的实体被重新生成,则通过--force生成
  • 如果你使用JhipsterUML代替import-jdl子生成器,你需要安装npm install -g jhipster-uml然后运行jhipster-uml youFileName.jh

实体字段

对于每个实体,你可以添加很多字段。你需要输入字段名称和类型,JHipster将会生成你所需的代码和配置。这些字段名称不能包含关键字

字段类型

JHipster支持很多字段类型,这些支持依赖于你的后台数据库,所有我们使用Java类型去描述他们:一个Java String在Oracle和Cassandra中不同,这是Jhipster生成健壮和正确的数据库代码的一种方式。

  • String 它的默认长度取决于后端,如果你使用JPA默认长度255,你可以通过检验规则来修改它
  • Integer
  • Long
  • Float
  • Double
  • BigDecimal
  • LocalDate 用于在Java中正确的管理日期
  • Instant 用于时间戳
  • ZoneDateTime 给定时区的本地时间
  • Enumeration 枚举对象,当选择此类型时,子生成器将会询问你对应的枚举值并创建enum类
  • Blob 用于存储二进制数据,当被选中时,子生成器会询问你存储的数据类型,是图片对象还是CLOB

校验

可以对每个字段设置校验,不同的字段具有不同校验选项

校验将会被自动的生成在:

  • Html views,使用the AngularJS validation mechanism
  • Java domain Obj, 使用 Bean Validation

当对象被以下面的方式使用时,会自动的对领域对象进行校验:

  • Sptring MVC/REST controllers(使用@Valid注解时)
  • Hibernate/JPA(实体在保存之前自动校验)

校验信息将尽可能清晰的使用数据库列的元数据描述:

  • 必填字段将被标记为non-nullable
  • 唯一字段将创建唯一约束
  • 有最大长度的字段与数据库中的长度相同

校验功能会有一些限制:

  • 不支持AnjularJS和Bean Validation的所有校验选项,只支持具有共同API的选项
  • Java和Javascript的正则表达式不一定能同时工作,当你配置其中一个时,你需要略微的修正另一个
  • JHipster生成的单元测试用例并不知道你的校验规则,这可能导致生成的单元测试用例不能通过校验规则,这时候你需要更新单元测试用例的值以使他们符合规则

实体关系

实体关系仅支持关系型数据库,稍后有章节详细说明

为你的业务逻辑生成单独的service class

相对于rest controller,单独的service类中允许有多种逻辑组合。业务层允许使用DTO。

相同逻辑使用spring service sub-generator,后续将由章节详细说明

Data Thransfer Objects(DTOs)

默认JHipster实体不使用DTOs,但是如果使用业务层时,DTOs是一中可用的数据传输方式,具体用法后续章节详细说明

Filtering(过滤或者查询)

可选,实体存储在数据库中可以使用JPA过滤。

分页

当你的应用使用Cassandra创建的时候,是不能使用分页的。当然这个特性将会被添加。

JHipster在服务端和客户端提供了依照标准规则的实现

当实体被生成时,JHipster提供了4中分页选项:

  • 不分页
  • 简单分页,基于bootstrap pager
  • 完成分页系统,基于Bootstrap pagination component
  • 滚动分页,基于infinite scroll directive

更新已存在的实体

实体配置保存在~/.jhipster/*.json文件中,如果你再次使用已存在的实体类名运行子生成器,你就可以更新或者重新生成实体

当你对一个已存在的实体运行子生成器时,将会询问’Do you want to update the entity? This will replace the existing files for this entity, all your custom code will be overwritten’(你想更新实体吗?这将会替换已存在的实体文件和实体相关的所有的代码将会被覆盖),有两种答案:

  • Yes, re generate the entity,这将重新生成你的实体
  • Yes, add more fields and relationships询问你以添加更多字段和关系
  • Yes, remove fields and relationships浙江询问你是否移除已存在的字段和关系
  • No, exit

你更新实体的理由可能如下:

  • 你想对已存在的实体添加或者移除字段及关系
  • 你想重置你的实体代码
  • 你更新了JHipster,想使用新的模板生成你的实体
  • 你修改了.json文件,所以你需要有一个新版本的实体
  • 你粘贴复制了.json文件,然后想生成此实体

快速生成多个实体:

  • linux & mac :
for f in `ls .jhipster`; do jhipster entity ${f%.*} --force ; done
  • Windows:
for %f in (.jhipster/*) do jhipster entity %~nf --force

指导

这是一个简短的引导以创建两个具有one-to-many的实体(Author 和 Book)

生成Author实体

一个Author包含多个Book的关系,所以我们需要首先创建Author,在数据库级别Jhipster将会创建一个外键在Book表上,这外键指向Author表

jhipster entity author

输入关于这个实体字段的问题的答案:

  • a “name” of type “String”
  • a “birthDate” of type “LocalDate”

输入关于这个实体关系的问题答案:

  • one-to-many with the “book” entity(当前Book还不存在)
生成Book实体

jhipster entity book

回答接下来关于book字段的的问题 :

  • title类型为String
  • description类型为String
  • publicationDate类型为LocalDate
  • price类型为BigDecimal

回答关于关系的问题:

  • 拥有与author的many-to-one的关系
  • 关系中使用name字段显示author
检查生成的代码

运行生成的测试用例mvn test,此测试用例测试Author和Book实体

启动应用,登录后点击- entities > Author

修改生成的代码

生成的文件包含了基础的CURD操作。如果你的需求比较简单的话,都不用进行任何修改。

如果你想修改生成的代码或者数据库表,你应该阅读后续的“开发指导”章节

如果你有一些组合的业务行为,你也许需要添加Spring的@Service类,请使用 service生成器

创建Controller

介绍

controller生成器相对实体生成器而言,简单的多。此生成器用于生成Spring MVC REST controller,如:jhipster spring-controller Foo,简单的回答完问题后,即可生成

可以使用swagger记录这个rest接口吗?

在dev模式下,点击aministration > API 即可看到。

可以为此接口添加安全控制吗?

只需要添加@Secured注解在你的类或者方法上,就可以限定用户的权限。

可以监控这个接口吗?

值需要添加@Timed注解就可以监控

创建Spring service

介绍

此生成器相对于实体类生成器简单的多。

此生成器生成你应用程序的业务逻辑的spring service对象,如:

jhipster spring-service Bar

这将会生成BarService.生成的代码行数很少,但通常会有一些问题。

为什么service class不通过实体类生成器生成?

我们这里有两个主要的架构原则:

  • 我们不想推广无用的服务,如果你仅需要一个基础的CURD操作,那么是不需要service对象的,这也是为什么Jhipster不生成他们的原因
  • 我们认为service bean 应该比repository的粒度更粗,service bean使用多个repository来实现他们的业务价值,所以Service没有和实体类一起生成

我们Service应该使用interface吗?

简单的说:不需要!

如果需要详细的解释,那么:

一个主要的兴趣点是使用Spring AOP,这个技术允许Spring添加一些行为在你的Bean之上:对于实力而言,提供了诸如事务控制、安全的工作等。

为了增加这写行为,spring需要为你累创建一个代理,一般由两种方式创建代理:

  • 如果你使用的接口,spring将会使用Java提供的标准机制来创建动态代理
  • 如果你没有使用接口,Spring将会使用CGLIB来生成一个新的类:这不是Java的标准机制,但是它比标准机制工作的要好

很多人的争论点之一是使用接口易于编写测试用例,但是我们详细我们不应该为了测试用例儿修改我们的产品代码,并且还有一些新的mock框架(如EasyMock)允许我们不用接口创建一些有些的单元测试用例。

因此,最终我们发现接口对于我们的Service Bean通常是无用的,所以我们不推荐使用(但我们任务提供了生成接口的选项)

为什么我们应该使用事务来延迟加载JPA的关系?

默认JPA使用延迟加载一对多和多对多的实体关系,如果你使用默认配置,你将很可能看到LazyInitializationException:这意味着你尝试在事务外使用未初始化的关系。

由于生成的service class默认使用了@ Transactional注解,所有的方式都具有事务性,这意味着你可以再方法内获取所需的延迟加载的关系了,不用担心出现LazyInitializationException

提示:如果你不修改数据时在方法上使用@Transactional(readOnly = true)。这是一个比较好的性能优化方式(hibernate不需要刷新一级缓存,因为我们没有修改任何数据)以及使用一些JDBC驱动时质量的提升(Oracle不允许发送INSERT/UPDATE/DELETE命令)

可以给Service Bean添加权限控制吗?

仅需要在类或者方法上添加@Secured注解即可限制用户的访问权限。

可以监控Service Bean吗?

仅需在方法上添加注解@Timed即可。

创建DTOs

警告这是一个比较新的特性,目前处于测试阶段,使用它有一定的风险,非常欢迎反馈。

介绍

Jhipster默认在REST端点使用领域模型对象(典型的JPA实体)。这样做有很多好处,如易于使用、便于理解和扩展。

但是在一些复杂的场景,你也许想在REST的端点暴露出数据传输对象(DTOs)。这些对象将在domain对象上添加一些额外的东西,并专门针对REST进行优化:他们最大的好处就是可以将多个domain对象聚合起来使用。

DTOs在JHipster中如何工作

在生产JHipster实体的时候,你拥有一个添加Service层的选项:只有当你选择一个需要进行映射的Service层时DTOs选线才可以使用(当你使用的是JPA,由于service层具有事务,所以延迟加载会起作用)。

当你选择拥有service层时,你将会拥有从实体生成DTO的选项,如:

  • 一个基于当前实体映射的DTO将会被生成
  • 它将聚合多对一关系,仅使用ID和一个用于在客户端(如angular)显示的字段,因此,在一个基于User实体的多对一关系中将会添加一个userId和userLogin到DTO中。
  • 忽略一对多和多对多关系:这与实体的工作方式相似(在这些字段上具有一个@JsonIgnore
  • 对于所有者的多对多关系:它将使用来自其他实体的DTO,并将他们放在Set中,因此,只有在另一个实体也是用DTO的时候才能起作用。

使用MapStruct映射实体与DTOs

DTOs看起来很像一个实体,因此需要一个自动映射他们的解决方案。

JHipster选择的解决方案是使用MapStruct,它是一个注解处理器,在Java编译的时候自动的生成需要的映射。

我们发现,相对于反射而言,它干净而且高效。(使用反射进行映射的时候,对于性能而言比较糟糕)

在IDE中配置MapStruct

MapStruct是一个注解处理,当IDE编译项目的时候应该将它设置为自动运行。

如果你使用的士Maven,你需要在IDE中激活profile,gradle用户不需要进行任何设置。

如何激活Profile在“配置IDE”章节说明。

MapStruct的高级使用

MapStruct映射可以被配置为一个SpingBeans,并且支持依赖注入。你可以将Repository注入到映射中,就可以使用ID在Mapper中获取JPA实体。 如:

@Mapper
public abstract class CarMapper {

    @Inject
    private UserRepository userRepository;

    @Mapping(source = "user.id", target = "userId")
    @Mapping(source = "user.login", target = "userLogin")
    public abstract CarDTO carToCarDTO(Car car);

    @Mapping(source = "userId", target = "user")
    public abstract Car carDTOToCar(CarDTO carDTO);

    public User userFromId(Long id) {
        if (id == null) {
            return null;
        }
        return userRepository.findOne(id);
    }
}

关系管理

当使用JPA的时候,实体生成器可以创建实体之间的关系。

简述

只有使用JPA的时候关系才会工作,如果你选择 Cassandra, MongoDB of Couchbase的话关系是不可用的。

一个关系产生于两个实体之间,JHipster会生成如下代码:

  • 生成实体时通过JPA管理关系
  • 生成正确的Liquibase changelog,以便在已有的数据库中维护关系。
  • 生成angular的前端,你就可以通过图形使用用户接口管理关系。

JHipster UML 以及 JDL studio

这个章描述了如何通过Jhipster标准命令行接口生成关系。如果你需要常见比较多的实体和关系的时候,更好使用一些图形工具。

  • JHipster UML: 允许使用UML编辑器
  • JDL Studio:在线工具,使用我们特定的语言创建实体和关系

你可以通过import-jdl子生成器使用JDL文件生成实体和关系jhipster import-jdl you-jdl-file.jh

可用的关系

我们使用JPA,通常可用的关系有:one-to-many,many-to-one,many-to-many,以及one-to-one:

  1. 双向一对多
  2. 单向多对一
  3. 单向一对多
  4. 同一实体上的两个多对一关系
  5. 多对多关系
  6. 一对一关系
  7. 单向一对一关系

请注意通过jhipster生成的User实体,你可以:

  • 给此实体添加many-to-one关系(一个Carmany-to-oneUser),这将会在你的新的实体Repository中生成一个特定的查询,然后你参考一使用当前的认证用户查询实体。在生成的angular界面,你可以通过下拉选择Car来选中一个用户。

  • many-to-manyone-to-one关系在User实体中,另一个实体必须是关系的所有者(一个TeamUser是many-to-many关系,但是Team能增删User,但是User不能增删Team).在angular界面,你可以在多选框中选择User.

单向一对多关系

让我们从两个实体开始吧,OwnerCar。一个owner可以多辆car,一个car只能属于一个owner。

因此在owner这边是一个简单的一对多关系,而在另一边是一个多对一的关系。

Owner (1) <------> (*) Car

首先我们来创建Owner,这有几个关系Owner的问题:

jhipster entity Owner
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? car
? What is the type of the relationship? one-to-many
? What is the name of this relationship in the other entity? owner

请注意我们选择默认的关系名称。接下来,我们生成Car:

jhipster entity Car
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Owner
? What is the name of the relationship? owner
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Owner' do you want to use? id

使用下边的JDL也可以实现:


entity Owner
entity Car

relationship OneToMany {
  Owner{car} to Car{owner}
}

这样,你现在就拥有了一个一对多的关系在两个实体之间。在angular界面你可以通过Car下拉框选中一个User.

单向对多一关系

在前边的例子中我们有一个双向关系:通过Car实例可以发现他的Owner,通过Owner可以找到他的所有Car

一个单向多对一的关系意味着Car可以知道他们的Owner,但是不能反过来:

Owner (1) <------ (*) Car

拥有这样的关系可能有两个原因:

  • 从业务的角度来讲,你仅需要通过这种方式使用实体,因此不希望有其他的API允许开发者做一些没有意义的事情
  • 期望使用Owner时性能提升(因为你不用哪个维护一个Car的集合)

在这个例子中,你仍然需要先创建Owner,但这次没有生成关系:

jhipster entity Owner
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? No

接下来生成Car实体,和前边的例子相似:

jhipster entity Car
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Owner
? What is the name of the relationship? owner
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Owner' do you want to use? id

这将会和前边的例子一样正常公国,但是我们不能通过Owner来添加和删除Car。在angular你将会通过Car的下来框选中一个Owner。下边是相关的JDL:

entity Owner
entity Car

relationship ManyToOne {
  Car{owner} to Owner
}

单向一对多关系

单向一对多关系意味着Owner实例可以获取一个Car集合,但不反过来不能。这个和前边的例子是相反的:

Owner (1) ------> (*) Car

目前,这种关系类型Jhipster默认是不提供的。原因查看#1569

你有两种解决方式:

  • 使用一个双向映射不用修改,这也是我们推荐的,相对比较简单
  • 使用一个双向映射,然后将西修改成单向映射:
    • @OneToMany注解上移除mappedBy属性
    • 生成所需要的表:你可以使用mvn liquibase:diff生成表。

在JHipster中不支持通过JDL生成这种关系。

同一实体的一对多关系

在这个例子中,一个Person可以成为多个Car的拥有者,当然他也可以驾驶多辆Car:

Persion (1) <---owns---> (*) Car
Persion (1) <---drivers---> (*) Car

为此我们需要使用我们之前的例子中保留的默认关系名称。

生成Persion实体,它具有到Car的一对多关系:

jhipster entity Person
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? ownedCar
? What is the type of the relationship? one-to-many
? What is the name of this relationship in the other entity? owner
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? drivedCar
? What is the type of the relationship? one-to-many
? What is the name of this relationship in the other entity? driver

生成Car实体,使用在Person中的相同的关系名称:

jhipster entity Car
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Person
? What is the name of the relationship? owner
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Person' do you want to use? id
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Person
? What is the name of the relationship? driver
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Person' do you want to use? id

也可以通过如下的JDL实现:

entity Person
entity Car

relationship OneToMany {
  Person{ownedCar} to Car{owner}
}

relationship OneToMany {
  Person{drivedCar} to Car{driver}
}

现在,一个Car拥有一个driver和一个owner,他们都是Person实体。在生产的angular的界面我们可以下拉Carownerdriver选中一个Person.

多对多关系

一个Driver可以驾驶多辆汽车,但是一辆Car也可以有被多个司机。

Driver (*) <------> (*) Car

在数据库级别,这意味着DriverCar会有一张关联表。

对于JPA,这两个实体中一个将负责管理关系:在我们的例子中,将有Car来负责增删Driver.

让我们在没有关系所有权的一方生成多对多关系:

jhipster entity Driver
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? car
? What is the type of the relationship? many-to-many
? Is this entity the owner of the relationship? No
? What is the name of this relationship in the other entity? driver

接下来生成具有多对多关系所有权的一方Car:

jhipster entity Car
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Driver
? What is the name of the relationship? driver
? What is the type of the relationship? many-to-many
? Is this entity the owner of the relationship? Yes
? When you display this relationship with AngularJS, which field from 'Driver' do you want to use? id

也可以通过JDL实现相同的目的:

entity Driver
entity Car

relationship ManyToMany {
  Car{driver} to Driver{car}
}

这样,你就拥有了两个实体之间的多对多关系。在生成的angular界面,你可以通过一个Car的复选下拉框来选择属于这个Car的多个Driver了。

一对一关系

接着我们之前的例子,一个一对一关系意味着一个Driver只能驾驶一辆Car,一辆Car只能有一个Driver

Driver (1) <------> (1) Car

首先我们创建没有关系所有权的一方,Driver:

jhipster entity Driver
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? car
? What is the type of the relationship? one-to-one
? Is this entity the owner of the relationship? No
? What is the name of this relationship in the other entity? driver

接下来我们创建拥有关系所有权的一方Car:

jhipster entity Car
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Driver
? What is the name of the relationship? driver
? What is the type of the relationship? one-to-one
? Is this entity the owner of the relationship? Yes
? What is the name of this relationship in the other entity? car
? When you display this relationship with AngularJS, which field from 'Driver' do you want to use? id

通过JDL可以实现相同的目的:

entity Driver
entity Car

relationship OneToOne {
  Car{driver} to Driver{car}
}

单向一对一关系

单向一对一关系意味着可以通过citizen获取到passport,但不能通过passport获取citizen

Citizen (1) -----> (1) Passport

首先我们生成没有任何所属关系的一方Passport:

jhipster entity Passport
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? No

接下来,生成Citizen实体:

jhipster entity Citizen
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Passport
? What is the name of the relationship? passport
? What is the type of the relationship? one-to-one
? Is this entity the owner of the relationship? Yes
? What is the name of this relationship in the other entity? citizen
? When you display this relationship with AngularJS, which field from 'Passport' do you want to use? id

做完这些,一个Citizen拥有一个护照,但是在Passport上没有定义Citizen实例。生产的angular界面上,你可以下拉Citizen来选中一个Passport

相应的JDL如下:

entity Citizen
entity Passport

relationship OneToOne {
  Citizen{passport} to Passport
}

国际化

介绍

在生成一个新项目的时候,你将会被询问是否需要启用国际化的支持。

如果启用,你需要为你的应用选择一个原生语言。然后你可以选择你需要添加的其他语言。如果你一开始没有选择支持或者添加语言,那么稍后你可以通过语言生成器添加语言。

如果你确认你的应用不需要翻译成其他语言,你就可以不启用国际化。

支持的语言

  • Arabic (Libya)
  • Armenian
  • Indonesian
  • Catalan
  • Chinese (Simplified)
  • Chinese (Traditional)
  • Czech
  • Danish
  • Dutch
  • English
  • Estonian
  • Farsi
  • French
  • Galician
  • German
  • Greek
  • Hindi
  • Hungarian
  • Italian
  • Japanese
  • Korean
  • Marathi
  • Polish
  • Portuguese
  • Portuguese (Brazilian)
  • Romanian
  • Russian
  • Slovak
  • Serbian
  • Spanish
  • Swedish
  • Turkish
  • Tamil
  • Thai
  • Turkish
  • Ukrainian
  • Vietnamese

在项目生成之后如何添加语言?

可以通过以下命令来进行添加:

jhipster languages

Languages configuration is starting
? Please choose additional languages to install (Press <space> to select, <a> to toggle all, <i> to inverse selection)
❯◯ Arabic (Libya)
 ◯ Armenian
 ◯ Catalan
 ◯ Chinese (Simplified)
 ◯ Chinese (Traditional)
 ◯ Czech
 ◯ Danish
(Move up and down to reveal more choices)

如何添加支持列表之外的语言

所有的语言文件存储于这些文件夹:客户端src/main/webapp/i18n,服务端src/main/resources/i18n

下边是一个叫做new_lang的语言的安装步骤:

  1. 复制src/main/webapp/i18/ensrc/main/webapp/i18/new_lang(这是所有前端文件存储的地方)
  2. 翻译存储在src/main/webapp/i18/new_lang所有文件
  3. 为AngularJs 1 添加语言的常量定义代码,此常量位于src/main/webapp/app/components/language/language.constants.js

    .constant('LANGUAGES', [
        'en',
        'fr',
        'new_lang'
        // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array
    ]
  4. src/main/resources/i18n文件夹中,拷贝messages_en.properties文件为messages_new_lang.properties(服务端所有的翻译文件存储在这儿)
  5. 翻译messages_new_lang.properties文件中的所有键
  6. src/main/webapp/app/components/language/language.filter.js文件的函数filter('findLanguageFromKey')为Angular JS 1添加新的语言名称。在src/main/webapp/app/shared/language/find-language-from-key.pipe.ts文件的变量FindLanguageFromKeyPipelanguages添加新的语言名称。
  7. webpack.common.js文件中为Angular 2+添加新的语言绑定

    new MergeJsonWebpackPlugin({
        output: {
            groupBy: [
                { pattern: "./src/main/webapp/i18n/en/*.json", fileName: "./i18n/en.json" },
                { pattern: "./src/main/webapp/i18n/new_lang/*.json", fileName: "./i18n/new_lang.json" }
                // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array
            ]
        }
    })

现在,新的语言new_lang就出现在了语言菜单的下来框中,并且前端和后端应用都可用。

如何移除已存在的语言?

通过以下步骤移除叫做old_lang的语言:

  1. 移除文件夹src/main/webapp/i18/old_lang
  2. 进入文件src/main/webapp/app/components/language/language.constants.js或者 src/main/webapp/app/shared/language/language.constants.tswebpack.common.js移除定义的常量
  3. 移除文件src/main/resources/i18n/messages_old_lang.properties

升级应用

当新版本的Jhipster被发布时,使用JHipster upgrade生成器帮助升级已存在的应用到最先进的版本。升级有益于以下几点:

  • 在应用中需要使用新的特性
  • 获取重要的BUG修正或者安全更新
  • 保留你的代码,与新版本易于合并

在升级之前一定要仔细的阅读这个章节,并且理解升级过程的工作。

必要条件

这个子生成器工作必须使用git

运行升级子生成器

进入应用的根目录:cd myapplication/

升级你的应用: jhipster upgrade

执行此命令时有以下几个选项:

  • --verbose 打印每一步骤的详细日志
  • --target-version=4.2.0 升级至指定的版本
  • --force 如果没有新的版本可用也可以强制升级

升级过程示意图

下边的图展示了升级过程:
升级流程

请注意:jhipster upgrade会在你的项目中创建一个没有历史信息的分支,尽管在此图中没有正确的显示出来。

一步一步的说明升级流程

jhister通过下边的步骤来进行升级:

  1. 检查是否有新版本可用
  2. 检查项目是否是一个git项目,如果不是jhipster将初始化你的项目为git项目,并将当前代码提交到主分支
  3. 检车确保没有未提交的代码在仓库中,如果有未提交的代码,升级程序将会退出
  4. 检车是否有jhipster_upgrade分支存在,如果不存在则创建(详细的过程下一小节描述)
  5. 检出并切换到jhipster_upgrade分支
  6. 全面升级到最新可用分支
  7. 清理当前目录
  8. 在应用中重新生成代码,使用此命令jhipster --force --with-entities
  9. 提交生成的代码到jhipster_upgrade分支
  10. jhipster upgrade命令将会合并jhipster_upgrade分支到原始分支
  11. 最后,如果有合并冲突的话就需要你来处理了

第一次执行升级的步骤

第一次执行jhipster upgrade为了避免擦除你的修改代码,将会有额外的步骤运行:

  1. 一个没有历史信息的jhipster_upgrade分支被创建
  2. 整个应用被生成(使用你当前的jhipster版本)
  3. master分支上进行块提交:你的master分支没有进行任何更改,master的当前jhipster分支是最新的,这是一种最佳实现

建议

不要再jhipster_upgrade分支上进行任何提交,这个分支将只用于jhipster升级:每次升级的时候,都会创建一个新的jhipster_upgrade分支

微服务

概述

微服务和单体架构

jhipster的第一个问题是询问你想生成何种类型的应用.你有两种不同架构的选择:

  • 单体架构应用。它同时包含了应用的前端和后端代码。
  • 微服务架构,它将前端和后端进行分离,这样做更易于你的应用的扩展和处理一些基础设施问题

单体应用是非常容易被使用的,因此你如果没有特殊需求,这是一个推荐的选项,也是我们默认的选项

微服务架构概述

jhipster的微服务架构通过以下方式工作:

  • 一个jhipster生产的网关应用(生产代码时选择应用类型为microservice gateway),它用来处理web通信并且是angular应用的服务端。如果你想要后台适应前端,这儿提供好几个网关,但不是强制的。
  • Treafik是一个现代HTTP反向代理和负载均衡器,它能与网关一起工作
  • Jhipster Registry是一个运行时的应用,用于注册所有应用并且从这儿获取配置。
  • Consul是一个以键值存储的服务发现的服务,他可以替代Jhipster Registry
  • JHipster UAA 是一个基于jhipster的用户认证和授权系统,使用OAuth2协议
  • Microservices是jhipster生成的应用,它用于处理REST请求,他们是无状态的,并且可启动多个实例处理高并发
  • Jhipster Console是一个监控和告警平台,基于ELK技术栈

在下边的图中,绿色的组件是你的应用程序,位于蓝色组件提供的基础服务之下

微服务架构图

API 网关

Jhipster可以生成API网关,网关是一个普通的jhipster应用,你可以使用通用的jhipster选项和开发工作流程,当然,你也可以用它作为你的微服务的入口。特别的,它为所有微服务提供了HTTP路由和负载均衡,服务质量,安全和API文档

架构图

这里写图片描述

使用网关进行HTTP请求路由

当网关和微服务启东时,他们会自己注册到注册中心(使用src/main/resources/config/application.yml文件的键eureka.client.serviceUrl.defaultZone

网关会使用每个微服务应用名称,自动的代理所有的请求。比如,当微服务app1注册了,通过网关可以通过/app1的URL来访问

比如,你的网关运行在localhost:8080,你可以通过http://localhost:8080/app1/rest/foos获取app1上的foos资源。如果你在web浏览器上尝试,不要忘记jhipster对于REST资源的安全的控制。因此,你需要发送正确的JWT请求头(下个小节详细解释),或者在微服务的MicroserviceSecurityConfiguration移除对这些URL的安全控制。

如果有多个相同服务实例运行时,网关可以从注册中心获取这些实例,然后:

  • 使用Netflix Ribbon进行HTTP请求负载
  • 使用Netflix Hystrix提供一个断路器,当实例失败时,快速安全的移除

每个网关都有一个特定的admin > gateway菜单,在这里可以打开http路由和监控每个微服务

安全

JHipster标准的安全选项在安全的章节有详细说明。然而,确保一个微服务体系有一些特定的调优和选项,这儿将详细解释:

JWT(JSON Web Token)

JWT是一个工业标准,为微服务架构的安全提供了易于使用的方法

JHipster使用JJWT libary,它由Okta提供,实现了JWT

通过网关生成token,然后发送给位于网关之下的微服务:这些微服务共享一个安全密钥,微服务能够使用其验证token,并且使用这个token能对用户进行认证

这些Token将会提供一些重要的信息(自给自足的信息):它们具有授权和认证信息,因此每个微服务不需要查询数据库或者扩展系统。这对于微服务的水平伸缩架构时比较重要的。

为了安全工作,一个JWT密钥令牌需要在所有的应用之间共享:

  • 每个应用通过jhiposter生成的默认令牌是唯一的。存储在.yo-rc.json文件中
  • 令牌在src/main/resources/config/application.yml文件的jhipster.security.authentication.jwt.secret键值配置
  • 共享这些key在你的所有应用之间,从你的网关复制key到所有的微服务中或者使用注册中心的spring config server共享(使用指定的配置在consul中)。这也是人们为什么使用中心配置服务的原因。
  • 一个最佳实践是在生产环境和正式环境使用不同的key
OpenId Connect

jhipster提供了OpenId连接的支持,详细的在OpenID章节详细解释。

当我们选择openid的选项的时候,你将默认使用Keycloak,并且很可能你的微服务应用完全使用docker compose:请确认读了我们的docker compose文档和正确的在你的/ets/hosts配置了Keycloak

当使用OpenId连接的时候,jhipster网关将发送OAuth2的令牌给微服务,他们将会接收这些令牌并连接到keycloak.

不同JWT,这些Token并不能自给自足,主要由两个原因:

  • 微服务中红的性能问题:由于查询当前用户信息是非常常见的需求,每个微服务调用OpenId连接服务器获取这些信息。在正常的设置中,每个微服务都会调用,每次请求很快会造成性能问题。

    • 如果你选择了缓存的选项,将会生成一个CachedUserInfoTokenServicesspring bean,她将会缓存这些调用。适当的调休会降低一些性能的问题。
    • 如果你想通过这次的userinfo请求获取更多的信息时,可以在src/main/resources/application.yml使用标准的spring boot配置键security.oauth2.resource.userInfoUri
  • 认证在Keycloak和应用之间不自动同步,请注意在标准的openid连接工作流中,我们希望在这个问题上进行一些具体的改进。结果如:

    • 当一个用户签出应用时,如果他刷新了浏览器会自动再次登录:因为他仍然登录了Keycloak,而Keycloak提供了自动认证
    • 当用户在session在Keycloak中失效是,用户依然在登录应用,他将仍可以使用应用。这是因为OpenId连接是一个状态机,应用并不能立即知道当前会话失效
Jhipster UAA

jhipster提供了一个生成基于spring security的UAA服务选项,这个服务为网关的安全提供了OAuth2的令牌

你将会在后续的章节发现UAA的文档。

网关使用Spring Security的JWT实现发送JWT令牌到微服务,这基本与上边描述的JWT配置相同

自动生成文档

网关公开了微服务定义的swagger api,这样你就可以从一些工具中获得收益如swagger UI和swagger-codegen

网关的admin > API下拉菜单,展示了网关api和api来自与哪个微服务。

使用这个下拉列表,所有的微服务API将会被自动的记录下来,并且可以从网关进行测试。

当使用一个安全控制的API,swaggerui会自动的添加安全令牌,因此一切请求开箱即用

限速

这是一个先进的功能,它使用Bucket4j个Hazelcast来保障微服务的质量

网关提供了一些限速功能,一些REST请求可以被限制:

  • 通过IP限制(针对匿名用户)
  • 通过用户的登录(显示登录用户)

jhipster使用bucket4j和hazelcast来估算请求总量,当超过限制时会发送HTTP响应码429(太多请求),默认每个用户每小时调用10万次API

这是一个重要的功能,保证了微服务体系不会被一些特定的用户的请求淹没.

网关可以保护REST的端点,他可以完全获取用户的安全信息。因此很容易的扩展,根据用户的安全角色提供特定的速率限制。

启用速率限制,打开application-dev.yml或者application-prod.yml文件并设置enabledtrue:

jhipster:
    gateway:
        rate-limiting:
            enabled: true

数据存储在Hazelcast中,因此只要配置了Hazelcast分布式缓存就可以对网关进行扩容,这些是开箱即用的:

  • 所有网关默认都有Hazelcast配置
  • 如果你使用jhipster注册中心,所有的网关实例应该自动的注册到分布式缓存

如果你想要添加更多的规则或者修改已存在的规则,你需要在RateLimitingFilter编码。例如可以修改:

  • 降低HTTP调用限制
  • 添加每分钟或者每天的限制
  • 移除对amdin用户的所有限制

访问控制策略

默认通过网关可以访问所有注册的微服务。如果你想通过网关排除指定的API,你可以使用网关指定的访问控制策略过滤器。这个配置在application-*.yml文件的jhipster.gateway.authorized-microservices-endpoints键上,如:

jhipster:
    gateway:
        authorized-microservices-endpoints: # Access Control Policy, if left empty for a route, all endpoints will be accessible
            app1: /api,/v2/api-docs # recommended dev configuration

例如,你仅想使bar微服务的/api/foo端点可用:

jhipster:
    gateway:
        authorized-microservices-endpoints:
            bar: /api/foo

Traefik

Traefik概述

traefik是一个现代HTTP反向代理和负载局衡器,使得微服务的部署比较容易

它可以像zuul一样路由HTTP请求,因此它与jhipster的gateway有一些功能上的重叠,但是它工作于网关的更低一级:它仅仅路由http请求,但不提供速率限制、安全和swagger文档聚合

对于多种微服务的发现方案是比较有益的,但是它仅工作与Consul

这有两种不同的架构风格,描述如下:

架构图1:默认配置

Traefik作为反向代理和均衡器,放弃使用Zuul,它将所有的http请求正确的转发到微服务。

这里写图片描述

在这种架构中,jhipster”网关“不是一个真正的网关,主要服务于angular应用,这是我们的默认配置

架构图2:Traefik和Zuul

Traefik可以和Zuul一起工作:在这例子中,一个HTTP请求通过Traefik然后通过Zuul到达目的地。

这里写图片描述

这种方式多增加了一此网络请求,因此比之前的架构低效。但是这允许网关充分发挥潜力:进行限速和swagger文档聚合

作为结果,Traefik作为一个边缘服务,允许扩展jhipster网关

这个配置在JHipster是开箱即用的:唯一问题是客户端应用需要使用绝对URL。因此对于microservice1:

  • 对于默认的URL/microservice1,只能通过Traefik访问
  • 对于/gateway/microservice1将会使用Traefik配置的gateway访问microservice1应用

入门

请注意Traefik只能工作于Consul,如果你使用的Jhipster Registry则不能工作

使用Traefik架构,运行docker-compose生成器时询问你选择哪种网关时,选择Traefik

这将会生成traefik.yml配置文件以便于在Docker运行Traefik,并会生成并配置traefik/traefik.toml文件

这个配置文件如此设置:

  • Traefik运行与80端口,如果你的需要访问网关,请访问http://localhost/gateway
  • Traefik管理界面运行于8080端口,你应该访问http://localhost:8080

当Traefik使用Consul时,检查Consul管理界面也比较有用,可以访问http://localhost:8500

JHipster Registry

概述

jhipster registry是由jhipster团队提供的一个应用,它是基于Apache2-licensed协议开源的,它的代码已托管至github,jhipster/jhipster-registry

JHipster Registry有三个主要用途:

  • 它是一个用于服务发现的Eureka服务,这就是为何jhipster能处理路由、负载均衡和服务伸缩
  • 它是一个spring cloud config server,提供了所有应用的运行时配置
  • 它是一个管理服务,在仪表盘可以监控和管理应用

所有的这些功能都被打包在一个基于angular的用户界面应用中。

这里写图片描述

安装

spring profiles

JHipster registry使用了jhipster通常使用的devprod的配置,以及使用了来自于 spring cloud config的标准nativegit配置

结果:

  • 使用dev配置运行JHipster Registry的时候,它将会使用devnative配置文件,native将会从文件系统加载spring cloud的配置,具体的文件位于运行目录的central-config
  • 使用prod配置运行JHipster Registry的时候,将会使用prodgit配置文件。gitprofile将会git仓库加载spring cloud配置,默认的仓库是https://github.com/jhipster/jhipster-registry-sample-config。在真实使用的时候,这个地址应该被修改为正确的地址,可以通过重新配置src/main/resources/config/bootstrap-prod.yml或者spring.cloud.config.server.git.uri属性

一旦JHipster Registry运行,你可以在Configuration > cloud config菜单中检查他们。请注意,如果你不能登录,这可能是由于你没配置好导致JWT的签名不对

使用war包

JHipster Registry是一个可执行的war包.

下载war包后,就像是通常运行JHipster应用程序一样,选择你想使用的配置。例如,运行Registry且Spring cloud config配置存储在cetral-config文件夹中:

./jhipster-registry-<version>.war --security.user.password=admin --jhipster.security.authentication.jwt.secret=secret-key --spring.cloud.config.server.native.search-locations=file:./central-config

注意在启动Registry的时候提供一个JWT的密钥是比较重要的事情,你可以通过环境变量JHIPSTER_SECURITY_AUTHENTICATION_JWT_SECRET或者使用类似于上边的参数的方式提供。另一种可能的方法是在application.yml文件中设置。

类似的,在以prod启动Registry的时候,你可以使用下边的命令:

./jhipster-registry-<version>.war --spring.profiles.active=prod --security.user.password=admin --jhipster.security.authentication.jwt.secret=secret-key --spring.cloud.config.server.git.uri=https://github.com/jhipster/jhipster-registry-sample-config

通过源码构建

JHipster Registry的源码可以从git上获取(克隆、引用或者下载).此应用也是通过jhipster生成器生成的。你可以用使用你喜欢的方式运行:

  • 在开发环境下使用./mvnw运行(作为一个Java服务)和yarn start(作为前端)他们默认使用dev配置。可以通过http://127.0.0.1:8761/访问
  • 使用./mvnw -Pprod package打包至正式环境,通常生成一个可执行的war。你也可以通过使用devprod配置来运行:./jhipster-registry-<version>.war --spring.profiles.active=prod

注意使用native配置,你需要由一个central-config目录。因此如果你已./jhipster-registry-<version>.war --spring.profiles.active=dev方式运行,你需要提供这个目录

使用Docker

如果你更喜欢通过docker镜像运行JHipster Registry,那么在docker hub上有一个可用镜像.存在于每个微服务的src/main/docker目录下的docker-compose文件可以很轻易使其运行与docker上:

  • 运行命令docker-compose -f src/main/docker/jhipster-registry.yml up启动JHipster registry。它将会暴露端口为8761,如果docker在你的主机上,可以访问http://127.0.0.1:8761/
运行在云上

在云上托管一个实例是非常容易的。在生产中是强制的,但在开发中也是比较有用的(你没必要在你的笔记本上运行),后续在生产环境使用微服务章节详细解释如何部署在云上.

使用Eureka进行服务发现

JHipster Registry是一个Eureka服务,它提供了所有应用的服务发现

  • 这是一种非常使用的微服务架构:网关可以知道哪些微服务可用,哪些实例已启动
  • 对于所有应用包括综合应用,这也是Hazelcast分布式自动伸缩的方式

使用Spring Cloud Config进行应用配置

JHipster Registry是一个Spring Config Server. 当应用程序启动时首先会连接Jhipster Registry获取他们的配置.对于网关和微服务都是这样的

这个配置是一个spring-boot的配置,类似于jhipster中的application-*.yml文件,但是这些是集中存储的,因此更易于管理

当启动的时候,网关和微服务应用将会查询Registry的配置服务,使用在Registry定义的属性覆盖本地的属性

有两种配置源可用:

  • native配置,一般被用于开发环境,使用本地文件系统
  • git配置,在生产环境默认使用,他们将会存储在git服务。允许使用git工具进行打标签、分支或者回滚配置.

为了集中管理你的配置,你只需在你的配置源中增加appname-profile.yml文件。例如,添加一个gateway-prod.yml文件,将会只为gateway服务通过prod配置启动的时候设置这些属性。此外,application[-dev|prod].yml文件中定义的属性将被设置到所有的服务上.

使用spring-boot网关也是可配置的,他也能使用SpringConfigServer管理。例如,你的分支v1的应用app-v1映射在/app1的url上,分支v2的应用app1-v2也映射在/app1上,这样就是一种很好的升级方式,而且不需要停机升级。

加密配置文件中的值

JHipster Registry有一个configuration > encryption页面允许容易的加密和解密配置文件中的值。

为了加密配置文件中的值你需要:

  • 下载JCE并按照说明安装它(如果你使用OracleJDK这是必须的)
  • 在bootstrap.yml设置encrypt.key或者使用ENCRYPT_KEY环境变量

如果所有的设置都是正确的,你可以使用configuration > encryption页面,你也可以POST你想要处理的文本置于body中请求/config/encrypt/config/decrypt

例如: curl localhost:8761/config/encrypt -d mypassword

密码文本必须放在*.yml中,password= '{cipher}myciphertextafterencryotion'将发送到客户端之前将被解密。这样你的git或者本地配置文件中就没有明文.

更多的细节,参照spring cloud config的加解密

管理界面

JHipster registry提供了一个管理各种类型应用的面板。当应用注册到eureka服务不久,就会在面板中看到。

为了读取应用的敏感信息,JHipster Registry将会使用JWT令牌(这就是JHipster Registry使用JWT的原因).请求签发的JWT令牌对于应用程序和注册中心是相同的:JHipster Registry通过spring cloud config来配置应用程序,这应该是开箱即用的,因为它向所有应用程序发送相同的密钥。

指标面板

指标面板使用Dropwizard指标对每个应用性能进行详细展示,这些指标有:

  • JVM相关
  • HTTP请求
  • Spring Beans的方法使用情况(使用@Timed注解)
  • 数据库连接池

点击JVM线程指标后边的眼睛图标,可以查看应用的运行栈,这对于查找阻塞线程是比较有用的

健康面板

健康面板使用springboot-actuator的接口获取应用的各部分健康信息。spring-boot-actuator提供了很多开心急用的方法,也很容易添加应用指定的健康检查。

配置面板

配置面板使用spring-boot-actuator的配置接口获取当前应用的配置视图

日志面板

日志面板允许在运行的时候管理Logbak的配置。改变package的日志级别简单到店家一个按钮就可以做到,这对于生成环境和开发环境提供了很大的便利性

JHipster Registry安全

JHipster Registry默认是安全的。你可以使用admin/admin像登录普通服务一样登录Registry.

应用也可以使用admin用户通过HTTP Basic的认证方式连接到Registry。如果你的应用不能读取注册中心的数据,并且你看到了”401 authentication error”信息,那是因为你在应用的配置缺失了.

为了提高JHipster Registry的安全:

  • 你必须修改默认的”admin”的密码。这个密码使用spring-boot的标准属性security.user.password设置,因此你可以采用spring-boot的机制设置它:你可以修改application-*.yml文件或者添加SECURITY_USER_PASSWORD环境变量
  • 由于你的应用通过HTTP连接到注册中心,因此确保连接通道的安全是非常重要的。很多方法都可以做到,最简单的方法是使用HTTPS

Consul

Consul概述

替代JHipster Registry的另一种选择是使用Consul——一个来自Hashicorp的数据中心管理解决方案,相对于使用Eurake有以下几种优点:

  • 在多节点中比Eurake更容易出操作
  • 更倾向于一致性而不是可用性,所以集群的状态可以更快的传播
  • Consul的服务发现可以简单与现有服务进行交互(通过DNS或者HTTP API)

架构图

这里写图片描述

入门

开始开发依赖Consul的应用程序,你可以在docker上启动Consul实例:

  • 运行docker-compose -f src/main/docker/consul.yml up启动一个consul服务在dev模式下,consul将在你的Docker主机上的8500端口可用,因此你主机上的应该是http://127.0.0.1:8500/

应用中配置Consul

当你生成你的项目或者网关的时候选择了Consul选项,他们将会自动的从consul的键值存储中获取他们的配置.

存储的键值对可以通过界面或者REST API进行修改。但是这样的修改是临时性的,当Consul服务关闭时丢失。因此为了轻松的与键值存储交互以及将配置作为简单的yaml文件进行管理,JHipster提供了一个小工具consul-config-loader.当docker-compose通过consul.yml文件启动consul的时候,consul-config-loader会自动的配置。它有两种运行模式:

  • dev模式,从central-server-config目录自动加载yaml文件到consul.此外对这个目录的任意修改都会同步到consul.
  • prod模式,使用Git2Consul将git仓库的yaml配置文文件设置为键值存储的源

注意,将此作为JHipster Registry的时候,你的配置文件需要命名为appname-profile.yml,appname好profile与你希望提供的服务的应用名称和配置相符。例如,添加一个consulapp-prod.yml文件将会把文件中的属性应用到consulapp应用的prod启动的模式中。此外,定义于application.yml文件定义的属性将会为所有的应用程序设置.

JHipster UAA

JHipster UAA是一个使用OAuth2认证协议、为JHipster微服务提供用户账户和授权的服务。

应当清晰的了解JHipster UAA和其他的UAA(例如cloudfoundry UAA)。 JHipster UAA是一个完全配置的使用内置的user和roleOAuth2授权认证服务,被打包成一个普通的JHipster应用。这允许开发人员深入的配置他的user领域模型的每个部分,而不限制它访问UAA的策略。

架构图

这里写图片描述

微服务架构的安全要求

在深入的研究位于JHipster微服务上的OAuth2与应用之前,需要清楚地了解可靠的安全解决方案的要求

  1. 集中认证

大多数微服务都是独立自主的应用。因此希望有一个一致的认证体验而不想用户注意到他的请求来自于不同的安全配置的应用

  1. 无状态

最大的好处是构建的微服务可扩展,因此选择的安全解决方案不能影响扩展性。在服务器上保持用户会话状态较为棘手,因此一个无状态的解决方案成为首选.

  1. 用户/机器访问区别

需要清除的区分不同的用户和不同的机器。使用微服务架构可以构建一个大型的不同领域的数据资源中心,因此需要限制不同的客户端,例如本地的应用、多个SPAs等等的访问.

  1. 细粒度的访问控制

在维护集中的角色时,需要在每个微服务中配置详细的访问控制策略。微服务不应该起识别用户的责任,并且每个进入的请求都经过了授权.

  1. 免受攻击

无论一个安全解决方案能够解决多少问题,它都应该尽可能强大

  1. 可扩展

使用无状态协议并不是安全解决方案的保证。最后,不应该有单点问题。一个反例是单节点共享的授权数据库或者单节点的授权服务实例。

理解在此场景下的OAuth2

使用OAuth2**协议**能够满足以上6点要求。它遵循严格的标准,使得它可以与所有的微服务或者其他的远程系统相容。JHipster基于此提供了一些解决方案:

JHipster OAuth2通信流程

  • 请求架构中的任意端点都由一个“client”执行
  • “client”是一个对于客户端的抽象,它可以是”angular $http_client”,可以是REST-Client,也可以是curl以及一切可以发起请求的事物
  • 在端点(包括UAA)上提供服务的每个微服务都是资源服务器
  • 蓝色的箭头显示客户在Oauth授权服务器上的身份认证
  • 绿色箭头显示客户端对资源服务器的请求
  • UAA服务是授权服务和资源服务的组合
  • UAA服务是微服务的数据提供者(它批准自动访问资源服务器)
  • 客户端使用用户身份认证信息访问资源,认证使用密码授权,而授权的信息clientId和密钥都安全的存储在网关的配置文件中
  • 客户端不使用用户身份访问资源,就需要通过客户端认证授权
  • 刻个客户端都在UAA内部定义

该设计可以应用于任何独立的语言或者框架的微服务架构体系

另外,以下规则也可以用于访问控制:

  • 用户访问可以被配置为使用rolesRBAC
  • 机器访问控制可以被配置文scopes和RBAC
  • 复杂访问配置可以使用ABAC,通过boolean表达式处理rolesscopes
    • 例子:hasRole(“ADMIN”) and hasScope(“shop-manager.read”, “shop-manager.write”)

使用UAA

在生成Jhipster微服务脚手架的时候,你可以选择使用UAA选项代替JWT认证

**注意**UAA解决方案也使用了JWT,它可以使用spring-cloud-security自定义JWT

基本设置

最基本的设置包括:

  1. 一个JHipster UAA服务
  2. 至少一个其他的微服务(使用UAA认证)
  3. 一个JHipster网关(使用UAA认证)

这是生成它的顺序。

除了提供身份验证类型之外还需要提供UAA的位置

这个设置的工作方式与JWT类型相同,但会有一个服务

理解组件

Jhipster UAA 服务做了三件事:

  • 提供了默认的用户领域模型,包含user和account资源
  • 它实现了AuthorizationServerConfigurerAdapter定义了基本的客户端(“web_app”和”internal”)
  • 它提供了JWT的公钥在/oauth/token_key,将会被其他的微服务消费

选择数据库、缓存方案、搜索引擎、构建工具以及更多的选项都开放给开发人员。

当一个微服务启动时,通常UAA已经启动并开始共享公钥。服务首先会调用/oauth/token_key获取公钥并使用此密钥配置签名(JwtAccessTokenConverter)

如果UAA没有启动,应用将会继续启动并在稍后获取公钥。有两个属性可以空多久(uaa.signature-verification.ttl)获取一次公钥和限制请求次数(uaa.signature-verification.public-key-refresh-rate-limit)避免频繁请求。这些值通常有默认值。在任何情况下,如果检查失败,微服务将会检查是否有新的公钥。这样,公钥可以被替换而服务一直可用.

从这点来看可能有两个用例使用这个基本设置:用户调用和机器调用(应用调用)

对于用户调用,登录请求发送至网关的/auth/login端点。这个端点使用OAuth2TokenEndpointClientAdapter发送请求到UAA进行密码去身份验证。因为这个请求发生在网关上,所以clientId和密钥在客户端没有存储而用户无法访问到。网关将返回一个包含令牌的Cookie,在客户端后续的请求中这个cookie将随着客户端请求发送至jhipster后端。

对于机器(应用)调用,该应用需要通过客户端证书使用UAA进行身份验证。JHipster提供了一个标准的解决方案,后续有详细描述。

Refresh Tokens

刷新访问令牌通常发生在网关上,如下所示:

  • 身份验证是通过AuthResource调用OAuth2AuthenticationService的认证(设置cookies)
  • 对于每个请求,RefreshTokenFilter(在RefreshTokenFilterConfigurer配置)会检查访问令牌是否有效、是否过期
  • 如果过期或者失效了,将会触发刷新令牌的流程,通过OAuth2AuthenticationService刷新令牌
  • 使用OAuth2TokenEndpointClient接口发送请求刷新令牌授权到OAuth2服务器,在我们的案例中是UAA(通过UaaTokenEndpointClient
  • 刷新的结果将被以新cookie传递到下游,并将上游(浏览器)设置为新cookie
常见的误区

下面是一个开发人员应该知道的事情的简要列表.

生产和演示环境使用相同的签名密钥

非常严肃的推荐大家尽可能的使用不同的密钥。一旦一个签名密钥落入一个错误的人手中,就可能在不知道任何用户凭据的情况下获取完全的访问授权密钥.

不使用TLS

如果攻击者设法截取到令牌,他将获得该令牌的所有权限,知道令牌过期。可以有很多种方式实现这一点,特别是再没有TLS加密的情况下。这在OAuth1的时候是不会出现的因为协议要求强制加密.

在url中使用访问令牌

根据标准规范,访问令牌可以在url、header或者cookie中。从TLS的观点来看,这三种方法都是安全,实际上URL的安全性较低,有很多种方式从url记录中获取。

选在对称加密签名

JWT签名并不一定需要RSA,Spring Security也提供了对称的令牌签名。这虽然解决了一些问题,但使得开发困难。这也是不安全的,攻击者只需要获取一个节点的微服务就可以生成自己的JWT令牌。

使用Feign进行安全的跨服务通信

目前,只有JHipster UAA提供了一种可伸缩的安全的跨服务通信方法。

使用JWT身份验证无需手动将JWT请求转发至内部以强制微服务之间通过网关互相调用(主请求的内部请求)。但即使自动转发,也不能将用户和应用的身份验证分离。

由于JHipster使用了OAuth2,所有的问题在这个协议上都得到了解决。

这个章节讲述了如何轻松的使用它。

使用Eureka, Ribbon, Hystrix以及Feign

当一个服务想要请求其他服务的数据时,会有4个组件参与进来。因此了解每个的职责是非常重要的:

  • Eureka: 注册服务,你从这里可以获取某个服务注册了的所有实例IP
  • Ribbon: 当有人请求服务时,根据服务的IP集合进行负载均衡

综上所述,当我们需要访问一个URLhttp://uaa/oauth/token/(此URL由运行在10.10.10.1:999910.10.10.2:9999上的两个实例提供服务),我们可以使用EUreka和Ribbon轮询算法快速的转换为http://10.10.10.1:9999/oauth/token或者http://10.10.10.2:9999/oauth/token

  • Hystrix: 一个在服务失败时的熔断器系统
  • Feign: 使用所有的声明式风格

在现实场景中,所有的服务实例都有可能出现问题。因此Hystrix作为一个断路器,以一种快速失败的方式来处理调用失败的场景

但是手工编码这些东西工作量巨大。而Feign提供使用一些带注解的接口为访问注册到Eureka端点的带负载均衡的REST客户端选项,并使用Hystrix实现了回退。

所以,对于内部通信,Feign是非常有用的。当一个服务需要通过REST客户端访问另一个服务”othser-service”时,可能的接口定义如:

@FeignClient(name = "other-service")
interface OtherServiceClient {
  @RequestMapping(value = "/api/other-resources")
  List<OtherResource> getResourcesFromOtherService();
}

接下来通过依赖注入进行使用:

@Service
class SomeService {
  private OtherServiceClient otherServiceClient;

  @Inject
  public SomeService(OtherServiceClient otherServiceClient) {
    this.otherServiceClient = otherServiceClient;
  }
}

类似于Spring Data JPA,这里也不需要实现任何的接口。当然如果你使用Hystrix时你也可以这样做。Feign接口的实现类作为回退的实现。

一个存在的问题是使用UAA保障通信安全。为了解决这个,有一些用于Feign的拦截器,它实现了来自于OAuth的客户端认证,以授权当前服务请求其他服务。在Jhipster中,你只需要使用@AuthorizedFeignClients即可。这注解是由JHipster提供的,它确实做到了这一点。

使用@AuthorizedFeignClients

考虑到上边的”other-service“服务的资源是受保护的,这个接口的注解如下:

@AuthorizedFeignClient(name = "other-service")
interface OtherServiceClient {
  @RequestMapping(value = "/api/other-resources")
  List<OtherResource> getResourcesFromOtherService();
}

当没有优点的令牌在内存中的时候,REST客户端会自动的从UAA获取认证。

测试UAA应用

模拟Feign客户端

与Feign一起工作的组件都是可测试的。在测试中使用Feign和生产中使用的方式一样,将迫使Jhipster Registry与UAA运行在同一台机器(测试用例执行的机器)。大多数场景中,你不需要测试Feign自己,但是使用Feign客户端的组件需要。

要测试内部使用Feign的组件可以使用@MockBean(自spring-boot 1.4.0引入)

这有一个例子,测试SomeService使用模拟数据的客户端:

@RunWith(SpringRunner.class)
@SpringBootTest(App.class)
public class SomeServiceTest {

    @MockBean
    private OtherServiceClient otherServiceClient;

    @Inject
    private SomeService someService;

    @Test
    public void testSomeService() {
        given(otherServiceClient.getResourcesFromOtherService())
        .willReturn(Arrays.asList(new OtherResource(...));

        someService.performActionWhichInkvokesTheAboveMentionedMethod();

        //assert that your application is in the desired state
    }
}

因此,通过这种技术,你可以模拟其他服务的行为并提供预期的资源实体。所有Bean都可以注入模拟的客户端,而你只要关注Bean中具体的逻辑。

模拟OAuth2认证

使用Spring对REST控制器的集成测试通常会绕过安全配置,因为它会使测试变得很困难,当唯一的意图是证明控制器在执行它应该做的事情时。但是有时候,测试控制器的安全性行为也是测试的一部分。

在这个用例中,JHipster提供了一个叫做OAuth2TokenMockUtil的组件,它可以模拟有效的认证而无需客户端或者用户存在。

要使用这个特性,有两件事情必须完成:

  1. 在模拟SpringMVC的环境中启用安全并注入模拟工具
 @Inject
    private OAuth2TokenMockUtil tokenUtil;

    @PostConstruct
    public void setup() {
        this.restMockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())
            .build();

    }
  1. 使用OAuth2TokenMockUtil

这个工具提供另一个方法oaut2authentication,可使MockMvn的”with”操作可用。它而已被配置使用以下字段模拟认证:

  • username
  • roles(Set)
  • scope(Set)

例子:

@Test
public void testInsufficientRoles() {
    restMockMvc.peform(
        get("url/requiring/ADMIN/role")
        .with(tokenUtil.oauth2Authentication("[email protected]", Sets.newSet("some-scope"), Sets.newSet("ROLE_USER")))
    ).andExpect(status().isForbidden());
}

创建微服务

微服务是JHipster的一种应用类型,它没有前端,它可以通过JHipster Registry进行配置、发现和管理

在微服务架构中生成实体

在微服务架构中使用实体生成器有一点不同,因为前端和后端代码不在同一个应用中。

首先,在微服务应用中生成实体:和通常一样你可以使用jhipster UML或者JDL studio来生成复杂的实体和关系。但微服务不需要前端,不会有angularJS代码生成。

接着,在网关上,再次运行实体生成器。一个新的问题出现在指定的网关上了:

  • 你将会选择生成一个普通的实体(网关本身是一个综合应用)或者使用来自微服务的已存在的JHipster配置
  • 如果选择从一个微服务生成实体,你需要输入这个微服务在你计算机上的路径,jhipster将会在网关上生成前端代码

分布式缓存 Hazelcast

如果你的应用使用SQL数据库,JHipster为微服务提供了二级缓存的解决方案:

  • Jhipster默认的微服务缓存方式是Hazelcast
  • 你可以仍然选择Ehcache(综合应用默认使用)和最后选择不使用

这是微服务的默认解决方案,在这个体系中可以扩展你的微服务:

  • 使用本地缓存,你的服务实例将不能同步缓存,得到不正确的结果
  • 不使用缓存,数据库的负担加重

在微服务中使用Hazelcast将有一个特定配置:

  • 启动的时候,你的应用将会连接JHipster Registry发现是否有其他的服务在运行
  • 当使用dev配置时,JHipster在localhost创建一个集群使用不同的端口。默认,Hazelcast的端口是你的应用程序的端口+5701.(你的应用程序的端口是8081那么Hazelcast的端口是13872)
  • 当使用prod配置的时候,JHipster将会在所有发现的节点中创建一个集群,使用默认的5701端口

不使用数据库的微服务

只要微服务应用在没有使用数据库的情况下创建。这是因为微服务很小,并且没有用户管理代码。

没有数据库的微服务非常小,可以用来连接到特定的后端,比如遗留系统。

生产环境的微服务

微服务是一种特殊的JHipster应用程序。请参考我们在生产文档中使用JHipster的主要内容,了解更多关于生产构建、优化和安全的信息。

微服务监控

请参考JHipster Registry[之前章节]文档,来了解哪些运行时面板可用以及如何使用。

我们的监控文档也是比较重要的,可以学习使用的详细信息:

  • JHipster Console使用ELK在你的微服务架构体系中
  • Zipkin在你的微服务中追踪http请求
  • Elastalert用于问题发生时的告警

使用Docker Compose开发和部署

在微服务体系结构上工作意味着您需要多个不同的服务和数据库一起工作,在这种环境下,Docker组合是管理您的开发、测试和生产环境的一个很好的工具。

在我们的Docker-compose文档中包含了一个关于微服务的特定部分,我们强烈建议您在处理微服务体系结构时熟悉它。

当Docker集群使用与Docker机器相同的API时,在云中部署微服务体系结构与在本地机器上部署微服务架构是完全一样的。请跟随我们的Docker-compose文档[后续章节],了解更多关于使用Docker与JHipster的组合。

【未完持续】

猜你喜欢

转载自blog.csdn.net/u010209217/article/details/79544357