Unity接入华为HMS SDK:AGC配置、登录支付推送避坑指南

发布时间:2026/11/6 2:54:04

Unity接入华为HMS SDK:AGC配置、登录支付推送避坑指南 简介面向 Unity 开发者的一份华为 HMS SDK 接入示例工程聚焦 Unity 项目集成华为账号、游戏与推送等服务的完整工程样本适合具备一定 Unity 和 Android 打包基础、希望快速理解 HMS SDK 调用方式的读者参考。压缩包共 2000 个文件约 28.82MB内含 HuaweiSdkDemo 示例工程及配套依赖bin、info、class、java、jar 等文件对应 Android 侧库与构建产物cs、dll、asset、meta 等构成 Unity 脚本与资源目录xml、properties、project 等用于工程和权限配置另有 apk、log、txt 便于真机验证与问题排查。目前已有 1845 人学习。其价值在于提供较完整的工程结构和接口调用样例可对照了解 SDK 初始化、登录、推送等功能的接入位置并借现成配置观察 IL2CPP、AndroidManifest 权限及版本兼容性处理目录层级与元数据文件也便于理解依赖组织和包体构成减少从零搭建与排错的时间。1. 拿到 Demo 先别急着 ImportUnity 接华为 SDK 接的到底是什么很多人以为 Unity 接华为 SDK 就是导个包、填个 AppID、点一下运行。真正让人出错的地方在后面换一台机器出包就报 6003登录能过但支付拉不起推送 Token 拿到了但通知栏什么都没有。原因是这套东西从来不是「一个 SDK」而是 HMS SDK 的各 Kit 加上 AGC 的工程配置体系两边靠包名、签名指纹、agconnect-services.json 这三根钉子绑死。这份 Demo 的价值不在于让你抄几行 C#而在于它把初始化顺序、Kit 调用链和 Android 构建配置完整摆了一遍。适合需要把 Unity 项目上架应用市场、要华为帐号登录、内支付和推送的团队也适合接手别人工程、面对一堆 AAR 不知道从哪下手的同学。下面按我实际接入的顺序拆。2. 从 AGC 建应用到 Unity 工程落地包名、指纹与 json 三件套这条链路的起点不在 Unity在 AGC 控制台。很多 Demo 之所以「直接能跑」是因为作者把 agconnect-services.json 和自己的签名文件一起打包进去了你换个 keystore立刻降级成只能看不能跑。所以第一步不是打开 Unity而是把你项目的身份信息定死。2.1 AGC 侧要填的四项每一项都会在别处讨债在 AGC 里创建应用时填的东西看着是表单其实每项都对应后面一个报错。我一般会先把手头工程的包名、签名确定下来再动手建应用避免建完发现包名对不上又去改。参数填什么后面在哪里讨债应用包名与 Unity Player Settings 里的 Package Name 完全一致不一致时 SDK 初始化直接返回失败日志里通常只说「app info invalid」SHA-256 证书指纹你实际出包用的 keystore 的 SHA-256最常见的就是 6003登录、支付全挂应用分类与内容分级按实际上架类目选影响支付商品和部分 Kit 的开通审核需要开通的 Kit帐号、IAP、Push 等按需勾选没开通的 Kit 调用会返回权限类错误而不是崩溃证书指纹这块我用 keytool 取debug 包和 release 包是两套别只填一套# debug keystoreUnity 默认就用它出调试包 keytool -list -v \ -keystore ~/.android/debug.keystore \ -alias androiddebugkey \ -storepass android # release keystore上架前必须填这一套 keytool -list -v \ -keystore /path/to/your/release.keystore \ -alias your_alias输出里挑SHA256:那一行去掉冒号、全大写或全小写都行AGC 会自动规范化。这里有个血泪经验团队里如果有人用 Android Studio 出过包他的 debug keystore 和你机器上的不是同一个文件指纹天然不同。所以证书指纹列表我建议把每个参与出包的人的 debug 指纹都加进去别指望「我这儿能跑」。agconnect-services.json 从 AGC 下载后常见做法是放在 Unity 工程的Assets/下构建后它应该出现在 APK 的 assets 根目录。这一步必须验证不能靠猜# 出包后确认配置真的进包了 unzip -l app-release.apk | grep agconnect # 期望看到类似assets/agconnect-services.json如果这里什么都没有说明 Unity 没把它当资源打进包AGC 那套东西在你机器上等于没配置。2.2 工程目录和插件导入顺序别把 AAR 叠成一锅粥Unity 侧的华为 SDK 一般由三部分组成AGC 基础包Analytics、Crash、Remote Config 走这套、各 Kit 的 Unity 封装、以及一堆.aar在Assets/Plugins/Android/下。接手别人工程时我最先做的事是把旧的 AAR 清干净再导新的因为不同版本的同名库叠在一起Gradle 只会给你一句Duplicate class排查起来像黑匣子。导入顺序我固定成AGC 基础包 → 需要的 Kit 封装 → 自己的业务脚本。原因是 Kit 封装依赖 AGC 的初始化上下文你先导 Kit 再导基础包容易出现AGConnectInstance为空的情况。目录上Assets/Plugins/Android/只放 AAR 和AndroidManifest.xml业务 C# 脚本单独放一个目录出问题时能一眼看出是插件层还是业务层。另外如果你项目里已经有别的第三方 SDK先确认它的AndroidManifest.xml有没有和 HMS 的冲突。合并冲突最常见的表现是minSdkVersion被某一方抬高或者provider的 authorities 撞车。2.3 首次出包Gradle 模板、minSdk 与 ARM64 的硬性约束Unity 默认的 Gradle 模板不一定带华为的 Maven 仓库得手动开 Custom Gradle Template。在 Player Settings 里勾上Custom Main Gradle Template和Custom Gradle Properties Template然后在生成的mainTemplate.gradle里补仓库和插件// Assets/Plugins/Android/mainTemplate.gradle allprojects { repositories { google() mavenCentral() // 华为 SDK 的依赖都在这个仓库缺了就是各种 ClassNotFound maven { url https://developer.huawei.com/repo/ } } } // 如果用了 AGC 的 agcp 插件做自动配置 buildscript { dependencies { classpath com.huawei.agconnect:agcp:1.x.y.300 } } apply plugin: com.huawei.agconnectagcp的版本号不要凭记忆写去插件包里的package.json或 release notes 里对一下版本对不上 Gradle 会明确告诉你找不到。这一段的作用是让 Gradle 在编译期把agconnect-services.json解析成资源所以你如果漏了apply plugin运行时不会报「找不到文件」而是初始化直接失败反而更难查。构建参数上三条硬约束Scripting Backend 选 IL2CPPTarget Architectures 勾 ARM64现在上架基本都要 64 位minSdkVersion 不低于 21。如果项目里有老的三方库只出 armeabi-v7a会出现「华为设备装得上但登录 SDK 加载失败」的情况这种问题日志里只报个UnsatisfiedLinkError容易被误判成签名问题// 出包前用一小段编辑器脚本做自检比出包后再查快得多 #if UNITY_EDITOR using UnityEditor; using UnityEngine; public class HmsBuildCheck { [MenuItem(Tools/HMS/Check Build Settings)] public static void Check() { // 架构检查缺 ARM64 在部分机型上会直接加载失败 var arm64 PlayerSettings.Android.targetArchitectures .HasFlag(AndroidArchitecture.ARM64); if (!arm64) Debug.LogError(Target Architectures 未勾选 ARM64); // 后端检查Mono 后端下部分 AAR 的 JNI 调用会异常 if (PlayerSettings.GetScriptingBackend(BuildTargetGroup.Android) ! ScriptingImplementation.IL2CPP) Debug.LogError(Scripting Backend 需为 IL2CPP); // 包名检查和 AGC 上填的必须一致 Debug.Log($Package Name {PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android)}); Debug.Log($minSdk {PlayerSettings.Android.minSdkVersion}); } } #endif这段脚本没做什么高深的事只是把三个最容易翻车的开关做成一次点击。实际项目里我还会把当前使用的 keystore 路径打印出来因为「我以为我用的是 release 签名」这种情况出现的频率比想象中高。3. 初始化链路HMSInitialize 的时序与登录态获取Demo 里初始化往往就一行实际项目里这一行的位置决定了后面所有 Kit 能不能用。这一段把时序、回调和状态缓存拆开讲。3.1 为什么初始化必须早于第一个 Kit 调用HMS 的各 Kit 在内部都依赖一个已经就绪的应用上下文这个上下文由初始化过程建立。你把登录按钮放在首屏用户点得比初始化回调快就会拿到「服务不可用」。所以我的做法是在第一个场景的Awake里发起初始化用一个静态状态位挡住所有业务入口UI 上按钮先置灰回调成功再放开。using UnityEngine; using System; public static class HmsBootstrap { public static bool Ready { get; private set; } public static event Actionbool OnReady; // 由首个场景的启动脚本调用保证只跑一次 public static void Init() { if (Ready) return; // 接口名以你手上插件 HUAWEI_HMS 命名空间里的实现为准 // 不同大版本的封装方法名会有差异别照抄网上的旧写法 HMSInitialize.Init((success, msg) { Ready success; if (!success) Debug.LogError($[HMS] init failed: {msg}); OnReady?.Invoke(success); }); } }这里的关键不是那行Init而是Ready这个闸门。所有 Kit 调用前先判一次HmsBootstrap.Ready为 false 就直接返回并在 UI 上提示「服务初始化中」。这么做看起来笨但能把「偶现的登录失败」这一类玄学问题挡在门外。如果初始化一直失败先看两件事agconnect-services.json是否真的在 APK 里以及当前签名指纹是否在 AGC 列表里。这两条占了初始化失败的绝大多数。3.2 Account Kit 的静默登录与授权码换 Token帐号这块Unity 封装的入口在不同插件版本里长得不一样有的是HuaweiAccount有的是直接暴露AccountAuthManager。与其记方法名不如理解流程拉起授权 → 拿到 AuthAccount → 取 authorizationCode → 交给自己的服务端换 access token。客户端不要直接信任拿到的信息去做业务判定。Android 原生侧的等价写法是这样Unity 层只是薄封装// 静默/显式登录的通用形态 AccountAuthParams params AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM; AccountAuthService service AccountAuthManager.getService(context, params); service.startSignIn(activity) .addOnSuccessListener(authAccount - { // authAccount 里有 openId、displayName、avatarUri 等 String code authAccount.getAuthorizationCode(); // 把 code 回传给自己的服务端由服务端换 token bridgeToUnity(authAccount.getOpenId(), code); }) .addOnFailureListener(e - { // 用户取消登录也会走这里别一律当异常弹窗 bridgeErrorToUnity(e.getMessage()); });参数说明DEFAULT_AUTH_REQUEST_PARAM只申请基础信息如果业务要手机号或邮箱需要换成带 scope 的构造方式并且在 AGC 侧确认该 scope 已开通。没开通就申请返回的是权限错误而不是空值这个区别在排查时很重要。openId是用户在应用维度的唯一标识可以做本地缓存做「上次登录帐号」提示authorizationCode是一次性的有效期短必须立刻发往后端。我见过把 code 存在本地、下次启动再用的写法结果就是间歇性登录失败。3.3 回调线程与 Unity 主线程的桥接HMS 的回调大多在 Android 的 UI 线程或子线程触发而 Unity 的 GameObject API 只能在主线程调。直接在主线程外Instantiate一个 UI 预制体轻则报错重则崩溃。稳妥方案是用一个队列把回调塞回Update消费using System; using System.Collections.Generic; using UnityEngine; public class MainThreadDispatcher : MonoBehaviour { private static readonly QueueAction _queue new QueueAction(); // 任意线程都可调用只做入队 public static void Enqueue(Action action) { lock (_queue) { _queue.Enqueue(action); } } private void Update() { // 每帧统一消费保证 Unity API 都在主线程执行 while (true) { Action act; lock (_queue) { if (_queue.Count 0) break; act _queue.Dequeue(); } act?.Invoke(); } } }有了这个SDK 回调里只管MainThreadDispatcher.Enqueue(() { ... })业务逻辑不用再关心线程。这个类我几乎每个接第三方 SDK 的项目都会放一份属于不用后悔药的那类基础件。4. 三个 Kit 的最小可用接法登录、支付、推送Demo 通常只演示登录真上架至少要这三样。每个 Kit 我都只给「能跑通业务闭环」的量不铺开讲全部能力。4.1 Account Kit字段映射与首次登录的帐号绑定登录成功后的字段要根据业务做一次映射别直接把 SDK 返回对象丢给业务层否则换插件版本时全项目都要改。SDK 返回字段业务含义注意事项openId应用内唯一用户标识不要用 unionId 做本应用主键跨应用场景才需要它displayName昵称可能为空UI 要有兜底avatarUri头像地址有有效期不要长期缓存这个 URLauthorizationCode一次性授权码立即发后端换取 token首次登录要不要做帐号绑定取决于你的老用户体系。常见做法是用 openId 查一次自家库命中就直接登录没命中再走「绑定已有帐号 / 注册新帐号」的分支。这一步别放在客户端做判定条件删除本地数据就能绕过。4.2 IAP消耗型商品的补单与校验链路内支付是最容易被「能拉起就行」糊弄过去的一块。下单成功只是第一步发货必须由服务端确认客户端拿到的东西一律视为不可信。消耗型商品的完整链路是客户端发起购买 → 用户支付 → 拿到 purchaseToken 和订单号 → 发后端 → 后端校验并消耗 → 客户端收到发货结果后才给道具。// 拉起购买developerPayload 用来带自己的订单号 PurchaseIntentReq req IapClientHelper.createPurchaseIntentReq( IapClientConst.PRICE_TYPE_INAPP_CONSUMABLE, sku, developerPayload); // 自己的订单号务必带补单时靠它对齐 Iap.getIapClient(activity) .createPurchaseIntent(req) .addOnSuccessListener(result - { // result 里是待拉起的支付信息这里不是支付成功 startBillingFlow(result); }) .addOnFailureListener(e - { // 商品未配置、未开通 IAP 都会走这里 reportError(e.getErrorCode(), e.getMessage()); });参数上要注意PRICE_TYPE_INAPP_CONSUMABLE必须和 AGC 后台配的商品类型一致配成非消耗型却按消耗型下请求返回的错误信息很含糊只会让你怀疑人生。补单是这块的核心。用户支付后 App 被杀下一次启动必须能把这笔订单找回来。我的做法是本地记录 pending 状态的订单号带 developerPayload启动时先去后端查一遍状态没有结果再调一次未消耗查询。这块我没见到哪个 Demo 写全但它决定了你线上会不会收到「我付了钱没到账」的反馈。4.3 Push KitToken 获取、通知栏样式与点击透传推送的链路是 Token → 后端保存 → 后端下发。Token 会在某些情况下刷新所以别只在首次启动取一次。HmsInstanceId inst HmsInstanceId.getInstance(context); // APP_ID 是 AGC 上的应用 ID不是包名 String token inst.getToken(APP_ID, HCM); // 主动删除 Token用于用户退出登录 inst.deleteToken(APP_ID, HCM);HCM这个固定值不是业务参数别自己改。取 Token 需要网络且大概率要在子线程第一次拿到可能失败要带重试。点击透传这块通知消息和透传消息走的是两条路。通知消息由系统直接展示点击后拉起应用你自定义的键值放在启动 Intent 的 extras 里透传消息走HmsMessageService.onMessageReceived需要你自己决定展示方式。Unity 层要做的就是把这两条路上的参数统一成一个「路由 key → 页面」的映射别在每个入口各写一遍解析。通知栏的图标不要在 Unity 里配置它由AndroidManifest.xml里的 meta-data 决定配错的表现是通知栏显示一个纯白方块。5. 避坑清单6003、依赖冲突与「初始化成功但登录失败」这一章全部是我和同事踩过的按现象、原因、解决的顺序写照着比对能省掉大半天。5.1 指纹与签名不一致6003 的三种伪装现象一调试包登录一直报 6003换 release 包就正常。原因AGC 只填了 release 证书指纹Unity 调试包用的是 debug keystore。解决把~/.android/debug.keystore的 SHA-256 也加进 AGC 的指纹列表别嫌麻烦。现象二本地能跑打给测试同事的包就 6003。原因包签名用的 keystore 和填进 AGC 的不是同一个。这种情况常见于 CI 与本地各有一套签名配置。解决在构建脚本里固定 keystore 路径和别名并在出包日志里打印当前签名指纹和 AGC 上填的做一次人工比对。现象三全部配置都对清缓存后恢复正常。原因设备上残留了旧版本的应用数据和 HMS 侧的应用信息缓存。解决卸载重装一次清理 HMS Core 的应用数据后再试。这类问题没什么道理可讲但确实存在。5.2 Gradle 依赖冲突与 AndroidX 的连带反应现象编译期报Duplicate class或者运行时报NoClassDefFoundError。原因项目里同时存在新旧版本的同名 AAR或者接入的其他三方 SDK 自带了一份旧版华为库。解决在Assets/Plugins/Android/下全局搜一遍同名 AAR只留一份Gradle 侧可以加一段强制版本但更推荐从源头清理。现象开了 AndroidX 和 Jetifier 之后编包变慢甚至失败。原因Jetifier 在转换部分 AAR 时会出现兼容问题。解决如果插件版本已支持 AndroidX优先关掉 Jetifier 让 Gradle 直接走新依赖不确定就先用一份最小工程验证再合并进主工程。现象Main Gradle Template 配置完了但依赖压根没进包。原因只改了mainTemplate.gradle没勾选 Custom 模板Unity 用的还是内置模板。解决确认 Player Settings 里模板勾选状态构建后看gradleTemplate.properties是否生效。5.3 设备与 HMS Core 版本的验证边界现象在一台非华为设备上初始化成功登录却异常。原因登录依赖设备上的 HMS Core 服务非华为设备的服务版本和可用性都存在差异。解决把华为真机作为登录、支付的唯一验收环境其他机型只用来验证降级逻辑——比如登录不可用时是否能用游客模式继续玩。现象老机型上返回 907135003 一类的服务缺失错误。原因设备上的 HMS Core 版本过低不满足当前 Kit 的最低要求。解决在代码里做一次版本判断低于下限时引导用户更新或直接隐藏对应入口别让按钮点下去一片空白。现象模拟器上一切正常真机上推送收不到。原因推送依赖设备与厂商通道模拟器不具备完整的推送能力。解决推送相关的一切验证只在真机做模拟器上跑通只能说明代码没崩。6. 提审前最后一遍日志开关与一张自检表到这一步功能基本跑通了剩下的问题都在「别人机器上能不能复现」。我的习惯是出包前过一遍下面这张表每一条都用构建产物验证而不是看工程配置。检查项验证方式通过标准配置文件是否进包解压 APK 看assets/agconnect-services.json文件存在且内容非空签名指纹出包日志打印 SHA-256与 AGC 比对完全一致架构unzip -l看lib/arm64-v8a/存在且不为空初始化时序冷启动首屏日志初始化回调早于任何 Kit 调用登录返回真机登录一次看日志openId 非空code 能换到 token支付补单支付中杀进程再启动订单状态能从后端查回推送透传后端下发一条自定义键点击后跳到指定页面日志开关也值得单独说一句。HMS 侧的详细日志默认不开线上出问题时你只能看到一句「操作失败」。常见做法是在调试包里打开 SDK 的日志输出同时给自己每个 Kit 调用点加统一前缀方便一次性过滤// 统一日志前缀方便 adb logcat 一次过滤 public static class HmsLog { private const string Tag HMS_TRACE; public static void I(string step, string detail ) Debug.Log($[{Tag}] {step} {detail}); public static void E(string step, string detail ) Debug.LogError($[{Tag}] {step} {detail}); }配合adb logcat -s Unity就能把整个链路串起来看初始化开始、初始化完成、拉起登录、拿到 code、发给后端、后端返回。这条日志链在线上排查时特别好用尤其是用户反馈「点了没反应」的时候你能立刻区分是没拉起、拉起了但回调没回来、还是回来了但业务层吞了。我吃过一次亏项目上线前所有验证都在自己的机器上做自检表里「签名指纹」那一项心想肯定没问题就跳过了结果提审版本的 keystore 是同事配的 CI 签名和 AGC 上填的不一致整个登录模块在审核环境里直接不可用。从那以后我每次出包都强制走一遍这张表签名指纹这一项必须是打印出来肉眼看过的不再靠「应该是对的」。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/11/6 2:54:04

紫微斗数排盘可复现:时间口径、四化分层与批量校验

简介:面向紫微斗数研究者与命理爱好者的 PC Windows 排盘工具,免注册即可使用,重点解决三合、飞星、四化等不同流派排盘与命例管理需求。软件支持阳历、阴历、四柱三种输入方式,可选真太阳时,并提供太阴天地人三盘、冬…

2026/11/6 2:54:04

嵌入式工程师如何快速掌握AI边缘计算技术

1. 嵌入式工程师为何需要拥抱AI技术作为一名在嵌入式领域深耕多年的工程师,我完全理解同行们对学习AI的犹豫。我们习惯了与寄存器、时序图和硬件调试打交道,突然要转向神经网络、TensorFlow和Python,确实会让人望而生畏。但现实情况是&#x…

2026/11/6 2:54:04

深度学习中的marginalization:从概率积分到工程实践

1. 这个词不是“边缘化”,而是深度学习里最常被误读的数学操作“Marginalization”在中文技术圈长期被直译为“边缘化”,导致大量初学者一看到这个词就联想到社会学概念,甚至怀疑自己是不是点错了文章——这其实是个典型的术语翻译陷阱。它根…

2026/11/6 3:14:06

影响1553B板卡选型的七大核心指标

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 3:14:06

41:数字统计

/*** 题目名称&#xff1a;数1的个数 <p>* 题目来源&#xff1a;http://noi.openjudge.cn/ch0105/41/ <p>* 程序功能&#xff1a;输出指定范围整数中数字2出现的次数** author 潘磊&#xff0c;just_panleijust.edu.cn* version 1.0*/import java.util.Scanner;pub…

2026/11/6 3:14:06

40:数1的个数

/*** 题目名称&#xff1a;数1的个数 <p>* 题目来源&#xff1a;http://noi.openjudge.cn/ch0105/40/ <p>* 程序功能&#xff1a;输出所有小于等于指定正整数的正整数范围内数字1出现的次数** author 潘磊&#xff0c;just_panleijust.edu.cn* version 1.0*/import…

2026/11/6 3:14:06

42:画矩形

/*** 题目名称&#xff1a;画矩形 <p>* 题目来源&#xff1a;http://noi.openjudge.cn/ch0105/42/ <p>* 程序功能&#xff1a;根据指定参数输出矩形** author 潘磊&#xff0c;just_panleijust.edu.cn* version 1.0*/import java.util.Scanner;public class Main {…

2026/11/6 3:14:06

【成品设计】STM32户外便携太阳能充电器设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代&#xff0c;基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而&#xff0c;与配合度高的人类不同&#xff0c;宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态&#xff0c;更无法控制光照环境。实际应用中&#xff0c;我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:03:39

MCU晶振电路设计全解析:从负载电容计算到PCB布局调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代&#xff0c;基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而&#xff0c;与配合度高的人类不同&#xff0c;宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态&#xff0c;更无法控制光照环境。实际应用中&#xff0c;我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑