@@ -21,7 +21,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -29,9 +28,6 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
@@ -49,25 +45,8 @@ public class FileController {
|
||||
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
@Value("${yudao.kkfile:}")
|
||||
private String onlinePreview;
|
||||
|
||||
@GetMapping("/download-url")
|
||||
@Operation(summary = "获取文件下载地址", description = "根据 fileId 返回文件下载 url")
|
||||
public CommonResult<FileRespVO> getDownloadUrl(@RequestParam("fileId") Long fileId) {
|
||||
FileDO fileDO = fileService.getActiveFileById(fileId);
|
||||
if (fileDO == null) {
|
||||
return CommonResult.error(HttpStatus.NOT_FOUND.value(), "文件不存在");
|
||||
}
|
||||
// FileDO 转换为 FileRespVO
|
||||
FileRespVO fileRespVO = BeanUtils.toBean(fileDO, FileRespVO.class);
|
||||
if (StrUtil.isEmpty(onlinePreview) || StrUtil.isEmpty(fileRespVO.getUrl())) {
|
||||
return CommonResult.error(HttpStatus.BAD_REQUEST.value(), "文件 URL 为空");
|
||||
}
|
||||
return success(fileRespVO);
|
||||
}
|
||||
|
||||
@GetMapping("/preview-url")
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取文件预览地址", description = "根据 fileId 返回文件预览 url(kkfile)")
|
||||
public CommonResult<FileRespVO> getPreviewUrl(@RequestParam("fileId") Long fileId) {
|
||||
FileDO fileDO = fileService.getActiveFileById(fileId);
|
||||
@@ -76,11 +55,6 @@ public class FileController {
|
||||
}
|
||||
// FileDO 转换为 FileRespVO
|
||||
FileRespVO fileRespVO = BeanUtils.toBean(fileDO, FileRespVO.class);
|
||||
if (StrUtil.isEmpty(onlinePreview) || StrUtil.isEmpty(fileRespVO.getUrl())) {
|
||||
return CommonResult.error(HttpStatus.BAD_REQUEST.value(), "在线预览地址未配置或文件 URL 为空");
|
||||
}
|
||||
String previewUrl = onlinePreview + URLEncoder.encode(Base64.getEncoder().encodeToString(fileRespVO.getUrl().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
||||
fileRespVO.setPreviewUrl(previewUrl);
|
||||
return success(fileRespVO);
|
||||
}
|
||||
|
||||
@@ -99,11 +73,6 @@ public class FileController {
|
||||
MultipartFile file = uploadReqVO.getFile();
|
||||
byte[] content = IoUtil.readBytes(file.getInputStream());
|
||||
FileRespVO fileWhitReturn = fileService.createFileWhitReturn(content, file.getOriginalFilename(), uploadReqVO.getDirectory(), file.getContentType(), uploadReqVO.getEncrypt());
|
||||
if (StrUtil.isEmpty(onlinePreview) || StrUtil.isEmpty(fileWhitReturn.getUrl())) {
|
||||
fileWhitReturn.setPreviewUrl(fileWhitReturn.getUrl());
|
||||
return success(fileWhitReturn);
|
||||
}
|
||||
fileWhitReturn.setPreviewUrl(onlinePreview + URLEncoder.encode(fileWhitReturn.getUrl(), StandardCharsets.UTF_8));
|
||||
return success(fileWhitReturn);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
|
||||
import cn.iocoder.yudao.module.infra.service.shortlink.ShortLinkService;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Schema(description = "管理后台 - 文件 Response VO,不返回 content 字段,太大")
|
||||
@Data
|
||||
public class FileRespVO {
|
||||
public String getUrl() {
|
||||
if (this.url == null || this.url.isEmpty()) return null;
|
||||
Long fileId = this.id;
|
||||
Long userId = null;
|
||||
try {
|
||||
userId = getLoginUserId();
|
||||
} catch (Exception ignored) {}
|
||||
ShortLinkService shortLinkService = SpringUtils.getBean(ShortLinkService.class);
|
||||
String serverPrefix = SpringUtils.getProperty("yudao.shortlink.server");
|
||||
String key = (userId != null ? userId : "anonymous") + "_" + (fileId != null ? fileId : "nofile");
|
||||
return serverPrefix + shortLinkService.generateShortLink(key, this.url);
|
||||
}
|
||||
|
||||
@Schema(description = "文件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
@@ -28,6 +49,26 @@ public class FileRespVO {
|
||||
@Schema(description = "附件预览地址", example = "https://www.iocoder.cn/yudao.jpg")
|
||||
private String previewUrl;
|
||||
|
||||
public String getPreviewUrl() {
|
||||
// 仅当 url 不为空时生成
|
||||
if (this.url == null || this.url.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 这里的 onlinePreview 通过 SpringUtils 获取
|
||||
String onlinePreview = SpringUtils.getProperty("yudao.kkfile");
|
||||
if (onlinePreview == null || onlinePreview.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String shortUrl = this.getUrl();
|
||||
if (shortUrl == null || shortUrl.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String base64ShortUrl = Base64.getUrlEncoder().encodeToString(shortUrl.getBytes(StandardCharsets.UTF_8));
|
||||
return onlinePreview + base64ShortUrl
|
||||
// + "&fileName=" + URLEncoder.encode(this.getName(), StandardCharsets.UTF_8) + "&watermarkTxt=中国铜业"
|
||||
;
|
||||
}
|
||||
|
||||
@Schema(description = "文件MIME类型", example = "application/octet-stream")
|
||||
private String type;
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.shortlink;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.infra.service.shortlink.ShortLinkService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Tag(name = "管理后台 - 文件存储")
|
||||
@Validated
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/infra/shortlink")
|
||||
public class ShortLinkController {
|
||||
|
||||
@Autowired
|
||||
private ShortLinkService shortLinkService;
|
||||
|
||||
|
||||
/**
|
||||
* 生成短链接
|
||||
*/
|
||||
@PostMapping("/generate")
|
||||
public CommonResult<String> generateShortLink(@RequestParam("key") String key, @RequestParam("url") String url) {
|
||||
String shortLink = "/infra/shortlink/" + shortLinkService.generateShortLink(key, url);
|
||||
return CommonResult.success(shortLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析短链接并重定向
|
||||
*/
|
||||
@GetMapping("/{shortKey}")
|
||||
@PermitAll
|
||||
@TenantIgnore
|
||||
public ResponseEntity<byte[]> parseShortLink(@PathVariable("shortKey") String shortKey) {
|
||||
String realUrl = shortLinkService.parseShortLink(shortKey);
|
||||
if (realUrl != null) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
try {
|
||||
ResponseEntity<byte[]> entity = restTemplate.exchange(realUrl, HttpMethod.GET, HttpEntity.EMPTY, byte[].class);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
// 只透传部分常用header,可根据需要扩展
|
||||
if (entity.getHeaders().getContentType() != null) {
|
||||
headers.setContentType(entity.getHeaders().getContentType());
|
||||
}
|
||||
if (entity.getHeaders().getContentLength() > 0) {
|
||||
headers.setContentLength(entity.getHeaders().getContentLength());
|
||||
}
|
||||
// 透传 disposition/encoding 等
|
||||
String disposition = entity.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
|
||||
if (disposition != null) {
|
||||
headers.set(HttpHeaders.CONTENT_DISPOSITION, disposition);
|
||||
}
|
||||
String encoding = entity.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING);
|
||||
if (encoding != null) {
|
||||
headers.set(HttpHeaders.CONTENT_ENCODING, encoding);
|
||||
}
|
||||
return new ResponseEntity<>(entity.getBody(), headers, entity.getStatusCode());
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(("目标链接内容获取异常: " + e.getMessage()).getBytes());
|
||||
}
|
||||
} else {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body("当前文件请求链接已失效,请从系统重新进行请求操作".getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,9 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
|
||||
@Override
|
||||
public List<TableInfo> getTableList(Long dataSourceConfigId, String nameLike, String commentLike) {
|
||||
List<TableInfo> tables = getTableList0(dataSourceConfigId, null);
|
||||
return tables.stream().filter(tableInfo -> (StrUtil.isEmpty(nameLike) || tableInfo.getName().contains(nameLike))
|
||||
&& (StrUtil.isEmpty(commentLike) || tableInfo.getComment().contains(commentLike)))
|
||||
return tables.stream().filter(tableInfo ->
|
||||
(StrUtil.isEmpty(nameLike) || tableInfo.getName().toLowerCase().contains(nameLike.toLowerCase()))
|
||||
&& (StrUtil.isEmpty(commentLike) || tableInfo.getComment().contains(commentLike)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.iocoder.yudao.module.infra.service.shortlink;
|
||||
|
||||
public interface ShortLinkService {
|
||||
/**
|
||||
* 生成短链接
|
||||
*/
|
||||
String generateShortLink(String key, String url);
|
||||
|
||||
/**
|
||||
* 解析短链接
|
||||
*/
|
||||
String parseShortLink(String shortKey);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.iocoder.yudao.module.infra.service.shortlink;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
|
||||
public class ShortLinkServiceImpl implements ShortLinkService {
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
@Value("${yudao.shortlink.expire-seconds:30}")
|
||||
private long expireSeconds;
|
||||
|
||||
public ShortLinkServiceImpl(StringRedisTemplate redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成短链接
|
||||
*/
|
||||
@Override
|
||||
public String generateShortLink(String key, String url) {
|
||||
// 使用url生成hash作为短链key
|
||||
String hashKey = hashUrl(url);
|
||||
// 存入Redis并设置过期时间
|
||||
redisTemplate.opsForValue().set(hashKey, url, expireSeconds, TimeUnit.SECONDS);
|
||||
return hashKey;
|
||||
}
|
||||
|
||||
private String hashUrl(String url) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(url.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
// 只取前8字节,够短链用
|
||||
for (int i = 0; i < 8 && i < hash.length; i++) {
|
||||
sb.append(String.format("%02x", hash[i]));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Hash算法异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析短链接
|
||||
*/
|
||||
@Override
|
||||
public String parseShortLink(String shortKey) {
|
||||
// 直接从Redis获取
|
||||
return redisTemplate.opsForValue().get(shortKey);
|
||||
}
|
||||
}
|
||||
@@ -114,5 +114,8 @@ spring:
|
||||
context-path: /admin # 配置 Spring
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
yudao:
|
||||
shortlink:
|
||||
server: "http://172.16.46.63:48080/admin-api/infra/shortlink/"
|
||||
|
||||
|
||||
|
||||
@@ -139,4 +139,6 @@ yudao:
|
||||
access-log: # 访问日志的配置项
|
||||
enable: true
|
||||
# 固定验证码
|
||||
verify-code: 666666
|
||||
verify-code: 666666
|
||||
shortlink:
|
||||
server: "http://localhost:48080/admin-api/infra/shortlink/"
|
||||
Reference in New Issue
Block a user