| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.project.common; |
| | | |
| | | import java.util.HashSet; |
| | | import java.util.Set; |
| | | import java.util.regex.Pattern; |
| | | |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | |
| | | /** |
| | | * æ¥éåéä»¶æä»¶åè§èæ ¡éªï¼2026-10-15ï¼ã |
| | | * |
| | | * <p>ä¸å¡å£å¾ï¼ä¸å¡æ¹ç¡®è®¤ï¼ï¼ |
| | | * <ol> |
| | | * <li>å¿
é¡»å
å«ä¸æï¼è³å° 1 个æ±åï¼ââ"为æ¹ä¾¿æ¥éªéä»¶ï¼è¯·ç¨ä¸æè§èå½åéä»¶ï¼"</li> |
| | | * <li>廿æ©å±ååçä¸»å¹²ï¼æå符æ°è®¡ç®ä¸è½è¶
è¿ 20 个åââ"为æ¹ä¾¿æ¥éªéä»¶ï¼éä»¶ååé¿åº¦ä¸è½è¶
è¿20个åï¼"</li> |
| | | * <li>ä¸è½å
å«ç©ºæ ¼å {@code / \ : * ? " < > | # %} çç¹æ®å符</li> |
| | | * <li>å䏿ç»è¡å
ä¸è½éåï¼æ¯è¾æ¶å¿½ç¥æ©å±å大å°åä¸é¦å°¾ç©ºæ ¼ï¼</li> |
| | | * </ol> |
| | | * |
| | | * <p>ä¸å端 {@code opo-web/src/utils/fileNameCheck.js} <b>åå£å¾</b>ï¼ |
| | | * å端è´è´£"ä¸ä¼ 峿é + ä¿å忦æª"ï¼æ¬ç±»è´è´£å端ä¿åæ¶ç<b>äºæ¬¡æ ¡éª</b>ï¼é²æ¢ç»è¿å端ï¼ã |
| | | * |
| | | * <p>ç¨æ³ï¼å¨ä¿åå
¥å£éï¼ï¼ |
| | | * <pre> |
| | | * String err = AttachmentNameValidator.checkJsonList(detail.getInvoicefiles(), detail.getAnnexfiles()); |
| | | * if (err != null) { return AjaxResult.error("éä»¶æä»¶åä¸ç¬¦åè¦æ±ï¼æ æ³ä¿åï¼" + err); } |
| | | * </pre> |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | public final class AttachmentNameValidator { |
| | | |
| | | /** 主干ï¼å»æ©å±åï¼æå¤§åç¬¦æ° */ |
| | | public static final int MAX_MAIN_LENGTH = 20; |
| | | |
| | | public static final String MSG_NO_CHINESE = "为æ¹ä¾¿æ¥éªéä»¶ï¼è¯·ç¨ä¸æè§èå½åéä»¶ï¼"; |
| | | public static final String MSG_TOO_LONG = "为æ¹ä¾¿æ¥éªéä»¶ï¼éä»¶ååé¿åº¦ä¸è½è¶
è¿20个åï¼"; |
| | | public static final String MSG_ILLEGAL = "为æ¹ä¾¿æ¥éªéä»¶ï¼éä»¶åä¸è½å
å«ç©ºæ ¼å / \\ : * ? \" < > | # % çç¹æ®å符ï¼"; |
| | | public static final String MSG_DUPLICATE = "为æ¹ä¾¿æ¥éªéä»¶ï¼åä¸è¡å
ä¸è½æéåçéä»¶ï¼"; |
| | | /** é件项éåä¸å°ä»»ä½æä»¶åï¼æ¢æ²¡æ name 乿²¡æ fileNameï¼æ¶çæç¤º */ |
| | | public static final String MSG_NO_NAME = "为æ¹ä¾¿æ¥éªéä»¶ï¼éä»¶å¿
须带æä»¶åï¼è¯·éæ°ä¸ä¼ ï¼"; |
| | | |
| | | /** æ±å */ |
| | | private static final Pattern CHINESE = Pattern.compile("[\\u4e00-\\u9fa5]"); |
| | | |
| | | /** éæ³å符ï¼ç©ºæ ¼ï¼å«å类空ç½ï¼+ / \ : * ? " < > | # % */ |
| | | private static final Pattern ILLEGAL = Pattern.compile("[\\s/\\\\:*?\"<>|#%]"); |
| | | |
| | | private static final ObjectMapper MAPPER = new ObjectMapper(); |
| | | |
| | | private AttachmentNameValidator() { |
| | | } |
| | | |
| | | /** |
| | | * å主干ï¼å»ææåä¸ä¸ªæ©å±åï¼ã |
| | | */ |
| | | public static String mainName(String fileName) { |
| | | String s = fileName == null ? "" : fileName.trim(); |
| | | int index = s.lastIndexOf('.'); |
| | | return index > 0 ? s.substring(0, index) : s; |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªå个æä»¶åã |
| | | * |
| | | * @param fileName åå§æä»¶å |
| | | * @return {@code null}=éè¿ï¼å¦åè¿åä¸åæ ¼åå |
| | | */ |
| | | public static String checkName(String fileName) { |
| | | String name = fileName == null ? "" : fileName.trim(); |
| | | if (name.isEmpty()) { |
| | | return MSG_NO_CHINESE; |
| | | } |
| | | if (ILLEGAL.matcher(name).find()) { |
| | | return MSG_ILLEGAL; |
| | | } |
| | | String main = mainName(name); |
| | | if (!CHINESE.matcher(main).find()) { |
| | | return MSG_NO_CHINESE; |
| | | } |
| | | // æå符æ°è®¡ï¼ç ç¹è®¡æ°ï¼ä¸æ/忝/æ°å/å
¨è§å计 1ï¼ |
| | | if (main.codePointCount(0, main.length()) > MAX_MAIN_LENGTH) { |
| | | return MSG_TOO_LONG; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªä¸ä¸ªéä»¶å表 JSONï¼å½¢å¦ {@code [{"name":"å¼ ä¸å票.pdf","url":"/profile/upload/..."}]}ï¼ã |
| | | * |
| | | * @param json éä»¶åæ®µå
容ï¼annexfiles / invoicefilesï¼ï¼å¯ä¸ºç©º |
| | | * @return {@code null}=éè¿ï¼å¦åè¿åä¸åæ ¼åå ï¼å«æä»¶åï¼ |
| | | */ |
| | | public static String checkJson(String json) { |
| | | return checkJson(json, null, true); |
| | | } |
| | | |
| | | /** |
| | | * ä¿®æ¹ä¿åæ¶ä½¿ç¨ï¼å¿½ç¥"ä¿ååå·²åå¨"çåå²éä»¶åã |
| | | * |
| | | * <p>ä¸å¡å£å¾ï¼2026-10-15 确认ï¼ï¼**åªæ ¡éªæ¬æ¬¡åæ´çéä»¶**ââåå²åæ®éå·²åçä¸åè§éä»¶åï¼ |
| | | * å¨ç¼è¾è¯¥åæ®æ¶ä¸åºæä¿å塿»ï¼å¦åèåæ®æ¹ä¸ä¸ªéé¢é½åä¸è¿å»ã |
| | | * |
| | | * @param existingNames ä¿åå该æç»å·²åå¨çéä»¶åï¼ç¨ {@link #collectNames(String)} æ¶éï¼ |
| | | * é为å°åå»ç©ºç½å½¢å¼ï¼ä¸ºç©º/为 null 表示æå
¨æ°åæ®å¤çï¼ |
| | | */ |
| | | public static String checkJsonForUpdate(String json, Set<String> existingNames) { |
| | | return checkJson(json, existingNames, false); |
| | | } |
| | | |
| | | /** |
| | | * æ¶ééä»¶ JSON éå·²æçæä»¶åï¼å°åãå»ç©ºç½ï¼ï¼ç¨äºè¯å«"åå²å·²åå¨"çéä»¶ã |
| | | */ |
| | | public static Set<String> collectNames(String json) { |
| | | Set<String> names = new HashSet<>(); |
| | | if (json == null || json.trim().isEmpty()) { |
| | | return names; |
| | | } |
| | | try { |
| | | JsonNode root = MAPPER.readTree(json); |
| | | if (root != null && root.isArray()) { |
| | | for (JsonNode node : root) { |
| | | String raw = fieldText(node, "name"); |
| | | if (raw == null) { |
| | | raw = fieldText(node, "fileName"); |
| | | } |
| | | if (raw != null && !raw.trim().isEmpty()) { |
| | | names.add(raw.trim().toLowerCase()); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception ignored) { |
| | | // åå²å段解æå¤±è´¥å°±å½ä½"没æåå²éä»¶"ï¼ä¸å è§£æé®é¢å½±åä¿å |
| | | } |
| | | return names; |
| | | } |
| | | |
| | | /** |
| | | * @param existingNames åå²éä»¶åï¼å°åï¼ï¼å½ä¸åè·³è¿æ ¡éªï¼{@code null} 表示ä¸å¿½ç¥ä»»ä½åå |
| | | * @param strictNoName {@code true}=åä¸å°æä»¶åæ¶æ¦æªï¼å
¨æ°ä¿åï¼ï¼{@code false}=æ¾è¡ï¼ä¿®æ¹åå²åæ®ï¼ |
| | | */ |
| | | private static String checkJson(String json, Set<String> existingNames, boolean strictNoName) { |
| | | if (json == null || json.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | JsonNode root; |
| | | try { |
| | | root = MAPPER.readTree(json); |
| | | } catch (Exception e) { |
| | | // è§£æä¸äºå°±ä¸å¨è¿éæ¦ï¼ä¿åæµç¨èªèº«ä¼å¤çæ ¼å¼é®é¢ï¼ï¼é¿å
è¯¯æ¦ |
| | | return null; |
| | | } |
| | | if (root == null || !root.isArray()) { |
| | | return null; |
| | | } |
| | | Set<String> seen = new HashSet<>(); |
| | | for (JsonNode node : root) { |
| | | String raw = fieldText(node, "name"); |
| | | if (raw == null) { |
| | | raw = fieldText(node, "fileName"); |
| | | } |
| | | if (raw == null || raw.trim().isEmpty()) { |
| | | // æ¢æ²¡æ name 乿²¡æ fileNameï¼æ æ³æ ¸å¯¹éä»¶åã |
| | | // å
¨æ°ä¿åï¼addï¼æ"ä¸è½ä¿å"å¤çï¼ä¿®æ¹åå²åæ®æ¶æ¾è¡ï¼é¿å
å¡ä½èæ°æ® |
| | | if (strictNoName) { |
| | | return MSG_NO_NAME; |
| | | } |
| | | continue; |
| | | } |
| | | String name = raw.trim(); |
| | | if (isUploadPath(name)) { |
| | | // 该é件项åçæ¯"ä¸ä¼ åçåå¨è·¯å¾"ï¼å½¢å¦ /profile/upload/2026/10/15/xxx.pdfï¼ï¼ |
| | | // 䏿¯æä½äººå½åçæä»¶åï¼æ æ³æ®æ¤å¤æå½åæ¯å¦è§è â è·³è¿æ ¡éªï¼é¿å
误æ¦åæ³åæ®ã |
| | | // å¾
å端æ¹ä¸ºä¸ä¼ æ¶è®°å½åå§æä»¶åï¼file.raw.nameï¼åï¼è¿äºé¡¹ä¼èªå¨çº³å
¥æ ¡éªã |
| | | continue; |
| | | } |
| | | if (existingNames != null && existingNames.contains(name.toLowerCase())) { |
| | | // åå²éä»¶ï¼ä¿ååå·²åå¨ï¼ä¸å䏿 ¡éªï¼ä¸å¡ç¡®è®¤"åªæ ¡éªæ¬æ¬¡åæ´çéä»¶"ï¼é¿å
å¡ä½èåæ® |
| | | continue; |
| | | } |
| | | String reason = checkName(name); |
| | | if (reason != null) { |
| | | return "ã" + name + "ã" + reason; |
| | | } |
| | | String key = name.toLowerCase(); |
| | | if (!seen.add(key)) { |
| | | return "ã" + name + "ã" + MSG_DUPLICATE; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** 读åé件项éçåç¬¦ä¸²åæ®µï¼åä¸å°ï¼ç¼ºå¤±/为空/nullï¼è¿å {@code null} */ |
| | | private static String fieldText(JsonNode node, String field) { |
| | | if (node == null) { |
| | | return null; |
| | | } |
| | | JsonNode value = node.get(field); |
| | | if (value == null || value.isNull()) { |
| | | return null; |
| | | } |
| | | String text = value.asText(); |
| | | return text == null || text.trim().isEmpty() ? null : text; |
| | | } |
| | | |
| | | /** 夿é件项éç name æ¯å¦ä¸º"ä¸ä¼ åçåå¨è·¯å¾ / URL"ï¼è䏿¯æä½äººå½åçæä»¶åï¼ */ |
| | | private static boolean isUploadPath(String name) { |
| | | String lower = name.toLowerCase(); |
| | | return name.startsWith("/") || name.startsWith("http") || name.contains("/") || name.contains("\\") |
| | | || lower.contains("profile/upload"); |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªå䏿ç»è¡ä¸çå¤ä¸ªéä»¶å表ï¼å¦å票 + å
¶å®éä»¶ï¼ï¼é个å表ç¬ç«æ ¡éªï¼ |
| | | * æ¯ä¸ªå表å
鍿¥éåï¼ä¸å端 checkDetails çå£å¾ä¸è´ï¼ã |
| | | * |
| | | * @param jsons éä»¶åæ®µå
容ï¼å¯ä¸º null/空 |
| | | * @return {@code null}=éè¿ï¼å¦åè¿åä¸åæ ¼åå |
| | | */ |
| | | public static String checkJsonList(String... jsons) { if (jsons == null) { |
| | | return null; |
| | | } |
| | | for (String json : jsons) { |
| | | String reason = checkJson(json); |
| | | if (reason != null) { |
| | | return reason; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** ä¿®æ¹ä¿åæ¶ä½¿ç¨ï¼å¿½ç¥åå²éä»¶åï¼é个åè¡¨æ ¡éªï¼ä¾"å¤ä¸ªå段ä¸èµ·æ ¡éª"çè°ç¨ç¹ï¼ */ |
| | | public static String checkJsonListForUpdate(Set<String> existingNames, String... jsons) { |
| | | if (jsons == null) { |
| | | return null; |
| | | } |
| | | for (String json : jsons) { |
| | | String reason = checkJsonForUpdate(json, existingNames); |
| | | if (reason != null) { |
| | | return reason; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | } |