发表于: 2020-01-16 20:01:54
1 1352
啥也不说就是干!!
今天完成的事情:
1、SpringBoot 实现文件上传
UploadCotroller
@RestController
@RequestMapping("image/")
public class UploadController {
@PostMapping("/upload")
public CommonResult uploadFile(HttpServletRequest request){
// public CommonResult uploadFile2(@RequestParam("file") MultipartFile[] files){
//获取前端上传的文件列表
List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
if (files==null||files.size()==0) {
// if (files==null) {
return CommonResult.error(-1,"文件不能为空") ;
}
String fileName = files.get(0).getOriginalFilename();
String filePath = "/Users/itinypocket/workspace/temp/";
File dest = new File(filePath + fileName);
try {
files.get(0).transferTo(dest);
return CommonResult.success(null,"上传成功");
} catch (IOException e) {
}
return CommonResult.error(-1,"文件上传失败");
}
}
2、对接阿里云图片存储
添加相关依赖及配置
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.8.0</version>
</dependency>
定义 Storage 接口
public interface Storage {
void store(InputStream inputStream,long contentLength,String contentType,String keyName);
void delete(String keyName);
String generateUrl(String keyName);
}
编写实现类 AliyunStoage
@Service
public class AliyunStorage implements Storage {
@Value("${storage.bucketName}")
private String bucketName;
@Value("${storage.endPoint}")
private String endPoint;
@Value("${storage.accessKeyId}")
private String accessKeyId;
@Value("${storage.accessKeySecret}")
private String accessKeySecret;
public String getBaseUrl() {
return "https://" + bucketName + "." + endPoint + "/";
}
public OSS getOssClient() {
return new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
}
@Override
public void store(InputStream inputStream, long contentLength, String contentType, String keyName) {
try {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(contentLength);
objectMetadata.setContentType(contentType);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, keyName, inputStream, objectMetadata);
PutObjectResult putObjectResult = getOssClient().putObject(putObjectRequest);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void delete(String keyName) {
try{
getOssClient().deleteObject(bucketName,keyName);
}catch (Exception e){
}
}
@Override
public String generateUrl(String keyName) {
return getBaseUrl()+keyName;
}
@Override
public Stream<Path> loadAll() {
return null;
}
@Override
public Path load(String keyName) {
return null;
}
@Override
public Resource loadAsResource(String keyName) {
try {
URL url = new URL(getBaseUrl() + keyName);
Resource resource = new UrlResource(url);
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
return null;
}
} catch (MalformedURLException e) {
return null;
}
}
}
编写上传接口 UploadController
@RestController
@RequestMapping("image/")public class UploadController {
@Autowired
private AliyunStorage storageService;
@PostMapping("/upload")
public CommonResult uploadFile(@RequestParam("file") MultipartFile file) {
//获取前端上传的文件列表
if (file == null) {
return CommonResult.error(-1, "文件不能为空");
}
try {
String fileName = file.getOriginalFilename();
storageService.store(file.getInputStream(), file.getSize(), file.getContentType(), fileName);
return CommonResult.success(null, "上传成功");
} catch (IOException e) {
}
return CommonResult.error(-1, "文件上传失败");
}
}
最终文件上传功能已经完成了
3、Redis 实现接口限流
参考网上现有的方案(https://blog.51cto.com/14230003/2419614)
在实际项目中有些接口得有限流保护的防攻击策略,可以采用 Redis 进行限流
1、通过 AOP 对 Controller 方法进行拦截,添加 AOP 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、自定义 LImit 注解及 AOP 增强类
核心就是调用 execute 方法传入我们的 Lua 脚本内容,然后通过返回值判断是否超出我们预期的范围,超出则给出错误提示
@Aspect
@Configuration
public class LimitInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);
@Resource
private RedisTemplate<String,Number> redisTemplate;
public LimitInterceptor() {
}
@Around("execution(public * *(..)) && @annotation(com.gerry.jnshu.core.limit.Limit)")
public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Limit limitAnnotation = method.getAnnotation(Limit.class);
LimitType limitType = limitAnnotation.limitType();
String name = limitAnnotation.name();
String key;
int limitPeriod = limitAnnotation.period();
int limitCount = limitAnnotation.count();
switch (limitType) {
case IP:
key = getIpAddress();
break;
case CUSTOMER:
key = limitAnnotation.key();
break;
default:
key = StringUtils.upperCase(method.getName());
}
ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));
String luaScript = buildLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = redisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
logger.info("Access try count is {} for name={} and key = {}", count, name, key);
if (count != null && count.intValue() <= limitCount) {
return pjp.proceed();
} else {
throw new ServiceException(REQUEST_LIMIT);
}
}
/**
* 限流 脚本
*
* @return lua脚本
*/
public String buildLuaScript() {
StringBuilder lua = new StringBuilder();
lua.append("local c");
lua.append("\nc = redis.call('get',KEYS[1])");
// 调用不超过最大值,则直接返回
lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
lua.append("\nreturn c;");
lua.append("\nend");
// 执行计算器自加
lua.append("\nc = redis.call('incr',KEYS[1])");
lua.append("\nif tonumber(c) == 1 then");
// 从第一次调用开始限流,设置对应键值的过期
lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
lua.append("\nend");
lua.append("\nreturn c;");
return lua.toString();
}
private static final String UNKNOWN = "unknown";
public String getIpAddress() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
原理也很简单,接口请求一次,执行 LUA 脚本,放入 Redis 并根据注解设置 key value 及 expireTime,再次请求就从 Redis 通过 key 将次数取出来跟限定的次数对比,符合规范执行,不符合直接抛异常。
4、深度思考
1.什么是Annotation,怎么自定义Annotation,Annotation和XML的优缺点各是什么?
自定义 Annotation 日志中已经有了
自定义一个 @Limit 注解来使用,具体看代码
Annotation 比较方便设置,但是与代码耦合度高
XML 配置比较繁琐,但是看起来一目了然,与代码耦合度不高
2.如何调用第三方API实现图片上传?
日志代码中已经实现
3.怎么用Python写一个图片迁移脚本?
暂时没有学 Python
4.第三方的服务可靠吗,如果出现错误该怎么处理?
记录日志错误,然后报警,进行补偿处理
5.怎么快速集成第三方服务组件,API和SDK的区别是什么?
PI是一类已经定义好的应用程序接口,可以直接调用接口中已经定义的方法来实现某些功能。
SDK是software development kit,软件开发工具包,一个SDK是一个具体的东西,例如Java的JDK,它一定以某种形式存在,比如说是一个jar包,一个可以依赖的lib,一个可以直接引用的js代码。
SDK与API的联系在于,实际上SDK包含了API的定义,API定义一种能力,一种接口的规范。
而SDK可以包含这种能力,包含这种规范。我们常常说,这个SDK具有某某些API可以用。
但SDK又不完完全全只包含API以及API的实现,它是一个软件工具包,它还有其他辅助性的功能。例如JDK定义了JDBC API的规范,定义xml解析api的规范,但它也提供了很多工具包,例如常用数据结构Map,list等的封装,目的是方便使用者开发程序。
6.上传图片应该由前端或客户端直接上传到第三方存储服务,还是应该先上传到服务器,再由服务器上传到第三方?
我用的是直接上传到第三方存储了,不知道真实项目应用场景是啥
7.怎么实现图片防盗链,缩略图,水印等功能?
在阿里云后台设置防盗链
关于缩略图水印 API 文档也介绍的很详细
8.为什么要使用第三方图片存储服务,好处是什么?
第三方存储比较方便快捷,服务器也不需要单独维护,资源受保护策略也比较健全
9.怎么样将短信发送服务抽像成公共代码,可以在多个项目中复用?
将共用的部分抽成工具类,或者做成单独的 module 供其他 module 调用
10.点对点短信和群发短信的通道区别是什么,怎么支持多种短信通道?
阿里云直接支持群发的,区别在于单发短信时,直接输入某个手机号,群发短信时,输入的是所有号码构成的集合。
明天计划的事情:
开始任务八
遇到的问题:
暂无
收获:
学习了图片的上传,及如何用 Redis +自定义注解实现限制接口请求频率。
评论