package com.doumee.core.haikang.isapi; import com.doumee.core.haikang.isapi.model.DeviceInfoDTO; import com.doumee.core.haikang.isapi.model.MediaItemDTO; import com.doumee.core.haikang.isapi.model.RecordTrackDTO; import com.doumee.core.haikang.isapi.model.SearchPageResult; import com.doumee.core.haikang.isapi.model.StorageInfoDTO; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * ISAPI XML 响应解析 */ public class IsapiXmlParser { private static final Logger log = LoggerFactory.getLogger(IsapiXmlParser.class); private IsapiXmlParser() { } public static DeviceInfoDTO parseDeviceInfo(String xml) { if (StringUtils.isBlank(xml)) { return null; } DeviceInfoDTO dto = new DeviceInfoDTO(); dto.setDeviceName(extractTag(xml, "deviceName")); dto.setDeviceId(extractTag(xml, "deviceID")); dto.setModel(extractTag(xml, "model")); dto.setSerialNumber(extractTag(xml, "serialNumber")); dto.setFirmwareVersion(extractTag(xml, "firmwareVersion")); dto.setDeviceType(extractTag(xml, "deviceType")); return dto; } public static StorageInfoDTO parseStorage(String xml) { if (StringUtils.isBlank(xml)) { return null; } StorageInfoDTO dto = new StorageInfoDTO(); long total = 0; long free = 0; try { Document doc = parseDocument(xml); NodeList hddNodes = doc.getElementsByTagName("hdd"); for (int i = 0; i < hddNodes.getLength(); i++) { Element hdd = (Element) hddNodes.item(i); total += parseLong(getChildText(hdd, "capacity")); free += parseLong(getChildText(hdd, "freeSpace")); } } catch (Exception e) { total = parseLong(extractTag(xml, "capacity")); free = parseLong(extractTag(xml, "freeSpace")); } if (total > 0) { dto.setTotalSpaceGb(total); dto.setFreeSpaceGb(free); } return dto; } public static List parseRecordTracks(String xml) { List list = new ArrayList<>(); if (StringUtils.isBlank(xml)) { return list; } try { Document doc = parseDocument(xml); NodeList trackNodes = doc.getElementsByTagName("Track"); if (trackNodes.getLength() == 0) { trackNodes = doc.getElementsByTagName("track"); } for (int i = 0; i < trackNodes.getLength(); i++) { Element track = (Element) trackNodes.item(i); String id = getChildText(track, "id"); if (StringUtils.isBlank(id)) { id = getChildText(track, "trackID"); } if (StringUtils.isBlank(id)) { continue; } RecordTrackDTO dto = new RecordTrackDTO(); dto.setId(id.trim()); dto.setChannel(getChildText(track, "channel")); dto.setStreamType(resolveStreamType(id)); list.add(dto); } } catch (Exception ignored) { } if (list.isEmpty()) { Matcher m = Pattern.compile("(\\d+)").matcher(xml); while (m.find()) { RecordTrackDTO dto = new RecordTrackDTO(); dto.setId(m.group(1)); dto.setStreamType(resolveStreamType(dto.getId())); list.add(dto); } } return list; } public static SearchPageResult parseSearchPage(String xml) { SearchPageResult page = new SearchPageResult(); if (StringUtils.isBlank(xml)) { return page; } String normalized = normalizeXml(xml); if (normalized.contains("statusCode") && normalized.contains("")) { String statusCode = firstNonBlank(extractTagIgnoreCase(normalized, "statusCode"), extractTag(normalized, "statusCode")); if (StringUtils.isNotBlank(statusCode) && !"1".equals(statusCode.trim()) && !"0".equals(statusCode.trim())) { log.warn("ISAPI search 返回错误 statusCode={} statusString={} subStatusCode={}", statusCode, firstNonBlank(extractTagIgnoreCase(normalized, "statusString"), extractTag(normalized, "statusString")), firstNonBlank(extractTagIgnoreCase(normalized, "subStatusCode"), extractTag(normalized, "subStatusCode"))); } } page.setResponseStatusStrg(firstNonBlank( extractTagIgnoreCase(normalized, "responseStatusStrg"), extractTag(normalized, "responseStatusStrg"))); page.setNumOfMatches((int) parseLong(firstNonBlank( extractTagIgnoreCase(normalized, "numOfMatches"), extractTag(normalized, "numOfMatches"), extractTagIgnoreCase(normalized, "totalMatches"), extractTag(normalized, "totalMatches")))); page.setHasMore(IsapiConstants.SEARCH_STATUS_MORE.equalsIgnoreCase(page.getResponseStatusStrg())); page.setItems(parseSearchResult(normalized)); if (page.getItems().isEmpty() && page.getNumOfMatches() > 0) { log.warn("ISAPI search numOfMatches={} 但解析 items 为空", page.getNumOfMatches()); } return page; } public static List parseSearchResult(String xml) { List list = new ArrayList<>(); if (StringUtils.isBlank(xml)) { return list; } String normalized = normalizeXml(xml); if (normalized.startsWith("{")) { return parseSearchResultJson(normalized); } try { Document doc = parseDocument(normalized); collectSearchMatchItems(doc.getDocumentElement(), list); } catch (Exception e) { log.warn("ISAPI search DOM解析失败,尝试正则: {}", e.getMessage()); } if (list.isEmpty()) { collectSearchMatchItemsByRegex(normalized, list); } if (list.isEmpty() && (normalized.toLowerCase().contains("searchmatchitem") || normalized.toLowerCase().contains("matchelement"))) { log.warn("ISAPI search 含匹配项但解析为空,response={}", StringUtils.abbreviate(normalized, 500)); } return list; } private static final String[] MATCH_ITEM_TAGS = {"searchMatchItem", "matchElement", "matchItem"}; private static void collectSearchMatchItems(Element root, List list) { if (root == null) { return; } for (String tag : MATCH_ITEM_TAGS) { NodeList nodes = getElementsByLocalName(root, tag); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } addParsedMedia(list, parseMatchItem((Element) node)); } if (!list.isEmpty()) { return; } } } private static void collectSearchMatchItemsByRegex(String xml, List list) { for (String tag : MATCH_ITEM_TAGS) { Matcher matcher = Pattern.compile( "(?is)<(?:\\w+:)?" + tag + "\\b[^>]*>(.*?)").matcher(xml); while (matcher.find()) { addParsedMedia(list, parseMatchItemFromBlock(matcher.group(1))); } if (!list.isEmpty()) { return; } } } private static void addParsedMedia(List list, MediaItemDTO media) { if (media == null) { return; } if (StringUtils.isBlank(media.getFileIndex())) { String fallback = buildFileIndexFallback(media); if (StringUtils.isNotBlank(fallback)) { media.setFileIndex(fallback); if (StringUtils.isBlank(media.getFileName())) { media.setFileName(fallback); } } } if (StringUtils.isNotBlank(media.getFileIndex()) || StringUtils.isNotBlank(media.getPlaybackUri())) { list.add(media); } } private static List parseSearchResultJson(String json) { List list = new ArrayList<>(); try { com.alibaba.fastjson.JSONObject root = com.alibaba.fastjson.JSONObject.parseObject(json); com.alibaba.fastjson.JSONObject searchResult = root.getJSONObject("CMSearchResult"); if (searchResult != null) { root = searchResult; } com.alibaba.fastjson.JSONArray matchList = extractJsonMatchList(root); if (matchList == null || matchList.isEmpty()) { return list; } for (int i = 0; i < matchList.size(); i++) { com.alibaba.fastjson.JSONObject item = matchList.getJSONObject(i); if (item == null) { continue; } com.alibaba.fastjson.JSONObject desc = item.getJSONObject("MediaSegmentDescriptor"); if (desc == null) { desc = item.getJSONObject("mediaSegmentDescriptor"); } if (desc == null) { desc = item.getJSONObject("recordSegmentDescriptor"); } if (desc == null) { desc = item; } MediaItemDTO dto = new MediaItemDTO(); dto.setTrackId(firstNonBlank(item.getString("trackID"), item.getString("trackId"))); com.alibaba.fastjson.JSONObject timeSpan = item.getJSONObject("timeSpan"); dto.setStartTime(parseIsapiTime(firstNonBlank(item.getString("startTime"), timeSpan != null ? timeSpan.getString("startTime") : null))); dto.setEndTime(parseIsapiTime(firstNonBlank(item.getString("endTime"), timeSpan != null ? timeSpan.getString("endTime") : null))); dto.setPlaybackUri(firstNonBlank(desc.getString("playbackURI"), desc.getString("playbackUri"), desc.getString("downloadURI"), desc.getString("fileUrl"))); dto.setContentType(firstNonBlank(desc.getString("contentType"), desc.getString("contenType"))); dto.setFileSize(desc.getLong("size")); dto.setFileName(firstNonBlank(desc.getString("name"), desc.getString("fileName"), desc.getString("mediaID"), item.getString("sourceID"))); dto.setUserName(firstNonBlank(desc.getString("policeName"), item.getString("policeName"))); dto.setRecorderSn(firstNonBlank(desc.getString("bodyCameraShortSN"), desc.getString("recorderCode"), desc.getString("bodyCameraShortSN"))); if (StringUtils.isBlank(dto.getFileName())) { dto.setFileName(extractNameFromUri(dto.getPlaybackUri())); } applyFileIdentity(dto, desc.getString("mediaID")); dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName())); addParsedMedia(list, dto); } } catch (Exception e) { log.warn("ISAPI search JSON解析失败: {}", e.getMessage()); } return list; } private static com.alibaba.fastjson.JSONArray extractJsonMatchList(com.alibaba.fastjson.JSONObject root) { com.alibaba.fastjson.JSONArray matchList = root.getJSONArray("MatchList"); if (matchList == null) { matchList = root.getJSONArray("matchList"); } if (matchList != null) { return matchList; } com.alibaba.fastjson.JSONObject matchObj = root.getJSONObject("MatchList"); if (matchObj == null) { matchObj = root.getJSONObject("matchList"); } if (matchObj == null) { return null; } com.alibaba.fastjson.JSONArray items = matchObj.getJSONArray("SearchMatchItem"); if (items == null) { items = matchObj.getJSONArray("searchMatchItem"); } if (items == null) { items = matchObj.getJSONArray("matchElement"); } if (items == null) { items = matchObj.getJSONArray("MatchElement"); } if (items != null) { return items; } com.alibaba.fastjson.JSONObject single = matchObj.getJSONObject("SearchMatchItem"); if (single == null) { single = matchObj.getJSONObject("searchMatchItem"); } if (single == null) { single = matchObj.getJSONObject("matchElement"); } if (single == null) { single = matchObj.getJSONObject("MatchElement"); } if (single != null) { com.alibaba.fastjson.JSONArray arr = new com.alibaba.fastjson.JSONArray(); arr.add(single); return arr; } return null; } private static MediaItemDTO parseMatchItemFromBlock(String block) { MediaItemDTO dto = new MediaItemDTO(); dto.setTrackId(extractTagIgnoreCase(block, "trackID")); dto.setStartTime(parseIsapiTime(firstNonBlank( extractTagIgnoreCase(block, "startTime"), extractNestedTagIgnoreCase(block, "timeSpan", "startTime")))); dto.setEndTime(parseIsapiTime(firstNonBlank( extractTagIgnoreCase(block, "endTime"), extractNestedTagIgnoreCase(block, "timeSpan", "endTime")))); String descBlock = firstNonBlank( extractBlockIgnoreCase(block, "mediaSegmentDescriptor"), extractBlockIgnoreCase(block, "recordSegmentDescriptor"), block); dto.setPlaybackUri(firstNonBlank( extractTagIgnoreCase(descBlock, "playbackURI"), extractTagIgnoreCase(descBlock, "playbackUri"), extractTagIgnoreCase(descBlock, "downloadURI"), extractTagIgnoreCase(descBlock, "fileUrl"))); dto.setContentType(firstNonBlank( extractTagIgnoreCase(descBlock, "contentType"), extractTagIgnoreCase(descBlock, "contenType"))); dto.setFileSize(parseLong(firstNonBlank( extractTagIgnoreCase(descBlock, "size"), extractSizeFromUri(dto.getPlaybackUri())))); String mediaId = extractTagIgnoreCase(descBlock, "mediaID"); dto.setFileName(firstNonBlank( extractTagIgnoreCase(descBlock, "name"), extractTagIgnoreCase(descBlock, "fileName"), mediaId, extractTagIgnoreCase(block, "sourceID"))); dto.setUserName(firstNonBlank( extractTagIgnoreCase(descBlock, "policeName"), extractTagIgnoreCase(block, "policeName"))); dto.setRecorderSn(firstNonBlank( extractTagIgnoreCase(descBlock, "bodyCameraShortSN"), extractTagIgnoreCase(descBlock, "recorderCode"), extractTagIgnoreCase(block, "bodyCameraShortSN"), extractTagIgnoreCase(block, "recorderCode"), extractTagIgnoreCase(descBlock, "shortSerialNumber"))); if (StringUtils.isBlank(dto.getFileName())) { dto.setFileName(extractNameFromUri(dto.getPlaybackUri())); } applyFileIdentity(dto, mediaId); dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName())); return dto; } private static void applyFileIdentity(MediaItemDTO dto, String mediaId) { if (StringUtils.isBlank(dto.getFileIndex())) { dto.setFileIndex(firstNonBlank( mediaId, StringUtils.isNoneBlank(dto.getTrackId(), dto.getFileName()) ? dto.getTrackId() + "_" + dto.getFileName() : null, dto.getFileName())); } } private static String resolveContentType(Element desc) { return firstNonBlank(getChildText(desc, "contentType"), getChildText(desc, "contenType")); } public static String parseSecurityToken(String json) { if (StringUtils.isBlank(json)) { return null; } Matcher m = Pattern.compile("\"value\"\\s*:\\s*\"([^\"]+)\"").matcher(json); if (m.find()) { return m.group(1); } return null; } private static MediaItemDTO parseMatchItem(Element item) { MediaItemDTO dto = new MediaItemDTO(); dto.setTrackId(firstNonBlank( getChildText(item, "trackID"), getChildText(item, "trackId"), getChildText(item, "trackid"))); Element timeSpan = findFirstChildElement(item, "timeSpan"); if (timeSpan != null) { dto.setStartTime(parseIsapiTime(getChildText(timeSpan, "startTime"))); dto.setEndTime(parseIsapiTime(getChildText(timeSpan, "endTime"))); } else { dto.setStartTime(parseIsapiTime(getChildText(item, "startTime"))); dto.setEndTime(parseIsapiTime(getChildText(item, "endTime"))); } Element desc = findDescriptorElement(item); dto.setPlaybackUri(firstNonBlank( getChildText(desc, "playbackURI"), getChildText(desc, "playbackUri"), getChildText(desc, "downloadURI"), getChildText(desc, "fileUrl"), getChildText(item, "playbackURI"), getChildText(item, "downloadURI"))); dto.setContentType(resolveContentType(desc)); dto.setFileSize(parseLong(firstNonBlank( getChildText(desc, "size"), getChildText(desc, "fileSize"), extractSizeFromUri(dto.getPlaybackUri())))); String mediaId = getChildText(desc, "mediaID"); String name = firstNonBlank( getChildText(desc, "name"), getChildText(desc, "fileName"), getChildText(item, "name"), getChildText(item, "fileName"), mediaId, getChildText(item, "sourceID")); if (StringUtils.isBlank(name)) { name = extractNameFromUri(dto.getPlaybackUri()); } dto.setFileName(name); applyFileIdentity(dto, mediaId); dto.setUserName(firstNonBlank( getChildText(desc, "policeName"), getChildText(item, "policeName"), getChildText(desc, "userName"))); dto.setRecorderSn(firstNonBlank( getChildText(desc, "bodyCameraShortSN"), getChildText(desc, "recorderCode"), getChildText(item, "bodyCameraShortSN"), getChildText(item, "recorderCode"), getChildText(desc, "shortSerialNumber"), getChildText(item, "shortSerialNumber"))); dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName())); return dto; } private static Element findDescriptorElement(Element item) { NodeList descriptors = getElementsByLocalName(item, "mediaSegmentDescriptor"); if (descriptors.getLength() == 0) { descriptors = getElementsByLocalName(item, "recordSegmentDescriptor"); } if (descriptors.getLength() == 0) { descriptors = getElementsByLocalName(item, "segmentDescriptor"); } return descriptors.getLength() > 0 ? (Element) descriptors.item(0) : item; } private static Element findFirstChildElement(Element parent, String localName) { if (parent == null) { return null; } NodeList nodes = parent.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } Element el = (Element) node; String name = el.getLocalName() != null ? el.getLocalName() : el.getNodeName(); if (localName.equalsIgnoreCase(name)) { return el; } } return null; } private static int resolveStreamType(String trackId) { if (StringUtils.isBlank(trackId) || trackId.length() < 3) { return 0; } char last = trackId.charAt(trackId.length() - 1); if (last == '3') { return 2; } if (last == '2') { return 1; } return 0; } private static int resolveMediaType(String contentType, String trackId, String fileName) { if (StringUtils.isNotBlank(contentType)) { String lower = contentType.toLowerCase(); if (lower.contains("picture") || lower.contains("image") || lower.equals("metadata")) { return 1; } if (lower.contains("audio")) { return 2; } if (lower.contains("video")) { return 0; } } if (StringUtils.isNotBlank(fileName)) { String lower = fileName.toLowerCase(); if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png") || lower.endsWith(".bmp")) { return 1; } if (lower.endsWith(".mp3") || lower.endsWith(".wav") || lower.endsWith(".aac")) { return 2; } if (lower.endsWith(".mp4") || lower.endsWith(".avi") || lower.endsWith(".mov") || lower.endsWith(".mkv")) { return 0; } } if (StringUtils.isNotBlank(trackId) && trackId.endsWith("3")) { return 1; } return 0; } private static int resolveMediaType(String contentType, String trackId) { return resolveMediaType(contentType, trackId, null); } private static String extractNameFromUri(String uri) { if (StringUtils.isBlank(uri)) { return null; } String normalized = uri.replace("&", "&"); Matcher m = Pattern.compile("[?&](?:name|filename|fileName)=([^&\\s]+)", Pattern.CASE_INSENSITIVE).matcher(normalized); if (m.find()) { try { return URLDecoder.decode(m.group(1), StandardCharsets.UTF_8.name()); } catch (Exception e) { return m.group(1); } } int slash = normalized.lastIndexOf('/'); if (slash >= 0 && slash < normalized.length() - 1) { String tail = normalized.substring(slash + 1); int q = tail.indexOf('?'); if (q > 0) { tail = tail.substring(0, q); } if (StringUtils.isNotBlank(tail) && !tail.equals("tracks")) { return tail; } } return null; } private static String buildFileIndexFallback(MediaItemDTO dto) { if (StringUtils.isNotBlank(dto.getPlaybackUri())) { return String.valueOf(Math.abs(dto.getPlaybackUri().hashCode())); } if (dto.getStartTime() != null) { return StringUtils.defaultString(dto.getTrackId(), "track") + "_" + dto.getStartTime().getTime(); } return null; } private static String getDirectChildText(Element parent, String tag) { if (parent == null) { return null; } NodeList nodes = parent.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE && tag.equalsIgnoreCase(node.getNodeName())) { return node.getTextContent(); } } return null; } private static NodeList getElementsByLocalName(Element root, String localName) { NodeList nodes = root.getElementsByTagNameNS("*", localName); if (nodes.getLength() > 0) { return nodes; } nodes = root.getElementsByTagName(localName); if (nodes.getLength() > 0) { return nodes; } return findElementsByLocalNameIgnoreCase(root, localName); } private static NodeList findElementsByLocalNameIgnoreCase(Element root, String localName) { java.util.List matched = new ArrayList<>(); collectElementsByLocalNameIgnoreCase(root, localName.toLowerCase(), matched); return new NodeListWrapper(matched); } private static void collectElementsByLocalNameIgnoreCase(Element element, String localNameLower, java.util.List matched) { if (localNameLower.equals(element.getLocalName() != null ? element.getLocalName().toLowerCase() : element.getNodeName().toLowerCase())) { matched.add(element); } NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE) { collectElementsByLocalNameIgnoreCase((Element) child, localNameLower, matched); } } } private static String normalizeXml(String xml) { String text = stripBom(StringUtils.defaultString(xml).trim()); if (text.startsWith("\"") && text.endsWith("\"")) { text = text.substring(1, text.length() - 1); } if (text.startsWith("<") || text.startsWith("<")) { text = text.replace("<", "<").replace(">", ">").replace(""", "\""); } return text; } private static String extractSizeFromUri(String uri) { if (StringUtils.isBlank(uri)) { return null; } Matcher matcher = Pattern.compile("[?&;]size=([0-9]+)", Pattern.CASE_INSENSITIVE) .matcher(uri.replace("&", "&")); return matcher.find() ? matcher.group(1) : null; } private static String stripBom(String text) { if (text != null && text.startsWith("\uFEFF")) { return text.substring(1); } return text; } private static String extractTagIgnoreCase(String xml, String tag) { Pattern pattern = Pattern.compile( "(?is)<(?:\\w+:)?" + Pattern.quote(tag) + "\\b[^>]*>([^<]*)"); Matcher matcher = pattern.matcher(xml); return matcher.find() ? matcher.group(1).trim() : null; } private static String extractNestedTagIgnoreCase(String xml, String parentTag, String childTag) { String parentBlock = extractBlockIgnoreCase(xml, parentTag); if (StringUtils.isBlank(parentBlock)) { return null; } return extractTagIgnoreCase(parentBlock, childTag); } private static String extractBlockIgnoreCase(String xml, String tag) { Pattern pattern = Pattern.compile( "(?is)<(?:\\w+:)?" + Pattern.quote(tag) + "\\b[^>]*>(.*?)"); Matcher matcher = pattern.matcher(xml); return matcher.find() ? matcher.group(1) : null; } /** 兼容命名空间 XML 的 NodeList */ private static class NodeListWrapper implements NodeList { private final java.util.List elements; NodeListWrapper(java.util.List elements) { this.elements = elements; } @Override public Node item(int index) { return elements.get(index); } @Override public int getLength() { return elements.size(); } } private static Document parseDocument(String xml) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); factory.setNamespaceAware(true); return factory.newDocumentBuilder() .parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); } private static String getChildText(Element parent, String tag) { if (parent == null) { return null; } NodeList nodes = getElementsByLocalName(parent, tag); if (nodes.getLength() == 0) { return null; } return nodes.item(0).getTextContent(); } private static String extractTag(String xml, String tag) { Matcher m = Pattern.compile("<" + tag + ">([^<]*)").matcher(xml); if (m.find()) { return m.group(1).trim(); } return null; } private static long parseLong(String val) { if (StringUtils.isBlank(val)) { return 0; } try { return Long.parseLong(val.trim()); } catch (NumberFormatException e) { return 0; } } private static Date parseIsapiTime(String timeStr) { if (StringUtils.isBlank(timeStr)) { return null; } String[] patterns = { "yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ssXXX", "yyyy-MM-dd'T'HH:mm:ss", "yyyyMMdd'T'HHmmss'Z'", "yyyyMMdd'T'HHmmss" }; for (String pattern : patterns) { try { SimpleDateFormat sdf = new SimpleDateFormat(pattern); if (pattern.endsWith("'Z'")) { sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); } return sdf.parse(timeStr.trim()); } catch (Exception ignored) { } } return null; } private static String firstNonBlank(String... values) { for (String v : values) { if (StringUtils.isNotBlank(v)) { return v; } } return null; } }