项目中使用云服务器上传图片产生垃圾文件的处理思路
这里举例用的是七牛云,编程语言java,项目的框架是ssm,架构方式是soa
首先,什么是垃圾图片?
1、就是客户端点击添加图片,就会发送请求,将图片发送到服务端,服务端会生成唯一图片名称,然后上传图片到七牛云,并返回给客户端唯一生成的图片名称,但是这个时候注意,并没有将图片名称保存到数据库中,只是将图片上传到了七牛云。
2、等客户端将所有表单数据添加完毕后,点击确认按钮,会将表单数据一起发送给服务端,表单数据中就包含了图片的名称,这个时候才会执行添加到数据库的操作。
3、而以上两步,有可能会出现一种情况:就是客户端在第一步点击了添加图片按钮,就会将图片上传到七牛云并保存,但是数据库中并没有保存图片名,如果这个时候在客户端点击了取消按钮,就不会发生第二步的操作,也就是说,图片在七牛云保存着,但是因为没有保存图片名到数据库,而浏览七牛云中的图片必须要图片名才能找到,导致这个图片上传之后再也找不到了,这种找不到的图片就是垃圾图片,光占云服务器的内存,无法利用。
清理垃圾图片的思路
简单说,创建两个容器,执行第一步操作的时候,将图片名称存到一个容器中,执行第二步操作的时候,又将图片名称存到另一个容器中,如果第一个容器中有的图片名称,而第二个容器中没有对应的图片名称(两个容器中图片名称的差集)则可以判定为垃圾图片,执行七牛云提供的代码就可以删除。
具体实现方式举例:可以使用Redis做这个容器
1、在web工程中添加配置文件:spring_redis.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--Jedis连接池的相关配置 可以参考下面网址 https://www.iteye.com/blog/fengguang0051-2237171 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal"> <value>200</value> </property> <property name="maxIdle"> <value>50</value> </property> <property name="testOnBorrow" value="true"/> <property name="testOnReturn" value="true"/> </bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg name="poolConfig" ref="jedisPoolConfig" /> <constructor-arg name="host" value="127.0.0.1" /> <!--Redis的端口号,自己设置,包括上面的ip地址127.0.0.1也可以自己设置--> <constructor-arg name="port" value="6379" type="int" /> <constructor-arg name="timeout" value="30000" type="int" /> </bean> </beans>
2、但凡配置文件编写完后,没有加载等于没有写,所以需要加载该配置文件,一般在web工程中会有spring_mvc的配置文件,如果有就在这个配置文件里面引入spring_redis.xml即可,如果没有就需要到web.xml中加载,这里两种方式都写一下
(1)springMVC中引入:
<import resource="classpath:spring-redis.xml"/>
(2)在web.xml中加载:二选一即可
<init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring_redis.xml</param-value> </init-param> <load-on-startup>1</load-on-startup>
3、两个容器的名字可以选择写死,但这里推荐用常量类,利于维护
public class RedisConstant { //套餐图片所有图片名称 public static final String SETMEAL_PIC_RESOURCES = "setmealPicResources"; //套餐图片保存在数据库中的图片名称 public static final String SETMEAL_PIC_DB_RESOURCES = "setmealPicDbResources"; }
4、准备工作做好了,现在可以开始编写代码了(编写代码只示范第一个容器了,第二个容器代码一样,只是容器名换一下就可以了)
(1)首先在controller中需要注入jedisPool
(2)然后:jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_RESOURCES,newFileName);
这里传入的两个参数:第一个是从定义的常量类中取出来的第一个容器名称,如果没有定义常量类,直接写字符串作为容器名称;
第二个参数是要保存进容器的图片名称
这一步执行会得到的效果:在redis中创建一个容器,并将上传成功的图片名称保存进这个容器,这样,客户端每添加一次图片,上传到七牛云成功后都会在容器中存储一个图片名称
(3)第二个容器也需要创建起来,就是在客户端点击确认后,会提交一个表单,表单中包含了图片名称,服务端不止要将图片名称保存到数据库,还要保存到另一个容器中,用于跟第一个容器计算差集,这里代码跟上面一样,不重复写了
5、两个容器都有了,那么如何计算插值呢?(这里的举例只用于测试,需要代码自动执行并删除七牛云中的垃圾图片的话,需要用到另一个quartz定时任务调度的框架,这个以后说)
可复制代码如下:
/** * 测试类(因为是测试类,所以要加下面两个注解) */ @ContextConfiguration(locations = "classpath:spring-redis.xml") @RunWith(SpringJUnit4ClassRunner.class) public class ClearImgTest { @Autowired private JedisPool jedisPool; // @Test public void clearImg(){ //传入第一个容器名称和第二个容器名称就可以得到两个集合的差集 Set<String> sdiff = jedisPool.getResource().sdiff(RedisConstant.SETMEAL_PIC_RESOURCES, RedisConstant.SETMEAL_PIC_DB_RESOURCES); //先要对差集进行非空判断,如果没有垃圾图片才执行删除操作 if(sdiff != null && sdiff.size()>0){ //循环差集执行删除操作,这里的删除操作是调用七牛云提供的删除代码,我这里将删除代码粘贴封装到自己的util类中了,所以调用QiniuUtils.deleteFileFromQiniu传入要删除的文件名就行了 for (String fileName : sdiff) { //调用七牛云接口删除图片 QiniuUtils.deleteFileFromQiniu(fileName); } } } }
这里再来补充这个案例用Quartz框架定时执行删除垃圾图片的步骤(想要了解Quartz使用,还可移步看我另外的随笔Quartz框架使用介绍)
1:创建maven聚合工程health_jobs,打包方式为war,导入Quartz等相关坐标
添加相关依赖,这里我的父工程有依赖,如果要直接复制的话会有问题,自己改一下,不会改另外的随笔Quartz框架使用介绍中有写,去那里复制
<dependencies> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <configuration> <!-- 指定端口 --> <port>84</port> <!-- 请求路径 --> <path>/</path> </configuration> </plugin> </plugins> </build>
2:配置web.xml
-
web容器启动,加载spring容器
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Archetype Created Web Application</display-name> <!-- 加载spring容器 --> <context-param> <param-name>contextConfigLocation</param-name>
<!-- 下面的两个 * ,前面一个表示当前工程依赖的工程里面如果有相同文件名的配置,也会被当前工程加载
后面一个表示这个文件名后面无论是什么,都会被这个工程加载进来
这里前面一个*可以不用,后面那个得要,因为我要加载两个配置文件,如果不加的话,需要在其中一个配置文件中引入另一个配置文件--> <param-value>classpath*:applicationContext*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
3:配置log4j.properties
### direct log messages to stdout ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.err log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### direct messages to file mylog.log ### log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=c:\\mylog.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### log4j.rootLogger=debug, stdout
4:配置applicationContext-redis.xml
-
spring整合redis
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--Jedis连接池的相关配置--> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal"> <value>200</value> </property> <property name="maxIdle"> <value>50</value> </property> <property name="testOnBorrow" value="true"/> <property name="testOnReturn" value="true"/> </bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg name="poolConfig" ref="jedisPoolConfig" /> <constructor-arg name="host" value="127.0.0.1" /> <constructor-arg name="port" value="6379" type="int" /> <constructor-arg name="timeout" value="30000" type="int" /> </bean> </beans>
5:配置applicationContext-jobs.xml
-
spring整合Quartz
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--组件的扫描包-->
<context:component-scan base-package="com.baidu"></context:component-scan>
<!-- 注册自定义Job --> <bean id="jobDemo" class="com.baidu.job.ClearImgJob"></bean> <!-- 注册JobDetail,作用是负责通过反射调用指定的Job --> <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!-- 注入目标对象 --> <property name="targetObject" ref="jobDemo"/> <!-- 注入目标方法 --> <property name="targetMethod" value="clearImg"/> </bean> <!-- 注册一个触发器,指定任务触发的时间 --> <bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <!-- 注入JobDetail --> <property name="jobDetail" ref="jobDetail"/> <!-- 指定触发的时间,基于Cron表达式(0 0 2 * * ?表示凌晨2点执行) --> <property name="cronExpression"> <value>0 0 2 * * ?</value> </property> </bean> <!-- 注册一个统一的调度工厂,通过这个调度工厂调度任务 --> <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <!-- 注入多个触发器 --> <property name="triggers"> <list> <ref bean="myTrigger"/> </list> </property> </bean> </beans>
6:创建ClearImgJob定时任务类(这个上面也有,这里为了完整再来一份)
/** * 定时任务:清理垃圾图片 */ public class ClearImgJob { @Autowired private JedisPool jedisPool; //清理图片 public void clearImg(){ //计算redis中两个集合的差值,获取垃圾图片名称 Set<String> set = jedisPool.getResource().sdiff( RedisConstant.SETMEAL_PIC_RESOURCES, RedisConstant.SETMEAL_PIC_DB_RESOURCES); Iterator<String> iterator = set.iterator(); while(iterator.hasNext()){ String pic = iterator.next(); System.out.println("删除图片的名称是:"+pic); //删除图片服务器中的图片文件 QiniuUtils.deleteFileFromQiniu(pic); //删除redis中的数据 jedisPool.getResource().srem(RedisConstant.SETMEAL_PIC_RESOURCES,pic); } }
//另外,如果想要的话,可以在执行删除垃圾图片之后,顺手把redis中存的图片名称清一下,不然它每次计算插值都是一样的,一直在重复删除已经删除的文件名称
//只是执行清空操作的时候,如果同一时间有客户往redis中保存图片名称的话,会存在数据安全问题,比如客户正常上传文件,
//但是在提交表单之前刚好这里执行了清空操作,就会出问题,自己测试后再用,我自己也没有测过
jedisPool.getResource().del();//参数要传需要清空的key,也就是上面说的容器名称
jedisPool.getResource().del();
}
-
使用Quartz清理垃圾图片,直接开启创建的工程就可以了,工程一开启就会按照指定的方式定时执行任务类(清理垃圾图片)