常用框架
约 12175 字大约 41 分钟
(1)Logback
理论基础
slf4j是一系列的日志接口,而log4j、logback是具体实现了的日志框架。
log4j:是apache实现的一个开源日志组件。
logback:同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架。是slf4j的原生实现,也就是说logback实现slf4j是不消耗内存和计算开销的。
Logback是SpringBoot内置的日志处理框架,spring-boot-starter其中包含了spring-boot-starter-logging,该依赖内容就是 Spring Boot 默认的日志框架 logback。
Logback相比log4j的优势,
比log4j更快,重写了内核,在一些关键路径上性能提升10倍,内存占用也更少。
经过大量的测试,和log4j测试不在一个量级。
logback-classic是SLF4J的实现,切换其他日志框架非常方便,
文档丰富并且不断更新,支持Groovy风格的配置文件
配置文件自动重新加载,如果更新了配置文件,logback-classic可以自动重新加载。
自动压缩归档的日志文件,压缩文件是异步进行,不影响应用。
配置文件可以处理不同的环境开发,测试,生产,这样一个配置文件就可以适应多个环境。
官方文档:http://logback.qos.ch/manual/
logback有5种日志级别,等级从低到高分别是TRACE < DEBUG < INFO < WARN < ERROR
Trace:是追踪,就是程序推进以下
Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.
Info:消息在粗粒度级别上突出强调应用程序的运行过程.
Warn:输出警告及warn以下级别的日志.
Error:输出错误信息日志.
OFF表示关闭全部日志
ALL表示开启全部日志。
@Slf4j
Log.debug() / log.info() / log.warn() / log.error()
项目应用
<?xml version="1.0" encoding="UTF-8"?>
<configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.padual.com/java/logback.xsd"
debug="false" scan="true" scanPeriod="30 second">
<property name="PROJECT" value="iorder" />
<property name="ROOT" value="logs/${PROJECT}/" />
<property name="FILESIZE" value="50MB" />
<property name="MAXHISTORY" value="100" />
<timestamp key="DATETIME" datePattern="yyyy-MM-dd HH:mm:ss" />
<!-- 控制台打印 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="utf-8">
<pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
</pattern>
</encoder>
</appender>
<!-- ERROR 输入到文件,按日期和文件大小 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder charset="utf-8">
<pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${ROOT}%d/error.%i.log</fileNamePattern>
<maxHistory>${MAXHISTORY}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${FILESIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- WARN 输入到文件,按日期和文件大小 -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder charset="utf-8">
<pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${ROOT}%d/warn.%i.log</fileNamePattern>
<maxHistory>${MAXHISTORY}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${FILESIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- INFO 输入到文件,按日期和文件大小 -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder charset="utf-8">
<pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${ROOT}%d/info.%i.log</fileNamePattern>
<maxHistory>${MAXHISTORY}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${FILESIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- DEBUG 输入到文件,按日期和文件大小 -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder charset="utf-8">
<pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${ROOT}%d/debug.%i.log</fileNamePattern>
<maxHistory>${MAXHISTORY}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${FILESIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- TRACE 输入到文件,按日期和文件大小 -->
<appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder charset="utf-8">
<pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${ROOT}%d/trace.%i.log</fileNamePattern>
<maxHistory>${MAXHISTORY}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${FILESIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- SQL相关日志输出-->
<logger name="org.apache.ibatis" level="INFO" additivity="false" />
<logger name="org.mybatis.spring" level="INFO" additivity="false" />
<logger name="com.github.miemiedev.mybatis.paginator" level="INFO" additivity="false" />
<!-- Logger 根目录 -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="DEBUG" />
<appender-ref ref="ERROR" />
<appender-ref ref="WARN" />
<appender-ref ref="INFO" />
<appender-ref ref="TRACE" />
</root>
</configuration>
(2)Druid
理论基础
Druid(德鲁伊)数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了 日志监控,可以很好的监控DB池连接和SQL的执行情况,是针对监控而生的DB连接池,是目前最好的连接池之一。
Druid 是阿里巴巴开源的数据库连接池,它是一个高效的并发连接管理工具,可大幅提升应用程序的数据库访问效率。它支持对于许多不同类型的数据库,例如 MySQL、Oracle、PostgreSQL 和 SQL Server 等。Druid 是一款全功能性的数据库连接池,拥有丰富的功能和可自定义配置选项,使得它非常适合在各种应用程序中使用。
项目应用
http://localhost:8080/druid
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
spring:
application:
name: ssm-father
datasource:
# 数据库相关配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db01?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
druid:
# 初始化连接数量
initial-size: 5
# 最小线连接数量
min-idle: 5
# 最大连接数量
max-active: 20
# 获取连接时最大等待时间,单位毫秒
max-wait: 60000
#销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
min-evictable-idle-time-millis: 30000
#用来检测连接是否有效的sql 必须是一个查询语句
#mysql中为 select 'x'
#oracle中为 select 1 from dual
validation-query: select 'x'
#申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-borrow: false
#归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为tru
test-on-return: false
# 配置监拉统计挡成的filters. stat: 监控统计
filters: stat
# 配置后台监控
stat-view-servlet:
# 允许访问的地址,这里因为时本地所以配置当前机器
allow: 127.0.0.1
# 是否开启访问
enabled: true
# 是否能够重置数据
reset-enable: false
# 管理页面登陆的用户名
login-username: admin
# 管理页面登陆的密码
login-password: admin
(3)Junit
理论基础
JUnit是一个Java语言的单元测试框架。JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
Junit5.x 版本首先需要 Java 8 以上的运行环境,虽然在旧版本 JDK 也能编译运行,但要完全使用 JUnit 5 功能, JDK 8 环境是必不可少的。
JUnit与另一个框架 TestNG 占据了 Java领域里单元测试框架的主要市场,其中 JUnit 有着较长的发展历史和不断演进的丰富功能,备受大多数 Java 开发者的青睐。
在断言 API 设计上,JUnit 5 进行显著地改进,并且充分利用 Java 8 的新特性,特别是 Lambda 表达式,最终提供了新的断言类: org.junit.jupiter.api.Assertions 。许多断言方法接受 Lambda 表达式参数
常用注解
@BeforeAll:在整个测试类中所有的测试方法之前执行,每个测试类只会执行一次。
@AfterAll:在所有测试方法都执行完毕后执行的,每个测试类只会执行一次。
@Test:表示该方法是一个测试方法。
@BeforeEach:在每一个测试方法之前执行。
@AfterEach:在每一个测试方法执行之后都会执行。
常用断言
assertEquals:断言预期和实际是相等的。
assertTrue:断言提供的条件为true
assertFalse:断言提供的条件不是真。
assertNotNull:断言提供的条件不为null
assertNull:断言提供的实际为null
项目经验
/**
* Junit5注解
*/
@DisplayName("我的第一个测试用例")
public class MyTestCase01 {
@BeforeAll
public static void init() {
System.out.println("初始化数据");
}
@AfterAll
public static void cleanup() {
System.out.println("清理数据");
}
@BeforeEach
public void tearup() {
System.out.println("当前测试方法开始");
}
@AfterEach
public void tearDown() {
System.out.println("当前测试方法结束");
}
@DisplayName("我的第一个测试")
@Test
void testFirstTest() {
System.out.println("我的第一个测试开始测试");
}
@DisplayName("我的第二个测试")
@Test
void testSecondTest() {
System.out.println("我的第二个测试开始测试");
}
}
/**
* Junit5断言
*/
class MyTestCase02 {
@Test
void standardAssertions() {
assertEquals(2, 2);
assertEquals(4, 4, "error message");
assertTrue(2 == 2, () -> "error message");
}
@Test
void groupedAssertions() {
// 分组断言,执行分组中所有断言,分组中任何一个断言错误都会一起报告
assertAll("person", () -> assertEquals("John", "John"), () -> assertEquals("Doe", "Doe"));
}
@Test
void dependentAssertions() {
// 分组断言
assertAll("properties", () -> {
// 在代码块中,如果断言失败,后面的代码将不会运行
String firstName = "John";
assertNotNull(firstName);
// 只有前一个断言通过才会运行
assertAll("first name", () -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("n")));
}, () -> {
// 分组断言,不会受到first Name代码块的影响,所以即使上面的断言执行失败,这里的依旧会执行
String lastName = "Doe";
assertNotNull(lastName);
// 只有前一个断言通过才会运行
assertAll("last name", () -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e")));
});
}
@Test
void exceptionTesting() {
// 断言异常,抛出指定的异常,测试才会通过
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
@Test
void timeoutNotExceeded() {
// 断言超时
assertTimeout(ofMinutes(2), () -> {
// 完成任务小于2分钟时,测试通过。
});
}
@Test
void timeoutNotExceededWithResult() {
// 断言成功并返回结果
String actualResult = assertTimeout(ofMinutes(2), () -> {
return "result";
});
assertEquals("result", actualResult);
}
@Test
void timeoutExceeded() {
// 断言超时,会在任务执行完毕后才返回,也就是1000毫秒后返回结果
assertTimeout(ofMillis(10), () -> {
// 执行任务花费时间1000毫秒
Thread.sleep(1000);
});
}
@Test
void timeoutExceededWithPreemptiveTermination() {
// 断言超时,如果在10毫秒内任务没有执行完毕,会立即返回断言失败,不会等到1000毫秒后
assertTimeoutPreemptively(ofMillis(10), () -> {
Thread.sleep(1000);
});
}
}
/**
* 代码覆盖率
* 注意:测试的类必须是src/main/java中的源代码!!
*/
public class MyTestCase03 {
M m = new M();
@Test
void max() {
System.out.println("测试max方法");
Assertions.assertEquals(3, m.max(3,2,1));
}
@Test
void sum() {
System.out.println("测试sum方法");
int[] arr = {1,2,3};
Assertions.assertEquals(6, m.sum(arr));
}
}
public class M {
public int max(int a, int b, int c) {
if (a > b && a > c) {
return a;
} else if (b > a && b > c) {
return b;
} else {
return c;
}
}
public int sum (int[] arr) {
if (arr == null) {
return 0;
}
int sum = 0;
for (int i: arr) {
sum += i;
}
return sum;
}
}
(4)Validation
理论基础
在 Web 应用中,客户端提交数据之前都会进行数据的校验,比如用户注册时填写的邮箱地址是否符合规范、用户名长度的限制等等,不过这并不意味着服务端的代码可以免去数据验证的工作,用户也可能使用 HTTP 工具直接发送违法数据。为了保证数据的安全性,服务端的数据校验是必须的。
JSR-303 是 JavaEE 6 中的一项子规范,又称作 Bean Validation,提供了针对 Java Bean 字段的一些校验注解,如@NotNull,@Min等。JSR-349 是其升级版本,添加了一些新特性。
Hibernate Validator 是对这个规范的实现(与 ORM 框架无关),并在它的基础上增加了一些新的校验注解。
Spring 本身也有一个校验接口Validator,位于 org.springframework.validation 包下,但是使用这个接口需要进行硬编码,也就是手动校验,没有提供注解进行简化。为了给开发者提供便捷,Spring 也全面支持 JSR-303、JSR-349 规范,对 Hibernate Validation 进行二次封装,在 SpringMVC 模块中添加了自动校验机制,可以利用注解对 Java Bean 的字段的值进行校验,并将校验信息封装进特定的类中。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
项目经验
@Data
public class PersonAddDTO {
@NotBlank(message = "name不能为空或者空格")
@Size(min=3, max=9,message = "长度在3-9之间")
private String name;
@NotNull(message = "age不能为空")
@Max(value=60,message = "年龄最大60")
@Min(value=18,message = "年龄最小18")
private Integer age;
}
@PostMapping("/add")
public boolean add(@Validated @RequestBody PersonAddDTO personAddDTO) {
Person person = BeanConvertUtils.convertTo(personAddDTO, Person::new);
return personService.save(person);
}
@RestControllerAdvice
@Component
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
// 获取异常信息
BindingResult bindingResult = e.getBindingResult();
List<ObjectError> allErrors = bindingResult.getAllErrors();
// 构建返回结果
Map<String, Object> result = new LinkedHashMap<>();
result.put("code", "400");
result.put("message", "参数校验失败");
Map<String, Object> errors = new LinkedHashMap<>();
allErrors.forEach(error -> errors.put(((FieldError)error).getField(), error.getDefaultMessage()));
result.put("errors", errors);
return result;
}
}
(5)Lombok
理论基础
Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码。仅五个字符(@Data)就可以替换数百行代码从而产生干净,简洁且易于维护的Java类。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
项目应用
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class {
String name;
int age;
public static void main(String[] args) {
Person person = new Person().setName("张三").setAge(30);
System.out.println(person);
Person person2 = new Person("李四",40);
System.out.println(person2);
}
}
@Data
@Accessors(chain = true)
@Slf4j
public class Member {
String name;
int age;
public static void main(String[] args) {
Member member = new Member().setName("张三").setAge(30);
// Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。
log.error("error信息");
log.warn("warn信息");
log.info("info信息" + member);
log.debug("debug信息");
}
}
(6)Fastjson
理论基础
Fastjson是阿里巴巴的开源一个JSON解析库,通常被用于将Java Bean和JSON 字符串之间进行转换。
Fastjson是一个Java语言编写的高性能功能完善的JSON库。它采用一种“假定有序快速匹配”的算法,把JSON Parse的性能提升到极致,是目前Java语言中最快的JSON库。Fastjson接口简单易用,已经被广泛使用在缓存序列化、协议交互、Web输出、Android客户端等多种应用场景。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
项目应用
public class FastjsonTest {
@Test
void testJSONObject(){
JSONObject jsonObject = JSON.parseObject("{\"name\":\"张三\",\"age\":30}");
Person person = jsonObject.toJavaObject(Person.class);
System.out.println(person);
}
@Test
void testJSONArray(){
JSONArray jsonArray = JSON.parseArray("[{\"name\":\"张三\",\"age\":30},{\"name\":\"李四\",\"age\":40}]");
List<Person> list = jsonArray.toJavaList(Person.class);
list.forEach(System.out::println);
}
}
(7)Knife4j
理论基础
https://doc.xiaominfo.com/
http://localhost:8080/doc.html
Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案
<!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
项目应用
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
@Bean(value = "dockerBean")
public Docket dockerBean() {
//指定使用Swagger2规范
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
//描述字段支持Markdown语法
.description("# Knife4j RESTful APIs")
.termsOfServiceUrl("https://doc.xiaominfo.com/")
.contact("931701567@qq.com")
.version("1.0")
.build())
//分组名称
.groupName("用户服务")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.zhaoyang.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
@Api(tags = "首页模块")
@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {
@Autowired
PersonService personService;
@ApiImplicitParam(name = "name",value = "姓名",required = true)
@ApiOperation(value = "添加")
@PostMapping("/add")
public boolean add(@Validated @RequestBody PersonAddDTO personAddDTO) {
Person person = BeanConvertUtils.convertTo(personAddDTO, Person::new);
return personService.save(person);
}
@ApiImplicitParam(name = "id",value = "主键",required = true)
@ApiOperation(value = "查询ID")
@GetMapping("/info")
public Person info(int id) {
Person person = personService.getById(id);
log.info("##调用info方法##\t" + person);
return person;
}
@ApiOperation(value = "查询所有")
@GetMapping("/list")
public List<Person> list() {
List<Person> personList = personService.list();
log.info("##调用list方法##\t" + personList);
return personList;
}
}
(8)Upload
后台实现
【FileVO】
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class FileVO {
private String virtualPath; //前台访问的逻辑路径
private String fileName; //文件名称
}
【FileController】
@RestController
@CrossOrigin
public class FileController {
@PostMapping("/upload")
public FileVO upload(MultipartFile file) throws IOException {
FileVO fileVO = UploadUtils.upload(file);
return fileVO;
}
@GetMapping("/download")
public void downloadFile(HttpServletResponse response, String filePath) throws IOException {
// 清空输出流
response.reset();
response.setContentType("application/x-download;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename="+ new String(filePath.getBytes("utf-8"), "utf-8"));
UploadUtils.download(response.getOutputStream(),filePath);
}
}
【文件上传下载工具类】
public class UploadUtils {
private static String localPathDir = "d:/files";
public static FileVO upload(MultipartFile file) throws IOException {
//获取文件名称
String fileName = file.getOriginalFilename();
//实现分目录存储
String datePath =
new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
//最终本地图片存储路径
String localDir = localPathDir + datePath;
//需要创建目录
File dirFile = new File(localDir);
if(!dirFile.exists()){
dirFile.mkdirs();
}
//采用UUID防止文件重名
String uuid = UUID.randomUUID().toString()
.replace("-", "");
//获取文件类型
String fileType =
fileName.substring(fileName.lastIndexOf("."));
//重新拼接文件名
String realFileName = uuid + fileType;
//最终文件存储的路径+文件名 = d:/files/2021/11/11/uuid.xls
String filePathAll = localDir + realFileName;
//实现文件上传
File realFile = new File(filePathAll);
file.transferTo(realFile);
//封装FileVO对象 //2021/11/11/uuid.pdf 图片路径 稍后给前台传递
//我们不可能将filePathAll告诉用户,这样不安全,容易被攻击
String virtualPath = datePath + realFileName;
//将文件存储路径(半个路径,没有具体盘符或根目录) 和 重命名后的文件名 封装到实体类中
return new FileVO(virtualPath,realFileName);
}
public static Object download(OutputStream os, String filePath) throws IOException {
//下载文件的路径
String downPath = localPathDir+filePath;
//读取目标文件
File f = new File(downPath);
//创建输入流
InputStream is = new FileInputStream(f);
//做一些业务判断,我这里简单点直接输出,你也可以封装到实体类返回具体信息
if (is == null) {
System.out.println("文件不存在");
}
//利用IOUtils将输入流的内容 复制到输出流
//org.apache.tomcat.util.http.fileupload.IOUtils
//项目搭建是自动集成了这个类 直接使用即可
IOUtils.copy(is, os);
os.flush();
is.close();
os.close();
return null;
}
}
前台实现
【Vue前端代码】
$(function(){
var model = {tu:'', url:''};
var vm = new Vue({
el:'#app',
data:model,
methods:{
upload(e){
var file = e.target.files[0];// 文件对象
var formdata = new FormData();// 表单对象
formdata.append('file', file);// 把文件放入表单中
axios.post('/file/upload',formdata,{
header:{'Content-Type':'multipart/form-data'}
}).then((res) => {
alert(res.data);
this.tu = '/image/' + file.name;
this.url = '/image/' + file.name;
});
}
}
});
});
(9)spring-boot-devtools
理论基础
Spring Boot 提供了一组开发工具 spring-boot-devtools 可以提高开发者的工作效率,开发者可以将该模块包含在任何项目中,spring-boot-devtools 最方便的地方莫过于热部署了。
Spring Boot DevTools使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为restart ClassLoader,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。
项目应用
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
DEA工具热部署设置
选择 IDEA 工具界面的【 File 】 -> 【 Settings 】选项,打开 Compiler 面板设置页面
选择 Build 下的 Compiler 选项,在右侧勾选 “Build project automatically” 选项将项目设置为自动编译,单击【 Apply 】 → 【 OK 】按钮保存设置
ctrl+alt+s,进入设置,然后选择高级设置,Advanced Settings
在Compiler下勾选 Allow auto-make to restart even if developed application is currently running。
(10)Hutool
理论基础
Hutool是一个小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.7.22</version>
</dependency>
项目应用
class HutoolTests {
// BeanUtil
// JavaBean的工具类,可用于Map与JavaBean对象的互相转换以及对象属性的拷贝。
@Test
void m1() {
Person person = new Person();
person.setName("张三");
person.setAge(30);
Map map = BeanUtil.beanToMap(person);
System.out.println(map);
}
// Convert
// 类型转换工具类,用于各种类型数据的转换。
@Test
void m2() {
//转换为字符串
int a = 1;
String aStr = Convert.toStr(a);
//转换为指定类型数组
String[] b = {"1", "2", "3", "4"};
Integer[] bArr = Convert.toIntArray(b);
//转换为日期对象
String dateStr = "2017-05-06";
Date date = Convert.toDate(dateStr);
//转换为列表
String[] strArr = {"a", "b", "c", "d"};
List<String> strList = Convert.toList(String.class, strArr);
}
// DateUtil
//日期时间工具类,定义了一些常用的日期时间操作方法。
@Test
void m3() {
//Date、long、Calendar之间的相互转换
//当前时间
Date date = DateUtil.date();
//Calendar转Date
date = DateUtil.date(Calendar.getInstance());
//时间戳转Date
date = DateUtil.date(System.currentTimeMillis());
//自动识别格式转换
String dateStr = "2017-03-01";
date = DateUtil.parse(dateStr);
//自定义格式化转换
date = DateUtil.parse(dateStr, "yyyy-MM-dd");
//格式化输出日期
String format = DateUtil.format(date, "yyyy-MM-dd");
//获得年的部分
int year = DateUtil.year(date);
//获得月份,从0开始计数
int month = DateUtil.month(date);
//获取某天的开始、结束时间
Date beginOfDay = DateUtil.beginOfDay(date);
Date endOfDay = DateUtil.endOfDay(date);
//计算偏移后的日期时间
Date newDate = DateUtil.offset(date, DateField.DAY_OF_MONTH, 2);
//计算日期时间之间的偏移量
long betweenDay = DateUtil.between(date, newDate, DateUnit.DAY);
}
// StrUtil
// 字符串工具类,定义了一些常用的字符串操作方法。
@Test
void m4() {
//判断是否为空字符串
String str = "test";
StrUtil.isEmpty(str);
StrUtil.isNotEmpty(str);
//去除字符串的前后缀
StrUtil.removeSuffix("a.jpg", ".jpg");
StrUtil.removePrefix("a.jpg", "a.");
//格式化字符串
String template = "这只是个占位符:{}";
String str2 = StrUtil.format(template, "我是占位符");
}
// ReflectUtil
// Java反射工具类,可用于反射获取类的方法及创建对象。
@Test
void m5() {
//获取某个类的所有方法
Method[] methods = ReflectUtil.getMethods(Person.class);
//获取某个类的指定方法
Method method = ReflectUtil.getMethod(Person.class, "getName");
//使用反射来创建对象
Person person = ReflectUtil.newInstance(Person.class);
//反射执行对象的方法
ReflectUtil.invoke(person, "setName", "张三");
}
// NumberUtil
// 数字处理工具类,可用于各种类型数字的加减乘除操作及判断类型。
@Test
void m6() {
double n1 = 1.234;
double n2 = 1.234;
double result;
//对float、double、BigDecimal做加减乘除操作
result = NumberUtil.add(n1, n2);
result = NumberUtil.sub(n1, n2);
result = NumberUtil.mul(n1, n2);
result = NumberUtil.div(n1, n2);
//保留两位小数
BigDecimal roundNum = NumberUtil.round(n1, 2);
String n3 = "1.234";
//判断是否为数字、整数、浮点数
NumberUtil.isNumber(n3);
NumberUtil.isInteger(n3);
NumberUtil.isDouble(n3);
}
// CollUtil
// 集合操作的工具类,定义了一些常用的集合操作
@Test
void m7() {
//数组转换为列表
String[] array = new String[]{"a", "b", "c", "d", "e"};
List<String> list = CollUtil.newArrayList(array);
//join:数组转字符串时添加连接符号
String joinStr = CollUtil.join(list, ",");
//将以连接符号分隔的字符串再转换为列表
List<String> splitList = StrUtil.split(joinStr, ',');
//创建新的Map、Set、List
HashSet<Object> newHashSet = CollUtil.newHashSet();
ArrayList<Object> newList = CollUtil.newArrayList();
//判断列表是否为空
CollUtil.isEmpty(list);
}
// MapUtil
// Map操作工具类,可用于创建Map对象及判断Map是否为空。
@Test
void m8() {
//将多个键值对加入到Map中
Map<Object, Object> map = MapUtil.of(new String[][]{
{"key1", "value1"},
{"key2", "value2"},
{"key3", "value3"}
});
//判断Map是否为空
MapUtil.isEmpty(map);
MapUtil.isNotEmpty(map);
}
// SecureUtil
// 加密解密工具类,可用于MD5加密。
@Test
void m9() {
//MD5加密
String str = "123456";
String md5Str = SecureUtil.md5(str);
System.out.println(md5Str);
}
}
(11)easy-captcha
理论基础
EasyCaptcha简介
Java图形验证码,支持gif、中文、算术等类型,可用于Java Web、JavaSE等项目。
开源地址 https://github.com/whvcse/EasyCaptcha
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
项目应用
@Controller
public class LoginController {
@RequestMapping("/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception{
GifCaptcha gifCaptcha = new GifCaptcha(130,48,4);
CaptchaUtil.out(gifCaptcha, request, response);
String verCode = gifCaptcha.text().toLowerCase();
request.getSession().setAttribute("CAPTCHA",verCode); //存入session
System.out.println(request.getSession().getId());
}
@GetMapping("/")
public String login() {
return "/test";
}
@RequestMapping("/test")
@ResponseBody
public String test(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
String password = request.getParameter("password");
String captcha = request.getParameter("code");
System.out.println(request.getSession().getId());
System.out.println(request.getSession().getAttribute("CAPTCHA"));
String sessionCode = request.getSession().getAttribute("CAPTCHA").toString();
if(sessionCode == null || StringUtils.isEmpty(sessionCode)){
return "验证码为空";
}
if(captcha.equals(sessionCode)){
return "验证通过";
}
return "验证失败";
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<form action="/test" method="post">
<div>
<label>用户名</label>
<input type="text" name="username" id="username">
<label>密码</label>
<input type="password" name="password" id="password">
<label>验证码</label>
<img src="/captcha" height="48px" width="130px">
<input type="text" name="code" id="code">
<input type="submit" value="登录">
</div>
</form>
</div>
</body>
</html>
(12)EasyPOI
理论基础
http://easypoi.mydoc.io/
easypoi功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员就可以方便的写出Excel导出,Excel模板导出,Excel导入,Word模板导出,通过简单的注解和模板语言(熟悉的表达式语法),完成以前复杂的写法
功能
Excel导入、Excel导出、Excel转html、word导出、pdf导出。
Excel自适应xls和xlsx两种格式,word只支持docx模式
常用注解
@ExcelTarget:限定一个到处实体的注解,以及一些通用设置,作用于最外面的实体
@Excel:这个是必须使用的注解,涵盖了常用的Excel需求
name:列名
orderNum:列的排序
type:导出类型 1 是文本 2 是图片,3 是函数,10 是数字 默认是文本
width:列宽
height:列高
format:时间格式
项目应用
@Data
@Accessors(chain = true)
@ExcelTarget("CompanyVO")
public class CompanyVO implements Serializable {
private Long id;
@Excel(name="公司名", orderNum="0", type=1)
private String name;
@ExcelCollection(name = "员工信息信息")
private List<MemberVO> memberVOList;
}
@Data
@Accessors(chain = true)
@ExcelTarget("MemberVO")
public class MemberVO implements Serializable {
private Long id;
@Excel(name = "姓名")
private String name;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "生日",exportFormat="yyyy-MM-dd HH:mm:ss",importFormat = "yyyy-MM-dd HH:mm:ss",width = 30)
private Date age;
@Excel(name = "性别",width = 25,replace = {"男_1", "女_2"})
private String gender;
@Excel(name = "工资")
private Double salary;
@Excel(name = "照片")
private String phone;
private CompanyVO companyVO;
}
// Excel文件导入导出的工具类
public class ExcelUtil {
// 导出
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName,
boolean isCreateHeader, HttpServletResponse response) {
ExportParams exportParams = new ExportParams(title, sheetName);
exportParams.setCreateHeadRows(isCreateHeader);
defaultExport(list, pojoClass, fileName, response, exportParams);
}
// 导出
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName,
HttpServletResponse response) {
defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName));
}
// 导出
public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
defaultExport(list, fileName, response);
}
// 默认导出
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response,
ExportParams exportParams) {
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
if (workbook != null)
;
downLoadExcel(fileName, response, workbook);
}
// 下载
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
workbook.write(response.getOutputStream());
} catch (IOException e) {
throw new ExcelException(e.getMessage());
}
}
// 默认导出
private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
if (workbook != null)
;
downLoadExcel(fileName, response, workbook);
}
// 导入
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (StringUtils.isBlank(filePath)) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
} catch (NoSuchElementException e) {
throw new ExcelException("模板不能为空");
} catch (Exception e) {
e.printStackTrace();
throw new ExcelException(e.getMessage());
}
return list;
}
// 导入
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows,
Class<T> pojoClass) {
if (file == null) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
} catch (NoSuchElementException e) {
throw new ExcelException("excel文件不能为空");
} catch (Exception e) {
throw new ExcelException(e.getMessage());
}
return list;
}
}
// excel异常
public class ExcelException extends RuntimeException{
public ExcelException(String err){
super(err);
}
}
@RequestMapping("/export")
public void export(HttpServletResponse response) {
CompanyDTO companyDTO = new CompanyDTO();
companyDTO.setName("");
List<CompanyVO> list = companyFeign.list(companyDTO);
list.forEach(System.out::println);
// 集合、标题、工作名、类对象、文件名、响应对象
ExcelUtil.exportExcel(list, "标题一", "工作一", CompanyVO.class, "company.xls", response);
}
(13)HTTPS
理论基础
HTTPS是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。
HTTPS 在HTTP 的基础下加入SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。
http是超文本传输协议,信息是明文传输的(http协议是不安全的,黑客可以在用户和服务器之间设置拦截器窃取传输的内容。也可以伪造用户提交的表单向服务器发出请求,从而获取到服务器的响应)。https则是具有安全性的ssl加密传输协议,http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
spring boot项目配置SSL证书,需要准备域名,ssl证书,将域名解析到自己的服务器,然后通过域名申请证书,再下载适配自己项目的证书文件,通过配置文件进行相应配置,就可以进行https访问了
项目应用
证书生成放在项目根目录
cd C:\Java\jdk-1.8\bin
keytool -genkey -alias tomcat -keyalg RSA -keystore d:/https.keystore
http:
port: 80
server:
port: 443
ssl:
key-store: https.keystore
key-alias: tomcat
enabled: true
key-store-password: 123456
key-store-type: JKS
@Configuration
public class HttpsConfig {
@Value("${server.port}")
private int httpsPort;
@Value("${http.port}")
private int httpPort;
@Bean
public ServletWebServerFactory servletConTAIner() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(initiateHttpConnector());
return tomcat;
}
private Connector initiateHttpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(httpPort);
connector.setSecure(false);
connector.setRedirectPort(httpsPort);
return connector;
}
}
(14)Websocket
理论基础
WebSocket是一种在单个TCP连接上进行全双工通信的协议。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
项目应用
【配置类】
@Component
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
【工具类】
@Component
@ServerEndpoint("/webSocket")
@Slf4j
public class WebSocket {
private Session session;
private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this);
log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size());
}
@OnClose
public void onClose() {
webSocketSet.remove(this);
log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
}
@OnMessage
public void onMessage(String message) {
log.info("【websocket消息】收到客户端发来的消息:{}", message);
}
public void sendMessage(String message) {
for (WebSocket webSocket : webSocketSet) {
log.info("【websocket消息】广播消息, message={}", message);
try {
webSocket.session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
【控制器】
@RestController
public class WebSocketController {
@Autowired
private WebSocket webSocket;
//WebSocket测试
@RequestMapping("/WebSocketMsg")
public void product(){
//WebSocket
webSocket.sendMessage("【WebSocket】信息查询成功,内容为:你好,WebSocketMsg");
}
}
【前端页面】
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
</body>
<script>
var websocket = null;
if ('WebSocket' in window) {
websocket = new WebSocket('ws://127.0.0.1:8080/webSocket');
} else {
alert('该浏览器不支持websocket!');
}
websocket.onopen = function (event) {
console.log('建立连接');
}
websocket.onclose = function (event) {
console.log('连接关闭');
}
websocket.onmessage = function (event) {
console.log('收到消息:' + event.data)
document.body.innerHTML=event.data;
}
websocket.onerror = function () {
alert('websocket通信发生错误!');
}
window.onbeforeunload = function () {
websocket.close();
}
</script>
</html>
(15)JavaMail
理论基础
https://mail.163.com/
设置授权码
<!-- mail -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
项目应用
// (1)继承Authenticator并重写getPasswordAuthentication方法
public class MyAuthenticator extends Authenticator {
String userName = null;
String password = null;
public MyAuthenticator() {
}
public MyAuthenticator(String username, String password) {
this.userName = username;
this.password = password;
}
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
}
/**
* (2)发送邮件核心类工具类EmailUtils
*
* 使用javax.mail发送邮件
*
* <p>
* 参数列表:
* 1.邮件服务器
* 2.发件人邮箱
* 3.发件人的授权密码
* 4.邮件主题
* 5.收件人,多个收件人以半角逗号分隔
* 6.抄送,多个抄送以半角逗号分隔
* 7.正文,可以用html格式的哟
* </p>
*/
public class EmailUtils {
private String smtpHost; // 邮件服务器地址
private String sendUserName; // 发件人的用户名
private String sendUserPass; // 发件人密码
private MimeMessage mimeMsg; // 邮件对象
private Multipart mp;// 附件添加的组件
private void init() {
// 创建一个密码验证器
MyAuthenticator authenticator = null;
authenticator = new MyAuthenticator(sendUserName, sendUserPass);
// 实例化Properties对象
Properties props = System.getProperties();
props.put("mail.smtp.host", smtpHost);
props.put("mail.smtp.auth", "true"); // 需要身份验证
props.put("mail.smtp.starttls.enable", "true");
// 建立会话
Session session = Session.getDefaultInstance(props, authenticator);
// 置true可以在控制台(console)上看到发送邮件的过程
session.setDebug(true);
// 用session对象来创建并初始化邮件对象
mimeMsg = new MimeMessage(session);
// 生成附件组件的实例
mp = new MimeMultipart();
}
private EmailUtils(String smtpHost, String sendUserName, String sendUserPass, String to, String cc, String mailSubject,
String mailBody, List<String> attachments) {
this.smtpHost = smtpHost;
this.sendUserName = sendUserName;
this.sendUserPass = sendUserPass;
init();
setFrom(sendUserName);
setTo(to);
setCC(cc);
setBody(mailBody);
setSubject(mailSubject);
if (attachments != null) {
for (String attachment : attachments) {
addFileAffix(attachment);
}
}
}
/**
* 邮件实体
*
* @param smtpHost 邮件服务器地址
* @param sendUserName 发件邮件地址
* @param sendUserPass 发件邮箱密码
* @param to 收件人,多个邮箱地址以半角逗号分隔
* @param cc 抄送,多个邮箱地址以半角逗号分隔
* @param mailSubject 邮件主题
* @param mailBody 邮件正文
* @param attachments 附件路径
* @return
*/
public static EmailUtils entity(String smtpHost, String sendUserName, String sendUserPass, String to, String cc,
String mailSubject, String mailBody, List<String> attachments) {
return new EmailUtils(smtpHost, sendUserName, sendUserPass, to, cc, mailSubject, mailBody, attachments);
}
/**
* 设置邮件主题
*
* @param mailSubject
* @return
*/
private boolean setSubject(String mailSubject) {
try {
mimeMsg.setSubject(mailSubject);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 设置邮件内容,并设置其为文本格式或HTML文件格式,编码方式为UTF-8
*
* @param mailBody
* @return
*/
private boolean setBody(String mailBody) {
try {
BodyPart bp = new MimeBodyPart();
bp.setContent("<meta http-equiv=Content-Type content=text/html; charset=UTF-8>" + mailBody,
"text/html;charset=UTF-8");
// 在组件上添加邮件文本
mp.addBodyPart(bp);
} catch (Exception e) {
System.err.println("设置邮件正文时发生错误!" + e);
return false;
}
return true;
}
/**
* 添加一个附件
*
* @param filename 邮件附件的地址,只能是本机地址而不能是网络地址,否则抛出异常
* @return
*/
public boolean addFileAffix(String filename) {
try {
if (filename != null && filename.length() > 0) {
BodyPart bp = new MimeBodyPart();
FileDataSource fileds = new FileDataSource(filename);
bp.setDataHandler(new DataHandler(fileds));
bp.setFileName(MimeUtility.encodeText(fileds.getName(), "utf-8", null)); // 解决附件名称乱码
mp.addBodyPart(bp);// 添加附件
}
} catch (Exception e) {
System.err.println("增加邮件附件:" + filename + "发生错误!" + e);
return false;
}
return true;
}
/**
* 设置发件人地址
*
* @param from 发件人地址
* @return
*/
private boolean setFrom(String from) {
try {
mimeMsg.setFrom(new InternetAddress(from));
} catch (Exception e) {
return false;
}
return true;
}
/**
* 设置收件人地址
*
* @param to 收件人的地址
* @return
*/
private boolean setTo(String to) {
if (to == null)
return false;
try {
mimeMsg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
} catch (Exception e) {
return false;
}
return true;
}
/**
* 设置抄送
*
* @param cc
* @return
*/
private boolean setCC(String cc) {
if (cc == null) {
return false;
}
try {
mimeMsg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc));
} catch (Exception e) {
return false;
}
return true;
}
/**
* no object DCH for MIME type multipart/mixed报错解决
*/
private void solveError() {
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap(
"multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed; x-java-fallback-entry=true");
mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);
Thread.currentThread().setContextClassLoader(EmailUtils.class.getClassLoader());
}
/**
* 发送邮件
*
* @return
*/
public boolean send() throws Exception {
mimeMsg.setContent(mp);
mimeMsg.saveChanges();
System.out.println("正在发送邮件....");
solveError();
Transport.send(mimeMsg);
System.out.println("发送邮件成功!");
return true;
}
}
// (3) 测试
public class MailApplicationTests {
public static void main(String[] args) throws Exception {
//163邮箱测试
String userName = "yulin_zhaoyang@163.com"; // 发件人邮箱
String password = "XXXXXXXXXXXX"; // 发件人密码,其实不一定是邮箱的登录密码,对于QQ邮箱来说是SMTP服务的授权文本
String smtpHost = "smtp.163.com"; // 邮件服务器
String to = "931701567@qq.com"; // 收件人,多个收件人以半角逗号分隔
String cc = "112834164@qq.com"; // 抄送,多个抄送以半角逗号分隔
String subject = "这是邮件的主题 163"; // 主题
String body = "这是邮件的正文163"; // 正文,可以用html格式的哟
List<String> attachments = Arrays.asList("D:/a.txt", "D:/b.txt"); // 附件的路径,多个附件也不怕
EmailUtils emailUtils = EmailUtils.entity(smtpHost, userName, password, to, cc, subject, body, attachments);
emailUtils.send(); // 发送!
}
}
(16)JWT案例一
理论基础
有状态登录(session认证)
服务器当中记录每一次的登录信息,从而根据客户端发送的数据来判断登录过来的用户是否合法。
缺点:
每个用户登录信息都会保存到服务器的session中,随着用户的增多服务器的开销会明显增大;
由于session存储在服务器的物理内存当中,所以在分布式系统当中这种方式将会失效。
当然我们也可以通过分布式session来解决相关问题,比如将session信息存储到Redis中,但这无疑会提升系统的复杂度。
因为session认证本质基于cookie,而移动端及非浏览器应用通常没有cookie,故对非浏览器的客户端、手机移动端等不适用;
由于基于cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用;
无状态登录(token认证)
服务器当中不记录用户的登录信息,而是将登录成功后的合法用户信息以token方式保存到客户端当中,用户在每次请求都携带token信息。
优点:
减轻服务端存储session的压力;
支持分布式,支持单点登录并对移动端友好;
支持跨域;
JWT及JSON Web Token,是一种在两方之间以紧凑、可验证的形式传输信息的方式。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
什么时候使用JWT
授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以您可以确定发件人就是他们所说的那个人。此外,由于使用标头和有效负载计算签名,您还可以验证内容没有被篡改。
JWT结构
JWT由以( . )分隔的三部分组成,它们是:
标头(Header)
有效荷载(Payload)
签名(Signature)
因此,JWT 通常如下所示:xxxxx.yyyyy.zzzzz。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串:
JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT 定义了一个标准,JJWT 是 JWT 基于 Java 的一个实现。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
项目应用
public class JWTTest01 {
public static void main(String[] args) {
// m1();
m2();
}
static void m1() {
// 生成令牌
JwtBuilder jwtBuilder = Jwts.builder()
.setId("888")//唯一ID
.setSubject("zhao")//接受用户
.setIssuedAt(new Date())//签发时间
.signWith(SignatureAlgorithm.HS512, "1234");//签名算法、和秘钥
// secret key byte array cannot be null or empty.
// key不能太短 最短四个字符!!!
String token = jwtBuilder.compact();
System.out.println("生成令牌:【" + token + "】");
}
static void m2(){
// 解析令牌
String token = "eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJ6aGFvIiwiaWF0IjoxNjk0MzUyMTk2fQ.cFeamoabpwXcPp49DzHhyOL-LAkpMUiz9Tu-ai7efg6xhTQy1TWpmd0mY6BH2aOEWwZ4dF5wNPFnBM5hTXtm7Q";
Claims claims = (Claims) Jwts.parser()
.setSigningKey("1234")
.parse(token)
.getBody();
System.out.println(claims.getId());
System.out.println(claims.getSubject());
System.out.println(claims.getIssuedAt());
System.out.println("解析令牌:【" + claims + "】");
}
}
public class JWTTest02 {
/**
* 使用JWT令牌时需要注意:
*
* JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
* 如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。
*
* @param args
*/
public static void main(String[] args) {
// m1();
m2();
}
// (1)生成JWT代码实现
public static void m1() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("username", "Tom");
String jwt = Jwts.builder()
.setClaims(claims) //自定义内容(载荷)
.signWith(SignatureAlgorithm.HS256, "itheima") //签名算法
.setExpiration(new Date(System.currentTimeMillis() + 24 * 3600 * 1000)) //有效期
.compact();
System.out.println(jwt);
}
public static void m2() {
Claims claims = Jwts.parser()
.setSigningKey("itheima")//指定签名密钥(必须保证和生成令牌时使用相同的签名密钥)
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjk0MzIzODExLCJ1c2VybmFtZSI6IlRvbSJ9.VDNnX-n68QjIIRpmL3Ktcj9K5V0UWcPHlp09qA4odk4")
.getBody();
System.out.println(claims);
}
}
public class JwtUtils {
private static String signKey = "zhaoyang";//签名密钥
private static Long expire = 43200000L; //有效时间
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)//自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
return claims;
}
}
(17)JWT案例二
JWT 请求流程
用户使用账号和密码发起 POST 请求;
服务器使用私钥创建一个 JWT;
服务器返回这个 JWT 给浏览器;
浏览器将该 JWT 串在请求头中像服务器发送请求;
服务器验证该 JWT;
返回响应的资源给浏览器。
引入jwt
<!-- 引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
用户类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String userName;
private String password;
}
Jwt工具类
/**
* Jwt工具类进行token的生成和认证
*/
public class JwtUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
/**
* 密钥
*/
private static final String SECRET = "my_secret";
/**
* 过期时间
/
private static final long EXPIRATION = 1800L;//单位为秒
/**
* 生成用户token,设置token超时时间
*/
public static String createToken(User user) {
//过期时间
Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000);
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create()
.withHeader(map)// 添加头部
//可以将基本信息放到claims中
.withClaim("id", user.getId())//userId
.withClaim("userName", user.getUserName())//userName
.withClaim("password", user.getPassword())//password
.withExpiresAt(expireDate) //超时设置,设置过期的日期
.withIssuedAt(new Date()) //签发时间
.sign(Algorithm.HMAC256(SECRET)); //SECRET加密
return token;
}
/**
* 校验token并解析token
*/
public static Map<String, Claim> verifyToken(String token) {
DecodedJWT jwt = null;
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
jwt = verifier.verify(token);
//decodedJWT.getClaim("属性").asString() 获取负载中的属性值
} catch (Exception e) {
logger.error(e.getMessage());
logger.error("token解码异常");
//解码异常则抛出异常
return null;
}
return jwt.getClaims();
}
}
JWT过滤器
/**
* JWT过滤器,拦截 /secure的请求
* JWT过滤器中进行token的校验和判断,token不合法直接返回,合法则解密数据并把数据放到request中供后续使用。
*
* 为了使过滤器生效,需要在启动类添加注解@ServletComponentScan(basePackages = "com.zhaoyang.config")。
*/
@Slf4j
@WebFilter(filterName = "JwtFilter", urlPatterns = "/secure/*")
public class JwtFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
response.setCharacterEncoding("UTF-8");
//获取 header里的token
final String token = request.getHeader("authorization");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(request, response);
}
// Except OPTIONS, other request should be checked by JWT
else {
if (token == null) {
response.getWriter().write("没有token!");
return;
}
Map<String, Claim> userData = JwtUtil.verifyToken(token);
if (userData == null) {
response.getWriter().write("token不合法!");
return;
}
Integer id = userData.get("id").asInt();
String userName = userData.get("userName").asString();
String password= userData.get("password").asString();
//拦截器 拿到用户信息,放到request中
request.setAttribute("id", id);
request.setAttribute("userName", userName);
request.setAttribute("password", password);
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
}
}
登录Controller
/**
* 登录Controller
* LoginController进行登录操作,登录成功后生产token并返回。
*/
@Slf4j
@RestController
public class LoginController
{
static Map<Integer, User> userMap = new HashMap<>();
static {
//模拟数据库
User user1 = new User(1,"张三","123456");
userMap.put(1, user1);
User user2 = new User(2,"李四","123123");
userMap.put(2, user2);
}
/**
* 模拟用户 登录
*/
@RequestMapping("/login")
public String login(User user)
{
for (User dbUser : userMap.values()) {
if (dbUser.getUserName().equals(user.getUserName()) && dbUser.getPassword().equals(user.getPassword())) {
log.info("登录成功!生成token!");
String token = JwtUtil.createToken(dbUser);
return token;
}
}
return "";
}
}
携带jwt才能访问
/**
* 需要登录后携带JWT才能访问
* SecureController中的请求会被JWT过滤器拦截,合法后才能访问。
*/
@Slf4j
@RestController
public class SecureController
{
/**
* 查询 用户信息,登录后携带JWT才能访问
*/
@RequestMapping("/secure/getUserInfo")
public String login(HttpServletRequest request) {
Integer id = (Integer) request.getAttribute("id");
String userName = request.getAttribute("userName").toString();
String password= request.getAttribute("password").toString();
return "当前用户信息id=" + id + ",userName=" + userName+ ",password=" + password;
}
}
启动类
@SpringBootApplication
@ServletComponentScan(basePackages = "com.zhaoyang.config")
public class SsmWebApplication {
public static void main(String[] args) {
SpringApplication.run(SsmWebApplication.class, args);
}
}
接口测试
(18)SpringSecurity案例一
导航菜单
<h1>导航菜单</h1>
<div>
<ul>
<li><a href="/a/a">A模块</a></li>
<li><a href="/b/b">B模块</a></li>
<li><a href="/c/c">C模块</a></li>
</ul>
</div>
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SecurityConfig
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("abc").password(new BCryptPasswordEncoder().encode("123")).roles("vip1")
.and()
.withUser("abcd").password(new BCryptPasswordEncoder().encode("1234")).roles("vip1", "vip2")
.and()
.withUser("abcde").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1", "vip2", "vip3");
}
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
// 授权规则
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/a/**").hasRole("vip1")
.antMatchers("/b/**").hasRole("vip2")
.antMatchers("/c/**").hasRole("vip3");
// 没有权限跳到登陆页面、需要开启登陆页面
http.formLogin();
}
public static void main(String[] args) {
// BCryptPasswordEncoder是官方推荐的密码解析器
PasswordEncoder pw = new BCryptPasswordEncoder();
String encode = pw.encode("123");
System.out.println(encode);
boolean boo = pw.matches("123", encode);
System.out.println(boo);
}
}
UserController
@Controller
public class UserController {
@RequestMapping("/main")
public String toMain(){
return "/main.html";
}
@RequestMapping("/a/a")
public String toA(){
return "/a/a.html";
}
@RequestMapping("/b/b")
public String toB(){
return "/b/b.html";
}
@RequestMapping("/c/c")
public String toC(){
return "/c/c.html";
}
}
(19)SpringSecurity案例二
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
SecurityConfig
// 启动时加载:密码解析器注入到IOC容器中
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successForwardUrl("/toMain")
.failureForwardUrl("/toErr");
http.authorizeHttpRequests()
.antMatchers("/login.html").permitAll()
.anyRequest().authenticated();
http.csrf().disable();
}
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
public static void main(String[] args) {
// BCryptPasswordEncoder是官方推荐的密码解析器
PasswordEncoder pw = new BCryptPasswordEncoder();
String encode = pw.encode("123");
System.out.println(encode);
boolean boo = pw.matches("123", encode);
System.out.println(boo);
}
}
UserDeTAIlServiceImpl
@Service
public class UserDeTAIlServiceImpl implements UserDeTAIlsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDeTAIls loadUserByUsername(String username) throws UsernameNotFoundException {
// 正常去数据库查询
if (!"admin".equals(username)) {
throw new UsernameNotFoundException("用户名不存在");
}
// 注册时已经加密
String password = passwordEncoder.encode("123");
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
SecurityController
@Controller
public class SecurityController {
@Autowired
private UserDeTAIlServiceImpl userDeTAIlService;
@RequestMapping("toMain")
@ResponseBody
public boolean m1(){
return true;
}
@RequestMapping("toErr")
public String m2(){
return "redirect:login.html";
}
}
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登陆页面</h1>
<form action="/login" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<button type="submit" value="登陆">登陆</button>
</form>
</body>
</html>
(20)Oauth2
授权码模式
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意链接。
https://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
上面 URL 中,response_type参数表示要求返回授权码(code),client_id参数让 B 知道是谁在请求,redirect_uri参数是 B 接受或拒绝请求后的跳转网址,scope参数表示要求的授权范围(这里是只读)。
第二步,用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个授权码,就像下面这样。
https://a.com/callback?code=AUTHORIZATION_CODE
上面 URL 中,code参数就是授权码。
第三步,A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌
https://b.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
上面 URL 中,client_id参数和client_secret参数用来让 B 确认 A 的身份(client_secret参数是保密的,因此只能在后端发请求),grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码,code参数是上一步拿到的授权码,redirect_uri参数是令牌颁发后的回调网址。
第四步,B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段 JSON 数据。
{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"read",
"uid":100101,
"info":{...}
}
上面 JSON 数据中,access_token字段就是令牌,A 网站在后端拿到了。
架构说明
app01【授权服务器】:
授权服务器springsecurity配置(登陆账号abc/123)
授权服务器oauth2配置(唯一标识id、密码、授权码模式、范围、重定向、资源)
app02【资源服务器】
资源服务器配置类验证令牌
http://127.0.0.1:8081/oauth/check_token
zhaoyang
123456
验证流程:
【请求授权码、返回授权码】 -> 【请求令牌、返回令牌】 -> 【请求资源、返回资源】
请求授权码
注意区分:
获取授权码的时候需要登陆账号、是abc和123、在springsecurity中配置。
获取访问令牌的时候也需要账号、是zhaoyang/123456、client_id是需要授权码的app或网站的唯一标识、client_secret是密码
http://127.0.0.1:8081/oauth/authorize?client_id=zhaoyang&response_type=code
https://www.baidu.com/?code=aRThiP
请求令牌
注意:
post请求、地址http://127.0.0.1:8081/oauth/token、选择x-www-form-urlencoded是post的默认Content-Type
授权码模式 grant_type=authorization_code
授权码 code=bzFvyd
客户端 client_id=zhaoyang
密码 client_secret=123456
重定向 redirect_uri=http://www.baidu.com
范围 scope=all
{
"access_token": "489b5501-b7e9-479c-b0aa-a99bdeee31a8",
"token_type": "bearer",
"expires_in": 43199,
"scope": "all"
}
请求资源
资源地址
http://127.0.0.1:8082/info
postman设置
Authorization -> TYPE -> Bearer Token
添加令牌
Token:489b5501-b7e9-479c-b0aa-a99bdeee31a8
返回结果
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
授权服务器SecurityConfig
/**
* 授权服务器的security配置
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("abc")
.password(passwordEncoder().encode("123"))
.authorities("/*");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf()
.disable();
}
}
授权服务器AuthorizationConfig
/**
* 授权服务器Authorize相关配置
*/
@Component
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
@Override
public void configure(ClientDeTAIlsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("zhaoyang")
.secret(passwordEncoder.encode("123456"))
.authorizedGrantTypes("authorization_code")
.scopes("all")
.redirectUris("http://www.baidu.com")
.resourceIds("security_resource");
// 我们可以为每一个Resource Server(一个微服务实例)设置一个resourceid。再给client授权的时候,
// 可以设置这个client可以访问哪一些微服务实例,如果没设置,就是对所有的resource都有访问权限。
}
}
资源服务器ResourceConfig
/**
* 资源服务器配置
*/
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Primary
public RemoteTokenServices remoteTokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl("http://127.0.0.1:8081/oauth/check_token");
remoteTokenServices.setClientId("zhaoyang");
remoteTokenServices.setClientSecret("123456");
return remoteTokenServices;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("security_resource").stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http.authorizeRequests().anyRequest().authenticated();
}
}
被访问的控制器资源
@RestController
public class UserController {
@RequestMapping("info")
public String info() {
return "可以访问资源数据!";
}
}