新增 页码定位 美化前端 其他功能调整等

This commit is contained in:
高雄
2026-01-19 17:46:32 +08:00
parent 904af89190
commit 7dc0469b30
48 changed files with 10922 additions and 1241 deletions

View File

@@ -1,6 +1,7 @@
package cn.keking.web.controller;
import cn.keking.config.ConfigConstants;
import cn.keking.model.FileType;
import cn.keking.model.ReturnResponse;
import cn.keking.utils.CaptchaUtil;
import cn.keking.utils.DateUtils;
@@ -27,13 +28,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import static cn.keking.utils.CaptchaUtil.CAPTCHA_CODE;
import static cn.keking.utils.CaptchaUtil.CAPTCHA_GENERATE_TIME;
@@ -54,23 +52,72 @@ public class FileController {
public static final String BASE64_DECODE_ERROR_MSG = "Base64解码失败请检查你的 %s 是否采用 Base64 + urlEncode 双重编码了!";
@PostMapping("/fileUpload")
public ReturnResponse<Object> fileUpload(@RequestParam("file") MultipartFile file) {
ReturnResponse<Object> checkResult = this.fileUploadCheck(file);
public ReturnResponse<Object> fileUpload(@RequestParam("file") MultipartFile file,
@RequestParam(value = "path", defaultValue = "") String path) {
ReturnResponse<Object> checkResult = this.fileUploadCheck(file, path);
if (checkResult.isFailure()) {
return checkResult;
}
File outFile = new File(fileDir + demoPath);
if (!outFile.exists() && !outFile.mkdirs()) {
logger.error("创建文件夹【{}】失败,请检查目录权限!", fileDir + demoPath);
String uploadPath = fileDir + demoPath;
if (!ObjectUtils.isEmpty(path)) {
uploadPath += path + File.separator;
}
File outFile = new File(uploadPath);
if (!outFile.exists() && !outFile.mkdirs()) {
logger.error("创建文件夹【{}】失败,请检查目录权限!", uploadPath);
return ReturnResponse.failure("创建文件夹失败,请检查目录权限!");
}
String fileName = checkResult.getContent().toString();
logger.info("上传文件:{}{}{}", fileDir, demoPath, fileName);
try (InputStream in = file.getInputStream(); OutputStream out = Files.newOutputStream(Paths.get(fileDir + demoPath + fileName))) {
logger.info("上传文件:{}{}", uploadPath, fileName);
try (InputStream in = file.getInputStream();
OutputStream out = Files.newOutputStream(Paths.get(uploadPath + fileName))) {
StreamUtils.copy(in, out);
return ReturnResponse.success(null);
} catch (IOException e) {
logger.error("文件上传失败", e);
return ReturnResponse.failure();
return ReturnResponse.failure("文件上传失败");
}
}
@PostMapping("/createFolder")
public ReturnResponse<Object> createFolder(@RequestParam(value = "path", defaultValue = "") String path,
@RequestParam("folderName") String folderName) {
if (ConfigConstants.getFileUploadDisable()) {
return ReturnResponse.failure("文件上传接口已禁用");
}
try {
// 验证文件夹名称
if (ObjectUtils.isEmpty(folderName)) {
return ReturnResponse.failure("文件夹名称不能为空");
}
if (KkFileUtils.isIllegalFileName(folderName)) {
return ReturnResponse.failure("非法文件夹名称");
}
String basePath = fileDir + demoPath;
if (!ObjectUtils.isEmpty(path)) {
basePath += path + File.separator;
}
File newFolder = new File(basePath + folderName);
if (newFolder.exists()) {
return ReturnResponse.failure("文件夹已存在");
}
if (newFolder.mkdirs()) {
logger.info("创建文件夹:{}", newFolder.getAbsolutePath());
return ReturnResponse.success();
} else {
logger.error("创建文件夹失败:{}", newFolder.getAbsolutePath());
return ReturnResponse.failure("创建文件夹失败,请检查目录权限");
}
} catch (Exception e) {
logger.error("创建文件夹异常", e);
return ReturnResponse.failure("创建文件夹失败:" + e.getMessage());
}
}
@@ -81,15 +128,55 @@ public class FileController {
return checkResult;
}
fileName = checkResult.getContent().toString();
File file = new File(fileDir + demoPath + fileName);
logger.info("删除文件:{}", file.getAbsolutePath());
if (file.exists() && !file.delete()) {
String msg = String.format("删除文件【%s】失败请检查目录权限", file.getPath());
logger.error(msg);
return ReturnResponse.failure(msg);
// 构建完整路径
String fullPath = fileDir + demoPath + fileName;
File file = new File(fullPath);
logger.info("删除文件/文件夹:{}", file.getAbsolutePath());
if (file.exists()) {
if (file.isDirectory()) {
// 删除文件夹及其内容
if (deleteDirectory(file)) {
WebUtils.removeSessionAttr(request, CAPTCHA_CODE);
return ReturnResponse.success();
} else {
String msg = String.format("删除文件夹【%s】失败请检查目录权限", file.getPath());
logger.error(msg);
return ReturnResponse.failure(msg);
}
} else {
// 删除文件
if (file.delete()) {
WebUtils.removeSessionAttr(request, CAPTCHA_CODE);
return ReturnResponse.success();
} else {
String msg = String.format("删除文件【%s】失败请检查目录权限", file.getPath());
logger.error(msg);
return ReturnResponse.failure(msg);
}
}
} else {
return ReturnResponse.failure("文件或文件夹不存在");
}
WebUtils.removeSessionAttr(request, CAPTCHA_CODE); //删除缓存验证码
return ReturnResponse.success();
}
/**
* 递归删除目录
*/
private boolean deleteDirectory(File dir) {
if (dir.isDirectory()) {
File[] children = dir.listFiles();
if (children != null) {
for (File child : children) {
boolean success = deleteDirectory(child);
if (!success) {
return false;
}
}
}
}
return dir.delete();
}
/**
@@ -124,32 +211,156 @@ public class FileController {
outputStream.close();
}
@GetMapping("/listFiles")
public List<Map<String, String>> getFiles() {
List<Map<String, String>> list = new ArrayList<>();
File file = new File(fileDir + demoPath);
if (file.exists()) {
File[] files = Objects.requireNonNull(file.listFiles());
Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()));
Arrays.stream(files).forEach(file1 -> {
Map<String, String> fileName = new HashMap<>();
fileName.put("fileName", demoDir + "/" + file1.getName());
list.add(fileName);
});
@PostMapping("/listFiles")
public Map<String, Object> getFiles(@RequestParam(value = "path", defaultValue = "") String path,
@RequestParam(value = "searchText", defaultValue = "") String searchText,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String sort,
@RequestParam(required = false) String order) {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> fileList = new ArrayList<>();
try {
// 构建完整路径
String basePath = fileDir + demoPath;
if (!ObjectUtils.isEmpty(path)) {
basePath += path + File.separator;
}
File currentDir = new File(basePath);
if (currentDir.exists() && currentDir.isDirectory()) {
File[] files = currentDir.listFiles();
if (files != null) {
// 转换为List
List<File> fileObjects = new ArrayList<>(Arrays.asList(files));
// 如果搜索文本不为空,进行过滤
if (!ObjectUtils.isEmpty(searchText)) {
String searchLower = searchText.toLowerCase();
fileObjects.removeIf(f -> !f.getName().toLowerCase().contains(searchLower));
}
// 排序
Comparator<File> comparator = getFileComparator(sort, order);
if (comparator != null) {
fileObjects.sort(comparator);
}
int total = fileObjects.size();
int start = page * size;
int end = Math.min(start + size, total);
if (start < total) {
for (int i = start; i < end; i++) {
File f = fileObjects.get(i);
Map<String, Object> fileInfo = new HashMap<>();
fileInfo.put("name", f.getName());
fileInfo.put("isDirectory", f.isDirectory());
fileInfo.put("lastModified", f.lastModified());
fileInfo.put("size", f.length());
// 构建路径信息
String relativePath = demoDir + "/" + (ObjectUtils.isEmpty(path) ? "" : path + "/") + f.getName();
fileInfo.put("relativePath", relativePath);
// 如果是目录,保存完整的相对路径用于导航
if (f.isDirectory()) {
String fullPath = ObjectUtils.isEmpty(path) ? f.getName() : path + "/" + f.getName();
fileInfo.put("fullPath", fullPath);
}
// 获取文件属性
try {
Path filePath = f.toPath();
BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
fileInfo.put("creationTime", attrs.creationTime().toMillis());
} catch (IOException e) {
logger.warn("获取文件属性失败: {}", f.getName(), e);
}
fileList.add(fileInfo);
}
}
result.put("total", total);
result.put("data", fileList);
}
} else {
result.put("total", 0);
result.put("data", Collections.emptyList());
}
} catch (Exception e) {
logger.error("获取文件列表失败", e);
result.put("total", 0);
result.put("data", Collections.emptyList());
}
return list;
return result;
}
/**
* 获取文件比较器
*/
private Comparator<File> getFileComparator(String sort, String order) {
if (ObjectUtils.isEmpty(sort)) {
// 默认按文件夹优先,然后按名称排序
return (f1, f2) -> {
if (f1.isDirectory() && !f2.isDirectory()) {
return -1;
} else if (!f1.isDirectory() && f2.isDirectory()) {
return 1;
} else {
return f1.getName().compareToIgnoreCase(f2.getName());
}
};
}
boolean isDesc = "desc".equalsIgnoreCase(order);
return switch (sort) {
case "name" -> (f1, f2) -> {
int compare = f1.getName().compareToIgnoreCase(f2.getName());
return isDesc ? -compare : compare;
};
case "lastModified" -> (f1, f2) -> {
long compare = Long.compare(f1.lastModified(), f2.lastModified());
return isDesc ? (int) -compare : (int) compare;
};
case "size" -> (f1, f2) -> {
if (f1.isDirectory() && f2.isDirectory()) {
return 0;
} else if (f1.isDirectory()) {
return isDesc ? 1 : -1;
} else if (f2.isDirectory()) {
return isDesc ? -1 : 1;
} else {
long compare = Long.compare(f1.length(), f2.length());
return isDesc ? (int) -compare : (int) compare;
}
};
case "isDirectory" -> (f1, f2) -> {
if (f1.isDirectory() && !f2.isDirectory()) {
return isDesc ? 1 : -1;
} else if (!f1.isDirectory() && f2.isDirectory()) {
return isDesc ? -1 : 1;
} else {
return f1.getName().compareToIgnoreCase(f2.getName());
}
};
default -> null;
};
}
/**
* 上传文件前校验
*
* @param file 文件
* @return 校验结果
*/
private ReturnResponse<Object> fileUploadCheck(MultipartFile file) {
private ReturnResponse<Object> fileUploadCheck(MultipartFile file, String path) {
if (ConfigConstants.getFileUploadDisable()) {
return ReturnResponse.failure("文件传接口已禁用");
return ReturnResponse.failure("文件传接口已禁用");
}
String fileName = WebUtils.getFileNameFromMultipartFile(file);
if (fileName.lastIndexOf(".") == -1) {
return ReturnResponse.failure("不允许上传的类型");
@@ -160,47 +371,56 @@ public class FileController {
if (KkFileUtils.isIllegalFileName(fileName)) {
return ReturnResponse.failure("不允许上传的文件名: " + fileName);
}
FileType type = FileType.typeFromFileName(fileName);
if (Objects.equals(type, FileType.OTHER)) {
return ReturnResponse.failure("该文件格式还不支持预览,请联系管理员,添加该格式: " + fileName);
}
// 判断是否存在同名文件
if (existsFile(fileName)) {
if (existsFile(fileName, path)) {
return ReturnResponse.failure("存在同名文件,请先删除原有文件再次上传");
}
return ReturnResponse.success(fileName);
}
/**
* 删除文件前校验
*
* @param fileName 文件名
* @return 校验结果
*/
private ReturnResponse<Object> deleteFileCheck(HttpServletRequest request, String fileName, String password) {
if (ObjectUtils.isEmpty(fileName)) {
return ReturnResponse.failure("文件名为空,删除失败!");
}
try {
fileName = WebUtils.decodeUrl(fileName,"base64");
fileName = WebUtils.decodeUrl(fileName, "base64");
} catch (Exception ex) {
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, fileName);
return ReturnResponse.failure(errorMsg + "删除失败!");
}
assert fileName != null;
if (ObjectUtils.isEmpty(fileName)) {
return ReturnResponse.failure("文件名为空,删除失败!");
}
if (fileName.contains("/")) {
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
}
if (KkFileUtils.isIllegalFileName(fileName)) {
return ReturnResponse.failure("非法文件名,删除失败!");
}
if (ObjectUtils.isEmpty(password)) {
return ReturnResponse.failure("密码 or 验证码为空,删除失败!");
}
String expectedPassword = ConfigConstants.getDeleteCaptcha() ? WebUtils.getSessionAttr(request, CAPTCHA_CODE) : ConfigConstants.getPassword();
String expectedPassword = ConfigConstants.getDeleteCaptcha() ?
WebUtils.getSessionAttr(request, CAPTCHA_CODE) :
ConfigConstants.getPassword();
if (!password.equalsIgnoreCase(expectedPassword)) {
logger.error("删除文件【{}】失败,密码错误!", fileName);
return ReturnResponse.failure("删除文件失败,密码错误!");
}
return ReturnResponse.success(fileName);
}
@@ -220,8 +440,12 @@ public class FileController {
return RarUtils.getTree(fileUrl);
}
private boolean existsFile(String fileName) {
File file = new File(fileDir + demoPath + fileName);
private boolean existsFile(String fileName, String path) {
String fullPath = fileDir + demoPath;
if (!ObjectUtils.isEmpty(path)) {
fullPath += path + File.separator;
}
File file = new File(fullPath + fileName);
return file.exists();
}
}
}

View File

@@ -77,6 +77,9 @@ public class OnlinePreviewController {
public String onlinePreview(@RequestParam String url,
@RequestParam(required = false) String key,
@RequestParam(required = false) String encryption,
@RequestParam(defaultValue = "false") String highlightall,
@RequestParam(defaultValue = "0") String page,
@RequestParam(defaultValue = "false") String kkagent,
Model model,
HttpServletRequest req) {
// 验证访问权限
@@ -90,7 +93,12 @@ public class OnlinePreviewController {
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "url");
return otherFilePreview.notSupportedFile(model, errorMsg);
}
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req); //这里不在进行URL 处理了
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req);
highlightall= KkFileUtils.htmlEscape(highlightall);
model.addAttribute("highlightall", highlightall);
model.addAttribute("page", page);
model.addAttribute("kkagent", kkagent);
model.addAttribute("file", fileAttribute);
FilePreview filePreview = previewFactory.get(fileAttribute);
logger.info("预览文件url{}previewType{}", fileUrl, fileAttribute.getType());
@@ -151,8 +159,8 @@ public class OnlinePreviewController {
public void getCorsFile(@RequestParam String urlPath,
@RequestParam(required = false) String key,
HttpServletResponse response,
@RequestParam(required = false) String encryption,
FileAttribute fileAttribute) throws Exception {
HttpServletRequest req,
@RequestParam(required = false) String encryption) throws Exception {
// 1. 验证接口是否开启
if (!ConfigConstants.getGetCorsFile()) {
@@ -177,6 +185,7 @@ public class OnlinePreviewController {
logger.info("读取跨域文件异常可能存在非法访问urlPath{}", urlPath);
return;
}
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(urlPath, req);
InputStream inputStream = null;
logger.info("读取跨域pdf文件url{}", urlPath);
if (!isFtpUrl(url)) {
@@ -188,7 +197,6 @@ public class OnlinePreviewController {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(factory);
RequestCallback requestCallback = request -> {
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
WebUtils.applyBasicAuthHeaders(request.getHeaders(), fileAttribute);

View File

@@ -47,6 +47,14 @@ public class AttributeSetFilter implements Filter {
request.setAttribute("homePagination", ConfigConstants.getHomePagination());
request.setAttribute("homePageSize", ConfigConstants.getHomePageSize());
request.setAttribute("homeSearch", ConfigConstants.getHomeSearch());
request.setAttribute("isshowaeskey", ConfigConstants.getisShowaesKey());
request.setAttribute("isjavascript", ConfigConstants.getisJavaScript());
request.setAttribute("xlsxallowEdit", ConfigConstants.getxlsxAllowEdit());
request.setAttribute("xlsxshowtoolbar", ConfigConstants.getxlsxShowtoolbar());
request.setAttribute("aeskey", ConfigConstants.getaesKey());
request.setAttribute("isshowkey", ConfigConstants.getisShowKey());
request.setAttribute("kkkey", ConfigConstants.getKey());
request.setAttribute("scriptjs", ConfigConstants.getscriptJs());
}
/**