使用Spring+React做一个简单的前后端分离项目
项目简介
这就是一个最简单的前后端分离的项目,由于项目比较小,所以说数据库设计的不是特别的规范,后端的代码也不是很完善可能会有一些bug什么的,这个项目主要讲述的就是React的各种用法吧
后端git地址:https://gitee.com/csmeng/nguc.git
前端git地址:https://gitee.com/csmeng/reactadmin.git
项目准备
我假设大家已经安装过Node和cnpm等前端常用的工具,并且对后端简单的SpringBoot项目开发也有一些了解,由于主要是讲述项目,这些基础的东西就不再一一赘述。
- 后端项目所需要的依赖 ,最基本的的SpringBoot的包还有mybatis,mybatis的分页插件,oss存储等,
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.4</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!-- 分页 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
</dependencies>
- 前端项目所需要的依赖:首先用脚手架创建一个前端的项目
npm install -g create-react-app : 全局下载工具
create-react-app react-admin :下载模板项目
这样就是一个空的前端项目,
package.json里面的内容,更改之后需要npm install或者cnpm install一下,引入依赖
"dependencies": {
"ali-oss": "^6.10.0",
"antd": "^3.18.1",
"axios": "^0.19.2",
"babel-plugin-import": "^1.13.0",
"customize-cra": "^0.2.12",
"draft-js": "^0.10.5",
"draftjs-to-html": "^0.9.1",
"html-to-draftjs": "^1.5.0",
"jsonp": "^0.2.1",
"less": "^3.12.2",
"less-loader": "^5.0.0",
"moment": "^2.27.0",
"react": "^16.2.0",
"react-app-rewired": "^2.1.3",
"react-dom": "^16.2.0",
"react-draft-wysiwyg": "^1.14.5",
"react-router-dom": "^5.2.0",
"react-scripts": "3.0.1",
"store": "^2.0.12"
},
- antd的按需引入问题 :这是一个比较令人纠结的问题,你可以选择不使用按需加载,但是我们下载antd的时候是全部下载的,antd是蚂蚁金服的开源UI框架,确实是比较好用的,使用按需加载之后我们每次打包只打包自己用到的组件即可,其他的没有使用到的样式不会被引入,如果不使用的话则需要引入所有的组件样式,运行效率会降低很多,我们之前环境里面已经有了antd和react-app-rewiredcustomize-crababel-plugin-import这些依赖,接下来进行一些配置就可以了,
const {
override, fixBabelImports,addLessLoader} = require('customize-cra');
module.exports = override(
// 针对antd 实现按需打包,根据import来打包
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
//使用less-loader对源码中的样式进行重新定义
addLessLoader({
javascriptEnabled: true,
modifyVars: {
'@primary-color': '#1890ff'},
}),
);
上面我们不仅声明了antd组件的按需加载,并且也把antd默认的ui颜色变成了我们喜欢的,
到现在为止我们的项目的大致环境已经算是搭建完成了
前端项目整体框架
这些大体的框架的搭建需要路由的使用,前面已经引入过路由的依赖,react-router-dom 接下来使用路由构建出我们整体的页面。
大体就是这样,我们先构建比较上层的登录和主页,其实登录和主界面属于同一级的路由。
在App.js里面配置如下
import React, {
Component} from 'react'
import {
BrowserRouter, Switch, Route} from 'react-router-dom'
import Admin from "./pages/admin/admin";
import WrapLogin from "./pages/login/login";
export default class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path='/login' component={
WrapLogin}/>
<Route path='/' component={
Admin}/>
</Switch>
</BrowserRouter>
)
}
}
其实路由的用法很简单,在外面想要显示的路由的页面来一个BrowserRouter或者HashRouter,然后使用Switch包括住我们想要的界面,Route里面进行各种各样的跳转,path对应的是相应的路径,component对应的是相应的组件,当界面的path路径对应为和某一个Route匹配的时候,页面上就会显示相应的component里面的内容,
当然还有一些比较高级的用法
<Switch>
<Route path='/home' component={
Home}/>
<Route path='/category' component={
Category}/>
<Route path='/product' component={
Product}/>
<Route path='/role' component={
Role}/>
<Route path='/user' component={
User}/>
<Route path='/charts/bar' component={
Bar}/>
<Route path='/charts/line' component={
Line}/>
<Route path='/charts/pie' component={
Pie}/>
<Redirect to='/home'/>
</Switch>
exact代表着精确匹配,有些场景下需要使用到,就好比上述,如果我的路由路径为 /charts/line ,但是其实它是可以匹配两个的 /charts也会被匹配到,这样就会报错,使用exact就可以避免此类问题,当路由路径为 /charts/line 时只能匹配精确的路径,那些模糊也可以的就代表不行,
Redirect则表示当所有的路由都不匹配的时候,重定向到某一个路由,一般都是重定向到首页或者登录页。
登录页面的生成
首先我们来写一下登录页面
首先在App.js文件中已经声明Login这个登录的页面也是属于路由界面的其中之一,所以它也可以使用路由界面的诸多属性,比如路由跳转重定向什么的,
这就是很普通的登录界面,除了样式之外,这还有很多东西需要我们做的,
首先我们先来分析一下登录界面的作用:
1.页面的显示,(使用antd的组件,还有图标什么的)
2.发送请求之前进行验证输入合法性,(还是使用antd的表单进行验证,自定义一些校验的规则,错误的话进行一些提示)
3.发送Ajax请求到后端后端进行校验,
4.后端返回结果的处理,登录成功之后需要重定向到指定界面
5.登录成功之后需要将当前用户的信息保存到我们的缓存当中
6.当打开这个页面的时候,我们需要判断我们缓存中有没有用户对象,如果有的话,就要跳转到主页面
解决方法:
我们定义了两个工具类,一个用来存储我们的用户对象,一个用来操作我们的用户对象,
memoryUtils.js
/**
* 用来在内存中保存一些数据的工具模块
*/
export default {
user: {
}, //保存当前登录的user
role: {
}, //当前登录的用户所拥有的角色
}
storageUtils.js
/**
* 进行local数据存储管理的工具模块
*/
import store from 'store'
const USER_KEY = 'user_key';
const ROLE_KEY = 'role_key';
export default {
/*
保存user,
*/
saveUser(user) {
//localStorage.setItem(USER_KEY,JSON.stringify(user));
store.set(USER_KEY, user);
},
/*
读取user
*/
getUser() {
//return JSON.parse(localStorage.getItem(USER_KEY) || '{}');
return store.get(USER_KEY) || {
}
},
/*
删除user
*/
delUser() {
//localStorage.removeItem(USER_KEY);
store.remove(USER_KEY);
},
/*
保存role,
*/
saveRole(role) {
//localStorage.setItem(USER_KEY,JSON.stringify(user));
store.set(ROLE_KEY, role);
},
/*
读取role
*/
getRole() {
//return JSON.parse(localStorage.getItem(USER_KEY) || '{}');
return store.get(ROLE_KEY) || {
}
},
/*
删除role
*/
delRole() {
//localStorage.removeItem(USER_KEY);
store.remove(ROLE_KEY);
},
}
还有我们也建立了一个api的包,用来定义各种请求向后端发送数据,我们对axios进行一系列的封装,
ajax.js,这个文件的主要作用就是将axios进行封装,将get和post请求进行统一的管理
/**
* 发送异步ajax请求的函数模块
* 封装axios库
* 函数的返回值是promise对象
* 优化:统一处理请求异常
* 在外层包一个自己创建的promise对象
* 在请求出错时,不去reject(error),而是显示错误信息,并返回
* 优化2:异步得到的不是response,而是response.data
* 在请求成功resolve时:直接resolve(response.data)
*/
import axios from 'axios'
import {
message} from 'antd'
export default function ajax(url, data = {
}, type = 'GET') {
return new Promise((resolve,reject) => {
let promise;
//1,执行异步ajax请求
if (type === 'GET') {
//发送get请求
promise = axios.get(url,{
params: data
})
} else {
//发送POST请求
promise = axios.post(url,data);
}
promise.then(response => {
//2.如果成功,调用resolve(value)
resolve(response.data);
}).catch(error => {
//3.如果失败了,不调用reject(reason),而是提示异常信息
message.error('请求出错:',error.message);
resolve(error.message);
})
});
}
接下来一个文件我们计划用来定义所有的ajax请求,然后将他们暴露出来,这样哪个地方需要用到都可以使用
由于这是一个比较小的项目,所以对于一些加密什么的也不是特别的在意,没有使用过常用的那些加密技巧,直接就是最简单的使用用户名和密码进行发送请求进行登录
index.js
export const reqLogin = (username, password) => ajax(BASE + '/portal/login', {
username, password}, 'POST');
当然仅仅这些还不是特别的足够,我们现在的很多请求是无法访问到后端的端口的,因为跨域的问题,所以,需要在package.json里面加入这样的一句话,
"proxy": "http://localhost:8080"
后端方面处理请求:
这里我为了方便自己定义了一些小小的处理思想,
所有的controller都继承一个BaseController,在BaseController里面有一个统一的处理数据将数据转换为Json数据的方法,可能有些臃肿,不过我只是为了省事,
import com.nguc.ngucpractice.common.entity.Result;
import org.springframework.web.servlet.ModelAndView;
public class BaseController {
protected ModelAndView feedback() {
return feedback(null);
}
protected ModelAndView feedback(Result obj) {
Object result = obj != null ? obj : "success";
return new ModelAndView(new JsonView(result));
}
}
还有我将所有的service处理的结果都定义为一个对象Result,这个对象具体有两部分status和data组成,具体的构造方法代码就省略了。
public class Result {
private String status;
private Object data;
}
后端具体的逻辑
controller
@Controller
@RequestMapping("/portal")
public class LoginController extends BaseController {
@Resource
private LoginService loginService;
@RequestMapping("/login")
public ModelAndView doLogin(@Request