最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于java實(shí)現(xiàn)具有時(shí)效性文件鏈接

 更新時(shí)間:2023年12月03日 14:07:41   作者:ThreeBody1998  
這篇文章主要為大家詳細(xì)介紹了如何基于java實(shí)現(xiàn)具有時(shí)效性的文件鏈接,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解一下

1.寫在前面

之前在某個(gè)項(xiàng)目中,用戶上傳的文件(頭像、視頻、文檔等等)是通過靜態(tài)路徑來訪問的,這導(dǎo)致一旦該文件的路徑暴露,用戶可以在不登錄的情況下,直接訪問服務(wù)器的文件資源。客戶因此提出,文件的路徑必須要具有時(shí)效性(類似對(duì)象存儲(chǔ)的文件鏈接,超過一定時(shí)間就無法訪問)。

我希望最終可以像對(duì)象存儲(chǔ)一樣,文件鏈接可以設(shè)定訪問時(shí)間,超期后直接報(bào)錯(cuò)。比較常見的方法可以通過緩存來實(shí)現(xiàn)。思路如下:

1.后端在接收到文件的訪問請(qǐng)求時(shí),生成一個(gè)唯一的文件ID,將它和指定的前綴拼接為URL返還給前端,并同時(shí)將此ID作為key,文件信息作為value存入緩存信息。

2.前端通過返回的文件鏈接訪問后端,后端對(duì)鏈接中的ID進(jìn)行截取,前往緩存中查詢,如果存在則以流的的形式返回文件,否則直接返回錯(cuò)誤信息。

時(shí)序圖如下:

2.時(shí)序圖

3.關(guān)鍵技術(shù)點(diǎn)

3.1時(shí)效性

對(duì)于時(shí)效性,我們可以通過緩存來實(shí)現(xiàn)。通過給緩存添加時(shí)間限制,到期就移除,從而達(dá)到URL的時(shí)效性。這里我們通過簡(jiǎn)單的緩存工具類來實(shí)現(xiàn)(也可用redis,數(shù)據(jù)庫(kù)理論上也可以,通過存文件的存入時(shí)間,每次文件請(qǐng)求的時(shí)候判斷一下是否超期,思路上是可以的,但是考慮訪問速度并不推薦)。

以下是緩存對(duì)象:

@AllArgsConstructor
public class CacheEntry<V> {
    //存儲(chǔ)數(shù)據(jù)
    private final V value;
    //過期時(shí)間
    private final long expirationTimeMillis;

    /**
     * 是否過期
     * @return  布爾值
     */
    public boolean isExpired() {
        return System.currentTimeMillis() > expirationTimeMillis;
    }

    public V getValue() {
        return value;
    }
}

再寫個(gè)簡(jiǎn)單的工具類來操作:

@Component
public class CacheManagerUtil<K,V> {
    private final Map<K, CacheEntry<V>> cacheMap;
    private final ScheduledExecutorService scheduler;
    /**
     * 默認(rèn)過期時(shí)間 3小時(shí)
     */
    public static long TTL=1000*60*60*3L;

    public CacheManagerUtil() {
        cacheMap = new ConcurrentHashMap<>();
        scheduler = Executors.newScheduledThreadPool(1);
    }

    /**
     * 存值
     * @param key   鍵
     * @param value 值
     * @param expirationTimeMillis  過期時(shí)間
     */
    public void put(K key, V value, long expirationTimeMillis) {
        expirationTimeMillis += System.currentTimeMillis();
        CacheEntry<V> entry = new CacheEntry<>(value, expirationTimeMillis);
        cacheMap.put(key, entry);
        // 定時(shí)任務(wù),在過期時(shí)間后自動(dòng)銷毀緩存條目
        scheduler.schedule(() -> cacheMap.remove(key), expirationTimeMillis, TimeUnit.MILLISECONDS);
    }

    /**
     * 根據(jù)鍵取值
     * @param key   鍵
     * @return  值
     */
    public V get(K key){
        CacheEntry<V> entry = cacheMap.get(key);
        if (entry != null && !entry.isExpired()) {
            return entry.getValue();
        }
        return null;
    }

    /**
     * 根據(jù)建刪除對(duì)應(yīng)的鍵值對(duì)
     * @param key   鍵
     */
    public void remove(K key) {
        cacheMap.remove(key);
    }

    /**
     * 獲取緩存鍵列表
     * @return  緩存鍵列表
     */
    public List<K> getKeys(){
        return new ArrayList<>(cacheMap.keySet());
    }
}

3.2同一個(gè)接口返回文件資源和錯(cuò)誤信息

首先我們要知道,瀏覽器在訪問某個(gè)鏈接的時(shí)候,會(huì)根據(jù)服務(wù)器返回的Response的中的Content-type來決定如何執(zhí)行響應(yīng)。如果服務(wù)器未指定任何Content-type,瀏覽器有內(nèi)置的處理能力來對(duì)常見的鏈接(圖片、PDF)進(jìn)行處理,常見的比如說使用瀏覽器去打開圖片,打開控制臺(tái),瀏覽器會(huì)有一個(gè)默認(rèn)的網(wǎng)頁(yè)將文件鏈接放在里面。

因此,我們可以通過同一個(gè)Mapping,實(shí)現(xiàn)不同的Response的返回,從而達(dá)到既能返回文件,又可以返回錯(cuò)誤信息。需要的Response格式如下:

1.流式文件下載

response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileCacheVO.getFileName());
response.setHeader("pragma", "no-cache");
response.setHeader("cache-control", "no-cache");
response.setHeader("expires", "0");

2.json格式錯(cuò)誤返回

response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");

這里可以使用策略模式進(jìn)行改寫,首先我們構(gòu)建一個(gè)簡(jiǎn)單的處理響應(yīng)的接口:

public interface ResponseStrategy {
    /**
     * 處理響應(yīng)
     * @param response  響應(yīng)
     * @param fileCacheVO   文件緩存VO
     * @return  操作結(jié)果
     */
    void handleResponse(HttpServletResponse response, FileCacheVO fileCacheVO);
}

A.下載文件策略處理類

@Slf4j
@Component
public class DownLoadFileStrategy implements ResponseStrategy {

    /**
     * 最大字節(jié)大小
     */
    private static final int MAX_BYTE_SIZE = 4096;

    @Override
    public void handleResponse(HttpServletResponse response, FileCacheVO fileCacheVO) {
        //設(shè)置響應(yīng)頭
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileCacheVO.getFileName());
        response.setHeader("pragma", "no-cache");
        response.setHeader("cache-control", "no-cache");
        response.setHeader("expires", "0");
        //以流的形式返回文件
        Path filePath=Paths.get(fileCacheVO.getFilePath());
        try {
            Resource resource = new UrlResource(filePath.toUri());
            InputStream inputStream = Objects.requireNonNull(resource).getInputStream();
            var outputStream = response.getOutputStream();
            byte[] buffer = new byte[MAX_BYTE_SIZE];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            outputStream.close();
        } catch (MalformedURLException e) {
            log.error("文件下載失敗{}", e.getMessage());
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (IOException e) {
            log.error("文件IO異常{}", e.getMessage());
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}

B.無文件策略類

@Slf4j
public class NoFileStrategy implements ResponseStrategy {
    @Override
    public void handleResponse(HttpServletResponse response, FileCacheVO fileCacheVO) {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        String errorJson=new ResponseResult<String>().setHttpResultEnum(HttpResultEnum.SERVER_ERROR).setMsg("文件不存在").toJsonString().toString();
        try {
            response.getWriter().write(errorJson);
        } catch (IOException e) {
            log.error("文件下載失敗{}",e.getMessage());
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}

4.流程及源碼分析

4.1文件上傳接口

文件上傳我們可以分為三步:文件接收并存儲(chǔ)、數(shù)據(jù)庫(kù)記錄插入和存入緩存。具體代碼如下:

/**
* 上傳文件
*
* @param uploadFile 上傳文件
* @return 返回臨時(shí)鏈接
*/
@PostMapping("/uploadFile")
@Transactional(rollbackFor = Exception.class)
public ResponseResult<FileVO> uploadFile(@Validated @NotNull(message = "上傳文件不能為空") MultipartFile uploadFile) {
	ResponseResult<FileVO> responseResult = new ResponseResult<>();
	//文件上傳
	FileUploadResult fileUploadResult = fileService.uploadFile(uploadFile);
	//加入緩存
	FileCacheVO fileCacheVO = new FileCacheVO(fileUploadResult);
	cacheManagerUtil.put(fileUploadResult.getFileId(), fileCacheVO, CacheManagerUtil.TTL);
	return responseResult.setData(new FileVO(fileUploadResult));
}

這里我們通過fileService同時(shí)進(jìn)行文件的存儲(chǔ)和數(shù)據(jù)庫(kù)插入,然后通過緩存工具類cacheManagerUtil存儲(chǔ),使用Apifox進(jìn)行接口測(cè)試,結(jié)果如下:

其中返回的文件鏈接為:
<http://127.0.0.1:8080/timelyFileLink/file/22bb8cda-99d8-4b0b-a6aa-9e3d516b5a5f>URL中的22bb8cda-99d8-4b0b-a6aa-9e3d516b5a5f就是我們緩存到服務(wù)器上的key

4.2鏈接校驗(yàn)

這里我們分為兩步:緩存校驗(yàn)和返回不同的Response。代碼如下:

/**
* 文件下載
*
* @param uuid     文件唯一ID
* @param response 響應(yīng)
*/
@GetMapping("/file/{uuid}")
public void file(@Validated @PathVariable @NotBlank(message = "文件ID不能為空") String uuid, HttpServletResponse response) {
    //校驗(yàn)
    FileCacheVO fileCacheVO = cacheManagerUtil.get(uuid);
    ResponseStrategy strategy = fileCacheVO == null ? new NoFileStrategy() : new DownLoadFileStrategy();
    strategy.handleResponse(response, fileCacheVO);
}

我們先使用緩存工具類進(jìn)行校驗(yàn),如果文件是超期或者不存在的,瀏覽器會(huì)直接返回錯(cuò)誤的json信息:

如果文件是存在,瀏覽器輸入這個(gè)鏈接會(huì)直接下載:

如果html中有地方調(diào)用了這個(gè)圖片,那么圖片會(huì)自動(dòng)反顯,這里我們寫一個(gè)簡(jiǎn)單的html測(cè)試代碼:

<html>
    <img src="http://127.0.0.1:8080/timelyFileLink/file/22bb8cda-99d8-4b0b-a6aa-9e3d516b5a5f"> 
</html>

頁(yè)面如下:

可以看到對(duì)于流式的接口,瀏覽器會(huì)自動(dòng)反顯圖片。

5.總結(jié)

整個(gè)Demo都是基于緩存操作來實(shí)現(xiàn)鏈接的時(shí)效性,對(duì)于需要展示文件鏈接的頁(yè)面,可以通過這種時(shí)效性鏈接來實(shí)現(xiàn)訪問文件的安全性。但由于是操作緩存,實(shí)際我們?cè)谑褂玫臅r(shí)候需要考慮用戶數(shù)量、接口頻率、緩存大小等等問題,如果是正式項(xiàng)目,我其實(shí)更建議使用redis,方便進(jìn)行緩存的管理以及問題的排查。

以上就是基于java實(shí)現(xiàn)具有時(shí)效性文件鏈接的詳細(xì)內(nèi)容,更多關(guān)于java時(shí)效性文件鏈接的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • springboot實(shí)現(xiàn)調(diào)用百度ocr實(shí)現(xiàn)身份識(shí)別+二要素校驗(yàn)功能

    springboot實(shí)現(xiàn)調(diào)用百度ocr實(shí)現(xiàn)身份識(shí)別+二要素校驗(yàn)功能

    本文介紹了如何使用Spring Boot調(diào)用百度OCR服務(wù)進(jìn)行身份識(shí)別,并通過二要素校驗(yàn)確保信息準(zhǔn)確性,感興趣的朋友一起看看吧
    2025-03-03
  • Java生成驗(yàn)證碼

    Java生成驗(yàn)證碼

    本文介紹了Java生成驗(yàn)證碼的流程與方法。具有很好的參考價(jià)值,下面跟著小編一起來看下吧
    2017-02-02
  • Java集合中的Set之LinkedHashSet詳解

    Java集合中的Set之LinkedHashSet詳解

    這篇文章主要介紹了Java集合中的Set之LinkedHashSet詳解,LinkedHashSet是Set集合的一個(gè)實(shí)現(xiàn),具有set集合不重復(fù)的特點(diǎn),同時(shí)具有可預(yù)測(cè)的迭代順序,也就是我們插入的順序,并且linkedHashSet是一個(gè)非線程安全的集合,需要的朋友可以參考下
    2023-09-09
  • Java中常用的異步方法(核心用法和場(chǎng)景)

    Java中常用的異步方法(核心用法和場(chǎng)景)

    文章介紹了Java中異步編程的各種方法和場(chǎng)景,包括基礎(chǔ)JDK的Thread/Runnable、Future+Callable、CompletableFuture,以及Spring框架中的@Async注解,還討論了異步IO和響應(yīng)式編程的實(shí)現(xiàn)方式,并提供了根據(jù)場(chǎng)景選擇合適異步方法的建議,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • java實(shí)現(xiàn)簡(jiǎn)易飛機(jī)大戰(zhàn)

    java實(shí)現(xiàn)簡(jiǎn)易飛機(jī)大戰(zhàn)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)易飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • 基于Java實(shí)現(xiàn)PDF文本旋轉(zhuǎn)傾斜

    基于Java實(shí)現(xiàn)PDF文本旋轉(zhuǎn)傾斜

    這篇文章主要介紹了基于Java實(shí)現(xiàn)PDF文本旋轉(zhuǎn)傾斜,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • java生成json實(shí)現(xiàn)隱藏掉關(guān)鍵屬性

    java生成json實(shí)現(xiàn)隱藏掉關(guān)鍵屬性

    這篇文章主要介紹了java生成json實(shí)現(xiàn)隱藏掉關(guān)鍵屬性,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringSecurity中的UserDetailsService深度解析

    SpringSecurity中的UserDetailsService深度解析

    文章介紹了Spring Security中的UserDetailsService接口,它用于自定義用戶認(rèn)證邏輯,通過實(shí)現(xiàn)該接口,可以自定義用戶認(rèn)證過程,文章詳細(xì)解釋了UserDetailsService接口的返回值、方法參數(shù)以及可能出現(xiàn)的異常,如UsernameNotFoundException,感興趣的朋友跟隨小編一起看看吧
    2025-11-11
  • Java向上轉(zhuǎn)型和向下轉(zhuǎn)型實(shí)例解析

    Java向上轉(zhuǎn)型和向下轉(zhuǎn)型實(shí)例解析

    這篇文章主要介紹了Java向上轉(zhuǎn)型和向下轉(zhuǎn)型實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • Java Stream 流實(shí)現(xiàn)合并操作示例

    Java Stream 流實(shí)現(xiàn)合并操作示例

    這篇文章主要介紹了Java Stream 流實(shí)現(xiàn)合并操作,結(jié)合實(shí)例形式詳細(xì)分析了Java Stream 流實(shí)現(xiàn)合并操作原理與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2020-05-05

最新評(píng)論

上思县| 涟水县| 绵阳市| 咸阳市| 麦盖提县| 衡南县| 改则县| 保定市| 闽侯县| 昭平县| 隆子县| 汉沽区| 刚察县| 柯坪县| 当阳市| 越西县| 安福县| 清丰县| 杭锦旗| 吴旗县| 冕宁县| 林西县| 芦山县| 翁牛特旗| 贵南县| 开封市| 杭锦旗| 长沙县| 资讯 | 普安县| 德格县| 巴东县| 南陵县| 奎屯市| 如东县| 城市| 莱芜市| 和静县| 漳平市| 易门县| 博乐市|