Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion server/src/main/config/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ watermark.height = ${WATERMARK_HEIGHT:80}
#水印倾斜度数,要求设置在大于等于0,小于90
watermark.angle = ${WATERMARK_ANGLE:10}


#首页功能设置
#是否禁用首页文件上传
file.upload.disable = ${KK_FILE_UPLOAD_DISABLE:true}
# 备案信息,默认为空
beian = ${KK_BEIAN:default}
#禁止上传类型
Expand Down
2 changes: 1 addition & 1 deletion server/src/main/java/cn/keking/config/ConfigConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ public static Boolean getFileUploadDisable() {
return fileUploadDisable;
}

@Value("${file.upload.disable:false}")
@Value("${file.upload.disable:true}")
public void setFileUploadDisable(Boolean fileUploadDisable) {
setFileUploadDisableValue(fileUploadDisable);
}
Expand Down
140 changes: 140 additions & 0 deletions server/src/main/java/cn/keking/web/controller/FileController.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,77 @@ public class FileController {
private final String demoPath = demoDir + File.separator;
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);
if (checkResult.isFailure()) {
return checkResult;
}
File outFile = new File(fileDir + demoPath);
if (!outFile.exists() && !outFile.mkdirs()) {
logger.error("创建文件夹【{}】失败,请检查目录权限!", fileDir + demoPath);
}
String fileName = checkResult.getContent().toString();
logger.info("上传文件:{}{}{}", fileDir, demoPath, fileName);
try (InputStream in = file.getInputStream(); OutputStream out = Files.newOutputStream(Paths.get(fileDir + demoPath + fileName))) {
StreamUtils.copy(in, out);
return ReturnResponse.success(null);
} catch (IOException e) {
logger.error("文件上传失败", e);
return ReturnResponse.failure();
}
}

@GetMapping("/deleteFile")
public ReturnResponse<Object> deleteFile(HttpServletRequest request, String fileName, String password) {
ReturnResponse<Object> checkResult = this.deleteFileCheck(request, fileName, password);
if (checkResult.isFailure()) {
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);
}
WebUtils.removeSessionAttr(request, CAPTCHA_CODE); //删除缓存验证码
return ReturnResponse.success();
}

/**
* 验证码方法
*/
@RequestMapping("/deleteFile/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (!ConfigConstants.getDeleteCaptcha()) {
return;
}

response.setContentType("image/jpeg");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", -1);
String captchaCode = WebUtils.getSessionAttr(request, CAPTCHA_CODE);
long captchaGenerateTime = WebUtils.getLongSessionAttr(request, CAPTCHA_GENERATE_TIME);
long timeDifference = DateUtils.calculateCurrentTimeDifference(captchaGenerateTime);

// 验证码为空,且生成验证码超过50秒,重新生成验证码
if (timeDifference > 50 && ObjectUtils.isEmpty(captchaCode)) {
captchaCode = CaptchaUtil.generateCaptchaCode();
// 更新验证码
WebUtils.setSessionAttr(request, CAPTCHA_CODE, captchaCode);
WebUtils.setSessionAttr(request, CAPTCHA_GENERATE_TIME, DateUtils.getCurrentSecond());
} else {
captchaCode = ObjectUtils.isEmpty(captchaCode) ? "wait" : captchaCode;
}

ServletOutputStream outputStream = response.getOutputStream();
ImageIO.write(CaptchaUtil.generateCaptchaPic(captchaCode), "jpeg", outputStream);
outputStream.close();
}

@GetMapping("/listFiles")
public List<Map<String, String>> getFiles() {
List<Map<String, String>> list = new ArrayList<>();
Expand All @@ -69,6 +140,70 @@ public List<Map<String, String>> getFiles() {
return list;
}

/**
* 上传文件前校验
*
* @param file 文件
* @return 校验结果
*/
private ReturnResponse<Object> fileUploadCheck(MultipartFile file) {
if (ConfigConstants.getFileUploadDisable()) {
return ReturnResponse.failure("文件传接口已禁用");
}
String fileName = WebUtils.getFileNameFromMultipartFile(file);
if (fileName.lastIndexOf(".") == -1) {
return ReturnResponse.failure("不允许上传的类型");
}
if (!KkFileUtils.isAllowedUpload(fileName)) {
return ReturnResponse.failure("不允许上传的文件类型: " + fileName);
}
if (KkFileUtils.isIllegalFileName(fileName)) {
return ReturnResponse.failure("不允许上传的文件名: " + fileName);
}
// 判断是否存在同名文件
if (existsFile(fileName)) {
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);
} catch (Exception ex) {
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, fileName);
return ReturnResponse.failure(errorMsg + "删除失败!");
}
assert fileName != null;
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();

if (!password.equalsIgnoreCase(expectedPassword)) {
logger.error("删除文件【{}】失败,密码错误!", fileName);
return ReturnResponse.failure("删除文件失败,密码错误!");
}
return ReturnResponse.success(fileName);
}

@GetMapping("/directory")
public Object directory(String urls) {
String fileUrl;
Expand All @@ -84,4 +219,9 @@ public Object directory(String urls) {
}
return RarUtils.getTree(fileUrl);
}

private boolean existsFile(String fileName) {
File file = new File(fileDir + demoPath + fileName);
return file.exists();
}
}
12 changes: 12 additions & 0 deletions server/src/main/resources/web/main/index.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@
<input type="file" id="file" name="file" style="float: left; margin: 0 auto; font-size:22px;" placeholder="请选择文件"/>
<input type="button" id="fileUploadBtn" class="btn btn-success" value=" 上 传 "/>
</form>
<#else>
<div style="padding: 20px; margin: 10px 0; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;">
<p style="margin: 0; color: #6c757d; font-size: 16px;">
文件上传功能默认已禁用。如需开启,请通过以下方式配置:
<br/>
• 配置文件:<code>file.upload.disable=false</code>
<br/>
• 环境变量:<code>KK_FILE_UPLOAD_DISABLE=false</code>
<br/>
<strong style="color: #dc3545;">请注意:文件上传限开发环境调试使用,生产环境建议保持关闭状态,避免非法上传导致的安全隐患。</strong>
</p>
</div>
</#if>
<table id="table" data-pagination="true"></table>
</div>
Expand Down
Loading