vlambda博客
学习文章列表

图片存储方案-七牛云存储

简介

在我们实际的项目中,经常会将服务器进行划分,比如:专门运行我们程序的应用服务器、专门运行我们数据库的数据库服务器、还有负责文件存储的文件服务器、负责视频存储的服务器。

这些服务器组成我们的所有服务、各司其职,这样划分的目的相比大家也知道,减轻一台服务器服务器的压力。

当用户请求我们应用应用服务器的时候,由我们的应用再分别访问我们数据库服务器、文件存储服务器等,最后将请求的资源返回给我们的用户,这样构成整个应用的请求过程:

图片存储方案-七牛云存储

我们今天要讲的就是图片存储,图片储存的方案有很多,比如:可以使用Fastdfs或者HDFS、使用nginx搭建图片存储服务器。

现在比较流行的就是「云存储」,使用「阿里云、七牛云」等。这一节我们使用七牛云做了实例讲解。

七牛云入门

图片存储方案-七牛云存储注册完后直接登陆,登陆后右上角有一个「管理控制平台」,点击管理控制平台的产品首页,还要进行「实名认证才能创建对象存储存储空间」,实名认证后点击对象存储的「立即添加」:

图片存储方案-七牛云存储点击立即添加后来到对象存储空间的创建页面,点击「新建空间」图片存储方案-七牛云存储

创建存储空间后可以看见自己创建的空间的详细信息,并且可以创建多个存储空间,每一个存储空间相互独立,不会互相干扰。

七牛云提供了多种的方式操作对象存储服务,我们这里使用的是Java SDK方式,具体的使用可以在开发者中心查看:

图片存储方案-七牛云存储
图片存储方案-七牛云存储

Java SDK开发文档里面有详细的操作的API说明,以及要引入的依赖坐标。

图片存储方案-七牛云存储

代码实战

首先在自己的项目中引入七牛云的依赖坐标:

<dependency>
  <groupId>com.qiniu</groupId>
  <artifactId>qiniu-java-sdk</artifactId>
  <version>7.2.0</version>
</dependency>

从Java SDK的操作文档中,简单操作文档的上传的示例代码如下:

//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.region0());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传,填写AK和SK以及buket name
String accessKey = "your access key";
String secretKey = "your secret key";
String bucket = "your bucket name";
//文件路径,如果是Windows情况下,格式是 D:\\qiniu\\test.png
String localFilePath = "/home/qiniu/test.png";
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = null;
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
    Response response = uploadManager.put(localFilePath, key, upToken);
    //解析上传成功的结果
    DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
    System.out.println(putRet.key);
    System.out.println(putRet.hash);
} catch (QiniuException ex) {
    Response r = ex.response;
    System.err.println(r.toString());
    try {
        System.err.println(r.bodyString());
    } catch (QiniuException ex2) {
        //ignore
    }
}

上面的是根据文件路径来上传文件,还支持字节数组上传到空间中:

//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.region0());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
String accessKey = "your access key";
String secretKey = "your secret key";
String bucket = "your bucket name";
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = null;
try {
    byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");
    Auth auth = Auth.create(accessKey, secretKey);
    String upToken = auth.uploadToken(bucket);
    try {
        Response response = uploadManager.put(uploadBytes, key, upToken);
        //解析上传成功的结果
        DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
        System.out.println(putRet.key);
        System.out.println(putRet.hash);
    } catch (QiniuException ex) {
        Response r = ex.response;
        System.err.println(r.toString());
        try {
            System.err.println(r.bodyString());
        } catch (QiniuException ex2) {
            //ignore
        }
    }
} catch (UnsupportedEncodingException ex) {
    //ignore
}

以及删除上传的文件:

//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.region0());
//...其他参数参考类注释
String accessKey = "your access key";
String secretKey = "your secret key";
String bucket = "your bucket name";
String key = "your file key";
Auth auth = Auth.create(accessKey, secretKey);
BucketManager bucketManager = new BucketManager(auth, cfg);
try {
    bucketManager.delete(bucket, key);
} catch (QiniuException ex) {
    //如果遇到异常,说明删除失败
    System.err.println(ex.code());
    System.err.println(ex.response.toString());
}

上面有提到比较关键的信息,因为使用七牛云服务要进行认证,上面有三个数据「AK、SK和bucket name」,AK和SK也就是AccessKey/SecretKey

AK和SK这个信息中可以在如下的页面获取,bucket name也就是你在创建存储空间的时候填信息的名称:

有了这些信息,我们可以将上面的代码封装成自己的工具类:

public class QiniuUtils {
    // 填写你的accessKey值
    public  static String accessKey = "your accessKey";
     // 填写你的secretKey 值
    public  static String secretKey = "your secretKey ";
    // 填写你的 bucket name值
    public  static String bucket = "your bucket name";

    /**
     * 上传文件
     * @param filePath 文件路径
     * @param fileName 文件名
     */
    public static void uploadFile(String filePath,String fileName){
        Configuration cfg = new Configuration(Zone.zone0());
        UploadManager uploadManager = new UploadManager(cfg);
        Auth auth = Auth.create(accessKey, secretKey);
        String upToken = auth.uploadToken(bucket);
        try {
            Response response = uploadManager.put(filePath, fileName, upToken);
            DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
        } catch (QiniuException ex) {
            Response r = ex.response;
            try {
                System.err.println(r.bodyString());
            } catch (QiniuException ex2) {
                //ignore
            }
        }
    }

    /**
     * 上传文件
     * @param bytes 文件字节数组
     * @param fileName 文件名
     */
    public static void uploadFile(byte[] bytes, String fileName){
        Configuration cfg = new Configuration(Zone.zone0());
        UploadManager uploadManager = new UploadManager(cfg);
        String key = fileName;
        Auth auth = Auth.create(accessKey, secretKey);
        String upToken = auth.uploadToken(bucket);
        try {
            Response response = uploadManager.put(bytes, key, upToken);
            DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
        } catch (QiniuException ex) {
            Response r = ex.response;
            try {
                System.err.println(r.bodyString());
            } catch (QiniuException ex2) {
                //ignore
            }
        }
    }

    /**
     * 删除文件
     * @param fileName 文件名
     */
    public static void deleteFile(String fileName){
        Configuration cfg = new Configuration(Zone.zone0());
        String key = fileName;
        Auth auth = Auth.create(accessKey, secretKey);
        BucketManager bucketManager = new BucketManager(auth, cfg);
        try {
            bucketManager.delete(bucket, key);
        } catch (QiniuException ex) {
            System.err.println(ex.code());
            System.err.println(ex.response.toString());
        }
    }
}

然后写一个接口作为文件上传的测试类:

@Autowired 
private JedisPool jedisPool; //图片上传 
@RequestMapping("/uploadImage"
public ResponseResult upload(@RequestParam("imgFile")MultipartFile imgFile){ 
 try{
     //获取原始文件名 
  String originalFilename = imgFile.getOriginalFilename(); 
  //获取图片名后缀 
  int lastIndexOf = originalFilename.lastIndexOf("."); 
  String suffix = originalFilename.substring(lastIndexOf ‐ 1); 
  //使用UUID生成图片名,防止名称一样
  String fileName = UUID.randomUUID().toString() + suffix; QiniuUtils.uploadFile(imgFile.getBytes(),fileName); //图片上传成功 
  ResponseResult result = new ResponseResult (ResponseConstant.UPLOAD_SUCCESS,"上传成功"); 
  result.setData(fileName); 
  //将上传图片名称存入Redis,基于Redis的Set集合存储 
  jedisPool.getResource().sadd(ImageConstant.ALl_PIC_RESOURCES,fileName ); 
  return result; 
 }catch (Exception e){ 
  e.printStackTrace(); //图片上传失败 
  return new ResponseResult (ResponseConstant.UPLOAD_FAIL,"文件上传失败"); 
 } 
}

这里在作为图片上传的时候使用Redis的set集合进行缓存,这样做的目的就是,用户自己点击上传文件后,填写完信息,但是后面就有可能包填写的消息取消掉。

这样这些图片就会成为我们的垃圾图片,若是没有存储这些图片的信息,从此也找不到这些图片,这样这些垃圾图片就会占用空间。

我们的解决方案就是,先把所有的这些图片还存在Redis的一个Set集合中,叫做ALl_PIC_RESOURCES

然后把上传成功的图片还存在Redis的另一个Set集合中叫做L:SUCCESS_PIC_RESOURCES

最后系统起一个定时任务,定期清理掉ALl_PIC_RESOURCESL:SUCCESS_PIC_RESOURCES差值的图片数据,清理时间可以设置在晚上凌晨,这样对系统的性能影响就不会那么大。

定时任务可以选用Quartz,我相信这个定时任务框架应该很多人都用过,在项目中直接引入依赖坐标:

<dependency>
     <groupId>org.quartz-scheduler</groupId>
     <artifactId>quartz</artifactId>
 </dependency>
 <dependency>
     <groupId>org.quartz-scheduler</groupId>
     <artifactId>quartz-jobs</artifactId>
 </dependency>

具体的定时任务的配置可以自行百度一下,这里只给出,定时任务清理图片的方法:

public void deleteImage(){
        //比较两个Set集合得到差集,得到无效的图片名称集合
        Set<String> imageSet = redisTemplate.opsForSet().difference("ALl_PIC_RESOURCES""L:SUCCESS_PIC_RESOURCES");
        for (String imageName : imageSet ) {
            //删除图片
            QiniuUtils.deleteFileFrom(imageName );
            //删除缓存中无用的图片
            redisTemplate.boundSetOps("ALl_PIC_RESOURCES").remove(imageName );
        }
    }

好了,今天的实例讲解就到这里了,觉得有帮助的求个三连,我们下期再见。