From 0f167a304dd1b831bfb8f92cfefd0d909654dba2 Mon Sep 17 00:00:00 2001 From: zhaojun <873019219@qq.com> Date: Thu, 28 Jul 2022 21:24:38 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E7=8B=AC=E7=AB=8B=E7=9B=B4?= =?UTF-8?q?=E9=93=BE=E5=92=8C=E7=9F=AD=E9=93=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zfile/admin/model/entity/DownloadLog.java | 18 ++++ .../zfile/admin/service/ShortLinkService.java | 102 ++++++++++++++++++ .../home/controller/ShortLinkController.java | 20 ++-- .../zfile/home/filter/DownloadLinkFilter.java | 82 ++------------ 4 files changed, 138 insertions(+), 84 deletions(-) diff --git a/src/main/java/im/zhaojun/zfile/admin/model/entity/DownloadLog.java b/src/main/java/im/zhaojun/zfile/admin/model/entity/DownloadLog.java index 62c5f82..69da0d6 100644 --- a/src/main/java/im/zhaojun/zfile/admin/model/entity/DownloadLog.java +++ b/src/main/java/im/zhaojun/zfile/admin/model/entity/DownloadLog.java @@ -1,13 +1,18 @@ package im.zhaojun.zfile.admin.model.entity; +import cn.hutool.extra.servlet.ServletUtil; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import im.zhaojun.zfile.common.util.RequestHolder; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpHeaders; +import javax.servlet.http.HttpServletRequest; import java.io.Serializable; import java.util.Date; @@ -19,6 +24,7 @@ import java.util.Date; @Data @ApiModel(value="文件下载日志") @TableName(value = "`download_log`") +@NoArgsConstructor public class DownloadLog implements Serializable { private static final long serialVersionUID = 1L; @@ -62,4 +68,16 @@ public class DownloadLog implements Serializable { @ApiModelProperty(value="访问 referer") private String referer; + + public DownloadLog(String path, String storageKey, String shortKey) { + this.path = path; + this.storageKey = storageKey; + this.shortKey = shortKey; + this.createTime = new Date(); + HttpServletRequest request = RequestHolder.getRequest(); + this.ip = ServletUtil.getClientIP(request); + this.referer = request.getHeader(HttpHeaders.REFERER); + this.userAgent = request.getHeader(HttpHeaders.USER_AGENT); + } + } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/admin/service/ShortLinkService.java b/src/main/java/im/zhaojun/zfile/admin/service/ShortLinkService.java index 64c75a3..12f2373 100644 --- a/src/main/java/im/zhaojun/zfile/admin/service/ShortLinkService.java +++ b/src/main/java/im/zhaojun/zfile/admin/service/ShortLinkService.java @@ -1,13 +1,32 @@ package im.zhaojun.zfile.admin.service; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import im.zhaojun.zfile.admin.mapper.ShortLinkMapper; +import im.zhaojun.zfile.admin.model.entity.DownloadLog; import im.zhaojun.zfile.admin.model.entity.ShortLink; +import im.zhaojun.zfile.common.context.StorageSourceContext; +import im.zhaojun.zfile.common.exception.InvalidStorageSourceException; +import im.zhaojun.zfile.common.exception.file.operator.DownloadFileException; +import im.zhaojun.zfile.common.util.HttpUtil; +import im.zhaojun.zfile.common.util.RequestHolder; +import im.zhaojun.zfile.home.model.dto.SystemConfigDTO; +import im.zhaojun.zfile.home.service.base.AbstractBaseFileService; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.util.EncodingUtils; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStream; import java.util.Date; /** @@ -16,6 +35,7 @@ import java.util.Date; * @author zhaojun */ @Service +@Slf4j public class ShortLinkService extends ServiceImpl implements IService { @Resource @@ -24,6 +44,15 @@ public class ShortLinkService extends ServiceImpl im @Resource private StorageSourceService storageSourceService; + @Resource + private StorageSourceContext storageSourceContext; + + @Resource + private SystemConfigService systemConfigService; + + @Resource + private DownloadLogService downloadLogService; + /** * 根据短链接 key 查询短链接 * @@ -113,4 +142,77 @@ public class ShortLinkService extends ServiceImpl im return shortLink; } + + /** + * 处理指定存储源的下载请求 + * + * @param storageKey + * 存储源 key + * + * @param filePath + * 文件路径 + * + * @param shortKey + * 短链接 key + * + * @throws IOException 可能抛出的 IO 异常 + */ + public void handlerDownload(String storageKey, String filePath, String shortKey) throws IOException { + HttpServletResponse response = RequestHolder.getResponse(); + + // 获取存储源 Service + AbstractBaseFileService fileService; + try { + fileService = storageSourceContext.getByKey(storageKey); + } catch (InvalidStorageSourceException e) { + log.error("无效的存储源,存储源 key {}, 文件路径 {}", storageKey, filePath); + response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"); + response.getWriter().write("无效的或初始化失败的存储源, 请联系管理员!"); + return; + } + + // 获取文件下载链接 + String downloadUrl; + try { + downloadUrl = fileService.getDownloadUrl(filePath); + } catch (DownloadFileException e) { + log.error("获取文件下载链接异常 {}. 存储源 ID: {}, 文件路径: {}", e.getMessage(), e.getStorageId(), e.getPathAndName()); + response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"); + response.getWriter().write("获取下载链接异常,请联系管理员!"); + return; + } + + // 判断下载链接是否为空 + if (StrUtil.isEmpty(downloadUrl)) { + log.error("获取到文件下载链接为空,存储源 key {}, 文件路径 {}", storageKey, filePath); + response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"); + response.getWriter().write("获取下载链接异常,请联系管理员![2]"); + } + + // 记录下载日志. + SystemConfigDTO systemConfig = systemConfigService.getSystemConfig(); + Boolean recordDownloadLog = systemConfig.getRecordDownloadLog(); + if (BooleanUtil.isTrue(recordDownloadLog)) { + DownloadLog downloadLog = new DownloadLog(filePath, storageKey, shortKey); + downloadLogService.save(downloadLog); + } + + // 判断下载链接是否为 m3u8 格式, 如果是则返回 m3u8 内容. + if (StrUtil.equalsIgnoreCase(FileUtil.extName(filePath), "m3u8")) { + String textContent = HttpUtil.getTextContent(downloadUrl); + response.setContentType("application/vnd.apple.mpegurl;charset=utf-8"); + OutputStream outputStream = response.getOutputStream(); + byte[] textContentBytes = EncodingUtils.getBytes(textContent, CharsetUtil.CHARSET_UTF_8.displayName()); + IoUtil.write(outputStream, true, textContentBytes); + } + + // 禁止直链被浏览器 302 缓存. + response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate, private"); + response.setHeader(HttpHeaders.PRAGMA, "no-cache"); + response.setHeader(HttpHeaders.EXPIRES, "0"); + + // 重定向到下载链接. + response.sendRedirect(downloadUrl); + } + } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/home/controller/ShortLinkController.java b/src/main/java/im/zhaojun/zfile/home/controller/ShortLinkController.java index b0b32b7..e2f3788 100644 --- a/src/main/java/im/zhaojun/zfile/home/controller/ShortLinkController.java +++ b/src/main/java/im/zhaojun/zfile/home/controller/ShortLinkController.java @@ -6,6 +6,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiSort; import com.github.xiaoymin.knife4j.annotations.DynamicParameter; import com.github.xiaoymin.knife4j.annotations.DynamicResponseParameters; import im.zhaojun.zfile.admin.model.entity.ShortLink; +import im.zhaojun.zfile.admin.model.entity.StorageSource; import im.zhaojun.zfile.admin.service.ShortLinkService; import im.zhaojun.zfile.admin.service.StorageSourceService; import im.zhaojun.zfile.admin.service.SystemConfigService; @@ -25,6 +26,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import javax.annotation.Resource; +import java.io.IOException; /** * 短链接口 @@ -67,7 +69,7 @@ public class ShortLinkController { Boolean showShortLink = systemConfig.getShowShortLink(); Boolean showPathLink = systemConfig.getShowPathLink(); if ( BooleanUtil.isFalse(showShortLink) && BooleanUtil.isFalse(showPathLink)) { - throw new IllegalDownloadLinkException("当前系统不允许使用短链和短链."); + throw new IllegalDownloadLinkException("当前系统不允许使用短链."); } String domain = systemConfig.getDomain(); @@ -91,28 +93,28 @@ public class ShortLinkController { @ApiOperationSupport(order = 2) @ApiOperation(value = "跳转短链", notes = "根据短链 key 跳转(302 重定向)到对应的直链.") @ApiImplicitParam(paramType = "path", name = "key", value = "短链 key", required = true) - public String parseShortKey(@PathVariable String key) { + public String parseShortKey(@PathVariable String key) throws IOException { ShortLink shortLink = shortLinkService.findByKey(key); if (shortLink == null) { throw new RuntimeException("此直链不存在或已失效."); } + // 获取站点域名 SystemConfigDTO systemConfig = systemConfigService.getSystemConfig(); - String domain = systemConfig.getDomain(); - // 是否允许生成短链. + // 判断是否允许生成短链. Boolean showShortLink = systemConfig.getShowShortLink(); if ( BooleanUtil.isFalse(showShortLink)) { throw new IllegalDownloadLinkException("当前系统不允许使用短链."); } - String directLinkPrefix = systemConfig.getDirectLinkPrefix(); Integer storageId = shortLink.getStorageId(); - String storageKey = storageSourceService.findKeyById(storageId); - String filePath = StringUtils.encodeAllIgnoreSlashes(shortLink.getUrl()); + StorageSource storageSource = storageSourceService.findById(storageId); + String storageKey = storageSource.getKey(); + String filePath = shortLink.getUrl(); - String url = StringUtils.concat(domain, directLinkPrefix, storageKey, filePath); - return "redirect:" + url; + shortLinkService.handlerDownload(storageKey, filePath, shortLink.getShortKey()); + return null; } } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/home/filter/DownloadLinkFilter.java b/src/main/java/im/zhaojun/zfile/home/filter/DownloadLinkFilter.java index 4056d74..4d49b91 100644 --- a/src/main/java/im/zhaojun/zfile/home/filter/DownloadLinkFilter.java +++ b/src/main/java/im/zhaojun/zfile/home/filter/DownloadLinkFilter.java @@ -1,14 +1,9 @@ package im.zhaojun.zfile.home.filter; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.extra.spring.SpringUtil; -import im.zhaojun.zfile.admin.model.entity.DownloadLog; import im.zhaojun.zfile.admin.model.entity.ShortLink; import im.zhaojun.zfile.admin.model.entity.StorageSource; import im.zhaojun.zfile.admin.service.DownloadLogService; @@ -18,14 +13,9 @@ import im.zhaojun.zfile.admin.service.StorageSourceService; import im.zhaojun.zfile.admin.service.SystemConfigService; import im.zhaojun.zfile.common.constant.ZFileConstant; import im.zhaojun.zfile.common.context.StorageSourceContext; -import im.zhaojun.zfile.common.exception.InvalidStorageSourceException; -import im.zhaojun.zfile.common.exception.file.operator.DownloadFileException; -import im.zhaojun.zfile.common.util.HttpUtil; import im.zhaojun.zfile.common.util.StringUtils; import im.zhaojun.zfile.home.model.dto.SystemConfigDTO; -import im.zhaojun.zfile.home.service.base.AbstractBaseFileService; import lombok.extern.slf4j.Slf4j; -import org.apache.http.util.EncodingUtils; import org.springframework.http.HttpHeaders; import javax.servlet.Filter; @@ -37,8 +27,6 @@ import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.OutputStream; -import java.util.Date; import java.util.List; @@ -109,7 +97,6 @@ public class DownloadLinkFilter implements Filter { // 获取系统配置的直链前缀 SystemConfigDTO systemConfig = systemConfigService.getSystemConfig(); String directLinkPrefix = systemConfig.getDirectLinkPrefix(); - if (StrUtil.equalsIgnoreCase(currentRequestPrefix, directLinkPrefix)) { if (BooleanUtil.isFalse(systemConfig.getShowPathLink())) { @@ -129,27 +116,7 @@ public class DownloadLinkFilter implements Filter { httpServletResponse.sendRedirect(forbiddenUrl); return; } - - Boolean recordDownloadLog = systemConfig.getRecordDownloadLog(); - if (BooleanUtil.isTrue(recordDownloadLog)) { - DownloadLog downloadLog = new DownloadLog(); - downloadLog.setPath(decodeFilePath); - downloadLog.setStorageKey(currentStorageKey); - downloadLog.setCreateTime(new Date()); - downloadLog.setIp(ServletUtil.getClientIP(httpServletRequest)); - downloadLog.setReferer(httpServletRequest.getHeader(HttpHeaders.REFERER)); - downloadLog.setUserAgent(httpServletRequest.getHeader(HttpHeaders.USER_AGENT)); - - ShortLink shortLink = shortLinkService.findByStorageIdAndUrl(storageId, decodeFilePath); - // 如果没有短链,则生成短链 - if (shortLink == null) { - shortLink = shortLinkService.generatorShortLink(storageId, decodeFilePath); - } - downloadLog.setShortKey(shortLink.getShortKey()); - - downloadLogService.save(downloadLog); - } - handleDownloadLink(httpServletResponse, currentStorageKey, decodeFilePath); + handleDownloadLink(httpServletResponse, storageId, currentStorageKey, decodeFilePath); return; } } @@ -170,7 +137,7 @@ public class DownloadLinkFilter implements Filter { * @param filePath * 文件路径 */ - private void handleDownloadLink(HttpServletResponse response, String storageKey, String filePath) throws IOException { + private void handleDownloadLink(HttpServletResponse response, Integer storageId, String storageKey, String filePath) throws IOException { StorageSource storageSource = storageSourceService.findByStorageKey(storageKey); Boolean enable = storageSource.getEnable(); if (!enable) { @@ -184,48 +151,13 @@ public class DownloadLinkFilter implements Filter { filePath = "/" + filePath; } - AbstractBaseFileService fileService; - try { - fileService = storageSourceContext.getByKey(storageKey); - } catch (InvalidStorageSourceException e) { - log.error("无效的存储源,存储源 key {}, 文件路径 {}", storageKey, filePath); - response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"); - response.getWriter().write("无效的或初始化失败的存储源, 请联系管理员!"); - return; + ShortLink shortLink = shortLinkService.findByStorageIdAndUrl(storageId, filePath); + // 如果没有短链,则生成短链 + if (shortLink == null) { + shortLink = shortLinkService.generatorShortLink(storageId, filePath); } - String downloadUrl; - try { - downloadUrl = fileService.getDownloadUrl(filePath); - } catch (DownloadFileException e) { - log.error("获取文件下载链接异常 {}. 存储源 ID: {}, 文件路径: {}", e.getMessage(), e.getStorageId(), e.getPathAndName()); - response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"); - response.getWriter().write("获取下载链接异常,请联系管理员!"); - return; - } - - if (StrUtil.isEmpty(downloadUrl)) { - log.error("获取到文件下载链接为空,存储源 key {}, 文件路径 {}", storageKey, filePath); - response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"); - response.getWriter().write("获取下载链接异常,请联系管理员![2]"); - return; - } - - if (StrUtil.equalsIgnoreCase(FileUtil.extName(filePath), "m3u8")) { - String textContent = HttpUtil.getTextContent(downloadUrl); - response.setContentType("application/vnd.apple.mpegurl;charset=utf-8"); - OutputStream outputStream = response.getOutputStream(); - byte[] textContentBytes = EncodingUtils.getBytes(textContent, CharsetUtil.CHARSET_UTF_8.displayName()); - IoUtil.write(outputStream, true, textContentBytes); - return; - } - - // 禁止直链被浏览器 302 缓存. - response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate, private"); - response.setHeader(HttpHeaders.PRAGMA, "no-cache"); - response.setHeader(HttpHeaders.EXPIRES, "0"); - - response.sendRedirect(downloadUrl); + shortLinkService.handlerDownload(storageKey, filePath, shortLink.getShortKey()); } } \ No newline at end of file