Blizzard
00ad6c8ac0
fix(admin): 任务下钻按真实轨迹字段渲染 + 修评测页白屏
...
docker 起来后拿真库验证,上一版有两处只有真数据才暴露的问题:
1. 轨迹字段名猜错了。实际是 seq/ts/node/kind/phase/label/detail,我写的是
type/msg/at。更糟的是兜底判断用了 !e.msg && !e.node —— node 恰好存在,
兜底不触发,结果会渲染出一列几乎空白的行。现按真实字段渲染:label 作
标题、detail 作正文、kind 上色(system/model/tool/agent/plan/section/
render/approval 八种)、phase=error 整行标红、ts 转本地时间。
2. 评测页签点开直接白屏。库里 flags 存的是字符串 "null"(不是 "[]"),
JSON.parse 出来是 null,flags.length 抛 TypeError 把整个控制台打崩。
`as string[]` 的类型断言骗过了 tsc,单测也覆盖不到——只有点开真数据的
页面才会炸。改为 Array.isArray 校验。
验证方式:本地起 gateway + 真 Postgres,浏览器里逐个页签点过(轨迹 15 条
真实事件、最终输出、评测 100/100/100、DSL 格式化),并在真库上确认跨租户
读取、jsonb ->> 取 topic、cast(jsonb as text) 取 graph 均正常。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 14:20:58 +08:00
Blizzard
e4ec74893f
feat(admin): 任务观测支持下钻 —— 轨迹/输出/评测/DSL
...
全平台任务页此前只能看列表,点进去什么都没有。现在点一行开抽屉,四个页签:
执行轨迹、最终输出、评测明细、提交时的 DSL。不含审批操作——审批是客户端
用户的行为(桌面端 ApprovalBar),管理端只做观测。
不复用用户面的 /tasks/:id/replay:Task/Eval 都在租户插件作用域内,用请求 ctx
查别的租户的任务不会报错,而是静默返回空输出/空轨迹,UI 上表现为"这任务没
产出",比报错难查得多。新增 admin 端点走 WithoutTenant。
数据取自 sundynix_task 收尾落库的 output/trace 列,不依赖 Redis 流(10min TTL)。
所以这是复盘视图,运行中的任务轨迹为空——UI 里明确写出来,免得被当成轨迹丢了。
修的两处与测试环境失真有关(写测试时暴露的):
- 测试库没配 NamingStrategy,与 OpenPostgres 不一致:多数模型有显式
TableName() 碰巧对得上,但 Task 这类没有的会退化成 "tasks",导致写裸
SQL 的查询在测试里查无此表。现已对齐 sundynix_ 前缀 + 单数表名。
- graph 的 ::text 换成标准 cast(... as text):前者是 Postgres 专有,
换掉后这条查询才能被内存库覆盖。
新增 5 组测试,其中一组专门先证明"租户过滤在测试环境里确实开着"——否则
"跨租户能读到"的断言可能只是因为插件没装,属于假过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 14:07:02 +08:00
Blizzard
84463394d4
fix(admin): 审计筛选下沉 SQL + 工具数不再写死 + 模型删除加确认
...
清单里那批小毛病,逐条复核后修(「审计详情列缺失」那条已不成立,早补上了)。
1. 审计筛选只在当前页生效 —— 影响最大的一条。分页是服务端的,筛选却在
前端对已取回的 50 条做,于是搜一个用户 ID 显示"无结果"时,后面几页
可能还有几百条。审计的用途就是查证,"搜不到"会被读成"没发生过"。
改为 action/path/q 三个条件全部落到 SQL,前端只管发条件(防抖 300ms)。
2. 服务状态页把 mcp-go/mcp-py 的工具数写死成 23/4 —— 增删工具后一直骗人,
且服务离线时照样显示,看不出工具其实一个都没注册上。改取实际上报值。
3. 模型删除一点即删,无任何确认。补二次确认,并对"正在使用中"的模型
单独说明后果(删掉会立刻打断线上对话/向量能力)。
审计筛选补了 4 组回归测试,两条是踩出来的坑:
- q 的 OR 组必须带括号:gorm 以 AND 拼接各 Where,裸 OR 会让 action
条件被绕过(测试里用"同 IP 不同方法"两行钉死这个语义);
- LIKE 必须显式写 ESCAPE '\':Postgres 默认拿反斜杠当转义符,SQLite
不写就没有转义符——原来的写法在单测里静默失效,搜 "100%" 命中 0 条。
顺带把 ILIKE 换成 LOWER()+LIKE,这段才能被内存库覆盖。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 13:51:59 +08:00
Blizzard
bfd0d74c34
feat(status): 全文索引降级上报到控制台 —— 让静默降级看得见
...
上一个提交修的是"卷没挂"这个实例,这个提交修的是"它坏了几周没人发现"这个
类别问题:全文路退内存兜底后,混合检索只表现为召回变差,不报错、无告警。
- mcp-go: bleveStore 记录 persistent;health 工具多报一个 fulltext_disk。
仍是 map[string]bool,gateway 现有反序列化不受影响(加非 bool 字段会让
整个 health 解析失败,连带 Milvus/Neo4j 健康灯一起黑)。
- gateway: 基建灯新增「全文索引」一项,退内存时给出后果与排查方向,
而不只是一盏灰灯。
- admin: 基建列表此前只渲染 name/up,detail 字段一直没露出——而降级原因
恰恰只存在于 detail 里。补上渲染(离线态用琥珀色),并给「全文索引」
补 INFRA_META(它不是独立服务,没有端口,落在容器卷上)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 13:44:48 +08:00
Blizzard
c1420b5665
fix(rag): 全文索引在容器里根本没持久化 —— 补 /data 卷 + BLEVE_PATH
...
排查「fulltext 路 0 命中」时发现的真问题,比原以为的分词器问题严重得多。
现状:mcp-go 容器**没有任何 volume**,也没设 BLEVE_PATH,于是索引落到相对
CWD 的 .data/bleve。而运行阶段没有 WORKDIR(CWD=/)、进程又是非 root(uid 10001),
两种结局都糟:
- 在 / 下建不了目录 → openBleve 静默退回内存索引,重启即清零;
- 就算建得了,也只是写进容器可写层,每次发版重建容器一样清零。
更糟的是它不报错:混合检索少一路只表现为召回变差,看不出故障。
改动:
- Dockerfile:建 /data 并 chown 给 app,WORKDIR 设为 /data;
- 两份 compose:BLEVE_PATH=/data/bleve + 挂持久卷(132 那份此前连
顶层 volumes 段都没有);
- .env.example:写清默认相对路径相对的是**启动目录**——本地从仓库根起
和从 sundynix-mcp-go/ 起会写出两份互不相干的索引,排查时极易被坑。
附带澄清:中文分词本身没问题。直接开落盘索引验证过,mapping 里 text 字段
确实是 cjk,实测「星间链」命中 4 条、「海关监管」命中 104 条,BM25 打分正常。
a4852c3 那次修复是有效的,之前怀疑分词器的判断作废。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 13:40:31 +08:00
Blizzard
2c9ecc833e
feat(admin): 积分包支持就地生成付款码,验证支付链路
...
管理端「支付 · 配置 → 积分包」每行加「生成付款码」:下单 → 二维码 → 盯到终态,
用来确认「渠道配置 → 下单 → 扫码 → 回调/查单 → 幂等入账 → 积分到账」在当前
环境真的通。
刻意走用户面的 /api/v1/billing/*,不做 admin 专用旁路 —— 造条测试专线的话,
验过了也不能说明线上用户那条路通。租户由服务端按登录用户解析,无需带租户头。
代价是这会真实扣款,所以:
- 弹窗顶部显著提示是真单、会真扣钱,并指向「订单与对账」的退款入口;
- 已下架的包按钮禁用(服务端本就会拒,别让人白扫)。
弹窗展示订单号/状态/轮询次数/到账积分,出问题时能直接判断卡在哪一环,
比只说一句"成功/失败"有用。轮询策略与 sundynix-web 支付弹窗保持一致:
递归 setTimeout、切后台暂停、失败指数退避、倒计时以服务端 expires_at 为准。
依赖:admin 新增 qrcode(懒加载进 PaymentConfigPage 分块,不进主包)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 12:52:24 +08:00
Blizzard
70b0419387
fix(payment): 支付轮询链路收口——查单节流 + 前端轮询重写
...
后端:
- 主动查单加最小间隔(5s)。此前 BillingOrderStatus 见 pending 就直连微信查单,
而前端 2.5s 轮一次 —— 单笔订单在 30min TTL 内可打出约 720 次渠道调用,
微信侧有频控,并发用户一多先被限流的是我们自己。回调才是入账主路径,
查单只是兜底,节流最坏只让在场用户多等 5s。
- 标记随订单落终态清除,并在补偿定时器每轮 prune 掉超 TTL 的残留
(用户扫码前就关弹窗的订单不会再被轮询,其标记无人回收)。
- 下单响应补 expires_at:二维码有效期由服务端 orderTTL 说了算,
前端硬编码一份迟早漂移。
前端(sundynix-web):
- orderStatus 不再丢掉 warn。服务端在「已付但金额与订单不符」时不入账、
挂起人工核对,订单一直停在 pending —— 丢掉 warn 用户就会一直等一个
永远不会来的结果。现在弹窗显式告警并给出订单号。
- setInterval → 递归 setTimeout:请求慢于间隔时 setInterval 会把请求摞起来,
对「每次可能触发渠道查单」的端点尤其糟。
- 标签页切走时暂停轮询(用户扫完码要切去微信 App),切回前台立刻补查一次。
- 连续失败指数退避至 15s 封顶,断网时不再定频猛打。
- 二维码倒计时 + 失效遮罩,过期不再让用户扫一个必然失败的码。
- 明说「关掉也不会丢钱,到账会自动补上」——掉单补偿定时器本就兜底,
但此前 UI 没讲,用户只能守着弹窗。
- 关闭弹窗一律刷新余额:用户可能在关闭前一刻付款、状态刚落地。
测试:新增 handler 包首个测试,钉住节流与标记回收行为(-race 通过)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 12:46:40 +08:00
Blizzard
34500e66a7
fix(admin): 补全 package-lock,修 CI 里 npm ci 失败
...
vitest@4 依赖 esbuild 0.28.x,而 vite@5.4 用 0.21.5,lock 里只写了后者,
vitest 那份嵌套 esbuild(含各平台包)从没落进 lock —— 当初加 vitest 时 lock
只做了部分更新。本地 node_modules 早已装好,npm run build / vitest 一直正常,
所以直到 Docker 里 npm ci 严格按 lock 装才炸:
npm error `npm ci` can only install packages when your package.json and
package-lock.json are in sync. Missing: esbuild@0.28 .1 from lock file
npm install 重新生成 lock,两套 esbuild 及其 linux/musl 平台包都已就位;
干净目录 npm ci 验证通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 11:25:26 +08:00
Blizzard
79b110afd0
feat(admin): 数据源页转 RAG 运维台 + 支付/模型菜单重组 + 概览升级为仪表盘
...
后端:
- 新增 POST /admin/kb/search:管理端跨租户检索,支持 mode 指定单路
(vector/fulltext/graph/hybrid),不走 scopedKB(否则会被强制锁到调用者
自己的 space,跨租户排障就没法做了)
- KB 清单补 space_id(检索键是 <space_id>/<name>,缺它前端拼不出 key)
前端:
- 数据源&RAG 页补「检索试验台」:同一 query 并排跑生产链路 + 四路诊断,
召回不准时能直接定位是向量/分词/图谱哪一环挂了
- 支付拆成「配置 / 订单与对账」两个子页,挂到运维 > 支付 下;
导航支持二级菜单(NavParent 命中子路由自动展开)
- SettingsPage → ModelConfigPage「模型配置」,模型参数与计费规则合一
- 概览 → 仪表盘:并入计费与用量(UsagePage → UsageSection),
去掉系统健康拓扑(与服务状态页重复,同一份 /admin/status 数据)
- 全局隐藏滚动条(保留滚动)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 11:17:54 +08:00
Blizzard
ac4fd00e28
fix(deploy): gateway 挂载微信支付证书目录(容器读不到宿主机路径)
...
证书在 132 宿主机 /home/workspace/wechat-pay-cert,但 gateway 跑在容器里、没挂任何卷,
LoadPrivateKeyWithPath 直接读磁盘 → 路径不存在 → 商户私钥加载失败、微信渠道隐藏。
只读挂进 gateway(只有它碰支付,dispatcher/mcp-* 不用):
/home/workspace/wechat-pay-cert → /etc/sundynix/wechat-cert (ro)
⚠️ admin「系统配置 → 支付」里填的必须是**容器内路径** /etc/sundynix/wechat-cert/xxx.pem,
填宿主机路径会失败。私钥不进镜像、不进 git。部署时 compose 变更会让 up -d 重建容器生效。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 10:19:32 +08:00
Blizzard
c3151d1078
refactor(admin): 配置按域收口到「系统配置」页,Usage 只留用量观测
...
原本模型配置在「模型」页、计费规则与支付渠道却塞在「分析 > 计费&用量」观测页里(该页
~1290 行,首屏全是表单、真正的用量观测在第四屏)。按「域」重组:
- 新建「系统配置」页,两个 Tab:
· 模型 = 模型登记/激活/连通性测试 + 计费规则(汇率/硬拦截/按模型定价与权重)
· 支付 = 支付渠道配置(兑换码/积分包/微信参数) + 订单流水(订单流/对账/人工退款)
配了什么就在同处看它的规则与数据,不用来回跳。三块直接复用原组件,零重写。
- 「计费 & 用量」瘦成纯用量观测(消耗趋势/租户排行/发放积分)。
- 导航「模型」页并入系统配置(12 页 → 11),侧栏图标换齿轮。
- 顺带修残留 bug:数据源页「前往模型管理」原为 href=#/models + onClick 改 hash,
既指向已废弃路径、又是 hash 写法(早已换 BrowserRouter,点了不跳),改正规 <Link to=/admin/settings>。
tsc + build + vitest 41 过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-20 10:19:32 +08:00
Blizzard
d189dd967a
fix(admin): resolve JSX namespace and className syntax compilation errors in admin console
2026-07-18 22:21:13 +08:00
Blizzard
0d2929931f
feat(admin): upgrade admin console UI widgets, add plan & status management, and redesign system status dashboard
2026-07-18 22:18:12 +08:00
Blizzard
197531ea33
fix(site): 官网页头去掉深浅切换按钮(未挂 ThemeProvider 会运行时报错)
...
ThemeToggle 依赖 useTheme/ThemeProvider,但按'官网默认浅色'计划没搬那套 provider →
useTheme must be used within ThemeProvider 运行时崩。删掉切换按钮 + 无用的 theme-provider.tsx。
顺带 index.html 换中性标题 + favicon 指向官网 logo。排查确认无其它 Context/Provider 依赖、
logo 路径 /brand/ 正确。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 17:44:43 +08:00
Blizzard
6de1b3f6b2
feat(admin): 官网落地页融进 admin —— / 是官网、/admin 是运维后台
...
用户要 agent.sundynix.cn/ 打开是营销官网、/admin 是后台,且只用一个前端。
把 sundynix-site/web 的营销落地页融进 sundynix-admin 这一个 Vite 工程(不挂两份 embed):
- 拷官网到 src/site/:home(hero/功能/架构/快速上手/下载)+layout(header/footer)
+terminal-demo/logo/reveal 等 + content/site.ts(文案本就是本产品)。去掉博客/posts/埋点/RSS
(营销 only,零后端改动);use-release 改纯静态版本号。public/ 加 brand logo+favicon。
- Tailwind v4→v3 对齐:site 的 ink/ground/accent/term 等 token 加进 tailwind.config(var 引用)
+ :root/.dark 变量+keyframes 进 index.css;bg-ground/85 用 color-mix 还原。admin 自身用默认
gray/violet 调色板,不冲突;landing 的 bg-ground 只作用在 SiteLayout 容器、不改 body。
- 路由重构:App.tsx 换 BrowserRouter —— / 官网(公开)、/admin/* 后台(AdminGate 内做 me/Login/AppShell)。
routes.tsx path 改相对、AppShell 链接加 /admin 前缀。@ 别名指向 src/site。
- gateway/Dockerfile/nginx 零改动:单份 dist 挂根 + NoRoute SPA 兜底已支持 BrowserRouter 深链。
验证:tsc + vite build(Tailwind 编译过)+ vitest 41 过;gateway 二进制冒烟打真 serve——
/ 返回官网 index.html、/admin 与 /admin/tasks SPA 兜底 200、/brand/*.webp 200、/api/v1 走 API 401;
dist 里确认落地页文案已进 bundle。(注:MCP 浏览器当前断开,视觉渲染待部署后线上看。)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 17:36:43 +08:00
Blizzard
373167b705
feat(admin): 补全平台任务观测 + 空间管理 + 基建加 MinIO/实时探针
...
对照已实现系统功能补 admin 缺失模块(feat/site):
- 全平台任务/运行观测:GET /admin/tasks(跨租户查 sundynix_task,join 租户名/提交人邮箱/评测,
按状态/租户筛 + 状态计数;含 HITL 待审批=筛 waiting)+ TasksPage(状态卡+筛+表)。
- 空间(Space)管理:GET /admin/spaces(跨租户列 Space + 成员数子查询 + kind/归档态)+ SpacesPage。
- 基建观测:infra 加 MinIO(126 对象存储);postgres/redis/minio 从启动标志升级为实时 ping
(blob.Ping/Postgres.Ping/Redis.Ping),能反映中途掉线。StatusPage 加 minio 标签、6 个依赖。
- 模型健康/熔断:确认 DashboardPage 已有「运行时链路态」渲染 m.health,无需重做。
验证:admin tsc + vitest 41 过、gateway build/vet/test 过;两新页浏览器实测渲染+优雅错误处理;
两新端点起临时 gateway 打真 PG 实测——tasks(counts+3路join)、spaces(成员数子查询)均返真数据。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 17:06:25 +08:00
Blizzard
de315450a4
fix(mcp-py): NATS_URL 逗号多节点拆成列表(集群连接)
...
deploy-132 / deploy (push) Successful in 1m56s
Python nats.py 不像 Go 客户端会自动拆逗号串,把 nats://a:4222,nats://a:4223 整串当单
URL 解析端口 → ValueError: Port '4222,nats:' → invalid connect url,mcp-py 崩溃重启循环。
按逗号拆成 servers 列表传入即可。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 16:25:32 +08:00
Blizzard
55b8a697aa
ci(gitea): 132 上扁平布局 —— compose 与 .env 同放 $REMOTE_DIR 根
...
deploy-132 / deploy (push) Successful in 1m45s
之前 scp 到 $REMOTE_DIR/deploy/132-app/ 并在那跑,需 .env 也放深层目录。改为 compose
直接落 $REMOTE_DIR/docker-compose.yml、在 $REMOTE_DIR 根下 up,与用户预放的 $REMOTE_DIR/.env
同级。--no-build 不碰构建上下文,compose 里的相对 build 路径在 132 上无影响。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 16:17:48 +08:00
Blizzard
09ac03b758
perf(docker): 依赖下载全部换国内源(内网构建提速)
...
deploy-132 / deploy (push) Failing after 5m35s
go mod download 走默认 proxy.golang.org 内网极慢。四个 Dockerfile 统一换国内镜像:
- Go: GOPROXY=https://goproxy.cn,direct (三个 Go 服务)
- alpine: apk 源换 mirrors.aliyun.com(ca-certificates/tzdata)
- pip: mcp-py 换清华 pypi 源
- npm: gateway admin 已在用 npmmirror(此前已有)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 16:03:45 +08:00
Blizzard
2d14245086
fix(docker): 运行镜像 gcr.io/distroless → alpine(内网 runner 连不上 gcr.io)
...
deploy-132 / deploy (push) Has been cancelled
128 的 runner 能拉 docker.io 但连不上 gcr.io(dial 443 i/o timeout),distroless 拉不到。
三个 Go 服务运行 stage 换 alpine:3.20 + ca-certificates(TLS 连 LLM/MinIO)+ tzdata +
非 root 用户。二进制 CGO_ENABLED=0 全静态,alpine 上直接跑;本地已验证构建+启动正常。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 15:54:35 +08:00
Blizzard
84a32023ee
ci(gitea): 检出改为从内网 Gitea 克隆,绕开 github.com 拉 actions/checkout
...
deploy-132 / deploy (push) Failing after 33s
内网 runner 连不上 github.com,actions/checkout@v4 下载 action 时 unexpected EOF。
改用 git init+fetch 从 git.sundynix.cn(同内网必达)拉当前分支,用工作流自带
github.token 认证,不再依赖 github.com。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 15:47:33 +08:00
Blizzard
7cacfdb5a3
Merge remote-tracking branch 'fork/main'
deploy-132 / deploy (push) Failing after 1m31s
2026-07-18 15:41:18 +08:00
Blizzard
e350fa9595
ci: release.yml 也加 Gitea 守卫(打 tag 时不在 Gitea 上误跑桌面发布构建)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 15:33:29 +08:00
Blizzard
05b66399f9
ci(gitea): 改为 runner 构建镜像 + save/scp(照 sundynix-site 模式)
...
之前写成 rsync 源码到 132 本地 build,需要 132 上有全套源码。改回 sundynix-site 的做法:
runner(128) 构建 4 个应用镜像 → docker save 打包 → scp 镜像包 + compose 到 132 →
132 docker load + docker compose up -d --no-build(用预构建镜像,不在 132 上 build)。
于是 132 上只有 docker-compose.yml + .env(+ 临时镜像包),没有源码。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 15:17:50 +08:00
Blizzard
da0d964054
ci(gitea): 部署目录改为 /home/workspace/sundynix-agentix
...
与 sundynix-site 的 /home/workspace/ 一致(用户实际部署路径)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 15:11:10 +08:00
Blizzard
2599936777
ci(gitea): push-to-main 自动部署到 132 + 防 GitHub CI 在 Gitea 误跑
...
- .gitea/workflows/deploy.yml:runner(128) 检出 → rsync 仓库到 132 → 在 132 本地
docker compose up -d --build → 循环 curl :3000/healthz。在 132 构建、132 运行(不拿
4 个大镜像去 128 构建);rsync 排除 .env/node_modules,不覆盖 132 上的密钥。
Gitea secrets:DEPLOY_HOST/DEPLOY_USER/DEPLOY_PASSWORD。
- .github/workflows/ci.yml:6 个 job 加 if !contains(github.server_url,'sundynix.cn'),
Gitea 上只跑 deploy、不误跑 GitHub CI(此前为此把 Gitea 工作流整个关了)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 14:45:24 +08:00
Blizzard
6b45adbbd2
chore(deploy): 三机部署 compose 拆分 + NATS 3 节点集群
...
126=MinIO(已部署)、128=基础设施、132=应用。
- deploy/128-infra:NATS 3 节点集群(独立卷+cluster routes,暴露4222/4223/4224)+PG/Redis/
Milvus(自带etcd+自带内部minio,不复用126)/Neo4j/Jaeger;端口对局域网暴露、卷持久化。
- deploy/nats-cluster/nats{1,2,3}.conf:max_payload 一致 + jetstream + cluster。
- deploy/132-app:仅 gateway(3000:8080)+dispatcher+mcp-go+mcp-py,无 admin nginx;env 指向
126/128;mcp-go 补齐 MINIO env(本会话 blob 遗漏);NATS_STREAM_REPLICAS=3;.env.example 模板。
- docker-compose.prod.yml(单机):删 admin 服务(已内嵌)、mcp-go 补 MINIO env。
- 三个 compose 均 docker compose config 校验通过;.env 已 gitignore、.env.example 可提交。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 14:45:24 +08:00
Blizzard
2e02a12260
feat(bus): JetStream 流/KV 副本数可配(NATS_STREAM_REPLICAS,默认1)
...
三机/单机多节点集群部署需 Replicas=3,durable 流才有 Raft quorum 容错(计费/状态/
用量/评测/审批/入库回写不因单节点挂掉而丢)。此前 6 处 CreateOrUpdateStream + 1 处
KV 桶都未设 Replicas(默认1),集群等于白搭。加 streamReplicas() 读 env(合法值 1/3/5),
7 处统一带上。默认 1 保证单机/CI 不变。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 14:45:24 +08:00
Blizzard
28a1543249
feat(gateway): 内嵌 admin 控制台(go:embed),去掉单独 nginx 容器
...
复刻 sundynix-site 的 webfs 模式:gateway 一个容器同时 serve 控制台 UI + API + 供
桌面端直连。admin 用 HashRouter + API base 走相对 /api/v1(构建传 VITE_GATEWAY=''),
源码零改动即可同源 serve。
- internal/webui:go:embed all:admin_dist + Dist();占位 index.html 让不构建前端也能编译。
目录命名 admin_dist(避开 .gitignore dist/ 与 .dockerignore **/dist 通配)。
- router.go NoRoute:非 /api/ 路径走内嵌静态,命中 /assets/* 直吐、否则回退 index.html;
embed 失败仅 log 降级不影响 API。/metrics /healthz /readyz /api/v1 已注册,永不进 NoRoute。
- Dockerfile 加 admin node 构建 stage,go build 前 COPY dist 覆盖 embed 占位。
- live 验证:/ 返回 admin(200)、/assets/*.js(200)、/api/v1/admin/overview(401 走 API)、/healthz(200)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 14:45:24 +08:00
Blizzard
3a4e1d53a5
refactor(payment): 渠道抽象成 Channel 接口 —— 接新渠道=加 adapter 不动骨架
...
PAYMENT_DESIGN §3 承诺的 internal/payment/channel.go 适配器接口此前不存在,微信硬编码在
manager/handler 里。补齐抽象:
- channel.go:Channel 接口(Name/CreatePay/QueryOrder/VerifyCallback)+ 统一 PayIntent/PayResult
+ 渠道名常量。入参用基本类型不吃 *store.PaymentOrder,payment 包不反依赖 store。
- Wechat 实现 Channel(编译期断言 var _ Channel);QueryResult 归一为 PayResult;CreatePay 返回 PayIntent。
- Manager 从「持一个 *Wechat」改为渠道注册表:Get(name)/Available()/Status(name)/ReloadWechat;
按渠道名持有已装配实例,热重载不变。
- 回调路由收敛 /billing/callback/wechat → /billing/callback/:channel 按名路由(旧 notify URL 仍匹配);
查单/掉单补偿据 order.Channel 路由,不再写死微信。下单支持可选 channel(缺省 wechat)。
- 支付宝/Stripe 现在真·只差一个 adapter+注册。唯一未泛化:回调 ack 应答格式(现微信态,注释标明)。
- payment 包首个测试:Manager 注册表 4 用例(空/注册摘除/空配置/配置不全)。build/vet/test/lint 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 13:56:24 +08:00
Blizzard
86e79d76ac
docs(eino): 诚实修正 Phase C —— 报告流水线未 compose 化(刻意保留独立路径)
...
Phase C 改动清单曾把「report.go 改用 compose」标 ✅ ,实际 report intent 仍走独立
handleReport(writeSections 并发流水线),不经 compose.Graph。改为诚实记录:报告是
固定形状专用流水线、并行已由 writeSections 拿到,硬迁移只是拿稳定功能冒回归风险、
无用户可见收益,故刻意保留为 compose 之外的第二条编排路径。顺带把 Phase C 头部
灰度开关的陈旧描述更新为「已单引擎、graph.go 退役」的实况(与代码一致)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 13:45:51 +08:00
Blizzard
1beef44625
ci: 加 -race + golangci-lint(new-issues) + govulncheck/gitleaks 安全扫描
...
补齐审计指出的「CI 缺 lint/安全扫描/-race」低成本质量门:
- go job:go test → go test -race(4 模块本地已验证 race-clean,ubuntu 自带 gcc)。
- lint job:golangci-lint(standard 集)仅 PR 跑 + only-new-issues —— 新代码必须干净,
存量 ~42 处(多为未检 Close/死代码)单独消化,不拿存量红门。加 .golangci.yml(测试放过 errcheck)。
- security job:govulncheck advisory(stdlib/nats CVE 靠 toolchain/依赖升级,只做可见性不阻断)
+ gitleaks 扫提交防密钥泄漏。
- 顺手清掉本会话新代码的 lint:blob GetBytes 的 defer Close、退款测试未检 CreateOrder。
注:gitleaks-action 个人/公开仓库免费;组织仓库需 GITLEAKS_LICENSE。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 13:44:38 +08:00
Blizzard
5e5f6e9610
feat(billing): 人工退款入口 —— paid 单冲销积分+置 refunded(幂等)
...
PAYMENT_DESIGN §5 承诺却只留了 OrderRefunded 常量、无入口。补齐全链路:
- store.RefundOrder:订单 CAS(paid→refunded) 为主闸(重复退 changed=false 幂等),
同事务记 adjust 负分录(ref=订单号)+ 回退物化余额。照抄 MarkOrderPaid 双闸范式;
新增 idx_ledger_refund_ref 部分唯一索引(kind='adjust' AND ref<>'')做账本级兜底,
与 grant 索引对称、不与 admin 手工校正(ref 空)冲突。
- 积分若已消费,回退后余额可为负(人工退款预期,账本仍自洽,后续消费被硬拦截)。
- handler AdminRefundOrder + POST /admin/orders/:id/refund(admin 组已挂 Audit 留痕);
真渠道钱款原路退回需 admin 另在商户后台操作,本地仅冲销积分与订单态(不接自动退款 API)。
- admin 订单流加「退款」按钮(仅 paid 单可见,二次确认+填原因)。
- 测试:RefundOrder 冲销+幂等、只退 paid 两个不变量测试(sqlite 真 DB,余额=账本之和)。
gateway build/vet/test 全绿,admin tsc 干净。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 13:35:31 +08:00
Blizzard
ec9431bfa4
fix(report): 报告源/产物落 MinIO,去掉 gateway↔mcp-go 本地盘耦合
...
多机/容器化第一时间断裂的隐患(ARCHITECTURE_REVIEW §7 #1 ):此前报告
源(reportStore)与 .docx 产物(reportExport/reportRender)都写 mcp-go
本地 SUNDYNIX_REPORTS_DIR,gateway 再按同一路径 c.File 回流——mcp-go
与 gateway 不共享磁盘即断。
- blob 包从 gateway/internal 提到 sundynix-shared/blob,gateway 与 mcp-go
共用同一 MinIO;新增 PutBytes/GetBytes 走二进制(.docx)。
- mcp-go 注入 blob:report 源/产物优先 Put 到对象存储(键 reports/<id>.{json,docx}),
导出结果返回 minio://<key>;MinIO 未就绪回退本地盘(单机降级,getSource 兼容旧本地报告)。
- gateway ExportReport 识别 minio:// → GetBytes 流式下载,否则 c.File 本地降级。
- 测试:mcp 本地回落往返单测 + shared/blob 真 MinIO 二进制往返(BLOB_TEST_ENDPOINT 门控)。
四模块 build 绿,受影响测试全过。
- 附带:go mod tidy 引入 genproto 拆包冲突,四模块统一钉单体 genproto 新版解决
(注:勿 go work sync,会剪掉 indirect require 回落旧版重现冲突)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 13:29:31 +08:00
Blizzard
1c7e8e1ea9
docs: 修 DEPTH_ROADMAP/EINO 两处过期标记
...
- 删 T4.E 熔断器接回 failover 重复的过期 [ ] 项(222 行 ✅ 为准);
T2.1 熔断器联动注明已整合。
- EINO Phase D FSM 回写主题名笔误 sundynix.tasks.status → sundynix.status.task
(文档 141 行自己记过这次迁移)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 13:29:01 +08:00
Blizzard
7ae7f7be67
feat(memory): 记忆召回加 Relevance —— Generative Agents 打分补齐第三项 (P1)
...
审计三真桩之一。记忆召回此前打分只有 Recency+Importance,缺 Relevance(对当前
任务的语义相关性)——注释写"待接 Milvus",但召回时甚至不知道当前问什么。
关键发现:dispatcher 注入点 fetchMemory(ctx,uid,_) 手上已有当前任务文本(b.query),
只是被 `_` 丢弃了。所以不是"接 Milvus"那么重,把 query 一路传下去 + 缓存嵌入即可。
设计(偏离注释的"接 Milvus"——用户偏好量小,不值当上向量库):
- Profile 加 embedding 列(float32 小端打包存 bytea);Upsert 时对 value 向量化缓存
(value 没变不重算,失败留空不阻断)。
- memory 包定义 Embedder 小接口,gateway 注入 rag.Engine(复用同一控制面下发的
embedding 模型),不硬依赖 rag 内部;rag.Engine 加导出 Embed 方法。
- memory_get 工具加可选 query 入参;fetchMemory 停止丢弃 b.query 传下去。
- Get(ctx,uid,query):query 非空且 embedder 就绪 → embed(query) 对每条缓存向量
内存算余弦 → 三项打分 0.25R+0.35I+0.4Rel;否则回落两项(升级前行为)。
- 优雅降级贯穿:无 query/无 embedder/query 嵌入失败/行无向量 → 静默回落,绝不报错。
零 Milvus 依赖、零向量库同步问题、保住"没 embedding 也能跑"。
验证:单测(编解码往返/cosine 截0/三项模式相关性翻转顺序/降级返 nil)+ 端到端
(真 PG:写入即向量化、query=咖啡把低重要度的咖啡记忆翻到运动前面)。migration
加列已 live;embedding 复用 RAG 已验证基建。三模块 build/vet/test 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 12:47:55 +08:00
Blizzard
a742d118ad
test(web): 薄 Web 面加 RTL 组件测试 —— 补最大覆盖缺口 (P1)
...
完成度审计:web 端几乎零覆盖(仅3个 api 纯逻辑用例、无任何组件测试)——三前端里
最薄。补上 RTL 基建 + 入口流组件测试。
- 加 @testing-library/react+jest-dom+user-event,src/test/setup.ts,
vitest setupFiles 接入。
- AuthPage.test.tsx(5 例,mock api 层):登录/注册态切换(名字字段显隐)、
邮箱密码空时按钮禁用、登录成功回调 onAuthed 且带对参数、登录失败显示后端
错误文案不回调、注册态走 authRegister 带名字。
web 测试 3→8。CI web 矩阵已跑 npm test,自动纳入关卡。
tsc + 三前端(desktop 68/admin 41/web 8)全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 12:28:57 +08:00
Blizzard
e29bc9a91e
feat(admin): 「数据源 & RAG」页做实 —— admin 三 mock 页清零 (P1)
...
审计 P1「admin 三页纯 mock」最后一页。此前 DatasourcesPage 的 GraphRAG 拓扑图
写死节点、「向量/全文/图谱权重滑块」纯 mock——而且权重概念本身虚构:mcp-go 的
RRF 融合是各路等权的倒排互惠融合(rrfK=60 平滑常数),根本没有"每路占几成"。
- store/datasource_query.go:AllDatasources(全平台 KB + 各库文档数/总字数,
按 (space_id,name) 关联 doc,WithoutTenant);GET /admin/datasources(含
SystemCounts 平台计数)。
- DatasourcesPage 重写:保留真实 Embedding 模型配置(ModelManager) + 诚实的
混合检索管线说明(三路 Milvus/Bleve/Neo4j + RRF 等权融合 k=60,非可调权重) +
平台计数卡片 + 真知识库清单表。删假滑块+假拓扑(~200行 mock)。
诚实边界:RRF 无每路权重,不摆假滑块;检索参数在 mcp-go 代码中。
live:/admin/datasources 返 34用户/25库/53文档,清单带真实文档数字数;
浏览器渲染全对。tsc+41 vitest 全绿。**admin 三 mock 页(Evals/Guardrails/
Datasources)全部做实。**
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 12:26:16 +08:00
Blizzard
c02ebc7bce
feat(admin): 「安全护栏」页做实 —— 接真护栏事件,去 mock (P1)
...
审计 P1「admin 三页纯 mock」之二。此前 GuardrailsPage 是写死的正则/敏感词
编辑框(改了不生效的假配置)+编造拦截日志。后端 /admin/guardrail-events 早已
现成(T4.B),纯前端做实。
- 命中事件流接真数据(guardrail_event,middleware.Guardrail 命中即落库):
blocked 硬拦/suspect 灰区放行,带原因/信号/路径/来源;计数卡片+原因 Top 分布
(客户端从近100条聚合)+按 kind 筛。
- 诚实处理假配置:护栏规则(Tier1 正则/词库 + Tier2 LLM 分类器)是中间件代码
常量、非运行时可配,故删掉"能改却不生效"的编辑框,换成只读规则说明 + 指出
运行时可配需另建配置存储(参考提示词控制面)。不摆假控件。
live:触发注入越狱输入→422 硬拦+落库(reason「疑似提示词注入」)→页面渲染
真事件流+原因分布。tsc+41 vitest 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 12:07:32 +08:00
Blizzard
d04d830c37
feat(admin): 「自动评测」页做实 —— 接真评测数据,去 mock (P1)
...
审计 P1「admin 三页纯 mock」之一。此前 EvalsPage 是写死的质量趋势+编造的
错题本+虚构纠偏轨迹。现接 sundynix_eval 真数据(评测经 JetStream eval 流持久
落库,刚升级)。
- store/eval_query.go:EvalTrend(按天 avg 综合分/忠实度+低分计数)、EvalSummaryFor
(ok/warn/poor/corrected 计数+均值)、PoorEvals(错题本,level in poor/warn +
评语+纠偏标记+租户名)。全 WithoutTenant 平台口径;忠实度均值只算 sources>0
(无来源的忠实度恒0会压低失真)。
- GET /admin/evals?days=(RequireAdmin);admin api.ts + EvalsPage 重写:
总览卡片(综合分/合格率/低分占比/纠偏采纳率)+质量&忠实度趋势(纯SVG折线+低分
背景条)+错题本(点行展开评语)。
- 诚实边界:纠偏「前后全文对照」后端未持久化,只存了 Reason/Corrected/各维度分,
故错题本展示评语+「已纠偏」标记,不再编造 before/after。
live:/admin/evals 返 57 次评测 avg=0.88、错题本10条、11天趋势;浏览器渲染
真数据(趋势线07-15真实下探)。go+tsc+41 vitest 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 12:03:54 +08:00
Blizzard
a1c35852ef
chore: 删死代码 —— OpenReport/ReadLocalFile/isDesktop/openReport + admin Soon (P2)
...
完成度审计确认全链零生产调用:
- app.go OpenReport(仅被死包装引用)、ReadLocalFile(仅测试引用);
desktop.ts openReport 包装、isDesktop 导出(实际用 isMacDesktop)——全删。
绑定重新生成(OpenReport/ReadLocalFile 归零,PrintReportPage/SaveReportAs/
Notify 保留)。删对应的 app_test TestReadLocalFile。
- admin Soon.tsx(规划中占位组件)已成未引用死组件——routes.tsx 9 条路由全
ready:true,import 未用。删组件+import。
openInSystem/filepath/os 仍被 PrintReportPage/download 使用,不孤立。
desktop go test + tsc + 68 vitest、admin tsc + 41 vitest 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 11:56:51 +08:00
Blizzard
940330cdb7
feat(eval): 评测结果回写升 JetStream 持久 —— 消灭最后一处 core NATS 回写 (P1)
...
完成度审计 P1 + 记忆 nats-durability 的既定规矩「计费/需落库的回写一律
JetStream+幂等,别fire-and-forget」。此前 eval 是全仓最后一处 core NATS pub-sub
回写:网关离线/慢消费者时评测结果直接丢——而质量趋势/门控都依赖它。
照抄已升级的 status/usage 范式(同为 dispatcher→gateway→PG 回写):
- contract 加 StreamEval/ConsumerEval;bus 加 EnsureEvalStream + ConsumeEval
(durable consumer,AckExplicit,落库失败 Nak 重投自愈),PublishEval 改
js.Publish 同步等 ack。删 core NATS 的 SubscribeEval。
- gateway/dispatcher 两个 wrapper 在 connect 时 EnsureEvalStream;gateway main
的评测订阅从 SubscribeEval(fire-and-forget)换 ConsumeEval(handler 返 error→
Nak),接入优雅停机 drain。
- 幂等前提已满足:SaveEval 按 task_id upsert,at-least-once 重投只覆盖不重复。
验证:e2e 去重测试(TestGatewayQueueDedup)升级到 ConsumeEval,50 条两副本合计
处理50次零重复;live 真端到端:提交任务→跑完→评测经新 eval 流落 PG(level=ok)
一次成功;两服务启动 eval 流 ensure 无报错。go build/vet/test 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 11:52:40 +08:00
Blizzard
3d15cdb493
fix(gateway): 任务落库失败不再吞 —— 关键写失败上浮 5xx (P0-2)
...
DEPTH_ROADMAP:225 未完项 + 完成度审计 P0-2:launch() 里 SaveTask 是 best-effort,
DB 写失败只 log 却照样 PublishTask + 返 202。结果任务发出去在后端跑了,却不进
运行历史、复盘不了、报告类的用户切页面回来彻底找不回——用户以为成功、实际没落库。
- SaveTask 对「DB 降级(nil)」返 nil、对「DB 活着写失败」返真 error,天然可区分:
前者静默跳过(开发态本就无库),后者上浮为提交失败。落库在 Publish 之前,
失败时还没发布,中止干净、不产生"看不见的执行"。
- 两个调用点(SubmitTask/GenerateReport)已把 launch 错误映射 5xx,无需再改。
范围克制:审计列的其它 best-effort 点保持不动——dispatcher 的异步回写
(UpdateTaskStatus/SaveTaskOutput/SaveTaskTrace)、审计日志、用量累计计数,
都不是"用户等着响应"的路径,没有请求可返 5xx,best-effort 是对的。
唯独 launch 这处是"用户以为提交成功实际没有",才该阻断。
live 验证:改名 task 表模拟 DB 写失败 → 提交返 502+明确文案(不再假 202);
表改回 → 立刻恢复 202。happy path 202+落库不变。go build/vet/test 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 11:45:08 +08:00
Blizzard
bff6e5c7fd
test(gateway): 补上「钱路径」与租户隔离的回归测试 —— P0 最高优先级缺口
...
完成度审计(见记忆 completion-audit)最尖的一条:涉及钱的路径此前 0 测试,
支付一上线 bug=真实错账;租户数据层隔离也只测了角色门禁没测数据层真隔离。
把本会话 live 手验过的断言固化成回归测试。
测试基建:纯 Go sqlite(glebarez,无 CGO)内存库,迁同款模型+建部分唯一索引+
挂租户作用域回调,复用生产 store 方法测真逻辑。CI ubuntu 无 Postgres 也能跑
(此前 store 测试全是纯逻辑,DB 事务逻辑从没进过关卡)。
钱路径不变量(6):
- GrantCredits 记分录+增余额,余额恒等于 SUM(ledger)
- 兑换码核销一次性(CAS)+原子入账,重复核销余额纹丝不动
- 账本(kind,ref)部分唯一索引:重复 grant 被兜底拦下、usage 不受约束
- MarkOrderPaid CAS 幂等:重复回调 changed=false 不重复入账
- SaveUsageEvent task_id 幂等:同任务重投不重复扣费
- ReconcileOrders 抓出 order_without_ledger(钱到了积分没给)
租户隔离(3):创建自动填 tenant_id、查询按 ctx 租户过滤、跨租户改/删命不中、
WithoutTenant 系统视角全可见。
go build/vet + 全 gateway 测试全绿;sqlite 仅测试引用,不进生产二进制。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 11:39:36 +08:00
Blizzard
3db2de1ef6
feat(billing): 支付 P5.3 —— 掉单补偿定时器 + admin 订单流 + 日终对账
...
支付线封口。此前 pending 单只在「用户开着账单页轮询」时才查单确认——用户扫完码
关页面,钱付了、积分永不到账。
- 掉单补偿定时器(payment_reconcile.go):gateway 内每分钟扫 pending 微信单,
逐单 reconcileOrder 主动查单落态。把「用户在不在场」从入账链路摘掉。
reconcileOrder 从 BillingOrderStatus 抽出、前端轮询与定时器共用一份幂等
落态逻辑(不重蹈 GenerateReport/SubmitTask 的漂移)。渠道未配置时空转不炸。
- admin 订单流 GET /admin/orders(状态计数+全平台订单,可筛)。
- 日终对账 GET /admin/orders/reconcile:paid 单 ↔ 账本 grant 分录逐单比对,
抓 order_without_ledger(钱到了积分没给,最严重)/ ledger_without_paid_order。
- admin 计费页「充值订单与对账」块:计数卡片+订单流+一键对账。
⚠️ live 抓到并修掉一个真 bug:OrderStats 复用同一个 gorm.DB 链式 Count 三次,
WHERE 累加成 status=A AND status=B → 恒 0(订单流显示 2 单但计数全 0)。
改成每次起新 query builder。—— 又一次只有 live 才暴露的。
验证:go 6 包测试+tsc+41 vitest 全绿;live 造差异单对账正确抓出
order_without_ledger、清账后回零差异;补偿器启动日志+渠道未配置空转不炸;
浏览器验订单流卡片+一键对账绿条。TTL 过期路径需真渠道触发,部署后自然覆盖。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 11:25:37 +08:00
Blizzard
3767c78ee8
chore: gitignore desktop 模块目录裸二进制(go build ./... 遗留)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-18 11:13:48 +08:00
Blizzard
53d9f49e96
docs: create VOICE_DESIGN.md and update go.work.sum dependencies
2026-07-17 22:54:53 +08:00
Blizzard
5d7eca5de3
fix(billing): 微信支付钉死「微信支付公钥」验签体系 —— 商户 2025-09 开户没有平台证书
...
用户拿旧项目代码对出来的真问题:我此前用 WithWechatPayAutoAuthCipher(平台证书
模式,APIv3 密钥自动下载平台证书验签),但 2024 起新注册商户只发「微信支付公钥」
(PUB_KEY_ID_ 开头)、没有平台证书——在该商户号上初始化/回调验签都会挂。
- 改 WithWechatPayPublicKeyAuthCipher(商户私钥+公钥ID+公钥文件);回调验签用
NewSHA256WithRSAPubkeyVerifier;平台证书模式不留双模式赘肉(YAGNI)。
- Config 增 public_key_path/public_key_id(必填,公钥文件同样只存路径);
admin 卡片补两字段;env 兜底加 WECHAT_PUBLIC_KEY(_ID)。
- 顺手修 live 撞出的真 bug:sundynix_setting.value 是 varchar(255),
支付配置 JSON(含加密密钥)一条就超(SQLSTATE 22001)→ 改 text。
live:列类型已迁 text;缺公钥两项报「配置不全,缺: public_key_path,
public_key_id」;GET 回显含新字段。go 6 包测试+tsc+41 vitest 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-17 11:12:26 +08:00
Blizzard
d1e1e0fc4a
feat(billing): 微信支付配置进 DB —— admin 控制面保存即热生效
...
用户定的形态:配置存数据库、密钥文件放服务器磁盘(库里只存路径)。
env 降级为兜底(DB 优先 → env → 隐藏,与 tokens_per_credit 同一约定)。
- payment 包重构:Config(6 字段)+ Manager(RWMutex 热重载,学 prompt 控制面
改完即生效不重启);未启用原因人话化(未配置/缺哪些字段/初始化失败具体错)。
- APIv3 密钥入库前 AES-GCM 加密(shared/secrets,与模型 API Key 同一把
SUNDYNIX_SECRET_KEY);GET 只回 has_apiv3_key 不回显;PUT 留空=沿用旧密钥
(只写不回显语义,同模型 Key)。
- admin GET/PUT /admin/payment/wechat;业务路径全部改经 Manager.Current()
取快照(BillingPacks/下单/查单/回调)。
- admin 计费页「微信支付配置」卡片:状态徽章(已启用/未启用+原因)+六字段
+保存并热生效。
live:无配置→「未配置」;存假配置→热重载报「私钥加载失败:decode err」;
去掉 appid→「配置不全,缺: appid」;密钥留空沿用(has_apiv3_key 保持 true);
psql 复核库内密文 enc:1: 前缀、不含明文子串。go/tsc/41 vitest 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-17 10:55:30 +08:00
Blizzard
efd185b779
feat(billing): 支付 P5.2 —— 微信支付 Native 渠道(扫码充值)
...
wechatpay-go v0.2.21。凭据全 env 注入(WECHAT_MCHID/MCH_CERT_SERIAL/
MCH_PRIVATE_KEY/APIV3_KEY/APPID/NOTIFY_URL),缺一渠道即隐藏——半配置/假凭据
只打日志不拖垮 gateway(用假私钥实测过降级)。
- internal/payment:Native 下单出 code_url、APIv3 回调验签解密、主动查单,
三者统一收敛为 QueryResult。
- 下单 POST /billing/orders {pack_id}(≥member+审计):金额/积分按在售包服务端
锁定进订单行,不信任客户端;渠道下单失败即作废,不留付不了的 pending。
- 到账两条路汇入同一个 MarkOrderPaid 幂等闸(CAS+唯一索引双闸,同 P5.1):
①公开回调路由(验签是唯一的门;金额与订单不符不入账);②前端轮询的
GET /billing/orders/:id 在 pending 时顺路主动查单——本地/内网收不到公网
回调也能确认到账,回调只是生产更快的通道。pending 超 30 分钟置 expired。
- Web 面:在售包卡片(渠道亮才出现)→扫码弹窗(qrcode 画 code_url,二维码底色
固定纯白——暗色主题下低对比码扫不出来)→2.5s 轮询→到账 toast+刷余额。
验证:go/tsc/vitest 全绿;无凭据+假凭据两种降级 live 四连
(channels 只剩 redeem/下单 400 引导兑换码/回调 503/兑换码闭环不受影响)。
⚠️ 真通道(prepay→扫码→回调/查单→入账)需真实商户号,未 live——用户配好
env 后用小额包实测,建议先配 ¥0.01 测试包走一单。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-17 10:39:24 +08:00