Skip to content

Commit 6cdbf92

Browse files
authored
优化:完善文件上传禁用功能的用户体验 (#684)
1 parent fdb4068 commit 6cdbf92

File tree

4 files changed

+155
-2
lines changed

4 files changed

+155
-2
lines changed

server/src/main/config/application.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ watermark.height = ${WATERMARK_HEIGHT:80}
159159
#水印倾斜度数,要求设置在大于等于0,小于90
160160
watermark.angle = ${WATERMARK_ANGLE:10}
161161

162-
163162
#首页功能设置
163+
#是否禁用首页文件上传
164+
file.upload.disable = ${KK_FILE_UPLOAD_DISABLE:true}
164165
# 备案信息,默认为空
165166
beian = ${KK_BEIAN:default}
166167
#禁止上传类型

server/src/main/java/cn/keking/config/ConfigConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ public static Boolean getFileUploadDisable() {
426426
return fileUploadDisable;
427427
}
428428

429-
@Value("${file.upload.disable:false}")
429+
@Value("${file.upload.disable:true}")
430430
public void setFileUploadDisable(Boolean fileUploadDisable) {
431431
setFileUploadDisableValue(fileUploadDisable);
432432
}

server/src/main/java/cn/keking/web/controller/FileController.java

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,77 @@ public class FileController {
5353
private final String demoPath = demoDir + File.separator;
5454
public static final String BASE64_DECODE_ERROR_MSG = "Base64解码失败,请检查你的 %s 是否采用 Base64 + urlEncode 双重编码了!";
5555

56+
@PostMapping("/fileUpload")
57+
public ReturnResponse<Object> fileUpload(@RequestParam("file") MultipartFile file) {
58+
ReturnResponse<Object> checkResult = this.fileUploadCheck(file);
59+
if (checkResult.isFailure()) {
60+
return checkResult;
61+
}
62+
File outFile = new File(fileDir + demoPath);
63+
if (!outFile.exists() && !outFile.mkdirs()) {
64+
logger.error("创建文件夹【{}】失败,请检查目录权限!", fileDir + demoPath);
65+
}
66+
String fileName = checkResult.getContent().toString();
67+
logger.info("上传文件:{}{}{}", fileDir, demoPath, fileName);
68+
try (InputStream in = file.getInputStream(); OutputStream out = Files.newOutputStream(Paths.get(fileDir + demoPath + fileName))) {
69+
StreamUtils.copy(in, out);
70+
return ReturnResponse.success(null);
71+
} catch (IOException e) {
72+
logger.error("文件上传失败", e);
73+
return ReturnResponse.failure();
74+
}
75+
}
76+
77+
@GetMapping("/deleteFile")
78+
public ReturnResponse<Object> deleteFile(HttpServletRequest request, String fileName, String password) {
79+
ReturnResponse<Object> checkResult = this.deleteFileCheck(request, fileName, password);
80+
if (checkResult.isFailure()) {
81+
return checkResult;
82+
}
83+
fileName = checkResult.getContent().toString();
84+
File file = new File(fileDir + demoPath + fileName);
85+
logger.info("删除文件:{}", file.getAbsolutePath());
86+
if (file.exists() && !file.delete()) {
87+
String msg = String.format("删除文件【%s】失败,请检查目录权限!", file.getPath());
88+
logger.error(msg);
89+
return ReturnResponse.failure(msg);
90+
}
91+
WebUtils.removeSessionAttr(request, CAPTCHA_CODE); //删除缓存验证码
92+
return ReturnResponse.success();
93+
}
94+
95+
/**
96+
* 验证码方法
97+
*/
98+
@RequestMapping("/deleteFile/captcha")
99+
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
100+
if (!ConfigConstants.getDeleteCaptcha()) {
101+
return;
102+
}
103+
104+
response.setContentType("image/jpeg");
105+
response.setHeader("Pragma", "no-cache");
106+
response.setHeader("Cache-Control", "no-cache");
107+
response.setDateHeader("Expires", -1);
108+
String captchaCode = WebUtils.getSessionAttr(request, CAPTCHA_CODE);
109+
long captchaGenerateTime = WebUtils.getLongSessionAttr(request, CAPTCHA_GENERATE_TIME);
110+
long timeDifference = DateUtils.calculateCurrentTimeDifference(captchaGenerateTime);
111+
112+
// 验证码为空,且生成验证码超过50秒,重新生成验证码
113+
if (timeDifference > 50 && ObjectUtils.isEmpty(captchaCode)) {
114+
captchaCode = CaptchaUtil.generateCaptchaCode();
115+
// 更新验证码
116+
WebUtils.setSessionAttr(request, CAPTCHA_CODE, captchaCode);
117+
WebUtils.setSessionAttr(request, CAPTCHA_GENERATE_TIME, DateUtils.getCurrentSecond());
118+
} else {
119+
captchaCode = ObjectUtils.isEmpty(captchaCode) ? "wait" : captchaCode;
120+
}
121+
122+
ServletOutputStream outputStream = response.getOutputStream();
123+
ImageIO.write(CaptchaUtil.generateCaptchaPic(captchaCode), "jpeg", outputStream);
124+
outputStream.close();
125+
}
126+
56127
@GetMapping("/listFiles")
57128
public List<Map<String, String>> getFiles() {
58129
List<Map<String, String>> list = new ArrayList<>();
@@ -69,6 +140,70 @@ public List<Map<String, String>> getFiles() {
69140
return list;
70141
}
71142

143+
/**
144+
* 上传文件前校验
145+
*
146+
* @param file 文件
147+
* @return 校验结果
148+
*/
149+
private ReturnResponse<Object> fileUploadCheck(MultipartFile file) {
150+
if (ConfigConstants.getFileUploadDisable()) {
151+
return ReturnResponse.failure("文件传接口已禁用");
152+
}
153+
String fileName = WebUtils.getFileNameFromMultipartFile(file);
154+
if (fileName.lastIndexOf(".") == -1) {
155+
return ReturnResponse.failure("不允许上传的类型");
156+
}
157+
if (!KkFileUtils.isAllowedUpload(fileName)) {
158+
return ReturnResponse.failure("不允许上传的文件类型: " + fileName);
159+
}
160+
if (KkFileUtils.isIllegalFileName(fileName)) {
161+
return ReturnResponse.failure("不允许上传的文件名: " + fileName);
162+
}
163+
// 判断是否存在同名文件
164+
if (existsFile(fileName)) {
165+
return ReturnResponse.failure("存在同名文件,请先删除原有文件再次上传");
166+
}
167+
return ReturnResponse.success(fileName);
168+
}
169+
170+
171+
/**
172+
* 删除文件前校验
173+
*
174+
* @param fileName 文件名
175+
* @return 校验结果
176+
*/
177+
private ReturnResponse<Object> deleteFileCheck(HttpServletRequest request, String fileName, String password) {
178+
if (ObjectUtils.isEmpty(fileName)) {
179+
return ReturnResponse.failure("文件名为空,删除失败!");
180+
}
181+
try {
182+
fileName = WebUtils.decodeUrl(fileName);
183+
} catch (Exception ex) {
184+
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, fileName);
185+
return ReturnResponse.failure(errorMsg + "删除失败!");
186+
}
187+
assert fileName != null;
188+
if (fileName.contains("/")) {
189+
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
190+
}
191+
if (KkFileUtils.isIllegalFileName(fileName)) {
192+
return ReturnResponse.failure("非法文件名,删除失败!");
193+
}
194+
if (ObjectUtils.isEmpty(password)) {
195+
return ReturnResponse.failure("密码 or 验证码为空,删除失败!");
196+
}
197+
198+
String expectedPassword = ConfigConstants.getDeleteCaptcha() ? WebUtils.getSessionAttr(request, CAPTCHA_CODE) : ConfigConstants.getPassword();
199+
200+
if (!password.equalsIgnoreCase(expectedPassword)) {
201+
logger.error("删除文件【{}】失败,密码错误!", fileName);
202+
return ReturnResponse.failure("删除文件失败,密码错误!");
203+
}
204+
return ReturnResponse.success(fileName);
205+
}
206+
72207
@GetMapping("/directory")
73208
public Object directory(String urls) {
74209
String fileUrl;
@@ -84,4 +219,9 @@ public Object directory(String urls) {
84219
}
85220
return RarUtils.getTree(fileUrl);
86221
}
222+
223+
private boolean existsFile(String fileName) {
224+
File file = new File(fileDir + demoPath + fileName);
225+
return file.exists();
226+
}
87227
}

server/src/main/resources/web/main/index.ftl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@
148148
<input type="file" id="file" name="file" style="float: left; margin: 0 auto; font-size:22px;" placeholder="请选择文件"/>
149149
<input type="button" id="fileUploadBtn" class="btn btn-success" value=" 上 传 "/>
150150
</form>
151+
<#else>
152+
<div style="padding: 20px; margin: 10px 0; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;">
153+
<p style="margin: 0; color: #6c757d; font-size: 16px;">
154+
文件上传功能默认已禁用。如需开启,请通过以下方式配置:
155+
<br/>
156+
• 配置文件:<code>file.upload.disable=false</code>
157+
<br/>
158+
• 环境变量:<code>KK_FILE_UPLOAD_DISABLE=false</code>
159+
<br/>
160+
<strong style="color: #dc3545;">请注意:文件上传限开发环境调试使用,生产环境建议保持关闭状态,避免非法上传导致的安全隐患。</strong>
161+
</p>
162+
</div>
151163
</#if>
152164
<table id="table" data-pagination="true"></table>
153165
</div>

0 commit comments

Comments
 (0)