SpringBoot刪除清理垃圾文件的實(shí)現(xiàn)
一、什么是垃圾文件?
垃圾文件是指,在表單中用戶(hù)上傳了文件到服務(wù)器,但表單未被提交或保存,導(dǎo)致上傳的這個(gè)文件將不會(huì)被記錄到相關(guān)業(yè)務(wù)表中,再也不會(huì)被使用,成為了垃圾文件。時(shí)間久了服務(wù)器上會(huì)有越來(lái)越多無(wú)用的垃圾文件占用服務(wù)器存儲(chǔ)。
二、我處理垃圾文件的方式
對(duì)于垃圾文件,一般可配合緩存+事件監(jiān)聽(tīng)進(jìn)行刪除;還有一種方式是定時(shí)任務(wù),定時(shí)掃表,掃描每一個(gè)使用文件資源的表字段,匯總成在使用的文件,再對(duì)比文件表,即可篩選出需要?jiǎng)h除的垃圾文件。本文中我將介紹兩種方式:
- Redis+事件監(jiān)聽(tīng)器
- 定時(shí)掃表求差集刪除文件
方式一、Redis+事件監(jiān)聽(tīng)器
當(dāng)文件通過(guò)上傳接口上傳到服務(wù)器時(shí),應(yīng)在上傳接口將fileUrl存入Redis中并設(shè)置過(guò)期事件。
當(dāng)提交表單時(shí),在ServiceImpl層方法中使用方法進(jìn)行驗(yàn)證,如果圖片在緩存中有,則說(shuō)明圖片是在有效期內(nèi)提交的,直接從緩存中刪除對(duì)應(yīng)的key即可。這里我設(shè)計(jì)一個(gè)工具類(lèi)AsyncFileHandler,checkValid()方法一次性可傳入多個(gè)字段進(jìn)行fileUrl校驗(yàn)。當(dāng)然這只能刪除新增時(shí)上傳不提交表單產(chǎn)生的垃圾文件,如果是更新時(shí),舊文件被替換了,提交表單時(shí)也不會(huì)知道舊文件是誰(shuí),這時(shí)有朋友可能會(huì)說(shuō)了:“前端文件組件在刪除文件時(shí)對(duì)應(yīng)的把文件也從服務(wù)器刪除不就好了嘛。”這么說(shuō)其實(shí)也可以,但是針對(duì)富文本情況呢?富文本里上傳了圖片就沒(méi)法再通過(guò)上傳組件刪除了。不過(guò)別慌,我們可以在提交表單時(shí)查詢(xún)出舊數(shù)據(jù),讓新舊數(shù)據(jù)字段進(jìn)行對(duì)比,如果舊字段中的fileUrl未出現(xiàn)在新字段中,則應(yīng)刪除舊字段的fileUrl。當(dāng)然,在更新業(yè)務(wù)中也需要執(zhí)行checkValid()方法校驗(yàn)新上傳文件的有效性,再執(zhí)行compareField()方法檢測(cè)舊文件是否還被使用,不再使用則刪除。
那么,redis中過(guò)期的文件怎么處理呢?這需要定義一個(gè)過(guò)期事件監(jiān)聽(tīng)器,當(dāng)key快過(guò)期時(shí)觸發(fā)監(jiān)聽(tīng)器,我們通過(guò)監(jiān)聽(tīng)器拿到redis key刪除未使用的垃圾文件即可。key保存的是fileUrl
1.將上傳的文件返回的fileUrl存入Redis并設(shè)置過(guò)期時(shí)間
String fileUrl = this.storageFile(engine, file, false); //上傳minio并返回fileUrl
String redisKey = "file:url:" + fileUrl;
cacheOperator.put(redisKey, fileUrl, 60 * 60 * 24); //將fileUrl存入Redis中并設(shè)置24小時(shí)過(guò)期
2.定義AsyncFileHandler類(lèi),方便檢測(cè)文件有效性和對(duì)比新文件刪除舊文件。
@Slf4j
@Service
@EnableAsync
@Component
public class AsyncFileHandler {
@Resource
private CommonCacheOperator cacheOperator;
@Resource
private DevFileApi devFileApi;
// URL正則表達(dá)式模式
private static final Pattern URL_PATTERN = Pattern.compile("/(\\d+)\\.[a-zA-Z0-9]+");
@Async
public void checkValid(String... imgFields) throws IOException {
for (String field : imgFields) {
Matcher matcher = URL_PATTERN.matcher(field);
Set<String> extractedUrls = new HashSet<>();
while (matcher.find()) {
String fileUrl = matcher.group(1);
extractedUrls.add(fileUrl);
}
// 刪除Redis中匹配的URL
deleteMatchedUrlsFromRedis(extractedUrls);
}
}
// 比較新舊記錄字段中文件的差異,并刪除未被使用的舊字段
@Async
public void compareField(String newFiled, String oldFiled){
Matcher matcher1= URL_PATTERN.matcher(newFiled);
Matcher matcher2= URL_PATTERN.matcher(oldFiled);
Set<String> newFileUrls = new HashSet<>();
Set<String> oldFileUrls = new HashSet<>();
while (matcher1.find()) {
String fileUrl = matcher1.group(1);
newFileUrls.add(fileUrl);
}
while (matcher2.find()) {
String fileUrl = matcher2.group(1);
oldFileUrls.add(fileUrl);
}
Set<String> toDelFileUrls = oldFileUrls.stream()
.filter(element -> !newFileUrls.contains(element))
.collect(Collectors.toSet());
// 刪除不再使用的文件
deleteDiffFileUrls(toDelFileUrls);
}
// 刪除在有效期內(nèi)提交表單的文件,不刪除的話后面會(huì)在redis過(guò)期事件中被刪除。
private void deleteMatchedUrlsFromRedis(Set<String> urls) {
int deletedCount = 0;
for (String url : urls) {
String redisKey = "file:url:" + url;
if (cacheOperator.get(url)!=null){
cacheOperator.remove(redisKey);
log.info("從Redis中刪除已引用的文件URL: {}", url);
deletedCount++;
}
}
log.info("共處理{}個(gè)文件URL,成功刪除{}個(gè)Redis鍵", urls.size(), deletedCount);
}
// 刪除新舊差異文件
private void deleteDiffFileUrls(Set<String> urls) {
for (String url : urls) {
devFileApi.deleteFileByUrl(url);
}
}
}
3.定義Redis過(guò)期事件監(jiān)聽(tīng)器
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
@Resource
private DevFileApi devFileApi;
/**
* 創(chuàng)建RedisKeyExpirationListener bean時(shí)注入 redisMessageListenerContainer
*
* @param redisMessageListenerContainer RedisConfig中配置的消息監(jiān)聽(tīng)者容器bean
*/
public RedisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) {
super(redisMessageListenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String channel = new String(message.getChannel()); // __keyevent@*__:expired
String pa = new String(pattern); // __keyevent@*__:expired
String expiredKey = message.toString();
if (expiredKey.startsWith("Cache:file:url:")){
System.out.println("監(jiān)聽(tīng)到過(guò)期文件:" + expiredKey);
devFileApi.deleteFileByUrl(expiredKey);
}
}
}
方式二、定時(shí)掃表求差集刪除文件
一個(gè)系統(tǒng)中可能好幾個(gè)表中的多個(gè)字段使用文件資源,一個(gè)情況方式一也可以解決。假設(shè)有這樣一種情況:系統(tǒng)中的一個(gè)文件可能被多個(gè)表的字段共享,表中的字段存入的是同一個(gè)fileUrl,如果刪除這個(gè)文件可能會(huì)牽扯到好幾張表。這種情況就適合掃表求差集刪除文件了。首先找出全數(shù)據(jù)庫(kù)表中使用文件的字段,找出這些字段使用的fileUrl,肯定有重復(fù)的fileUrl,所以我們使用Set集合保存,多個(gè)fileUrl只保留一個(gè)即可,這個(gè)Set集合里存的就是我們數(shù)據(jù)庫(kù)中所有在使用的文件,非垃圾文件,那么直接在文件表中使用notIn查詢(xún),查詢(xún)出不在使用的文件,也就是垃圾文件即可,然后刪除這些垃圾文件。直接上代碼:
/**
* 清理垃圾文件定時(shí)任務(wù)
*/
@Slf4j
@Component
public class ClearGarbageFileTaskRunner implements CommonTimerTaskRunner {
@Resource
private DevFileService devFileService;
@Override
public void action(String extJson) {
// 哪個(gè)表哪個(gè)字段在使用文件資源
Map<String, String[]> map = new HashMap<>();
map.put("BIZ_CHILD_USER", new String[]{"AVATAR"});
map.put("BIZ_COLLECT", new String[]{"ANALYSIS_VIDEO","ANALYSIS", "COVER", "OPTIONS", "CONTENT"});
map.put("BIZ_QUESTION", new String[]{"ANALYSIS_VIDEO", "ANALYSIS","COVER", "OPTIONS", "CONTENT"});
map.put("BIZ_WRONG", new String[]{"ANALYSIS_VIDEO","ANALYSIS", "COVER", "OPTIONS", "CONTENT"});
map.put("BIZ_EXAM_RECORD", new String[]{"ANALYSIS_VIDEO","ANALYSIS", "COVER", "OPTIONS", "CONTENT"});
map.put("BIZ_LECTURE", new String[]{"VIDEO", "INTRODUCE"});
map.put("BIZ_NOTICE", new String[]{"IMAGE", "CONTENT"});
// 全表中在使用的文件ID集合
Set<String> allTableInUseFileIdSet = new HashSet<>();
for (Map.Entry<String, String[]> entry : map.entrySet()) {
Set<String> oneTableInUseFileIdSet = devFileService.getInUseFileIdList(entry.getKey(), entry.getValue());
allTableInUseFileIdSet.addAll(oneTableInUseFileIdSet);
}
log.info("------------------------------------------------清理垃圾文件Start------------------------------------------------");
log.info("在使用的文件數(shù)量:{}", allTableInUseFileIdSet.size());
// 獲取垃圾文件ID
List<DevFileIdParam> junkFileIds = devFileService.list(new LambdaQueryWrapper<DevFile>().notIn(DevFile::getId, allTableInUseFileIdSet))
.stream().map(devFile -> {
DevFileIdParam param = new DevFileIdParam();
param.setId(devFile.getId()); // 確保字段名稱(chēng)匹配
return param;
}).toList();
log.info("待清理的文件數(shù)量:{}", junkFileIds.size());
if (!junkFileIds.isEmpty()) {
// 執(zhí)行物理刪除垃圾文件
devFileService.deleteGarbageFiles(junkFileIds);
}
log.info("------------------------------------------------清理垃圾文件End------------------------------------------------\n");
}
}
注意:方式二刪除垃圾文件,一定不要遺漏需要過(guò)濾的數(shù)據(jù)表字段,否則會(huì)被誤刪除。
到此這篇關(guān)于SpringBoot刪除清理垃圾文件的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot刪除清理垃圾文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis中強(qiáng)大的resultMap功能介紹
這篇文章主要給大家介紹了關(guān)于Mybatis中強(qiáng)大的resultMap功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Mybatis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
SpringBoot詳細(xì)講解視圖整合引擎thymeleaf
這篇文章主要分享了Spring Boot整合使用Thymeleaf,Thymeleaf是新一代的Java模板引擎,類(lèi)似于Velocity、FreeMarker等傳統(tǒng)引擎,關(guān)于其更多相關(guān)內(nèi)容,需要的小伙伴可以參考一下2022-06-06
JAVA基本類(lèi)型包裝類(lèi) BigDecimal BigInteger 的使用
Java 中預(yù)定義了八種基本數(shù)據(jù)類(lèi)型,包括:byte,int,long,double,float,boolean,char,short,接下來(lái)文章小編將向大家介紹其中幾個(gè)類(lèi)型的內(nèi)容,需要的朋友可以參考下文章2021-09-09
spring boot simple類(lèi)型cache使用詳解
這篇文章主要介紹了spring boot simple類(lèi)型cache使用,這里用的不是 redis 的緩存,simple 的緩存默認(rèn)用的是java的ConcurrentHashMap, 單純的simple緩存,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
解決springcloud Zuul丟失Cookie的問(wèn)題
這篇文章主要介紹了解決springcloud Zuul丟失Cookie的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
解決SpringBoot2.1.0+RocketMQ版本沖突問(wèn)題
這篇文章主要介紹了解決SpringBoot2.1.0+RocketMQ版本沖突問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06

