不用Firebase也能实现Google登录?对比原生Android与FirebaseAuth两种方案

news2026/3/14 18:23:47
告别Firebase依赖深度解析Android原生Google登录方案与FirebaseAuth的抉择最近在重构一个老项目时我遇到了一个经典的技术选型问题用户认证模块。团队里有人坚持使用Firebase Authentication认为它省心省力也有人提出既然核心需求只是Google登录为什么不直接用原生方案减少对第三方平台的依赖这场讨论最终演变成了一次深入的技术调研。今天我就把这次调研的收获、踩过的坑以及两种方案在实际项目中的表现毫无保留地分享给你。这篇文章不是简单的教程罗列而是一份面向中高级Android开发者的技术选型指南。我们会深入对比两种实现路径一种是完全依赖Google官方Firebase平台的FirebaseAuth方案另一种则是绕过Firebase直接使用Google Sign-In SDK与自有后端集成的原生方案。我们将从配置复杂度、功能完整性、长期维护成本、数据自主权等多个维度进行拆解并附上可落地的代码示例和架构思考。无论你是在启动一个新项目还是优化现有架构相信这些内容都能为你提供有价值的参考。1. 方案全景理解两种技术路径的本质差异在深入代码之前我们必须先厘清两种方案的根本区别。这不仅仅是选择哪个SDK的问题而是选择了两种不同的技术架构和运维哲学。FirebaseAuth方案本质上是一种“托管式”服务。你将自己的用户认证逻辑几乎完全外包给了Google的Firebase平台。你的应用通过Firebase SDK与Google服务通信而Firebase后端则帮你处理令牌验证、用户状态管理、与Google服务器的安全交互等所有繁重工作。对于开发者而言这极大地简化了客户端代码你只需要关心如何触发登录流程和接收登录结果。注意选择FirebaseAuth意味着你引入了一个强大的中间层但也将用户身份验证的核心流程与特定厂商的服务深度绑定。而原生Google登录方案则是一种“直连式”的实现。你的Android应用直接与Google的OAuth 2.0授权服务器交互获取代表用户身份的ID Token或Access Token。随后你需要自行将这个令牌传递给你的应用后端服务器由后端服务器负责向Google验证令牌的有效性并在此基础上建立你自己的用户会话和身份体系。这种方式下Firebase完全退出了舞台。为了更直观地对比我们来看一下两种方案的核心流程与责任划分对比维度FirebaseAuth 方案原生Google登录方案核心依赖firebase-authSDK,google-services插件play-services-authSDK (Google Sign-In)令牌验证方Firebase 后端服务器你自己的应用后端服务器用户信息存储可存储在Firebase Firestore/Realtime DB或同步到自有后端完全存储在你自己的数据库如PostgreSQL, MySQL配置复杂度中高需Firebase控制台、google-services.json中需Google Cloud Console配置OAuth 2.0客户端功能完整性高集成多种登录方式、UI模板、安全规则基础仅完成Google身份验证其余功能需自研数据自主权低用户标识符依赖Firebase UID高完全掌控用户身份映射与数据网络请求应用 ↔ Firebase ↔ Google应用 ↔ Google 应用 ↔ 你的后端从表格可以看出FirebaseAuth提供的是“一站式”解决方案而原生方案则要求你搭建“流水线”的各个环节。这个根本差异会直接影响到后续的每一个开发决策。2. 实战原生方案从零构建不依赖Firebase的Google登录让我们暂时忘掉Firebase看看如何仅凭Android SDK和你的后端服务实现一套健壮的Google登录。这个过程会让你更深刻地理解OAuth 2.0在移动端的运作机制。2.1 前期准备在Google Cloud Console上的关键配置首先你需要一个Google Cloud Platform (GCP) 项目。没错即使不用Firebase这一步也绕不开因为Google登录的权限审核和客户端标识管理都在这里。访问并创建项目打开 Google Cloud Console创建一个新项目或选择一个现有项目。这个项目将作为你应用身份的唯一管理者。配置OAuth 2.0客户端ID在左侧导航栏找到“API和服务” - “凭据”。点击“创建凭据”选择“OAuth 2.0 客户端 ID”。应用类型选择“Android”。你需要填写两个关键信息软件包名称必须与你Android应用build.gradle中的applicationId完全一致。SHA-1 证书指纹用于确保只有你的应用或特定构建变体可以使用这个客户端ID。获取方式如下# 对于调试密钥库默认在 ~/.android/debug.keystore keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android # 对于发布密钥库请替换your_keystore.jks和别名 keytool -list -v -keystore path/to/your_keystore.jks -alias your_alias从输出中找到“SHA1”指纹并填入GCP控制台。重要提示如果你有多个构建变体如debug, release, staging且使用不同的签名证书你需要为每个变体分别添加一个SHA-1指纹到同一个OAuth客户端ID下或者创建不同的客户端ID。获取你的客户端ID创建成功后你会获得一个以.apps.googleusercontent.com结尾的客户端ID。请妥善保存它将是连接你应用和Google服务的钥匙。至此云端配置完成。你会发现这里并没有下载任何google-services.json文件因为我们已经跳过了Firebase环节。2.2 客户端集成精简的Android代码实现接下来我们在Android项目中集成Google Sign-In SDK。首先在模块级的build.gradle.kts或build.gradle中添加依赖dependencies { implementation(com.google.android.gms:play-services-auth:21.0.0) // 使用最新稳定版本 // 其他依赖... }现在让我们编写核心的登录逻辑。与FirebaseAuth方案不同我们不再需要FirebaseAuth实例而是直接操作GoogleSignInClient。// MainActivity.kt import android.content.Intent import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInClient import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.common.api.ApiException import kotlinx.coroutines.* // 假设使用协程处理网络请求 class MainActivity : AppCompatActivity() { private lateinit var googleSignInClient: GoogleSignInClient private val coroutineScope CoroutineScope(Dispatchers.Main SupervisorJob()) private val RC_SIGN_IN 9001 // 任意请求码 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 1. 配置Google登录选项 // 注意这里我们请求了idToken这是后端验证所必需的 val gso GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(YOUR_WEB_CLIENT_ID_HERE) // 关键使用Web客户端ID .requestEmail() .requestProfile() // 可选项用于获取用户基本资料 .build() // 2. 构建GoogleSignInClient googleSignInClient GoogleSignIn.getClient(this, gso) // 3. 检查是否已有账户登录静默登录 val account GoogleSignIn.getLastSignedInAccount(this) if (account ! null) { // 用户已登录直接使用account中的信息或向后端验证令牌有效性 handleLoggedInAccount(account) } // 绑定登录按钮点击事件 findViewByIdView(R.id.btn_google_signin).setOnClickListener { signIn() } } private fun signIn() { val signInIntent googleSignInClient.signInIntent startActivityForResult(signInIntent, RC_SIGN_IN) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode RC_SIGN_IN) { val task GoogleSignIn.getSignedInAccountFromIntent(data) try { // 登录成功获取GoogleSignInAccount对象 val account task.getResult(ApiException::class.java) Log.d(TAG, Google Sign-In成功获取到ID Token: ${account.idToken?.take(20)}...) // 关键步骤将ID Token发送给你的后端服务器进行验证 account.idToken?.let { idToken - verifyTokenWithYourBackend(idToken) } } catch (e: ApiException) { // 登录失败 Log.w(TAG, Google Sign-In失败错误码: ${e.statusCode}, e) when (e.statusCode) { GoogleSignInStatusCodes.SIGN_IN_CANCELLED - showToast(用户取消了登录) GoogleSignInStatusCodes.SIGN_IN_FAILED - showToast(登录失败请重试) // 处理其他错误码... } } } } private fun verifyTokenWithYourBackend(idToken: String) { // 这里发起网络请求将idToken发送到你的后端API进行验证 coroutineScope.launch { try { val response withContext(Dispatchers.IO) { // 假设你使用Retrofit yourApiService.authenticateWithGoogle(GoogleTokenRequest(idToken)) } if (response.isSuccessful) { val authResult response.body() // 后端验证成功返回了你自定义的会话令牌如JWT和用户信息 saveCustomSessionToken(authResult?.yourAppToken) updateUIForLoggedInUser(authResult?.userInfo) } else { // 后端验证失败 showToast(服务器验证失败) } } catch (e: Exception) { Log.e(TAG, 网络请求异常, e) showToast(网络连接异常) } } } private fun handleLoggedInAccount(account: GoogleSignInAccount) { // 处理已登录的账户例如检查本地存储的自定义令牌是否过期 // 如果过期可以用account.idToken重新向后台请求新令牌 } override fun onDestroy() { super.onDestroy() coroutineScope.cancel() } companion object { private const val TAG GoogleSignInNative } }这段代码的核心逻辑非常清晰配置与初始化使用从GCP获取的Web客户端ID注意不是Android客户端ID来配置GoogleSignInOptions。requestIdToken是必须的因为后端验证需要这个令牌。启动登录通过GoogleSignInClient发起授权流程。处理结果在onActivityResult中获取GoogleSignInAccount其中包含关键的idToken。后端验证将idToken发送到你自己的后端服务器进行验证并换取应用自身的认证凭证。2.3 后端验证完成身份链路的最后一环客户端拿到idToken只是第一步这个令牌必须在你的后端服务器上经过Google的验证才能确认用户的真实身份。这是一个关键的安全步骤防止客户端伪造令牌。以下是一个简单的Node.js (Express) 后端验证示例// server.js const express require(express); const { OAuth2Client } require(google-auth-library); const app express(); app.use(express.json()); // 初始化OAuth2客户端使用同一个Web客户端ID const CLIENT_ID YOUR_WEB_CLIENT_ID_HERE.apps.googleusercontent.com; const client new OAuth2Client(CLIENT_ID); app.post(/api/auth/google, async (req, res) { const { idToken } req.body; if (!idToken) { return res.status(400).json({ error: ID Token is required }); } try { // 1. 验证ID Token const ticket await client.verifyIdToken({ idToken: idToken, audience: CLIENT_ID, // 指定受众必须是你的客户端ID }); const payload ticket.getPayload(); const userId payload[sub]; // Google用户的唯一标识符 const email payload[email]; const name payload[name]; const picture payload[picture]; console.log(用户 ${name} (${email}) 已验证成功); // 2. 检查用户是否首次登录查询你自己的数据库 let user await findUserByGoogleId(userId); if (!user) { // 首次登录在数据库中创建新用户记录 user await createUser({ googleId: userId, email: email, name: name, avatar: picture, // ... 其他自定义字段 }); } // 3. 生成你自己应用的访问令牌例如JWT const yourAppToken generateJWTForUser(user); // 4. 返回令牌和用户信息给客户端 res.json({ success: true, token: yourAppToken, // 你自定义的会话令牌 user: { id: user.id, email: user.email, name: user.name, // ... 只返回必要的用户信息 } }); } catch (error) { console.error(Token验证失败:, error); res.status(401).json({ error: Invalid token }); } }); // 辅助函数根据Google用户ID查找本地用户 async function findUserByGoogleId(googleId) { // 实现你的数据库查询逻辑例如使用Prisma、Sequelize或原生SQL // return await db.user.findUnique({ where: { googleId } }); } // 辅助函数创建新用户 async function createUser(userData) { // 实现你的数据库插入逻辑 // return await db.user.create({ data: userData }); } // 辅助函数生成JWT function generateJWTForUser(user) { const jwt require(jsonwebtoken); const JWT_SECRET process.env.JWT_SECRET; return jwt.sign( { userId: user.id, email: user.email }, JWT_SECRET, { expiresIn: 7d } ); } app.listen(3000, () console.log(Auth server running on port 3000));提示在后端验证时务必验证audience受众与你的客户端ID一致这是防止令牌被其他应用重放攻击的关键。同时verifyIdToken方法会自动检查令牌的签名、发行者issuer和有效期。至此一个完整的、不依赖Firebase的Google登录流程就实现了。客户端获取身份凭证后端验证凭证并建立自己的用户会话体系数据完全掌控在自己手中。3. FirebaseAuth方案再审视便捷背后的权衡现在让我们回过头来更深入地审视FirebaseAuth方案。它的便捷性毋庸置疑但这份便捷是否是无代价的我们通过几个实际开发中的场景来分析。配置与集成的“黑盒”。FirebaseAuth的初始配置如添加google-services.json文件、在Firebase控制台启用登录提供商确实提供了图形化引导。然而当出现问题时比如SHA-1指纹不匹配导致登录失败或者default_web_client_id配置错误调试过程往往需要你在Firebase控制台、Google Cloud Console和本地项目之间来回切换排查链条较长。相比之下原生方案的配置虽然步骤明确但所有环节GCP控制台、后端代码都是透明且直接可控的。功能完整性的双刃剑。FirebaseAuth不仅仅提供登录它是一整套解决方案的入口。例如多提供商登录只需在控制台点选即可轻松添加Facebook、Twitter、GitHub等登录方式。预构建UIFirebaseUI-Android库提供了开箱即用的登录界面大幅缩短开发时间。深度集成登录状态可直接与Firestore、Cloud Storage等Firebase服务安全规则绑定。这些功能对于快速原型开发或功能相对简单的应用极具吸引力。但问题在于如果你只需要Google登录这些附加功能就变成了不必要的依赖和复杂度。更关键的是FirebaseAuth的用户标识符是Firebase生成的UID而不是Google的原始用户ID (sub)。这意味着如果你的应用未来需要迁移或与不使用Firebase的其他服务集成这个UID可能无法直接对应存在潜在的厂商锁定风险。网络延迟与可用性。FirebaseAuth增加了一次额外的网络跳转你的应用 → Firebase服务器 → Google服务器。在绝大多数情况下这对用户体验的影响微乎其微。但在网络条件不佳或对延迟极度敏感的场景下这多出来的一跳可能成为考量因素。原生方案是应用直接与Google服务器通信理论上路径更短。成本考量。Firebase Auth本身在用量不大时是免费的但它的价值在于引导你使用Firebase生态的其他付费服务如Cloud Firestore、Cloud Functions。如果你的应用规模增长需要更复杂的用户管理或数据分析可能会自然而然地走向Firebase全家桶。原生方案的前期开发成本更高但后期的运维成本和架构灵活性可能更具优势。4. 技术选型决策框架如何为你的项目选择经过前面的对比你可能已经对两种方案有了更立体的认识。但具体到你的项目该如何选择呢我总结了一个简单的决策框架你可以根据项目的阶段、团队和未来规划来对号入座。优先选择FirebaseAuth方案如果项目处于MVP或快速验证阶段你的首要目标是验证想法快速推出产品。FirebaseAuth能让你在几小时内集成稳定的登录功能把精力集中在核心业务逻辑上。团队规模小或移动端经验不足Firebase提供了详细的文档、错误代码和社区支持能降低开发门槛和运维负担。应用已深度集成Firebase生态如果你已经在使用Cloud Firestore、Cloud Messaging或Crashlytics那么继续使用FirebaseAuth能获得无缝的集成体验和数据联动。需要支持多种社交登录计划在未来几个月内添加Facebook、Apple等登录方式Firebase控制台的一站式管理会非常方便。优先考虑原生Google登录方案如果你对数据主权和架构自主性有高要求不希望用户身份体系与任何特定第三方服务绑定追求技术栈的纯净和可控。已有成熟的后端用户体系你的后端已经有一套完整的用户注册、管理、会话系统比如基于JWT或Session只需要将Google作为一种快捷的登录方式集成进来。团队具备全栈能力或后端团队强势有能力设计和实现安全的令牌验证后端并且希望前后端的认证逻辑清晰、解耦。有明确的长期发展规划或迁移预期考虑到未来可能的技术栈调整、成本优化或合规要求希望从一开始就保持架构的灵活性。应用对包大小极其敏感虽然play-services-auth本身也不小但避免引入整个firebase-bom依赖树可能对缩减包大小有细微帮助。一个折中的混合策略 在实际项目中还有一种常见的模式客户端使用原生Google Sign-In SDK获取ID Token然后将其发送到你的后端后端再使用Firebase Admin SDK来验证这个令牌。这样做的好处是客户端摆脱了Firebase依赖更轻量。后端依然可以利用Firebase Admin SDK强大的令牌验证和用户管理能力如检查令牌吊销状态。用户标识符可以在后端统一映射为你自己的用户ID。这种方案结合了两者的优点但需要后端服务的支持。它的代码示例如下// 后端 - 使用Firebase Admin SDK验证Node.js示例 const admin require(firebase-admin); // 初始化Firebase Admin SDK使用服务账户密钥 admin.initializeApp({ credential: admin.credential.cert(serviceAccountKey), }); app.post(/api/auth/google-hybrid, async (req, res) { const { idToken } req.body; try { // 使用Firebase Admin验证ID Token const decodedToken await admin.auth().verifyIdToken(idToken); const uid decodedToken.uid; // Firebase UID const email decodedToken.email; // 然后将Firebase UID与你数据库中的用户关联 // const localUser await findOrCreateUserByFirebaseUid(uid, email); // 生成你自己的应用令牌... // res.json({ token: yourJWT, user: localUser }); } catch (error) { console.error(Firebase Token验证失败:, error); res.status(401).json({ error: Invalid token }); } });这个混合方案在追求架构清晰度和利用成熟服务之间取得了不错的平衡是我在不少中型项目中推荐的做法。5. 进阶考量与实战避坑指南无论选择哪种方案在实际开发中都会遇到一些共性的挑战和进阶问题。这里分享几个我踩过坑后总结的经验。令牌管理与安全。ID Token是有生命周期的通常1小时。你不能假设一次登录后令牌永远有效。客户端需要处理令牌刷新的逻辑。原生方案中可以使用GoogleSignInClient.silentSignIn()尝试无交互刷新如果失败再引导用户重新登录。FirebaseAuth内部会自动管理令牌刷新。后端每次收到客户端发来的ID Token进行验证确保其新鲜有效。对于需要持久会话的场景验证ID Token后应颁发一个你自己控制的自定义会话令牌如JWT并设置合理的过期时间和刷新机制。用户信息同步与去重。同一个用户可能用不同的Google账户不同邮箱登录也可能未来想绑定其他登录方式。在你的用户表中设计一个灵活的identities关联结构会很有帮助。例如-- 简化的用户表结构示例 CREATE TABLE users ( id UUID PRIMARY KEY, created_at TIMESTAMP ); CREATE TABLE user_identities ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id) ON DELETE CASCADE, provider VARCHAR(50), -- 如 google, facebook provider_user_id VARCHAR(255), -- 如Google的 sub email VARCHAR(255), UNIQUE(provider, provider_user_id) -- 防止同一第三方账户关联多个本地用户 );这样当一个新的Google账户登录时你可以通过provider和provider_user_id查找是否已存在关联。如果不存在再检查邮箱是否已被其他本地用户使用根据业务规则决定是创建新用户还是合并账户。Android生态适配。从Android 14开始Google推出了新的Credential ManagerAPI旨在统一密码、通行密钥和第三方登录包括Google的体验。虽然目前play-services-authAPI仍然可用且稳定但关注新API的演进是必要的。如果你的应用最低支持版本较高如API 24可以开始评估Credential Manager它提供了更现代、更一致的编程模型。调试与排查。当登录流程出现问题时系统的日志是你的第一手资料。对于原生方案关注GoogleSignInStatusCodes中的错误码如SIGN_IN_NETWORK_ERROR、SIGN_IN_FAILED等。对于FirebaseAuth方案除了Google的错误还要关注Firebase的FirebaseAuthException及其错误码。一个非常实用的调试技巧是在开发初期在后端验证令牌的代码中将验证成功的令牌payload完整地打印出来注意不要在日志中泄露生产数据。这能帮你确认你收到的令牌确实包含了预期的用户信息并且audience等字段是正确的。最后关于测试。务必为你的登录流程编写单元测试和集成测试。模拟网络错误、用户取消、令牌无效等各种边界情况。对于原生方案你可以使用MockWebServer来模拟后端API的响应。对于FirebaseAuth虽然模拟其内部行为较复杂但你可以通过依赖注入在测试中替换真实的FirebaseAuth实例为模拟对象从而测试你的业务逻辑是否正确处理了各种登录状态。技术选型没有绝对的银弹。FirebaseAuth的“快”与原生方案的“稳”对应的是项目不同阶段和不同团队的不同诉求。在我经历的项目中早期快速迭代阶段使用FirebaseAuth快速上线待业务稳定、团队扩充后再逐步将核心身份逻辑迁移到自主可控的后端是一种被验证过的平滑演进路径。关键在于无论选择哪条路都要清晰地了解其背后的代价和未来的可能性而不是被“便捷”的表象蒙蔽了架构上的长期考量。希望这份深度对比能帮助你做出更明智的决策。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

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

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

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

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

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

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

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