react hooks

react hooks

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

React的设计使创建交互式UI变得轻松自如。 它的状态管理非常有效,并且仅在数据更改时才更新组件。 组件逻辑是用JavaScript编写的,这意味着您可以将状态保持在DOM之外,并创建封装的组件。

开发人员喜欢CRUD(创建,读取,更新和删除)应用程序,因为它们显示了创建应用程序时需要的许多基本功能。 一旦在应用程序中完成了CRUD的基础知识,大多数客户端-服务器管道就完成了,您可以继续实施必要的业务逻辑。

今天,我将向您展示如何在React中使用Spring Boot创建一个基本的CRUD应用。 您可能还记得我去年为Angular撰写的一篇类似文章: 使用Angular 5.0和Spring Boot 2.0构建Ba​​sic 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
  • 依赖项JPAH2WebLombok

React

单击生成项目,下载后展开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@GeneratedValueprivate Long id;@NonNullprivate 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@GeneratedValueprivate Long id;private Instant date;private String title;private String description;@ManyToManyprivate 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 {@Idprivate 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;}@Overridepublic 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';class App extends Component {state = {isLoading: true,groups: []};async componentDidMount() {const response = await fetch('/api/groups');const body = await response.json();this.setState({ groups: body, isLoading: false });}render() {const {groups, isLoading} = this.state;if (isLoading) {return <p>Loading...</p>;}return (<div className="App"><header className="App-header"><img src={logo} className="App-logo" alt="logo" /><h1 className="App-title">Welcome to React</h1></header><div className="App-intro"><h2>JUG List</h2>{groups.map(group =><div key={group.id}>{group.name}</div>)}</div></div>);}
}export default App;

要将/api代理到http://localhost:8080/api ,请将代理设置添加到app/package.json

"scripts": {...},
"proxy": "http://localhost:8080"

要了解有关此功能的更多信息,请在app/README.md搜索“ proxy”。 Create React App随该文件附带了各种文档,这有多酷?

确保Spring Boot正在运行,然后在您的app目录中运行yarn start 。 您应该看到默认组的列表。

React

构建一个React GroupList组件

React是关于组件的,您不想在主App呈现所有内容,因此请创建app/src/GroupList.js并使用以下JavaScript进行填充。

import React, { Component } from 'react';
import { Button, ButtonGroup, Container, Table } from 'reactstrap';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';class GroupList extends Component {constructor(props) {super(props);this.state = {groups: [], isLoading: true};this.remove = this.remove.bind(this);}componentDidMount() {this.setState({isLoading: true});fetch('api/groups').then(response => response.json()).then(data => this.setState({groups: data, isLoading: false}));}async remove(id) {await fetch(`/api/group/${id}`, {method: 'DELETE',headers: {'Accept': 'application/json','Content-Type': 'application/json'}}).then(() => {let updatedGroups = [...this.state.groups].filter(i => i.id !== id);this.setState({groups: updatedGroups});});}render() {const {groups, isLoading} = this.state;if (isLoading) {return <p>Loading...</p>;}const groupList = groups.map(group => {const address = `${group.address || ''} ${group.city || ''} ${group.stateOrProvince || ''}`;return <tr key={group.id}><td style={{whiteSpace: 'nowrap'}}>{group.name}</td><td>{address}</td><td>{group.events.map(event => {return <div key={event.id}>{new Intl.DateTimeFormat('en-US', {year: 'numeric',month: 'long',day: '2-digit'}).format(new Date(event.date))}: {event.title}</div>})}</td><td><ButtonGroup><Button size="sm" color="primary" tag={Link} to={"/groups/" + group.id}>Edit</Button><Button size="sm" color="danger" onClick={() => this.remove(group.id)}>Delete</Button></ButtonGroup></td></tr>});return (<div><AppNavbar/><Container fluid><div className="float-right"><Button color="success" tag={Link} to="/groups/new">Add Group</Button></div><h3>My JUG Tour</h3><Table className="mt-4"><thead><tr><th width="20%">Name</th><th width="20%">Location</th><th>Events</th><th width="10%">Actions</th></tr></thead><tbody>{groupList}</tbody></Table></Container></div>);}
}export default GroupList;

在同一目录中创建AppNavbar.js ,以在组件之间建立通用的UI功能。

import React, { Component } from 'react';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';export default class AppNavbar extends Component {constructor(props) {super(props);this.state = {isOpen: false};this.toggle = this.toggle.bind(this);}toggle() {this.setState({isOpen: !this.state.isOpen});}render() {return <Navbar color="dark" dark expand="md"><NavbarBrand tag={Link} to="/">Home</NavbarBrand><NavbarToggler onClick={this.toggle}/><Collapse isOpen={this.state.isOpen} navbar><Nav className="ml-auto" navbar><NavItem><NavLinkhref="https://twitter.com/oktadev">@oktadev</NavLink></NavItem><NavItem><NavLink href="https://github.com/oktadeveloper/okta-spring-boot-react-crud-example">GitHub</NavLink></NavItem></Nav></Collapse></Navbar>;}
}

创建app/src/Home.js作为应用程序的登录页面。

import React, { Component } from 'react';
import './App.css';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';class Home extends Component {render() {return (<div><AppNavbar/><Container fluid><Button color="link"><Link to="/groups">Manage JUG Tour</Link></Button></Container></div>);}
}export default Home;

另外,更改app/src/App.js以使用React Router在组件之间导航。

import React, { Component } from 'react';
import './App.css';
import Home from './Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import GroupList from './GroupList';class App extends Component {render() {return (<Router><Switch><Route path='/' exact={true} component={Home}/><Route path='/groups' exact={true} component={GroupList}/></Switch></Router>)}
}export default App;

为了使您的UI更加宽敞,请在app/src/App.css容器类中添加一个上边距。

.container, .container-fluid {margin-top: 20px
}

当您进行更改时,您的React应用程序应该会自我更新,并且您应该在http://localhost:3000看到如下屏幕。

React
点击Manage JUG Tour ,您应该会看到默认组的列表。
React
可以在React应用程序中查看Spring Boot API的数据真是太好了,但是,如果您不能编辑它,那就太无聊了!

添加一个React GroupEdit组件

创建app/src/GroupEdit.js并使用其componentDidMount()从URL中获取具有ID的组资源。

import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
import { Button, Container, Form, FormGroup, Input, Label } from 'reactstrap';
import AppNavbar from './AppNavbar';class GroupEdit extends Component {emptyItem = {name: '',address: '',city: '',stateOrProvince: '',country: '',postalCode: ''};constructor(props) {super(props);this.state = {item: this.emptyItem};this.handleChange = this.handleChange.bind(this);this.handleSubmit = this.handleSubmit.bind(this);}async componentDidMount() {if (this.props.match.params.id !== 'new') {const group = await (await fetch(`/api/group/${this.props.match.params.id}`)).json();this.setState({item: group});}}handleChange(event) {const target = event.target;const value = target.value;const name = target.name;let item = {...this.state.item};item[name] = value;this.setState({item});}async handleSubmit(event) {event.preventDefault();const {item} = this.state;await fetch('/api/group', {method: (item.id) ? 'PUT' : 'POST',headers: {'Accept': 'application/json','Content-Type': 'application/json'},body: JSON.stringify(item),});this.props.history.push('/groups');}render() {const {item} = this.state;const title = <h2>{item.id ? 'Edit Group' : 'Add Group'}</h2>;return <div><AppNavbar/><Container>{title}<Form onSubmit={this.handleSubmit}><FormGroup><Label for="name">Name</Label><Input type="text" name="name" id="name" value={item.name || ''}onChange={this.handleChange} autoComplete="name"/></FormGroup><FormGroup><Label for="address">Address</Label><Input type="text" name="address" id="address" value={item.address || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><FormGroup><Label for="city">City</Label><Input type="text" name="city" id="city" value={item.city || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><div className="row"><FormGroup className="col-md-4 mb-3"><Label for="stateOrProvince">State/Province</Label><Input type="text" name="stateOrProvince" id="stateOrProvince" value={item.stateOrProvince || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><FormGroup className="col-md-5 mb-3"><Label for="country">Country</Label><Input type="text" name="country" id="country" value={item.country || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><FormGroup className="col-md-3 mb-3"><Label for="country">Postal Code</Label><Input type="text" name="postalCode" id="postalCode" value={item.postalCode || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup></div><FormGroup><Button color="primary" type="submit">Save</Button>{' '}<Button color="secondary" tag={Link} to="/groups">Cancel</Button></FormGroup></Form></Container></div>}
}export default withRouter(GroupEdit);

在底部需要withRouter()高阶组件来显示this.props.history以便您在添加或保存GroupList后可以导航回this.props.history

修改app/src/App.js以导入GroupEditapp/src/App.js指定路径。

import GroupEdit from './GroupEdit';class App extends Component {render() {return (<Router><Switch>...<Route path='/groups/:id' component={GroupEdit}/></Switch></Router>)}
}

现在您应该可以添加和编辑组了!

React
React

使用Okta添加身份验证

构建CRUD应用程序非常酷,但是构建安全的应用程序甚至更酷。 为此,您需要添加身份验证,以便用户必须先登录才能查看/修改组。 为简化起见,您可以使用Okta的OIDC API。 在Okta,我们的目标是使身份管理比您以往更加轻松,安全和可扩展。 Okta是一项云服务,允许开发人员创建,编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。 我们的API使您能够:

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

你卖了吗注册一个永久免费的开发者帐户,完成后再回来,这样您就可以了解有关使用Spring Boot构建安全应用程序的更多信息!

Spring Security + OIDC

Spring Security在其5.0版本中增加了OIDC支持。 从那时起,他们进行了许多改进并简化了所需的配置。 我认为探索最新和最有趣的东西很有趣,因此我首先使用Spring的快照存储库更新pom.xml ,将Spring Boot和Spring Security升级到夜间构建,并添加必要的Spring Security依赖项来进行OIDC身份验证。

<?xml version="1.0" encoding="UTF-8"?>
<project>...<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.0.BUILD-SNAPSHOT</version><relativePath/> <!-- lookup parent from repository --></parent><properties>...<spring-security.version>5.1.0.BUILD-SNAPSHOT</spring-security.version></properties><dependencies>...<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-client</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId></dependency></dependencies><build...><pluginRepositories><pluginRepository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><snapshots><enabled>true</enabled></snapshots></pluginRepository></pluginRepositories><repositories><repository><id>spring-snapshots</id><name>Spring Snapshot</name><url>http://repo.spring.io/snapshot</url></repository></repositories>
</project>

在Okta中创建OIDC应用

登录到您的1563开发者帐户(或者注册,如果你没有一个帐户)并导航到应用程序>添加应用程序。 单击“ Web” ,然后单击“下一步” 。 给应用程序起一个您会记住的名称,并指定http://localhost:8080/login/oauth2/code/okta作为登录重定向URI。 单击完成,然后单击编辑以编辑常规设置。 将http://localhost:3000http://localhost:8080为注销重定向URI,然后单击保存

将默认授权服务器的URI,客户端ID和客户端密钥复制并粘贴到src/main/resources/application.yml 。 创建此文件,然后可以删除同一目录中的application.properties文件。

spring:security:oauth2:client:registration:okta:client-id: {clientId}client-secret: {clientSecret}scope: openid email profileprovider:okta:issuer-uri: https://{yourOktaDomain}/oauth2/default

为React和用户身份配置Spring Security

为了使Spring Security React友好,请在src/main/java/.../jugtours/config创建一个SecurityConfiguration.java文件。 创建config目录并将该类放入其中。

package com.okta.developer.jugtours.config;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);@Overrideprotected void configure(HttpSecurity http) throws Exception {RequestCache requestCache = refererRequestCache();SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();handler.setRequestCache(requestCache);http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/okta")).and().oauth2Login().successHandler(handler).and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and().requestCache().requestCache(requestCache).and().authorizeRequests().antMatchers("/**/*.{js,html,css}").permitAll().antMatchers("/", "/api/user").permitAll().anyRequest().authenticated();}@Beanpublic RequestCache refererRequestCache() {return new RequestCache() {private String savedAttrName = getClass().getName().concat(".SAVED");@Overridepublic void saveRequest(HttpServletRequest request, HttpServletResponse response) {String referrer = request.getHeader("referer");if (referrer != null) {request.getSession().setAttribute(this.savedAttrName, referrerRequest(referrer));}}@Overridepublic SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) {HttpSession session = request.getSession(false);if (session != null) {return (SavedRequest) session.getAttribute(this.savedAttrName);}return null;}@Overridepublic HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) {return request;}@Overridepublic void removeRequest(HttpServletRequest request, HttpServletResponse response) {HttpSession session = request.getSession(false);if (session != null) {log.debug("Removing SavedRequest from session if present");session.removeAttribute(this.savedAttrName);}}};}private SavedRequest referrerRequest(final String referrer) {return new SavedRequest() {@Overridepublic String getRedirectUrl() {return referrer;}@Overridepublic List<Cookie> getCookies() {return null;}@Overridepublic String getMethod() {return null;}@Overridepublic List<String> getHeaderValues(String name) {return null;}@Overridepublic Collection<String> getHeaderNames() {return null;}@Overridepublic List<Locale> getLocales() {return null;}@Overridepublic String[] getParameterValues(String name) {return new String[0];}@Overridepublic Map<String, String[]> getParameterMap() {return null;}};}
}

这堂课正在进行很多,所以让我解释一些事情。 在年初configure()方法,你建立一个新类型,缓存网址标头(拼错请求缓存的referer在现实生活中),所以Spring Security可以验证后回重定向到它。 当您在http://localhost:3000上开发React并希望在登录后重定向到那里时,基于引用者的请求缓存会派上用场。

@Override
protected void configure(HttpSecurity http) throws Exception {RequestCache requestCache = refererRequestCache();SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();handler.setRequestCache(requestCache);http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/okta")).and().oauth2Login().successHandler(handler).and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and().requestCache().requestCache(requestCache).and().authorizeRequests().antMatchers("/**/*.{js,html,css}").permitAll().antMatchers("/", "/api/user").permitAll().anyRequest().authenticated();
}

authenticationEntryPoint()行使Spring Security自动重定向到Okta。 在Spring Security 5.1.0.RELEASE中,当您仅配置了一个OIDC提供程序时,不需要此行。 它会自动重定向。

使用CookieCsrfTokenRepository.withHttpOnlyFalse()配置CSRF(跨站点请求伪造)保护意味着XSRF-TOKEN cookie将不会被标记为仅HTTP,因此React可以读取它并在尝试操作数据时将其发送回去。

antMatchers行定义了匿名用户可以使用哪些URL。 您将很快进行配置,以便由Spring Boot应用程序服务您的React应用程序,因此允许使用Web文件和“ /”的原因。 您可能会注意到也有一个公开的/api/user路径。 创建src/main/java/.../jugtours/web/UserController.java并使用以下代码填充它。 React会使用此API进行以下操作:1)查找用户是否已通过身份验证,以及2)执行全局注销。

package com.okta.developer.jugtours.web;import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;@RestController
public class UserController {@Value("${spring.security.oauth2.client.provider.okta.issuer-uri}")String issuerUri;@GetMapping("/api/user")public ResponseEntity<?> getUser(@AuthenticationPrincipal OAuth2User user) {if (user == null) {return new ResponseEntity<>("", HttpStatus.OK);} else {return ResponseEntity.ok().body(user.getAttributes());}}@PostMapping("/api/logout")public ResponseEntity<?> logout(HttpServletRequest request,@AuthenticationPrincipal(expression = "idToken") OidcIdToken idToken) {// send logout URL to client so they can initiate logout - doesn't work from the server side// Make it easier: https://github.com/spring-projects/spring-security/issues/5540String logoutUrl = issuerUri + "/v1/logout";Map<String, String> logoutDetails = new HashMap<>();logoutDetails.put("logoutUrl", logoutUrl);logoutDetails.put("idToken", idToken.getTokenValue());request.getSession(false).invalidate();return ResponseEntity.ok().body(logoutDetails);}
}

您还需要创建组时,这样就可以通过你的壶之旅筛选添加用户信息。 在与GroupRepository.java相同的目录中添加UserRepository.java

package com.okta.developer.jugtours.model;import org.springframework.data.jpa.repository.JpaRepository;public interface UserRepository extends JpaRepository<User, String> {
}

GroupRepository.java添加一个新的findAllByUserId(String id)方法。

List<Group> findAllByUserId(String id);

然后将UserRepository注入GroupController.java并在添加新组时使用它来创建(或获取现有用户)。 在那里,修改groups()方法以按用户过滤。

package com.okta.developer.jugtours.web;import com.okta.developer.jugtours.model.Group;
import com.okta.developer.jugtours.model.GroupRepository;
import com.okta.developer.jugtours.model.User;
import com.okta.developer.jugtours.model.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;@RestController
@RequestMapping("/api")
class GroupController {private final Logger log = LoggerFactory.getLogger(GroupController.class);private GroupRepository groupRepository;private UserRepository userRepository;public GroupController(GroupRepository groupRepository, UserRepository userRepository) {this.groupRepository = groupRepository;this.userRepository = userRepository;}@GetMapping("/groups")Collection<Group> groups(Principal principal) {return groupRepository.findAllByUserId(principal.getName());}@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,@AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {log.info("Request to create group: {}", group);Map<String, Object> details = principal.getAttributes();String userId = details.get("sub").toString();// check to see if user already existsOptional<User> user = userRepository.findById(userId);group.setUser(user.orElse(new User(userId,details.get("name").toString(), details.get("email").toString())));Group result = groupRepository.save(group);return ResponseEntity.created(new URI("/api/group/" + result.getId())).body(result);}@PutMapping("/group")ResponseEntity<Group> updateGroup(@Valid @RequestBody Group group) {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();}
}

为了放大更改,它们在groups()createGroup()方法中。 Spring JPA会为您创建findAllByUserId()方法/查询,并且userRepository.findById()使用Java 8的Optional ,这是一个很好的选择。

@GetMapping("/groups")
Collection<Group> groups(Principal principal) {return groupRepository.findAllByUserId(principal.getName());
}@PostMapping("/group")
ResponseEntity<Group> createGroup(@Valid @RequestBody Group group,@AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {log.info("Request to create group: {}", group);Map<String, Object> details = principal.getAttributes();String userId = details.get("sub").toString();// check to see if user already existsOptional<User> user = userRepository.findById(userId);group.setUser(user.orElse(new User(userId,details.get("name").toString(), details.get("email").toString())));Group result = groupRepository.save(group);return ResponseEntity.created(new URI("/api/group/" + result.getId())).body(result);
}

修改React Handle CSRF并识别身份

您需要对React组件进行一些更改,以使它们能够识别身份。 您要做的第一件事是修改App.js以将所有内容包装在CookieProvider 。 该组件允许您读取CSRF cookie并将其作为标题发送回。

import { CookiesProvider } from 'react-cookie';class App extends Component {render() {return (<CookiesProvider><Router...></CookiesProvider>)}
}

修改app/src/Home.js以调用/api/user来查看用户是否已登录。如果未登录,请显示“ Login按钮。

import React, { Component } from 'react';
import './App.css';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';
import { withCookies } from 'react-cookie';class Home extends Component {state = {isLoading: true,isAuthenticated: false,user: undefined};constructor(props) {super(props);const {cookies} = props;this.state.csrfToken = cookies.get('XSRF-TOKEN');this.login = this.login.bind(this);this.logout = this.logout.bind(this);}async componentDidMount() {const response = await fetch('/api/user', {credentials: 'include'});const body = await response.text();if (body === '') {this.setState(({isAuthenticated: false}))} else {this.setState({isAuthenticated: true, user: JSON.parse(body)})}}login() {let port = (window.location.port ? ':' + window.location.port : '');if (port === ':3000') {port = ':8080';}window.location.href = '//' + window.location.hostname + port + '/private';}logout() {console.log('logging out...');fetch('/api/logout', {method: 'POST', credentials: 'include',headers: {'X-XSRF-TOKEN': this.state.csrfToken}}).then(res => res.json()).then(response => {window.location.href = response.logoutUrl + "?id_token_hint=" +response.idToken + "&post_logout_redirect_uri=" + window.location.origin;});}render() {const message = this.state.user ?<h2>Welcome, {this.state.user.name}!</h2> :<p>Please log in to manage your JUG Tour.</p>;const button = this.state.isAuthenticated ?<div><Button color="link"><Link to="/groups">Manage JUG Tour</Link></Button><br/><Button color="link" onClick={this.logout}>Logout</Button></div> :<Button color="primary" onClick={this.login}>Login</Button>;return (<div><AppNavbar/><Container fluid>{message}{button}</Container></div>);}
}export default withCookies(Home);

您应该在此组件中注意一些事项:

  1. withCookies()Home组件包装在底部,以使其可以访问cookie。 然后,您可以在构造const {cookies} = props中使用const {cookies} = props ,并使用cookies.get('XSRF-TOKEN')获取cookie。
  2. 使用fetch() ,您需要包括{credentials: 'include'}来传输cookie。 如果不包含此选项,则将获得“ 403禁止访问”。
  3. Spring Security的CSRF cookie的名称与您需要发回的标头的名称不同。 cookie名称是XSRF-TOKEN ,而标题名称是X-XSRF-TOKEN

更新app/src/GroupList.js以进行类似更改。 好消息是您不需要对render()方法进行任何更改。

import { Link, withRouter } from 'react-router-dom';
import { instanceOf } from 'prop-types';
import { withCookies, Cookies } from 'react-cookie';class GroupList extends Component {static propTypes = {cookies: instanceOf(Cookies).isRequired};constructor(props) {super(props);const {cookies} = props;this.state = {groups: [], csrfToken: cookies.get('XSRF-TOKEN'), isLoading: true};this.remove = this.remove.bind(this);}componentDidMount() {this.setState({isLoading: true});fetch('api/groups', {credentials: 'include'}).then(response => response.json()).then(data => this.setState({groups: data, isLoading: false})).catch(() => this.props.history.push('/'))}async remove(id) {await fetch(`/api/group/${id}`, {method: 'DELETE',headers: {'X-XSRF-TOKEN': this.state.csrfToken,'Accept': 'application/json','Content-Type': 'application/json'},credentials: 'include'}).then(() => {let updatedGroups = [...this.state.groups].filter(i => i.id !== id);this.setState({groups: updatedGroups});});}render() {...}
}export default withCookies(withRouter(GroupList));

也更新GroupEdit.js

import { instanceOf } from 'prop-types';
import { Cookies, withCookies } from 'react-cookie';class GroupEdit extends Component {static propTypes = {cookies: instanceOf(Cookies).isRequired};emptyItem = {name: '',address: '',city: '',stateOrProvince: '',country: '',postalCode: ''};constructor(props) {super(props);const {cookies} = props;this.state = {item: this.emptyItem,csrfToken: cookies.get('XSRF-TOKEN')};this.handleChange = this.handleChange.bind(this);this.handleSubmit = this.handleSubmit.bind(this);}async componentDidMount() {if (this.props.match.params.id !== 'new') {try {const group = await (await fetch(`/api/group/${this.props.match.params.id}`, {credentials: 'include'})).json();this.setState({item: group});} catch (error) {this.props.history.push('/');}}}handleChange(event) {const target = event.target;const value = target.value;const name = target.name;let item = {...this.state.item};item[name] = value;this.setState({item});}async handleSubmit(event) {event.preventDefault();const {item, csrfToken} = this.state;await fetch('/api/group', {method: (item.id) ? 'PUT' : 'POST',headers: {'X-XSRF-TOKEN': csrfToken,'Accept': 'application/json','Content-Type': 'application/json'},body: JSON.stringify(item),credentials: 'include'});this.props.history.push('/groups');}render() {...}
}export default withCookies(withRouter(GroupEdit));

完成所有这些更改后,您应该能够重新启动Spring Boot和React,并见证计划自己的JUG Tour的荣耀!

React
React

配置Maven以使用Spring Boot构建和打包React

要使用Maven构建和打包React应用,可以使用frontend-maven-plugin和Maven的配置文件来激活它。 将版本的属性和<profiles>部分添加到pom.xml

<properties>...<frontend-maven-plugin.version>1.6</frontend-maven-plugin.version><node.version>v10.6.0</node.version><yarn.version>v1.8.0</yarn.version>
</properties><profiles><profile><id>dev</id><activation><activeByDefault>true</activeByDefault></activation><properties><spring.profiles.active>dev</spring.profiles.active></properties></profile><profile><id>prod</id><build><plugins><plugin><artifactId>maven-resources-plugin</artifactId><executions><execution><id>copy-resources</id><phase>process-classes</phase><goals><goal>copy-resources</goal></goals><configuration><outputDirectory>${basedir}/target/classes/static</outputDirectory><resources><resource><directory>app/build</directory></resource></resources></configuration></execution></executions></plugin><plugin><groupId>com.github.eirslett</groupId><artifactId>frontend-maven-plugin</artifactId><version>${frontend-maven-plugin.version}</version><configuration><workingDirectory>app</workingDirectory></configuration><executions><execution><id>install node</id><goals><goal>install-node-and-yarn</goal></goals><configuration><nodeVersion>${node.version}</nodeVersion><yarnVersion>${yarn.version}</yarnVersion></configuration></execution><execution><id>yarn install</id><goals><goal>yarn</goal></goals><phase>generate-resources</phase></execution><execution><id>yarn test</id><goals><goal>yarn</goal></goals><phase>test</phase><configuration><arguments>test</arguments></configuration></execution><execution><id>yarn build</id><goals><goal>yarn</goal></goals><phase>compile</phase><configuration><arguments>build</arguments></configuration></execution></executions></plugin></plugins></build><properties><spring.profiles.active>prod</spring.profiles.active></properties></profile>
</profiles>

在使用时,将活动配置文件设置添加到src/main/resources/application.yml

spring:profiles:active: @spring.profiles.active@security:

添加./mvnw spring-boot:run -Pprod之后,您应该可以运行./mvnw spring-boot:run -Pprod并且您的应用程序可以看到您的应用程序在http://localhost:8080

React
注意:如果您无法登录,则可以尝试在隐身窗口中打开您的应用程序。

Spring Security的OAuth 2.0与OIDC支持

在撰写这篇文章时,我与Rob Winch (Spring Security Lead)合作,以确保我有效地使用了Spring Security。 我开始使用Spring Security的OAuth 2.0支持及其@EnableOAuth2Sso批注。 Rob鼓励我改用Spring Security的OIDC支持,这对使一切正常发挥了作用。

随着Spring Boot 2.1和Spring Security 5.1的里程碑和发行版的发布,我将更新本文以删除不再需要的代码。

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

我希望您喜欢本教程,了解如何使用React,Spring Boot和Spring Security进行CRUD。 您可以看到Spring Security的OIDC支持非常强大,并且不需要大量配置。 添加CSRF保护并将Spring Boot + React应用打包为单个工件也很酷!

您可以在GitHub上的https://github.com/oktadeveloper/okta-spring-boot-react-crud-example上找到本教程中创建的示例。

我们还编写了其他一些很棒的Spring Boot和React教程,如果您有兴趣的话可以查看它们。

  • 使用Spring Boot和React进行Bootiful开发
  • 构建一个React Native应用程序并使用OAuth 2.0进行身份验证
  • 使用Jenkins X和Kubernetes将CI / CD添加到您的Spring Boot应用程序
  • 在15分钟内使用用户身份验证构建React应用程序

如有任何疑问,请随时在下面发表评论,或在我们的Okta开发者论坛上向我们提问。 如果您想查看更多类似的教程,请在Twitter上关注我们!

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

``使用React和Spring Boot构建简单的CRUD应用程序''最初于2018年7月19日发布在Okta开发者博客上。

翻译自: https://www.javacodegeeks.com/2018/07/react-spring-boot-build-crud-app.html

react hooks

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

相关文章

  1. android glsurfaceview renderer

    MyArrow的专栏 目录视图摘要视图订阅 Android系统--视图绘制 (View Render) 分类&#xff1a; Android Framework 2013-11-21 16:29 8208人阅读 评论(4) 收藏 举报 目录(?)[] 1. 简介 Android系统--视图绘制主要由以下几部分组成&#xff1a; 1) Canvas(画布) 提供画图所需要…...

    2024/4/27 7:38:19
  2. 埋线双眼皮全切双眼皮多久恢复自然

    ...

    2024/4/27 4:51:03
  3. 洛阳隔埋线双眼皮会不会留疤痕吗

    ...

    2024/4/27 13:57:56
  4. 交大九院南京双眼皮费用施尔美v

    ...

    2024/4/27 13:55:05
  5. 南京双眼皮哪里做的好医院好

    ...

    2024/4/27 3:50:09
  6. angularJS 正则表达式 验证

    <!DOCTYPE html><html><head><meta charset"UTF-8"><title>anglar为核心验证</title><script src"js/angular.min.js"></script><style>input{display: block;}ul li{color: red;}</style>&l…...

    2024/4/27 2:20:20
  7. angular表达式{{}}先渲染问题

    一般是这样解决的 用angular的指令 ng-cloak指令。这个指令意思是先把这个指令的代码给隐藏&#xff0c;如果有值再显示。但是效果不理想。一般还得自己加一个class <style>[ng-cloak],.ng-cloak{display:none;} </style>...

    2024/4/21 12:04:13
  8. 开双眼皮要拆线吗

    ...

    2024/4/21 12:04:12
  9. augular.js 菜鸟学习笔记 (二)

    AngularJS 控制器 AngularJS 应用程序被控制器控制。 ng-controller 指令定义了应用程序控制器。 控制器是 JavaScript 对象&#xff0c;由标准的 JavaScript 对象的构造函数 创建。 控制器的 $scope 是控制器所指向的应用程序 HTML 元素。 AngularJS 控制器 控制 AngularJS 应…...

    2024/4/27 9:32:28
  10. 寒假还是暑假割精微双眼皮是啥

    ...

    2024/4/21 12:04:09
  11. 学习笔记—angular(1)

    Angularjs概念简述 概念 说明 模板(Template) 带有Angular扩展标记的HTML 指令(Directive) 用于通过自定义属性和元素扩展HTML的行为 模型(Model) 用于显示给用户并且与用户互动的数据 作用域(Scope) 用来存储模型(Model)的语境(context)。模型放在这个语境中才能被控制器、指令…...

    2024/4/21 12:04:09
  12. 男生 割双眼皮 明星图片

    ...

    2024/4/21 12:04:07
  13. 埋线双眼皮线是

    ...

    2024/4/21 12:04:06
  14. 南京双眼皮医院那种双眼皮恢复比较快

    ...

    2024/4/21 12:04:05
  15. 内双做双眼皮的效果

    ...

    2024/4/22 5:44:16
  16. 合肥割双眼皮特定韩美

    ...

    2024/4/26 21:27:50
  17. 开双眼皮会招桃花吗

    ...

    2024/4/20 16:39:15
  18. 从零开始构建一个spring boot + angular web应用(2)

    http://www.jianshu.com/p/46def544bd40 上一部分中&#xff0c;我们引入了spring-boot&#xff0c;此时项目的结构应该如下&#xff1a; Paste_Image.png[ps: 我更改了一下jdk的配置&#xff0c;换成了工作空间配置的jdk。] 接着&#xff0c;继续。 step 1 参考我的另一篇文…...

    2024/4/20 16:39:13
  19. leaflet与angularJS的使用之画线、矩形及自定义iCON

    需求&#xff1a;在angularJS的页面中使用OSM添加相应的元素&#xff0c;画线&#xff0c;画圈&#xff0c;画矩形以及网格。 效果&#xff1a;废话不多说&#xff0c;先看效果。如果不是你需要的效果&#xff0c;那么就不要再往下翻浪费时间了。 详细描述&#xff1a; talk i…...

    2024/4/20 16:39:12
  20. angular 中表单验证的探索

    需求 之前有一段时间做一个搜索查询 但是有很多限制条件&#xff0c;如果校验不成功需要给用户提示错误&#xff0c;当然项目用的是组件库的校验 我能否自己写一个&#xff1f; 其实 我是不会的&#xff01;&#xff01;&#xff01; 探索 angular 的校验功能很强大 自身校验 &…...

    2024/4/20 16:39:11

最新文章

  1. PyQt6 优化操作:建立侧边栏,要求可拖拽改变宽度,可用按钮控制侧边栏的展开和收起

    1. 官方文档 QSplitter — PyQt Documentation v6.6.0 2. 效果展示 可拖拽改变宽度比例 点击按钮快速收起或展开侧边栏 点击按钮&#xff0c;侧边栏收起&#xff0c;同时按钮图标变为向左箭头 (对应展开功能)&#xff0c;再次点击按钮&#xff0c;侧边栏展开&#xff0c;同…...

    2024/4/27 22:33:26
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. BetterZip for Mac2024最新mac解压缩软件

    作为一名软件专家&#xff0c;对于市面上各类软件都有较为深入的了解&#xff0c;下面介绍的是一款适用于Mac系统的解压缩软件——BetterZip&#xff0c;将从其功能特点、使用方法、用户体验及适用人群等方面进行详细介绍。 BetterZip5-安装包绿色版下载如下&#xff1a; htt…...

    2024/4/26 5:49:40
  4. Unity 布局元素Layout Element

    Layout Element是一种用于控制UI元素在布局组件&#xff08;如Horizontal Layout Group、Vertical Layout Group、Grid Layout Group、Content Size Fitter和Aspect Ratio Fitter&#xff09;中的大小和位置的组件。Layout Element组件可以附加到UI元素上&#xff0c;以便在布局…...

    2024/4/26 5:42:36
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57