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 个.trkey 全部有译文,各语言文件无重复键,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 条。