前言:目前web开发越来越采用前后端分离的开发模式,使从事后台和前端开发的程序员分别精通于自己的方向,同时也有效提高团队的工作效率。后台接口主要返回Json格式的数据,无需按照Spring-MVC模式把渲染视图的工作也交给后台完成;而前端比较流行的三框架有Vue,React和Angular,它们比起传统的js都具有显著的优势。其中React采用虚拟dom节点渲染视图,即每次dom节点发生变化只渲染发生变化的部分(react底层采用了differ算法),显著提升了页面的加载速度和网络开销。前端采用react框架开发出来的web项目一个显著的优点是实现组件化,大大提高代码的复用率,开发过程中代码修改后自动实现编译和热加载。比起传统的javascript实现网页功能,流畅效果非常明显!本文在上一篇spring-boot项目博客的基础上,前端采用react-app实现前后端分离,实现一个简单的用户信息的登录+修改信息等功能,解决了开发过程中遇到的棘手的跨域问题。
1.1 通过npm 使用 React
1) 如果你的系统还不支持 Node.js 及 NPM,可以参考菜鸟教程的Node.js教程:https://www.runoob.com/nodejs/nodejs-tutorial.html
2) 建议在 React 中使用 CommonJS 模块系统,比如 browserify 或 webpack,本文使用 webpack
3)国内使用 npm 速度很慢,你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
$ npm config set registry https://registry.npm.taobao.org
这样就可以使用 cnpm 命令来安装模块了:
$ cnpm install [name]
create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。
create-react-app 自动创建的项目是基于 Webpack + ES6 。
在D:/myproject目录下右键Git Bash Here(需要安装Git)执行以下命令创建项目:
$ cnpm install -g create-react-app
$ create-react-app my-app
$ cd my-app/
$ npm start
在浏览器中打开 http://localhost:3000/ ,结果如下图所示:
项目的目录结构如下:
my-app/
README.md
node_modules/
package.json
.gitignore
public/
favicon.ico
index.html
manifest.json
src/
App.css
App.js
App.test.js
index.css
index.js
logo.svg
manifest.json 指定了开始页面 index.html,一切的开始都从这里开始,所以这个是代码执行的源头。
本项目的前台脚本从修改src/App.js 文件代码开始:
由于项目中需要用到jquery插件,因此在项目的根目录下执行如下Bash脚本
cnpm install jquery
npm安装后的jquery最新版本是3.4.0
一个登陆页面需要一个登陆用的form表单及登陆按钮,贴上修改后的js代码如下
import React, { Component } from 'react';
import * as $ from 'jquery';
// import logo from './logo.svg';
import './App.css';
class App extends Component {
componentDidMount(){
this.login=this.login.bind(this);
}
login(){
let userAccount = $('#userAccount').val();
let password = $('#password').val();
var xhr = new XMLHttpRequest();
xhr.withCredentials=true;
var postUrl = "http://localhost:8080/myApp/login?userAccount="+userAccount+"&password="+password;
xhr.open('post', postUrl, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState ===4 && xhr.status === 200) {
alert(xhr.responseText);
}
};
}
render() {
return (
);
}
}
export default App;
由于本项目的开发在跨域方面遇到了阻塞,因此没有使用传统的ajax请求,而是使用了XMLHttpRequest对象xhr
login()方法中,除了xhr需要设置xhr.withCredentials=true及设置xhr的请求头信息Content-Type为application/x-www-form-urlencoded外(注意,这里不能设置为application/json,否则会报错,Content-Type为application/json格式无法通过的Bug下期楼主会解决)还需要后台服务端设置允许跨域,服务端控制层代码如下
后台代码用的都是“spring-boot项目一”博文中所构建的代码,只是在UserController类上加上了@CrossOrigin注解
package com.spring.samples.impl.controller;
import com.spring.samples.common.enums.ResponseEnum;
import com.spring.samples.common.response.ServiceResponse;
import com.spring.samples.common.web.BaseController;
import com.spring.samples.common.web.form.UserForm;
import com.spring.samples.impl.model.UserInfo;
import com.spring.samples.impl.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
@CrossOrigin(origins = {"http://localhost:3000"},allowCredentials = "true",allowedHeaders = {"X-Custom-Header"},
maxAge = 3600L, methods={RequestMethod.GET,RequestMethod.POST,RequestMethod.HEAD})
public class UserController extends BaseController {
@Autowired
private IUserService userService;
@RequestMapping(path="/saveUser",method= RequestMethod.POST)
public UserInfo saveUser(@RequestBody UserInfo userInfo){
logger.info("/saveUser request parameters:{}",userInfo.toString());
return userService.saveUser(userInfo);
}
@RequestMapping(path = "/findUser",method=RequestMethod.GET)
public UserInfo findUserbyUserAccount(@RequestParam("userAccount") String userAccount){
logger.info("/findUser request param:{}",userAccount);
return userService.findUserByUserAccount(userAccount);
}
@RequestMapping(path="/login",method= RequestMethod.POST)
public ServiceResponse userLogin(@RequestParam("userAccount") String userAccount, @RequestParam("password") String password,
HttpServletRequest request){
UserForm form = new UserForm();
form.setUserAccount(userAccount);
form.setPassword(password);
logger.info("/login request params:{}",form);
ServiceResponse response = null;
if(StringUtils.isEmpty(form.getUserAccount())){
response = new ServiceResponse<>(ResponseEnum.PARAM_ERROR.getStatus(),"userAccount cannot be null");
return response;
}
if(StringUtils.isEmpty(form.getPassword())){
response = new ServiceResponse<>(ResponseEnum.PARAM_ERROR.getStatus(),"password cannot be null");
return response;
}
HttpSession session = request.getSession();
//在此之前未登录
if(null==session.getAttribute("userAccount")){
boolean result = userService.login(form.getUserAccount(),form.getPassword());
if(result){
session.setAttribute("userAccount",form);
// session.setMaxInactiveInterval(30);
response = new ServiceResponse<>(ResponseEnum.SUCCESS.getStatus(),ResponseEnum.SUCCESS.getMsg());
response.setData("login success");
}else{
response = new ServiceResponse<>(ResponseEnum.LOGIN_ERROR.getStatus(),ResponseEnum.LOGIN_ERROR.getMsg());
response.setData("login failed");
}
}else{
response = new ServiceResponse<>(ResponseEnum.SUCCESS.getStatus(),ResponseEnum.SUCCESS.getMsg());
response.setData("You already have login success!");
}
return response;
}
@RequestMapping(path="/logout/{userAccount}",method = RequestMethod.GET)
public ServiceResponse logout(@PathVariable String userAccount, HttpServletRequest request){
logger.info("/logout request param:{}",userAccount);
ServiceResponse response = null;
if(StringUtils.isEmpty(userAccount)){
response = new ServiceResponse<>(ResponseEnum.PARAM_ERROR.getStatus(),"userAccount cannot be null");
return response;
}
HttpSession session = request.getSession();
//删除session中的用户信息
session.removeAttribute("userAccount");
response = new ServiceResponse<>(ResponseEnum.SUCCESS.getStatus(),"login out success");
return response;
}
@RequestMapping(path="/modify/pass",method = RequestMethod.POST)
public ServiceResponse modifyPassword(@RequestBody UserForm form){
logger.info("/modify/pass request params:{}",form);
ServiceResponse response = null;
if(StringUtils.isEmpty(form.getUserAccount())){
response = new ServiceResponse<>(ResponseEnum.PARAM_ERROR.getStatus(),"userAccount cannot be null");
return response;
}
if(StringUtils.isEmpty(form.getPassword())){
response = new ServiceResponse<>(ResponseEnum.PARAM_ERROR.getStatus(),"password cannot be null");
return response;
}
userService.modifyPassword(form.getUserAccount(),form.getPassword());
response = new ServiceResponse<>(ResponseEnum.SUCCESS.getStatus(),ResponseEnum.SUCCESS.getMsg());
response.setData("modify password success");
return response;
}
}
之前使用ajax向后台发送登录请求一直报无法通过预检请求,错误信息如下
jquery.js:8663 OPTIONS http://localhost:8080/myApp/login 403 ()
Failed to load http://localhost:8080/myApp/login: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. The response had HTTP status code 403.
后来发现是我后台写的处理跨域请求的拦截器一直没生效,一时没有找到为啥没生效的原因和解决办法,于是改成在控制器上添加跨域注解
App.css样式表代码如下
.app-body{
margin-top:15px;
width:100%;
height:auto;
font-family: serif;
font-size: 12px;
color: #333333;
background-color: #f1f2f3;
}
form.login-form{
width: 50%;
margin-left: 25%;
margin-top: 30px;
}
div.line-div{
width:100%;
height:35px;
margin-top:10px;
}
label.text{
width:30%;
height:26px;
padding-top:5px;
text-align: right;
font-size:14px;
}
input[type='text'],input[type='password']{
width: 50%;
height: 24px;
padding-top: 4px;
padding-left: 5px;
margin-left: 10px;
border-radius: 2px;
}
div.left-div{
height:32px;
float:left;
}
div.login{
width:30%;
}
div.line-div.btn{
margin-left:5%;
}
input.def-btn.login{
width:85%;
}
div.register{
width:50%;
}
input.def-btn{
width:50%;
height:32px;
text-align: center;
background-color: #61dafb;
color:#ffffff;
font-size: 14px;
border-radius: 10px;
cursor: pointer;
}
input.def-btn.forget-pass{
width:26%;
}
input[name='register']{
color:yellow;
}
#messageBox{
width: 60px;
height:30px;
color:#333333;
text-align: center;
}
启动后台服务器后,在前端myapp项目的根目录下使用Git Bash控制台运行npm start命令,控制台出现Complied successfully !信息表示react项目启动成功,如下图所示
在谷歌浏览器中输入http://localhost:3000/回车后登录界面效果如下图所示
输入数据库中存入正确的账号和密码:heshengfu251/Aa123456后点击登录按钮后弹出如下信息,表示跨域调用后台http://localhost:8080/myApp/login 接口成功!
关于在前后端分离项目中实现跨域的系统解决方案请感兴趣的同学参考下面这篇博文
前端如何去做跨域解决方案