封裝 HttpServletRequest.getHeader("User-Agent") 解析邏輯,提供結構化物件:
company):Microsoft, Opera Software, Netscape Communications, unknownname):Microsoft Internet Explorer, Netscape Navigator, Opera, unknownversion)、主版本 (mainVersion)、次版本 (minorVersion)os):Windows 95/98/NT/3.x (舊版範例,現代需擴充)language):依 Accept-Language Header 解析Locale 物件:供國際化使用package com.example.util;
import java.util.*;
import javax.servlet.http.*;
public class Browser {
private HttpServletRequest request;
private HttpSession session;
private String userAgent;
private String company;
private String name;
private String version;
private String mainVersion;
private String minorVersion;
private String os;
private String language = "zh-TW"; // 預設繁體中文
private Locale locale;
private final Map<String, String> supportedLanguages = new HashMap<>();
public Browser(HttpServletRequest request, HttpSession session) {
this.request = request;
this.session = session;
initSupportedLanguages();
parseUserAgent();
parseLanguage();
buildLocale();
}
private void initSupportedLanguages() {
supportedLanguages.put("en", "English");
supportedLanguages.put("zh-TW", "繁體中文");
supportedLanguages.put("zh-CN", "简体中文");
supportedLanguages.put("ja", "日本語");
supportedLanguages.put("ko", "한국어");
}
private void parseUserAgent() {
userAgent = request.getHeader("User-Agent");
if (userAgent == null) userAgent = "";
userAgent = userAgent.toLowerCase();
// 廠商與瀏覽器判斷 (順序重要:較特定者優先)
if (userAgent.contains("edg/")) { // Microsoft Edge (Chromium)
company = "Microsoft"; name = "Microsoft Edge";
extractVersion("edg/");
} else if (userAgent.contains("msie") || userAgent.contains("trident/")) { // IE 11-
company = "Microsoft"; name = "Microsoft Internet Explorer";
extractVersion(userAgent.contains("msie") ? "msie" : "rv:");
} else if (userAgent.contains("opera") || userAgent.contains("opr/")) {
company = "Opera Software"; name = "Opera";
extractVersion(userAgent.contains("opr/") ? "opr/" : "opera");
} else if (userAgent.contains("chrome")) {
company = "Google"; name = "Chrome";
extractVersion("chrome/");
} else if (userAgent.contains("firefox")) {
company = "Mozilla Foundation"; name = "Firefox";
extractVersion("firefox/");
} else if (userAgent.contains("safari")) {
company = "Apple"; name = "Safari";
extractVersion("version/");
} else if (userAgent.contains("mozilla")) {
company = "Netscape Communications"; name = "Netscape Navigator";
extractVersion("mozilla/");
} else {
company = "unknown"; name = "unknown"; version = "0";
}
parseOS();
splitVersion();
}
private void extractVersion(String key) {
int idx = userAgent.indexOf(key);
if (idx != -1) {
String sub = userAgent.substring(idx + key.length());
int end = sub.indexOf(' ');
if (end == -1) end = sub.indexOf(';');
if (end == -1) end = sub.length();
version = sub.substring(0, end).trim();
} else {
version = "0";
}
}
private void splitVersion() {
int dot = version.indexOf('.');
if (dot != -1) {
mainVersion = version.substring(0, dot);
minorVersion = version.substring(dot + 1);
} else {
mainVersion = version;
minorVersion = "0";
}
}
private void parseOS() {
if (userAgent.contains("windows nt 10.0") || userAgent.contains("windows nt 6.3")) os = "Windows 10/11";
else if (userAgent.contains("windows nt 6.2")) os = "Windows 8";
else if (userAgent.contains("windows nt 6.1")) os = "Windows 7";
else if (userAgent.contains("windows nt 6.0")) os = "Windows Vista";
else if (userAgent.contains("windows nt 5.1") || userAgent.contains("windows nt 5.2")) os = "Windows XP/Server 2003";
else if (userAgent.contains("win98")) os = "Windows 98";
else if (userAgent.contains("win95")) os = "Windows 95";
else if (userAgent.contains("win16") || userAgent.contains("windows 3.")) os = "Windows 3.x";
else if (userAgent.contains("mac os x")) os = "macOS";
else if (userAgent.contains("linux")) os = "Linux";
else if (userAgent.contains("android")) os = "Android";
else if (userAgent.contains("iphone") || userAgent.contains("ipad")) os = "iOS";
else os = "unknown";
}
private void parseLanguage() {
String acceptLang = request.getHeader("Accept-Language");
if (acceptLang != null) {
String[] parts = acceptLang.split(",");
for (String part : parts) {
String lang = part.split(";")[0].trim().toLowerCase(); // 取 q-value 前
if (supportedLanguages.containsKey(lang)) {
language = lang;
break; // 取優先度最高的支援語言
}
}
}
}
private void buildLocale() {
String[] seg = language.split("-");
locale = (seg.length == 2) ? new Locale(seg[0], seg[1]) : new Locale(language);
}
// ---- Getters ----
public String getCompany() { return company; }
public String getName() { return name; }
public String getVersion() { return version; }
public String getMainVersion() { return mainVersion; }
public String getMinorVersion() { return minorVersion; }
public String getOs() { return os; }
public String getLanguage() { return language; }
public Locale getLocale() { return locale; }
}
<%@ page import="com.example.util.Browser" %>
<%
Browser browser = new Browser(request, session);
%>
<html><body>
<h2>客戶端環境資訊</h2>
<table border="1">
<tr><th>屬性</th><th>值</th></tr>
<tr><td>廠商</td><td><%=browser.getCompany()%></td></tr>
<tr><td>瀏覽器</td><td><%=browser.getName()%></td></tr>
<tr><td>版本</td><td><%=browser.getVersion()%> (主: <%=browser.getMainVersion()%>, 次: <%=browser.getMinorVersion()%>)</td></tr>
<tr><td>作業系統</td><td><%=browser.getOs()%></td></tr>
<tr><td>偏好語言</td><td><%=browser.getLanguage()%></td></tr>
<tr><td>Locale</td><td><%=browser.getLocale()%></td></tr>
<tr><td>原始 User-Agent</td><td><%=request.getHeader("User-Agent")%></td></tr>
</table>
</body></html>
ConcurrentHashMap<String, Browser> 或 Guava Cache 緩存。DeviceDetector 判斷行動裝置/平板/桌機、品牌/型號。@Component 或 HandlerInterceptor 將 Browser 物件放入 request 屬性,Controller 直接注入使用。解析 User-Agent 是 Web 分析、相容性處理、個性化內容的基礎需求。雖然手寫解析器可理解原理,但 生產環境強烈建議使用成熟開源庫,避免瀏覽器版本迭代導致邏輯失效。本文提供的 Browser Bean 結構清晰、擴充性佳,可作為整合第三方庫的介面適配層。