图片存储方案-七牛云存储
简介
在我们实际的项目中,经常会将服务器进行划分,比如:专门运行我们程序的应用服务器、专门运行我们数据库的数据库服务器、还有负责文件存储的文件服务器、负责视频存储的服务器。
这些服务器组成我们的所有服务、各司其职,这样划分的目的相比大家也知道,减轻一台服务器服务器的压力。
当用户请求我们应用应用服务器的时候,由我们的应用再分别访问我们数据库服务器、文件存储服务器等,最后将请求的资源返回给我们的用户,这样构成整个应用的请求过程:
我们今天要讲的就是图片存储,图片储存的方案有很多,比如:可以使用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_RESOURCES
和L: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 );
}
}
好了,今天的实例讲解就到这里了,觉得有帮助的求个三连,我们下期再见。