如何使用React、Spring Boot和JHipster的全栈式Java

574 阅读11分钟

如果你在互联网上搜索 "全栈Java",你可能会发现大量的招聘、课程和工作。作为一个全栈开发者可以是令人兴奋的,因为你可以独自创建一个应用程序的后端和前端。有商业逻辑和算法,以及像造型,使事情看起来很好,并确保一切安全。它的报酬也很高。今天,我将向你展示如何利用Spring Boot、React和JHipster成为一个全栈的Java开发者。

先决条件。

如果你是在Windows上,你可能需要安装Windows Subsystem for Linux,以便某些命令能够工作。

我建议使用SDKMAN来管理你的OpenJDK安装。只要运行 sdk install java 11.0.2-open来安装Java 11和 sdk install java 17-open来安装Java 17。

本教程不会提供关于如何用Java、React或Spring Boot编写代码的细枝末节。这是因为JHipster将为你编写大部分的代码然而,如果你是使用这些技术进行编程的新手,我推荐以下资源。

你可以使用下面的目录在本教程的各个部分之间跳过。

你也可以克隆已完成的例子,然后继续学习。

git clone https://github.com/oktadev/auth0-full-stack-java-example

如果你更喜欢视觉学习,你可以从OktaDev的YouTube频道观看下面的截屏。

使用React和Spring Boot的全栈开发

开始使用React的最简单的方法之一是使用Create React AppCRA)。你在本地安装它,然后运行 create-react-app <project>来生成一个具有最小依赖性的React应用。它使用webpack来构建项目,启动网络服务器,并运行其测试。

Spring Boot有一个类似的工具,叫做Spring Initializr。Spring Initializer与CRA有点不同,因为它是一个网站(和API),你可以用它创建应用程序。

今天,我将向你展示如何用React和Spring Boot建立一个Flickr的克隆。然而,我打算作弊。我没有使用上述的工具来构建一切,而是使用JHipster。JHipster是一个应用程序生成器,最初只支持Angular和Spring Boot。现在它支持Angular、React和Vue的前端。JHipster在后端也支持Kotlin、Micronaut、Quarkus、.NET和Node.js

在本教程中,我们将使用React,因为它似乎是当今最流行的前端框架。

开始使用JHipster 7

如果你还没有听说过JHipster,那我可要好好犒劳一下你了。JHipster早在2013年就开始作为Yeoman应用程序生成器,现在已经发展成为一个开发平台。它允许你快速生成、开发和部署现代网络应用和微服务架构。今天,我将向你展示如何用JHipster建立一个Flickr克隆,并通过OAuth和OpenID Connect(OIDC)将其锁定。

要开始使用JHipster,你需要一个快速的网络连接和安装Node.js。该项目建议你使用最新的LTS(长期支持)版本,在写这篇文章的时候是14.7.6。要运行该应用,你需要安装Java 11。如果你安装了Git,JHipster会在创建你的项目后自动提交。这将允许你在不同版本之间进行升级。

运行下面的命令来安装JHipster。

npm i -g generator-jhipster@7

要用JHipster创建一个全栈应用程序,创建一个目录,并在其中运行jhipster

mkdir full-stack-java
cd full-stack-java
jhipster

JHipster会提示你要创建的应用程序的类型,以及你想包括哪些技术。对于本教程,请做出以下选择。

问题回答
应用程序的类型?Monolithic application
名称?flickr2
Spring WebFlux?No
Java包的名称?com.auth0.flickr2
认证类型?OAuth 2.0 / OIDC
数据库的类型?SQL
生产数据库?PostgreSQL
开发数据库?H2 with disk-based persistence
哪种缓存?Ehcache
使用Hibernate二级缓存?Yes
使用Maven还是Gradle?Maven
使用JHipster注册表?No
其他技术?<blank>
客户端框架?React
管理界面?Yes
Bootswatch主题?United >Dark
启用i18n?Yes
应用程序的本地语言?English
其他语言?您的选择!
额外的测试框架?Cypress
安装其他生成器?No

按下Enter键,JHipster将在当前目录下创建你的应用程序,并运行npm install ,以安装所有指定在 package.json.

验证一切与Cypress和Keycloak的工作情况

当你选择OAuth 2.0和OIDC进行认证时,用户被存储在应用程序之外,而不是在其中。你需要配置一个身份提供者(IdP)来存储你的用户,并允许你的应用程序检索关于他们的信息。默认情况下,JHipster为Docker Compose提供了一个Keycloak文件。一组默认的用户和组在启动时被导入,并且它有一个为你的JHipster应用注册的客户端。

下面是 keycloak.yml在你的应用程序的 src/main/docker目录中的样子。

# This configuration is intended for development purpose; it's **your** responsibility
# to harden it for production
version: '3.8'
services:
  keycloak:
    image: jboss/keycloak:15.0.2
    command:
      [
          '-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',
          '-Dkeycloak.profile.feature.upload_scripts=enabled',
      ]
    volumes:
      - ./realm-config:/opt/jboss/keycloak/realm-config
    environment:
      - KEYCLOAK_USER=admin
      - KEYCLOAK_PASSWORD=admin
      - DB_VENDOR=h2
    # If you want to expose these ports outside your dev PC,
    # remove the "127.0.0.1:" prefix
    ports:
      - 127.0.0.1:9080:9080
      - 127.0.0.1:9443:9443
      - 127.0.0.1:10990:10990

在你项目的根目录下用以下命令启动Keycloak。

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

你可以通过用Maven启动你的应用程序来验证一切工作。

./mvnw

打开另一个终端,运行你的新应用的Cypress测试。

npm run e2e

你应该看到类似以下的输出。

  (Run Finished)

       Spec                                              Tests  Passing  Failing  Pending  Skipped
  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ ✔  administration/administration.spec.      00:12        5        5        -        -        - │
  │    ts                                                                                          │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
    ✔  All specs passed!                        00:12        5        5        -        -        -

将您的身份提供者改为Auth0

JHipster使用Spring Security的OAuth 2.0和OIDC支持来配置其使用的IdP。在Spring Boot中使用Spring Security时,你可以在属性文件中配置大多数设置。你甚至可以用环境变量重写属性。

要从Keycloak切换到Auth0,你只需要覆盖默认属性(对于Spring Security OAuth)。你甚至不需要写任何代码

为了了解它是如何工作的,请在你的项目根目录下创建一个 .auth0.env文件,并在其中填入以下代码以覆盖默认的OIDC设置。

export SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=https://<your-auth0-domain>/
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=<your-client-id>
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=<your-client-secret>
export JHIPSTER_SECURITY_OAUTH2_AUDIENCE=https://<your-auth0-domain>/api/v2/

⚠️警告:修改你现有的 .gitignore文件,使其具有 *.env这样你就不会不小心检查到你的秘密了!你需要创建一个新的文件。

你需要在Auth0中创建一个新的网络应用程序,并填入 <...>占位符才行。

在Auth0上创建一个OpenID Connect应用程序

登录到你的Auth0账户(如果你没有账户,也可以注册)。你应该有一个独特的域名,如 dev-xxx.eu.auth0.com.

应用程序部分创建应用程序按钮。使用一个名称如 JHipster Baby!,选择Regular Web Applications ,然后点击创建

切换到设置标签,配置你的应用程序设置。

  • 允许的回调URL。 http://localhost:8080/login/oauth2/code/oidc
  • 允许的注销URLs。 http://localhost:8080/

滚动到底部,点击保存更改

角色部分,创建新的角色,名为 ROLE_ADMINROLE_USER.

用户部分创建一个新的用户账户。点击角色标签,将你刚刚创建的角色分配给新账户。

在尝试登录之前,请确保你的新用户的电子邮件是经过验证的!

接下来,前往Auth Pipeline>Rules>Create。选择Empty rule 模板。提供一个有意义的名字,如Group claims ,并将脚本内容替换为以下内容。

function(user, context, callback) {
  user.preferred_username = user.email;
  const roles = (context.authorization || {}).roles;

  function prepareCustomClaimKey(claim) {
    return `https://www.jhipster.tech/${claim}`;
  }

  const rolesClaim = prepareCustomClaimKey('roles');

  if (context.idToken) {
    context.idToken[rolesClaim] = roles;
  }

  if (context.accessToken) {
    context.accessToken[rolesClaim] = roles;
  }

  callback(null, user, context);
}

这段代码是将用户的角色添加到一个自定义的索赔(前缀为 https://www.jhipster.tech/roles).这个请求被映射到Spring Security权限中的 SecurityUtils.java.

public static List<GrantedAuthority> extractAuthorityFromClaims(Map<String, Object> claims) {
    return mapRolesToGrantedAuthorities(getRolesFromClaims(claims));
}

@SuppressWarnings("unchecked")
private static Collection<String> getRolesFromClaims(Map<String, Object> claims) {
    return (Collection<String>) claims.getOrDefault(
        "groups",
        claims.getOrDefault("roles", claims.getOrDefault(CLAIMS_NAMESPACE + "roles", new ArrayList<>()))
    );
}

private static List<GrantedAuthority> mapRolesToGrantedAuthorities(Collection<String> roles) {
    return roles.stream().filter(role -> role.startsWith("ROLE_")).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}

SecurityConfiguration.java类有一个Bean,它调用这个方法来配置用户的OIDC数据中的角色。

@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
    return authorities -> {
        Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

        authorities.forEach(authority -> {
            // Check for OidcUserAuthority because Spring Security 5.2 returns
            // each scope as a GrantedAuthority, which we don't care about.
            if (authority instanceof OidcUserAuthority) {
                OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
                mappedAuthorities.addAll(SecurityUtils.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims()));
            }
        });
        return mappedAuthorities;
    };
}

单击 "保存更改"以继续。

ℹ️注意:想让所有这些步骤为你自动化吗?请在Auth0 CLI项目中为这个问题投票。

用Auth0运行你的JHipster应用程序

使用Ctrl+C停止你的JHipster应用程序,在Auth0中设置你的属性。 .auth0.env中设置Auth0属性,然后再次启动你的应用程序。

source .auth0.env
./mvnw

Voilà- 你的全栈应用程序现在正在使用Auth0!打开你最喜欢的浏览器到 http://localhost:8080.

JHipster default homepage

你应该看到你的应用程序的主页上有一个登录的链接。点击登录,你将被重定向到Auth0进行登录。

Auth0 Login

输入你的证书后,你将被重定向到你的应用程序。

Authenticated

用Cypress测试你的全栈Java应用

JHipster内置了对Auth0的支持,因此你可以为Cypress测试指定你的凭证,并自动进行UI测试!

要做到这一点,打开一个新的终端窗口,指定你刚刚创建的Auth0用户的证书,然后运行npm run e2e

export CYPRESS_E2E_USERNAME=<new-username>
export CYPRESS_E2E_PASSWORD=<new-password>
npm run e2e

提示:如果你想用一个 .env文件来设置环境变量,你可以使用cypress-dotenv。你也可以把这些值放在 cypress.json,但由于这个文件将在源代码控制中,把你的秘密放在里面是一个不好的做法。

一切都应该在一分钟左右通过。

       Spec                                              Tests  Passing  Failing  Pending  Skipped
  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ ✔  administration/administration.spec.      00:31        5        5        -        -        - │
  │    ts                                                                                          │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
    ✔  All specs passed!                        00:31        5        5        -        -        -

Execution time: 44 s.

关闭运行你的JHipster应用程序的进程--现在是为你的Flickr克隆创建一些数据处理的时候了。

创建实体以允许对照片进行CRUD

我已经谈了很多关于如何保护你的应用程序的问题,但我们还没有对照片做任何处理!我想说的是,JHipster有一个JDL实体。JHipster有一个JDL(JHipster Domain Language)功能,它允许你在你的应用程序中对数据进行建模并从中生成实体。你可以使用JDL工作室在线完成这项工作,完成后保存在本地。

我的这个应用程序的数据模型有Album,Photo, 和Tag 实体,并在它们之间建立了关系。下面是它在JDL Studio中的屏幕截图。

JDL Studio

复制下面的JDL,并将其保存在你项目根目录下的一个 flickr2.jdl文件中,并保存在你项目的根目录下。

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和JJSX)。

jhipster jdl flickr2.jdl

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

这个过程将创建Liquibase changelog文件(创建你的数据库表)、实体、资源库、Spring MVC控制器,以及创建、读取、更新和删除实体所需的所有React代码。它甚至会生成JUnit单元测试、Jest单元测试和Cypress端到端测试

在这个过程完成后,你可以重新启动你的应用程序,登录,并浏览实体菜单。尝试添加一些数据,以确认一切正常。

现在,你可以看到,JHipster是相当强大的。它认识到你有一个ImageBlob 类型的图像属性,并创建了必要的逻辑来上传和存储数据库中的图像Booyah!

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

Photo 实体有几个属性,可以通过读取上传照片的EXIF(可交换图像文件格式)数据来计算。你可能会问,你如何在Java中做到这一点?

值得庆幸的是,Drew Noakes创建了一个元数据提取器库来做这件事。把对Drew库的依赖性添加到你的 pom.xml:

<dependency>
    <groupId>com.drewnoakes</groupId>
    <artifactId>metadata-extractor</artifactId>
    <version>2.16.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<Photo> 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(applicationName, true, 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;
    }
    ...
}

因为你在提取信息,所以你可以从用户界面和测试中删除字段,这样用户就不能设置这些值。

src/main/webapp/app/entities/photo/photo-update.tsx中,隐藏元数据,这样用户就不能编辑它了。与其显示height,width,taken, 和uploaded 的值,不如隐藏它们。你可以通过搜索来做到这一点 photo-height,抓取这些元素(和它后面的三个元素),并将它们添加到metadata 常量中,就在 defaultValues()lambda函数。

const defaultValues = () =>
  ...

const metadata = (
  <div>
    <ValidatedField label={translate('flickr2App.photo.height')} id="photo-height" name="height" data-cy="height" type="text" />
    <ValidatedField label={translate('flickr2App.photo.width')} id="photo-width" name="width" data-cy="width" type="text" />
    <ValidatedField
      label={translate('flickr2App.photo.taken')}
      id="photo-taken"
      name="taken"
      data-cy="taken"
      type="datetime-local"
      placeholder="YYYY-MM-DD HH:mm"
    />
    <ValidatedField
      label={translate('flickr2App.photo.uploaded')}
      id="photo-uploaded"
      name="uploaded"
      data-cy="uploaded"
      type="datetime-local"
      placeholder="YYYY-MM-DD HH:mm"
    />
  </div>
);
const metadataRows = isNew ? '' : metadata;

return ( ... );

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

<ValidatedBlobField
  label={translate('flickr2App.photo.image')}
  id="photo-image"
  name="image"
  data-cy="image"
  isImage
  accept="image/*"
  validate={{
    required: { value: true, message: translate('entity.validation.required') },
  }}
/>
{metadataRows}
<ValidatedField id="photo-album" name="albumId" data-cy="album" label={translate('flickr2App.photo.album')} type="select">
  <option value="" key="0" />
  {albums
    ? albums.map(otherEntity => (
      <option value={otherEntity.id} key={otherEntity.id}>
        {otherEntity.title}
      </option>
    ))
    : null}
</ValidatedField>

src/test/javascript/cypress/integration/entity/photo.spec.ts中,删除设置这些字段数据的代码。

cy.get(`[data-cy="height"]`).type('99459').should('have.value', '99459');
cy.get(`[data-cy="width"]`).type('61514').should('have.value', '61514');
cy.get(`[data-cy="taken"]`).type('2021-10-11T16:46').should('have.value', '2021-10-11T16:46');
cy.get(`[data-cy="uploaded"]`).type('2021-10-11T15:23').should('have.value', '2021-10-11T15:23'););

停止你的Maven进程,运行 source .auth0.env,然后 ./mvnw再次运行。打开一个新的终端窗口,设置你的Auth0凭证,然后运行npm run e2e ,确保一切正常。

export CYPRESS_E2E_USERNAME=<auth0-username>
export CYPRESS_E2E_PASSWORD=<auth0-password>
npm run e2e

ℹ️注意:如果你在Cypress测试中遇到认证错误,可能是因为你违反了Auth0的速率限制政策。作为一种变通方法,我建议你使用Keycloak进行Cypress测试。你可以通过打开一个新的终端窗口,并在那里用以下方式启动你的应用程序 ./mvnw.然后,打开第二个终端窗口,运行npm run e2e

如果你上传一张你用智能手机拍摄的图片,高度、宽度和拍摄的数值都应该被填充。如果它们不是,很可能你的图片中没有这些数据。

需要一些带有EXIF数据的样本照片吗?你可以从Flickr上的相册下载我的1966年大众汽车的照片。

添加一个React图片库

你已经在你的后台添加了元数据提取,但你的照片仍然显示在一个列表中,而不是在一个网格中(像Flickr)。为了解决这个问题,你可以使用React Photo Gallery组件。使用npm安装它。

npm i react-photo-gallery@8 --force

src/main/webapp/app/entities/photo/photo.tsx中,为Gallery 添加一个导入。

import Gallery from 'react-photo-gallery';

然后在后面添加以下内容 const { match } = 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
}));

接下来,在结尾处添加一个 <Gallery>组件,紧随其后 </h2>.

return (
  <div>
    <h2 id="photo-heading" data-cy="PhotoHeading">
      ...
    </h2>
    <Gallery photos={photoSet} />
    ...
);

保存你所有的改动并重新启动你的应用程序。

source .auth0.env
./mvnw

登录并在顶部导航栏中浏览到实体>照片。你会看到由Liquibasefaker.js加载的大量的照片。为了制作一个没有这些数据的干净的截图,我修改了 src/main/resources/config/application-dev.yml以删除Liquibase的 "faker "上下文。

liquibase:
  # Append ', faker' to the line below if you want sample data to be loaded automatically
  contexts: dev

停止你的Spring Boot后端并运行 rm -r target/h2db来清除你的数据库(或者直接删除 target/h2db目录)。重新启动你的后端。

现在你应该可以上传照片,并在列表顶部的一个漂亮的网格中看到结果。

Gallery with Photos

你还可以在网格上添加一个 "灯箱 "功能,这样你就可以点击照片并放大。React Photo Gallery的文档显示了如何做到这一点。我已经把它整合到这篇文章的例子中,但为了简洁起见,我不会在这里展示代码。你可以在GitHub上看到添加了Lightbox的最终photo.tsx也可以看到必要修改的差异

把你的全栈Java应用变成一个PWA

渐进式网络应用程序,又称PWA,是开发者使其网络应用程序加载更快、性能更强的最佳方式。简而言之,PWA是使用最新网络标准的网站,允许安装在用户的电脑或设备上,并向这些用户提供类似于应用程序的体验。要把一个网络应用变成PWA。

  1. 你的应用必须通过HTTPS提供服务
  2. 你的应用程序必须注册一个服务工作者,以便它可以缓存请求并离线工作
  3. 你的应用程序必须有一个包含安装信息和图标的webapp清单

对于HTTPS,你可以为localhost设置一个证书,或者(甚至更好),把它部署到生产中去像Heroku这样的云服务提供商将为你提供开箱即用的HTTPS,但他们不会强制HTTPS。要强制使用HTTPS,请打开 src/main/java/com/auth0/flickr2/config/SecurityConfiguration.java并添加一条规则,在发送头信息时强制使用安全通道。 X-Forwarded-Proto头的时候强制使用安全通道。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        ...
    .and()
        .frameOptions()
        .deny()
    .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) {
    window.addEventListener('load', function () {
      navigator.serviceWorker.register('/service-worker.js').then(function () {
        console.log('Service Worker Registered');
      });
    });
  }
</script>

最后一个功能--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。当被提示使用Okta进行OIDC时,选择No 。然后,部署过程将开始。

你会被提示覆盖 pom.xml-typea 以允许覆盖所有文件。

如果你有一个稳定和快速的网络连接,你的应用程序应该在6分钟内就可以在互联网上运行了

remote: -----> Compressing...
remote:        Done: 120.9M
remote: -----> Launching...
remote:        Released v7
remote:        https://flickr-2.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.

To https://git.heroku.com/flickr-2.git
 * [new branch]      HEAD -> main

Your app should now be live. To view it, run
    heroku open
And you can view the logs with this command
    heroku logs --tail
After application modification, redeploy it with
    jhipster heroku
Congratulations, JHipster execution, is complete!
Sponsored with ❤️ by @oktadev.
Execution time: 6 min. 19 s.

为Auth0配置并使用Lighthouse分析你的PWA得分

要将你的应用程序配置为Heroku上的Auth0,请运行以下命令来设置Heroku上的Auth0变量。

heroku config:set \
  SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI="https://<your-auth0-domain>/" \
  SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID="<your-client-id>" \
  SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET="<your-client-secret>" \
  JHIPSTER_SECURITY_OAUTH2_AUDIENCE="https://<your-auth0-domain>/api/v2/"

然后,登录你的Auth0账户,导航到你的应用程序,并将你的Heroku URLs添加为有效的重定向URI。

  • 允许的回调URLs: https://flickr-2.herokuapp.com/login/oauth2/code/oidc
  • Allowed Logout URLs: https://flickr-2.herokuapp.com

在Heroku重新启动你的应用后,用heroku open ,并登录。

Running on Heroku!

然后,用Lighthouse进行测试(使用Chrome开发工具中的Lighthouse标签)。看起来很不错,是吧!?💯

Lighthouse Score 💯

它也很安全,至少根据securityheaders.com的说法。

Security Headers on Heroku

了解更多关于全栈式Java开发的信息

本教程告诉你如何用JHipster简化全栈Java开发。你开发了一个带有React前端和Spring Boot后端的工作应用。你可以在GitHub的auth0-full-stack-java-example仓库中找到本教程创建的应用程序。

你可能还会喜欢这些相关的博文。