抛出异常的处理

main
kangwenjing 2026-04-09 15:50:56 +08:00
parent 2709171839
commit 3bea7ab015
2 changed files with 144 additions and 14 deletions

View File

@ -20,12 +20,16 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
public class OmsClient { public class OmsClient {
private static final Set<String> SUCCESS_CODES = Set.of("0", "200", "success", "SUCCESS"); private static final Set<String> SUCCESS_CODES = Set.of("0", "200", "success", "SUCCESS");
private static final Pattern HTML_TITLE_PATTERN = Pattern.compile("<title[^>]*>(.*?)</title>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private static final Pattern HTML_HEADING_PATTERN = Pattern.compile("<h[1-6][^>]*>(.*?)</h[1-6]>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private static final List<String> PROJECT_CODE_FIELDS = List.of( private static final List<String> PROJECT_CODE_FIELDS = List.of(
"project_code", "project_code",
"projectCode", "projectCode",
@ -207,16 +211,22 @@ public class OmsClient {
private JsonNode sendRequest(HttpRequest request) { private JsonNode sendRequest(HttpRequest request) {
try { try {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
String responseBody = response.body();
String contentType = response.headers().firstValue("Content-Type").orElse(null);
if (response.statusCode() < 200 || response.statusCode() >= 300) { if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new BusinessException("OMS接口调用失败HTTP状态码: " + response.statusCode() + ",响应: " + trimBody(response.body())); throw new BusinessException(resolveOmsResponseMessage(response.statusCode(), responseBody, contentType));
} }
JsonNode root = objectMapper.readTree(response.body()); if (!looksLikeJson(contentType, responseBody)) {
throw new BusinessException(resolveOmsResponseMessage(response.statusCode(), responseBody, contentType));
}
JsonNode root = objectMapper.readTree(responseBody);
String code = normalizeText(root.path("code").asText(null)); String code = normalizeText(root.path("code").asText(null));
if (!isSuccessCode(code)) { if (!isSuccessCode(code)) {
String message = firstNonBlank( String message = firstNonBlank(
normalizeText(root.path("msg").asText(null)), normalizeText(root.path("msg").asText(null)),
normalizeText(root.path("message").asText(null)), normalizeText(root.path("message").asText(null)),
trimBody(response.body()), trimBody(responseBody),
"OMS接口调用失败"); "OMS接口调用失败");
throw new BusinessException(message); throw new BusinessException(message);
} }
@ -229,6 +239,74 @@ public class OmsClient {
} }
} }
private boolean looksLikeJson(String contentType, String body) {
String normalizedContentType = normalizeText(contentType);
if (normalizedContentType != null) {
String lowerCaseContentType = normalizedContentType.toLowerCase(Locale.ROOT);
if (lowerCaseContentType.contains("application/json") || lowerCaseContentType.contains("+json")) {
return true;
}
}
String normalizedBody = normalizeText(body);
return normalizedBody != null && (normalizedBody.startsWith("{") || normalizedBody.startsWith("["));
}
private String resolveOmsResponseMessage(int statusCode, String body, String contentType) {
String visibleMessage = firstNonBlank(
extractHtmlErrorMessage(body),
trimBody(body));
if (!isBlank(visibleMessage)) {
return visibleMessage;
}
String normalizedContentType = normalizeText(contentType);
if (!isBlank(normalizedContentType)) {
return "OMS接口调用失败HTTP状态码: " + statusCode + ",响应类型: " + normalizedContentType;
}
return "OMS接口调用失败HTTP状态码: " + statusCode;
}
private String extractHtmlErrorMessage(String body) {
String normalizedBody = normalizeText(body);
if (normalizedBody == null || !normalizedBody.startsWith("<")) {
return null;
}
String heading = extractHtmlFragment(normalizedBody, HTML_HEADING_PATTERN);
String title = extractHtmlFragment(normalizedBody, HTML_TITLE_PATTERN);
String textOnly = normalizeText(
normalizedBody
.replaceAll("(?is)<script[^>]*>.*?</script>", " ")
.replaceAll("(?is)<style[^>]*>.*?</style>", " ")
.replaceAll("(?is)<[^>]+>", " ")
.replace("&nbsp;", " ")
.replaceAll("\\s+", " "));
if (!isBlank(heading)) {
return heading;
}
if (!isBlank(textOnly)) {
if (!isBlank(title) && !textOnly.startsWith(title)) {
return (title + " " + textOnly).trim();
}
return textOnly;
}
return title;
}
private String extractHtmlFragment(String body, Pattern pattern) {
Matcher matcher = pattern.matcher(body);
if (!matcher.find()) {
return null;
}
String content = matcher.group(1);
if (content == null) {
return null;
}
return normalizeText(content.replaceAll("(?is)<[^>]+>", " ").replaceAll("\\s+", " "));
}
private URI buildUri(String path, Map<String, String> queryParams) { private URI buildUri(String path, Map<String, String> queryParams) {
validateConfigured(); validateConfigured();
String baseUrl = normalizeBaseUrl(omsProperties.getBaseUrl()); String baseUrl = normalizeBaseUrl(omsProperties.getBaseUrl());

View File

@ -650,6 +650,64 @@ function handleUnauthorizedResponse() {
window.location.href = `${LOGIN_PATH}?timeout=1`; window.location.href = `${LOGIN_PATH}?timeout=1`;
} }
function tryParseApiBody<T>(rawText: string, contentType?: string | null): (ApiEnvelope<T> & ApiErrorBody) | null {
const normalizedText = rawText.trim();
const normalizedContentType = (contentType || "").toLowerCase();
const looksLikeJson = normalizedContentType.includes("application/json")
|| normalizedContentType.includes("+json")
|| normalizedText.startsWith("{")
|| normalizedText.startsWith("[");
if (!normalizedText || !looksLikeJson) {
return null;
}
try {
return JSON.parse(normalizedText) as ApiEnvelope<T> & ApiErrorBody;
} catch {
return null;
}
}
function extractHtmlErrorMessage(rawText: string) {
const titleMatch = rawText.match(/<title[^>]*>([^<]+)<\/title>/i);
if (titleMatch?.[1]?.trim()) {
return titleMatch[1].trim();
}
const headingMatch = rawText.match(/<h[1-6][^>]*>([^<]+)<\/h[1-6]>/i);
if (headingMatch?.[1]?.trim()) {
return headingMatch[1].trim();
}
const textOnly = rawText
.replace(/<script[\s\S]*?<\/script>/gi, " ")
.replace(/<style[\s\S]*?<\/style>/gi, " ")
.replace(/<[^>]+>/g, " ")
.replace(/\s+/g, " ")
.trim();
return textOnly || null;
}
function buildApiErrorMessage(rawText: string, status: number, body?: ApiErrorBody | null) {
const directMessage = body?.msg?.trim() || body?.message?.trim();
if (directMessage) {
return directMessage;
}
const normalizedText = rawText.trim();
if (!normalizedText) {
return `请求失败(${status})`;
}
if (normalizedText.startsWith("<")) {
return extractHtmlErrorMessage(normalizedText) || `请求失败(${status})`;
}
return normalizedText.length > 300 ? normalizedText.slice(0, 300) : normalizedText;
}
async function request<T>(input: string, init?: RequestInit, withAuth = false): Promise<T> { async function request<T>(input: string, init?: RequestInit, withAuth = false): Promise<T> {
const headers = new Headers(init?.headers); const headers = new Headers(init?.headers);
if (!headers.has("Content-Type") && init?.body && !(init.body instanceof FormData)) { if (!headers.has("Content-Type") && init?.body && !(init.body instanceof FormData)) {
@ -670,25 +728,19 @@ async function request<T>(input: string, init?: RequestInit, withAuth = false):
throw new Error("登录已失效,请重新登录"); throw new Error("登录已失效,请重新登录");
} }
let body: (ApiEnvelope<T> & ApiErrorBody) | null = null; const rawText = await response.text();
try { const body = tryParseApiBody<T>(rawText, response.headers.get("content-type"));
body = (await response.json()) as ApiEnvelope<T> & ApiErrorBody;
} catch {
if (!response.ok) {
throw new Error(`请求失败(${response.status})`);
}
}
if (!response.ok) { if (!response.ok) {
throw new Error(body?.msg || body?.message || `请求失败(${response.status})`); throw new Error(buildApiErrorMessage(rawText, response.status, body));
} }
if (!body) { if (!body) {
throw new Error("接口返回为空"); throw new Error(rawText.trim() ? buildApiErrorMessage(rawText, response.status, null) : "接口返回为空");
} }
if (body.code !== "0") { if (body.code !== "0") {
throw new Error(body.msg || "请求失败"); throw new Error(buildApiErrorMessage(rawText, response.status, body));
} }
return body.data; return body.data;