发表于: 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 +自定义注解实现限制接口请求频率。





返回列表 返回列表
评论

    分享到