在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
文件服务器:负责存储用户上传文件的服务器
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。
常见的图片存储方案:
方案一:使用nginx搭建图片服务器
方案二:使用开源的分布式文件存储系统,例如Fastdfs==、HDFS等
方案三:使用云存储,例如阿里云、七牛云等
七牛云(隶属于上海七牛信息技术有限公司)是国内领先的以视觉智能和数据智能为核心的企业级云计算服务商,同时也是国内知名智能视频云服务商,累计为 70 多万家企业提供服务,覆盖了国内80%网民。围绕富媒体场景推出了对象存储、融合 CDN 加速、容器云、大数据平台、深度学习平台等产品、并提供一站式智能视频云解决方案。为各行业及应用提供可持续发展的智能视频云生态,帮助企业快速上云,创造更广阔的商业价值。
官网:https://www.qiniu.com/
通过七牛云官网介绍我们可以知道其提供了多种服务,==我们主要使用的是七牛云提供的对象存储服务来存储图片。
文件上传
public class TestQiniu {
// 上传本地文件
@Test
public void uploadFile(){
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone0());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
String accessKey ="xxxxxxxxxxxxxxx";
String secretKey = "xxxxxxxxxxxxxxxxxxxx";
String bucket = "itcast_health";
//如果是Windows情况下,格式是 D:\\qiniu\\test.png,可支持中文
String localFilePath = "D:/2.jpg";
//默认不指定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
}
}
}
}
文件删除
// 删除空间中的文件
@Test
public void deleteFile(){
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone0());
//...其他参数参考类注释
String accessKey = "xxxxxxxxxxxxxxxxx";
String secretKey = "xxxxxxxxxxxxxxxx";
String bucket = "itcast_health";
String key = "xxxxxxxxxxxxx";
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());
}
}
封装工具类
/**
* 批量删除
* @param filenames 需要删除的文件名列表
* @return 删除成功的文件名列表
*/
public static List<String> removeFiles(String... filenames){
// 删除成功的文件名列表
List<String> removeSuccessList = new ArrayList<String>();
if(filenames.length > 0){
// 创建仓库管理器
BucketManager bucketManager = getBucketManager();
// 创建批处理器
BucketManager.Batch batch = new BucketManager.Batch();
// 批量删除多个文件
batch.delete(BUCKET,filenames);
try {
// 获取服务器的响应
Response res = bucketManager.batch(batch);
// 获得批处理的状态
BatchStatus[] batchStatuses = res.jsonToObject(BatchStatus[].class);
for (int i = 0; i < filenames.length; i++) {
BatchStatus status = batchStatuses[i];
String key = filenames[i];
System.out.print(key + "\t");
if (status.code == 200) {
removeSuccessList.add(key);
System.out.println("delete success");
} else {
System.out.println("delete failure");
}
}
} catch (QiniuException e) {
e.printStackTrace();
throw new RuntimeException(MessageConstant.PIC_UPLOAD_FAIL);
}
}
return removeSuccessList;
}
public static void uploadFile(String localFilePath, String savedFilename){
UploadManager uploadManager = getUploadManager();
String upToken = getToken();
try {
Response response = uploadManager.put(localFilePath, savedFilename, upToken);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(String.format("key=%s, hash=%s",putRet.key, putRet.hash));
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
throw new RuntimeException(MessageConstant.PIC_UPLOAD_FAIL);
}
}
public static void uploadViaByte(byte[] bytes, String savedFilename){
UploadManager uploadManager = getUploadManager();
String upToken = getToken();
try {
Response response = uploadManager.put(bytes, savedFilename, 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
}
throw new RuntimeException(MessageConstant.PIC_UPLOAD_FAIL);
}
}
private static String getToken(){
// 创建授权
Auth auth = Auth.create(ACCESSKEY, SECRETKEY);
// 获得认证后的令牌
String upToken = auth.uploadToken(BUCKET);
return upToken;
}
private static UploadManager getUploadManager(){
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
//构建上传管理器
return new UploadManager(cfg);
}
private static BucketManager getBucketManager(){
// 创建授权信息
Auth auth = Auth.create(ACCESSKEY, SECRETKEY);
// 创建操作某个仓库的管理器
return new BucketManager(auth, new Configuration(Zone.zone2()));
}
完善文件上传,Redis存储图片名称
前面我们已经完成了文件上传,将图片存储在了七牛云服务器中。但是这个过程存在一个问题,就是如果用户只上传了图片而没有最终保存套餐信息到我们的数据库,这时我们上传的图片就变为了垃圾图片。对于这些垃圾图片我们需要定时清理来释放磁盘空间。这就需要我们能够区分出来哪些是垃圾图片,哪些不是垃圾图片。如何实现呢?
方案就是利用redis来保存图片名称,具体做法为:
1、当用户上传图片后,将图片名称保存到redis的一个Set集合中,例如集合名称为setmealPicResources
2、当用户添加套餐后,将图片名称保存到redis的另一个Set集合中,例如集合名称为setmealPicDbResources
3、计算setmealPicResources集合与setmealPicDbResources集合的差值,结果就是垃圾图片的名称集合,清理这些图片即可
提供Redis常量类
public class RedisConstant {
//套餐图片所有图片名称
public static final String SETMEAL_PIC_RESOURCES = "setmealPicResources";
//套餐图片保存在数据库中的图片名称
public static final String SETMEAL_PIC_DB_RESOURCES = "setmealPicDbResources";
}
完善SetmealController,在文件上传成功后将图片名称保存到redis集合中
@Autowired
private JedisPool jedisPool;
//图片上传
@RequestMapping("/upload")
public Result upload(@RequestParam(“imgFile”)MultipartFile imgFile){
try{
//获取原始文件名
String originalFilename = imgFile.getOriginalFilename();
int lastIndexOf = originalFilename.lastIndexOf(".");
//获取文件后缀
String suffix = originalFilename.substring(lastIndexOf);
//使用UUID随机产生文件名称,防止同名文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
QiniuUtils.upload2Qiniu(imgFile.getBytes(),fileName);
//图片上传成功
Result result = new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS,fileName);
//将上传图片名称存入Redis,基于Redis的Set集合存储
jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_RESOURCES,fileName);
return result;
}catch (Exception e){
e.printStackTrace();
//图片上传失败
return new Result(false,MessageConstant.PIC_UPLOAD_FAIL);
}
}
添加:
//将上传图片名称存入Redis,基于Redis的Set集合存储
jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_RESOURCES,fileName);
完善SetmealController服务类,在保存完成套餐信息后将图片名称存储到redis集合中
@Autowired
private JedisPool jedisPool;
/**
- 添加
*/
@PostMapping("/add")
public Result add(@RequestBody Setmeal setmeal, Integer[] checkgroupIds){
// 调用服务更新
setmealService.add(setmeal, checkgroupIds);
// 添加成功,要记录有用的图片到redis集合中
Jedis jedis = jedisPool.getResource();
jedis.sadd(RedisConstant.SETMEAL_PIC_DB_RESOURCES, setmeal.getImg());
jedis.close();
return new Result(true, MessageConstant.ADD_SETMEAL_SUCCESS);
}
@PostMapping("/update")
public Result update(@RequestBody Setmeal setmeal, Integer[] checkgroupIds){
// 获取原有图片的名称,判断图片是否更改了,如果更改了,那么旧的图片应该从有用的集合中移除
Setmeal setmealInDb = setmealService.findById(setmeal.getId());
// 调用服务更新
setmealService.update(setmeal, checkgroupIds);
Jedis jedis = jedisPool.getResource();
// 判断是否是需要从有用的集合中删除
if(!setmealInDb.getImg().equals(setmeal.getImg())){
//图片修改了,旧的就没用,就要删除
jedis.srem(RedisConstant.SETMEAL_PIC_DB_RESOURCES, setmealInDb.getImg());
}
// 修改成功,要记录有用的图片到redis集合中
jedis.sadd(RedisConstant.SETMEAL_PIC_DB_RESOURCES, setmeal.getImg());
jedis.close();
return new Result(true, MessageConstant.EDIT_SETMEAL_SUCCESS);
}
// 删除套餐
- 删除
*/
@PostMapping("/delete")
public Result deleteById(int id){
// 查一下图片名称
Setmeal setmeal = setmealService.findById(id);
// 调用业务服务删除
setmealService.deleteById(id);
// 从redis,保存了数据库存放的图片集合中移除这个被删除的图片
Jedis jedis = jedisPool.getResource();
jedis.srem(RedisConstant.SETMEAL_PIC_DB_RESOURCES, setmeal.getImg());
jedis.close();
return new Result(true, MessageConstant.DELETE_SETMEAL_SUCCESS);
}
删除垃圾图片,根据以下代码完成,创建测试类,进行删除
@ContextConfiguration(locations = “classpath:spring-redis.xml”)
@RunWith(value = SpringJUnit4ClassRunner.class)
public class TestDeletePic {
@Autowired
JedisPool jedisPool;
// 删除Redis中2个不同key值存储的不同图片
@Test
public void test(){
Set<String> set = jedisPool.getResource().sdiff(RedisConstant.SETMEAL_PIC_RESOURCE, RedisConstant.SETMEAL_PIC_DB_RESOURCE);
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
String pic = iterator.next();
System.out.println("删除的图片:"+pic);
// 删除七牛云上的图片
QiniuUtils.deleteFileFromQiniu(pic);
// 删除Redis中key值为SETMEAL_PIC_RESOURCE的数据
jedisPool.getResource().srem(RedisConstant.SETMEAL_PIC_RESOURCE,pic);
}
}
}
定时清理垃圾图片
垃圾图片定义: 数据库上的图片就是要的,数据中没有的是不需要。图片是存到7牛上。
垃圾图片: 7牛上的减掉数据上的剩下的就是垃圾
希望后台自动运行,什么时候。 触发时机凌晨4点, 没有人来操作时,才能执行
创建ClearImgJob定时任务类
@Component
public class ClearImgJob {
@Autowired
private JedisPool jedisPool;
/**
* 清理垃圾图片的执行方法
*/
public void clearImg(){
// 获取 redis的连接
Jedis jedis = jedisPool.getResource();
// 计算2个set集合的差集 所有七牛图片-保存到数据库
// 需要删除的图片
Set<String> need2Delete = jedis.sdiff(RedisConstant.SETMEAL_PIC_RESOURCES, RedisConstant.SETMEAL_PIC_DB_RESOURCES);
// 调用七牛删除
QiNiuUtils.removeFiles(need2Delete.toArray(new String[]{}));
// 删除redis上的图片, 两边的图片已经同步了
jedis.del(RedisConstant.SETMEAL_PIC_RESOURCES, RedisConstant.SETMEAL_PIC_DB_RESOURCES);
}
}
测试:
控制台输出 “delete success” 代表成功