UserAgent包名识别工具

news2025/6/9 3:52:33

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 背景
  • 具体实现


背景

为了更准确地分析用户下单行为的来源渠道,并实现精细化运营与风险控制,我们希望在用户下单时,能够通过请求中的 User-Agent(UA)信息 提取其使用的客户端 App 的包名(Package Name)

具体实现

import com.ejoined.commons.plugin.utils.StringUtils;
import java.net.URLDecoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * UserAgent包名识别工具
 */
public class ComprehensiveUserAgentParserUtil {
    // 应用分类枚举
    private enum AppCategory {
        SOCIAL, ECOMMERCE, FOOD_DELIVERY, VIDEO, UTILITY
    }

    // 预定义应用库(关键词 -> 应用信息)
    private static final Map<String, AppInfo> PREDEFINED_APP_LIBRARY = new HashMap<>();

    // 浏览器包名集合(用于误匹配过滤)
    private static final Set<String> BROWSER_PACKAGE_SET = new HashSet<>(Arrays.asList(
            "com.android.chrome", "com.UCMobile", "com.tencent.mtt", "com.apple.mobilesafari"
    ));

    static {
        // 社交类应用注册(支持中英文关键词)
        registerApp("微信", "MicroMessenger", "com.tencent.mm", AppCategory.SOCIAL);
        registerApp("微信", "微信", "com.tencent.mm", AppCategory.SOCIAL);
        registerApp("微信", "WeChat", "com.tencent.mm", AppCategory.SOCIAL);
        registerApp("QQ", "QQ", "com.tencent.mobileqq", AppCategory.SOCIAL);
        registerApp("微博", "Weibo", "com.sina.weibo", AppCategory.SOCIAL);
        registerApp("快手", "Kwai", "com.kuaishou.nebula", AppCategory.SOCIAL);
        registerApp("快手极速版", "KwaiLite", "com.kuaishou.nebula.lite", AppCategory.SOCIAL);
        registerApp("小红书", "xingin", "com.xingin.xhs", AppCategory.SOCIAL);
        registerApp("知乎", "Zhihu", "com.zhihu.android", AppCategory.SOCIAL);

        // 电商类应用注册
        registerApp("淘宝", "AliApp(TB", "com.taobao.taobao", AppCategory.ECOMMERCE);
        registerApp("天猫", "AliApp(TM", "com.tmall.wireless", AppCategory.ECOMMERCE);
        registerApp("京东", "JD4iPhone", "com.jingdong.app.mall", AppCategory.ECOMMERCE);
        registerApp("拼多多", "pinduoduo", "com.xunmeng.pinduoduo", AppCategory.ECOMMERCE);
        registerApp("亚马逊", "Amazon", "com.amazon.mShop.android", AppCategory.ECOMMERCE);
        registerApp("唯品会", "vipshop", "com.achievo.vipshop", AppCategory.ECOMMERCE);

        // 外卖类应用注册
        registerApp("美团", "waimai", "com.sankuai.meituan", AppCategory.FOOD_DELIVERY);
        registerApp("饿了么", "Eleme", "me.ele", AppCategory.FOOD_DELIVERY);
        registerApp("百度外卖", "baidu.waimai", "com.baidu.waimai", AppCategory.FOOD_DELIVERY);

        // 视频类应用注册
        registerApp("抖音", "Aweme", "com.ss.android.ugc.aweme", AppCategory.VIDEO);
        registerApp("TikTok", "com.zhiliaoapp.musically", "com.zhiliaoapp.musically", AppCategory.VIDEO);
        registerApp("爱奇艺", "IQIYI", "com.qiyi.video", AppCategory.VIDEO);
        registerApp("B站", "BiliBili", "tv.danmaku.bili", AppCategory.VIDEO);
        registerApp("优酷", "Youku", "com.youku.phone", AppCategory.VIDEO);
        registerApp("腾讯视频", "TencentVideo", "com.tencent.qqlive", AppCategory.VIDEO);

        // 工具类应用注册
        registerApp("支付宝", "AlipayClient", "com.eg.android.AlipayGphone", AppCategory.UTILITY);
//        registerApp("Chrome浏览器", "Chrome", "com.android.chrome", AppCategory.UTILITY);
        registerApp("华为应用商店", "com.huawei.appmarket", "com.huawei.appmarket", AppCategory.UTILITY);
    }

    // 应用信息结构体
    private static class AppInfo {
        final String appName;
        final String packageName;
        final Pattern matchPattern;

        public AppInfo(String appName, String keyword, String packageName) {
            this.appName = appName;
            this.packageName = packageName;
            this.matchPattern = Pattern.compile(
                    Pattern.quote(keyword) + "/?([^\\s\\(\\)]+)",
                    Pattern.CASE_INSENSITIVE
            );
        }
    }

    // 注册应用方法
    private static void registerApp(String appName, String keyword, String packageName, AppCategory category) {
        PREDEFINED_APP_LIBRARY.put(keyword, new AppInfo(appName, keyword, packageName));
    }

    // 严格包名匹配模式(至少包含两个点)
    private static final Pattern STRICT_PACKAGE_PATTERN = Pattern.compile(
            "^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*){2,}$"
    );

    // 系统词汇黑名单(过滤干扰词)
    private static final Set<String> SYSTEM_WORD_BLACKLIST = new HashSet<>(Arrays.asList(
            "Linux", "Android", "iPhone", "iPad", "Windows", "Mac", "iOS", "CPU", "Build",
            "NetType", "Language", "Version", "Mobile", "Safari", "WebKit", "wv", "KHTML"
    ));

    /**
     * 主解析方法:提取应用包名
     * @param userAgent 用户代理字符串
     * @return 解析出的应用包名,未匹配则返回空字符串
     */
    public static String extractPackageName(String userAgent) {
        if (StringUtils.isBlank(userAgent)) {
            return "";
        }

        // 1. 后缀路径解析(处理类似 /版本号/包名 的结构)
        String packageName = parseSuffixPackage(userAgent);
        if (StringUtils.isNotBlank(packageName) && !isBrowserPackage(packageName)) {
            return packageName;
        }

        // 2. 预定义应用匹配
        packageName = matchPredefinedApps(userAgent);
        if (StringUtils.isNotBlank(packageName) && !isBrowserPackage(packageName)) {
            return packageName;
        }

        // 3. 应用商店格式解析(如 (com.huawei.appmarket; 版本号))
        packageName = parseAppStoreFormatPackage(userAgent);
        if (StringUtils.isNotBlank(packageName) && !isBrowserPackage(packageName)) {
            return packageName;
        }

        // 4. 启发式匹配(版本号关联、括号内容)
        packageName = parseHeuristicPackage(userAgent);
        if (StringUtils.isNotBlank(packageName) && !isBrowserPackage(packageName)) {
            return packageName;
        }

        // 5. 严格模式兜底(全局匹配合法包名)
        packageName = parseStrictModePackage(userAgent);
        if (StringUtils.isNotBlank(packageName) && !isBrowserPackage(packageName)) {
            return packageName;
        } else {
            return "";
        }
    }

    /**
     * 解析后缀路径中的包名(如 open_news_u_s/6817/cn.copper.fokapi.mysterious)
     */
    private static String parseSuffixPackage(String userAgent) {
        try {
            String decodedUa = URLDecoder.decode(userAgent);
            Pattern pattern = Pattern.compile("/\\d+/([\\w\\.]+)(?:\\s|$)");
            Matcher matcher = pattern.matcher(decodedUa);
            if (matcher.find()) {
                String candidate = matcher.group(1);
                return isValidPackage(candidate) ? candidate : "";
            }
        } catch (Exception e) {
            // 忽略解码异常
        }
        return "";
    }

    /**
     * 匹配预定义应用库
     */
    private static String matchPredefinedApps(String userAgent) {
        for (AppInfo appInfo : PREDEFINED_APP_LIBRARY.values()) {
            Matcher matcher = appInfo.matchPattern.matcher(userAgent);
            if (matcher.find()) {
                return appInfo.packageName;
            }
        }
        return "";
    }

    /**
     * 解析应用商店格式包名(如 (com.huawei.appmarket; 11.0.0))
     */
    private static String parseAppStoreFormatPackage(String userAgent) {
        Pattern pattern = Pattern.compile("\\(([^;]+);", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(userAgent);
        if (matcher.find()) {
            String candidate = matcher.group(1).trim();
            return isValidPackage(candidate) ? candidate : "";
        }
        return "";
    }

    /**
     * 启发式匹配(版本号关联、括号内容解析)
     */
    private static String parseHeuristicPackage(String userAgent) {
        // 模式1:包名/版本号 格式匹配
        Pattern versionPattern = Pattern.compile(
                "([a-zA-Z_][a-zA-Z0-9_\\.]*)/[0-9\\.]+",
                Pattern.CASE_INSENSITIVE
        );
        Matcher versionMatcher = versionPattern.matcher(userAgent);
        if (versionMatcher.find()) {
            String candidate = versionMatcher.group(1);
            if (isValidPackage(candidate)) {
                return candidate;
            }
        }

        // 模式2:括号内内容解析
        Pattern bracketPattern = Pattern.compile("\\(([^)]+)\\)", Pattern.CASE_INSENSITIVE);
        Matcher bracketMatcher = bracketPattern.matcher(userAgent);
        while (bracketMatcher.find()) {
            String content = bracketMatcher.group(1);
            for (String part : content.split(";|,| ")) {
                if (isValidPackage(part)) {
                    return part;
                }
            }
        }
        return "";
    }

    /**
     * 严格模式匹配合法包名(全局搜索)
     */
    private static String parseStrictModePackage(String userAgent) {
        Matcher matcher = STRICT_PACKAGE_PATTERN.matcher(userAgent);
        List<String> validCandidates = new ArrayList<>();
        while (matcher.find()) {
            String candidate = matcher.group();
            if (isValidPackage(candidate)) {
                validCandidates.add(candidate);
            }
        }
        // 按长度降序排序,取最长合法包名
        return validCandidates.stream()
                .max(Comparator.comparingInt(String::length))
                .orElse("");
    }

    /**
     * 包名有效性验证
     */
    private static boolean isValidPackage(String packageName) {
        if (packageName == null || packageName.length() < 6) {
            return false;
        }
        if (!STRICT_PACKAGE_PATTERN.matcher(packageName).matches()) {
            return false;
        }
        String firstSegment = packageName.split("\\.")[0];
        return !SYSTEM_WORD_BLACKLIST.contains(firstSegment.toUpperCase());
    }

    /**
     * 浏览器包名判断
     */
    private static boolean isBrowserPackage(String packageName) {
        return BROWSER_PACKAGE_SET.contains(packageName);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

96.如何使用C#实现串口发送? C#例子

Nuget包名称 System.IO.Ports 参考代码 using System; using System.IO.Ports; using System.Threading;namespace test {class Program{static void Main(){SerialPort port new SerialPort("COM1", 9600); // 配置串口port.Open();Timer timer new Timer((_) &…

【工具使用】STM32CubeMX-FreeRTOS操作系统-信号标志、互斥锁、信号量篇

一、概述 无论是新手还是大佬&#xff0c;基于STM32单片机的开发&#xff0c;使用STM32CubeMX都是可以极大提升开发效率的&#xff0c;并且其界面化的开发&#xff0c;也大大降低了新手对STM32单片机的开发门槛。     本文主要讲述STM32芯片FreeRTOS信号标志、互斥锁和信号…

大数据Spark(六十一):Spark基于Standalone提交任务流程

文章目录 Spark基于Standalone提交任务流程 一、Standalone-Client模式 1、提交命令 2、任务执行流程 二、Standalone-Cluster模式 1、提交命令 2、任务执行流程 Spark基于Standalone提交任务流程 在Standalone模式下&#xff0c;Spark的任务提交根据Driver程序运行的位…

Android 平台RTSP/RTMP播放器SDK接入说明

一、技术背景 自2015年起&#xff0c;大牛直播SDK持续深耕音视频直播领域&#xff0c;自主研发并迭代推出跨平台 RTSP/RTMP 播放模块&#xff0c;具备如下核心优势&#xff1a; 全平台兼容&#xff1a;支持 Android/iOS/Windows/Linux 等主流系统&#xff1b; 超低延迟&#…

Nodejs工程化实践:构建高性能前后端交互系统

一、工程架构设计 1.1 现代化项目初始化 采用多包管理架构&#xff1a; mkdir content-platform && cd content-platform npm init -y npx lerna init mkdir -p {packages/client,packages/server,packages/shared} 关键模块划分&#xff1a; client/: 基于Next.js…

STM32什么是寄存器

提示&#xff1a;文章 文章目录 前言一、背景二、2.12.2 三、3.1 总结 前言 前期疑问&#xff1a; 1、什么是寄存器&#xff1f; 答&#xff1a;在4GB的地址空间中&#xff0c;512MB的block2上&#xff0c;每4个字节组成32位&#xff0c;这个32位为一个单元&#xff0c;控制&a…

第六个微信小程序:教师工具集

源于工作需要&#xff0c;下面开始。 安装及使用 | Taro 文档 vscode 代码管理 git 辅助 开发技术如上&#xff1a; 1.开始创建模板 taro4.1.1 $ taro init teachers-tools 2.用vsocde开始吧。 选择 第二个文件夹找一。 (base) PS D:\react\teachers-tools> pnpm…

记录一个用了很久的git提交到github和gitee比较方便的方法

在当前git init后&#xff0c;在隐藏的git文件夹中找到config文件 [user]name thels [remote "github"]url your github repository urlfetch refs/heads/*:refs/remotes/origin/* [remote "gitee"]url your gitee repository urlfetch refs/heads/*:…

Qt Qml模块功能及功能解析

QtQml 是 Qt 6.0 中用于声明式 UI 开发和应用程序逻辑的核心模块&#xff0c;它提供了 QML 语言的支持和运行时环境。 一、主要功能 1. QML 语言支持 QML 语法解析&#xff1a;支持 QML (Qt Meta-Object Language 或 Qt Modeling Language) 的完整语法 JavaScript 集成&…

NLP学习路线图(二十九):BERT及其变体

在自然语言处理(NLP)领域,一场静默的革命始于2017年。当谷歌研究者发表《Attention is All You Need》时,很少有人预料到其中提出的Transformer架构会彻底颠覆NLP的发展轨迹,更催生了以GPT系列为代表的语言模型风暴,重新定义了人类与机器的交互方式。 一、传统NLP的瓶颈:…

【LLM-Agent】智能体的记忆缓存设计

note 实践&#xff1a;https://modelscope-agent.readthedocs.io/zh-cn/latest/modules/memory.html 文章目录 note一、Agent的记忆实现二、相关综述三、记忆体的构建四、cursor的记忆设计1. 记忆生成提示词2. 记忆评估提示词 五、记忆相关的MCPReference 一、Agent的记忆实现…

一起学Spring AI:核心概念

人工智能概念 本节描述了 Spring AI 使用的核心概念。我们建议您仔细阅读&#xff0c;以理解 Spring AI 实现背后的思想。 模型&#xff08;Models&#xff09; 人工智能模型是设计用来处理和生成信息的算法&#xff0c;通常模仿人类的认知功能。通过从大型数据集中学习模式…

PicSharp(图片压缩工具) v1.1.6

PicSharp 一个简单、高效、灵活的跨平台桌面图像压缩应用程序。软件基于Rust实现&#xff0c;高性能低资源&#xff0c;能快速扫描文件或目录&#xff0c;批处理图像。软件还具备组合压缩策略&#xff0c;TinyPNG提供最佳压缩比&#xff0c;但需要互联网连接&#xff0c;对大量…

前端文件下载常用方式详解

在前端开发中&#xff0c;实现文件下载是常见的需求。根据不同的场景&#xff0c;我们可以选择不同的方法来实现文件流的下载。本文介绍三种常用的文件下载方式&#xff1a; 使用 axios 发送 JSON 请求下载文件流使用 axios 发送 FormData 请求下载文件流使用原生 form 表单提…

【DAY42】Grad-CAM与Hook函数

内容来自浙大疏锦行python打卡训练营 浙大疏锦行 知识点: 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 在深度学习中&#xff0c;我们经常需要查看或修改模型中间层的输出或梯度。然而&#xff0c;标准的前向传播和反…

如何生成和制作PDF文件

在数字化办公的今天&#xff0c;PDF文件已经成为我们工作和学习中不可或缺的一部分。无论是合同、报告、简历&#xff0c;还是电子书、表单&#xff0c;PDF格式都以其跨平台兼容性、不可编辑性和清晰的排版而被广泛使用。但你是否知道&#xff0c;生成和制作PDF文件其实并不复杂…

【K8S系列】Kubernetes 中 Pod(Java服务)启动缓慢的深度分析与解决方案

本文针对 Kubernetes 中 Java 服务启动时间慢的深度分析与解决方案文章,结合了底层原理、常见原因及具体优化策略: Kubernetes 中 Java 服务启动缓慢的深度分析与高效解决方案 在 Kubernetes 上部署 Java 应用时,启动时间过长是常见痛点,尤其在需要快速扩缩容或滚动更新的…

【Java学习笔记】StringBuilder类(重点)

StringBuilder&#xff08;重点&#xff09; 1. 基本介绍 是一个可变的字符串序列。该类提供一个与 StringBuffer 兼容的 API&#xff0c;但不保证同步&#xff08;StringBuilder 不是线程安全的&#xff09; 该类被设计用作 StringBuffer 的一个简易替换&#xff0c;用在字符…

iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出

在vue项目中使用iview 框架部分组件时&#xff0c;直接引入使用报Maximum call stack size exceeded image.png 堆栈溢出 解决方案 更换组件名称就可以了 image.png 或 image.png 就可以了 猜测是因为和vue自己提供的组件名称一致了&#xff0c;重名问题导致的&#xff0c;具体…

基于Halcon深度学习之分类

***** ***环境准备*** ***系统&#xff1a;win7以上系统 ***显卡&#xff1a;算力3.0以上 ***显卡驱动&#xff1a;10.1以上版本&#xff08;nvidia-smi查看指令&#xff09;***读取深度学习模型*** read_dl_model (pretrained_dl_classifier_compact.hdl, DLModelHandle) ***获…