Image Studio
把 Azure gpt-image-2 / 1.5 / MAI 等图像模型包成的设计师向 SaaS:access key 登录 + 积分计费(无支付)+ 隐藏管理后台。线上 design.mvp.restry.cn。
当前 Tasks
🔥 进行中
-
src/lib/azure.ts加 429 指数退避重试 + 读Retry-Afterheader;客户端友好文案(“目前画图较忙,稍后重试”),不要把 Azure 原话甩给用户 — (待派, 2026-05-13) - Azure
gpt-image-2/gpt-image-15配额扩容评估(S0 tier IPM 个位数,高峰真不够)— (待 resley 决定升 tier 还是限流, 2026-05-13)
⏸ 等决策/卡点
- 仓库 owner 转移:
bochub/image-studio(私有) →Restry账号;当前 fries/Restry 都不是 owner,需 daddy 用 bochub 账号在 GitHub 网页 Settings → Transfer ownership 操作,转完 fries 把本地 remote 切到新地址 — (等 daddy, 2026-05-14)
✅ 最近完成(保留 7 天)
- 线上 429 限流提示根因定位(azure.ts 只对 5xx 重试,429 直抛 + Azure 原话透传)— (2026-05-13)
概述
2026-04-23 ottor 在 deployer 上从零起项目,目标是把团队自有的 Azure 多模型画图能力(azure-imagen 的 gpt-image-2 / gpt-image-1.5 / MAI-Image-2e / Sora)包成一个对外可用的极简画图 SaaS:陌生人能看示例画廊 → 用 access key 登录 → 用积分调用模型出图 → 充值码加积分(不接支付,仅积分)。代码生成由本地 Claude Code (默认模型 claude-opus-4.6) 在 background 跑,每轮人审一次。
架构
| 项 | 值 |
|---|---|
| 域名 | design.mvp.restry.cn(mvp-deployer Caddy 路由) |
| 端口 | 3789(PM2 管理) |
| 部署 | 走 deployer 异步管线(POST /api/deploy 202+taskId → 轮询 /api/deploy/tasks/:id) |
| 项目目录 | ~/projects/image-studio/(本地) / /opt/mvp-apps/image-studio/(线上) |
| 技术栈 | Next.js 15 + Prisma + 后端 Postgres |
| Admin 入口 | /mgmt-7bx3k/login(隐藏路径,强密码 otbqvOyBDSy2Jt9i-h6A_cB1) |
| 用户入口 | /login(access key 单字段登录) |
| 图片存储 | /var/lib/image-studio/images(项目外,不被部署覆盖;IMAGE_STORAGE_DIR env 注入) |
| Skill | ~/.openclaw/skills/azure-image-generation/(已脱敏,引用 ~/.credentials/.env) |
核心功能
- 双登录:access key(普通用户) + 微信公众号(嵌入微信用,仍预留,未启用)
- 积分计费:模型支持调倍率,Image 1.5 可设 1 积分/张、Image 2 可设 2-3 积分/张;管理端可改
- 管理后台
/mgmt-7bx3k:Dashboard(使用情况)+ models / access-keys / topup-codes / 生成日志(含预览图 + 实际命中的 endpoint) - 公开首页画廊:
opennana-gallery+awesome-gpt-image-2数据源融合后筛选过的 266 条 safe prompt(瀑布流 + 分类 tab + 弹窗预览,无搜索框,使用 Anthropic 橙色调),不抓 youmind(一开始尝试抓 youmind 27 类后改用开源数据源) - 生成排队:
GenerationJob状态机queued → running → succeeded/failed/blocked,前端轮询;含多 endpoint 轮询负载均衡(管理端可配多个同模型 endpoint,按 in-flight 分流:第 1 个忙就走第 2 个) - 限流:通过 caddy-reverse-proxy 网关层
caddy-ratelimit模块做 4 档限流(auth 5/min、topup 10/min、generate 20/min、其他 60/min),见下文 04-23 安全章节 - 手机端:移动端生成中加 shimmer 占位骨架(“图在显形”),不强制跳转历史
- 图生图:用户端生成页面支持上传/选择参考图 + prompt 一起送进
/images/edits(Azure multipart),与纯文生图共用同一队列与积分扣费
凭据治理
完全跟随 deployer 04-23 的 secret v2 方案:
- 项目读
process.env.IMAGE_STUDIO__*(在 ecosystem.config.js 中显式从 PM2 daemon env 拿,daemon 由pm2-claw.servicesystemd unit 启动并EnvironmentFile=/home/claw/.credentials/.env) - 部署时
manifest.env只传公开值(如NEXT_PUBLIC_APP_URL),所有 8 个 secret 由 deployer 按IMAGE_STUDIO__前缀自动从~/.credentials/.env抽出去前缀写进/opt/mvp-apps/image-studio/.env - 涉及 keys:
DATABASE_URL/SESSION_SECRET/ADMIN_PASSWORD/WECHAT_APPID/WECHAT_APPSECRET/AZURE_OPENAI_ENDPOINT/AZURE_OPENAI_API_KEY/AZURE_OPENAI_API_VERSION
数据库迁移:SQLite → Postgres(04-23 当天 hotfix)
最初版本用 SQLite (file:./data/app.db),04-23 晚上踩了致命的部署覆盖坑:
- Bug 1(Prisma 路径解析):
DATABASE_URL=file:./data/app.db被 Prisma 解析成 schema.prisma 的相对路径 → 真库在/opt/mvp-apps/image-studio/prisma/data/app.db - Bug 2(preserve-data 错位):deployer
--preserve-data data保的是项目根data/,真库在prisma/data/没被保住 - Bug 3(zip 漏 exclude):本地打包只
-x 'data/*',没 excludeprisma/data/*,dev 库被压进 zip 直接覆盖线上库 - 现象:每次重新部署用户 / access key / 历史图全没了
解法(同日完成):
- 服务器侧:复用 mvp-deployer 那台的
mvp-postgres容器(postgres:16-alpine,0.0.0.0:5432,但 Azure NSG 拦了公网,只能127.0.0.1:5432访问)。建独立 dbimage_studio+ 独立 user,密码进~/.credentials/.env - 本地侧:装 systemd
postgresql-16起 dev pg(同名 db / user / 独立密码),生产/开发库完全隔离,本地用 SSH tunnel127.0.0.1:15432 → 服务器:5432在需要时连 prod - Prisma schema provider
sqlite→postgresql,重灌 266 条 safe prompts seed
04-23 部署历险一览
按时间序的关键踩坑(同 deployer v2 异步管线一同诞生,互相是对方的 smoke test):
- Next.js 15 standalone 不读 .env —
next start在生产模式下并不自动加载.env,导致 ADMIN_PASSWORD / DATABASE_URL 全空 → 401。改为非 standalone + dotenv-cli 起或在 PM2 daemon env 注入 - Admin 登录 middleware 死循环 — middleware 把
/api/admin/session(登录接口本身)也拦截,要求带 admin-session-token cookie,鸡生蛋。修:豁免/api/admin/session - PM2 cwd 错位 — deployer
pm2 reload时 ecosystem.config.js 没设 cwd,导致 image-studio 进程 cwd 是/opt/mvp-deployer/,读到 deployer 自己的 .env(postgres URL 串了),完全连不上自家 sqlite。修:deployer builder 强制写cwd: '/opt/mvp-apps/<project>' response_format参数 400 — gpt-image-2 不再接受这个字段,删src/lib/azure.ts第 10 / 43 行(commit14f9981)- Azure key 在 git 历史泄露过 — 早期 admin/models/route.ts 硬编码过 key,建议轮换 + 把 key 收回
~/.credentials/.env - 图片 404 抢救 — 部署 zip 解压清掉了
public/prompts/<id>/N.jpg用户原有图,扁平化搬到/var/lib/image-studio/images(项目外目录),代码改读IMAGE_STORAGE_DIRenv 后部署不再覆盖 - Endpoint 输入框焦点跳出 — admin 表单组件定义在父 render 内,每次 setState 整个重建,输入完一个字符就丢焦。抽到组件外修复
- 首页改瀑布流 + IntersectionObserver 触底翻页(首屏 24,266 条全可滚动加载)+ 卡片标题/描述浮动到图上 + Anthropic 橙色
- Azure 偶发 500 (server processing error) — 服务器侧 gpt-image-2 偶发返 “The server had an error while processing your request”,本地无法复现。临时方案:客户端超时拉到 ≥120s + 最多 5 次指数退避重试,并在生成日志里记录最终命中的 endpoint,方便配合多 endpoint 轮询规避故障实例
- gpt-image-2 不传
quality必 500 — 04-23 当晚对照实验确认:quality=low/medium/high都正常返回 200,省略字段必 500(同 endpoint / key / prompt 复测三次稳定复现)。src/lib/azure.ts默认"medium"是对的,UI 走的路径都会传,所以前端选项不影响;这条独立于上面 Azure 偶发 500,是参数契约问题 AZURE_OPENAI_API_KEYvsAZURE_OPENAI_KEY命名不一致 — 服务器.env用AZURE_OPENAI_API_KEY,代码读AZURE_OPENAI_KEY,导致替换硬编码 key 后第一次部署直接连不上 Azure。改src/lib/azure.ts兼容两种名(commit2ba8bc5)
04-23 晚间安全收尾 + git 历史擦除
当天发现的硬编码 Azure key 等问题做了一次集中收尾(commit 2ba8bc5),随后因 repo 还在本地(未 push GitHub),直接 squash 成单个 e0e284c initial commit (history cleaned for security),把 15 个含泄漏 key 的 commit 全清掉,比走 key 轮换更彻底。落地项:
- 5 个安全 header:HSTS / X-Frame-Options / X-Content-Type-Options / Referrer-Policy / Permissions-Policy
- Admin 密码改
crypto.timingSafeEqual比较(防时序攻击) - Prompt 长度上限 10000 字(防注入面)
- 充值码并发兑换走事务内二次校验
- 自定义 404 页(
src/app/not-found.tsx,Anthropic palette + “返回首页” 橙色按钮) - AppShell logo
<Link href="/">—— 之前登录后无法点 logo 回首页 - Caddy 限流方案落到
deploy/rate-limit.md(不直接动服务器配置,留给 review 后再上);正式上线在 04-25 那一轮
- gpt-image-2 必须传
quality— 直连 curl A/B 实测:quality=low✅ 200/118s、quality=medium✅ 200/86s、不传 quality ❌ 500/92s。早期 UI 删output_format/background/moderation/compression时把quality透传链路也撞坏,必须强制low/medium/high之一
2026-04-25 大爆发:拉新 + 充值 + 反馈 + test 环境
ottor 一整天对 image-studio 做了第二轮重构,从”内部画图 SaaS”演化为”对外引流付费产品”。9+ 批次提交,覆盖六大模块;同时拆出独立 test.design.mvp.restry.cn 测试环境。
1. 账号体系重构(脱离 access key)
- 账密注册/登录:User 表加
username/passwordHash(unique + bcrypt),/api/auth/random-name生成”动物名+2位数字”形式(最终规范:总长 ≤5 字,词根 1-3 字 + 2 位数字,例:猫77/狐狸42/熊猫08,密码用crypto.randomInt16 位 4×4 dash 分隔,去掉易混淆字符) - 注册 UX:眼睛图标查看密码 + 二次确认 modal(amber-500 警示横幅 + ring-4 + 不可关)+
navigator.credentials自动写浏览器密码 - 微信公众号 OAuth 仍保留:未认证个人订阅号
snsapi_userinfo没权限拿 nickname → 早期出现Mock User是因为WECHAT_APPSECRET解密失败回退 mock;同时 client component 把NEXT_PUBLIC_APP_URL=http://localhost:3000固化进 bundle 导致 redirect_uri 串号,部署时必须--env NEXT_PUBLIC_APP_URL=https://design.mvp.restry.cn - 微信浏览器:未配 APPID 时禁止
mock=1自动登录,强制走密码表单 - 小程序登录方案归档(未实施):凭据存
~/.credentials/image-studio-miniprogram.env(MINIPROGRAM__前缀 chmod 600),完整方案docs/miniprogram-qrlogin-plan.md,因审核 7-10 天放弃
2. Onboarding + 充值 B 流程
- Onboarding:User 加
hasSeenOnboarding,4 步介绍(欢迎 / 怎么生成 / 进阶玩法 / 进充值页)+ 圆角小一些的设计 token - 充值 B 流程(管理员审核):放弃 A 方案(自助提单事后核对)以减少防刷复杂度。AppSetting 配
payUrl(微信支付码主区)+contactUrl(客服微信码 → 右下角 Headphones 浮动按钮弹窗)+flow_note(默认 “管理员审核后到账,通常 10 分钟到半小时”)+topup_enabled总开关 - 6 档定价(最终方案,¥9-499):体验 ¥9/25 积分(≈5 张 image2)、入门 ¥29/100、标准 ¥49/180、进阶 ¥99/400、高级 ¥199/850、旗舰 ¥499/2300。SQL
UPDATE直接覆盖topup_price_tiers(migration 用ON CONFLICT DO NOTHING不会覆盖已有空行,这是教训) - 尺寸友好名:
1024x1024→ 正方形(社交头像/封面)、1536x1024→ 横屏(壁纸/Banner)、1024x1536→ 竖屏(手机屏/海报);质量挡保留 low/medium/high/auto + 中文副标
3. 站内通知 + 反馈 + 公告 + 拉新
| Batch | 模块 | 关键设计 |
|---|---|---|
| Notification 表 | 通知 | 充值审核结果自动写通知;用户端顶栏铃铛 + 红点 + 30s 轮询 + ding 音效 + toast |
| Feedback | 反馈入口 | 右下角浮按(topup/login/onboarding 隐藏)+ 类型/内容/附图 + 单用户每日 ≤3;admin 审核通过事务 += feedback_reward_credits(默认 1,可配置) |
| Announcement | 公告群发 | targets: all / active30d / paid / userlist;level: info / warning / event;warning/event 强弹 modal |
| Invite | 拉新 | 首充审核通过才结算(不在注册/出图触发);邀请人 15%、被邀请人 10、封顶 1000 积分;自引用 User.invitedBy + Invite 审计;档位计算:旗舰 ¥499 → 邀请人 ≈345 积分 |
4. Admin 后台 6 组侧栏
重构成两级导航:概览 / 用户与权限 / 财务 / 内容 / 增长(反馈/公告/邀请)/ 系统(审计/设置)。
新增 /mgmt-7bx3k/generations “加入示例库”按钮:admin 点完弹 modal 填 title/desc/category/featured/safe → 复制图到 public/prompts/{no}/{idx}.png + 写 Prompt + PromptImage + AuditLog(416 条 prompt 库里 179 条默认 safe=false 因为分类空/人像/时尚类,不是审查”不适合”)。
子代理清理低质 prompt 25 条(粤语吐槽 / @grok 互动 / 推特发布通告 / 重复 showcase / 日文炫耀贴):Prompt 表 446 → 421。no=28 由 daddy 强制点名删除。
5. 多轮安全审计 + Caddy 限流
全天三轮 Claude Code review(报告分别在 /tmp/review-2026-04-25.md、/tmp/review-2026-04-25-round2.md),落地 P0:
crypto.randomInt替换Math.random防 PRNG 预测- password-login 加 dummy bcrypt + 统一错误信息 + middleware
PUBLIC_PATHS精确 Set - 注册 NFKC + Unicode 白名单 + UTF-8 字节长度校验 + P2002 race-safe;登录共享
normalizeUsernamehelper - topup approve 双花用
updateMany where status=pending幂等锁 → 重复点 409 - generate 负余额:
updateMany + balance gte守卫 + 整个流程进事务 - WeChat open redirect:
new URL(state, req.url).origin === origin校验,免疫\//等所有解析变种 - Feedback / promote-to-prompt TOCTOU 复制 topup 模式
caddy-reverse-proxy 加新限流:/api/auth/login、/api/auth/password-login、/api/auth/register 共享 5/min;/api/auth/check-ref 30/min;feedback 10/min;topup 10/min;generate 20/min;其他兜底 60/min。
6. Test 环境 test.design.mvp.restry.cn
- 端口 3790;DB 隔离(同 PG 上
image_studio_test库);存储/var/lib/image-studio-test/images;Azure key 复用 - deployer 流程铁律:默认只发 test,prod 走显式触发不自动同步
- 配置基线同步:
AppSetting(13) /AIModel(2) /AIEndpoint(3) 三张表pg_dump --data-only→ testTRUNCATE CASCADE→psql -f灌入;Prompt 库不带(外键复杂) /api/auth/check-ref当时不在PUBLIC_EXACT,访客调用 401 → 前端setRefCode(null)→ 注册时 ref 丢失,邀请关系全 NULL(commit9678bcc修复)- 邀请链接、
metadataBase硬编码 prod 域名 → 改读NEXT_PUBLIC_APP_URL,兜底req.urlorigin
7. 关键踩坑
- deployer 缺
--host:design.mvp.restry.cn被替换成默认image-studio.mvp.restry.cn,TLS 证书失效 → 站点打不开。所有部署必须显式--host ?schema=public串到 postgres 库 →unrecognized configuration parameter "schema";Prisma 写 URL 时手动 strippm2 restart --update-env不可靠,env 缓存死掉 → 必须pm2 delete && pm2 start- deployer 读
.env权限:手动chown root:root chmod 600后claw用户读不到,fallback 用空 env → 连到 prod 库;改chown claw:claw --preserve-env对新项目无效:第一次部署需先 push.env到服务器/opt/mvp-apps/<project>/.env
8. Git + Review 化
- 9 commit 分批落(安全 / 账密 / 充值 / 通知 / promote / UI 杂项 / 侧栏 / NFKC + race / test 修复)
- 推到新建私有 GitHub repo(之前一直只本地 git)
2026-04-26 续:Admin prompts 页 + 自建 analytics + WeChat 推送修复
Admin prompts 管理页
/mgmt-7bx3k/prompts:抽 src/components/GalleryCard.tsx 给首页 LandingClient + admin 共用(之前误整页复用 LandingClient 被否决,“共用是只共用卡片组件”)。Admin 嵌侧栏 + 搜索 + category + safe 三态筛选 + Grid hover 三按钮(编辑/safe/删)+ 24/页分页 + 编辑抽屉(title/desc/promptText/category/author/safe)。API GET/PATCH/DELETE /api/admin/prompts[/no],updateMany 防并发,DELETE 级联。
自建访问统计(30 分钟自撸 vs 51.la)
51.la 接入失败 — sdk.51.la/js-sdk-pro.min.js 这个 CDN 域名从服务器 + 国内大部分网络 curl 直接 ERR_CONNECTION_CLOSED/timeout,不是浏览器拦截而是该域名本身在国内连通性挂了。同日切自撸:
- Prisma 加
PageView表(path/referer/userAgent/ipHash(日盐 SHA256 截 16 字符)/ip(原始)/country/city/sessionId(shu_sidcookie,30 分钟滚动)/userId?/createdAt+ 4 索引) POST /api/track:匿名/登录都可,IP 取x-forwarded-for第一段(Caddy 反代已塞),fire-and-forget<AnalyticsTracker />客户端组件:usePathname监听路由变化每次打点/mgmt-7bx3k/analytics:今日/7天/30天 tab + PV/UV 4 大数字卡 + recharts 双线日趋势 + Top 20 页面 + Top 10 来源 + 登录 vs 游客分布 + 最近访问表(时间/用户/IP/国家·城市/路径/UA)- IP 地理:机会式回填,首次访问该页时后台异步跑
ip-api.combatch 查最近 7 天去重 IP 写回 country/city - 侧栏新增「数据分析」分组
教训:Next.js 15 不允许 server component 用 dynamic(..., { ssr: false }),recharts 直接 'use client' 即可;Postgres migration 不能写 SQLite DATETIME 语法(首次 prod 跑 fail,需先清 _prisma_migrations 那条 failed 标记再重跑)。
WeChat 推送 9 天全挂复盘
发现:本周 7/7 cron 推送生成成功但全部投递失败,向前追溯 4/18 起连续 9 天微信推送 0 送达,last_status: ok 但 last_delivery_error 长期未清。
两个 bug 叠加:
- 上游 iLink ret=-2(4/22 起每天必现):Tencent iLink
sendmessage返回ret=-2 errcode=None,可能是context_token过期(妈妈那条 DM 30 天+无新消息刷新)或 iLink 服务端校验异常 - 跨 event loop 复用 aiohttp session(真代码 bug):
cron/scheduler.py:418用asyncio.run(coro)在 worker thread 跑 fallback,进gateway/platforms/weixin.py的send_weixin_direct后短路用live_adapter._send_session.send(...)。但_send_session是 gateway 启动时在主 event loop 里建的,此刻在asyncio.run()起的新 loop 里跑 — aiohttp 的 timeout context 跨 loop 失效,抛Timeout context manager should be used inside a task。fallback 路径永远不会成功,把 Bug 1 的失败用一个误导性错误覆盖掉
修法(A):send_weixin_direct 加 live_adapter._send_session._loop is current_loop 检查 — loop 不一致就跳过 live adapter 短路、走 standalone 路径在当前 loop 上建新 aiohttp session。重启 gateway 生效。Bug 1 (ret=-2) 是独立问题,待手动测一条确认是否还在
修法(B,2026-04-27 ottor):进一步给 gateway/platforms/weixin.py 的 _send_text_chunk 加 PUSH_WINDOW_EXPIRED_RET = -2 常量,ret=-2 现在和 -14 一样会触发”清掉 context_token 重试一次”;如果重试后仍 -2,日志写 “recipient’s 48h proactive-push window has expired”,不再是模糊的 “unknown error”。验证当天 00:42 实际错误就是 ret=-2(两次都是),加重试 + 让妈妈在微信回一句话重开 48h 窗口即可恢复。
2026-04-27 增长 + 留存模块(test 上线 + Github 同步)
ottor-laptop 一天里把”新人礼 + 每日签到 + admin 三页面”做完,合并 commit 32c55e0 推到 github.com/bochub/image-studio(私有)(仓库本来就在远端,但当天的所有 growth/retention 改动是首次提交)。Daddy 邀请 Restry 为 collaborator(write)—— 同邀请也发给了 bochub/wiki / bochub/project-management / bochub/wukong-host。
1. 新人礼 +10 积分(三维度风控)
- WeChat OAuth 注册即送,unionid + IP + browser fingerprint 三维度,任意命中两维度即拒,全审计
- fingerprint:同步 djb2 hash(UA + screen + lang + timezone + cores),写
shu_fpcookie,避开 WeChat 重定向前的异步竞态(async 指纹库会丢) - unionid 在
prisma.$transaction里二次校验防 TOCTOU - 新表
WelcomeBonusLog(IP / UA / unionid / fingerprint / decision / reason) - admin 后台
/mgmt-7bx3k/welcome-bonus-log查审计 - 风控参数全走 admin 配置(额度、冷却期、启用开关),不硬编码
2. 每日签到(连续递增 + 断签清零)
- 新表
CheckIn,按Asia/Shanghai时区切日,@@unique([userId, date])防并发双签 - 曲线可在 admin 配置(默认
[1,2,3,4,5,6,7]),断签从头算 - AppShell header 加签到 popover,未签到带 amber 小红点
3. Admin 后台增长 / 留存 / 审计三区块
- 增长配置:新人礼额度 / 风控阈值 / 启用开关
- 留存配置:签到曲线 / 启用开关
- 审计日志:欢迎奖发放历史、签到打卡历史
4. test 环境部署链路
- 走 deployer
--build "pnpm install --frozen-lockfile && pnpm prisma generate && pnpm prisma migrate deploy && pnpm build"—— 关键是prisma migrate deploy自动跑新 migration 建WelcomeBonusLog+CheckIn表 --preserve-env抓服务器现有 10 个 env vars 全量回传,不冲掉密钥- 实际 PG 是 Postgres 不是 SQLite(之前 04-23 已迁完);deployer 部署 82s 完成
- 验证:test 站
/、/mgmt-7bx3k、/mgmt-7bx3k/welcome-bonus-log全 200
5. Admin 用户列表展示 gap
WeChat OAuth 拿到的 nickname + headimgurl + unionid 已写 WechatAccount 表、displayName 用昵称;但 admin /mgmt-7bx3k 用户列表 API 没返回 avatarUrl、UI 也没渲染头像 —— 待补三处小改:API 加 wechatAccount.avatarUrl 字段、列表第一列加头像、详情页显示。
2026-04-29 — 「Design Studio」品牌升级 + WeChat Pay 接入
1. 多 vertical 化(launcher + pack + general)
顶部品牌从 “PackSmith” 抽象为 “Design Studio”(Layout/login/footer/launcher 全改),只有 /pack/* 子路由保留 PackSmith 命名。
lib/verticals.ts注册表:launcher/+/pack(PackSmith — source=xiaofang,120 张包装样本,注入包装 prompt 模板) +/general(source ≠ xiaofang,238 张通用图)/generalUX 大改:复用主分支landing-client.tsx的openModal/closeModallightbox;卡片改 3:4 竖版,5 列响应式/gallery+/generate新增 ✏️ hover 编辑按钮 →/generate?editFromGen=&imgId=自动预填 sourceImage(点图本身仍是 lightbox)
2. WeChat Pay JSAPI 接入(走 wx-gateway)
Payment表 + migration20260430000000_add_paymentlib/wx-gateway.ts:HMAC +payCreate / payJsapiPrepay / payStatus / verifyWebhook4 个 wrapper/api/checkout/start:渠道路由(personal_qr vs wxpay)/api/wx/payment-webhook:HMAC 验签 + 事务内addCredits幂等(idempotent onPayment.status)/topupUI:保留个人码在上,下方加#07C160微信支付按钮,3 道守卫(UA 必须微信内 / 必须有 openid / 渠道开关)
3. 架构转向 — 网关托管 checkout 页(重要)
JSAPI 痛点:每个业务 app 都要单独在公众号后台登记「JS 接口安全域名」+「支付授权目录」。改为网关侧 host 一个 /pay/checkout/[paymentId] 公共页,业务 app 只 POST /pay/create + returnUrl 拿回 checkoutUrl 跳转过去;微信支付授权目录只需登记 https://wx.mvp.restry.cn/pay/ 一次,所有租户复用。
- 网关 commit
1b70f25+ image-studio commitc5ef489 wx-gateway-integrateskillpayment.mdv2 重写,把 redirect 流程立为唯一推荐路径- 安全约束:
?paid=1仅 UI 提示,不可信;webhook 是唯一信任源;returnUrlhost 必须匹配app.appBaseUrl
4. notify_url 路径化(架构 bug 修)
微信 reject 含 ? 的 notify_url → 网关把 ?app=X 改 /pay/wxpay/notify/<app> 路径式(commit 48986bb)。
5. 商户配置(design-studio-prod)
mchId 1591949631,wxpayAppId wx225bf76b06064faa(造悟者公众号),apiV3Key + privateKey 已就位。当前商户号在微信侧 NO_AUTH 状态(账户审核问题,与代码无关),等审核通过开收。
6. mvp-deployer 踩坑(沉淀进 deployer)
--preserve-env在 SSH key 缺失时静默 fallback 空 env → 必须 base64 抓服务器.env然后多--env KEY=VAL注入deploy.py不支持--post-deploy;postDeploy 阶段会自动跑prisma migrate deploy,不需要手动指定
7. 流量
Day-1 在 design.mvp.restry.cn(CST 5/1):5 PV / 4 sessions / 1 logged-in / 0 generations。
相关页面
- deployer — 部署平台 + secret v2 + inventory 接口(一同诞生)
- azure-imagen — 上游模型(gpt-image-2 04-23 上线)
- caddy-reverse-proxy — 限流方案落在 Caddy
caddy-ratelimit模块 - shutiao-world — 同部署器上的另一个项目
- echo — 同 deployer 上 04-25 上线的公众号写作工作台
2026-05-13 — 线上 429 限流提示根因 + Azure 重试缺口
线上 https://design.mvp.restry.cn/generate 持续提示 “We are currently servicing too many requests at the moment. Please wait and try again later.”(用户原话即 Azure 报文透传)。
根因:src/lib/azure.ts:78-83 重试逻辑只对 5xx 重试,429 直接 throw:
if (response.status >= 500 && response.status < 600 && attempt < MAX_ATTEMPTS) {
// retry...
}
// 429 跳过这里 → 直接 throw err.error.message两层问题:
- Azure 容量真不够(根因):
gpt-image-2/gpt-image-15部署在resley-east-us-2-resource,S0 tier 默认 IPM 个位数,高峰/连发触发 429 - 代码层无兜底(放大):429 不重试 + 错误文案直接透传 Azure 原话
待补:429 走指数退避 + 客户端友好文案(“目前画图较忙,稍后重试”),不要把 Azure 原话甩给用户。
项目记忆刷新(5-13 整理):本地 ~/projects/image-studio、GitHub bochub/image-studio(Restry 账号),当前分支 dev(5/6 起 daddy 说”以后都用 dev”);pack 同步、main 已落后;Stack 是 Next.js 15 + React 19 + Prisma 6 + PostgreSQL,AI 走 Azure OpenAI(不是 Gemini)。两套部署:prod design.mvp.restry.cn:3789,PackSmith 分支 pack.mvp.restry.cn:3792。
2026-05-14 — .memory/PROJECT.md 落项目根目录 + 仓库归属确认
承接 5-13 复盘做了两件:
-
项目级 memory 落盘:
~/projects/image-studio/.memory/PROJECT.md(5.2KB,8 节)—— 基础 / 部署(hostclaw@163.228.243.161,PM2image-studio:3789,/opt/mvp-apps/image-studio,PG127.0.0.1:5432db/userimage_studio)/ 微信 / 踩坑 / 当前 429 问题(同上 5-13 根因)/ 凭据索引(~/.credentials/.env走IMAGE_STUDIO__前缀,详见 deployer Path B)/ 部署 APIhttps://deploy.mvp.restry.cn/ 远程地址。同 nexora-loop 5-14 同日做的PROJECT_MEMORY.md一个模式——让任何接手 agentcat .memory/PROJECT.md即可对齐,不依赖 hermes vault。 -
仓库归属:
bochub/image-studio私有仓库(不是公开),当前登录的 GitHub 账号Restry/zhendl都不是 owner——4-27 历史邀请的 collaborator 而已。要转给 Restry 账号必须 daddy 用 bochub 账号登录 GitHub Settings → Transfer ownership 触发,Restry 这边收邮件确认接收;转完 fries 这边git remote set-url origin https://github.com/Restry/image-studio.git切走。日志 / pm2 进程目录 / .env 凭据这套链路完全不动,是 GitHub 仓库归属层的事。已加进 ⏸ 卡点等 daddy。