在本教程中,你将创建一个 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 generator-jhipster@6.9.0使用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
当它询问你是否覆盖文件时,只需键入atooverwrite 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/splitio@10.14.2
更新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 = ;@Componentexport 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}} .menu-button { margin-bottom: 10px; width: 100%; } .order { text-align: center; padding:20px; } .title h1, .title p { text-align: center; }```请注意,该文件使用 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 的完整源代码。
本文由博客群发一文多发等运营工具平台 OpenWrite 发布