智慧云教育平台实战项目笔记
智慧云智慧教育平台实战项目笔记一、简介课程内容:智慧云教育平台管理后台、智慧云教育平台学生端、项目的正式部署1、技术说明后端技术:JDK1.8 + SpringBoot + MyBatis + Shiro缓存框架:Redis数据库:MySQL 5.7前端技术:Element-UI + Vue开发工具:IDEA 2019.3.3项目管理工具:Maven、Git使用最主流的框架 SpringBoot
智慧云智慧教育平台实战项目笔记
一、简介
课程内容:智慧云教育平台管理后台、智慧云教育平台学生端、项目的正式部署
1、技术说明
- 后端技术:JDK1.8 + SpringBoot + MyBatis + Shiro
- 缓存框架:Redis
- 数据库:MySQL 5.7
- 前端技术:Element-UI + Vue
- 开发工具:IDEA 2019.3.3
- 项目管理工具:Maven、Git
使用最主流的框架 SpringBoot + Vue 实现完全前后端分离
2、核心功能介绍
- 管理后台核心功能:RBAC权限管理、试题管理、试卷批改
- 学生端核心功能:考试中心、我的错题本
3、学习前提
- 后端技术:掌握SpringBoot + MyBatis + Shiro + MySQL的基本使用
- 前端技术:掌握Vue、Element-UI、CS6语法的基本使用
- 热爱Java编程,喜欢研究新技术
4、课程收获
- 加强对Java程序员基础知识的掌握
- 掌握企业级项目编码规范,提升代码优化的能力
- 掌握企业级 SpringBoot + Vue + Element-UI 全栈开发技能,增加项目经验,提升职场竞争力
- 掌握项目从零搭建到项目正式部署的完整流程
二、Maven介绍及其配置
1、Maven是什么?
Maven是Apache下的一一个纯Java 开发的开源项目。它主要用来帮助实现项目的构建、测试、打包和部署。Maven提供了标准的软件生命周期模型和构建模型,通过配置就能对项目进行全面的管理。想了解更多点击这里
2、Maven的优势
- Maven能够帮助我们快速构建和发布项目,提高工作效率
- Maven能够非常方便的帮助我们管理jar包和解决jar包冲突
- Maven对于目录结构有要求,约定优于配置,开发者在项目间切换就省去了学习成本
- Maven有助于项目的多模块开发
3、Maven项目结构
目录 | 目的 |
---|---|
${basedir} | 存放pom.xml和所有的子目录 |
${basedir}/src/main/java | 项目的java源代码 |
${basedir}/src/main/resources | 项目的资源,比如说property文件,springmvc.xml |
${basedir}/src/test/java | 项目的测试类,比如说Junit代码 |
${basedir}/src/test/resources | 测试用的资源 |
${basedir}/src/main/webapp/WEB-INF | web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面 |
${basedir}/target | 打包输出目录 |
${basedir}/target/classes | 编译输出目录 |
${basedir}/target/test-classes | 测试编译输出目录 |
Test.java | Maven只会自动运行符合该命名规则的测试类 |
~/.m2/repository | Maven默认的本地仓库目录位置 |
4、Maven下载
Maven 下载地址:http://maven.apache.org/download.cgi
不同平台下载对应的包:
系统 | 包名 |
---|---|
Windows | apache-maven-3.3.9-bin.zip |
Linux | apache-maven-3.3.9-bin.tar.gz |
Mac | apache-maven-3.3.9-bin.tar.gz |
下载包后解压到对应目录:
系统 | 存储位置 (可根据自己情况配置) |
---|---|
Windows | D:\Maven\apache-maven-3.3.9 |
Linux | /usr/local/apache-maven-3.3.9 |
Mac | /usr/local/apache-maven-3.3.9 |
5、设置Maven环境变量
右键 “计算机”,选择 “属性”,之后点击 “高级系统设置”,点击"环境变量",来设置环境变量,有以下系统变量需要配置:
新建系统变量 MAVEN_HOME
,变量值:E:\Maven\apache-maven-3.3.9
编辑系统变量 Path
,添加变量值:;%MAVEN_HOME%\bin
**注意:**注意多个值之间需要有分号隔开,然后点击确定。
6、修改Maven配置文件
打开E:\Maven\apache-maven-3.3.9\conf
目录下的setting.xml
文件
修改本地仓库路径:
<localRepository>D:\Apps\Maven-3.8.1\repository</localRepository>
修改镜像源为阿里云
<mirror>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
修改编译器JDK版本为1.8
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
7、pom.xml文件中常见的标签使用
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 描述这个POM文件遵从哪个版本的项目描述符 -->
<modelVersion>4.0.0</modelVersion>
<!-- 项目组织的唯一标识 -->
<groupId>cn.jasondom.springboot</groupId>
<!-- 项目唯一标识 -->
<artifactId>Template</artifactId>
<!-- 项目版本号 -->
<version>1.0.0</version>
<!-- 项目打包类型jar、war、pom -->
<packaging>jar</packaging>
<!-- 项目名称 -->
<name>SpringBoot</name>
<!-- 项目介绍 -->
<description>SpringBoot is a lightweight Java Web Framework</description>
<!-- 项目地址 -->
<url>http://maven.apache.org</url>
<!-- 定义变量,通常用于定义依赖版本 -->
<properties>
<java.version>1.8</java.version>
<velocity.version>2.2.3</velocity.version>
</properties>
<!--jar包依赖列表-->
<dependencies>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
<!--
当前依赖的作用范围:
compile:当前依赖参与所有
runtime:当前依赖仅参与项目的运行阶段
provided:与compile类似,区别在于不参与项目的最终打包
system:从本地磁盘中引用一个jar包(需要添加一个systemPath标签,用于指明jar包路径)
test:当前依赖仅参与项目的单元测试
-->
<scope></scope>
<!-- 排除jar包依赖列表 -->
<exclusions>
<!-- 排除的jar包1 -->
<exclusion>
<groupId>...</groupId>
<artifactId>...</artifactId>
</exclusion>
<!-- 排除的jar包2 -->
<exclusion>
<groupId>...</groupId>
<artifactId>...</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!-- 指定继承父项目的标签 -->
<parent>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<!-- 指定父工程的pom.xml路径,此标签默认值为../pom.xml -->
<relativePath/>
</parent>
<!-- 指定子模块 -->
<modules>
<module>子项目1目录路径</module>
<module>子项目2目录路径</module>
<module>...</module>
</modules>
<!-- 编译配置 -->
<build>
<!--插件列表-->
<plugins>
<plugin>
<groupId></groupId>
<artifactId></artifactId>
</plugin>
</plugins>
</build>
</project>
8、多模块开发的好处
- 降低项目复杂性,提升我们的开发效率
- 有利于项目遵从高内聚,低耦合的设计模式,保证了代码的质量和健壮性
- 避免重复造轮子,减少工作量
9、IDEA上配置Maven
在File
中打开Settings...
,搜索Maven
,根据安装目录修改配置
注意:建议使用Maven-3.5,如果版本过高可能与IDEA不兼容
为了避免所有新建项目都要修改Maven配置,可以打开File
-> Other Settings
-> Settings for New Project...
重新设置一次Maven
三、后端项目环境的搭建
1、创建Maven类型的父工程
接下来点击Next
-> Finish
完成创建,等项目构建完成后删除src
加粗样式目录
2、创建SpringBoot类型的管理模块
(1)、在工程目录上右键单击,选择New
-> Module...
(2)、设置模块名称
(3)、选择基本依赖
(4)、点击Next
-> Finish
完成创建,将管理模块的SpringBoot
父依赖剪切至父工程中,并将版本改为2.1.9.RELEASE
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/>
</parent>
(5)、将管理模块的parent
标签修改成以下内容,使其依赖于父工程
<parent>
<groupId>com.education</groupId>
<artifactId>education</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
(6)、剪切管理模块的依赖列表至父工程
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
(7)、将JDK版本改为1.8
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
3、创建Maven类型的service模块
(1)、在工程目录上右键单击,选择New
-> Module...
(2)、设置模块名称(将GroupId
设置为com.education.service
)
(3)、点击Next
-> Finish
完成创建,打开service
模块下的pom.xml
文件,修改groupId
和父工程为如下内容
<parent>
<groupId>com.education</groupId>
<artifactId>education</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<groupId>com.education.service</groupId>
<artifactId>education-service</artifactId>
<version>1.0-SNAPSHOT</version>
4、创建common工具模块
(1)、在工程目录上右键单击,选择New
-> Module...
(2)、设置模块名称(将GroupId
设置为com.education.common
)
(3)、点击Next
-> Finish
完成创建,打开common
模块下的pom.xml
文件,修改groupId
和父工程为如下内容
<parent>
<groupId>com.education</groupId>
<artifactId>education</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<groupId>com.education.common</groupId>
<artifactId>education-common</artifactId>
<version>1.0-SNAPSHOT</version>
5、修改依赖关系
(1)、添加模块到父工程,在父工程的pom.xml
文件中添加如下内容
<modules>
<module>education-admin-api</module>
<module>education-service</module>
<module>education-common</module>
</modules>
(2)、在admin-api
模块中添加依赖
<dependency>
<groupId>com.education.common</groupId>
<artifactId>education-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.education.service</groupId>
<artifactId>education-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
(3)在service
模块中添加依赖
<dependency>
<groupId>com.education.common</groupId>
<artifactId>education-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
6、测试环境
(1)、在管理模块src/main/resources/
目录下创建如下三个文件(删除默认的application.properties
文件)
配置文件名 | 说明 |
---|---|
application.yml | 默认环境配置文件 |
application-dev.yml | 开发环境配置文件 |
application-prod.yml | 生产环境配置文件 |
(2)、在application.yml
中添加如下配置,激活开发环境配置文件
spring:
profiles:
active: dev
(3)、在application-dev.yml
中添加如下配置,设置服务器访问端口号
server:
port: 80
(4)、在com/education/admin/api/EducationAdminApiApplication.java
入口类中编写测试方法test
@SpringBootApplication
@RestController
public class EducationAdminApiApplication {
@GetMapping
public String test() {
return "success";
}
public static void main(String[] args) {
SpringApplication.run(EducationAdminApiApplication.class, args);
}
}
(5)、删除education-admin-api/src/test/java/com/education/admin/api
包下的test类后,启动main函数
(6)、在浏览器中访问localhost
,如果提示success
表示环境搭建成功
7、Maven中常用的命令
指令 | 功能 |
---|---|
clean | 清除编译后的class文件 |
compile | 编译项目的源代码 |
test | 对项目进行单元测试 |
package | 对项目进行打包 |
install | 对项目进行打包,并安装到本地仓库 |
四、Git简介及基本使用
1、Git简介
1.1 Git是什么?
Git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。
1.2 为什么要使用Git?
- 基于分布式的设计,有利于项目的多人合作开发,提高工作效率
- 方便开发者解决代码冲突
- 可以从当前版本回退到任意版本,防止误操作导致代码丢失
1.3 Git的工作流程
1.4 Git分支的概念
可以理解成一条条的河流,最终都要流入大海(master)
1.5 Git分支结构
2、下载并配置Git
2.1 下载Git
Git 各平台安装包下载地址为:http://git-scm.com/downloads
2.2 在IDEA上配置Git
在File
中打开Settings...
,搜索Git
,根据安装目录修改配置
3、将代码上传至码云仓库
3.2 项目码云步骤
-
注册码云账号 -> https://gitee.com/
-
使用码云创建一个远程仓库
-
在IDEA中将代码上传至码云仓库
3.1 IDEA中将代码上传至码云仓库
1)选择Create Git Repository...
,在弹出的对话框中选择项目路径
2)将代码提交至本地仓库
选中要提交的文件,输入提交信息,点击Commit
即可提交到本地仓库(只提交源码和配置文件,.idea
、.mvn
和编译等文件不用提交)
3)将项目提交至远程仓库
点击Define remote
,输入远程仓库的地址点击OK
,选择Push
即可提交代码至远程仓库
4、代码的更新
(1)、可以直接在码云线上修改代码
(2)添加一个测试方法,并提交
(3)按图示操作,即可更新代码到本地(有的IDEA有多个选项,可以选择Merge
或Branch Default
,但推荐选择Merge
,因为Merge
不仅可以更新代码,在一般情况下,它还会自动帮忙合并代码)
(4)测试完成后将test测试方法删除,重新push到码云(图示中的Commit and Push
可以同时将代码提交到本地和远程仓库,而Commit
只能提交到本地)
5、使用Git解决代码冲突
当多个人修改了同一方法,就会产生代码冲突,下面通过线上线下同时修改代码模拟多人修改同一方法的场景
(1)、线上修改代码并提交
(2)、线下修改代码并提交
提交代码,选择Commit and Push
,弹出对话框后继续选择Commit and Push
,再选择Push
(3)、此时会弹出拒绝提交,需要合并代码的提示,选择Merge
(4)、弹出冲突提示,继续选择Merge
(5)、此时会弹出冲突代码的对比窗口,可以点击第15行代码处指向中间窗口的箭头(>>
或<<
)来控制合并代码,选择好后点击Apply
,再按快捷键Ctrl + Shift + K 重新提交代码
6、将远程仓库代码导入到本地
(1)、从远程仓库新建项目
(2)、输入远程仓库地址,选择项目的存放路径,点击Clone
,弹出提示后点击Yes
(3)、你会发现IDEA没有自动打开项目,可以通过File
-> Open
-> 选择项目的pom.xml
文件打开,此时会多次弹出提示框,按提示操作即可,右下角弹出提示后选择Add as Maven Project
,至此,项目就成功导入了
7、Git分支的使用
可通过右下角的Git xxx
按钮查看当前分支
Local Branches
表示本地分支;Remote Branches
表示远程仓库分支;按钮中的xxx
表示当前分支名称;可以通过New Branch
创建新分支;点击相应分支后会弹出二级菜单:Checkout
表示切换到此分支;Merge into Current
表示合并此分支到当前分支
7.1 创建dev
分支
(1)点击 New Branch
-> 在弹出的对话框中输入分支名dev
,再按快捷键Ctrl + Shift + K 直接提交分支
(2)接下来就可以在码云平台看到新添加的dev
分支了
7.2 切换分支
点击右下角的 Git dev
按钮,点击要切换的分支,在弹出的二级菜单中点击Checkout
即可切换到指定分支
7.3 分支代码的合并
(1)先切换到dev
分支
(2)在SpringBoot入口类中添加 test
方法
(3)按快捷键 Ctrl + K ,按照图示操作之后会弹出提交窗口,选择Push
提交即可
(4)切换到master
分支
(5)将dev
分支合并到master
分支,此时dev
上修改的代码就合并到master
上了
(7)按快捷键Ctrl + Shift + K 直接提交
7.4 在dev
上检出一个bug
分支
(1)先切换到dev
分支
(2)新建一个bug
分支
(3)在SpringBoot
入口类中添加test1
方法
(4)提交(Ctrl + K )
(5)切换到dev
分支,会发现没有test1
方法
(6)将bug
分支合并到dev
分支,再按快捷键Ctrl + Shift + K
提交
(7)最后切换到master分支,按快捷键Ctrl + Shift + K 提交
7.5 使用码云创建分支
(1)登录码云,进入要创建分支的仓库,打开分支管理
(2)创建feature
分支
(3)在IDEA中点击Fetch可以更新分支到本地,更新完成后,在右下角点击git master
即可看到线上新建的feature
分支了
8、码云平台相关功能
可以为项目添加合作伙伴,按不同的职责分配不同的角色权限
五、项目的前期准备
1、Map和传统JavaBean技术选型
1.1 Map的优缺点
优点:
- 灵活性强于JavaBean,易扩展,耦合度低。
- 写起来简单,代码量少
- MyBatis查询的返回结果本身就是Map
缺点:
- 不能一眼看出Map中有哪些参数
1.2 JavaBean的优缺点
优点:
- 符合Java语言面向对象设计的原则
- 数据结构清晰,便于团队开发和后期维护
缺点:
- 需要不断地去维护实体类
1.3 如何选型呢?
- 团队人数少,追求开发效率,建议使用Map代替实体类
- 项目庞大,需要持续维护,团队人数多,建议使用实体类
2、响应结果封装及全局异常处理
2.1 MVC概念
2.2 MVC流程
2.3 前后端分离
2.4 前后端分离的优缺点
优点
- 分工更明确,提升各自领域专注度
- 前后端代码完全分离,有利于项目维护
缺点:
- 需要不断地维护接口文档
- 沟通成本更高
- 部署相比较MVC架构要复杂点
2.5 后端接口统一json数据格式
接口返回成功示例:
{
"code": 1,
"message": "请求成功",
"data": {
"user_info": {
"name": "张三",
"address": "北京市xx区"
}
}
}
接口请求失败示例:
{
"code": 0,
"message": "系统异常"
}
2.6 响应结果封装
(1)给父工程添加以下两个依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
(2)在common
模块下创建utils
包,用于放置系统中的工具类
(3)创建ResultCode
类和Result
类
package com.education.common.utils;
/**
* http 响应状态码
* SUCCESS: 响应成功状态码
* FAIL: 响应失败状态码
* @author Jason
*/
public class ResultCode {
public static final int SUCCESS = 1;
public static final int FAIL = 0;
public static final String DEFAULT_SUCCESS_MESSAGE = "操作成功";
public static final String DEFAULT_FAIL_MESSAGE = "操作失败";
private int code = SUCCESS;
private String message;
public ResultCode() {}
public ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
package com.education.common.utils;
/**
* 对请求结果的封装
* @author Jason
*/
public class Result {
private Object data;
private ResultCode resultCode;
public Result() {}
public Result(ResultCode resultCode) {
this.resultCode = resultCode;
}
public Result(Object data) {
this.resultCode = new ResultCode(ResultCode.SUCCESS, ResultCode.DEFAULT_SUCCESS_MESSAGE);
this.data = data;
}
public Result(ResultCode resultCode,Object data) {
this(resultCode);
this.data = data;
}
/**
* 响应成功
* @param data 响应的数据
* @return 封装的请求
*/
public static Result success(Object data) {
return new Result(data);
}
/**
* 响应成功
* @param resultCode 响应的状态码
* @param data 响应的数据
* @return 封装的请求
*/
public static Result success(ResultCode resultCode, Object data) {
return new Result(resultCode, data);
}
/**
* 响应成功
* @param resultCode 响应的状态码
* @return 封装的请求
*/
public static Result success(ResultCode resultCode) {
return new Result(resultCode);
}
/**
* 响应失败
* @param resultCode 状态码
* @return 封装的请求
*/
public static Result fail(ResultCode resultCode) {
return new Result(resultCode);
}
/**
* 响应失败
* @return 封装的请求
*/
public static Result fail() {
return new Result(new ResultCode(ResultCode.FAIL,ResultCode.DEFAULT_FAIL_MESSAGE));
}
/**
* 判断请求是否成功
* @return 请求的状态:成功 true 失败 false
*/
public boolean isSuccess() {
return ResultCode.SUCCESS == this.resultCode.getCode();
}
public <T> T getData() {
return (T) data;
}
public void setData(Object data) {
this.data = data;
}
public ResultCode getResultCode() {
return resultCode;
}
public void setResultCode(ResultCode resultCode) {
this.resultCode = resultCode;
}
}
2.7 全局异常处理
(1)在com.education.common
下创建exception
包
(2)在exception
包下创建系统业务异常类BusinessException
和全局异常处理类SystemExceptionHandler
package com.education.common.exception;
import com.education.common.utils.ResultCode;
/**
* 系统业务异常类
* @author Jason
*/
public class BusinessException extends RuntimeException {
private ResultCode resultCode;
public BusinessException(ResultCode resultCode) {
this.resultCode = resultCode;
}
public BusinessException(String message) {
super(message);
}
public BusinessException(Throwable throwable) {
super(throwable);
}
public BusinessException(String message, Throwable throwable) {
super(message, throwable);
}
public ResultCode getResultCode() {
return resultCode;
}
public void setResultCode(ResultCode resultCode) {
this.resultCode = resultCode;
}
}
package com.education.common.exception;
import com.education.common.utils.Result;
import com.education.common.utils.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 处理全局异常
* @author Jason
*/
@ControllerAdvice
public class SystemExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(SystemExceptionHandler.class);
@ExceptionHandler(Exception.class)
@ResponseBody
public Result resolveException(Exception e) {
Result result = new Result(new ResultCode(ResultCode.FAIL,ResultCode.DEFAULT_FAIL_MESSAGE));
// 判断是否为业务异常
if(e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
if (businessException.getResultCode() != null) {
result.setResultCode(businessException.getResultCode());
}
}
logger.error("系统异常",e);
return result;
}
}
3、Java线程池技术
3.1 为什么要使用线程池?
减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务,降低了系统资源的消耗
3.2 Web系统中使用多线程的场景
主业务程序与子业务耦合度低:发短信或发送邮件、请求第三方接口
3.3 线程池的执行原理
3.4 线程池的销毁
//等待所有正在执行的任务全试图停止所有正在执行
.shutdoen();
// 部执行完毕之后才会销毁的线程
.shutdownNow();
3.5 线程池的创建方法
Executors
创建线程池
方法名 | 功能 |
---|---|
newFixedThreadPool(int nThreads) | 创建固定大小的线程池 |
newSingleThreadExcutor() | 创建只有一个线程的线程池 |
newCachedThreadPool() | 创建一个不限线程数上限的线程池,任何提交的任务都将立即执行 |
ThreadPoolExecutor
// Java 线程池的完整构造函数
public ThreadPoolExecutor (
int corePoolSize, // 线程池长期维持的线程数,即使线程处于idle状态,也不会回收
int maximumPoolSize, // 线程数的上限
long keepAliveTiem, TimeUnit unit, // 超过corePoolSize的线程的idle时长
// 超过这个时间,多余的线程会被回收
BlookingQueue<Runnable> workQueue, // 任务的排队队列
ThreadFactory threadFactory, // 新线程的产生方式
RejectedExecutionHandler handler) // 拒绝策略
3.6 Spring与线程池的整合
private static final int COUNT = Runtime.getRuntime().availableProcessors(); // cpu个数
private static final int CORE_SIZE = COUNT * 2;
private static final int MAX_SIZE = COUNT * 4;
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setMaxPoolSize(MAX_SIZE);
threadPoolTaskExecutor.setCorePoolSize(CORE_SIZE);
threadPoolTaskExecutor.setQueueCapacity(20);
threadPoolTaskExecutor.setKeepAliveSeconds(200);
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return threadPoolTaskExecutor;
}
4、线程池的使用
4.1 线程资源复用示例
package com.education.common;
import java.util.concurrent.*;
/**
* Unit test for simple App.
*/
public class AppTest
{
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); // 获取cpu数量
private static final int CORE_SIZE = CPU_COUNT * 2;
private static final int MAX_CORE_SIZE = CPU_COUNT * 4;
private static final int QUEUE_SIZE = 30;
static class MyThread implements Runnable {
private int number;
public MyThread(int number) {
this.number = number;
}
@Override
public void run() {
System.out.println("正在执行任务" + number + " " + Thread.currentThread());
}
}
public static void main(String [] args) throws ExecutionException, InterruptedException {
// SynchronousQueue; 是一个无界缓存等待队列,不能指定队列容量
// ArrayBlockingQueue; 是一个游街缓存等待队列,可以指定队列容量
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_SIZE, // 线程池长期维持的线程数,即使线程处于idle状态,也不会回收
MAX_CORE_SIZE, // 线程数的上限
60L, TimeUnit.SECONDS, // 超过corePoolSize线程的idle时长,超过这个时间,多余的线程会被回收
// 此处设置了队列容量,当设置了队列容量后,最大并发量不能超过队列容量和线程数的总和
new LinkedBlockingQueue<>(QUEUE_SIZE));
// System.out.println("并发上限:" + (MAX_CORE_SIZE + QUEUE_SIZE));
for (int i = 0; i < 200; i++) {
// 将任务添加到线程池
Future<?> future = threadPoolExecutor.submit(new MyThread(i));
if (future.get() == null) {
System.out.println(Thread.currentThread() + "任务已完成");
}
// threadPoolExecutor.execute(new MyThread(i));
}
}
}
通过运行结果会发现线程资源被多次复用。
4.2 线程池的拒绝策略
(1)修改for循环体代码:
for (int i = 0; i < 200; i++) {
// 将任务添加到线程池
/*
Future<?> future = threadPoolExecutor.submit(new MyThread(i));
if (future.get() == null) {
System.out.println(Thread.currentThread() + "任务已完成");
}
*/
threadPoolExecutor.execute(new MyThread(i));
}
(2)重新运行,会发现报错(没报错可以增大循环次数),这是因为设置了队列容量QUEUE_SIZE
,当并发量超过线程数和队列容量的总和时,就会抛出异常
(3)可以通过setRejectedExecutionHandler()
方法设置拒绝策略,在for循环上方添加以下语句:
threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
(4)重新运行,会发现所有任务全部执行完成
(5)修改拒绝策略
threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
(6)重新运行,会发现有部分任务没有执行,并且也没有抛出异常
4.3 线程池在实际项目中的使用
(1) 在com.education.common.utils
包下创建SpringBeanManager
类
package com.education.common.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* bean 实例工具类
* @author Jason
*/
@Component
@Lazy(false)
public class SpringBeanManager implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanManager.applicationContext = applicationContext;
}
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return (T) applicationContext.getBean(clazz);
}
}
(2) 在service
模块创建com.education.service.task
包,并创建TaskListener
接口和TaskManager
类
package com.education.service.task;
/**
* 任务监听器
* @author Jason
*/
public interface TaskListener {
void onMessage(TaskParam taskParam);
}
package com.education.service.task;
import com.education.common.utils.SpringBeanManager;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 任务管理器
* @author Jason
*/
public class TaskManager {
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
private final Map<String, TaskListener> taskListenerMap = new ConcurrentHashMap<>();
public TaskManager(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
this.threadPoolTaskExecutor = threadPoolTaskExecutor;
this.registerTaskListener();
}
private void registerTaskListener() {
ApplicationContext applicationContext = SpringBeanManager.getApplicationContext();
String[] beanNames = applicationContext.getBeanNamesForType(TaskListener.class);
for (String name: beanNames) {
TaskListener taskListener = SpringBeanManager.getBean(name);
taskListenerMap.put(name, taskListener);
}
}
public void pushTask(TaskParam taskParam) {
String beanName = taskParam.getTaskListenerBeanName();
TaskListener taskListener = taskListenerMap.get(beanName);
if (taskListener != null) {
threadPoolTaskExecutor.execute(() -> {
taskListener.onMessage(taskParam);
});
}
}
}
(3)封装任务参数类TaskParam
package com.education.service.task;
/**
* 封装任务参数类
* @author Jason
*/
public class TaskParam {
private String taskListenerBeanName;
private final long timestamp;
private Object data;
public TaskParam() {
this.timestamp = 0L;
}
public TaskParam(String taskListenerBeanName, long timestamp, Object data) {
this.taskListenerBeanName = taskListenerBeanName;
this.timestamp = timestamp;
this.data = data;
}
public String getTaskListenerBeanName() {
return taskListenerBeanName;
}
public void setTaskListenerBeanName(String taskListenerBeanName) {
this.taskListenerBeanName = taskListenerBeanName;
}
public long getTimestamp() {
return timestamp;
}
public <T> T getData() {
return (T) data;
}
public void setData(Object data) {
this.data = data;
}
}
(4)创建配置类BeanConfig
package com.education.service.task;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 配置线程池
* @author Jason
*/
@Configuration
public class BeanConfig {
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_SIZE = CPU_COUNT * 2;
private static final int MAX_CORE_SIZE = CPU_COUNT * 4;
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(CORE_SIZE);
threadPoolTaskExecutor.setMaxPoolSize(MAX_CORE_SIZE);
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return threadPoolTaskExecutor;
}
@Bean
public TaskManager taskManager (ThreadPoolTaskExecutor threadPoolTaskExecutor) {
return new TaskManager(threadPoolTaskExecutor);
}
}
(5)创建TaskListener
接口的实现类LogTaskListener
package com.education.service.task;
import org.springframework.stereotype.Component;
/**
* @author Jason
*/
@Component
public class LogTaskListener implements TaskListener{
@Override
public void onMessage(TaskParam taskParam) {
System.out.println("执行LogTaskListener:" + taskParam.getData());
}
}
(6)在admin-api
模块下com.education.admin.api.App
中编写测试代码
package com.education.admin.api;
import com.education.service.task.TaskManager;
import com.education.service.task.TaskParam;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
/**
* @author Jason
* 注解@SpringBootApplication默认扫描当前包,这样会导致无法扫描
* 到com.education.service和com.education.common下的注解,
* 此时就需要设置扫描包路径为com.education
*/
@SpringBootApplication(scanBasePackages = "com.education")
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(App.class, args);
// 获取TaskManager的实例
TaskManager taskManager = applicationContext.getBean(TaskManager.class);
// 定义任务参数
TaskParam taskParam = new TaskParam();
taskParam.setData("test");
// 设置监听器bean实例名称
taskParam.setTaskListenerBeanName("logTaskListener");
taskManager.pushTask(taskParam);
System.out.println(Thread.currentThread().getName());
}
}
5、整合Quartz
5.1 Quartz介绍
5.1.1 Quartz是什么?
Quartz是一个功能丰富的开源任务调度框架,它可以创建简单或者复杂的几十、几百、甚至成千上万的job。此外,quartz调度器还支持JTA事务和集群
5.1.2 Quartz与Spring Task的区别
- Quartz默认多线程异步执行,而Spring Task默认单线程同步执行
- Spring Task属于轻量级任务调度框架,使用更简单
- Quartz在执行任务过程中如果抛出异常,不影响下一次任务的执行,当下一次执行时间到来时,定时器会再次执行任务。而使用Spring Task一旦某个任务在执行过程中抛出异常,则整个定时器生命周期就结束,以后永远不会再执行定时器任务。
- Quartz每次执行都创建一个新的任务类对象,Spring Task则每次使用同一个任务类对象。
5.1.3 Quartz三要素
- **Scheduler:**任务调度器,它是quartz的核心所在,所有的任务都是通过scheduler开始
- **Trigger:**任务触发器
- **JobDetail和Job:**定义任务具体执行的逻辑
5.1.4 Trigger类中常用的方法
方法名 | 功能 |
---|---|
withIdentity(String name, String gtoup) | 给触发器一些属性比如名字,组名 |
startNow() | 立刻启动 |
withSchedule(ScheduleBuilder schedBuilder) | 以某种触发器触发 |
usingJobData(String dataKey, Boolean value) | 给具体job传递参数 |
5.1.5 Cron表达式
Cron表达式用于配置CronTrigger
的实例,由七个子表达式组成,用来描述详细的时间信息
格式 [秒] [分] [时] [日] [月] [周] [年]
5.1.6 配置Cron表达式
Cron表达式的格式:秒 分 时 日 月 周 年(可选)
字段名 | 允许的值 | 允许的特殊字符 |
---|---|---|
秒 | 0—59 | , - * / |
分 | 0—59 | , - * / |
时 | 0—23 | , - * / |
日 | 1—31 | , - * ?/LWC |
月 | 1—12 或 JAN—DEC | , - * / |
周 | 1—7 或 SUN—SAT | , - * ?/LC# |
年(可选字段) | empty | 1970—2099, - * / |
5.1.7 Cron表达式示例
表达式 | 含义 |
---|---|
0 * * * ? | 每一分钟触发一次 |
0 0 10,14,16 * * ? | 每天上午10点,下午2点,4点触发 |
0 0 5-15 * * ? | 每天5-15点整点触发 |
0 0 10 * * ? | 每天10点触发一次 |
*/5 * * * * ? | 每隔5秒执行一次 |
5.2 Quartz定时任务实例
(1)在父类工程中添加Quartz起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
(1)创建任务测试类
package com.education.admin.api;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestJob implements Job {
/**
* 执行具体的业务逻辑
* @param jobExecutionContext
* @throws JobExecutionException
*/
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date));
System.out.println("执行任务:" + TestJob.class.getSimpleName());
}
private static final String DEFAULT_GROUP = "default_group";
public static void main(String[] args) throws SchedulerException {
// 创建一个JobDetail对象
JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity(TestJob.class.getSimpleName(), DEFAULT_GROUP).build();
// 创建任务触发器
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(TestJob.class.getSimpleName(), DEFAULT_GROUP)
.startNow().withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?")).build();
// 创建任务调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
DailyTimeIntervalScheduleBuiIder
:
SimpIeScheduleBuilder
:简单循环执行,设定执行次数,开始结束时间等CronScheduleBuiIder
:Cron表达式实现的定时执行
运行调试会发现每5秒钟会运行一次execute
方法
5.3 SpringBoot整合Quartz
(1)在service
模块下创建job
包,再创建一个任务基类BaseJob
package com.education.service.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
* @author Jason
*/
public abstract class BaseJob extends QuartzJobBean {}
(2)创建系统任务类SystemJob
package com.education.service.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author Jason
*/
public class SystemJob extends BaseJob {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date));
System.out.println("执行任务:" + SystemJob.class.getSimpleName());
}
}
(3)创建任务配置类JobBeanConfig
package com.education.service.job;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Jason
*/
@Configuration
public class JobBeanConfig {
private static final String DEFAULT_GROUP = "default_group";
@Bean
public JobDetail systemJob() {
return JobBuilder.newJob(SystemJob.class)
.withIdentity(SystemJob.class.getSimpleName(), DEFAULT_GROUP).build();
}
@Bean
public Trigger jobTrigger() {
return TriggerBuilder.newTrigger().forJob(systemJob().getKey())
.withIdentity(SystemJob.class.getSimpleName(), DEFAULT_GROUP)
.startNow().withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?")).build();
}
}
(4)修改admin-api
入口类
package com.education.admin.api;
import com.education.service.task.TaskManager;
import com.education.service.task.TaskParam;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
/**
* @author Jason
* 注解@SpringBootApplication默认扫描当前包,这样会
* 导致无法扫描到com.education.service和com.education.common下的注解,
* 所以需要设置扫描包路径为com.education
*/
@SpringBootApplication(scanBasePackages = "com.education")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
/*
ApplicationContext applicationContext = SpringApplication.run(App.class, args);
TaskManager taskManager = applicationContext.getBean(TaskManager.class);
TaskParam taskParam = new TaskParam();
taskParam.setData("test");
taskParam.setTaskListenerBeanName("logTaskListener");
taskManager.pushTask(taskParam);
System.out.println(Thread.currentThread().getName());
*/
}
}
(5)启动main函数,会发现报错
(6)在JobBeanConfig
类systemJob
方法中添加storeDurably()
方法
(7)重启启动main函数,会发现任务每个5秒钟执行一次
六、RBAC权限管理
1、RBAC简介
1.1 RBAC权限管理
- 基于角色的权限访问控制(Role-Based Access Control)
- 用户和角色关联
- 角色关联权限
1.2 RBAC流程
1.3 什么是权限?
权限是资源的集合,主要包括菜单、页面、字段、操作功能(增删改查)等等
-
页面权限
-
操作权限
-
数据权限
1.4 为什么要使用权限?
-
使用者的角度
在限制范围内正确的使用权力
-
设计者角度来说
保证系统更加安全:控制不同的角色合理的访问不同的资源
1.5 RBAC中的功能模块
2、RBAC权限系统数据库设计
2.1 系统(System)
2.1.1 系统管理员表(system_admin)
字段名 | 类型 | KEY | 默认值 / 描述 |
---|---|---|---|
id | INT | PRIMARY KEY | 主键 |
login_name | VARCHAR(100) | NOT NULL | 登录名 |
name | VARCHAR(100) | 真实姓名 | |
password | VARCHAR(100) | NOT NULL | 密码 |
encrypt | VARCHAR(100) | NOT NULL | MD5盐值 |
mobile | VARCHAR(20) | NOT NULL | 手机号 |
disabled_flag | TINYINT(1) | DEFAULT 0 | 1 是 0 否 |
VARCHAR(50) | 邮箱 | ||
last_login_time | DATETIME | 最后登录的时间 | |
login_count | INT(11) | DEFAULT 0 | 登录次数 |
last_login_ip | VARCHAR(100) | 最后登录的IP | |
super_flag | TINYINT(1) | DEFAULT 0 | 是否为超级管理员 |
create_date | DATETIME | 创建时间 | |
update_date | DATETIME | 更新时间 |
2.1.2 系统角色表(system_role)
字段名 | 类型 | KEY | 默认值 / 描述 |
---|---|---|---|
id | INT | PRIMARY KEY | 主键 |
role_name | VARCHAR(100) | NOT NULL | 角色名称 |
remark | VARCHAR(100) | NOT NULL | 备注 |
create_date | DATETIME | 创建时间 | |
update_date | DATETIME | 更新时间 |
2.1.3 系统菜单表(system_menu)
字段名 | 类型 | KEY | 默认值 / 描述 |
---|---|---|---|
id | INT | PRIMARY KEY | 主键 |
name | VARCHAR(100) | NOT NULL | 菜单名称 |
url | VARCHAR(100) | NOT NULL | 菜单地址 |
permission | VARCHAR(100) | NOT NULL | 权限标识 |
icon | VARCHAR(100) | 菜单图标 | |
parent_id | INT | DEFAULT 0 | 父ID |
sort | INT | DEFAULT 0 | 排序 |
type | TINYINT(2) | 菜单类型:0 目录 1 菜单 2 按钮 | |
create_date | DATETIME | 创建时间 | |
update_date | DATETIME | 更新时间 |
2.1.4 系统角色关联表(system_admin_role)
字段名 | 类型 | KEY | 默认值 / 描述 |
---|---|---|---|
id | INT | PRIMARY KEY | 主键 |
role_id | INT | NOT NULL | 角色ID |
admin_id | INT | NOT NULL | 用户ID |
2.1.5 角色权限关联表(system_role_menu)
字段名 | 类型 | KEY | 默认值 / 描述 |
---|---|---|---|
id | INT | PRIMARY KEY | 主键 |
role_id | INT | NOT NULL | 角色ID |
menu_id | INT | NOT NULL | 菜单ID |
2.1.6 地区表(system_region)
2.1.7 系统操作日志表(system_log)
字段名 | 类型 | KEY | 默认值 / 描述 |
---|---|---|---|
id | INT | PRIMARY KEY | 主键 |
operation_name | VARCHAR(255) | 操作详情 | |
request_url | VARCHAR(255) | NOT NULL | 请求接口 |
method | VARCHAR(100) | NOT NULL | 请求方式 |
request_time | VARCHAR(100) | 接口请求时间 | |
user_id | INT(11) | 用户ID(前台或后台用户) | |
params | text | 接口请求参数 | |
exception | text | 接口请求异常信息 | |
platform_type | TINYINT(2) | DEFAULT 1 | 类型(1 系统后台 2 web前端) |
2.1.8 数据字典表(system_dict)
2.2 教学(education)
2.2.1 试题信息表(question_info)
2.2.1 试卷试题关联表(test_paper_question_info)
2.2.2 科目信息表(subject_info)
2.2.3 学校信息表(school_info)
2.2.4 试卷信息表(test_paper_info)
2.2.5 充值卡信息表(refillable_card)
2.2.6 用户答题记录表(user_question_answer)
2.2.7 课程信息表(course_info)
2.2.8 考试记录表(exam_info)
2.3 用户(user_info)
2.4 RBAC权限系统登录流程
2.4.1 前后端分离用户登录流程
2.5 JWP介绍及其使用
2.5.1 JWT简介
JWT:Json Web Token,是基于json的一个公开规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
2.5.2 Json Web Token的组成
(1)Header
头信息通常包含两部分,type:代表token的类型,这里使用的是JWT类型。alg:使用的Hash算法,例如HMAC SHA256或RSA。
例如:{: "alg HS256", : "typ JWT" }
(2)Payload
荷载信息,它包含一些声明Claim(实体的描述,通常是一个User信息,还包括一些其他的元数据)
(3)signature
签证信息,需要使用编码后的header
和payload
以及我们提供的一个 密钥,然后使用header
中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过。
2.5.3 项目实践
(1)在父工程中引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.53</version>
</dependency>
(2)在common
模块下创建包model
,然后在包下创建JwtToken
类
package com.education.common.model;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.util.JSONPObject;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.util.Date;
/**
* @author Jason
*/
public class JwtToken {
private final String secret;
private static final Logger logger = LoggerFactory.getLogger(JwtToken.class);
public JwtToken(String secret) {
if (secret == null) {
throw new NullPointerException("secret value cant not ben null");
}
this.secret = secret;
}
/**
* 生成JWT token
* @param value
* @param expirationTime
* @return
*/
public String createToken(Object value, long expirationTime) {
// 生成SecretKey对象
SecretKey secretKey = this.createSecretKey();
String jsonString = JSONObject.toJSONString(value);
SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date();
JwtBuilder jwtBuilder = Jwts.builder().setIssuedAt(now).setSubject(jsonString).signWith(hs256, secretKey);
if (expirationTime > 0L) {
long expMills = nowMillis + expirationTime;
Date exp = new Date(expMills);
// 设置token过期时间
jwtBuilder.setExpiration(exp);
}
return jwtBuilder.compact();
}
private SecretKey createSecretKey() {
byte[] bytes = DatatypeConverter.parseBase64Binary(secret);
return new SecretKeySpec(bytes, 0, secret.length(), "AES");
}
public <T> T parserToken(String token, Class<T> clazz) {
SecretKey secretKey = this.createSecretKey();
try {
Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJwt(token).getBody();
String subject = claims.getSubject();
return (T) JSONObject.parseObject(subject);
} catch (SignatureException | MalformedJwtException e) {
// token签名失败
logger.error("token 签名失败", e);
} catch (ExpiredJwtException e) {
// token已过期
logger.error("token 已过期", e);
} catch (Exception e) {
logger.error("token 验证异常", e);
}
return null;
}
}
(3)编写单元测试方法
@Test
public void testToken() throws InterruptedException {
JwtToken jwtToken = new JwtToken("education");
Map<String, Object> params = new HashMap<>();
params.put("id", 1);
params.put("name", "我要自学网");
String token = jwtToken.createToken(params, 2000);
System.out.println(token);
Thread.sleep(6000);
Map<String, Object> map = jwtToken.parserToken(token, Map.class);
System.out.println(map);
}
(4)测试运行,发现报错
(5)检查修正
(6)测试运行,发现能正常生成token,但解析时抛出了一个异常
(7)检查修正
(8)测试运行,运行正常
(9)修改有效时间为3秒,模拟token过期
(10)测试运行
更多推荐
所有评论(0)