使用Spring Boot,JHipster和React构建照片库PWA

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

从本质上讲,React只是一个UI工具包,即ala GWT,但是它周围有一个非常健康的生态系统,它提供构建踢踏式渐进式Web应用程序(PWA)所需的一切。 PWA很酷,因为如果操作正确,它们可以为您的用户提供类似本机的体验,允许他们安装您的应用程序,并在脱机时使用它。

但是,“为什么要使用React?” 您现在可能会问自己是什么,对吗? 好吧,您可能听说过Angular可以成为希望学习JavaScript的Java开发人员的入门药物。 如果您是一位熟悉Angular的经验丰富的Java开发人员,那么您很可能会从AngularJS开始。 AngularJS具有与Java MVC框架类似的概念,例如控制器,服务和指令(我相信它们类似于JSP标签恕我直言)。 如果您仍在进行Angular开发,则可能在此过程中学习了TypeScript。 您喜欢TypeScript,因为它具有Java之类的类型,并且它也是一种非常不错的语言!

我敢打赌,如果您已经了解Angular,那么您可能想了解React的主要竞争对手。 总是有几种编写Web应用程序的方法,而React提供了一种完全不同的方法来执行它,您也可以将TypeScript与它一起使用!

在这篇文章中,我将向您展示如何构建一个安全的PWA,该PWA可以上载和处理图像,将它们显示在类似Flickr的网格中,并使用Spring Boot作为其后端。

React和Spring Boot入门

开始使用React的最简单方法之一就是使用Create React App (CRA)。 您在本地安装它,然后运行create-react-app $projectName生成具有最小依赖性的框架React应用程序。 它使用秘密的webpack构建项目,启动Web服务器并运行其测试。

Spring Boot有一个类似的工具,称为Spring Initializr 。 Spring Initializer与CRA有所不同,因为它是您用来创建应用程序的网站(和API)。

这两个工具都值得研究,您可以通过阅读我的《 使用Spring Boot和React进行Bootiful开发》教程来学习如何使用它们创建基本应用。

今天,我将向您展示如何使用React和Spring Boot为照片构建CRUD应用程序。 但是,我要作弊。 我将使用JHipster而不是从头开始构建所有内容。 JHipster是一个应用程序生成器,最初仅支持Angular和Spring Boot。 在其5.0版本中,它增加了对React,webpack 4和Spring Boot 2的支持。

JHipster附带了每个应用程序都需要的许多功能,包括身份验证/授权,单元和端到端测试支持,以及使其易于部署到云的工具。

JHipster 5入门

要开始使用JHipster,你需要有互联网连接和Node.js的安装。 该项目建议您使用最新的LTS(长期支持)版本,在撰写本文时为8.3.11。 您可以使用npm,但如果安装了JHipster,它将使用Yarn 。 要运行该应用程序,您需要安装Java 8 。 如果安装了Git,JHipster将在创建项目后自动提交项目,并允许您在版本之间进行升级。

运行以下命令来安装JHipster:

npm i -g generator-jhipster@5.0.1

要使用JHipster创建图库应用,请创建目录并在其中运行jhipster

mkdir gallery
cd gallery
jhipster

JHipster询问有关您要创建的应用程序类型以及要包括哪些技术的许多问题。 下表显示了您要进行的选择:

回答
应用类型? Monolithic application
名称? gallery
Java包名称? com.okta.developer
使用JHipster注册表? No
认证类型? OAuth 2.0 / OIDC
数据库类型? SQL
生产数据库? PostgreSQL
开发数据库? H2 with disk-based persistence
使用Spring缓存? Yes, with Ehcache
使用Hibernate 2级缓存? Yes
Maven还是Gradle? Maven
其他技术? <blank>
客户框架? React
启用S​​ASS支持? No
启用i18n? Yes
应用程序的母语? English
其他语言? French
其他测试框架? Protractor
安装其他发电机? No

回答完所有这些问题后,JHipster将在当前目录中创建大量文件,然后运行yarn (或npm install )以安装package.json指定的所有依赖项。

验证一切适用于量角器和Keycloak

当您选择OAuth 2.0和OIDC进行身份验证时,用户将存储在应用程序外部,而不是存储在应用程序中。 这意味着您需要有一个身份提供程序(IdP),用于存储用户并允许您的应用检索有关他们的信息。 默认情况下,JHipster随附用于Docker Compose的Keycloak文件。 默认的用户和组集在启动时被导入,并且为您的JHipster应用程序注册了一个客户端。

这是您应用程序的src/main/docker目录中的keycloak.yml样子:

version: '2'
services:keycloak:image: jboss/keycloak:4.0.0.Finalcommand: ["-b", "0.0.0.0", "-Dkeycloak.migration.action=import", "-Dkeycloak.migration.provider=dir", "-Dkeycloak.migration.dir=/opt/jboss/keycloak/realm-config", "-Dkeycloak.migration.strategy=OVERWRITE_EXISTING", "-Djboss.socket.binding.port-offset=1000"]volumes:- ./realm-config:/opt/jboss/keycloak/realm-configenvironment:- KEYCLOAK_USER=admin- KEYCLOAK_PASSWORD=adminports:- 9080:9080- 9443:9443- 10990:10990

要启动Keycloak,您需要安装Docker Compose 。 然后在终端窗口中运行以下命令:

docker-compose -f src/main/docker/keycloak.yml up

您可以使用Maven在一个终端中启动应用程序,从而从一开始就验证一切正常:

./mvnw

然后在另一个终端上运行所有的量角器测试:

yarn e2e

如果您的环境设置正确,您将看到类似以下的输出:

yarn run v1.7.0
$ protractor src/test/javascript/protractor.conf.js
(node:97048) [DEP0022] DeprecationWarning: os.tmpDir() is deprecated. Use os.tmpdir() instead.
[15:36:33] W/configParser - pattern ./e2e/entities/**/*.spec.ts did not match any files.
[15:36:33] I/launcher - Running 1 instances of WebDriver
[15:36:33] I/direct - Using ChromeDriver directly...Account✓ should fail to login with bad password✓ should login with admin account (2720ms)Administration✓ should load metrics✓ should load health✓ should load configuration✓ should load audits✓ should load logs7 passing (10s)[15:36:45] I/launcher - 0 instance(s) of WebDriver still running
[15:36:45] I/launcher - chrome #01 passed
✨  Done in 13.67s.

在Keycloak中为您的React + Spring Boot App启用用户注册

将OIDC身份验证与JHipster一起使用时,似乎缺少的功能之一是用户注册。 如果您使用会话或JWT身份验证,则主页上会提供一个注册链接。 使用OIDC,您需要在IdP中启用它。 对于Keycloak,您可以通过导航到http://localhost:9080并单击Administration Console来实现 。 使用admin/admin登录,然后单击“ 登录”选项卡。 该屏幕允许您启用忘记密码,记住我以及通过电子邮件进行验证。

照片库

启用此设置后,您将在Keycloak的登录表单上看到“ 注册”链接。

照片库

您需要在Keycloak中为新用户配置默认角色。 导航到角色 ,然后单击默认角色选项卡。 选择ROLE_USER ,然后单击“ 添加所选内容” 。 要配置默认组,请转到“ 组” >“ 默认组” 。 单击Users然后添加 。 添加默认组是必要的,因为JHipster希望用户将ROLE_USERROLE_ADMIN组(或角色)作为其ID令牌声明的一部分。

保存用于JPA关系的用户数据

我添加到JHipster的功能之一是我喜欢称之为“ 保存用户快照” 。 使用JPA时,很高兴能够与JHipster的User实体建立关系。 这样一来,您就可以说出“此用户拥有此相册”之类的字眼,并根据该信息限制访问权限。

该功能默认情况下处于启用状态,其工作方式如下:

  1. 登录后, /api/account发出请求。
  2. AccountResource.javagetAccount()方法被映射到该端点,并UserService#getUserFromAuthentication()UserService#getUserFromAuthentication()以提取用户的详细信息。
  3. getUserFromAuthentication()方法从Spring Security中提取用户的详细信息,将ID令牌中的组/角色映射到授权机构,并在数据库中添加/更新用户。

此功能使您可以与User实体创建关系。 唯一的缺点是当您具有具有用户关系的实体时,“用户”下拉列表将仅包含已登录您的应用程序的用户。

将您的身份提供者更改为Okta

JHipster利用Spring Security的OAuth 2.0支持来配置应从中获取用户信息的IDP。 将Spring Security与Spring Boot结合使用时,可以在属性文件中配置大多数配置设置。 您甚至可以使用环境变量覆盖属性。

要从Keycloak切换到Okta(或其他任何IdP),您可以覆盖默认属性(对于Spring Security OAuth)。

为什么用Okta代替Keycloak?

Keycloak在开发中表现出色,Okta具有免费的多因素身份验证,电子邮件支持以及出色的生产性能。 您可以在developer.okta.com/pricing上查看其他免费功能和透明价格。

若要查看其工作原理,请创建具有以下属性的~/.okta.env文件:

export SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI="https://{yourOktaDomain}/oauth2/default/v1/token"
export SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI="https://{yourOktaDomain}/oauth2/default/v1/authorize"
export SECURITY_OAUTH2_RESOURCE_USER_INFO_URI="https://{yourOktaDomain}/oauth2/default/v1/userinfo"
export SECURITY_OAUTH2_CLIENT_CLIENT_ID="{clientId}"
export SECURITY_OAUTH2_CLIENT_CLIENT_SECRET="{clientSecret}"

您需要在Okta中创建一个新的OIDC客户端,然后填写变量,然后才能运行。 完成此操作后,可以运行以下命令来设置这些环境变量。

source ~/.okta.env

重新启动您的应用程序, -您现在正在使用Okta!

如果您不知道如何在Okta上设置OIDC应用程序,以下是一个简短的摘要。

在Okta上设置OIDC应用

登录到您的1563开发者帐户(或者注册 ,如果你没有一个帐户)并导航到应用程序 > 添加应用程序 。 单击“ Web” ,然后单击“ 下一步” 。 给应用程序起一个您会记住的名称,并指定http://localhost:8080/login作为登录重定向URI。 单击“完成”,并记下客户端ID和密码。 您需要在一分钟内将它们复制/粘贴到文件中。

创建一个ROLE_ADMINROLE_USER组(“ 用户” >“ 组” >“ 添加组” )并将用户添加到其中。 我建议将您注册时使用的帐户添加到ROLE_ADMIN并创建一个新用户(“ 用户” >“ 添加人” )以添加到ROLE_USER

导航到API > 授权服务器 ,然后单击一个名为default的名称进行编辑。 点击索赔标签,然后添加索赔 。 将其命名为“角色”,并将其包含在ID令牌中。 将值类型设置为“ Groups”,并将过滤器设置为.*的正则表达式。 单击创建以完成该过程。

什么是Okta?

简而言之,我们使身份管理比您通常使用的更加轻松,安全和可扩展。 Okta是一项云服务,允许开发人员创建,编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。 我们的API使您能够:

  • 验证和授权用户
  • 存储有关您的用户的数据
  • 执行基于密码的社交登录
  • 通过多因素身份验证保护您的应用程序
  • 以及更多! 查看我们的产品文档

想要每月免费提供一千个用户吗? 注册一个免费的开发人员帐户 ,完成后再回来,这样您就可以了解有关使用Spring Boot 2.0和JHipster构建React PWA的更多信息!

在Okta启用自助服务注册

要在Okta中启用自助服务注册,您需要从Okta Developer仪表板导航到Classic UI。 在屏幕的左上角有一个可以在两者之间切换的链接。

照片库

然后导航至目录 > 自我注册 ,然后单击启用注册 。 将默认组设置为ROLE_USER ,将默认重定向设置为以http://localhost:8080作为其值的自定义URL,然后单击保存

注意:如果收到错误消息'http://localhost:8080' is not a valid redirect URI ,那是因为需要在安全性 > API > 可信来源下将http://localhost:8080为可信重定向。 进行更改后,导航至目录 > 自助服务注册,然后编辑设置以再次配置自定义URL。 这次应该可以了。

照片库

提示:部署应用程序后,您需要将默认重定向更改为生产URL。

Okta自定义选项

除了允许自我注册外,Okta还允许您自定义其登录屏幕的外观,以及使用自定义域和电子邮件。 您可以在“ 登录窗口小部件指南”中有关此内容的信息。

您还可以尝试使用我们方便的实时窗口小部件页面实时自定义窗口小部件 。

创建实体以允许在您的图库上使用CRUD

我花了很多时间讨论如何保护您的应用程序,现在让我们开始构建它! JHipster具有JDL(JHipster域语言)功能,可让您在应用程序中对数据建模并从中生成实体。 您可以使用其JDL Studio功能在线完成此操作,并在完成后将其保存在本地。

我为此应用创建了一个数据模型,该数据模型具有一个AlbumPhotoTag实体,并在它们之间建立了关系。 下面是JDL Studio的屏幕截图。

照片库

为了方便起见,您可以复制下面的JDL并将其保存在项目根目录下的gallery.jh文件中。

entity Album {title String required,description TextBlob,created Instant
}entity Photo {title String required,description TextBlob,image ImageBlob required,height Integer,width Integer,taken Instant,uploaded Instant
}entity Tag {name String required minlength(2)
}relationship ManyToOne {Album{user(login)} to User,Photo{album(title)} to Album
}relationship ManyToMany {Photo{tag(name)} to Tag{photo}
}paginate Album with pagination
paginate Photo, Tag with infinite-scroll

您可以使用以下命令生成实体和CRUD代码(用于Spring Boot的Java;用于React的TypeScript和JSX):

jhipster import-jdl gallery.jh

出现提示时,键入a以允许覆盖现有文件。

此过程将创建Liquibase changelog文件(以创建数据库表),实体,存储库,Spring MVC控制器以及创建,读取,更新和删除数据对象所需的所有React代码。 它甚至会生成Jest单元测试和量角器端到端测试!

该过程完成后,您可以重新启动应用程序(Ctrl + C ./mvnw进程并重新启动它),然后再次运行yarn e2e以快速确认所有内容./mvnw正确生成。

现在,您可以看到JHipster非常强大。 它认识到您具有ImageBlob类型的image属性,并自动创建了需要在数据库中上传和存储图像的管道! 头晕!

在Spring Boot API中添加图像EXIF处理

Photo实体具有一些属性,可以通过从上载的照片读取EXIF(可交换图像文件格式)数据来计算。 您可能会问,如何用Java做到这一点?

值得庆幸的是,Drew Noakes创建了一个元数据提取程序库来完成此任务。 在您的pom.xml添加对Drew库的依赖:

<dependency><groupId>com.drewnoakes</groupId><artifactId>metadata-extractor</artifactId><version>2.11.0</version>
</dependency>

然后,修改PhotoResource#createPhoto()方法以设置上载图像时的元数据。

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import com.drew.metadata.jpeg.JpegDirectory;import javax.xml.bind.DatatypeConverter;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;import java.time.Instant;
import java.util.Date;public class PhotoResource {...public ResponseEntity createPhoto(@Valid @RequestBody Photo photo) throws Exception {log.debug("REST request to save Photo : {}", photo);if (photo.getId() != null) {throw new BadRequestAlertException("A new photo cannot already have an ID", ENTITY_NAME, "idexists");}try {photo = setMetadata(photo);} catch (ImageProcessingException ipe) {log.error(ipe.getMessage());}Photo result = photoRepository.save(photo);return ResponseEntity.created(new URI("/api/photos/" + result.getId())).headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString())).body(result);}private Photo setMetadata(Photo photo) throws ImageProcessingException, IOException, MetadataException {String str = DatatypeConverter.printBase64Binary(photo.getImage());byte[] data2 = DatatypeConverter.parseBase64Binary(str);InputStream inputStream = new ByteArrayInputStream(data2);BufferedInputStream bis = new BufferedInputStream(inputStream);Metadata metadata = ImageMetadataReader.readMetadata(bis);ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);if (directory != null) {Date date = directory.getDateDigitized();if (date != null) {photo.setTaken(date.toInstant());}}if (photo.getTaken() == null) {log.debug("Photo EXIF date digitized not available, setting taken on date to now...");photo.setTaken(Instant.now());}photo.setUploaded(Instant.now());JpegDirectory jpgDirectory = metadata.getFirstDirectoryOfType(JpegDirectory.class);if (jpgDirectory != null) {photo.setHeight(jpgDirectory.getImageHeight());photo.setWidth(jpgDirectory.getImageWidth());}return photo;}...
}

由于要提取信息,因此可以从UI中删除字段并进行测试,以便用户无法设置这些值。

src/main/webapp/app/entities/photo/photo-update.tsx ,添加metadatametadataRows变量,以及在添加照片时将其隐藏和将其更新时为只读的逻辑。 在下面的代码块中找到第一行,并将其替换为以下代码。

const { description, image, imageContentType } = photoEntity;const metadata = (<div><AvGroup><Label id="heightLabel" for="height"><Translate contentKey="galleryApp.photo.height">Height</Translate></Label><AvField id="photo-height" type="number" className="form-control" name="height" readOnly /></AvGroup><AvGroup><Label id="widthLabel" for="width"><Translate contentKey="galleryApp.photo.width">Width</Translate></Label><AvField id="photo-width" type="number" className="form-control" name="width" readOnly /></AvGroup><AvGroup><Label id="takenLabel" for="taken"><Translate contentKey="galleryApp.photo.taken">Taken</Translate></Label><AvInputid="photo-taken"type="datetime-local"className="form-control"name="taken"readOnlyvalue={isNew ? null : convertDateTimeFromServer(this.props.photoEntity.taken)}/></AvGroup><AvGroup><Label id="uploadedLabel" for="uploaded"><Translate contentKey="galleryApp.photo.uploaded">Uploaded</Translate></Label><AvInputid="photo-uploaded"type="datetime-local"className="form-control"name="uploaded"readOnlyvalue={isNew ? null : convertDateTimeFromServer(this.props.photoEntity.uploaded)}/></AvGroup></div>
);
const metadataRows = isNew ? '' : metadata;

然后,在return块中,删除image属性和album属性之间的JSX并将其替换为{metadataRows}

<input id="file_image" type="file" onChange={this.onBlobChange(true, 'image')} accept="image/*" /></AvGroup>
</AvGroup>
{metadataRows}
<AvGroup><Label for="album.title"><Translate contentKey="galleryApp.photo.album">Album</Translate></Label>

src/test/javascript/e2e/entities/photo/photo.spec.ts ,删除在以下字段中设置数据的代码:

photoUpdatePage.setHeightInput('5');
expect(await photoUpdatePage.getHeightInput()).to.eq('5');
photoUpdatePage.setWidthInput('5');
expect(await photoUpdatePage.getWidthInput()).to.eq('5');
photoUpdatePage.setTakenInput('01/01/2001' + protractor.Key.TAB + '02:30AM');
expect(await photoUpdatePage.getTakenInput()).to.contain('2001-01-01T02:30');
photoUpdatePage.setUploadedInput('01/01/2001' + protractor.Key.TAB + '02:30AM');
expect(await photoUpdatePage.getUploadedInput()).to.contain('2001-01-01T02:30');

您还可以在src/test/javascript/e2e/entities/photo/photo-update.page-object.ts删除这些字段的所有getter和setter:

setHeightInput(height) {this.heightInput.sendKeys(height);
}getHeightInput() {return this.heightInput.getAttribute('value');
}setWidthInput(width) {this.widthInput.sendKeys(width);
}getWidthInput() {return this.widthInput.getAttribute('value');
}setTakenInput(taken) {this.takenInput.sendKeys(taken);
}getTakenInput() {return this.takenInput.getAttribute('value');
}setUploadedInput(uploaded) {this.uploadedInput.sendKeys(uploaded);
}getUploadedInput() {return this.uploadedInput.getAttribute('value');
}

停止您的Maven进程,运行yarn webpack:build ,再次启动Maven,然后运行yarn e2e以确保一切仍然正常。 如果您上传使用智能手机拍摄的图像,则高度,宽度和拍摄值均应填充。 如果不是,则很可能您的图像中没有数据。

需要一些带有EXIF数据的样本照片吗? 您可以从Flickr上的相册下载我的1966 VW Bus的图片。

将React照片库添加到您的React PWA中

您已经将元数据提取添加到了后端,但是您的照片仍然显示在列表中,而不是显示在网格中(例如Flickr)。 要解决此问题,您可以使用React Photo Gallery组件。 使用Yarn安装它:

yarn add react-photo-gallery@6.0.28

或npm:

npm i --save-exact react-photo-gallery@6.0.28

注意:我首先尝试使用Leisan Kazberova的 react-photo-feed ,但是在将其添加到我的项目后发现它导致了编译错误。

src/main/webapp/app/entities/photo/photo.tsx ,为Gallery添加导入:

import Gallery from 'react-photo-gallery';

然后在结束</h2>之后,在render()方法和<Gallery>组件中添加一个photoSet变量。

render() {const { photoList, match } = this.props;const photoSet = photoList.map(photo => ({src: `data:${photo.imageContentType};base64,${photo.image}`,width: photo.height > photo.width ? 3 : photo.height === photo.width ? 1 : 4,height: photo.height > photo.width ? 4 : photo.height === photo.width ? 1 : 3}));return (<div><h2 id="photo-heading">...</h2><Gallery photos={photoSet} />...);
}

由于您仅修改了前端代码,因此可以运行yarn start来启动webpack-dev-server实例,该实例代理对后端的请求,并在每次更改任何React文件时自动刷新浏览器(使用Browsersync)。

登录并导航到顶部导航栏中的实体 > 照片 。 您应该可以上传照片,并在列表顶部的漂亮网格中查看结果。

照片库

您还可以在网格中添加“灯箱”功能,以便单击照片并放大。ReactPhoto Gallery文档显示了如何执行此操作。 我已经将其集成到本文的示例中,但是为了简洁起见,这里不会显示代码。 您可以在GitHub上看到添加了Lightbox的最终photo.tsx或所需更改的差异 。

使您的React + Spring Boot App成为PWA

成为PWA需要具备以下三个功能:

  1. 您的应用必须通过HTTPS进行投放
  2. 您的应用必须注册服务工作者,才能缓存请求并脱机工作
  3. 您的应用必须具有包含安装信息和图标的webapp清单

对于HTTPS,您可以为本地主机设置证书,或者(甚至更好)将其部署到生产环境! 像Heroku和Cloud Foundry这样的云提供商将为您提供现成的HTTPS,但他们不会强制使用 HTTPS。 要强制HTTPS,请打开src/main/java/com/okta/developer/config/SecurityConfiguration.java并添加一条规则,以在发送X-Forwarded-Proto标头时强制使用安全通道。

@Override
protected void configure(HttpSecurity http) throws Exception {http....and().headers().frameOptions().disable().and().requiresChannel().requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null).requiresSecure().and().authorizeRequests()...
}

已经对workbox-webpack-plugin进行了配置,以生成服务工作者,但是仅在使用生产配置文件运行您的应用程序时它才有效。 这很好,因为这意味着在开发时不会在浏览器中缓存数据。

要注册服务工作者,请打开src/main/webapp/index.html并取消注释以下代码块。

<script>if ('serviceWorker' in navigator) {navigator.serviceWorker.register('./service-worker.js').then(function() { console.log('Service Worker Registered'); });}
</script>

src/main/webapp/manifest.webapp包含了最后一个功能src/main/webapp/manifest.webapp 。 它定义了应用名称,颜色和图标。 您可能需要调整这些值以适合您的应用程序。

将您的React + Spring Boot应用程序部署到Heroku

要将应用程序部署到Heroku,首先需要安装Heroku CLI 。 您可以通过运行heroku --version确认其安装。

如果您没有Heroku帐户,请访问heroku.com并注册。 不用担心,它是免费的,而且您很可能会喜欢这种体验。

运行heroku login以登录到您的帐户,然后使用JHipster启动部署过程:

jhipster heroku

这将启动Heroku子生成器 ,该生成器会向您询问有关您的应用程序的几个问题:您要为其命名的名称以及是否要将其部署到美国地区或欧盟。 然后,它将提示您选择在本地构建还是在Heroku的服务器上使用Git进行选择。 选择Git,这样您就不必上载繁琐的JAR,部署过程就会开始。

如果您拥有稳定且快速的互联网连接,则您的应用程序应在六分钟之内在互联网上启动!

remote: -----> Compressing...
remote:        Done: 134.5M
remote: -----> Launching...
remote:        Released v5
remote:        https://gallery-pwa.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/gallery-pwa.git* [new branch]      HEAD -> masterYour app should now be live. To view it runheroku open
And you can view the logs with this commandheroku logs --tail
After application modification, redeploy it withjhipster heroku
Congratulations, JHipster execution is complete!
Execution time: 5 min. 31 s. sec

配置Okta的React + Spring Boot应用程序并使用Lighthouse分析您的PWA分数

要将您的应用程序配置为在Heroku上与Okta一起使用,请运行以下命令,将与Okta相关的本地环境变量传输到Heroku。

heroku config:set \SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI="$SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI" \SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI="$SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI" \SECURITY_OAUTH2_RESOURCE_USER_INFO_URI="$SECURITY_OAUTH2_RESOURCE_USER_INFO_URI" \SECURITY_OAUTH2_CLIENT_CLIENT_ID="$SECURITY_OAUTH2_CLIENT_CLIENT_ID" \SECURITY_OAUTH2_CLIENT_CLIENT_SECRET="$SECURITY_OAUTH2_CLIENT_CLIENT_SECRET"

Heroku重新启动您的应用程序后,登录,然后使用Lighthouse对其进行测试。 看起来不错,是吗? 💯

照片库

了解有关React,Spring Boot,JHipster和OAuth的更多信息

本教程向您展示了如何使用Spring Boot,React,JHipster和OIDC开发照相馆PWA。 它向您展示了一些有用的开放源代码库,这些库可简化实现甚至生成测试。

您可以在GitHub上的oktadeveloper / okta-react-photo-gallery-example回购中找到本文中创建的示例的源代码。 我录制了一个截屏视频,以逐步完成所有步骤。 在下面或在YouTube上观看嵌入式视频。


如果您想了解有关React,Spring Boot或OAuth 2.0的更多信息,建议您检查以下资源:

  • 使用Spring Boot和React进行Bootiful开发
  • 在JHipster中使用OpenID Connect支持
  • 什么是OAuth 2.0授权码授予类型?
  • 从JHipster Docs使用React(与Redux)
  • 使用OAuth 2.0和JHipster开发微服务架构

如有任何疑问,请在下面发表评论,在Twitter @mraible上ping我,或发布到我们的开发人员论坛 。 如果您喜欢这篇文章,请在其他类似的人发表时关注@oktadev 。

``使用React,Spring Boot和JHipster构建图库PWA''最初于2018年6月25日发布在Okta开发人员博客上。

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

翻译自: https://www.javacodegeeks.com/2018/07/react-spring-boot-photo-gallery-pwa.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/346199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html5画布funcition,2020前端基础知识学习第一节(示例代码)

一、选择题1. 浏览器端的存储技术有哪些()A.cookie  B.localStorage  C.session  D.userData答案&#xff1a;A B D分析&#xff1a;cookie通常用于存储用户等登录信息&#xff0c;能够存储的数据是有限的&#xff0c;而且在指定域名下所有的请求都会带上。localStoage是…

names文件和data文件_data.table让你的读取速度提升百倍

不知道大家有没有用read.table和read.csv读取过文件&#xff0c;当文件不大的时候你可能还感觉不出读取速度&#xff0c;但是当文件比较大的时候&#xff0c;比如有上万行的时候&#xff0c;你就会感觉到等待时间明显变长&#xff0c;甚至无法忍受。今天小编给大家安利一个实用…

差分放大电路差模共模公式_提高差分放大器的共模抑制比,电阻的选择很关键...

在各种应用领域&#xff0c;采用模拟技术时都需要使用差分放大器电路。例如测量技术&#xff0c;根据其应用的不同&#xff0c;可能需要极高的测量精度。为了达到这一精度&#xff0c;尽可能减少典型误差源(例如失调和增益误差&#xff0c;以及噪声、容差和漂移)至关重要。为此…

计算机网络的含义是什么意思,互联网的基本含义是什么

互联网大家都知道&#xff0c;但是互联网的基本含义都了解吗?学习啦小编为大家整理了相关内容&#xff0c;供大家参考阅读!互联网的基本含义互联网是由一些使用公用语言互相通信的计算机连接而成的网络&#xff0c;即广域网、局域网及单机按照一定的通讯协议组成的国际计算机网…

在单个try-with-resources语句中仔细指定多个资源

Java 7更有用的新功能之一是引入了try-with-resources语句 [AKA 自动资源管理 &#xff08; ARM &#xff09;]。 try-with-resources语句的吸引力在于其承诺 “确保在语句末尾关闭每个资源”。 在这种情况下&#xff0c;“资源”是实现AutoCloseable及其close&#xff08;&…

两轮差速机器人舵机转轴示意图_机器人教程2:舵机及转向控制原理

图3舵机外形图2、舵机的组成一般来讲&#xff0c;舵机主要由以下几个部分组成&#xff0c;舵盘、减速齿轮组、位置反馈电位计、直流电机、控制电路等&#xff0c;如图4、图5所示。图4舵机的组成示意图图5舵机组成舵机的输入线共有三条&#xff0c;如图6所示&#xff0c;红色中间…

label标签 html5,HTML5 label 标签

实例HTML5 标签用于为 input 元素做出标记。带有两个输入字段和相关标记的简单 HTML 表单&#xff1a;MaleFemale尝试一下 浏览器支持目前大多数浏览器支持 标签。标签定义及使用说明标签为 input 元素定义标注(标记)。label 元素不会向用户呈现任何特殊效果。不过&#xff0c;…

me shy是什么歌 抖音make_抖音星河滚烫你是人间的理想下一句是什么歌 星河歌词完整版...

最近在抖音上听到一段话&#xff0c;这段话的开头是“星河滚烫&#xff0c;你是人间的理想……”。抖音星河滚烫你是人间的理想下一句是什么歌&#xff1f;星河歌词介绍&#xff1f;星河&#xff0c;是非常好听的一首歌曲&#xff0c;是很多人都喜欢的一首歌曲。抖音星河滚烫你…

修改使用计算机的名称,Windows 10中修改计算机名称的方法,你知道几种?

当用户新入手一台电脑时&#xff0c;通常做的第一件事就是做一些个性化设置。一般来说个性化的第一步就是给计算机起一个自己满意的名字&#xff0c;下面介绍几种修改计算机名字的方法。通过设置更改计算机名字按WinI键进入设置,在设置窗口中找到“系统”&#xff0c;点击它。在…

apollo 参数传递_使用Apollo通过WebSocket通过STOMP轻松进行消息传递

apollo 参数传递在我以前的文章中&#xff0c;我介绍了几个有趣的用例&#xff0c;这些用例使用著名的消息代理HornetQ和ActiveMQ通过Websockects实现STOMP消息传递。 但是我没有介绍的是Apollo&#xff0c;因为我个人认为它的API是冗长的&#xff0c;并且不像Java开发人员那样…

mysql不能改编码_(转)MySQL设置和修改编码问题

mysql版本&#xff1a;5.7.18一、常用查看字符集设定命令1、查看数据库支持的所有字符集。show character set;(或 show char set;)2、查看当前状态命令(显示结果会包含数据库的字符集设置)status(或 /s)其中Db characterset对应的是数据库目录下的db.opt文件内容。(查到mysql安…

python 编程语言排行榜_2019年6月编程语言排行榜:Python 排名飙升,三年内有望超越Java...

TIOBE 6 月编程语言排行榜 更新已发布&#xff0c;排名前三的依然是&#xff1a;Java, C, Python。本月Python的TIOBE指数再次创下8.5%的历史新高。如果Python能够保持这样的速度&#xff0c;那它完全有可能在3到4年内取代C和Java&#xff0c;从而成为世界上***的编程语言。Pyt…

使用IntelliJ调试Java流

流功能非常强大&#xff0c;只需几行就可以捕获您预期功能的要点。 但是&#xff0c;就像它们在一切正常工作时一样平滑&#xff0c;就像它们在表现不正常时一样令人痛苦。 了解如何使用IntelliJ调试Java Streams并深入了解Stream的中间操作。 在本文中&#xff0c;我将在示例…

四川对口高职本科学校计算机,2018四川对口高职本科学校有哪些

相信很多想要报考四川单招对口高职的同学&#xff0c;都是非常的关心四川对口高职本科学校有哪些的&#xff0c;小编整理了相关信息&#xff0c;希望会对大家有所帮助!四川文化艺术学院(Sichuan University of Culture and Arts)&#xff0c;简称“川文艺”&#xff0c;是经教育…

echart 饼图每一块间隙_花10分钟做酱香饼,简单方便又美味,酥脆咸香比买的还要好吃...

花10分钟做酱香饼&#xff0c;简单方便又美味&#xff0c;酥脆咸香比买的还要好吃前几年&#xff0c;在单位附近有一家只做酱香饼的小店&#xff0c;刚开张的时候排很长队&#xff0c;我为了一块酱香饼排队半小时。后来买酱香饼的人少了&#xff0c;我三天两头买一块酱香饼解馋…

html 边框轮廓,CSS半圈(边框,仅轮廓)

小编典典您可以使用border-top-left-radius和border-top-right- radius属性根据框的高度(和添加的边框)在框上四角。然后在框的顶部/右侧/左侧添加边框以达到效果。干得好&#xff1a;.half-circle {width: 200px;height: 100px; /* as the half of the width */background-col…

人类一败涂地做图教程_《人类一败涂地》形象绘画技巧图文指南

第2页&#xff1a;展开第四步进行对人物细节描边处理&#xff0c;使粗细不同的变得一致&#xff0c;还是拉近镜头&#xff0c;利用底色扣细致。最重要的一点来了&#xff01;不管你画成什么样子&#xff0c;如果你在护理细节的过程中镜头过于拉进并且没有某一部位时模型会变成黑…

Spring Data Pivotal Gemfire教程

1. Spring Data Pivotal Gemfire –简介 在这篇文章中&#xff0c;我们将介绍有关Spring Data Pivotal Gemfire的全面教程。 Pivotal Gemfire是由Apache Geode支持的内存中数据网格解决方案。 使用Pivotal Gemfire构建的应用程序使您可以在分布式服务器节点之间轻松扩展系统。 …

计算机诞生发展分类特点及应用,计算机的诞生与发展,及其特点

计算机基本常识一、计算机的概念&#xff1a;计算机是一种能迅速而高效的自动完成信息处理的电子设备&#xff0c;它能按照程序对信息进行加工、处理、存储。二、计算机的诞生与发展1、诞生&#xff1a;1946年&#xff0c;美国为计算弹道轨迹而研制成功了世界第一台计算机。2、…

404 单页应用 报错 路由_通过 Laravel 创建一个 Vue 单页面应用(五)

文章转发自专业的Laravel开发者社区&#xff0c;原始链接&#xff1a;https://learnku.com/laravel/t/34858我们在第4部分完成了编辑用户的功能&#xff0c;并且学习了如何使用 v-model 来监听视图组件中用户信息的更改。现在我们可以开始构思删除用户功能&#xff0c;以及删除…