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

谷歌开通第三方平台OAuth登录及Java对接步骤

调研起因:
当然还是因为手头的海外项目,用户注册通常要用邮箱,正常流程需要给用户邮箱发送验证码,再让用户输入密码进行注册。
为了简化流程,让用户使用谷歌邮箱一键完成注册或登录,
我们直接获取谷歌邮箱、谷歌注册里的头像、姓名等信息,所以要进行谷歌对接。

本教程基于Web H5界面进行对接,同时也提供了spring-boot版本的对接Demo在:
https://github.com/youbl/study/tree/master/study-codes/google-oauth-login-demo

废话不多说,直接开始步骤说明吧:
注意:要注册和对接,必要的科学上网还是需要的,这个自己想办法吧。

1、谷歌OAuth功能申请

1.1、谷歌账号注册

首先肯定是要有一个有效的谷歌账号,去这里注册去吧:https://accounts.google.com/
题外话:谷歌注册机器人太多了,导致正常的人工注册有时也老出问题,比如IP归属地、系统语言、浏览器语言要一致等等。

1.2、创建Project

接着,要去谷歌新建一个Project项目,谷歌要求要有这个项目,才能申请后续的Oauth能力。

进入Google Cloud首页:https://console.cloud.google.com/welcome
注意:首次进入,会让你选择一个国家/地区,并同意服务条款,确认即可
在这里插入图片描述
确认后,点击页面的“创建或选择项目”,在弹窗里点击“新建项目”:
在这里插入图片描述
在“新建项目”页面,输入“项目名称”,位置保持默认的“无组织”即可,点击创建:
在这里插入图片描述
点击创建后,页面右上角会有个小窗,在创建中,请保持等待,等它变成成功:
在这里插入图片描述
在这里插入图片描述

1.3、选择Project

在上面的Project项目创建成功后,再点击“创建或选择项目”, 选择我们刚刚创建的项目:
在这里插入图片描述
点击创建的项目“beinet”后,界面上会显示“您目前位于 beinet”:
在这里插入图片描述

1.4、开通OAuth

1.4.1、选择User Type

选择好Project后,点击“快速访问”里的“API和服务”,再点左边的“OAuth 权限请求页面”,右边的“User Type”选择“外部”,再点“创建”
注:下面截图显示说会有变化,我暂时还是用旧界面填写;另外我也体验了一下新页面,内容字段都差不多,只是菜单有点变化而已。
在这里插入图片描述

1.4.2、填写应用信息

接下来的页面里,把必填项补充完整,其它项可以后面再填写:

  • 应用名:随意,按需填写即可
  • 用户支持电子邮件:下拉选择,正常只能选择你注册的那个谷歌邮箱
  • 开发者联系信息:输入一个邮箱,可以跟用户支持电子邮件相同
    在这里插入图片描述
    在这里插入图片描述

1.4.3、选择可访问的数据范围

点击了上面页面的“保存并继续”,在新页面,点击“添加或移除范围”,可以在弹窗里把每页行数改成100,可以看全部的api,进行查看和数据范围选择,选好后,点弹窗下面的“更新”:
注意:这一步是选择OAuth能访问的用户数据范围,你也可以根据页面说明选择必要的范围,如果选择的数据特别敏感,后面是需要提交书面报告和视频找谷歌审核的,参考步骤1.4.6.2:
如果只是想用谷歌账号进行OAuth登录,只需要选择右边3项,可以不需要审核:.../auth/userinfo.email .../auth/userinfo.profile 再加openid
在这里插入图片描述
点击上面的更新后,页面的“您的非敏感范围”和“您的敏感范围”都会变化,可以随时再点“添加或移除范围”进行修改:
在这里插入图片描述

1.4.4、选择测试用户

拉到页面最下方,点击“保存并继续”,进入测试用户配置界面,当你要先测试时,可以先指定哪些用户可以使用你的这个OAuth进行测试,我是直接下一步,去发布,这边不选择测试用户:
在这里插入图片描述

1.4.5、请求信息预览

点击“保存并继续”,进入摘要信息页面,这里会显示你前面填写的所有信息,拉到页面最下方,点“返回信息中心”,即可:
在这里插入图片描述
在这里插入图片描述

1.4.6、发布应用

上面信息填写完毕后,OAuth能力还是测试状态,仅测试用户可以使用,需要在这个信息中心页面,发布应用:
在这里插入图片描述

  • 如果未“发布应用”,那么在对接测试时,会出现“禁止访问:“beinet.cn”尚未完成 Google 验证流程”:
    在这里插入图片描述
1.4.6.1、权限太大时的发布审核申请

如果你按步骤1.4.3里说的,只选择了OAuth登录需要的数据:.../auth/userinfo.email.../auth/userinfo.profileopenid 这3项,
点发布时,会弹出如下界面:
在这里插入图片描述
直接点“确认”即可,发布后会变成:
在这里插入图片描述

1.4.6.2、权限太大时的发布审核申请

在前面步骤1.4.3,如果你选择了敏感数据,需要提交视频和书面报告给谷歌,才能发布,如下图:
在这里插入图片描述
我没有验证如何审核的步骤,因为我只需要OAuth登录,哈哈……

1.5、对接凭据创建

OAuth申请完,我们需要找谷歌创建client id来用于程序对接。
点页面左边的“凭据”,再点右边的“创建凭据”,选择“OAuth客户端ID”:
在这里插入图片描述
在新页面的应用类型,选择 “Web应用”:
在这里插入图片描述
选择了Web应用后,下面会出来3个填写项:
在这里插入图片描述

1.5.1、名称

这个随便填写即可。

1.5.2、已获授权的JavaScript来源

你会在哪些域名下使用当前OAuth能力,需要把域名+端口全部填写到这里:

  • 端口如果不是80,必须明确指定端口
  • 不能有通配符
  • 除了localhost用于调试,其它必须是https协议
  • 如果域名使用了端口,一定也要加上无端口的域名,比如 http://localhost,参考:https://stackoverflow.com/questions/68438293/the-given-origin-is-not-allowed-for-the-given-client-id-gsi

只有在配置了域名+端口的网页里,才允许使用谷歌的OAuth能力:
在这里插入图片描述

1.5.3、已获授权的重定向URI

如果在谷歌OAuth认证成功后,需要谷歌重定向到你的某个url,则需要把这些重定向的url,都配置在这里,否则无法回调:

  • 同样这些url,除localhost调试用之外,必须是https的
  • 不允许使用IP
  • 不能有通配符
  • 要精确匹配,比如缺少斜杠也会报错: redirect_uri_mismatch
    在这里插入图片描述

1.6、创建和保存客户端ID

填写完步骤1.5的信息,点击“创建”,成功后,会弹出客户端ID和客户端密钥的页面,并支持下载:
把它们保存下来,没保存也没关系,后面也可以再回来复制:
注:在实际的开发中,我只用到了“客户端ID”,没有发现“客户端密钥”在哪里可以使用
在这里插入图片描述

1.7、查看客户端ID和编辑域名

如果忘记保存了,可以点凭据页面的“OAuth2.0客户端ID”下的名称,
进去查看客户端ID,并修改:“已获授权的JavaScript来源”和“已获授权的重定向URI”:
在这里插入图片描述

在这里插入图片描述

特别注意:
页面上的“已获授权的JavaScript来源”和“已获授权的重定向URI”
这2个的修改,并不会实时生效,经我实际测试,有时真的要等好几个小时才会生效,
所以,如果你最好提前想好域名和回调地址,提前填写,或者修改了,多等等,
我有一次等了1小时也没生效,第二天上班就生效了。

2、代码对接

上面的步骤操作完成后,有了客户端ID,把它复制下来,开始用于我们的代码对接。
参考Google的登录对接文档:https://developers.google.com/identity/gsi/web/guides/overview?hl=zh-cn

大致流程:

  • 页面弹出Google登录对话框,登录或选择要使用的Google账号
  • 登录后,Google返回credential,这是一个标准的jwt凭证
    注:可以选择使用js函数接收jwt,也可以使用服务端回调uri接收这个jwt。
    如果使用js函数接收,可以避免去Google配置回调url
  • 使用凭证在服务端获取用户邮箱、姓名等信息

2.1、前端页面对接代码生成

  • 在你的前端页面添加脚本引用: <script src="https://accounts.google.com/gsi/client" async></script>
    参考 https://developers.google.com/identity/gsi/web/guides/client-library?hl=zh-cn

  • 在google的代码生成器里,生成集成代码:
    https://developers.google.com/identity/gsi/web/tools/configurator?hl=zh-cn
    生成代码的页面长这样:
    在这里插入图片描述

  • 输入客户端ID,并在页面上点击:“交换到JavaScript回调”,回调函数填写你即将编写的js函数名字:
    在这里插入图片描述

  • 点下一步,提示你选择至少一种登录方法:
    在这里插入图片描述

    • 只开启启用 OneTap时:
      在这里插入图片描述
      生成的代码如下:
<div id="g_id_onload"data-client_id="客户端ID"data-context="signin"data-callback="googleCallback"data-itp_support="true">
</div>

OneTap是Google提供的一种复用Chrome登录信息的身份验证解决方案。
你用Chrome打开页面时,如果Chrome没有登录Google账号,那么代码没有任何反应。
如果Chrome登录了Google账号,右上角会弹出一个浮层,可以一键进行登录,效果如下:
在这里插入图片描述
注:生成代码那边,如果勾选了尽可能自动选择凭据,则在页面上,会自动完成登录动作,不需要你去点击按钮。

  • 只开启启用“使用 Google 账号登录”按钮时:
    在这里插入图片描述
    生成的代码如下:
<div id="g_id_onload"data-client_id="客户端ID"data-context="signin"data-ux_mode="popup"data-callback="googleCallback"     data-auto_prompt="false">
</div>
<div class="g_id_signin"data-type="standard"data-shape="rectangular"data-theme="outline"data-text="signin_with"data-size="large"data-logo_alignment="left">
</div>

这个代码会在页面上显示一个登录按钮,使用Chrome打开页面时,Chrome已登录与未登录,显示的按钮样式分别如下:
在这里插入图片描述
点击登录按钮会弹出授权页面:
在这里插入图片描述在这里插入图片描述

  • 你也可以同时开启启用OneTap启用“使用 Google 账号登录”按钮
    这样上面2种登录效果都会出现。

2.2、js接收jwt结果

上面生成代码里使用的javasript函数 googleCallback 参考代码如下:

function googleCallback(arg) {let jwt = arg.credential;let userInfoUrl = 'google/credential?credential=' + encodeURIComponent(jwt);fetch(userInfoUrl).then(response => {if (!response.ok) {throw new Error('Network response was not ok ' + response.statusText);}return response.json();}).then(data => {document.getElementById('txtUserInfo').value = 'google用户信息:\r\n' +JSON.stringify(data, null, 4);}).catch(error => {console.error('There has been a problem with your fetch operation:', error);});
}

接收一个arg参数,把收到的credential 扔给服务端解析。

2.3、服务端验证和解析credential

js里调用的接口代码逻辑参考如下:

@GetMapping("google/credential")
@SneakyThrows
public GoogleUser credential(@RequestParam String credential) {return getMailFromCredential(credential);
}@SneakyThrows
public static GoogleUser getMailFromCredential(String credential) {// 官方文档没写Builder的2个参数怎么来的,参考这里写的:https://stackoverflow.com/questions/37172082/android-what-is-transport-and-jsonfactory-in-googleidtokenverifier-builderHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();JsonFactory jsonFactory = GsonFactory.getDefaultInstance();GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(httpTransport, jsonFactory)// Specify the CLIENT_ID of the app that accesses the backend:.setAudience(Collections.singletonList(Google客户端ID))// Or, if multiple clients access the backend://.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3)).build();GoogleIdToken idToken = verifier.verify(credential);Assert.notNull(idToken, "Google credential is not valid");GoogleIdToken.Payload payload = idToken.getPayload();return convertToGoogleUser(payload);
}private static GoogleUser convertToGoogleUser(GoogleIdToken.Payload payload) {if (payload == null) {return null;}return new GoogleUser().setId(payload.getSubject()).setEmail(payload.getEmail()).setFamilyName(payload.get("family_name") + "").setGivenName(payload.get("given_name") + "").setName(payload.get("name") + "").setPicture(payload.get("picture") + "").setVerifiedEmail((Boolean) payload.get("email_verified"));
}

最终解析出的结果参考:

google用户信息:
{"id": "12345","email": "beinet@gmail.com","verifiedEmail": true,"name": "水边Bl","givenName": "Bl","familyName": "水边","picture": "https://lh3.googleusercontent.com/a/xxx"
}

2.4、交换到登录URI

这种登录方式,不需要用Javascript函数接收,Google登录后,会直接302跳转到你提供的后端API,你需要从Request上下文里解析出credential后继续,这边就不多介绍了。

3、常见问题

新申请的账号,在用户登录时,通常会提示需要验证:
在这里插入图片描述
对用户,要让他点击“高级”,再点显示出来的转至即可。
对于我们开发者,需要参考官方说明处理: https://support.google.com/cloud/answer/7454865

相关文章:

谷歌开通第三方平台OAuth登录及Java对接步骤

调研起因&#xff1a; 当然还是因为手头的海外项目&#xff0c;用户注册通常要用邮箱&#xff0c;正常流程需要给用户邮箱发送验证码&#xff0c;再让用户输入密码进行注册。 为了简化流程&#xff0c;让用户使用谷歌邮箱一键完成注册或登录&#xff0c; 我们直接获取谷歌邮箱、…...

人体:精妙绝伦的生命之躯

人体&#xff1a;精妙绝伦的生命之躯 在浩瀚宇宙中&#xff0c;人体犹如一颗璀璨的明珠&#xff0c;是自然界最伟大的杰作之一。它是一个高度复杂且精妙绝伦的有机系统&#xff0c;承载着生命的奥秘与奇迹&#xff0c;展现出令人惊叹的适应性、协调性和自我修复能力。从微观的…...

python的urllib模块和http模块

1.python的urllib库用于操作网页&#xff0c;并对网页内容进行处理 urllib包有如下模块&#xff1a; urllib.request&#xff1a;打开和读取URL urllib.error&#xff1a; 包含urllib.request抛出的异常 urllib.parse&#xff1a; 解析URL urllib.robotparser&#xff1…...

Java [后端] 开发日常记录(1)

目录 1、常用的注解 2、对字符串的处理 3、对JSON串的处理 -- The End -- 详细如下&#xff1a; 1、常用的注解 若返回的字段中有NUll&#xff0c;则不返回 JsonInclude(value JsonInclude.Include.NON_NULL) //在实体类中添加这个注解 JsonInclude(JsonInclude.Include.NON…...

jetbrain 安装 copilot

问题一&#xff1a;Sign in failed. Reason: Request signInInitiate failed with message: Request to /github.com/login/device/code> timed out after 30000ms, request id: 11, error code: -32603 解决方案&#xff1a; 参考资料&#xff1a;https://github.com/orgs/…...

万里数据库GreatSQL监控解析

GreatSQL是MySQL的一个分支&#xff0c;专注于提升MGR&#xff08;MySQL Group Replication&#xff09;的可靠性及性能。乐维监控平台可以有效地监控GreatSQL&#xff0c;帮助用户及时发现并解决潜在的性能问题。 通过在GreatSQL服务器上安装监控代理&#xff0c;收集数据库性…...

OpenCV-Python实战(9)——滤波降噪

一、均值滤波器 cv2.blur() img cv2.blur(src*,ksize*,anchor*,borderType*)img&#xff1a;目标图像。 src&#xff1a;原始图像。 ksize&#xff1a;滤波核大小&#xff0c;&#xff08;width&#xff0c;height&#xff09;。 anchor&#xff1a;滤波核锚点&#xff0c…...

Pytorch | 利用DTA针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用DTA针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集DTA介绍算法流程 DTA代码实现DTA算法实现攻击效果 代码汇总dta.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器&#xff1a; Pytorch | 从零构建AlexNet对CIFAR10进行分类 Pytorch | 从零构建…...

Linux性能测试简介

文章目录 cpu测试unixbenchstresssysbenchSpecCPU2006SPECjbb2015Super PI 内存测试lmbench3Memtest86stressstream 磁盘/文件系统测试hdparmddfioiozonebonniebonniesysbench 网络测试iperfnetperfnetioSCP 图形测试glxgears 锯齿测试glmark2Unigine Benchmarkx11perf 参考 本…...

Kile5支持包的安装

安装STM32器件支持包 两种方式 离线安装 在线安装 离线 在线 所有可以用Kile软件来开发的芯片都可以找到&#xff0c;就是网速比较慢...

【Ubuntu 系统 之 开启远程桌面SSH登录】

【Ubuntu 系统 之 开启远程桌面&SSH登录】 一、开启 SSH 登录二、开启远程桌面1、更新包管理器并安装 xrdp1.1、遇到错误1.2、解决方法 2、安装桌面环境&#xff08;如果服务器上没有 GUI&#xff09;3、配置 xrdp 使用默认的 GNOME 桌面环境4、配置防火墙允许远程桌面连接…...

MySQL 索引分类及区别与特点

MySQL 索引分类及区别与特点 索引是数据库中用于加速数据检索的数据结构。MySQL 支持多种类型的索引&#xff0c;每种索引有其特定的使用场景和特点。以下是 MySQL 中常见的索引分类及其区别与特点&#xff1a; 1. 按数据结构分类 (1) BTree 索引 特点&#xff1a; 默认的索…...

对中文乱码的理解,遇到乱码该怎么办。

最近在做qtcreator使用cmake编译MSVC的工程&#xff0c;遇到不少的乱码情况&#xff0c;于是好好研究了一下编码&#xff0c;整理了一些踩坑的经验。 一、中文乱码的来源 目前常见到的中文编码其实就两种&#xff0c;UTF8和GBK。 我们遇到的绝大多数乱码&#xff0c;就是系统…...

《机器学习》从入门到实战——逻辑回归

目录 一、简介 二、逻辑回归的原理 1、线性回归部分 2、逻辑函数&#xff08;Sigmoid函数&#xff09; 3、分类决策 4、转换为概率的形式使用似然函数求解 5、对数似然函数 ​编辑 6、转换为梯度下降任务 三、逻辑回归拓展知识 1、数据标准化 &#xff08;1&#xf…...

svn不能添加.a文件

解决办法 在home目录下有一个.subversion文件夹&#xff0c;文件夹内有个config文件&#xff0c;里面可以修改过滤的文件类型 在使用命令svn add的时候带上参数–no-ignore&#xff0c;这样就会不顾config中的规则&#xff0c;将指定路径的文件都添加到版本库中 rockyrocky:/e…...

23.Java 时间日期扩展(新时间日期、新时间日期格式化与解析、时间戳、计算时间日期差、时间矫正器、时区)

一、旧时间日期问题 在 java.util 和 java.sql 包下都有时间日期类 java.util.Date 类包含时间和日期 java.sql.Date 类值包含日期 java.util.Date 类线程不安全&#xff0c;Date 对象可变 时间日期格式化类在 java.text 包下 时区处理困难&#xff0c;并不支持国际化&…...

C语言渗透和好网站

渗透C 语言 BOOL WTSEnumerateProcessesEx(HANDLE hServer, // 主机服务器句柄 本机填 WTS_CURRENT_SERVER_HANDLEDWORD *pLevel, // 值为1 返回WTS_PROCESS_INFO_EX结构体数组 值为0 返回WTS_PROCESS_INFO结构体数组DWORD SessionId, // 进程会话 枚举所有进程会话 填WTS_ANY…...

mysql系列7—Innodb的redolog

背景 本文涉及的内容较为底层&#xff0c;做了解即可&#xff0c;是以前学习《高性能Mysql》和《mysql是怎样运行的》的笔记整理所得。 redolog(后续使用redo日志表示)的核心作用是保证数据库的持久性。 在mysql系列5—Innodb的缓存中介绍过&#xff1a;数据和索引保存在磁盘上…...

静态时序分析:线负载模型的选择机制

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html 线负载模型及其选择 线负载模型仅在Design Compiler线负载模式&#xff08;非拓扑模式&#xff09;下时使用&#xff0c;它估算了导线长度和扇出对网线的电阻、电容和面积的影响&#xff…...

git 中 工作目录 和 暂存区 的区别理解

比喻解释 可以把工作目录和暂存区想象成两个篮子&#xff1a; 工作目录是你把所有东西&#xff08;文件和更改&#xff09;扔进去的地方。你正在修改的东西都放在这里。暂存区则是你整理好的东西放进第二个篮子&#xff0c;准备提交给老板&#xff08;提交到仓库&#xff09;…...

C++ 变量:深入理解与应用

C 变量&#xff1a;深入理解与应用 一、引言 C作为一种强大且广泛应用的编程语言&#xff0c;变量是其程序设计的基础构建块之一。变量允许我们在程序中存储、操作和访问数据&#xff0c;对于实现各种复杂的功能至关重要。正确地理解和使用变量&#xff0c;能够编写出高效、可…...

http报头解析

http报文 http报文主要有两类是常见的&#xff0c;第一类是请求报文&#xff0c;第二类是响应报文&#xff0c;每个报头除了第一行&#xff0c;都是采用键值对进行传输数据&#xff0c;请求报文的第一行主要包括http方法&#xff08;GET&#xff0c;PUT&#xff0c; POST&#…...

数据库的概念和操作

目录 1、数据库的概念和操作 1.1 物理数据库 1. SQL SERVER 2014的三种文件类型 2. 数据库文件组 1.2 逻辑数据库 2、数据库的操作 2.1 T-SQL的语法格式 2.2 创建数据库 2.3 修改数据库 2.4 删除数据库 3、数据库的附加和分离 1、数据库的概念和操作 1.1 物理数据库…...

《XML Schema 字符串数据类型》

《XML Schema 字符串数据类型》 1. 引言 XML Schema 是一种用于描述和验证 XML 文档结构和内容的语言。在 XML Schema 中&#xff0c;字符串数据类型是一种基本的数据类型&#xff0c;用于表示文本数据。本文将详细介绍 XML Schema 中的字符串数据类型&#xff0c;包括其定义…...

idea 开发Gradle 项目

在Mac上安装完Gradle后&#xff0c;可以在IntelliJ IDEA中配置并使用Gradle进行项目构建和管理。以下是详细的配置和使用指南&#xff1a; 1. 验证Gradle是否已安装 在终端运行以下命令&#xff0c;确保Gradle安装成功&#xff1a; gradle -v如果输出Gradle版本信息&#xff…...

Keepalived + LVS 搭建高可用负载均衡及支持 Websocket 长连接

一、项目概述 本教程旨在助力您搭建一个基于 Keepalived 和 LVS&#xff08;Linux Virtual Server&#xff09;的高可用负载均衡环境&#xff0c;同时使其完美适配 Websocket 长连接场景&#xff0c;确保您的 Web 应用能够高效、稳定地运行&#xff0c;从容应对高并发访问&…...

产品经理2025年展望

产品经理作为连接技术、设计与市场需求的桥梁&#xff0c;在快速变化的商业环境中扮演着至关重要的角色。展望2025年&#xff0c;随着技术的不断进步和消费者需求的日益多样化&#xff0c;产品经理的工作将面临更多挑战与机遇。 一、人工智能与自动化深化应用&#xff1a; 到…...

【信息系统项目管理师】第14章:项目沟通管理过程详解

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 一、规划沟通管理1、输入2、工具与技术3、输出二、管理沟通1、输入2、工具与技术3、输出三、监督沟通1、输入2、工具与技术3、输出一、规划沟通管理 定义:规划沟通管理是基于每个干系人或干系人群体的信息需求…...

串口DMA接收数据基本思路

串口DMA接收基本思路 串口DMA接收数据基本思路一、串口处理使用背景及常用处理方法二、串口DMA接收相关思路三、串口DMA发送相关思路 串口DMA接收数据基本思路 一、串口处理使用背景及常用处理方法 单片机经常有串口处理大量数据的场景&#xff0c;常用的串口处理数据方式有如…...

数据结构复习 (二叉查找树,高度平衡树AVL)

1.二叉查找树: 为了更好的实现动态的查找(可以插入/删除),并且不超过logn的时间下达成目的 定义: 二叉查找树&#xff08;亦称二叉搜索树、二叉排序树&#xff09;是一棵二叉树&#xff0c;其各结点关键词互异&#xff0c;且中根序列按其关键词递增排列。 等价描述: 二叉查找…...