Rodger-Wang
66463e53f6
记忆中心客户端:拾忆页接服务端 + 写操作 UI + 本地提醒 + 每日播报
dart analyze 零错误,flutter build apk --debug 通过。
零期修缮
- tabTodo 补齐 41 个语言。此前只有 11 个文件有这个 key,其中 8 个的值是
'Remin'(不是任何语言里的词,像 Reminisce 打了一半),缺 key 的 30 个经 GetX
回退到 en_US 同样显示 'Remin' —— 41 个语言里 38 个是坏的,只是不报错。
- i18n_audit.py 补「缺失 key」检查。它此前**结构上无法发现缺失的 key**:循环遍历
的是该文件已有的 key,且对 baseline 里没有的直接 continue。这正是 tabTodo 在 30
个语言里悄悄丢掉而没人发现的原因。现以 zh_cn 为基准,实测报出 7243 处历史缺失
(40 个语言只有 1 个不缺)。加了 --fail-on-missing/--allow-missing 给 CI 卡「只增
不减」,默认不开——一上来就红会让人直接把检查关掉。
- pubspec 引入 timezone + flutter_timezone。zonedSchedule 只接受 tz.TZDateTime,
没有这两个依赖,提醒功能一行都写不了。
拾忆页从纯展示改成可写(此前最大的工作量遗漏)
页面原来没有 RefreshIndicator、没有 FloatingActionButton、卡片没有 onTap,
于是服务端的 add/update/del/complete 四个写接口在客户端一个调用点都没有,
user_edited 那套「保护用户修改」的机制完全空转。本次补齐:
- MemoryEditSheet:新增/编辑弹层,按分类分流表单(统一表存储 ≠ 统一表单)
- 卡片 onTap 打开编辑、左侧勾选框标记完成(灵感不给勾选框,它没有完成一说)
- 右下角 + 新增、RefreshIndicator 下拉刷新、带引导的空态页
- date_certain=false 的项单独归到「待定日期」小节。一次会抽出 8 条没写截止时间的
待办全堆在会议当天,不分开那一格就没法看了。
数据层
- MemoryItem / MemoryRemindSlot / MemoryReport 手写镜像服务端 pb。
**每个字段都经宽松解析器**(memInt/memBool/memStr),不直接 as int:
服务端删字段或老记录缺列时,non-nullable 会让整个 fromJson 抛 TypeError,
表现是整页空白而不是少一个字段(appconfig_model 已经为此踩过坑)。
单条解析失败只跳过那一条。
- 分类保留原始字符串而不是 enum 解析。服务端加分类只改常量不发包,
客户端 switch 不到就抛等于服务端一加分类就打崩所有存量 App。
CalendarEventCategory 加了 other 作为未知兜底,筛选与卡片分流都改成值比较
而不是穷举 switch。
- MemoryService 只缓存最近 90 天。GetStorage 是把整个 box 读进内存的 JSON,
一年三千多条全量缓存会让每次启动越来越慢。
- 拉取失败保留本地缓存并标 staleFromCache,页面显示行内「当前显示的是本地缓存」,
不清空、不弹红条。
- 勾选完成是乐观更新 + 失败回滚。不回滚会让用户以为已经勾上,下次进页面又变回来。
- memory_today / memory_upcoming 加进 AuthInterceptor 的红条抑制名单:
它们在启动与前台恢复时自动调,用户什么都没点就吃一条英文内部状态词很差。
删除 mock
mockCalendarEvents() 与它引用的 44 个 demo* i18n key 一并删除(41 个文件共 484 行)。
真实数据上线后混着演示数据会让人以为系统出错。
本地提醒(MemoryReminderScheduler)
- 通知 id 占 920000 起的固定区间,**逐条 cancel,绝不 cancelAll()** —— 仓库里已有
两个各自 new 的 FlutterLocalNotificationsPlugin 实例,cancelAll 会顺手干掉正在
录音的前台通知,表现是录音悄悄失去前台身份被系统回收。
- 单独的通知通道:录音提示是常驻低优先级通道,共用会让提醒被降级成静默。
- **只查权限不申请**。申请必须走 PermissionUtil(它收敛了过审要求的 48 小时冷却),
且只允许用户在权限管理页主动发起,这里主动申请等于绕过合规规则。
无权限时降级为「打开 App 才提醒」,不阻塞。
- 精确排期失败(Android 14 起 SCHEDULE_EXACT_ALARM 默认不授予)自动退化成
inexact 再试一次——不精确的提醒也比没有强,日志会记下来。
- 拿不到系统时区时按本机偏移找等价区兜底,不落回 UTC(那会让国内用户全部偏 8 小时)。
每日播报(MemoryDailySheet)
- **不 await 在启动路径上**。冷启动已串四次网络往返,仓库刚做过相反方向的优化
(6a64c3d0 把会议模板拉取改成 unawaited)。这里是进主页后异步拉、拿到再弹。
- 排在隐私政策与强更弹窗之后,且必须在登录态确认之后。
- 通话翻译进行中一律不播报:两者共用同一个 AVAudioSession,在对方刚调好的会话上
再设一次会把通话音频路由弄乱(iOS 那条 phone_call 分支被改坏过三次)。
- 关窗必须真停:speakStream 是队列式播放,只 stop 不清队列会「关了还在念下一句」。
- 播报文案本地拼,不调 LLM。
生命周期与账号
- 切前台的刷新挂在 AppLifecycleManager(仓库已有 emai/translation/permissions
三个各自实现的 observer,本类就是收敛点),全部 unawaited。
- 登出清空记忆数据与已排提醒。不清的话换账号会看到上一个人的日程花销灵感,
提醒也会继续按上一个账号的安排响。
助手闹钟迁移
SET_clock 改为双写:本地 reminders 保留一个版本(上传失败时本机仍可用),
同时写服务端 memory_item(category=alarm)。**幂等键用指令 id** —— 本类的按 id
去重只在本进程内存里,换机/重装/重启后全失效,服务端靠 (uid, client_key)
唯一索引兜住。认不出的重复规则按单次处理,宁可只响一次也不按猜的规则天天响。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
0b0ac16b3c
记忆中心后端:memory 模块 + 会议待办抽取 + 周报月报
按设计文档一~三期的后端部分落地。全量 go build / go vet 通过,新增 22 个测试。
memory 模块(modules/memory,由 home 装载,路径 /api/home/memory_*)
11 个接口:list / today / add / update / del / complete / upcoming / stats /
getreport / confirmreport / listreports。
表由 proto + gorm tag 生成(不是手写 DDL——本仓所有业务表都走
mysql.CreateTable + AutoMigrate)。protobuf 没有 DECIMAL/DATE/DATETIME/null,
逐个落成:金额 int64 存分(与 pay/结算同口径,绝不用 double)、happen_date
string + gorm type:date、next_remind_at int64 unix 秒(0=无)。
几处刻意的选择
- 建表失败只 Errorln 不外传。lego.Run 里 Init 返回非 nil 就 Panicf,而五个服务
同镜像、entrypoint 见任一 pid 退出就全杀,一次建表失败等于全站 502。
allhelp/model.go 那段用命名返回值 err,第二张表失败时会漏出去,是反例。
- client_key 服务端保证非空。它参与 (uid, client_key) 唯一索引,而 MySQL 的唯一
索引不像放行 NULL 那样放行空串,留空会让同一用户的第二条记录直接插不进去。
用 crypto/rand 生成:这个值是去重依据,可预测就可被抢占。
- 重复项的提醒改成「就地展开」。设计文档 v0.1 写的「remind_at 服务端算好客户端
直接用」对重复项不成立——一个字段表达不了「每天」,且没人在它过去后往前推。
现在 memory_upcoming 按项自己的 tz 展开成绝对时刻列表返回,不落库。
单条项按自己的 tz 算而不跟着请求方走:「今天有哪些事」是视图问题可以跟着人走,
「明早 8 点响」是承诺问题不能。
- 花销按币种分行统计,不做汇率换算。把 CNY 和 JPY 加成一个数是错的。
- 统计全部走 SQL,LLM 只把 stat_json 组织成一段适合朗读的话。明细截断到 20 条
(重度用户一周两百条未完成,全塞进 prompt 会顶爆上下文,播报也没人听得完)。
- 查询/删除/更新一律带 uid 作条件,报告确认校验 uid 归属。
- 更新字段走白名单而不是黑名单,漏挡一个 uid/id 就是越权改别人的数据。
选路走编排,不再写死服务商
新增 comm.IEchomeet.ChatLLM,由 echomeet 用它的 PickLLM/GetSummarizer 实现。
memory 的报告生成与会议抽取都经它选模型。allhelp/summary.go 直接 import
sys/doubao 是历史债:绕过后台编排,海外应用照样调国内豆包、后台换模型也换不掉。
新模块不复制这个写法。
会议纪要 → 待办抽取(第三路)
挂在 AIProcess 写完 summary、状态置 Completed、出队之后,另起 goroutine。
prompt 内置代码不放 echomeet_template(1189 条模板改不齐,且输出要被程序 parse)。
输入用已生成的 summary 而非转写全文。注入 <meeting_date>,服务端二次校验
due_date:解析不出或早于会议当天的一律只留 due_raw、落会议当天并置
date_certain=false。抽取失败只记日志,绝不回头改 rec.State。
- gen_round 由 memory 按 source_id 现有最大值 +1 推出,不由调用方传。
echomeet 没有地方存轮次计数器,硬编码成 1 会让第二次「重新生成」的清理条件
gen_round < 1 一条都匹配不上,旧待办从此永远留在库里。
- 会议归属日期只能用 creationtime(上传时间)——DBEchoMeetRecord 上没有「会议实际
发生日期」字段,也没有客户端时区。补录历史录音会算错,是已知偏差,注释已记。
周报月报
cron 建单 → Redis 队列 → N worker。锁与队列 key 都过 redissys.RKey 加应用前缀:
测试机多应用共用一个 Redis,锁 key 不带前缀会让 A 应用挡住 B 应用整周不生成报告,
而且安安静静什么都不报。
补偿比防重更要紧:当前 compose 是单副本,多副本重复是前瞻性风险;真会发生的是
00:30 跑到一半容器重启、cron 不补跑、报告永远停在 pending。所以 memory_getreport
里做超时重新入队(同 echomeet 用 PollTranscribe 兜「转写回调丢了」的思路)。
文案生成失败不算整体失败——统计是完整的,宁可少一句话不可让报告整个没有。
顺带修的既有缺陷
- user 模块 cancel()(账号注销)补上 memory_item / memory_report 两张表,
以及既有遗漏的 useridverify(身份证掩码 + 加盐指纹)。这个清单是硬编码的,
漏了不报错,只是个人数据静静留在库里。
- errorcode.proto 新开 5201-5206 段。复用 ReqParameterError 会让客户端无法分流,
「报告还没生成好」该转圈重试而不是弹错。
测试
comm/memory_test.go 8 个:周日在 Go 里是 0、Dart 里是 7 的换算(错了不报错,
只会让每周日的闹钟在周一响)、ISO 跨年周、闰年二月、非法时区必须回退 time.Local
而不是 UTC(否则静默偏移 8 小时)、client_key 非空且不重复。
modules/memory 14 个:一次性/每天/每周展开、锚点在一年前仍能算出未来时刻、
过期项不排、灵感花销不提醒、全天项按 9:00 不落 0:00、展开结果必须升序
(客户端要按 iOS 64 条上限截断,顺序错了会把明天的排掉留下一周后的)、
项按自己时区算、LLM 输出容错(代码块/前后解释文字)与真失败必须报错。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
4a24ece7d3
记忆中心设计文档:服务端到客户端全局复核,推翻 5 处设计
在 v0.2 复核现状盘点的基础上,从 gateway 逐层过到客户端,又推翻或补充 5 处。
风险表 11 高 / 20 中 / 2 低。
拾忆页是纯展示页,写操作 UI 一个都没有(最大的工作量遗漏)
全页没有 RefreshIndicator、没有 FloatingActionButton/Icons.add、卡片没有
onTap(唯一的 onTap 是范围切换 Tab)。于是 memory_update/del/complete 三个
写接口在客户端没有任何调用点,user_edited + gen_round 这套「保护用户修改」
的机制在没有编辑 UI 之前完全是空转的。分期表 #6 原先只写了「分类筛选、换源、
删 mock」,补上 6b:编辑层 / 勾完成 / 新增表单 / 下拉刷新 / 空态。
每日弹窗不能 await 在 splash——与 6a64c3d0 方向相反
那次提交刚把 _initMeetingTemplate 从启动串行链摘出去改成 unawaited,理由是
「启动时没有任何地方要用它」。文档写「每日弹窗排在启动第三个」等于把刚摘掉的
成本原样加回来,而且这次摘不掉。改成:splash 照常放行导航,进主页后 unawaited
拉 memory_today,拿到再弹。取舍同设备页那处「新耳机晚一两秒出现」。
MCP 工具会造成第二份查询实现
mcp 是独立进程,工具直接 mysql.Table(...).Where(...) 裸查,全模块搜不到一次
RpcCall。5 个新工具 = 过滤条件/分类白名单/权限规则在两个进程各写一遍,改一边
不报错地漂移。且 mcp.yaml 只配了 mysql,没有 redis/postgres。列了两条出路
(RPCX 调 home / 抽共享包),一期就要选,否则三期返工。
cron 的真实风险是漏跑不是重复
docker-compose.yml 没有 replicas/deploy,当前就是单副本,「多副本重复生成」
是前瞻性风险。现在会发生的是:00:30 跑到一半容器重启,cron 不补跑,
memory_report 永远停在 state=0,那周永远没报告且不报错。补上客户端驱动的
补偿(照 echomeet 的 PollTranscribe)。顺带记下 timer_uselog 那个每日 cron
零防重零补偿、靠 Redis Del 巧合幂等,不是可照抄的范例。
「报告挂额度」走不通
既有额度扣减全是「用户主动发起」语义(api_starttask 先查够不够再扣、失败 +=
退还;api_usages 扣 AI 次数)。周报是 cron 主动生成,用户什么都没做就被扣,
余额不足时「报告不生成」也没有合理的交互位置。改成三个可行选项,推荐只给
「上周期有 N 条以上记录」的活跃用户生成。
另补四条中危
- 业务错误会自动弹红色 snackbar(AuthInterceptor 默认行为),抑制名单里只有
user_getinfo/user_binddevice;memory_today/upcoming 是启动路径自动调的。
- code==0 且 data 为空拿到的是 {} 不是 null,与「业务错误」含义相反。
- AppLifecycleManager 已存在且 permanent 注册,前台恢复钩子直接复用,别挂
第四个 WidgetsBindingObserver(已有 emai/translation/permissions 三个)。
- ErrorCode 要新开 5201- 段,复用 ReqParameterError 会让客户端无法分流。
顺带确认无需改动的:/api/:param1/:param2 的 param1 就是服务名,模块挂进 home
后路径直接命中;meettemplates.vue 已能按语言编辑 outline/template;IsSign 时间戳
防重放所有 yaml 都没配(默认 false),并发请求暂不受影响;播报走 TTS 的 normal
支不碰 80c6ea1b 刚修好的 phone_call 路由,但共用同一个 AVAudioSession,判据是
通话翻译活动期间一律不播报。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
7c5c23acaa
记忆中心设计文档 v0.2:复核现状盘点
v0.1 之后主仓有 4 次提交(翻译延迟优化、设备表合表、收拢积压改动、
上传改预签名 URL),本次逐条复核「现状盘点」,修正已不成立的条目,
并记下会绊到本设计的既有缺陷。状态仍为未开工。
1 month ago
Rodger-Wang
6a64c3d008
把两处网络请求移出启动与设备页的关键路径
会议模板不再挡启动与登录
_performParallelTasks 名字叫并行、实际完全串行(网络检查 → 版本检查 →
token 校验,后者内部又是 getLoginToken → getAppConfig → _initMeetingTemplate),
冷启动要串等 5 次网络往返,而 _initializeApp 要等这一整串跑完才允许导航。
其中 ②③ 之间的串行是刻意的(强更弹窗必须先出、把 splash 卡住,见 CLAUDE.md),
不能改回并行;但 _initMeetingTemplate 排在最后、**启动时没有任何地方要用它**
——模板只落 sqlite,由会议模板页 getMeetingTemplateList 消费。改成 unawaited,
模板照常拉取更新,启动不再等它。
登录流程里同一句挡在 Get.offAllNamed(Routes.home) 前面,一并改。
设备页先渲染再补登记
loadDevices 里 `await BesDeviceAuth.verifyConnected()` 串在 getdevices
前面,首次进入白名单没命中时会真的去打 user_binddevice(带 10s 超时),
整个页面干等两次网络往返——就是「点设备页半天没响应」。
- isLoading 改成只在本地无数据时才遮罩:onInit 里 _refreshFromUser() 本来
就已经拿本地列表渲染过一次了,再盖一层纯属让用户对着转圈等。
- verifyConnected 挪到列表渲染之后、改成后台跑。它登记成功时会直接把设备
塞进 User.instance.devices,回来 _refreshFromUser() 刷一次即可,
不必再拉一遍 getdevices。
⚠️ 代价:首次连接的新耳机会晚一两秒才出现在列表里(以前是干等到它出现才
显示整个页面)。这是「页面立刻可见」的必然结果。
⚠️ 两处的实际收益都要在 release 包上量,debug 的 JIT 会把差异淹掉。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 month ago
Rodger-Wang
80c6ea1be3
iOS 真机排查:修同传播报路由、录音时长与电平崩溃
一轮 iPhone 真机联调里发现并修掉的一组问题,多数是既有缺陷,两处是我在
本轮前期改出来的回归(已在注释里标明,避免以后再踩)。
同传播报路由(AzureTtsHelper.swift,phone_call 分支)
这一支被改坏过三次,结论固定在注释里:
① 最早 `.defaultToSpeaker` + `overrideOutputAudioPort(.speaker)`
→ 连了 A2DP 也被强拽到外放;元凶是那次 override(已删)。
② 修 ① 时把 `.defaultToSpeaker` 一并删了 → 删过头:`.playAndRecord` 的
默认输出是**听筒**,没耳机时译文几乎听不见(「翻译出来了但不播报」)。
③ 补回 `.defaultToSpeaker` 且 mode 设成 `.videoChat`(为了 AEC)
→ `.videoChat` 本身偏好内置扬声器,连了耳机又被拽到外放,绕回 ①。
现在按**有没有耳机**分两支:有耳机走 `.default` + 仅 A2DP;没耳机走
`.videoChat` + `.defaultToSpeaker`(外放且开回声消除)。分支口径与采集侧
MicrophoneCapture 启用 voice processing 的条件完全一致,两边不再打架。
没有这个 AEC 时,外放的译文会被自己的麦克风收回去,ASR 当成新一句再翻一遍。
现场录音(bes_recording_service.dart)
- 时长改为按**已写入的数据量**算(32 字节 = 1ms),与 WAV 头一致,
因此界面时长与文件实际播放长度永远相等。墙钟算不准:耳机收到 AA 59 后
要 1.8~6.5 秒才上行首帧,还会把等待期缓存的音频一次性补推,实测数据量
比「首帧到停止」的墙钟窗口还多 21~45%,从哪头计时都对不齐。
真机验证:音频19.15s/墙钟18.88s、音频18.72s/墙钟18.69s,
等效采样率均为 16000Hz 整、0 丢帧。
- 修 `_updateLevel` 的 `asInt16List(offsetInBytes, …)`:原生来的 Uint8List
是带 offset 的视图,偏移为奇数时必抛 RangeError。原来藏在 try 里被吞掉,
表现是波形/电平一直不动(数据本身没丢)。改成按小端手工拼 int16,
与 BesCallRecordingService._writeStereo 同一修法。
⚠️ 本轮我一度把它挪到写入之前且未包 try,一抛异常整个 _onPcm 中断、
音频一帧都写不进去(实测 6990 次未捕获异常)——现在它排在写入成功之后
并单独包 try,波形挂了绝不能影响录音。
- 音频写入排成 Future 队列。Stream.listen 的 async 回调不会被等待,
每秒上百帧必然重叠,而 RandomAccessFile 同一时刻只允许一个异步操作。
(事后核对通话录音同样写法却没丢数据,所以这条是防御性的,不是主因。)
识别异常与状态收口(translation_controller.dart)
- 新增 _abortRecognition:catch / error / canceled 三条异常路径统一回滚,
**把录音一起停掉**。此前 `isRecording` 全文件只有一处 `= true`、从来没被
置回 false,识别挂了右上角还在计时;异常路径更是连 stopRecording 都不调。
标记同时收口进 stopRecording,所有停止路径都覆盖得到。
- startRecognition 点击即点亮 isPreparing(view 早已绑好按钮反馈,只是从来
没人设过它,点下去毫无视觉变化,就是「点了没反应」)。
- `await _modeInitFuture` 补 3 秒超时。它是本轮前期为消除语言竞态加的,
但没有超时——iOS 首次要激活音频会话、连着蓝牙还要等音频路由,一旦迟迟
不返回点击就永久卡住。把「可能用错语言」换成「可能完全卡住」是坏交易,
超时后走兜底 initialize,至少起得来。
- 三段耗时打点提到 warning 级:release 的日志级别就是 warning,用 info 打
在真机上一条都留不下。
其它
- Logger 在非 release 下同时 print 一份:`developer.log` 只进 VM service 的
logging stream,`flutter run` 控制台看不到、调试态下 idevicesyslog 也抓不到,
真机排查时加了一堆打点却一条都看不见。
- 连接握手不再自动跑 probeCommands:每条间隔 400ms、一轮十几秒,期间 SPP
通道被占满,用户点录音/翻译的指令只能排队。需要重扫时手动调。
本次在固件 0.0.3 上的扫描结果记进注释(0x0B/0C/0D/0F/10/19 有回包,
但取值都不像电量百分比,电量命令仍未定位到)。
- 通话状态变化打出触发它的原始字节,便于排查「刚连上就被判成通话中」。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 month ago
Rodger-Wang
8d580a545d
文件上传改走服务端签发预签名 URL,客户端直传阿里云 OSS
客户端不再持有对象存储的长期密钥。
旧实现用腾讯云 COS SDK 直连,bucket 与 SecretId/SecretKey 明文随
user_getappconfig 的 env 下发给每一个 App 用户(config 表的 COS_* 那几个键)。
抓包或反编译就能拿到这对长期密钥,而它对整个桶有读写权限——不只是自己那部分,
User/ 下所有人的录音都能被拉走或删掉。
现在:新增 user_getuploadurl,服务端签发 15 分钟有效、且只对某一个 object key
生效的阿里云 OSS 预签名 PUT URL,客户端拿着它直传。存储与 console 后台传固件/
产品图同一个桶(ymaioss),不再是两个云厂商两套存储。
服务端
- 新接口 modules/user/api_getuploadurl.go:
· object key 由服务端拼死 EAIMAR/User/<uid>/<scene>/<yyyy/MM/dd>/<12位随机>.<ext>,
uid 取自会话不采信客户端,客户端连自己传到谁的目录下都决定不了;
· scene 白名单 LocalAudio / TranslatAudio / ExternalAudio / ChatImages
——加新场景前要把客户端 addUpload 与 UploadOss.upload 的调用点全捞一遍,
少一个值就是那条链路整条报错,且只在真机跑到才发现;
· 扩展名白名单决定 Content-Type,不在表里直接拒,不回退成
application/octet-stream(那等于把桶变成任人上传的网盘);
· 随机名用 crypto/rand 而非 math/rand:key 可枚举就等于别人能猜到录音地址。
- 抽出 comm/ossconf.go:OSS 配置读取、上传根前缀、publicURL 拼接从 console 挪来,
console 改为调用它。两边必须落同一个桶,各写一份迟早漂移。
解密用 ${FIELD_ENCRYPT_KEY},console 与业务服务本就必须配同一把。
客户端
- upload_oss.dart 重写为「要 URL → dio 流式 PUT」,upload() 签名与返回值不变,
MeetingUploadService 不用改。用独立 Dio 实例:项目那个挂着 AuthInterceptor,
会加 token 与业务签名头、还会把响应按业务 JSON 解析,直传 OSS 时全是多余,
签名头还可能与预签名冲突。
- Content-Type 必须用服务端回带的值,它计入了签名,写别的会 403。
- agent 图片的 rootDir 从 'User/<uid>/ChatImages' 改成 'ChatImages':
前缀由服务端加,客户端再拼一层就是重复路径。
真机验证:录音落到
https://oss.ymaikj.com/EAIMAR/User/ <uid>/LocalAudio/2026/09/04/howXMScPdsNP.wav,
下载 212524B 与库里 size 逐字节一致,Content-Type=audio/wav,文件头 RIFF/WAVE。
其余三个 scene 走同一段代码但尚未实跑。
⚠️ 新老客户端并存:老包仍读 COS_* 直传腾讯云,config 表里那 5 个键先别删;
历史录音的 audiourl 是 COS 绝对地址,不会自动搬家。
⚠️ 录音目前仍是公共可读的(bucket 公共读,实测匿名 GET 206、不存在的 key 404),
只靠 12 位随机 key 不可猜测来保护。要锁住得转私有桶 + 播放时也签临时读 URL,未做。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 month ago
Rodger-Wang
24e17b3434
收拢积压改动:恒玄 OTA、发版配置拆表、实名认证、芯片厂商/代工厂管理等
把工作区里积压的多轮改动一次性入库。主要几块:
客户端
- 恒玄 OTA 打通:新增 local_plugins/bes_ota_manager(自包含 BES SDK,
Android Java + iOS ObjC,SPP OTA 2.0),路由切到 BesOtaUpgradeView /
BesOtaUpgradeController,配套下载服务;杰理那套文件保留但已不挂路由。
- 通话翻译新增阿里 3.5 档(alibaba35_language_config.dart,
qwen3.5-livetranslate-flash-realtime + 实时声音复刻)。
- 实名认证接入、游客登录改走服务端 api_sgin 的 Tourists 分支、
功能闸门由 GuestGuard 改为 IdVerifyGuard。
- 41 个语种文案同步(OTA 新增 28 个 key 等)。
- 默认主题改为浅色,不跟随系统。
服务端
- 发版类配置拆表:app_release(版本控制 / 游客显隐,一应用一行)与
channel_app(渠道级下载地址 / 上架状态 / 支付渠道)分家,
新增 comm/apprelease.go 与 console/api_apprelease.go。
- 授权码字符串整体移除:删除 utils/license 包,新增 utils/devcode
(MAC 规范化与 PID 反解),配套迁移 SQL 在 docs/migrations/。
- console 新增芯片厂商、代工厂、用户管理接口与对应 admin 页面。
- 身份证实名核验(sys/idverify)与第三方服务的服务端专用类别。
其它
- .gitignore 补挡 apps/services/lego/sys/gin/log-*.log:gin 子系统跑起来
会按时间戳滚动生成日志(多为 0 字节),与 apps/services/comm/log 同一
性质,不进版本管理。已跟踪的 log-2026-08-20 那个仍在库里,未动。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
d7fad60658
设备表合表:license_<pid> 分表 → 全局 device_mac,只按 MAC 校验
设备的身份是它自己的 MAC,本来就全局唯一,定位一台设备不需要 pid。
原先按产品切分表,pid 既是查询键、又得由客户端猜出来,猜错就报
"这台 MAC 没登记在该产品下"——提示指向数据缺失,实际数据好好的。
触发改造的事故:一台已正确导入的 DEEPVOICE 耳机(MAC 在 license_b022,
产品 45090,status=0 未绑过)连不上、弹"设备连接受限"。根因是产品 45090
的 devicetype 被配成 2(BLE双端)而不是 1(经典蓝牙):客户端
_resolveProductId 第一步就 where(devicetype==1) 把整个产品滤掉,候选只剩
Echo-one,名字互不包含 → 兜底退回 headsets.first.id = 45091,服务端拿错
pid 去 license_b023 查,自然查不到。而且就算 pid 猜对也过不去——服务端
只有 devicetype==1 才走 MAC 反查,2 会走 else 分支从空 code 反解 pid、
回退写死的 45058。
改动:
- 新增全局表 device_mac(comm.TableDeviceMac),主键 code = MAC;
productid 降为归属列,不再是查询键。TableLicense 标记废弃,只留给迁移读。
- registry.go 的 migrateLicenseToDeviceMac 每次启动跑、幂等:
① code := devicemac 修存量 → ② 按两表列名交集做 INSERT ... ON CONFLICT
DO NOTHING(不能 SELECT *:各分表建于不同时期,有的多 factoryid/probatch)
→ ③ productid 为空的用表名里的 pid 补上。孤儿分表里也是真实发出去的设备,
一并搬。搬完刻意不删旧表——不可逆数据,旧表是出问题时唯一的回退依据。
- binddevice / unbinddevice 改为先按 code 再按 MAC 全局定位,命中行的
productid 才是产品。req.Pid 不再采信。杰理链路也顺带不需要 devcode
反解 pid 了。
- 新增 comm.ScopeToProduct:合表后分表那层天然隔离没有了,每个"按产品"
的读写都必须显式带 productid,漏一处就跨产品操作——查询多列几行只是
难看,批量禁用 / 按批次删除漏掉就是删别人产品的数据。它会把调用方的
条件整体括起来,防止 "a=? or b=?" 让 or 把 productid 短路掉。
- 导入查重改成全局:同一个 MAC 出现在两个产品下会让按 MAC 反查出现歧义。
- 客户端删掉 _resolveProductId,binddevice 只报 MAC 不传 pid。
按需求明确不做应用隔离:MAC 存在即放行。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
946204cc3c
翻译延迟优化:接上阿里增量文本、面对面/同传去掉固定等待
通话翻译:阿里百炼端到端协议本来就是流式的(音频 response.audio.delta
一直在逐帧回灌),但增量文本订阅的事件名是错的——代码监听
response.audio_transcript.delta(字段 delta),而实时语音翻译
audio+text 模态实际发的是 response.audio_transcript.text(字段 text),
.delta 是 OpenAI Realtime / 通义 Omni 的写法。于是 onPartialText 一次
都不触发,译文只在 .done 时整句蹦出来,看着像"等说完才翻"。
现在两套名字都认、两个字段都取,并按首次出现的事件名锁定,防止服务端
兼容层双发导致文本累计两遍。Android / iOS 两端同改。
面对面 / 同声翻译(走 ASR → HTTP 机器翻译 → TTS 三段串行):
- 补上 Speech_SegmentationSilenceTimeoutMs=300,原来一行没设走 Azure
默认 500ms,这是"说完到出译文"里唯一一段纯等待;
- 同传 / 音视频改单语 ASR,关掉连续语种识别(它要缓冲够音频、给候选
语言逐个打分才敢出 final)。只有面对面需要判断"谁在说",仍保持双语;
- 中间结果翻译从"每 300ms 无条件发"改成 700ms + 至少多 4 个字 +
CancelToken 撤旧请求 + 序号丢弃过期结果。中间译文只上屏不播报,
原来一句 5 秒的话要打十几次真实服务商调用,还会跟 final 抢链路;
- TTS 提到 _updateExistingHistoryItem 最前,不再排在浮窗 / 写历史 /
记统计那串 await 之后;
- startRecognition 先 await onInit 那次 changeTranslationMode,堵掉
"进页面立刻点开始 → 落到默认 zh-CN/en-US"的竞态。
AuthInterceptor 的 DioExceptionType.cancel 分支不再弹红色"请求已取消"
——取消永远是客户端自己发起的,不改的话中间结果一接上就会一路狂弹。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
27434754a1
记忆中心设计与开发文档
把「代办」页升级为「记忆」页的完整设计:统一 memory_item 模型
承载闹钟/待办/灵感/花销四类且分类可扩展,会议纪要经 AI 抽取自动
落成待办,每日播报、提前 5 分钟提醒、周报月报复盘,以及经 MCP
工具让 EMAI 助手随时问答,形成录入→提醒→复盘→问答的闭环。
盘点后确认在 allhelp 已有的任务体系与异步总结流水线之上演进,
而非另起炉灶;DBTask「仅记录不自动触发」的限制与花销后端空白
是本次要补的主要缺口。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
90abb0ad42
身份证实名核验、翻译与新用户开户礼
本次会话开始前就已存在于工作区的改动,一并提交。主要是三块:
1) 身份证实名核验(sys/idverify + user 模块)
接入阿里云 Id2MetaVerify、腾讯云 IdCardVerification、创蓝三家 provider,
无状态工厂——配置在 svc_config 里按应用作用域存,调用时才解析。
客户端接口 user_idverify / user_getidverify。
凭据是云账号主 AK/SK,故新增服务端专用类别 comm.SvcCatIdVerify=11,
由 svcresolve.go 在下发口整条跳过,svcpool_serveronly_test.go 守着这条底线。
2) 翻译(sys/aliyun/translate + comm/lang.go)
语言码归一与阿里云翻译调用。
3) 新用户开户礼(newuser_gift.go)
配套 api_sgin 建号流程。
注:go test ./modules/user/ 里的 TestApiProbeAppConfigV3 会失败,但与本次改动无关
——那是打线上接口的联调探针,硬编码的域名 app-dev.voitrans.net 已废弃、JWT 也过期了,
该文件自 b2577e16 起未改动过。go test -short 会跳过它,其余全部通过。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
0663446f0c
运行日志 apps/services/comm/log 移出版本管理
lego 未配置日志路径时会在工作目录写 ./log,本地跑一次服务或 go test 就被覆写一次,
每次都产生一条无意义 diff(内容是同一条微信支付密钥解密失败的告警,只有时间戳在变)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
2d1ee0b783
apps/client 首次入库(Flutter 移动端)
从 /Users/yunyanzhineng/work/eaimar_client-main 搬入,未带原 .git,历史留在原仓库。
3215 个文件 / 约 118MB,其中约 40MB 是构建必需的预编译 SDK(azure_speech 的
MicrosoftCognitiveServicesSpeech.xcframework、device_jieli 的 JL_BLEKit 等)。
build/ (5.2G)、ios/Pods/ (311M)、android/.gradle/ (120M) 已由 .gitignore 挡掉。
本次入库的代码包含两项刚做完的改动:
1) 游客登录入口接上服务端开关。此前 _checkGuestLoginConfig() 恒定写死 true,
入口根本不读配置。现改为取服务端算好的结论,并把入口所在的 Builder 改成 Obx
——配置是异步拿到的,Builder 只渲染 onInit 时的初值 false,入口再也不会出现。
拿不到配置时按隐藏处理:默认显示会在送审期把不该露的入口露给审核员。
⚠️ 已发布的老客户端关不掉(写死 true),只对本版及以后生效。
2) iOS 上架合规修复:
- 移除 ATT/IDFA:删掉 Info.plist 的 NSUserTrackingUsageDescription、
SKAdNetworkItems、NSAdvertisingAttributionReportEndpoint,删掉 splash 里的
授权请求与 app_tracking_transparency 依赖。本 App 从不读取 IDFA,也没有任何
广告/归因 SDK,弹框纯粹劝退用户,还要在 App Store Connect 申报追踪数据类型。
- ios/Podfile 里那段 config.build_settings.delete('NSUserTrackingUsageDescription')
是无效代码(该 key 在 Info.plist 里,不是 build setting),换成注释说明。
- 通讯录符号(ITMS-90683 被拒原因)随之一并解决:permission_handler 走 SPM 后
按 Info.plist 有无对应 key 决定编不编,判定结果被 SwiftPM 按内容哈希缓存,
改完 plist 必须清 DerivedData 与 manifests 缓存才会重新求值。
另附 GOOGLE_PLAY_RELEASE_CHECKLIST.md:Google Play 上架阻塞项的实测梳理
(targetSdk 36 与 Billing 8 的 2026-08-31 期限、16KB 页对齐不合规的两个 .so、
支付回退逻辑的下架风险)。
⚠️ android/app/sign/ 下的 eaimar.keystore 与 Eaimar.properties(含明文签名密码)
随本次提交入库,与本仓库"私有仓库下接受真实凭据"的既有取舍一致。
仓库若要公开,这两个必须先摘出去。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
db47e0f3f0
游客登录入口改为后台按版本控制
需求:后台一个开关控制游客登录按钮显隐,且只对指定版本号的客户端生效,
其余版本一律隐藏(应用商店送审场景:只给正在过审的那一版开)。
白名单语义(comm.ChannelApp.ShowTourists):
- tourists=false → 全部隐藏
- tourists=true 且 touristsversion 空 → 全部隐藏(没点名任何版本 = 谁都不放行)
- tourists=true 且 touristsversion=X → 只有版本号等于 X 的客户端可见
留空按"全隐藏"而非"全放行"是刻意选的失败方向:判宽的后果是不该露游客入口的版本
露了出去(过审风险、白嫖新用户开户礼),判严只是入口少显示。要全部隐藏就取消勾选,
后台 saveChannelApp 会拒绝"勾了开关却没填版本"这种等于没开的配置。
判定放在服务端,与 NeedForceUpdate/IsReviewing 同规矩:客户端在 user_getchannelapps
的请求里带 version,服务端把每条渠道配置的 tourists 换算成结论再下发。版本比较各端
各写一遍必然出现实现不一致,而这个开关直接关系到过审,判错代价高。
touristsversion 是 channel_app 的后加列,console 启动时用 ADD COLUMN IF NOT EXISTS
补上——CreateTable 对已存在的表跳过 AutoMigrate,不补就永远没这列。
注:db.proto / user_msg.proto / CLAUDE.md 里另有先于本次改动就存在的编辑,一并带上。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
2f81aaef93
构建产物与依赖目录移出版本管理
apps/admin/.output 与 apps/admin/node_modules 此前被纳入 git 跟踪(共 18425 个文件),
每跑一次 nuxt build 就产生数百个哈希文件的增删,真实代码 diff 被完全淹没。
部署不依赖仓库里这份产物:apps/admin/Dockerfile 是 COPY . . 之后 RUN pnpm build
在镜像内重新构建,且 .dockerignore 已排除 node_modules/.nuxt/.output——仓库里这份
从来没被用到过。
只从索引移除(git rm --cached),磁盘文件保留,本地 dev/build 不受影响。
.gitignore 同时补上此前遗漏的 .DS_Store。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Rodger-Wang
bdfcb1ecbd
站点品牌名改为翊迈SaaS管理平台
- console 的 SiteName 与 admin 的页面标题统一改名,部署模板
(console.yaml.example / env.example) 同步跟上
- 侧栏与登录页标题由后端 api_getsiteinfo 下发,改 CONSOLE_SITE_NAME
即可生效,无需重建镜像;浏览器标签页在 nuxt.config.ts 里,需重建
- dev-deploy.sh 的 along 档案密钥改为 ${DEV_KEY:-原路径},保留原默认值
的同时允许用环境变量覆盖,便于在没有该路径密钥的开发机上部署
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2 months ago
liwei1dao
bb3b1c7a3a
上传代码
2 months ago
liwei1dao
b2577e16fa
上传最新的平台代码
2 months ago
liwei1dao
af05ff12ce
上传代码
2 months ago
liwei1dao
1639cf35a0
上传代码
2 months ago
liwei1dao
aca7204e57
上传代码
2 months ago
liwei1dao
8924a3cd3b
上传服务后台代码
2 months ago
liwei1dao
554a4ab501
上传最新的版本代码
2 months ago
liwei1dao
948e368996
上传代码
2 months ago
liwei1dao
59e54eb575
上传发布版本
2 months ago
liwei1dao
6f7cfe3bb2
上传主版本
2 months ago
liwei1dao
a36f0a016a
上传服务端代码
2 months ago
liwei1dao
4896d64366
上传代码
2 months ago
liwei1dao
7660f33258
上传最新代码
2 months ago
liwei1dao
97ecbf9df5
上传新版本的管理后台
2 months ago
liwei1dao
18ffb48466
上传代码
2 months ago
liwei1dao
e270cf89b6
上传新版本管理后台
2 months ago
liwei1dao
6e5da54cf8
上传代码
3 months ago
liwei1dao
1f0f42adb8
移除误提交的 82MB home 服务编译二进制并忽略各服务同名产物
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
liwei1dao
af9e210f9a
会议记录接入后台服务编排:三段独立选路+客户端可选总结模型,移除旧 yaml 四套凭据模式
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
liwei1dao
a25b0c9fc0
上传最新的服务后台代码
3 months ago
liwei1dao
040af15192
上传基础服务代码
3 months ago
liwei1dao
d7445984cb
添加 授权吗 的方案
3 months ago
liwei1dao
197d544062
上传管理后台的系统升级和服务配置逻辑优化
3 months ago
liwei1dao
99de0fa26f
上传代码修复
3 months ago
liwei1dao
1390f50144
优化邮件登陆服务
3 months ago
liwei1dao
eaecd00d20
上传基本版本
3 months ago
liwei1dao
603dc89073
上传优化节点部署代码
3 months ago
liwei1dao
1d25322437
上传当前版本
3 months ago
liwei1dao
b493d5b4b1
上传部署代码
3 months ago
liwei1dao
c44d7145b0
上传主界面
3 months ago
liwei1dao
ae39fdeef1
优化会议记录
4 months ago
liwei1dao
1b2f74e0c0
优化部署脚本
4 months ago
liwei1dao
ec7d512172
上传基础开发版
4 months ago