這里舉例用的是七牛雲,編程語言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清理垃圾圖片,直接開啟創建的工程就可以了,工程一開啟就會按照指定的方式定時執行任務類(清理垃圾圖片)