项目中使用云服务器上传图片产生垃圾文件的处理思路

这里举例用的是七牛云,编程语言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清理垃圾图片,直接开启创建的工程就可以了,工程一开启就会按照指定的方式定时执行任务类(清理垃圾图片)

 

posted @ 2020-10-13 12:15  名难  阅读(1215)  评论(0编辑  收藏  举报