“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。
React的设计使创建交互式UI变得轻松自如。 它的状态管理非常有效,并且仅在数据更改时才更新组件。 组件逻辑是用JavaScript编写的,这意味着您可以将状态保持在DOM之外,并创建封装的组件。
开发人员喜欢CRUD(创建,读取,更新和删除)应用程序,因为它们显示了创建应用程序时需要的许多基本功能。 一旦在应用程序中完成了CRUD的基础知识,大多数客户端-服务器管道就完成了,您可以继续实施必要的业务逻辑。
今天,我将向您展示如何在React中使用Spring Boot创建一个基本的CRUD应用。 您可能还记得我去年为Angular撰写的一篇类似文章: 使用Angular 5.0和Spring Boot 2.0构建Basic CRUD应用程序 。 该教程使用OAuth 2.0的隐式流程和我们的Okta Angular SDK 。 在本教程中,我将使用OAuth 2.0授权代码流,并将React应用打包在Spring Boot应用中进行生产。 同时,我将向您展示如何保持React高效的工作流以进行本地开发。
您将需要安装Java 8 , Node.js 8和Yarn才能完成本教程。 您可以使用npm代替Yarn,但是您需要将Yarn语法转换为npm。
使用Spring Boot 2.0创建API应用
我经常在世界各地的会议和用户组中演讲。 我最喜欢发言的用户组是Java用户组(JUG)。 我从事Java开发人员已有近20年的时间,而且我喜欢Java社区。 我的一个好朋友詹姆斯·沃德(James Ward)表示,进行水罐巡游是他当时最喜欢的开发商倡导者活动之一。 我最近接受了他的建议,并在海外会议上进行了JUG聚会在美国的聚会。
我为什么要告诉你呢? 因为我认为今天创建一个“ JUG Tours”应用很有趣,它允许您创建/编辑/删除JUG,以及查看即将发生的事件。
首先,导航至start.spring.io并进行以下选择:
- 组:
com.okta.developer
- 神器:
jugtours
- 依赖项 :
JPA
,H2
,Web
,Lombok
单击生成项目 ,下载后展开jugtours.zip
,然后在您喜欢的IDE中打开该项目。
提示:如果您使用的是IntelliJ IDEA或Spring Tool Suite,则在创建新项目时也可以使用Spring Initializr。
添加一个JPA域模型
您需要做的第一件事是创建一个保存数据的域模型。 在高层次上,有一个Group
表示酒壶,一个Event
有一个多到一的关系Group
,以及User
具有与一个一对多的关系Group
。
在其中创建一个src/main/java/com/okta/developer/jugtours/model
目录和一个Group.java
类。
package com.okta.developer.jugtours.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import javax.persistence.*;
import java.util.Set;
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@Entity
@Table(name = "user_group")
public class Group {
@Id
@GeneratedValue
private Long id;
@NonNull
private String name;
private String address;
private String city;
private String stateOrProvince;
private String country;
private String postalCode;
@ManyToOne(cascade=CascadeType.PERSIST)
private User user;
@OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
private Set<Event> events;
}
在同一包中创建一个Event.java
类。
package com.okta.developer.jugtours.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import java.time.Instant;
import java.util.Set;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Event {
@Id
@GeneratedValue
private Long id;
private Instant date;
private String title;
private String description;
@ManyToMany
private Set<User> attendees;
}
还有一个User.java
类。
package com.okta.developer.jugtours.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.Id;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User {
@Id
private String id;
private String name;
private String email;
}
创建一个GroupRepository.java
来管理组实体。
package com.okta.developer.jugtours.model;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface GroupRepository extends JpaRepository<Group, Long> {
Group findByName(String name);
}
要加载一些默认数据,请在com.okta.developer.jugtours
包中创建一个Initializer.java
类。
package com.okta.developer.jugtours;
import com.okta.developer.jugtours.model.Event;
import com.okta.developer.jugtours.model.Group;
import com.okta.developer.jugtours.model.GroupRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Collections;
import java.util.stream.Stream;
@Component
class Initializer implements CommandLineRunner {
private final GroupRepository repository;
public Initializer(GroupRepository repository) {
this.repository = repository;
}
@Override
public void run(String... strings) {
Stream.of("Denver JUG", "Utah JUG", "Seattle JUG",
"Richmond JUG").forEach(name ->
repository.save(new Group(name))
);
Group djug = repository.findByName("Denver JUG");
Event e = Event.builder().title("Full Stack Reactive")
.description("Reactive with Spring Boot + React")
.date(Instant.parse("2018-12-12T18:00:00.000Z"))
.build();
djug.setEvents(Collections.singleton(e));
repository.save(djug);
repository.findAll().forEach(System.out::println);
}
}
提示:如果您的IDE Event.builder()
问题,则意味着您需要打开注释处理和/或安装Lombok插件。 我必须在IntelliJ IDEA中卸载/重新安装Lombok插件才能正常工作。
如果在添加此代码后启动应用程序(使用./mvnw spring-boot:run
),您将看到控制台中显示的组和事件列表。
Group(id=1, name=Denver JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[Event(id=5, date=2018-12-12T18:00:00Z, title=Full Stack Reactive, description=Reactive with Spring Boot + React, attendees=[])])
Group(id=2, name=Utah JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[])
Group(id=3, name=Seattle JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[])
Group(id=4, name=Richmond JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[])
添加一个GroupController.java
类(在src/main/java/.../jugtours/web/GroupController.java
), src/main/java/.../jugtours/web/GroupController.java
可用于CRUD组。
package com.okta.developer.jugtours.web;
import com.okta.developer.jugtours.model.Group;
import com.okta.developer.jugtours.model.GroupRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Optional;
@RestController
@RequestMapping("/api")
class GroupController {
private final Logger log = LoggerFactory.getLogger(GroupController.class);
private GroupRepository groupRepository;
public GroupController(GroupRepository groupRepository) {
this.groupRepository = groupRepository;
}
@GetMapping("/groups")
Collection<Group> groups() {
return groupRepository.findAll();
}
@GetMapping("/group/{id}")
ResponseEntity<?> getGroup(@PathVariable Long id) {
Optional<Group> group = groupRepository.findById(id);
return group.map(response -> ResponseEntity.ok().body(response))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
@PostMapping("/group")
ResponseEntity<Group> createGroup(@Valid @RequestBody Group group) throws URISyntaxException {
log.info("Request to create group: {}", group);
Group result = groupRepository.save(group);
return ResponseEntity.created(new URI("/api/group/" + result.getId()))
.body(result);
}
@PutMapping("/group/{id}")
ResponseEntity<Group> updateGroup(@PathVariable Long id, @Valid @RequestBody Group group) {
group.setId(id);
log.info("Request to update group: {}", group);
Group result = groupRepository.save(group);
return ResponseEntity.ok().body(result);
}
@DeleteMapping("/group/{id}")
public ResponseEntity<?> deleteGroup(@PathVariable Long id) {
log.info("Request to delete group: {}", id);
groupRepository.deleteById(id);
return ResponseEntity.ok().build();
}
}
如果重新启动服务器应用程序,并使用浏览器或命令行客户端访问http://localhost:8080/api/groups
,则应看到组列表。
您可以使用以下HTTPie命令创建,读取,更新和删除组。
http POST :8080/api/group name='Dublin JUG' city=Dublin country=Ireland
http :8080/api/group/6
http PUT :8080/api/group/6 name='Dublin JUG' city=Dublin country=Ireland address=Downtown
http DELETE :8080/api/group/6
使用Create React App创建一个React UI
Create React App是一个命令行实用程序,可为您生成React项目。 这是一个方便的工具,因为它还提供了一些命令,这些命令将生成和优化您的项目以进行生产。 它使用webpack在后台进行构建。 如果您想了解更多关于webpack的信息,我建议使用webpack.academy 。
使用Yarn在jugtours
目录中创建一个新项目。
yarn create react-app app
应用程序创建过程完成后,导航至app
目录并安装Bootstrap ,对React的cookie支持,React Router和Reactstrap 。
cd app
yarn add bootstrap@4.1.2 react-cookie@2.2.0 react-router-dom@4.3.1 reactstrap@6.3.0
您将使用BootstrapCSS和Reactstrap的组件来使UI看起来更好,尤其是在手机上。 如果您想了解有关Reactstrap的更多信息,请参见https://reactstrap.github.io 。 它具有有关其各种组件以及如何使用它们的大量文档。
将BootstrapCSS文件添加为app/src/index.js
的导入文件。
import 'bootstrap/dist/css/bootstrap.min.css';
调用您的Spring Boot API并显示结果
修改app/src/App.js
以使用以下代码调用/api/groups
并在UI中显示列表。
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css