文章目录
安全性
问题引入:如果用户上传一个超大的文件怎么办?比如1000G?
预防:
只要涉及到用户自主上传的操作,一定要校验文件(图像)
校验什么?
- 文件的大小
- 文件的后缀
- 文件的内容(成本高一点)
- 文件的合规性,比如敏感内容(建议用第三方审核功能),todo 接入腾讯云的图片万象数据审核(COS对象存储的审核功能)
代码校验实现:
java
//校验文件大小
long ONE_MB = 1024 * 1024l;
long size = multipartFile.getSize();
ThrowUtils.throwIf(size > ONE_MB,ErrorCode.PARAMS_ERROR,"文件过大");
//校验后缀名
String originalFilename = multipartFile.getOriginalFilename();
String suffix = FileUtil.getSuffix(originalFilename);
List<String> validSuffix = Arrays.asList("png","jpg","svg","webp","jpeg");
ThrowUtils.throwIf(!validSuffix.contains(suffix),ErrorCode.PARAMS_ERROR,"文件后缀非法");
todo 数据存储
现状:我们把每个图表的原始数据全部放在了同一个数据表(chart表)的字段里
问题:
- 如果用户上传的原始数据量很大,图表数日益增多,查询chart表就会很慢
- 对于BI平台,用户是有查看原始数据,对原始数据进行简单查询的需求的,现在如果把所有数据放在一个字段(列)中,查询时,只能取出这个列的所有内容
**解决方案:分库分表:
**把每个图表对应的原始数据单独保存为一个新的数据表,而不是都存在一个字段里
优点:
- 存储时,能够分开存储,互不影响(也能增加安全性)
- 查询时,可以使用各种sql语句灵活取出需要的字段,查询性能更快
todo 实现:动态sql,这里鱼皮也实现了,不过没有应用,只是测试,等等复习下知识再说
限流
现在的问题 :使用系统是需要消耗成本的,用户有可能疯狂刷量,让你破产
解决问题:
- 控制成本 -> 限制用户调用总次数
- 用户在短时间内疯狂使用,导致服务器资源被占满,其他用户无法使用->限流
思考:限流阈值多大合适?参考正常用户的使用,比如限制单个用户在每秒只能使用一次
限流的几种算法
- 固定窗口限流
- 滑动窗口限流
- 漏桶限流
- 领令牌桶限流
限流粒度
- 针对某个方法限流
- 针对某个用户限流
- 针对用户调用某个方法限流
限流的实现
本地限流(单机限流)
每个服务器单独限流,一般适用于单体项目,你的项目只有一个服务器
Guava RateLimiter
java
import com.google.common.util.concurrent.RateLimiter;
public static void main(String[] args) {
// 每秒限流5个请求
RateLimiter limiter = RateLimiter.create(5.0);
while (true) {
if (limiter.tryAcquire()) {
// 处理请求
} else {
// 超过流量限制,需要做何处理
}
}
}
Redisson实现分布式限流(多机限流)
- 引入依赖
java
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.28.0</version>
</dependency>
- 创建Redisson配置类
java
package com.yupi.springbootinit.config;
import io.lettuce.core.RedisClient;
import io.swagger.models.auth.In;
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("spring.redis")
@Data
public class RedissonConfig {
private Integer database;
private String host;
private Integer port;
// spring启动时,会自动创建一个RedissonClient对象
@Bean
public RedissonClient getRedissonClient() {
// 1.创建配置对象
Config config = new Config();
// 2. 添加单机Redisson配置
config.useSingleServer()
// 设置数据库
.setDatabase(1)
//设置redis的地址
.setAddress("redis://" + host + ":" + port);
//3..创建Redisson实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
- 创建通用限流管理类RedisLimiterManager
(专门提供 RedisLimiter 限流基础服务),manager包存放通用模版,没有业务逻辑,可以放在任何一个项目里
java
package com.yupi.springbootinit.manager;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class RedisLimiterManager {
@Resource
private RedissonClient redissonClient;
public void doRateLimit(String key){
// 创建一个名称为rateLimiter的限流器
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 限流器的统计规则(每秒2个请求;连续的请求,最多只能有1个请求被允许通过)
// RateType.OVERALL表示速率限制作用于整个令牌桶,即限制所有请求的速率
rateLimiter.trySetRate(RateType.OVERALL,2,1, RateIntervalUnit.SECONDS);
// 每当一个操作来了后,请求一个令牌
boolean canop = rateLimiter.tryAcquire(1);
// 如果没有令牌,还想执行操作,就抛出异常
if(!canop){
throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);
}
}
}
- 测试后整合进项目(一行代码解决)
java
//限流判断,每个用户一个限流器
redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());