mirror of
https://github.com/zfile-dev/zfile.git
synced 2025-04-19 05:34:52 +00:00
✨ 独立直链和短链功能
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ShortLinkMapper, ShortLink> implements IService<ShortLink> {
|
||||
|
||||
@Resource
|
||||
@@ -24,6 +44,15 @@ public class ShortLinkService extends ServiceImpl<ShortLinkMapper, ShortLink> 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<ShortLinkMapper, ShortLink> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user