Rodger-Wang
|
9f105bf7c8
|
会议纪要分享:链接最后一段改成服务端签发的只读票据,不再放登录 JWT
链接格式 share.ymaikj.com/share/{serverHost}/ymaikj.com/{id}/{ticket}。最后一段
以前放的是 logintoken(完整登录 JWT)——谁拿到链接就拿到了整个账号,转发进群、
被截图、进浏览器历史都等于把账号交出去,而且除了改密码没有任何失效手段。
现在是 HMAC 签名的分享票据:绑定单条记录、只读、默认 7 天过期,密钥由
FIELD_ENCRYPT_KEY 派生(上线不需要新配置项)。
- echomeet_getsharedrecord 必须在 gateway 白名单里——打开链接的人本来就没登录,
漏了是 code:18、表现为「分享链接打开一片空白」。签发端 echomeet_createsharelink
反之,不要加进白名单。
- 落地页嵌在 gateway 二进制里(go:embed),路由 GET /share/*any 必须是 catch-all:
路径里的段是数据不是文件路径,gin.Static 会去磁盘找同名文件、一律 404。
- 落地页的 ALLOWED_HOSTS 白名单不能删:serverHost 来自 URL,不校验就等于「构造
一条链接让别人的浏览器带着票据去请求任意域名」。新增环境要加一行。
- 极简 Markdown 渲染器先 esc() 再套正则,块级语法要匹配转义后的形态(引用块匹配
的是 > 不是 >),写成 > 不报错、只会静默退化成普通段落。
客户端侧:分享前先申请票据并加载提示,拿不到就不分享出一条必然打不开的链接。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
811a008545
|
services: 语音纪要→拾忆→MCP 链路审计落地;修两个「什么都没发生」的既有 bug
审计与落地计划见 docs/语音纪要-拾忆-MCP链路审计与落地计划.md,已在阿龙测试环境
逐条验收通过(记录见该文档 §5.1)。
## 安全
echomeet 八个按 id 操作的接口**一处都没有比对 record.Uid 与会话 uid**,任何登录用户
改一个 id 就能读别人纪要全文、删改别人的记录、给别人的记录发起总结(扣自己算力,
但覆盖对方 summary 并触发对方的拾忆抽取)。统一改走带 uid 的查询:
- 「不存在」与「不是你的」回同一个错误码同一句话——分开回等于给出一个探测他人 id
的接口;用的还是改动前记录真不存在时的 DBError,合法用户行为无任何变化;
- 批量接口按交集处理、**不报错**:客户端轮询队列里本就可能留着已被别的设备删掉的 id,
报错会把整个轮询循环掀掉;delrecords 响应回实际删掉的那批;
- starttask 的校验排在扣算力之前,否则拒绝了还先把钱扣了;
- 回调接口是第三方来的、没有 session,不在此列。
- TestApisUseUidScopedQueries 扫源码守着:最容易复发的不是有人改回去,
而是新加接口时照旧写法抄一遍,那样既不报错也看不出来。
## gorm 的 default: 标签吞零值
mysql.Insert = db.Table().Create(model),gorm 对带 default: 的字段**一律把零值换成
默认值**。DBMemoryItem 的 date_certain(default:true) / remind_ahead(default:5) 因此
永远写不进 false/0:测试库 25 条记录全部落成 1/5,「待定日期」分组恒为空,
会议待办还会在 08:55 响提醒——与「会议待办默认不提醒」正好相反。
⚠️ 加 Select("*") **救不了**:替换在 ConvertToCreateValues 的 reflect.Struct 分支里
无条件做,只看值是不是零值,与 Select/Omit 无关(DryRun 实测两种写法生成的 VALUES
一模一样)。改成插入后把这两列按结构体现值补写回去,并把真值还给调用方——
gorm 连结构体上的字段一起改了,不还原的话 memory_add 回给客户端的也是错的。
存量由 migrate_defaults.go 幂等修正(只碰 source=meeting 且 user_edited=0)。
## 内容门槛
6 秒的测试通话、20 秒的单人自述照样被抽成「继续进行进一步的功能测试」这类废话待办,
再靠条数凑够周报阈值又触发一次 LLM 调用。门槛卡**转写正文**不卡纪要——内容太薄时
模型按模板兜底规则照样能写出六百多字。只拦抽取与 MCP 检索,**纪要照出**:
短语音备忘是合法用法,也不判成 SummarizFail(客户端把那个码显示成「请稍后重试」,
用户会一直点)。数据缺失时按通过处理,不因上游漏传一个字段就静默关掉抽取。
## 其余
- 报告补偿:原先的 requeueIfStale 由 memory_getreport 驱动,而客户端调无参版本 →
走 latestUnconfirmedReport,那条 SQL 只查 state=Done,pending/processing/failed
永远查不出来也就永远补不了。新增 ensureRecent 挂在 memory_today:漏建的补建
(过与 cron 同一道条数闸门)、卡住的复用 requeueIfStale、失败的至多重试 3 次
(计数寄在 error_msg 的 retry=N; 前缀,fail() 必须保住它,否则上限形同虚设)。
加 enqueueOnce 判重与按报告 id 的 SETNX 生成锁,cron 与 ensure 撞上只跑一次 LLM。
- 说话人标签:finishSyncTranscribe 拼的是「Speaker 0」(空格)、其余是「Speaker_0」,
抽出来的负责人就成了 Speaker2、[Speaker_0] 各种样。四条写入路径统一走 normalizeSpeakers,
顺带修掉没开说话人分离时拼出「Speaker_」空尾巴。
- 时区:LoadMemoryLocation 现在认 IANA / 固定偏移 / CST(按 Asia/Shanghai 解释,
兜住已落库那批),认不出来仍退服务器本地时区但**打 warn**——原来是全静默的。
- 删纪要连带删自动待办(只删 user_edited=0),查询带 uid:入参来自删除请求,
光信 source_id 等于把归属校验的成果又丢一次。
- 回调幂等:两个回调既不看状态也不抢锁,与轮询撞车就各翻译一遍、各入队一次。
失败分支同样要过闸门——一条已 Completed 的记录被迟到的失败回调打回 TranscribeFail,
用户看到的就是「纪要好端端地变成了转写失败」。挡下来仍回 SUCCESS,不让第三方重投。
SubmitAITask 保持不判重(用户点的「重新生成」悄悄跳过等于按钮失灵),
自动路径改走 submitAITaskOnce。
- 重复项统计:只展开闹钟。花销展开等于虚构金额,待办的 done/undone 按次拆不开。
## 顺带修的两个既有 bug(部署验收时挖出来的)
happen_date / period_start 都是 type:date 列 + DSN parseTime=True,读进 Go 的 string
是「2026-09-07T00:00:00+08:00」。SQL 比较靠 MySQL 转换还是对的,所以这个问题
**在任何日志任何报错里都看不见**,只在 Go 侧解析它的地方发作:
1. remind.go 的 expandItem 解析不了 → **memory_upcoming 恒返回 0 个提醒时刻,
整个拾忆提醒在服务端一直空转**(真机实测:建一条每天 08:00 的闹钟,slots 为 []);
2. 下发给客户端的 happen_date 一直是 RFC3339 而非协议约定的 YYYY-MM-DD,
客户端 _localToday() 的字符串相等比较恒不匹配、_mergeRange 在区间端点会重复。
这也是审计里「会议待办会在 08:55 响通知」只能是推演的原因:实际一条都不会响,
两个 bug 互相遮蔽。修法两层:ParseMemoryDate 容忍带时间的形式(安全网),
读路径出口统一归一(管对外格式),缺任一层都会留坑。
## 列表骨架化(同批)
echomeet_getallrecords 只回列表要用的骨架字段,original/translate/summary 占每条
97% 的字节(实测 59 条 ≈ 500KB,列表用得上的 15KB)。
⚠️ 骨架记录**绝不能写回库**:mysql.Save 是整行 UPDATE,三列会被清空。
StartTask 允许对已完成/已阅/失败的记录「重新转写」,那期间 state=Transcribing 而旧
纪要还在库里,用户切一下列表页旧转写和旧总结就没了。轮询前重新取整条再操作,
TestListEndpointNeverSavesBriefRecord 守着不许回退。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
e00b617025
|
services,admin,client: 服务配置按应用隔离、应用参数下线、客户端凭据走加密 v3
后台
- 服务配置去掉「全局默认」层,svc_config / svc_region_override / echomeet_orch
/ echomeet_orch_setting / echomeet_template 五张表统一按 app_name 隔离,
启动迁移 migrate_scope.go 幂等分配(首个应用原地改名保 id,其余拷贝);
写接口 app_name 必填(scope_check.go)。
- 类别落库 svc_category,可增删改;内置 11 个 id 不可改不可删,运行时语义仍只挂 comm.SvcCat*。
- 「应用参数」标签页下线:业务库 config 表不再随 user_getappconfig 下发,
migrate_appparams.go 把 AGENT_TYPE / MOBILE_ELF_* 迁到服务 llm_mobile_elf、
iapCustomerServiceQQ 迁到 app_params,COMPUTE_*/NEWUSER_GIFT_* 留在 config 表不下发,
无人读的音乐/公码/导航/COS_*/MeetServers 删除,其余键保留并在启动日志告警。
- 删除 console appcfg/* 与 api 模块的 get/add/update/delconfig、唤醒音四个接口(无调用方)。
- 修 getmcpservers 仍查全局层导致 MCP 类服务永远读不到。
客户端
- 凭据改走结构化通道:user_getappconfig_v3 整体 AES-CBC 加密(api_crypto.dart,
拦截器须排在 AuthInterceptor 之前),AppConfig.cred(svcId, field, envKey) 取值,
老键名只作兜底;lib 内 AppConfig.env( 已降到 0 处调用。
- 图片翻译的 ALIBABA_VL_MODEL / ENDPOINT 改挂 ast_alibaba。
启动日志
- 删除 sys/auth/firebase(google_auth 的重复实现,零调用方,且同一错误刷两行)。
- api 模块不再每次启动无条件插入默认超管(Duplicate entry 'admin')。
- sys/auth/* 五个包的 logger 名从复制来的 sys.tavily 各改各名;
google_auth 启动时即判断配置是否可用。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
4 weeks ago |
Rodger-Wang
|
48e31cf605
|
MCP 加会议纪要检索工具;用户类工具改用专用短时令牌鉴权
EMAI 助手此前答不了「我上次开会讲了什么」——纪要在服务端,助手拿不到。
补 search_meeting_notes 工具,配套解决身份怎么传。
身份链路(关键取舍):
百炼是第三方,MCP 请求经它转发,所以不能把网关那把能开全站的 JWT 交出去。
新增 MCP 专用令牌:user_getmcptoken 由 home 签发(uid 从网关解出的会话取,
客户端说了不算),aud=mcp、TTL 2h,用独立的 MCP_TOKEN_KEY 签。
即使泄漏,能做的也只有读这一个用户的只读数据,且两小时后失效。
⚠️ 裸 uid 参数已不再被信任:谁都能填别人的 uid。ResolveUIDWithToken 要么拿到
Authorization 里的登录态,要么拿到这张令牌,两者都没有就直接拒。
auth_mcptoken_test.go 守着这条底线(含 aud 不符、过期、换密钥三种伪造)。
⚠️ 令牌必须按工具名嵌套传:真机实测百炼**不会**把 biz_params.user_defined_params
平铺进工具参数——那次调用服务端只收到 [limit]。按 {"工具名": {"auth_token": ...}}
嵌套才生效。这个形状文档没写死,是试出来的。
检索是三级回退:全文索引(按相关度排序) → LIKE(兜单字,ngram_token_size=2 时
单字进不了索引) → 最近几条。最后一级不能省:模型常把「最近的会议纪要」整句
当关键词传,纪要正文里不会有「最近」二字,LIKE 全落空,用户看到的就是
「查不到您的会议纪要」而库里明明有。回退结果带 fallback 标记,免得模型把
「最近一次会议」当成「你问的那次会议」来答。
文件头注释里记了三个「该换向量/知识库方案」的触发条件,量大了照着判断。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
1 month ago |
Rodger-Wang
|
73eb2ef2ca
|
MCP 补上会话鉴权,并挂三个拾忆只读工具
## 先修鉴权:此前公网接口完全没有身份校验
authFromRequest 只把 Authorization 塞进 context,**全仓没有一处读回来,也没有
任何 JWT 校验**;三个用户工具(get_user_tasks / allhelp_task / cancel_user_task)
的 uid 都是 mcp.WithString("uid", Required()) —— 由大模型填进来的普通参数。
而 mcp 是独立进程、独立端口 7300,不经过 gateway 那套 parseToken + isInWhiteList
(那只管 /api/* 和 /web/*),又必须对百炼公网可达。所以这不是「内网接口没做鉴权」,
是公网接口完全没有鉴权:知道 uid 就能读改任意人的数据。
新增 modules/mcp/auth.go:
- 从 Authorization 解 JWT 取 uid,与 gateway/core.go 同一套口径(RegisteredClaims,
uid 在 ID 字段);兼容 "Bearer xxx" 前缀(gateway 收的是裸 token,标准 MCP
客户端会加前缀)。
- 参数里的 uid **只用于比对**,不一致直接拒。不静默改用会话 uid——调用方显然
误以为自己能指定用户,让它失败比让它以为成功了更安全。
- 解不出会话 uid 一律拒,**不回退到参数 uid**,那等于这层没做。
- TokenKey 未配置时拒绝所有用户数据类工具:配置漏了导致鉴权静默失效,
比工具不可用严重得多。TokenKey 必须与 gateway 同值,两边都读 GATEWAY_TOKEN_KEY。
存量三个工具同步改造,uid 参数保留但改为可选(兼容百炼后台已配好的工具定义)。
9 个测试守住这条线,其中三条是核心安全断言:参数 uid 与会话不符必须拒、
没有 token 必须拒而不是回退、未配 TokenKey 必须全拒。另有伪造签名、过期 token、
空 uid claim 的用例。
## 共享查询层:避免 home 与 mcp 各写一份
comm/memoryquery.go。mcp 从不 RpcCall、只裸查 MySQL,如果记忆项的过滤条件、
分类白名单、排序口径两边各写一遍,改了一边另一边不报错,只会让 EMAI 答的和
App 里显示的对不上——而且没人会发现。memory 模块的 listItems 也改走这里。
ApplyMemoryQuery 里 uid 为空时拼 "1 = 0" 而不是不加条件:少一个 uid 条件就是把
全库记忆项返回给调用方,而它有两个调用方,其中一个公网可达。
另提供 MemoryRelativeRange 把「本周/上月/最近7天」换算成日期区间给模型用——
模型对「今天几号」的认知来自对话上下文,很不可靠,让它自己算 start_date 经常
算错(SET_clock 把「下午 3:30」填成 03:30 是同一类问题)。
## 三个只读工具
get_memory_items / get_memory_stats / get_memory_report。
**只读是刻意的**:新增/修改/删除不放这里。写路径的校验、幂等(client_key)、
提醒时刻重算都在 home 的 memory 模块内,在 MCP 里复制一份必然漂移。
录入走端侧指令(tool_calls)、查询走 MCP,这个分工不变。
- 花销按币种分行返回,不做汇率换算;同时给「分」和「元」,模型念元不容易错,
留分是以免它想自己做加减时用浮点。
- get_memory_report 只返回已生成完的:还在生成中的 summary 是空的,
给出去只会让模型编一段话填空。
- 返回给模型的是精简字段,不是整行 DBMemoryItem——client_key/gen_round/extra
这些内部字段对回答问题没用,只占 token 还诱导模型去解释它们。
- date_certain=false 的项带上 date_is_inferred 标记,让模型别把推断出来的日期
当成用户确认过的安排来陈述。
- 模型填错分类时忽略该筛选当作「不限分类」,而不是整次查询失败——它多半会再填错一次。
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
|
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 |
liwei1dao
|
1639cf35a0
|
上传代码
|
2 months ago |
liwei1dao
|
aca7204e57
|
上传代码
|
2 months ago |
liwei1dao
|
948e368996
|
上传代码
|
2 months ago |
liwei1dao
|
6e5da54cf8
|
上传代码
|
3 months ago |