转载:
http://download.csdn.net/download/plus_dy/8972653
转载:
http://download.csdn.net/download/plus_dy/8972653
前端:React
1.新建项目
2.组件Book.js Home.js User.js
3.路由模块化 router.js
cnpm install react-router-dom --save (安装)
要在根组件里引入这个,才可以用路由。
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
router.js
import User from '../components/User';
import Home from '../components/Home';
import User from '../components/User';
let routers = [
{
path: "/",
component: Home,
exact:true,
},
{
path: "/user",
component: User,
},
{
path: "/book",
component: Book
},
];
export default routers;
在根组件引入router.js,之后根组件里这样写。
<Router>
<div>
<header className='title'>
<Link to='/'>首页组件</Link>
<Link to='/user'>用户首页</Link>
<Link to='/book'>书籍组件</Link>
</header>
{
routers.map((route,key)=>{
if(route.exact){
return <Route key={key} exact path={route.path}
component={route.component} routes={route.routes}
/>
}else{
return <Route key={key} path={route.path}
component={route.component} routes={route.routes}
/>
}
})
}
</div>
</Router>
写到这里,项目应该可以运行,点击根组件的几个链接可以实现跳转。
接下来是引入css以及在Book.js引入html的页面 让页面变得能看。
index.css
.title{
height: 44px;
line-height: 44px;
background: #000;
}
.title a{
color: white;
padding: 6px;
text-decoration: none;
}
/*左右分栏*/
.content{
width: 100%;
height:500px;
display: flex;
}
.content .left{
width: 200px;
height: 500px;
background: #eeee;
border: 1px solid #eeee;
}
.content .right{
flex:1;
height: 500px;
border: 1px solid #eeee;
}
<div className='book'>
<div className='content'>
<div className='left'>
<Link to='/book/'>书籍列表</Link>
<br/> <br/>
<Link to='/book/addBook'> 添加书籍</Link>
</div>
<div className='right'>
{
}
{/* <Route exact path='/user/' component={UserList}/>
<Route path='/user/UserAdd' component={UserAdd}/> */}
</div>
</div>
</div>
接下来就是路由嵌套,
首先新建两个组件 BookList.js 和 AddBook.js
然后就要在router.js 把这两个子路由写进去。记得放进去别放错了,要放在Book的下面。
routes:[ /*嵌套路由*/
{
path: "/book/",
component: BookList
},
{
path: "/book/AddBook",
component: AddBook
},
]
接下来在根组件里写法要注意。因为路由嵌套,要把嵌套的路由的值传给子组件,这样子在组件里点击链接才能实现跳转。
{
// 前面的js跳转还挺简单的
// 然后是路由嵌套,整了个简单的页面,还是不错的
// 实现了路由的模块化,的确挺不错的,整的怪方便的..也挺麻烦的,真的怪麻烦的,父子组件传值也要在这里面吗,要弄清楚逻辑呀
// 这一节比较难,尤其是到了父子组件传值的那一部分
routers.map((route,key)=>{
if(route.exact){
return <Route key={key} exact path={route.path}
render={props=>(
<route.component {...props} routes={route.routes}/>
)}
/>
}else{
return <Route key={key} path={route.path}
// component={route.component} routes={route.routes}
render={props=>(
<route.component {...props} routes={route.routes}/>
)}
/>
}
})
}
什么?看不懂?看不懂就对了。
render={props=>(
<route.component {...props} routes={route.routes}/>
)}
这一部分真的看不懂,反正就cv吧,
!!!!!最后一步了,在子组件里获取父组件的传的值,然后写个循环,把路由弄出来。
<div className='right'>
{
this.props.routes.map((route,key)=>{
return <Route key={key} exact path={route.path} component={route.component}/>
})
}
{/* <Route exact path='/user/' component={UserList}/>
<Route path='/user/UserAdd' component={UserAdd}/> */}
</div>
几个页面都没有问题,拿这个做图书管理系统的demo。
数据库设计(book 表):
后端 :springboot
参考教程:
https://www.bilibili.com/video/BV1PE411i7CV?p=33
https://blog.csdn.net/wogieni/article/details/88778969
搭项目结构,实现查询书籍列表的功能。
注意pom.xml 需要引入整合的依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter:整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
这个是整合框架要用到的。
在 pom.xml 还发现一些依赖报红了,可以通过降低它们的版本来避免报红。
BookMapper.xml 中的格式有一定要求
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo05.mapper.BookMapper">
</mapper>
然后在 application.yaml 中也要配置数据库,整合等。
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/bookmanage?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
# 整合mybatis
mybatis:
type-aliases-package: com.example.demo05.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
配置基本完成了,现在来写实体类,Book.java
然后写controllr层,BookController
service层,Bookservice BookserviceImpl
mapper层,BookMapper BookMapper.xml
在运行的时候找不到BookMapper.xml文件,原来是目录的问题。
controller层注解要这样写 @RestController 返回的值才对。
要解决 跨域 在后端项目 BookController.java 文件中 @RestController 后加入
@CrossOrigin(origins = {"http://localhost:3000"},allowCredentials = "true",allowedHeaders = {"X-Custom-Header"}, maxAge = 3600L, methods={RequestMethod.GET,RequestMethod.POST,RequestMethod.HEAD})
接着还是前端:
接下来用前端获取后端的数据,然后渲染出来,
在前端获取数据要一个插件,
https://github.com/axios/axios
因为后台已经允许跨域请求,所以使用axios就可以实现请求。
大概就三步:1.安装 2.引入 3.看文档
cnpm install axios --save
import axios from 'axios';
getData=()=>{
//通过axios获取服务器数据
// Make a request for a user with a given ID
var api='http://localhost:8080/booklist';
axios.get(api)
.then( (response)=> {
// handle success
//用到this,要注意this指向
console.log(response.data);
this.setState({
list:response.data,
})
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
//组件的生命周期函数,在页面刷新的时候触发
componentDidMount(){
this.getData();
}
组件加载完成后就去请求数据,在生命周期函数里调用请求数据的方法。
4.接收数据,把数据渲染出来
定义一个list,在请求数据的函数里获得数据,
this.setState({
list:response.data,
})
把数据渲染出来。设计布局,然后遍历。
<div >
<table className='table'>
<thead>
<tr>
<th>书名</th>
<th>作者</th>
<th>ISBN</th>
<th>价格(元)</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
this.state.list.map((v,k)=>{
return (
<tr key={k}>
<td>{v.name}</td>
<td>{v.author}</td>
<td>{v.isbn}</td>
<td>{v.price}</td>
<td>
{/* 修改的话需要再写一个页面,然后父子组件传值,传书的id过去,在另一个页面查询书的信息,然后渲染,然后修改。 */}
<Link to={`/book/modifyBook/${v.id}`}>修改</Link>
<button onClick={this.deleteBook.bind(this,v.id)}>删除</button>
</td>
</tr>
)
})
}
</tbody>
</table>
</div>
到这里,展示图书列表暂时没有问题了。
现在写下一个功能,添加图书,
首先点一个链接(路由)跳到添加图书组件。
<Link to='/book/addBook'> 添加书籍</Link>
在AddBook组件里,
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
useHistory,
useLocation
} from "react-router-dom";
import axios from 'axios';
class addBook extends React.Component {
constructor(props) {
super(props);
this.state = {
name:'',
author:'',
isbn:'',
price:'',
AddBookFlag:false,
}
}
doAddBook=(e)=>{
// e.preventDefault();
let name = this.state.name;
let author = this.state.author;
let isbn = this.state.isbn;
let price = this.state.price;
console.log(name,author,isbn,price);
this.AddBook(name,author,isbn,price);
//正常情况需要请求接口
if(true)
{
this.setState({
AddBookFlag:true,
})
//登录成功,跳转到首页
}else{
alert('登录失败');
}
}
AddBook=(name,author,isbn,price)=>{
//通过axios获取服务器数据
// Make a request for a user with a given ID
// var api='http://localhost:8080/booklist';
var api='http://localhost:8080/addBook'+'?name='+name+'&author='+author+'&isbn='+isbn+'&price='+price;
axios.get(api)
.then( (response)=> {
// handle success
//用到this,要注意this指向
console.log(response);
this.setState({
AddBookFlag:true,
})
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
componentWillUnmount = () => {
this.setState = (state,callback)=>{
return;
};
}
inputChange1=(event)=>{
this.setState({
name:event.target.value,
})
}
inputChange2=(event)=>{
this.setState({
author:event.target.value,
})
}
inputChange3=(event)=>{
this.setState({
isbn:event.target.value,
})
}
inputChange4=(event)=>{
this.setState({
price:event.target.value,
})
}
render() {
if(this.state.AddBookFlag){
// return <Redirect to={{pathname:'/'}}/>
return <Redirect to='/book'/>
}
return (
<div>
<form onSubmit={this.doAddBook}>
书名:<input type='text' name='name' value={this.state.name} onChange={this.inputChange1}/>
<br/><br/>
作者:<input type='text' name='author' value={this.state.author} onChange={this.inputChange2}/>
<br/><br/>
ISBN:<input type='text' name='isbn' value={this.state.isbn} onChange={this.inputChange3} />
<br/><br/>
价格:<input type='text' name='price' value={this.state.price} onChange={this.inputChange4} />
<br/><br/>
<input type='submit' value='添加' />
</form>
</div>
);
}
}
export default addBook;
大致的逻辑就是获取表单里所有的值,然后调一个接口,把参数全部传过去,最后跳转页面。
axios,前端向后端发送请求,并且带上参数,然后在后端里获取参数。差不多难点就是这些。
发送请求,带上参数。在后端获取。
这个我当初一点头绪都没有,不知道咋带参数,经过尝试后,这样简单的两行代码就可以传递并接收数据,不过我相信还有更好的方法求传参,例如封装等。
var api='http://localhost:8080/addBook'+'?name='+name+'&author='+author+'&isbn='+isbn+'&price='+price;
public int addBook(String name,String author,String isbn,double price)
像修改和删除就是用的不同的方法写的,思路是什么,
删除的话就是拿到书籍的id,然后调用一个方法,方法里调用一个接口,记得要传值(书籍的id)。
这个是写在BookList组件里面的。
<button onClick={this.deleteBook.bind(this,v.id)}>删除</button>
deleteBook(id){
console.log(id);
//通过axios获取服务器数据
// Make a request for a user with a given ID
var api='http://localhost:8080/deleteBook?id='+id;
axios.get(api)
.then( (response)=> {
// handle success
//用到this,要注意this指向
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
this.getData();
}
修改的话有点像添加书籍,都是用的路由,不过他这个是动态路由,要传值(传递书籍的id)
{
path: "/book/modifyBook/:id",
component: ModifyBook
},
<Link to={`/book/modifyBook/${v.id}`}>修改</Link>
跳到ModifyBook组件,在这个组件里拿到书籍的id,再调一个接口,查询到这本书的信息,然后渲染到前端,前端得到数据,就可以在页面上修改,然后再调一个接口,记得传书籍的id,修改书的信息。
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
useHistory,
useLocation
} from "react-router-dom";
import axios from 'axios';
class ModifyBook extends React.Component {
constructor(props) {
super(props);
this.state = {
id:'',
name:'',
author:'',
isbn:'',
price:'',
ModifyBookFlag:false,
}
}
getData=(id)=>{
//通过axios获取服务器数据
// Make a request for a user with a given ID
var api='http://localhost:8080/getBook?id='+id;
axios.get(api)
.then( (response)=> {
// handle success
//用到this,要注意this指向
console.log('OKOK');
console.log(response.data);
this.setState({
id:response.data.id,
name:response.data.name,
author:response.data.author,
isbn:response.data.isbn,
price:response.data.price,
})
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
// componentWillUnmount = () => {
// this.setState = (state,callback)=>{
// return;
// };
// }
//组件的生命周期函数,在页面刷新的时候触发
componentDidMount(){
console.log('OK');
console.log(this.props.match.params.id);
let id=this.props.match.params.id;
this.getData(id);
}
doModifyBook=()=>{
console.log('doModifyBook方法');
// 进这个方法,然后提交了表单,很快就被刷新了
let id = this.state.id;
let name = this.state.name;
let author = this.state.author;
let isbn = this.state.isbn;
let price = this.state.price;
console.log(id,name,author,isbn,price);
this.ModifyBook(id,name,author,isbn,price);
this.setState({
ModifyBookFlag:true,
})
}
ModifyBook=(id,name,author,isbn,price)=>{
//通过axios获取服务器数据
// Make a request for a user with a given ID
// var api='http://localhost:8080/booklist';
var api='http://localhost:8080/modifyBook'+'?id='+id+'&name='+name+'&author='+author+'&isbn='+isbn+'&price='+price;
axios.get(api)
.then( (response)=> {
// handle success
//用到this,要注意this指向
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
inputChange1=(event)=>{
this.setState({
name:event.target.value,
})
}
inputChange2=(event)=>{
this.setState({
author:event.target.value,
})
}
inputChange3=(event)=>{
this.setState({
isbn:event.target.value,
})
}
inputChange4=(event)=>{
this.setState({
price:event.target.value,
})
}
render() {
if(this.state.ModifyBookFlag){
// return <Redirect to={{pathname:'/'}}/>
return <Redirect to='/book'/>
}
return (
<div>
<form onSubmit={this.doModifyBook}>
书名:<input type='text' name='name' value={this.state.name} onChange={this.inputChange1}/>
<br/><br/>
作者:<input type='text' name='author' value={this.state.author} onChange={this.inputChange2}/>
<br/><br/>
ISBN:<input type='text' name='isbn' value={this.state.isbn} onChange={this.inputChange3} />
<br/><br/>
价格:<input type='text' name='price' value={this.state.price} onChange={this.inputChange4} />
<br/><br/>
<input type='submit' value='修改' />
</form>
</div>
);
}
}
export default ModifyBook;
目前的主要问题是
前后端没有数据校验,后端没有返回具体的信息,只是调了一个接口,没有返回类似于成功,失败(失败的类型)的信息给前端,然后让前端显示给用户。
第二个问题就是刷新的问题,不管是添加,删除,还是修改,最后都不会刷新书籍列表的数据,这个暂时也不知道咋解决。
代码,在github上。
经过对SpringBoot及VUE的学习,我们已经对他们的开发流程有了一定的了解,我们将这两个框架进行整合,并实现一个简单的前后的开发案例“HelloWorld”。这个案例我们主要实现:(1)系统的登陆、退出;(2)用户的查询、新增、修改、删除。系统架构如下图所示:
从架构图中可以看到前端UI采用VUE框架开发, vue框架整合了mock、validator、vuex等组件。后端包括:控制层、业务服务层和数据访问层,基于SpringBoot2开发,控制层主要实现了rest的http服务以及权限控制;业务服务层主要用于实现业务逻辑,同时也具备了日志、事物、缓存等功能;数据访问层采用mybatis框架实现。本节案例开发采用mysql作为数据库。
后端框架采用SpringBoot2+mybatis+redis整合框架开发,可以参照我们在SpringBoot开发的第一个工程,我们创建一个helloworld的后端工程。结构如下:
SpringBoot工程的主要配置文件包括maven的POM文件和application.properties配置文件。
pom文件内容为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>helloworld</artifactId>
<packaging>jar</packaging>
<version>1.0.1.RELEASE</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<lombok.version>1.16.18</lombok.version>
<commons-lang3.version>3.8.1</commons-lang3.version>
<commons-collections.version>3.2.1</commons-collections.version>
<redis.version>2.9.0</redis.version>
<fastjson.version>1.2.21</fastjson.version>
<swagger2.version>2.2.2</swagger2.version>
<oracle.version>11.2.0.3</oracle.version>
<mysql.version>8.0.11</mysql.version>
<druid-starte.version>1.1.1</druid-starte.version>
<junit.version>4.12</junit.version>
<log4j.version>1.2.15</log4j.version>
<net.minidev.json-smart.version>2.2.1</net.minidev.json-smart.version>
<net.minidev.asm.version>1.0.2</net.minidev.asm.version>
<httpclient.version>4.5.2</httpclient.version>
<shiro.version>1.4.0</shiro.version>
<shiro-redis.version>3.1.0</shiro-redis.version>
<mybatis-spring.version>1.3.2</mybatis-spring.version>
<mybatis-mapper.version>3.4.0</mybatis-mapper.version>
<mybatis-pagehelper.version>1.2.10</mybatis-pagehelper.version>
<guava.version>18.0</guava.version>
<jwt.verson>0.9.0</jwt.verson>
<docker.host>http://127.0.0.1:2375</docker.host>
<docker.repostory>127.0.0.1:5000</docker.repostory>
<docker.registry.name>zone7</docker.registry.name>
<docker.plugin.version>0.4.13</docker.plugin.version>
<skipDockerBuild>false</skipDockerBuild>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 移除嵌入式tomcat插件,需要增加servlet-api,打包时打开注解 -->
<!--<exclusion>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-tomcat</artifactId>-->
<!--</exclusion>-->
<!-- 移除springboot自带的logback日志管理 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 移除嵌入式tomcat插件,需要增加servlet-api,打包时打开注解 -->
<!--<dependency>-->
<!--<groupId>javax.servlet</groupId>-->
<!--<artifactId>javax.servlet-api</artifactId>-->
<!--<version>3.1.0</version>-->
<!--<scope>provided</scope>-->
<!--</dependency>-->
<!-- 引入依赖之后spring.aop.auto默认开启,不需要添加@EnableAspectJAutoProxy来启动 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--安全验证相关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<!--解析JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.verson}</version>
</dependency>
<!-- 安全相关 -->
<!-- log4j2日志管理 (springboot1.4以上不支持log4j) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.1</version>
</dependency>
<!-- log4j2日志管理-->
<!--junit-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>asm</artifactId>
<version>1.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--junit-->
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- mybatis -->
<!-- mybatis通用mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mybatis-mapper.version}</version>
</dependency>
<!-- mybatis通用mapper -->
<!-- mybatis pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${mybatis-pagehelper.version}</version>
</dependency>
<!-- mybatis pagehelper -->
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-starte.version}</version>
</dependency>
<!--druid-->
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--fastjson-->
<!--oracle-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>${oracle.version}</version>
</dependency>
<!--oracle-->
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- mysql -->
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis-->
<!-- mongodb -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- mongodb -->
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger2.version}</version>
</dependency>
<!--swagger2-->
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--shiro redis支持-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro-redis.version}</version>
</dependency>
<!--shiro-->
<!--httpclient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<!--httpclient-->
<!-- 监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--guava-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!--guava-->
<!-- rabbitmq -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!-- rabbitmq -->
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--lombok-->
<!--commons-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<!--commons-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>net.sf.ezmorph</groupId>
<artifactId>ezmorph</artifactId>
<version>1.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<!-- 设置资源文件的编码方式 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
<configuration>
<!-- 不指定单元测试 -->
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>${docker.plugin.version}</version>
<executions>
<execution>
<id>build-image</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
<execution>
<id>push-image</id>
<phase>deploy</phase>
<goals>
<goal>push</goal>
</goals>
<configuration>
<imageName>${docker.repostory}/${docker.registry.name}/${project.artifactId}:${project.version}</imageName>
</configuration>
</execution>
</executions>
<configuration>
<registryUrl>${docker.repostory}</registryUrl>
<pushImage>true</pushImage>
<dockerHost>${docker.host}</dockerHost>
<dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory>
<imageName>${docker.repostory}/${docker.registry.name}/${project.artifactId}:${project.version}</imageName>
<imageTags>
<imageTag>${project.version}</imageTag>
</imageTags>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.properties配置文件,在这里我们将其配置为dev模式,同时在resources下增加一个application-dev.properties的配置文件,用于配置开发测试期间的参数。
文件application.properties
spring.profiles.active=dev
文件application-dev.properties
#================== server ===================#
server.port=8080
#server.context-path=/springboot
#================== mybatis =====================#
mybatis.mapper-locations=classpath:mappers/**/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
#================ mybatis pagehelper ==============#
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
#================== database ===================#
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zone-demo
spring.datasource.username=root
spring.datasource.password=zgq
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
#================== redis ===================#
# redis 单节点地址
spring.redis.host=localhost
# redis 集群
#spring.redis.cluster.nodes=192.168.177.128:7001,192.168.177.128:7002,192.168.177.128:7003
#spring.redis.cluster.max-redirects=3
# Redis 数据库
spring.redis.database=0
# Redis 端口
spring.redis.port=6379
# Redis 密码
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=3000
#================== cache ===================#
spring.cache.type=redis
#================== RabbitMq ===================#
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.listener.concurrency=10
spring.rabbitmq.listener.max-concurrency=20
spring.rabbitmq.listener.prefetch=50
#================== RabbitMq 队列配置 ===================#
mq.env=local
basic.info.mq.exchange.name=${mq.env}:basic:info:mq:exchange
basic.info.mq.routing.key.name=${mq.env}:basic:info:mq:routing:key
basic.info.mq.queue.name=${mq.env}:basic:info:mq:queue
#================== mongoDB 配置 ===================#
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=test
#================== slf4j日志配置 ===================#
# 路径
logging.path=/Users/zgq/logs
logging.file=helloworld.log
#location of config file (default classpath:logback.xml for logback)
#logging.config=
# levels for loggers, e.g. "logging.level.org.springframework=DEBUG" (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF)
logging.level.com.zone7=INFO
#================== 默认密码 ===================#
system.default.password=123456
#================== 监控 ===================#
#actuator端口
management.server.port=8310
#修改访问路径,2.0之前默认是/,2.0默认是/actuator
management.endpoints.web.base-path=/actuator
#开放所有页面节点 ,默认只开启了health、info两个节点
management.endpoints.web.exposure.include=*
#显示健康具体信息,默认不会显示详细信息
management.endpoint.health.show-details=always
首先我们沿用第一章的SpringBoot工程,在 “/src/main/java/com/zone7/demo/helloworld/config/filter”下新增加一个安全过滤器,在实际应用中可以采用Oauth2、SpringSecurity或者Shiro来实现,这里只使用了session保存用户状态,并通过一个Filter的实现类来获取用户是否处于登陆状态来控制系统的安全访问。代码如下所示:
package com.zone7.demo.helloworld.config.filter;
import com.google.common.collect.Sets;
import com.zone7.demo.helloworld.sys.common.RequestHolder;
import com.zone7.demo.helloworld.sys.vo.SysUserVo;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* RequestFilter
* 请求过滤器
* 安全认证
* @author: zone7
* @time: 2019.02.19
*/
@WebFilter(filterName = "RequestFilter", urlPatterns = "/*")
public class RequestFilter implements Filter {
private static Set<String> URL_WHITE_LIST = Sets.newHashSet();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//忽略不过旅的路径
URL_WHITE_LIST.add("/unauth");
URL_WHITE_LIST.add("/login");
URL_WHITE_LIST.add("/logout");
URL_WHITE_LIST.add("/sys/unauth");
URL_WHITE_LIST.add("/sys/login");
URL_WHITE_LIST.add("/sys/logout");
URL_WHITE_LIST.add("/static");
URL_WHITE_LIST.add("/actuator");
URL_WHITE_LIST.add("/oauth");//oauth2.0默认接口
URL_WHITE_LIST.add("/api"); // 外部接口采用oauth进行权限验证
}
private boolean isWhiteUrl(String requestUrl){
if (URL_WHITE_LIST.contains(requestUrl)){
return true;
}
for(String str:URL_WHITE_LIST){
if(requestUrl.startsWith(str)){
return true;
}
}
return false;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestUrl = request.getRequestURI();
if (isWhiteUrl(requestUrl)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
SysUserVo userVo = (SysUserVo) request.getSession().getAttribute("user");
if (userVo == null) {
request.getRequestDispatcher("/unauth").forward(request, response);
return;
}
RequestHolder.add(userVo);
RequestHolder.add(request);
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
3.2 前端开发
采用vue-cli 命令生成工程脚手架(创建命令:vue init webpack helloworld_web),前端工程的名称为“helloworld_web” 。采用IDEA工具打开工程,默认的结构如下图所示:
工程目录默认有了router、compotent、assets,我们还需要增加api、views、store、utils目录。api目录用于存放采用mock实现的前后端交互脚本,views用于存放功能页面vue模板,store用于存放采用vuex实现的状态管理功能,utils用于存放通用工具脚本。工程目录结构如下图所示: