文件上传
图片存储方案
实现图片上传服务,需要有存储的支持,那么我们的解决方案有以下几种:
-
直接将图片保存到服务的硬盘 优点:开发便捷,成本低 缺点:扩容困难
-
使用分布式文件系统进行存储(Minio FastDFS) 优点:容易实现扩容 缺点:开发复杂度稍大
-
使用第三方的存储服务(阿里云 华为云 七牛云) 优点:开发简单,拥有强大功能,免维护 缺点:付费
在本套课程中选用阿里云的OSS服务进行图片存储
阿里云OSS
官网地址:https://www.aliyun.com/product/oss
注册登录
充值
开通服务
创建空间
创建秘钥
官方SDK
https://help.aliyun.com/document_detail/84781.html?spm=a2c4g.11174283.6.946.425e7da24qVtmx
<!--阿里云-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.0.6</version>
</dependency>
package com.itheima.test;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class OssTest {
@Test
public void testUpload() throws FileNotFoundException {
// 区域
String endpoint = "http://oss-cn-beijing.aliyuncs.com";
// 秘钥
String accessKeyId = "LTAI5tNmH22y9C7AxdFEgdNv";
String accessKeySecret = "AHGC0JSnBuYtYg3BnNvyTuwlwyl27n";
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件
InputStream inputStream = new FileInputStream("D:\\upload\\jr.jpg");
//参数1: 桶名字 参数2: 图片上传后名字 参数3: 图片流
ossClient.putObject("tlias-gxm-191", "haha.jpg", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
}
}
工具类抽取
创建配置类
在项目中创建
com.itheima.util.OssProperties
package com.itheima.util;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "oss")//读取配置信息
public class OssProperties {
private String key; //访问key
private String secret;//访问秘钥
private String endpoint;//端点
private String bucket;//桶名
}
创建工具类
在项目中创建
com.itheima.util.OssTemplate
package com.itheima.util;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
//阿里存储工具类
@Component
@Import(OssProperties.class)//导入,相当于将OssProperties对象放入容器
public class OssTemplate {
@Autowired
private OssProperties ossProperties;
//文件上传
public String upload(String fileName, InputStream inputStream) {
//创建客户端
OSS ossClient = new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getKey(), ossProperties.getSecret());
//设置文件最终的路径和名称
String objectName = "images/" + new SimpleDateFormat("yyyy/MM/dd").format(new Date())
+ "/" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
//meta设置请求头,解决访问图片地址直接下载
ObjectMetadata meta = new ObjectMetadata();
meta.setContentType(getContentType(fileName.substring(fileName.lastIndexOf("."))));
//上传
ossClient.putObject(ossProperties.getBucket(), objectName, inputStream, meta);
//关闭客户端
ossClient.shutdown();
return "https://" + ossProperties.getBucket() + "." + ossProperties.getEndpoint() + "/" + objectName;
}
//文件后缀处理
private String getContentType(String FilenameExtension) {
if (FilenameExtension.equalsIgnoreCase(".bmp")) {
return "image/bmp";
}
if (FilenameExtension.equalsIgnoreCase(".gif")) {
return "image/gif";
}
if (FilenameExtension.equalsIgnoreCase(".jpeg") ||
FilenameExtension.equalsIgnoreCase(".jpg") ||
FilenameExtension.equalsIgnoreCase(".png")) {
return "image/jpg";
}
return "image/jpg";
}
}
添加配置
在配置文件
application.yml
中添加oss的配置注意:安装自己的阿里云配置修改
oss:
key: LTAI5tRYNe7GUtiqsktTwfVZ
secret: EcKCLKzCIuTH7b2gVtfCoMbwm9Etbr
endpoint: oss-cn-beijing.aliyuncs.com
bucket: tanhua-gxm
测试
在测试类中进行测试
com.itheima.test.OssTemplateTest
package com.itheima.test;
import com.itheima.util.OssTemplate;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@SpringBootTest
@Slf4j
public class OssTemplateTest {
@Autowired
private OssTemplate ossTemplate;
@Test
public void testFileUpload() throws FileNotFoundException {
String filePath = ossTemplate.upload("1.jpg", new FileInputStream("D:/upload/1.jpg"));
log.info("文件上传完毕之后的路径{}", filePath);
}
}
图片上传实现
后台首先使用Spring的文件上传解析器接收上传的文件,然后将文件保存到阿里云,然后返回访问地址
配置文件大小
在配置文件
application.yml
中添加下面配置
spring:
servlet:
multipart:
max-request-size: 100MB # 最大请求文件大小,默认10MB
max-file-size: 10MB # 单个请求文件大小,默认1MB
FileController
系统登录
系统登录的本质是查询,即服务端接收用户发送的用户名和密码,然后跟数据库中的账号密码进行比对
名称 | 说明 |
---|---|
Entity | 实体对象,通常和数据表对应 |
DTO | 数据传输对象,主要用于接收前端数据,并在各层之间传递 |
VO | 视图对象,为展示前端数据提供的对象 |
准备DTO类
创建一个LoginDTO用于接收前端提交的登录参数
业务代码
EmpController
EmpService
EmpServiceImpl
EmpMapper
访问测试
启动后端程序和nginx软件,使用浏览器访问
http://localhost:90/#/login
进入登录页面,输入账号和密码进行测试
登录校验
理论
我们的系统目前在不经过登录的情况下,直接输入员工页面地址就可以访问,这是非常不安全的。
正确的流程应该是:当访问请求到达服务器后,服务器要校验当前用户是否已经登录过,如果登录过,就放行请求;如果未登录过,就禁止请求访问
那如何知道用户是否已经登录过呢?
这就需要在用户登录成功后,由服务器为其颁发一个token(身份标识),然后后面用户每次发送请求,都会携带着这个token
而作为服务器会对每次的请求进行拦截,校验token的合法性即可
JWT
介绍
全称:JSON Web Token (https://jwt.io/),用于对应用程序上的用户进行身份标记
本质上就是一个经过加密处理与校验处理的字符串,它由三部分组成:
- 头信息(Header):记录令牌类型和签名算法,例如:{“alg”: “HS256”,“typ”: “JWT”}
- 有效载荷(Payload):记录一些自定义能够区分身份的非敏感信息,例如:{“id”: “1”,“username”: “tom”}
- 签名(Signature):用于保证Token在传输过程中不被篡改,它是header、payload,加入指定算法计算得来的
使用流程
代码测试
① 在pom.xml中引入依赖
<!--Token生成与解析-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
② 生成并校验token
package com.itheima.test;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtTest {
//生成token
@Test
public void genJwt() {
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() + 12 * 3600 * 1000)) //有效期
.compact();
System.out.println(jwt);
}
//校验token
@Test
public void checkJwt() {
Claims claims = Jwts.parser()
.setSigningKey("itheima")//盐
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjc3MzU3MjE0LCJ1c2VybmFtZSI6IlRvbSJ9.RBtRZGHUefLElDWWIlQRoy0_Dl71sZysPP61vVa46oo")//上一步得到的值
.getBody();
System.out.println(claims);
}
}
功能改进
修改目前的登录功能,当登录成功后,创建token并返回给客户端
拦截器
介绍
拦截器是Spring提供的一种技术,它会在进入controller之前,离开controller之后以及响应离开服务时进行拦截。
入门案例
① 开发拦截器
作用:要对拦截的资源做什么
语法:实现HandlerInterceptor接口,重写3个方法
② 配置拦截器
作用:确定你要拦截那些资源
语法:在配置类中添加拦截路径的配置
拦截路径
拦截器的路径写法相对简单,其实只有两个:
*表示一层路径 **表示多层路径
路径 | 解释 | 备注 |
---|---|---|
/* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
/** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
/depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
/depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
拦截器链
多个拦截器也可以同时使用,一条拦截器链,他们的顺序是有
.order()
方法控制
访问校验
- 创建
com.itheima.interceptor.LoginCheckInterceptor
编写过滤逻辑
package com.itheima.interceptor;
import com.google.gson.Gson;
import com.itheima.util.JwtUtils;
import com.itheima.vo.Result;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Autowired
private Gson gson;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 获取请求头中的令牌(token)
String token = request.getHeader("token");
//2. 解析token,如果解析失败,返回错误结果(未登录)。
try {
Claims claims = JwtUtils.parseJWT(token);
}catch (Exception e){
log.info("token错误");
//返回错误消息
String json = gson.toJson(Result.error("NOT_LOGIN"));
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
return false;//结束判断
}
//3. 放行
return true;
}
}
- 配置拦截器
自动装配
源码分析
SpringBootApplication注解分析
@Target({ElementType.TYPE}) //标注位置: 类 接口
@Retention(RetentionPolicy.RUNTIME) // 保留策略 运行时代码
@Documented // 生成JavaDoc
@Inherited //运行被继承
@SpringBootConfiguration // 相当于@Configuration 表示这是一个配置类
@EnableAutoConfiguration // 将所有符合自动配置条件的bean定义加载到IoC容器(重点!!!)
@ComponentScan(...) // 注解扫描 扫描的是当前类所在包及其子包中类中的注解
public @interface SpringBootApplication {}
@EnableAutoConfiguration注解分析
@AutoConfigurationPackage // 获取注解扫描的包路径
@Import({AutoConfigurationImportSelector.class})// 选择所有符合自动配置条件的bean,然后将其加载到IoC容器
public @interface EnableAutoConfiguration {}
AutoConfigurationImportSelector类分析
public class AutoConfigurationImportSelector...{
...
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
}
下面是spring-boot-autoconfigure这个jar中META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的内容
通过读取这个配置获取一组@Configuration类。
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration
GsonAutoConfiguration
@AutoConfiguration
@ConditionalOnClass(Gson.class)
@EnableConfigurationProperties(GsonProperties.class)
public class GsonAutoConfiguration {
...
//向容器中放入gson对象
@Bean
@ConditionalOnMissingBean
public Gson gson(GsonBuilder gsonBuilder) {
return gsonBuilder.create();
}
}
条件装配
* 并不是读取到的所有的配置类中Bean都会被初始化,在配置类中使用@Condition来加载满足条件的Bean
- ConditionalOnClass: 判断环境中是否有对应字节码文件才初始化Bean
- ConditionalOnProperty: 判断配置文件中是否有对应属性和值才初始化Bean
- ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean
源码图示
nableConfigurationProperties(GsonProperties.class)
public class GsonAutoConfiguration {
...
//向容器中放入gson对象
@Bean
@ConditionalOnMissingBean
public Gson gson(GsonBuilder gsonBuilder) {
return gsonBuilder.create();
}
}
>条件装配
~~~markdown
* 并不是读取到的所有的配置类中Bean都会被初始化,在配置类中使用@Condition来加载满足条件的Bean
- ConditionalOnClass: 判断环境中是否有对应字节码文件才初始化Bean
- ConditionalOnProperty: 判断配置文件中是否有对应属性和值才初始化Bean
- ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean
[外链图片转存中…(img-owzGdQ00-1728558247935)]
源码图示
[外链图片转存中…(img-jYigDfYw-1728558247935)]