当前位置: 首页 > news >正文

JSON路径工具类`JsonPathUtil`的实现与应用

JSON路径工具类JsonPathUtil的实现与应用

作者:zibo
日期:2024/11/25
口号:慢慢学,不要停。

文章目录

  • JSON路径工具类`JsonPathUtil`的实现与应用
    • 〇、完整代码
    • 一、引言
    • 二、功能概述
    • 三、代码实现详解
      • 1. 工具类基础结构
      • 2. 核心方法`getValue`
      • 3. 处理表达式片段`processPart`
      • 4. 处理数组类型的表达式片段`processArrayPart`
      • 5. 获取对象的字段值`getFieldValue`
      • 6. 测试主方法`main`
    • 四、应用示例
    • 五、总结
    • 六、后记

〇、完整代码

package com.kumy.requrchase.treasure.service.letusign.impl;import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;/*** JSON路径工具类* 用于根据表达式获取JSON字符串中的值* 支持以下功能:* 1. 获取普通属性值,如: user.name* 2. 获取数组元素,如: users[0].name* 3. 支持多层嵌套,如: company.department.employees[0].name* * @author zibo* @date 2024/11/25* @slogan 慢慢学,不要停。*/
public class JsonPathUtil {// 定义常量,提高代码可维护性private static final String DOT_SEPARATOR = "\\.";private static final String LEFT_BRACKET = "[";private static final String RIGHT_BRACKET = "]";private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();private JsonPathUtil() {throw new IllegalStateException("工具类不允许实例化");}/*** 根据表达式获取对象的值** @param jsonString JSON字符串,不能为空* @param expression 表达式,不能为空* @return 表达式对应的值* @throws IllegalArgumentException 参数校验异常* @throws Exception 解析异常*/public static Object getValue(String jsonString, String expression) throws Exception {// 参数校验if (Objects.isNull(jsonString) || Objects.isNull(expression)) {throw new IllegalArgumentException("参数不能为空");}// 将 JSON 字符串转换为 Map 对象Map<String, Object> rootObject = OBJECT_MAPPER.readValue(jsonString, new TypeReference<Map<String, Object>>() {});// 分割表达式并处理String[] parts = expression.split(DOT_SEPARATOR);Object currentObject = rootObject;for (String part : parts) {currentObject = processPart(currentObject, part);if (Objects.isNull(currentObject)) {return null;}}return currentObject;}/*** 处理表达式片段* * @param currentObject 当前对象* @param part 表达式片段* @return 处理后的对象* @throws Exception 处理异常*/private static Object processPart(Object currentObject, String part) throws Exception {if (part.contains(LEFT_BRACKET)) {return processArrayPart(currentObject, part);}return getFieldValue(currentObject, part);}/*** 处理数组类型的表达式片段* * @param currentObject 当前对象* @param part 表达式片段* @return 处理后的对象* @throws Exception 处理异常*/private static Object processArrayPart(Object currentObject, String part) throws Exception {String fieldName = part.substring(0, part.indexOf(LEFT_BRACKET));int index = Integer.parseInt(part.substring(part.indexOf(LEFT_BRACKET) + 1, part.indexOf(RIGHT_BRACKET)));Object arrayObject = getFieldValue(currentObject, fieldName);return arrayObject instanceof List ? ((List<?>) arrayObject).get(index) : null;}/*** 获取对象的字段值** @param object 对象,不能为空* @param fieldName 字段名,不能为空* @return 字段值* @throws Exception 反射异常*/@SuppressWarnings("all")private static Object getFieldValue(Object object, String fieldName) throws Exception {if (Objects.isNull(object) || Objects.isNull(fieldName)) {return null;}if (object instanceof Map) {return ((Map<?, ?>) object).get(fieldName);}Field field = object.getClass().getDeclaredField(fieldName);field.setAccessible(true);return field.get(object);}public static void main(String[] args) {try {// 测试JSON字符串String jsonString = "{"+ "\"userInfo\": {"+ "\"id\": 1,"+ "\"photoPath\": \"yx.mm.com\","+ "\"realName\": \"张三\","+ "\"examInfoDict\": ["+ "{\"id\": 1, \"examType\": 0, \"answerIs\": 1},"+ "{\"id\": 2, \"examType\": 0, \"answerIs\": 0}"+ "]"+ "},"+ "\"flag\": 1"+ "}";// 测试不同场景System.out.println("测试普通属性: " + getValue(jsonString, "userInfo.realName")); // 输出张三System.out.println("测试数组访问: " + getValue(jsonString, "userInfo.examInfoDict[0].id")); // 输出1System.out.println("测试空值处理: " + getValue(jsonString, "userInfo.notExist")); // 输出null} catch (Exception e) {System.err.println("处理异常: " + e.getMessage());e.printStackTrace();}}
}

一、引言

在日常的Java开发中,经常需要根据特定的路径或表达式,从JSON字符串中提取所需的数据。虽然市场上有诸如JsonPath等强大的工具可以实现这一需求,但有时候我们需要一个轻量级、可自定义的解决方案。本文将介绍一个自定义实现的JSON路径工具类JsonPathUtil,它可以根据表达式从JSON字符串中获取对应的值,支持获取普通属性、数组元素以及多层嵌套的属性值。

二、功能概述

JsonPathUtil工具类的主要功能包括:

  1. 获取普通属性值:如user.name,获取user对象的name属性值。
  2. 获取数组元素:如users[0].name,获取users数组中第一个元素的name属性值。
  3. 支持多层嵌套:如company.department.employees[0].name,获取嵌套结构中指定员工的姓名。

三、代码实现详解

1. 工具类基础结构

首先,定义了JsonPathUtil工具类,并声明了一些常量:

package com.kumy.requrchase.treasure.service.letusign.impl;import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;public class JsonPathUtil {private static final String DOT_SEPARATOR = "\\.";private static final String LEFT_BRACKET = "[";private static final String RIGHT_BRACKET = "]";private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();private JsonPathUtil() {throw new IllegalStateException("工具类不允许实例化");}// 其他方法...
}

解析:

  • 使用了ObjectMapper来处理JSON字符串的解析。
  • 工具类的构造方法被私有化,防止实例化。

2. 核心方法getValue

getValue方法是工具类的核心,用于根据表达式从JSON字符串中获取对应的值。

public static Object getValue(String jsonString, String expression) throws Exception {// 参数校验if (Objects.isNull(jsonString) || Objects.isNull(expression)) {throw new IllegalArgumentException("参数不能为空");}// 将 JSON 字符串转换为 Map 对象Map<String, Object> rootObject = OBJECT_MAPPER.readValue(jsonString, new TypeReference<Map<String, Object>>() {});// 分割表达式并处理String[] parts = expression.split(DOT_SEPARATOR);Object currentObject = rootObject;for (String part : parts) {currentObject = processPart(currentObject, part);if (Objects.isNull(currentObject)) {return null;}}return currentObject;
}

解析:

  • 参数校验:确保jsonStringexpression不为空,否则抛出IllegalArgumentException
  • JSON解析:使用ObjectMapper将JSON字符串解析为Map<String, Object>类型的rootObject
  • 表达式解析:根据.分隔符,将表达式拆分为多个部分parts,然后逐一处理每个部分。
  • 逐层解析:通过循环,每次处理表达式的一部分,并不断更新currentObject,直到获取最终的值。

3. 处理表达式片段processPart

该方法用于处理表达式中的每一部分,判断是普通属性还是数组访问。

private static Object processPart(Object currentObject, String part) throws Exception {if (part.contains(LEFT_BRACKET)) {return processArrayPart(currentObject, part);}return getFieldValue(currentObject, part);
}

解析:

  • 如果表达式部分包含[,说明需要处理数组,调用processArrayPart方法。
  • 否则,直接调用getFieldValue获取属性值。

4. 处理数组类型的表达式片段processArrayPart

该方法用于解析数组元素的访问。

private static Object processArrayPart(Object currentObject, String part) throws Exception {String fieldName = part.substring(0, part.indexOf(LEFT_BRACKET));int index = Integer.parseInt(part.substring(part.indexOf(LEFT_BRACKET) + 1, part.indexOf(RIGHT_BRACKET)));Object arrayObject = getFieldValue(currentObject, fieldName);return arrayObject instanceof List ? ((List<?>) arrayObject).get(index) : null;
}

解析:

  • 获取字段名和索引:通过字符串操作,提取数组字段名fieldName和索引index
  • 获取数组对象:使用getFieldValue方法获取对应的数组对象arrayObject
  • 获取数组元素:检查arrayObject是否为List的实例,如果是,则返回对应索引的元素。

5. 获取对象的字段值getFieldValue

该方法用于获取当前对象中指定字段的值。

@SuppressWarnings("all")
private static Object getFieldValue(Object object, String fieldName) throws Exception {if (Objects.isNull(object) || Objects.isNull(fieldName)) {return null;}if (object instanceof Map) {return ((Map<?, ?>) object).get(fieldName);}Field field = object.getClass().getDeclaredField(fieldName);field.setAccessible(true);return field.get(object);
}

解析:

  • 空值检查:如果objectfieldNamenull,直接返回null
  • 处理Map类型:如果当前对象是Map,直接获取对应键的值。
  • 处理普通对象:使用反射获取对象的字段值,即使字段是私有的(通过setAccessible(true))。

6. 测试主方法main

编写了一个main方法用于测试工具类的功能。

public static void main(String[] args) {try {// 测试JSON字符串String jsonString = "{"+ "\"userInfo\": {"+ "\"id\": 1,"+ "\"photoPath\": \"yx.mm.com\","+ "\"realName\": \"张三\","+ "\"examInfoDict\": ["+ "{\"id\": 1, \"examType\": 0, \"answerIs\": 1},"+ "{\"id\": 2, \"examType\": 0, \"answerIs\": 0}"+ "]"+ "},"+ "\"flag\": 1"+ "}";// 测试不同场景System.out.println("测试普通属性: " + getValue(jsonString, "userInfo.realName")); // 输出张三System.out.println("测试数组访问: " + getValue(jsonString, "userInfo.examInfoDict[0].id")); // 输出1System.out.println("测试空值处理: " + getValue(jsonString, "userInfo.notExist")); // 输出null} catch (Exception e) {System.err.println("处理异常: " + e.getMessage());e.printStackTrace();}
}

解析:

  • 构造了一个包含嵌套对象和数组的JSON字符串。
  • 测试了获取普通属性、数组元素以及处理不存在的属性的情况。
  • 输出结果验证了工具类的功能。

四、应用示例

为了更清晰地展示JsonPathUtil的应用,下面提供一个实际例子。

示例JSON字符串:

{"employee": {"name": "李华","age": 30,"department": {"name": "研发部","location": "北京"},"skills": [{"name": "Java", "level": "高级"},{"name": "Python", "level": "中级"}]}
}

示例代码:

String jsonString = "{...}"; // 如上所示的JSON字符串// 获取员工姓名
String name = (String) JsonPathUtil.getValue(jsonString, "employee.name");
System.out.println("员工姓名:" + name); // 输出:员工姓名:李华// 获取部门名称
String departmentName = (String) JsonPathUtil.getValue(jsonString, "employee.department.name");
System.out.println("部门名称:" + departmentName); // 输出:部门名称:研发部// 获取第一项技能的名称
String firstSkill = (String) JsonPathUtil.getValue(jsonString, "employee.skills[0].name");
System.out.println("第一项技能:" + firstSkill); // 输出:第一项技能:Java// 尝试获取不存在的属性
Object nonExistent = JsonPathUtil.getValue(jsonString, "employee.address");
System.out.println("不存在的属性:" + nonExistent); // 输出:不存在的属性:null

解析:

  • 使用JsonPathUtil.getValue方法,根据不同的表达式,成功获取了嵌套对象和数组中的值。
  • 当尝试获取不存在的属性时,方法返回null,程序没有抛出异常,这体现了对异常情况的良好处理。

五、总结

本文详细介绍了JsonPathUtil工具类的实现原理和应用。通过逐步解析代码,我们了解到:

  • 如何解析复杂的JSON路径表达式,包括嵌套属性和数组元素。
  • 使用ObjectMapper将JSON字符串转换为可操作的Java对象。
  • 通过反射和类型检查,实现了对Map和普通Java对象的字段访问。

优点:

  • 轻量级:不依赖于第三方库,适合对JSON路径解析需求不复杂的场景。
  • 易于理解和扩展:代码简洁明了,方便根据需求进行定制。

不足:

  • 功能有限:不支持复杂的表达式,如过滤条件、通配符等。
  • 性能考虑:对于大规模的JSON数据和高并发场景,可能需要优化或选择性能更优的方案。

建议:

  • 对于简单的JSON解析需求,可以直接使用JsonPathUtil工具类。
  • 如果需要更高级的JSON路径功能,建议使用专业的JSON路径解析库,如Jayway的JsonPath。
    • JsonPath 开源地址:https://github.com/json-path/JsonPath
    • 在线语法检查:https://jsonpath.com/

六、后记

“慢慢学,不要停。”在编程的道路上,理解每一段代码背后的原理,都能让我们走得更远。希望通过本文的讲解,能帮助到有需要的读者,加深对JSON解析和Java反射的理解。


感谢阅读!

相关文章:

JSON路径工具类`JsonPathUtil`的实现与应用

JSON路径工具类JsonPathUtil的实现与应用 作者&#xff1a;zibo 日期&#xff1a;2024/11/25 口号&#xff1a;慢慢学&#xff0c;不要停。 文章目录 JSON路径工具类JsonPathUtil的实现与应用〇、完整代码一、引言二、功能概述三、代码实现详解1. 工具类基础结构2. 核心方法get…...

人名分类器(nlp)

# coding: utf-8 import osos.environ[KMP_DUPLICATE_LIB_OK] True# 导入torch工具 import jsonimport torch # 导入nn准备构建模型 import torch.nn as nn import torch.nn.functional as F import torch.optim as optim # 导入torch的数据源 数据迭代器工具包 from torch.ut…...

斐波那契数列 相关问题 详解

斐波那契数列相关问题详解 斐波那契数列及其相关问题是算法学习中的经典主题&#xff0c;变形与应用非常广泛&#xff0c;涵盖了递推关系、动态规划、组合数学、数论等多个领域。以下是斐波那契数列的相关问题及其解法的详解。 1. 经典斐波那契数列 定义 初始条件&#xff1…...

Pytorch微调深度学习模型

在公开数据训练了模型&#xff0c;有时候需要拿到自己的数据上微调。今天正好做了一下微调&#xff0c;在此记录一下微调的方法。用Pytorch还是比较容易实现的。 网上找了很多方法&#xff0c;以及Chatgpt也给了很多方法&#xff0c;但是不够简洁和容易理解。 大体步骤是&…...

springboot 使用笔记

1.springboot 快速启动项目 注意&#xff1a;该启动只是临时启动&#xff0c;不能关闭终端面板 cd /www/wwwroot java -jar admin.jar2.脚本启动 linux shell脚本启动springboot服务 3.java一键部署springboot 第5条 https://blog.csdn.net/qq_30272167/article/details/1…...

网络安全基础——网络安全法

填空题 1.根据**《中华人民共和国网络安全法》**第二十条(第二款)&#xff0c;任何组织和个人试用网路应当遵守宪法法律&#xff0c;遵守公共秩序&#xff0c;遵守社会公德&#xff0c;不危害网络安全&#xff0c;不得利用网络从事危害国家安全、荣誉和利益&#xff0c;煽动颠…...

SCAU软件体系结构实验四 组合模式

目录 一、题目 二、源码 一、题目 个人(Person)与团队(Team)可以形成一个组织(Organization)&#xff1a;组织有两种&#xff1a;个人组织和团队组织&#xff0c;多个个人可以组合成一个团队&#xff0c;不同的个人与团队可以组合成一个更大的团队。 使用控制台或者JavaFx界面…...

Amazon商品详情API接口:电商创新与用户体验的驱动力

在电子商务蓬勃发展的今天&#xff0c;作为全球最大的电商平台之一&#xff0c;亚马逊&#xff08;Amazon&#xff09;凭借其强大的技术实力和丰富的商品资源&#xff0c;为全球用户提供了优质的购物体验。其中&#xff0c;Amazon商品详情API接口在电商创新与用户体验提升方面扮…...

手机无法连接服务器1302什么意思?

你有没有遇到过手机无法连接服务器&#xff0c;屏幕上显示“1302”这样的错误代码&#xff1f;尤其是在急需使用手机进行工作或联系朋友时&#xff0c;突然出现的连接问题无疑会带来不少麻烦。那么&#xff0c;什么是1302错误&#xff0c;它又意味着什么呢&#xff1f; 1302错…...

Android adb shell dumpsys audio 信息查看分析详解

Android adb shell dumpsys audio 信息查看分析详解 一、前言 Android 如果要分析当前设备的声音通道相关日志&#xff0c; 仅仅看AudioService的日志是看不到啥日志的&#xff0c;但是看整个audio关键字的日志又太多太乱了&#xff0c; 所以可以看一下系统提供的一个调试指令…...

Python 网络爬虫操作指南

网络爬虫是自动化获取互联网上信息的一种工具。它广泛应用于数据采集、分析以及实现信息聚合等众多领域。本文将为你提供一个完整的Python网络爬虫操作指南&#xff0c;帮助你从零开始学习并实现简单的网络爬虫。我们将涵盖基本的爬虫概念、Python环境配置、常用库介绍。 上传…...

基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功

基于FPGA的2FSK调制 前言一、2FSK储备知识二、代码分析1.模块分析2.波形分析 总结 前言 设计实现连续相位 2FSK 调制器&#xff0c;2FSK 的两个频率为:fI15KHz&#xff0c;f23KHz&#xff0c;波特率为 1500 bps,比特0映射为f 载波&#xff0c;比特1映射为 载波。 1&#xff09…...

JavaScript的基础数据类型

一、JavaScript中的数组 定义 数组是一种特殊的对象&#xff0c;用于存储多个值。在JavaScript中&#xff0c;数组可以包含不同的数据类型&#xff0c;如数字、字符串、对象、甚至其他数组。数组的创建有两种常见方式&#xff1a; 字面量表示法&#xff1a;let fruits [apple…...

第三讲 架构详解:“隐语”可信隐私计算开源框架

目录 隐语架构 隐语架构拆解 产品层 算法层 计算层 资源层 互联互通 跨域管控 本文主要是记录参加隐语开源社区推出的第四期隐私计算实训营学习到的相关内容。 隐语架构 隐语架构拆解 产品层 产品定位&#xff1a; 通过可视化产品&#xff0c;降低终端用户的体验和演…...

JDBC编程---Java

目录 一、数据库编程的前置 二、Java的数据库编程----JDBC 1.概念 2.JDBC编程的优点 三.导入MySQL驱动包 四、JDBC编程的实战 1.创造数据源&#xff0c;并设置数据库所在的位置&#xff0c;三条固定写法 2.建立和数据库服务器之间的连接&#xff0c;连接好了后&#xff…...

Python绘制太极八卦

文章目录 系列目录写在前面技术需求1. 图形绘制库的支持2. 图形绘制功能3. 参数化设计4. 绘制控制5. 数据处理6. 用户界面 完整代码代码分析1. rset() 函数2. offset() 函数3. taiji() 函数4. bagua() 函数5. 绘制过程6. 技术亮点 写在后面 系列目录 序号直达链接爱心系列1Pyth…...

Spring框架特性及包下载(Java EE 学习笔记04)

1 Spring 5的新特性 Spring 5是Spring当前最新的版本&#xff0c;与历史版本对比&#xff0c;Spring 5对Spring核心框架进行了修订和更新&#xff0c;增加了很多新特性&#xff0c;如支持响应式编程等。 更新JDK基线 因为Spring 5代码库运行于JDK 8之上&#xff0c;所以Spri…...

Linux关于vim的笔记

Linux关于vim的笔记&#xff1a;(vimtutor打开vim 教程) --------------------------------------------------------------------------------------------------------------------------------- 1. 光标在屏幕文本中的移动既可以用箭头键&#xff0c;也可以使用 hjkl 字母键…...

linux mount nfs开机自动挂载远程目录

要在Linux系统中实现开机自动挂载NFS共享目录&#xff0c;你需要编辑/etc/fstab文件。以下是具体步骤和示例&#xff1a; 确保你的系统已经安装了NFS客户端。如果没有安装&#xff0c;可以使用以下命令安装&#xff1a; sudo apt-install nfs-common 编辑/etc/fstab文件&#…...

【vue】导航守卫

什么是导航守卫 在vue路由切换过程中对行为做个限制 全局前置守卫 route.beforeEach((to, from, next)) > {// to是切换到的路由// from是正要离开的路由// next控制是否允许进入目标路由next(false); //不允许 }路由级别的导航守卫 const routes [{path: /User,name: U…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...