1. 概述

在构建 Web 应用时,我们经常需要获取访问设备和浏览器的信息,以便提供优化的用户体验。

User Agent 是一个标识客户端身份的字符串,包含设备、操作系统、浏览器等信息,通过 HTTP 请求头中的 User-Agent 字段发送给服务器。

但 User Agent 字符串结构复杂且多变,解析起来相当棘手。在 Java 生态中,Yauaa (Yet Another UserAgent Analyzer) 库能帮我们简化这个过程。

本教程将探索如何在 Spring Boot 应用中使用 Yauaa 解析 User Agent 字符串,并实现基于设备的路由控制

2. 项目搭建

开始实现前,需要先添加 SDK 依赖并正确配置应用。

2.1. 添加依赖

pom.xml 中添加 yauaa 依赖

<dependency>
    <groupId>nl.basjes.parse.useragent</groupId>
    <artifactId>yauaa</artifactId>
    <version>7.28.1</version>
</dependency>

这个依赖提供了解析和分析 User Agent 请求头所需的核心类。

2.2. 配置 UserAgentAnalyzer Bean

添加依赖后,定义 UserAgentAnalyzer Bean:

private static final int CACHE_SIZE = 1000;

@Bean
public UserAgentAnalyzer userAgentAnalyzer() {
    return UserAgentAnalyzer
      .newBuilder()
      .withCache(CACHE_SIZE)
      // ... 其他配置
      .build();
}

UserAgentAnalyzer 是解析 User Agent 字符串的主入口。

默认使用大小为 10000 的内存缓存,可通过 withCache() 方法调整(如示例所示)。缓存能避免重复解析相同字符串,显著提升性能

3. 探索 UserAgent 字段

定义好 Bean 后,就能用它解析 User Agent 并获取客户端信息。

以编写本教程所用设备的 User Agent 为例:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15

使用 UserAgentAnalyzer 解析并提取所有可用字段:

UserAgent userAgent = userAgentAnalyzer.parse(USER_AGENT_STRING);
userAgent
  .getAvailableFieldNamesSorted()
  .forEach(fieldName -> {
      log.info("{}: {}", fieldName, userAgent.getValue(fieldName));
  });

执行后生成的日志如下:

com.baeldung.Application : DeviceClass: Desktop
com.baeldung.Application : DeviceName: Apple Macintosh
com.baeldung.Application : DeviceBrand: Apple
com.baeldung.Application : DeviceCpu: Intel
com.baeldung.Application : DeviceCpuBits: 64
com.baeldung.Application : OperatingSystemClass: Desktop
com.baeldung.Application : OperatingSystemName: Mac OS
com.baeldung.Application : OperatingSystemVersion: >=10.15.7
com.baeldung.Application : OperatingSystemVersionMajor: >=10.15
com.baeldung.Application : OperatingSystemNameVersion: Mac OS >=10.15.7
com.baeldung.Application : OperatingSystemNameVersionMajor: Mac OS >=10.15
com.baeldung.Application : LayoutEngineClass: Browser
com.baeldung.Application : LayoutEngineName: AppleWebKit
com.baeldung.Application : LayoutEngineVersion: 605.1.15
com.baeldung.Application : LayoutEngineVersionMajor: 605
com.baeldung.Application : LayoutEngineNameVersion: AppleWebKit 605.1.15
com.baeldung.Application : LayoutEngineNameVersionMajor: AppleWebKit 605
com.baeldung.Application : AgentClass: Browser
com.baeldung.Application : AgentName: Safari
com.baeldung.Application : AgentVersion: 17.6
com.baeldung.Application : AgentVersionMajor: 17
com.baeldung.Application : AgentNameVersion: Safari 17.6
com.baeldung.Application : AgentNameVersionMajor: Safari 17
com.baeldung.Application : AgentInformationEmail: Unknown
com.baeldung.Application : WebviewAppName: Unknown
com.baeldung.Application : WebviewAppVersion: ??
com.baeldung.Application : WebviewAppVersionMajor: ??
com.baeldung.Application : WebviewAppNameVersion: Unknown
com.baeldung.Application : WebviewAppNameVersionMajor: Unknown
com.baeldung.Application : NetworkType: Unknown

UserAgent 类提供了设备、操作系统、浏览器等关键信息,可根据业务需求灵活使用。

⚠️ 注意:并非所有字段对所有 User Agent 都可用(日志中的 Unknown?? 即为证)。若不想显示这些无效值,可用 UserAgent 类的 getCleanedAvailableFieldNamesSorted() 方法替代。

4. 实现基于设备的路由

了解了 UserAgent 的可用字段后,我们来实现基于设备的路由功能

假设需求是:仅允许移动设备访问应用,禁止非移动设备访问。可通过检查 User Agent 的 DeviceClass 字段实现。

首先定义支持的移动设备类型列表:

private static final List<String> SUPPORTED_MOBILE_DEVICE_CLASSES = List.of("Mobile", "Tablet", "Phone");

然后创建控制器方法:

@GetMapping("/mobile/home")
public ModelAndView homePage(@RequestHeader(HttpHeaders.USER_AGENT) String userAgentString) {
    UserAgent userAgent = userAgentAnalyzer.parse(userAgentString);
    String deviceClass = userAgent.getValue(UserAgent.DEVICE_CLASS);
    boolean isMobileDevice = SUPPORTED_MOBILE_DEVICE_CLASSES.contains(deviceClass);

    if (isMobileDevice) {
        return new ModelAndView("/mobile-home");
    }
    return new ModelAndView("error/open-in-mobile", HttpStatus.FORBIDDEN);
}

方法逻辑:

  1. 通过 @RequestHeader 获取 User-Agent 字符串
  2. UserAgentAnalyzer 解析字符串
  3. 提取 DEVICE_CLASS 字段并判断是否为移动设备
  4. 若是移动设备返回 /mobile-home 视图
  5. 否则返回错误视图并设置 HTTP 403 状态码

5. 测试设备路由

实现后,MockMvc 测试功能是否正常

private static final String SAFARI_MAC_OS_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15";

mockMvc.perform(get("/mobile/home")
  .header("User-Agent", SAFARI_MAC_OS_USER_AGENT))
  .andExpect(view().name("error/open-in-mobile"))
  .andExpect(status().isForbidden());

模拟 macOS 的 Safari User Agent 请求 /home 接口,验证返回错误视图和 403 状态码。

再用移动设备 User Agent 测试:

private static final String SAFARI_IOS_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1";

mockMvc.perform(get("/mobile/home")
  .header("User-Agent", SAFARI_IOS_USER_AGENT))
  .andExpect(view().name("/mobile-home"))
  .andExpect(status().isOk());

使用 iOS 的 Safari User Agent,验证请求成功并返回正确视图。

6. 优化内存与性能

默认情况下 Yauaa 会解析所有可用字段。如果只需要部分字段,可在 UserAgentAnalyzer Bean 中指定:

UserAgentAnalyzer
  .newBuilder()
  .withField(UserAgent.DEVICE_CLASS)
  // ... 其他配置
  .build();

这里配置只解析 DEVICE_CLASS 字段(设备路由中使用的字段)。需要多个字段时可链式调用多个 withField()强烈推荐此方式,能有效减少内存占用并提升性能

7. 总结

本文介绍了使用 Yauaa 解析和分析 User Agent 字符串的方法,包括:

  • ✅ 必要的配置步骤
  • ✅ 可用字段的详细说明
  • ✅ 基于设备的路由实现
  • ✅ 性能优化技巧

文中的所有代码示例可在 GitHub 获取。


原始标题:User Agent Parsing Using Yauaa | Baeldung