在本教程中,你将创建一个 CoffeeBot 应用程序。该应用程序就像机器人咖啡机的控制器。遗憾的是,它实际上不会为你提供咖啡,但它将演示大量有用的编程技术。该应用程序将有一个 Vue.js 客户端和一个 Spring Boot 资源服务器。它将使用 JHipster 进行引导,节省大量时间并演示一些最佳实践,包括在客户端和服务器中自动配置端到端 JWT 身份验证。你还将使用 Split 来展示如何分别使用 Split 的 Javascript SDK 和 Java SDK 在运行时动态地使用功能标志来管理前端和后端的功能集。
该应用程序本身提供饮料。服务器维护着一份饮料清单,主要是咖啡,附有尺寸、价格和名称。为简单起见,饮料列表将使用内存中的开发数据库进行保存,但如果需要,可以轻松地重新配置以实现持久性。客户端从服务器请求饮料列表,如果用户通过身份验证,则传递任何身份验证数据。
客户端接收此列表并将饮料列表显示为按钮列表。第二次分割纯粹与客户有关。添加了一项新功能:能够在饮料中添加奶油。正如你可以想象的那样,考虑到人们对咖啡中奶油的喜爱程度,虚拟骚乱已经开始要求此功能。人们在叫嚷。但经理们希望在广泛发布之前确保奶油功能正常工作(CoffeeBot 有时会失灵)。因此,根据经过身份验证的用户,添加奶油的能力被切换。你可以在这个链接:
看到如何使用 Split 的 Javascript SDK 来控制客户端的奶油功能,以及如何使用 Split 的 Java SDK 来控制服务器的饮料列表
Java + Vue.js 教程依赖项
Java:我在本教程中使用了 Java 12。你可以访问AdaptOpenJdk 网站下载并安装 Java 。或者你可以使用SDKMAN甚至Homebrew等版本管理器。
Node:按照 Node网站上的说明安装 Node 。本教程是使用 Node 12.14.0 编写的。
JHipster:一旦安装了 Java 和 Node,就可以安装 JHipster。按照其网站上的说明进行操作(如果出现问题,有助于排除故障)或只需运行此命令:npm install -g [email protected]使用npm.
拆分:如果你还没有免费的拆分帐户,请注册一个。这就是实现功能标志的方式。
使用 JHipster 引导你的 Spring Boot 应用程序
要创建示例 CoffeBot 应用程序,你将使用 JHipster。正如其网站上所述,“JHipster 是一个快速生成、开发和部署现代 Web 应用程序和微服务架构的开发平台。” 它允许你快速启动具有各种前端和服务器配置的基于 Java 的项目。本教程将使用 Vue.js。
JHipster 的优点之一是它为你创建了一个包含 Java 服务器应用程序和 Vue.js 前端应用程序的组合项目。它还包括将创建数据模型实体和 Web 服务控制器的生成器。它做了很多事情并创建了很多文件。如果你对这些平台相对陌生,那么项目结构可能看起来有点令人难以承受,但他们在网站上记录了所有内容,做得很好。他们布置的项目遵循当前的最佳实践,因此它是一个很好的学习工具。
打开 shell 并为你的项目创建一个根目录,例如 CoffeeBotApp.
导航到该目录。你将在此处生成项目文件。
通常,当你运行 JHipster 时,它会询问你许多有关你正在引导的项目的问题。但是,为了简化事情,你将使用此.yo-rc.json文件来预先配置项目,从而绕过询问。
在根项目目录中,创建一个.yo-rc.json包含以下内容的文件。此配置的一些亮点是:
applicationType:整体应用程序:典型的标准应用程序(本质上不是微服务)
baseName : coffeebot– 应用程序的名称
packageName : com.split.coffeebot– 基础 Java 包
authenticationType : jwt– JSON Web 令牌身份验证
devDatabaseType : h2Memory– 开发数据库使用内存中的 H2 数据库,该数据库不会跨会话持久化
clientFramework : vue– 使用Vue.js作为前端客户端框架
SkipFakeData : true– JHipster 默认情况下会为数据模型生成一组随机的假数据,我们希望在本教程中跳过这些数据
有很多选择。请参阅文档以深入了解这一点。
{
"generator-jhipster": {
"promptValues": {
"packageName": "com.split.coffeebot"
},
"jhipsterVersion": "6.9.0",
"applicationType": "monolith",
"baseName": "coffeebot",
"packageName": "com.split.coffeebot",
"packageFolder": "com/split/coffeebot",
"serverPort": "8080",
"authenticationType": "jwt",
"cacheProvider": "ehcache",
"enableHibernateCache": true,
"websocket": false,
"databaseType": "sql",
"devDatabaseType": "h2Memory",
"prodDatabaseType": "mysql",
"searchEngine": false,
"messageBroker": false,
"serviceDiscoveryType": false,
"buildTool": "gradle",
"enableSwaggerCodegen": false,
"jwtSecretKey": "ZDg4ZjkzMDJkNWQ4YWJlMjUxOTY3YjE1MDNjY2ZkMzJjYWQwYjJiOTkyMWQ3YTE5ZTgwNWY3Y2E1ZDg0OWViZjM0Nzg1NDE3MjNlMGY1MDBkNTg4YWU1MmZmNTU1ZGEzOTJiMTVlMWZjZDc5NDUyMTlmZmRmYTU0NDJjMDdiODA=",
"embeddableLaunchScript": false,
"useSass": true,
"clientPackageManager": "npm",
"clientFramework": "vue",
"clientTheme": "none",
"clientThemeVariant": "",
"creationTimestamp": 1601147759112,
"testFrameworks": [],
"jhiPrefix": "jhi",
"entitySuffix": "",
"dtoSuffix": "DTO",
"otherModules": [
{
"name": "generator-jhipster-vuejs",
"version": "1.8.0"
}
],
"enableTranslation": false,
"blueprints": [
{
"name": "generator-jhipster-vuejs",
"version": "1.8.0"
}
],
"skipFakeData": true
}
}
通过运行以下命令(在包含该文件的根项目目录中.yo-rc.json)创建入门应用程序。
jhipster
当 JHipster 创建项目时,你将看到大量控制台输出。它应该以以下行结束。
INFO! Congratulations, JHipster execution is complete!
代码语言: Swift (斯威夫特)
JHipster 已经创建了一个 Git 存储库并进行了初始提交。此时,你可以通过打开两个 shell(一个用于客户端,一个用于服务器)并运行以下命令来运行入门应用程序。
Spring Boot 服务器:
./gradlew
Vue.js 客户端:
npm start
生成 Spring Boot 数据模型
现在你想要使用 JHipster 生成数据模型或实体。它们定义了将存储在数据库中并由 REST 服务提供服务的数据结构。当你使用 JHipster 的生成器创建实体时,JHipster 和 Spring 会为你完成许多出色的幕后工作。它创建表示数据结构的 Java 类,并使用允许将数据保存到数据库的 JPA 注释进行注释。它还创建一个实现创建、读取、更新和删除 (CRUD) 功能的资源文件,该文件会自动受到所选身份验证方案(在我们的示例中为 JWT)的保护。
在前端,生成器创建必要的文件,允许你与实体的资源服务器进行交互,以及前端文件来创建、更新和检查持久数据实体(要访问它,你必须以管理员用户身份登录)。
在项目根目录中,创建一个新文件:entities.jdl. 该文件定义了一种具有四个属性的实体类型,以及该实体中使用的枚举类型。
enum DrinkSize {
Small,
Medium,
Large,
XLarge,
XXLarge
}
entity Drink {
name String required,
size DrinkSize required,
caffeineMilligrams Integer required,
priceDollars Integer required,
}
通过运行生成实体文件:
jhipster import-jdl entities.jdl
当它询问你是否覆盖文件时,只需键入ato overwrite this and all others。
现在是运行入门应用程序并探索引导功能的好时机。请记住,你需要运行两个不同的进程。
Spring Boot Java 服务器`
./gradlew
Vue.js 客户端(你可能需要等待一分钟左右服务器才能完成运行):
npm start
客户端应用程序应自动打开。如果没有,请打开 http://localhost:9000
使用默认凭据以管理员用户身份登录admin:admin。查看“管理”菜单下的所有功能。另请查看“实体”菜单。你可以在此处查看、添加和更新你创建的实体。在我们的例子中,这是Drink实体,它有四个属性:name、size、caffeine mgs和Price Dollars。
Vue.js 客户端使用 TypeScript 和类组件,并将模板和组件声明拆分为两个单独的文件。如果你习惯于更传统的.vue单文件结构,一开始这可能看起来有点奇怪,但大多数差异都是不言自明的。如果你需要帮助,请查看文档中的官方页面以获取更多信息。
将功能标志添加到你的 Spring Boot Java 服务器
你将使用 Split 在客户端和服务器上实现功能标志。你应该已经注册了一个免费帐户(如果没有,请立即注册)。目前,你要将 Java Split SDK 集成到 Spring Boot 应用程序中。我将引导你完成此过程,但如果你需要更多信息或遇到麻烦,请查看他们的 Java SDK 文档。
首先,将Split依赖添加到build.gradle文件中(在项目根目录中)。该build.gradle文件包含大量内容。只需在末尾附近添加以下行,就在开始的注释行上方//jhipster-needle-gradle-dependency。
dependencies {
....
compile 'io.split.client:java-client:4.0.1'
//jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
}
你将需要你的 Split API 密钥。打开你的拆分仪表板。通过转到仪表板左上角的方形工作区图标(可能显示默认为DE)找到 API 密钥,单击它,然后单击Admin Settings。单击左侧面板中“工作区设置”下的“API 密钥”。
你将看到已创建四个 API 密钥,其中两个用于生产,两个用于暂存。服务器端 SDK 使用和客户端 Javascript 使用有不同的密钥。SDK和staging-default密钥是你稍后需要的。
将 API 密钥添加到配置文件的末尾application.yml。
src/main/resources/application.yml
#application:
split:
api-key: 代码语言: 小黄瓜 (gherkin )
我只是指出,在这里你将其添加到全局配置文件中,但在更实际的用例中,你可能会使用两个不同的 API 密钥,一个用于暂存和生产,分别将它们添加到 和application-dev.yml文件application-prod.yml中。
创建一个名为 的 Java 文件,SplitConfig.java该文件将在 Spring Boot 应用程序中配置 Split 客户端。它创建了一个可用于依赖注入的 Spring Bean,并且由于 Bean 的默认行为是创建一个单例实例,因此这与 Split 自己的指导一致,建议只创建一个客户端实例。
src/main/java/com/split/coffeebot/config/SplitConfig.java
@Configurationpublic class SplitConfig {
@Value("#{ @environment['split.api-key'] }")
private String splitApiKey;
@Bean
public SplitClient splitClient() throws Exception {
SplitClientConfig config = SplitClientConfig.builder()
.setBlockUntilReadyTimeout(1000)
.enableDebug()
.build();
SplitFactory splitFactory = SplitFactoryBuilder.build(splitApiKey, config);
SplitClient client = splitFactory.client();
client.blockUntilReady();
return client;
}
}
你还需要向DrinkRepository. 它JPARepository为你提供了相当多的功能,无需任何自定义,但在这个应用程序中,你将需要一个自定义方法,该方法允许你从标准方法中排除一些饮料findAll()。该方法findByNameNotIn()是一个JPA查询方法,其语法和实现由Spring Boot提供。你所要做的就是定义方法以使其可供使用。有关更多信息,请参阅Spring Data JPA 查询方法的文档。
src/main/java/com/split/coffeebot/repository/DrinkRepository.java
@SuppressWarnings("unused") @Repository public interface DrinkRepository extends JpaRepository {
List findByNameNotIn(Collection names);
}
现在创建一个CoffeeBotResource.java文件,其中包含 CoffeeBot 应用程序的业务逻辑和 REST 端点。
src/main/java/com/split/coffeebot/web/rest/CoffeeBotResource.java
package com.split.coffeebot.web.rest;
…
@RestController
@RequestMapping("/api/coffee-bot")public class CoffeeBotResource {
private final Logger log = LoggerFactory.getLogger(CoffeeBotResource.class);
SplitClient splitClient;
DrinkRepository drinkRepository;
public CoffeeBotResource(SplitClient splitClient, DrinkRepository drinkRepository) {
this.splitClient = splitClient;
this.drinkRepository = drinkRepository;
}
private Drink makeDrink(String name, DrinkSize size, Integer caffeineMg, Integer price) {
Drink drink = new Drink();
drink.setCaffeineMilligrams(caffeineMg);
drink.setName(name);
drink.setSize(size);
drink.setPriceDollars(price);
return drink;
}
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
drinkRepository.save(makeDrink("Water", DrinkSize.Small, 0, 1));
drinkRepository.save(makeDrink("Soda", DrinkSize.Medium, 30, 3));
drinkRepository.save(makeDrink("Coffee", DrinkSize.XLarge, 50, 5));
drinkRepository.save(makeDrink("Coffee", DrinkSize.Small, 30, 3));
drinkRepository.save(makeDrink("Coffee", DrinkSize.Medium, 40, 3));
drinkRepository.save(makeDrink("Latte", DrinkSize.Large, 100, 8));
drinkRepository.save(makeDrink("Latte", DrinkSize.Small, 80, 6));
drinkRepository.save(makeDrink("Latte", DrinkSize.Medium, 60, 5));
}
@GetMapping("/list-drinks")
public List listDrinks() {
Optional userName = SecurityUtils.getCurrentUserLogin();
String treatment = splitClient.getTreatment(userName.get(),"drink-types");
if (treatment.equals("on")) {
return drinkRepository.findAll();
}
else {
return drinkRepository.findByNameNotIn(Arrays.asList("Latte", "Soda"));
}
}
}
和方法作为辅助方法,用于在应用程序启动时创建一些示例makeDrink()数据onApplicationEvent()(请记住,它使用的是内存数据库,不会在会话之间保留任何数据)。
该类使用 Spring 的依赖注入来使两个对象可用:DrinkRepository,这是自动创建的接口,定义应用程序如何操作实体(饮料);,SplitClient它是负责与 Split 通信并获取给定密钥和治疗名称的治疗的客户端。
你很快就会创建这种治疗方法。现在,请注意该getTreatment()方法至少需要两个参数。一种是文本键,它是任意字符串值,通常是用户名、帐户 ID 或用于区分用户的另一个唯一键。另一个是分割名称,它指定使用哪种处理来进行分割。
专业提示:可选的第三个getTreatment参数(我们在这里不会使用)是一个属性 映射对象,包含名称-值对中的用户属性。即使是敏感的用户数据也可以在此映射中传递,因为这些数据都不会发送到斯普利特的云。相反,属性映射会在本地内存中与你在拆分 UI 中输入的定位规则进行比较。更多内容请参见 Split SDK 文档:使用属性映射进行自定义定位。
如果治疗是on,它会返回所有可用的饮料。如果处理不是on(off或control或任何其他值),则返回除Latte和之外的所有饮料Soda。这演示了一种基于拆分来分叉代码的简单方法。更复杂的分割和治疗用例是可能的。
在你搬家之前,最后一项改变。打开SecurityConfiguration文件并允许api/coffee-bot资源路径上的所有流量。这将允许匿名用户获得饮料清单。
你要添加这一行:
.antMatchers("/api/coffee-bot/**").permitAll() 代码语言: Bash (bash )
至configure(HttpSecurity http)方法。立场很重要。该行需要添加到该.antMatchers("/api/**").authenticated()行之前。
src/main/java/com/split/coffeebot/config/SecurityConfiguration.java
@Overridepublic void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
...
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/account/reset-password/init").permitAll()
.antMatchers("/api/account/reset-password/finish").permitAll()
.antMatchers("/api/coffee-bot/**").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
...
// @formatter:on
}
创建特征标志处理
如果你对治疗和 Split 不熟悉,你可能需要阅读Split 网站上的入门信息。简而言之,分割定义了一个决策点,一个标志,可以在代码中使用它来修改呈现给一组用户的功能。键(以及可选的属性映射)是根据拆分中定义的规则确定标志状态的值。这个决定是在运行时调用该方法时做出的。SplitClientgetTreatment()
在我们非常简单的示例中,你将创建一个名为Drink-types的拆分。这个分割将是一个简单的开/关分割,就像一个布尔标志(除了开和关之外,还可以有多个值)。分割将默认为off,但on如果admin用户在场,则分割将变为 。这是一个非常幼稚的例子。举个例子,在生产中,你可以根据用户群的一部分来定义这种划分,以在细分中推出新功能;或者,你可以先将功能仅提供给公共 Beta 测试人员,然后再将其发布给整个用户群。
打开拆分仪表板。你应该位于默认工作区中。
单击左侧的“拆分” 。
单击蓝色的“创建拆分”按钮。
为分割命名:drink-types。你可以将其余部分留空。
单击创建。
从环境下拉列表中选择暂存-默认。
单击添加规则按钮。
请注意,在定义处理部分下,定义了两个值:on和off。对于我们的用例来说,这非常棒。
在“创建单个目标”下,单击“添加目标”按钮。添加名为 的用户admin。这意味着用户admin将受到治疗on。
单击面板右上角的保存更改。
单击下一个面板上的“确认”以确认更改。
更新 Vue.js 客户端应用程序
客户端代码使用axios向资源服务器发出请求。你还需要安装 Split 模块依赖项。从项目根目录添加依赖项。
npm install --save axios @splitsoftware/[email protected]
更新home.component.ts文件以创建 CoffeeBot 应用程序。你需要为下面代码中的位置添加Javascript – staging-default键。const SPLIT_AUTH_KEY
src/main/webapp/app/core/home/home.component.ts
import Component from 'vue-class-component';
import { Inject, Vue, Watch } from 'vue-property-decorator';
import LoginService from '@/account/login.service';
import { SplitFactory } from '@splitsoftware/splitio';
import { IClient } from '@splitsoftware/splitio/types/splitio';
import axios from 'axios';const SPLIT_AUTH_KEY = ;
@Component
export default class Home extends Vue {
@Inject('loginService')
private loginService: () => LoginService;
private splitClient: IClient = null;
// our list of drinks
private drinks = [];
// holds the drink that is the current order
private currentOrder = null;
// cream or no cream?
private withCream = false;
// the current Split.io treatment
private treatment = null;
public openLogin(): void {
this.loginService().openLogin((this).$root);
}
public get authenticated(): boolean {
return this.$store.getters.authenticated;
}
public get username(): string {
return this.$store.getters.account ? this.$store.getters.account.login : '';
}
async getTreatment() {
// create a configured SplitFactory
const splitFactory = SplitFactory({
core: {
authorizationKey: SPLIT_AUTH_KEY, // your Split.io auth key
key: this.username, // identifier for this treatment (username in this case)
trafficType: 'user'
},
startup: {
readyTimeout: 1.5 // 1.5 sec
}
});
// create the split client (NOT READY TO USE YET)
this.splitClient = splitFactory.client();
// block untli the client is ready
this.splitClient.on(this.splitClient.Event.SDK_READY, function() {
// client is ready, get the treatment
this.treatment = this.splitClient.getTreatment('drink-types');
}.bind(this));
}
// triggered when username changes to update list
// of drinks and Split.io treatment
@Watch('username')
async usernameChanged(newVal: string, oldVal: String) {
// get treatment from split.io
await this.getTreatment();
// call the REST service to load drinks
await this.loadDrinks();
// clear the current order
this.currentOrder = null;
}
async loadDrinks() {
const response = await axios.get('http://localhost:8080/api/coffee-bot/list-drinks');
console.log(response);
if (response && response.status === 200) {
this.drinks = response.data;
}
else {
this.drinks = [];
}
}
async mounted() {
await this.getTreatment();
await this.loadDrinks();
}
beforeDestroy() {
this.splitClient.destroy();
}
}
该组件的身份验证部分是通过 JHipster 引导程序免费提供的。通过该方法从 Spring Boot 资源服务器加载数据loadDrinks(),该方法只是将饮料存储在本地数组中,巧妙地称为drinks. 当安装组件和用户更改时(因为可用的饮料取决于治疗,而治疗由用户决定),则会调用此方法。你可能会注意到此方法没有传递用户名。这是因为用户名会自动通过 JWT(JSON Web 令牌)传递到服务器,由 Spring Security 处理,身份验证代码由 JHipster 引导。
另一个重要的函数是usernameChanged()方法,当属性更改时调用该方法username。每次有新用户时,都需要创建新的 Split 客户端并重新加载处理。你还需要从服务器重新加载饮料。这个方法处理所有这些。
请注意此处的一般流程。首先,SplitFactory使用分割身份验证密钥和新用户名配置 a。该SplitFactory实例用于创建SplitClient实例。然而,客户此时不一定准备好。该代码会阻塞,直到SDK_READY事件被触发(参见下面的代码),然后才尝试从 获取处理SplitClient,否则它将仅返回control处理。
// block until SDK is readythis.splitClient.on(this.splitClient.Event.SDK_READY, function () {
// ready now, so get treatment
this.treatment = this.splitClient.getTreatment('drink-types');
}.bind(this))
home.vue现在更新与该组件对应的模板文件。
src/main/webapp/app/core/home/home.vue
CoffeeBot!
Drink coffee!
{{username ? username : "anonymous"}}, treatment: {{treatment}}
{{drink.name}} {{drink.size}} - ${{drink.priceDollars}}
Add Cream - FREE
Remove Cream
Current order:
{{currentOrder.name}} {{currentOrder.size}} {{withCream ? "(w/ cream)" : ""}} - ${{currentOrder.priceDollars}}
请注意,该文件使用 Vue 的条件语法根据处理状态有条件地渲染添加和删除奶油按钮。在这种情况下,即使组件中有一些与添加和删除奶油功能相关的代码未切换,这就是我们管理功能状态所需要做的全部工作。
Add Cream - FREE
Remove Cream
尝试完成的 Spring Boot + Vue.js 教程应用程序
你现在可以尝试完成的应用程序。启动或重新启动服务器和客户端。你可能希望在启动客户端之前让服务器完成启动。
Spring Boot Java 服务器:
./gradlew
Vue.js 客户端:
npm start
客户端应用程序应自动打开。如果没有,请打开 http://localhost:9000
当你第一次加载应用程序时,你会看到用户是anonymous并且治疗是对照治疗。
使用默认管理员凭据 ( ) 登录admin:admin,你将看到扩展的饮料列表。当你添加一个饮料时,你会看到一个名为"Add Cream"的按钮。该按钮的可见性或功能可能受到名为"split treatment"的处理方式的控制。换句话说,根据某种分割处理的规则或逻辑,决定了是否显示或启用这个"Add Cream"按钮。
注销并以默认用户 ( user:user) 身份登录,你将看到处理方式,并且off你将获得与该用户相同的饮料列表anonymous。此外,你无法选择添加奶油。
了解有关 Spring Boot、功能标志和生产中测试的更多信息
在本教程中,你创建了一个全栈应用程序,其中包括 Vue.js 客户端和 Spring Boot 资源服务器。Vue.js 客户端使用 TypeScript 进行更现代、无错误的开发(因为如果使用得当,类型检查可以大大减少运行时错误)。Spring Boot 服务器使用 Spring Security 和 Spring JPA 等技术来快速轻松地定义数据模型(或实体),将该实体的实例保存到数据库,并在 REST 接口中提供该数据。
客户端和服务器都使用 JWT 身份验证来保护安全。Split用于实现功能标志,在服务器端使用Java SDK实现拆分,在客户端使用Javascript SDK实现拆分。
所有这一切都是使用 JHipster 引导的,这使得使用现代最佳实践和技术启动新的全栈项目变得非常容易。
你可以在Split 的示例 GitHub上找到所有设置的 JHipster 的完整源代码。