forked from opentiny/tiny-engine-backend-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUtils.java
More file actions
436 lines (392 loc) · 14.8 KB
/
Utils.java
File metadata and controls
436 lines (392 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
/**
* Copyright (c) 2023 - present TinyEngine Authors.
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
package com.tinyengine.it.common.utils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.tinyengine.it.common.base.Result;
import com.tinyengine.it.common.enums.Enums;
import com.tinyengine.it.common.exception.ExceptionEnum;
import com.tinyengine.it.common.exception.ServiceException;
import com.tinyengine.it.model.dto.FileInfo;
import com.tinyengine.it.model.dto.JsonFile;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* The type Utils.
*
* @since 2024-10-20
*/
@Slf4j
public class Utils {
/**
* The Res keys.
*/
private static final String[] RES_KEYS = {"is_body", "parent_id", "is_page", "is_default"};
private static final Pattern CHAR_WORD = Pattern.compile("_(\\w)");
private static final Pattern CHAR_AZ = Pattern.compile("([A-Z])");
/**
* Remove duplicates list.
*
* @param <T> the type parameter
* @param list the list
* @return the list
*/
// 泛型去重方法
public static <T> List<T> removeDuplicates(List<T> list) {
// 使用 Set 去重
Set<T> set = new LinkedHashSet<>(list);
// 返回去重后的 List
return new ArrayList<>(set);
}
/**
* Find max version string.
*
* @param versions the versions
* @return the string
*/
// 查找最大版本
public static String findMaxVersion(List<String> versions) {
return versions.stream().max(
Comparator.comparing(
version -> Arrays.stream(version.split("\\."))
.mapToInt(Integer::parseInt).toArray(), Comparator
.comparingInt((int[] arr) -> arr[0])
.thenComparingInt(arr -> arr[1])
.thenComparingInt(arr -> arr[2]))).orElse(null);
}
/**
* To hump string.
*
* @param name the name
* @return the string
*/
public static String toHump(String name) {
// 定义正则表达式模式
Matcher matcher = CHAR_WORD.matcher(name);
// 使用 StringBuilder 来构建结果字符串
StringBuilder result = new StringBuilder();
int lastEnd = 0;
// 遍历匹配的结果
while (matcher.find()) {
// 将之前的字符串添加到结果中
result.append(name, lastEnd, matcher.start());
// 获取匹配到的字母并转换为大写
// 确保此处是有效的调用
String match = matcher.group(1);
result.append(match.toUpperCase(Locale.ROOT));
lastEnd = matcher.end();
}
// 添加最后的部分
result.append(name.substring(lastEnd));
return result.toString();
}
/**
* To line string.
*
* @param name the name
* @return the string
*/
public static String toLine(String name) {
// 定义正则表达式模式
Matcher matcher = CHAR_AZ.matcher(name);
// 使用 StringBuilder 来构建结果字符串
StringBuilder result = new StringBuilder();
int lastEnd = 0;
// 遍历匹配的结果
while (matcher.find()) {
// 将之前的字符串添加到结果中
result.append(name, lastEnd, matcher.start());
// 在大写字母前添加下划线
// 确保此处是有效的调用
result.append("_").append(matcher.group(1));
lastEnd = matcher.end();
}
// 添加最后的部分并转换为小写
result.append(name.substring(lastEnd).toLowerCase(Locale.ROOT));
return result.toString();
}
/**
* Convert map.
*
* @param obj the obj
* @return the map
*/
// 对象转map
public static Map<String, Object> convert(Object obj) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 将对象转换为 JSON 字符串,然后再解析为 Map
return objectMapper.convertValue(obj, Map.class);
}
/**
* 解压并处理zip文件,把读取到的JSON文件内容以字符串返回
*
* @param multipartFile multipartFile
* @return String
* @throws IOException IOException
*/
// 主方法:解压 MultipartFile 文件
public static List<FileInfo> unzip(MultipartFile multipartFile) throws IOException {
File tempDir = createTempDirectory(); // 创建临时目录
File zipFile = convertMultipartFileToFile(multipartFile); // 转换 MultipartFile 为临时文件
List<FileInfo> fileInfoList = new ArrayList<>();
// 使用 try-with-resources 来自动关闭 ZipInputStream
try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipFile.toPath()))) {
// 处理 zip 文件的内容
fileInfoList = processZipEntries(zis, tempDir);
} finally {
// 在 finally 块中执行资源清理
cleanUp(zipFile, tempDir); // 清理临时文件和目录
}
return fileInfoList;
}
/**
* 创建临时目录
*
* @return File the File
* @throws IOException IOException
*/
private static File createTempDirectory() throws IOException {
return Files.createTempDirectory("unzip").toFile();
}
/**
* 转换 MultipartFile 为 File 的方法
*
* @param multipartFile the multipartFile
* @return File the File
* @throws IOException IOException
*/
private static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
File tempFile = File.createTempFile("temp", null);
tempFile.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
fos.write(multipartFile.getBytes());
}
return tempFile;
}
/**
* 处理解压文件的每个条目,返回一个文件集合
*
* @param zis the zis
* @param tempDir the tempDir
* @return List<FileInfo> the List<FileInfo>
* @throws IOException IOException
*/
private static List<FileInfo> processZipEntries(ZipInputStream zis, File tempDir) throws IOException {
List<FileInfo> fileInfoList = new ArrayList<>();
ZipEntry zipEntry;
while ((zipEntry = zis.getNextEntry()) != null) {
File newFile = new File(tempDir, zipEntry.getName());
if (zipEntry.isDirectory()) {
fileInfoList.add(new FileInfo(newFile.getName(), "", true)); // 添加目录
} else {
extractFile(zis, newFile); // 解压文件
fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false)); // 添加文件内容
}
zis.closeEntry();
}
return fileInfoList;
}
/**
* 解压文件
*
* @param zis the zis
* @param newFile the newFile
* @throws IOException IOException
*/
private static void extractFile(ZipInputStream zis, File newFile) throws IOException {
Files.createDirectories(newFile.getParentFile().toPath()); // 确保父目录存在
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile))) {
byte[] buffer = new byte[1024];
int length;
while ((length = zis.read(buffer)) != -1) {
bos.write(buffer, 0, length);
}
}
}
/**
* 读取传入的文件内容,并返回内容字符串
*
* @param file the file
* @return String the String
*/
public static String readFileContent(File file) {
List<String> lines = FileUtil.readLines(file, Charset.defaultCharset());
StringBuilder contentBuilder = new StringBuilder();
for (String line : lines) {
contentBuilder.append(line).append(System.lineSeparator());
}
return contentBuilder.toString();
}
// 清理临时文件和目录
private static void cleanUp(File zipFile, File tempDir) {
// 删除临时的 zip 文件
if (zipFile.exists()) {
if (!zipFile.delete()) {
log.error("Failed to delete zip file: " + zipFile.getAbsolutePath());
} else {
log.info("Successfully deleted zip file: " + zipFile.getAbsolutePath());
}
}
// 删除临时解压目录及其内容
try (Stream<Path> paths = Files.walk(tempDir.toPath())) { // 使用 try-with-resources 自动关闭流
paths.sorted(Comparator.reverseOrder()) // 反向删除
.map(Path::toFile)
.forEach(file -> {
if (!file.delete()) {
log.error("Failed to delete file: " + file.getAbsolutePath());
} else {
log.info("Successfully deleted file: " + file.getAbsolutePath());
}
});
} catch (IOException e) {
log.error("Error walking through temp directory: " + e.getMessage());
}
}
/**
* 将传入的 InputStream 中的所有字节读取到一个字节数组中,并返回该字节数组
*
* @param inputStream the inputStream
* @return byte[] the byte[]
* @throws IOException IOException
*/
public static byte[] readAllBytes(InputStream inputStream) throws IOException {
// 使用 try-with-resources 确保自动关闭资源
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
// 返回读取的所有字节
return byteArrayOutputStream.toByteArray();
} finally {
// 显式关闭传入的 InputStream,防止未关闭的资源泄漏
if (inputStream != null) {
inputStream.close();
}
}
}
/**
* 将一个嵌套的 JSON 对象扁平化
*
* @param jsonData the json data
* @return map
*/
public static Map<String, Object> flat(Map<String, Object> jsonData) {
Map<String, Object> flattenedMap = new HashMap<>();
flatten("", jsonData, flattenedMap);
return flattenedMap;
}
private static void flatten(String prefix, Map<String, Object> data, Map<String, Object> flattenedMap) {
for (Map.Entry<String, Object> entry : data.entrySet()) {
String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
if (entry.getValue() instanceof Map) {
flatten(key, (Map<String, Object>) entry.getValue(), flattenedMap);
} else {
flattenedMap.put(key, entry.getValue());
}
}
}
/**
* 解析JSON文件
*
* @param file the file
* @return result
*/
public static Result<JsonFile> parseJsonFileStream(MultipartFile file) {
String fileName = file.getOriginalFilename();
log.info("Parsing JSON file: {}", fileName);
// 校验文件流合法性
validateFileStream(file, ExceptionEnum.CM308.getResultCode(), Arrays.asList(Enums.MimeType.JSON.getValue()));
JsonFile jsonFile = new JsonFile();
// 解析国际化词条文件
try {
// 使用 try-with-resources 自动管理输入流
byte[] fileBytes = Utils.readAllBytes(file.getInputStream());
String jsonContent = new String(fileBytes, StandardCharsets.UTF_8);
String jsonString = removeBOM(jsonContent);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonData =
objectMapper.readValue(jsonString, new TypeReference<Map<String, Object>>() {
});
jsonFile.setFileName(fileName);
jsonFile.setFileContent(jsonData);
} catch (IOException e) {
log.error("Error parsing JSON: {}", e.getMessage());
return Result.validateFailed("Error parsing JSON");
}
log.info("Successfully parsed JSON file: {}", fileName);
return Result.success(jsonFile);
}
/**
* 去除文件BOM字符
*
* @param input the inpu
* @return input the input
*/
public static String removeBOM(String input) {
if (input != null && input.startsWith("\uFEFF")) {
return input.substring(1);
}
return input;
}
/**
* 校验文件流合法性
*
* @param file 文件
* @param code 报错码
* @param mimeTypes 文件类型集合
*/
public static void validateFileStream(MultipartFile file, String code, List<String> mimeTypes) {
boolean hasCondition = file.getOriginalFilename() != null
&& mimeTypes.contains(file.getContentType());
if (hasCondition) {
return;
}
// 只要文件不合法就throw error, 无论是批量还是单个
try {
file.getInputStream().close();
} catch (IOException e) {
log.error("file close fail:{}", e.getMessage());
}
throw new ServiceException(code, "validate file fail");
}
}