You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

14 KiB

apps/client 全量体检(2026-09-03)

扫描范围:428 个 dart 文件 / 187,759 行 / 41 个语种。 工具:dart analyze 全量 + 括号配平的自定义扫描脚本 + 逐条 grep 反查。 未做:真机运行、动态链路验证、local_plugins/ 下 25 个原生插件的内部审查。

统计:analyzer 1,222 项(1,049 info + 173 warning)。


必须先修

01. 32 27 个翻译 key 在全部 41 个语言文件里都不存在 —— 用户可见 ✅ 已修(2026-09-03)

修复记录:26 个 key 已补入全部 41 个语言文件(1,066 条);第 27 个 tips 改为复用 项目里已有的 tip(30 处在用),不新增同义重复键。 复查结果:代码里生效的 1,151 个 .tr key 全部有译文,各语言文件无重复键, dart analyze 全量 1,222 → 1,222 项、0 error(无新增问题)。

修正初版报告:初版列的 32 个里有 5 个只出现在被注释掉的代码里,界面上并不会显示, 已从清单剔除:codeSend、clearInput、ttsInterruptToggle、unsupportedMode、 checkUpdateTooFrequent。真正生效的是 27 个。 (pleaseEnterVerificationCode 在 :477 是生效的,未受此修正影响。)

原始问题描述

GetX 的 .tr 找不到 key 时原样返回 key 本身,不报错、不回退中文。这 32 处在 包括简体中文在内的每一种语言下,界面上直接显示英文标识符。

命中主流程:

key 位置 表现
pleaseEnterVerificationCode login_controller.dart:477 登录页显示 pleaseEnterVerificationCode
codeSend login_controller.dart:331 发送验证码按钮
userDataParseFailed login_controller.dart:718 登录失败提示
notCharging home_controller.dart:513 首页设备卡片充电状态
finishRecording / pauseRecording / markFavorite / unmarkFavorite / enterFileName meeting_record_view.dart 会议录音页控制按钮
monthlyTranscriptionDuration / industrySpecificSummaryTemplate / distinguishDifferentSpeakers / importAudioFromAppWeb / findMore / bindImmediately / autopilotTemplateSummary member_bottom_sheet.dart 会员权益弹窗 6 条文案
clearInput text_translation_view.dart:84 文本翻译清空按钮
ttsInterruptToggle / tips agent_view.dart:319, agent_controller.dart:1773 智能体页
notBound goodsvip_view.dart:363 VIP 商品页
unsupportedMode translation_controller.dart:343 通话翻译
storageBytes / storageUploads meeting_stats_view.dart:226,232 用量统计
checkUpdateTooFrequent settings_controller.dart:319 设置页
scanIdle / scanning / startScan / stopScan / discoveredDevices / noDevicesFound / unnamedDevice / disconnect devices_view.dart 杰理扫描页(本项目不走,优先级低)

修法:先补 zh_cn / en_us,再批量灌其余 39 语。 ⚠️ 语言文件里单双引号两种写法都存在,补之前两种都要查,否则 const map 会在 build 阶段撞重复键。

遗留:防回归的 CI 检查还没加(见第 04 条),现在仍然可能再漏进新的缺失 key。

02. 微信 appSecret 硬编码 + 无条件打印进设备日志

weixinLogin.dart:148   print("_getTokenUrl 数据获取${_appId},${_appSecret}");
weixinLogin.dart:160   print("_getRefreshTokenUrl 数据获取${_appId},${_appSecret}");
login_service.dart:9   appSecret: 'c792a0387c339f7b5c85d439466c67b7'
pay_service.dart:41    appSecret: '9ceb39df312b3ba95a229b2aa0d15496'

裸 print() 不受 kReleaseMode 约束,正式包照打。(同项目 appleLogin.dart 的 print 都套了 kDebugMode,这两行漏了。)拿到 appSecret 就能以应用身份换 access_token。

修法:先删这两行 print(零成本止血)。appSecret 本身应挪到服务端(客户端只发 code); 一时改不动至少改成后端 AppConfig.env() 下发。已进过 git 的 secret 必须视为已泄漏,去微信开放平台重置。

03. 支付宝已签名订单串被打进日志

pay_service.dart 有 68 处裸 print(),全文件零 kDebugMode 门禁。

pay_service.dart:732   print('支付宝支付订单: $orderInfo');   ← 含服务端签好的 sign
pay_service.dart:610   print('创建支付订单: $response');
pay_service.dart:836   print('创建支付宝订单: $response');

修法:整文件 print 换成项目已有的 Logger(自带 kReleaseMode ? warning : debug 分级); 涉及订单串/响应体的直接删,不要降级。


高

04. 30 个语种各缺 160+ 个正在使用的 key

全项目 key 并集 1,599 个:

语种 key 数 覆盖率
zh_cn en_us(2 个) 1,555 97%
de es fr ja ko pt ru zh_hk zh_tw(9 个) 1,541 96%
ar bg cs da el … (28 个) 1,322 83%
tr_tr 1,320 83%
nl_nl 1,213 76%

荷兰语/土耳其语等用户会在自动回连授权弹窗、蓝牙扫描页、设备绑定页看到 autoConnectConsentTitle 这样的标识符。

修法:把「以 en_us 为基准做 key 差集」做成 CI 检查,仓库已有的 tools/i18n_audit.py 加一条覆盖率断言。

05. AppConfig.getproduct() 的地雷仍有 6 处调用 ✅ 已修(2026-09-03)

修复记录:appconfig.dart 的 getproduct() 与 agentsystemPrompt() 都改成了 显式循环 + 返回 null(不引入 collection 依赖——项目 pubspec 里没有它)。 同时补了两道原本不成立的前置条件:

  • 未初始化时返回 null:config 是 static late,user_getappconfig 还没回来时 原实现会抛 LateInitializationError,null 契约照样不成立。
  • 空设备名返回 null:devicename.contains('') 恒为 true,原实现会把产品表里的 第一个产品当成匹配结果返回。调用点本来就都紧接着判 deviceName != '',无一依赖此行为。

6 个调用点的代码没动——它们写的 if (product != null) 现在才真正开始工作。 另外把三处「不要用 getproduct,它会抛」的注释改写成了真实理由(那三个本地 _findProduct 有更宽的双向包含匹配规则,所以保留,见第 10 条)。 dart analyze 对改动文件 0 新增告警。

内部 firstWhere 没有 orElse,查不到抛 StateError 而不是返回 null。 代码里已有三处注释写着「不要用这个函数」,但调用点一个没换。

调用方全写成 DBProduct? p = getproduct(...); if (p != null) {...}——那个 null 分支 永远等不到,异常被外层 try 吞掉后直接走兜底,全程不报错:

home_controller.dart:526        getCurrentDeviceType() → 设备类型退化成 unknown
home_controller.dart:609
translation_controller.dart:1490  broadcastPeer 恒为 false
translation_controller.dart:1584
agent_controller.dart:368       TTS 打断偏好读不到
ble_manager.dart:1055

同类:appconfig.dart:35 的 agentsystemPrompt() 也是无 orElse 的 firstWhere。

修法:改成 firstWhereOrNull(collection 包已在依赖里)。改完那 6 个 if (p != null) 才真正开始工作。

06. 耳机配对页整页硬编码中文

bes_scan_view.dart 是恒玄耳机唯一配对入口、新用户必经流程,一句都没走 .tr:

:223  Text('添加 AI 耳机($_targetName)')
:274  Text('没有找到 $_targetName 耳机')
:282  '请先在系统「设置 → 蓝牙」里连接 …'
:86   Get.snackbar('连接成功', …)
:190  Get.snackbar('连接失败', …)

07. 会议转写编辑框每次重建都被重置

speech_tab.dart:142 在 Obx 回调里 new TextEditingController(text: item['content'])。 Obx 每次重建都造一个新的:用户正在改的发言人文字会被打回原文,旧 controller 一个都没 dispose。

同类(仅泄漏、不影响功能):profile_view.dart:148、profile_meeting_view.dart:12、 translation_list_view.dart:347。

修法:speech_tab 改 StatefulWidget,controller 按 item['id'] 缓存在 State 里、dispose 时统一释放。

08. 两端都全局放开了明文 HTTP

ios/Runner/Info.plist                    NSAllowsArbitraryLoads = true
android/…/AndroidManifest.xml:73         usesCleartextTraffic="true"
android/…/network_security_config.xml    base-config 全放开 + certificates src="user"

全站可被降级到明文(业务后端、支付回调、会议音频上传都在口子里),而实际需要明文的 只有讯飞声音复刻一家:timbre_controller.dart:107,128,155,200,241 (opentrain.xfyousheng.com / avatar-hci.xfyousheng.com)。 Apple 对全局 NSAllowsArbitraryLoads 会要求提交理由说明。

修法:iOS 用 NSExceptionDomains 只放 xfyousheng.com;Android base-config 改 cleartextTrafficPermitted="false" 再加 domain-config。顺带去掉 certificates src="user" (当前配置默认接受用户证书 = 默认接受抓包代理)。


中

09. 付费页在卖一个已经关掉的功能

goodsai_view.dart:324                'helpCallAndSMS'.tr   ← AI 商品购买页卖点
assistant_directive_service.dart:48  'SEND_message': '本版已移除短信与联系人能力'

用户付费后说「给爸爸发短信」,什么都不会发生。 修法:摘掉这张 feature card,或去百炼后台把这两个 tool 也摘掉并恢复端侧实现。两边同时留着是最差组合。

10. 「按设备名找产品」有 5 套各写各的实现

appconfig.dart:53                    getproduct()          ← 会抛异常那个
home_controller.dart:89              _findProductByName()
bes_ota_upgrade_controller.dart:208  _findProduct()
ota_core_contropller.dart:429        findProduct()
bes_device_auth.dart:269             内联实现

匹配规则互不相同,改一次规则要改 5 处。 修法:收敛成 AppConfig.findProduct(name),规则用 OTA 那版最完整的 (名字互相包含 → 唯一 devicetype==1 → null)。和第 05 条一起做。

11. eq_settings 是 1,911 行完全不可达的死代码

app_routes.dart:22 声明了 /eq_settings 常量,但 app_pages.dart 没有对应 GetPage, 全项目没有任何一处导航过去。模块内部 UI 标签也全是硬编码中文(音效/均衡器/默认/流行/摇滚…)。

12. 4 个调试页面挂在正式路由上,无任何门禁

bes_test、opus_test、speech_test、log_export 都在 app_pages.dart 注册为正常 GetPage, 正式包可达。bes_test 尤其敏感——能对耳机下发任意私有协议命令,而 0x5x/0x6x 段会切换耳机工作模式。

13. 60 处空 catch 把失败原因全吞了

分布在 34 个文件,top:volcano_tts_api_service.dart(6)、translation_controller.dart(3)、 translation_detail_controller.dart(3)、agent_controller.dart(3)、ble_manager.dart(3)。

修法:不必全改,先给网络与蓝牙相关的补一行 Logger.w——这两类线上最常出问题、最难复现。

14. 核心流程里还有一批中文硬编码

call_recording_controller.dart:470,763     「已标记」「录音已存入语音记录助手」
picture_translation_controller.dart:214,221
agent_controller.dart:1773,2446,2728        导航失败 / 播放提示
settings_controller.dart:278,279,301-306    清除配对设备确认框
calendar_controller.dart:345,528+           ${month}月${day}日 + 中国节假日表

15. 微信登录里残留着教程示例代码

weixinLogin.dart:193 的 postData() 往 jsonplaceholder.typicode.com(公开 API 教程站)发 POST。 全项目无调用。同文件 fetchData() 也只剩注释里的调用。删掉即可,顺带解决 :177/:208 打印接口响应体的 print。

16. 网络图片零缓存

8 处 Image.network,未引 cached_network_image。产品图/头像每次重建、滚动都重新走网络。


清理项

17. 1,222 项 analyzer 告警

358  deprecated_member_use      ← 升 SDK 时变硬错误,值得先看
198  prefer_const_constructors  ← 直接影响重建开销
191  avoid_print                ← 即第 02/03 条
 53  unused_import
 34  unused_element / 23 unused_field / 17 unused_local_variable
 14  dead_null_aware_expression(goods_base_controller 占 10 处)
  2  must_call_super

dart fix --apply 能自动清掉约 400 项。deprecated_member_use 建议升 Flutter 前单独排一次。

18. 两处 onClose 没调 super.onClose()

record_timer_controller.dart:88、translation_history_controller.dart:373。 GetX 的 ListNotifierMixin.onClose 标了 @mustCallSuper,不调则该 controller 的 worker 不回收。

19. 本地闹钟记录读回时不校验时间格式

fromDirective 走 _normalizeTime、格式不对返回 null,这条路径是稳的。 但 assistant_directive.dart:376 的 fromJson 直接 j['time'] ?? '00:00' 不校验, 而 :354/:355 的 hour/minute 是裸 int.parse(time.split(':')[…])—— 本地存储里有一条格式不对的旧记录,代办页整页渲染就抛。

20. CLAUDE.md 与代码已经对不上的几处

位置 文档说 代码实际
微信 Universal Link https://ymaikj.com/app/ https://yimai.games/app/。代码与 Runner.entitlements 的 applinks:yimai.games 一致,不是 bug;但第三条腿(微信开放平台登记 + yimai.games/.well-known/apple-app-site-association)需确认已同步,否则微信登录与支付静默失败
version_update_service 失败会把游客入口锁死 10 分钟 已修::156 成功后才写 _lastCheckTime,失败分支显式不写
bes_bluetooth_service.dart:296 — 注释写「AA 20 耳机状态」,而实现和下方长注释都明确是 AA 09。AA 20 恰是实测过、完全无回包的那个错误命令
Google Play 权限 — 已核对:SEND_SMS 与 MANAGE_EXTERNAL_STORAGE 确实是 XML 注释状态,GOOGLE_PLAY_RELEASE_CHECKLIST.md 判断正确。仍生效的受限权限是 QUERY_ALL_PACKAGES 和已废弃的 GET_TASKS,checklist §3 已覆盖

扫描过程中修正过的判断

  • 一度把 SEND_SMS / MANAGE_EXTERNAL_STORAGE 记成生效权限,逐行核对后确认是 XML 注释,已从清单移除。
  • appleLogin.dart 的 31 处 print 确认套了 kDebugMode,未列入第 02 条。