Skip to content

Commit c6b8c14

Browse files
authored
feat: platform API and check file (opentiny#216)
1 parent 4847dcf commit c6b8c14

23 files changed

+1238
-39
lines changed

app/src/main/resources/sql/h2/create_all_tables_ddl_v1.h2.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ create table `t_platform_history`
4141
`ref_id` int not null comment '关联主表id',
4242
`version` varchar(255) not null comment '版本',
4343
`name` varchar(255) not null comment '名称',
44-
`publish_url` varchar(255) not null comment '设计器静态资源托管地址',
44+
`publish_url` varchar(255) comment '设计器静态资源托管地址',
4545
`description` varchar(2000) comment '描述',
4646
`vscode_url` varchar(255) comment '设计预留字段',
4747
`material_history_id` int not null comment '关联物料包历史id',
48-
`sub_count` int not null comment '设计预留字段',
48+
`sub_count` int comment '设计预留字段',
4949
`material_pkg_name` varchar(255) comment '物料包名称',
5050
`material_version` varchar(255) comment '物料包版本',
5151
`image_url` varchar(255) comment '封面图地址',
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
ALTER TABLE t_component DROP INDEX u_idx_component;
2-
ALTER TABLE t_component ADD INDEX u_idx_component (tenant_id, name_en, version, library_id);
2+
ALTER TABLE t_component ADD INDEX u_idx_component (tenant_id, name_en, version, library_id);
3+
4+
ALTER TABLE t_datasource DROP INDEX u_idx_datasource;
5+
ALTER TABLE t_datasource ADD INDEX u_idx_datasource (`tenant_id`, `platform_id`, `name`, `app_id`);
6+
7+
ALTER TABLE t_platform_history MODIFY sub_count int NULL;
8+
ALTER TABLE t_platform_history MODIFY publish_url varchar(255) NULL;

app/src/main/resources/sql/mysql/create_all_tables_ddl_v1.mysql.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ create table `t_platform_history`
4141
`ref_id` int not null comment '关联主表id',
4242
`version` varchar(255) not null comment '版本',
4343
`name` varchar(255) not null comment '名称',
44-
`publish_url` varchar(255) not null comment '设计器静态资源托管地址',
44+
`publish_url` varchar(255) comment '设计器静态资源托管地址',
4545
`description` varchar(2000) comment '描述',
46-
`vscode_url` varchar(255) comment '设计预留字段',
46+
`vscode_url` varchar(255) comment '设计预留字段',
4747
`material_history_id` int not null comment '关联物料包历史id',
48-
`sub_count` int not null comment '设计预留字段',
48+
`sub_count` int comment '设计预留字段',
4949
`material_pkg_name` varchar(255) comment '物料包名称',
5050
`material_version` varchar(255) comment '物料包版本',
5151
`image_url` varchar(255) comment '封面图地址',
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
ALTER TABLE t_component DROP INDEX u_idx_component;
2-
ALTER TABLE t_component ADD INDEX u_idx_component (tenant_id, name_en, version, library_id);
2+
ALTER TABLE t_component ADD INDEX u_idx_component (tenant_id, name_en, version, library_id);
3+
4+
ALTER TABLE t_datasource DROP INDEX u_idx_datasource;
5+
ALTER TABLE t_datasource ADD INDEX u_idx_datasource (`tenant_id`, `platform_id`, `name`, `app_id`);
6+
7+
ALTER TABLE t_platform_history MODIFY sub_count int NULL;
8+
ALTER TABLE t_platform_history MODIFY publish_url varchar(255) NULL;

base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,22 @@ public enum ExceptionEnum implements IBaseError {
235235
/**
236236
* Cm 322 exception enum.
237237
*/
238-
CM322("CM322", "调用接口失败");
238+
CM322("CM322", "调用接口失败"),
239+
240+
/**
241+
* Cm 323 exception enum.
242+
*/
243+
CM323("CM323", "文件名长度范围为1-100,以数字或字母开头"),
244+
245+
/**
246+
* Cm 324 exception enum.
247+
*/
248+
CM324("CM324","文件名或路径无效"),
249+
250+
/**
251+
* Cm 325 exception enum.
252+
*/
253+
CM325("CM325","文件校验失败");
239254

240255
/**
241256
* 错误码
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* Copyright (c) 2023 - present TinyEngine Authors.
3+
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
4+
*
5+
* Use of this source code is governed by an MIT-style license.
6+
*
7+
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
8+
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
9+
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
10+
*/
11+
12+
package com.tinyengine.it.common.utils;
13+
14+
import cn.hutool.core.util.ObjectUtil;
15+
import com.tinyengine.it.common.exception.ExceptionEnum;
16+
import com.tinyengine.it.common.exception.ServiceException;
17+
import org.springframework.util.StringUtils;
18+
import org.springframework.web.multipart.MultipartFile;
19+
20+
import java.io.File;
21+
import java.util.Map;
22+
import java.util.Objects;
23+
import java.util.regex.Pattern;
24+
25+
/**
26+
* The file check Utils.
27+
*
28+
* @since 2025-05-13
29+
*/
30+
public class SecurityFileCheckUtil {
31+
32+
private static final String REGX_FILE_NAME = "^[a-z0-9A-Z][^\\\\/:*<>|]+$";
33+
private static final Pattern PATTERN_FILE_NAME = Pattern.compile(REGX_FILE_NAME);
34+
35+
/**
36+
* Determine whether the file has cross path connections.
37+
*
38+
* @param dirOrFileName the dirOrFileName
39+
* @return true or false
40+
*/
41+
public static boolean checkPathHasCrossDir(String dirOrFileName) {
42+
if (!dirOrFileName.contains("../") && !dirOrFileName.contains("/..")) {
43+
if (!dirOrFileName.contains("..\\") && !dirOrFileName.contains("\\..")) {
44+
return dirOrFileName.contains("./") || dirOrFileName.contains(".\\.\\") || dirOrFileName.contains("%00");
45+
} else {
46+
return true;
47+
}
48+
} else {
49+
return true;
50+
}
51+
}
52+
53+
/**
54+
* Type of inspection document.
55+
*
56+
* @param file the file
57+
* @param fileTypeMap the fileTypeMap
58+
* @return true or false
59+
*/
60+
public static boolean checkFileType(MultipartFile file, Map<String, String> fileTypeMap) {
61+
if (Objects.isNull(file) || fileTypeMap.isEmpty()) {
62+
throw new ServiceException(ExceptionEnum.CM307.getResultCode(), ExceptionEnum.CM307.getResultMsg());
63+
}
64+
String originalFileName = file.getOriginalFilename();
65+
for (Map.Entry<String, String> entry : fileTypeMap.entrySet()) {
66+
if (originalFileName.endsWith(entry.getKey())) {
67+
return checkFileType(file, entry.getKey(), entry.getValue());
68+
}
69+
}
70+
return false;
71+
}
72+
73+
/**
74+
* Type of inspection document.
75+
*
76+
* @param file the file
77+
* @param fileNameEnd the fileNameEnd
78+
* @param fileType the fileType
79+
* @return true or false
80+
*/
81+
public static boolean checkFileType(MultipartFile file, String fileNameEnd, String fileType) {
82+
String originalFileName = file.getOriginalFilename();
83+
String contentType = file.getContentType();
84+
if (ObjectUtil.isEmpty(originalFileName) || ObjectUtil.isEmpty(contentType)) {
85+
return false;
86+
}
87+
if (!originalFileName.endsWith(fileNameEnd)) {
88+
return false;
89+
}
90+
if (!contentType.equalsIgnoreCase(fileType)) {
91+
return false;
92+
}
93+
return true;
94+
}
95+
96+
/**
97+
* Inspection file name.
98+
*
99+
* @param fileName the fileName
100+
*/
101+
public static void validFileName(String fileName) {
102+
if (!StringUtils.hasText(fileName)) {
103+
throw new ServiceException(ExceptionEnum.CM320.getResultCode(), ExceptionEnum.CM320.getResultMsg());
104+
}
105+
if (!checkFileNameLength(fileName, 1, 100)) {
106+
throw new ServiceException(ExceptionEnum.CM323.getResultCode(), ExceptionEnum.CM323.getResultMsg());
107+
}
108+
if (!filePathIsValid(fileName)) {
109+
throw new ServiceException(ExceptionEnum.CM324.getResultCode(), ExceptionEnum.CM324.getResultMsg());
110+
}
111+
String fullFileName = getFileName(fileName);
112+
if (!PATTERN_FILE_NAME.matcher(fullFileName).matches()) {
113+
throw new ServiceException(ExceptionEnum.CM324.getResultCode(), ExceptionEnum.CM324.getResultMsg());
114+
}
115+
}
116+
117+
/**
118+
* Check if the file name length is within the specified range.
119+
*
120+
* @param fileName the fileName
121+
* @param min the min
122+
* @param max the max
123+
* @return true or false
124+
*/
125+
public static boolean checkFileNameLength(String fileName, int min, int max) {
126+
if (!StringUtils.hasText(fileName)) {
127+
return min <= 0;
128+
}
129+
String temp = fileName.replaceAll("[^\\x00-\\xff]", "**");
130+
return temp.length() <= max;
131+
}
132+
133+
/**
134+
* Verify file path.
135+
*
136+
* @param fileName the fileName
137+
* @return true or false
138+
*/
139+
public static boolean filePathIsValid(String fileName) {
140+
if (fileName == null || fileName.trim().isEmpty()) {
141+
return false;
142+
}
143+
144+
// 获取当前操作系统的名称
145+
String os = System.getProperty("os.name").toLowerCase();
146+
147+
// 定义通用的非法字符
148+
String illegalChars = "";
149+
150+
if (os.contains("win")) {
151+
// 针对Windows的非法字符
152+
illegalChars = "[<>:\"/\\|?*]";
153+
} else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) {
154+
// 针对Linux和macOS的非法字符(一般来说,Linux和macOS对文件名的限制较少,但有一些常见的非法字符)
155+
illegalChars = "[/]"; // Linux和macOS的路径不能包含斜杠 '/'
156+
}
157+
// 检查路径中是否包含非法字符
158+
if (fileName.matches(".*" + illegalChars + ".*")) {
159+
return false;
160+
}
161+
// 检查路径是否超过文件系统允许的最大长度(例如,Windows上的路径限制通常为260个字符)
162+
if (fileName.length() > 260) {
163+
return false;
164+
}
165+
166+
// 检查路径中是否包含空格或其他特殊字符,视需要进行定制
167+
// 如果需要你也可以根据不同操作系统做不同的检查
168+
169+
return true;
170+
}
171+
172+
private static String getFileName(String filePath) {
173+
File file = new File(filePath);
174+
return file.getName();
175+
}
176+
177+
178+
}

base/src/main/java/com/tinyengine/it/controller/ComponentController.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.tinyengine.it.common.base.Result;
1616
import com.tinyengine.it.common.exception.ExceptionEnum;
1717
import com.tinyengine.it.common.log.SystemControllerLog;
18+
import com.tinyengine.it.common.utils.SecurityFileCheckUtil;
1819
import com.tinyengine.it.model.dto.BundleResultDto;
1920
import com.tinyengine.it.model.dto.CustComponentDto;
2021
import com.tinyengine.it.model.dto.FileResult;
@@ -71,6 +72,7 @@ public Result<FileResult> bundleCreateComponent(@RequestParam MultipartFile file
7172
if (file.isEmpty()) {
7273
return Result.failed(ExceptionEnum.CM307);
7374
}
75+
SecurityFileCheckUtil.validFileName(file.getOriginalFilename());
7476
// 返回插入和更新的条数
7577
return componentService.readFileAndBulkCreate(file);
7678
}
@@ -92,6 +94,7 @@ public Result<BundleResultDto> bundleSplit(@RequestParam MultipartFile file) {
9294
if (file.isEmpty()) {
9395
return Result.failed(ExceptionEnum.CM307);
9496
}
97+
SecurityFileCheckUtil.validFileName(file.getOriginalFilename());
9598
return componentService.bundleSplit(file);
9699
}
97100

base/src/main/java/com/tinyengine/it/controller/I18nEntryController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.tinyengine.it.common.exception.ExceptionEnum;
1717
import com.tinyengine.it.common.exception.ServiceException;
1818
import com.tinyengine.it.common.log.SystemControllerLog;
19+
import com.tinyengine.it.common.utils.SecurityFileCheckUtil;
1920
import com.tinyengine.it.model.dto.DeleteI18nEntry;
2021
import com.tinyengine.it.model.dto.FileResult;
2122
import com.tinyengine.it.model.dto.I18nEntryDto;
@@ -234,10 +235,10 @@ public Result<FileResult> updateI18nSingleFile(
234235
for (Map.Entry<String, MultipartFile> entry : filesMap.entrySet()) {
235236
// 获取对应的文件
236237
MultipartFile file = entry.getValue();
237-
238238
if (file.isEmpty()) {
239239
return Result.failed(ExceptionEnum.CM307);
240240
}
241+
SecurityFileCheckUtil.validFileName(file.getOriginalFilename());
241242
// 返回插入和更新的条数
242243
result = i18nEntryService.readSingleFileAndBulkCreate(file, id);
243244
}
@@ -274,10 +275,10 @@ public Result<FileResult> updateI18nMultiFile(
274275
for (Map.Entry<String, MultipartFile> entry : filesMap.entrySet()) {
275276
String key = entry.getKey(); // 获取动态的参数名
276277
MultipartFile file = entry.getValue(); // 获取对应的文件
277-
278278
if (file.isEmpty()) {
279279
return Result.failed(ExceptionEnum.CM307);
280280
}
281+
SecurityFileCheckUtil.validFileName(file.getOriginalFilename());
281282
// 返回插入和更新的条数
282283
result = i18nEntryService.readFilesAndbulkCreate(key, file, id);
283284
}

0 commit comments

Comments
 (0)