Skip to content

Releases: modelbus/one-api-pro

v0.0.20

Choose a tag to compare

@github-actions github-actions released this 13 Sep 08:58

v0.0.20 — 管理后台 UI 全面优化 + 定价 Id 清零兜底

渠道 / 令牌管理界面对齐 web/default 老前端体验:模型可搜索多选、模型重定向、分组下拉、永不过期;模型下拉数据源从编译期硬编码 catalogue 切换到运行时可配置的 model_price 表。同时修复「新建令牌触发 Page is not found 报错」与「定价表单残留 id 写入」两条链路隐患。

Channel & token admin UIs catch up with the reference web/default experience: searchable model multi-select, model redirect, group dropdown, "never expire" toggle. The model dropdown source is switched from the compile-time hardcoded catalogue to the runtime-configurable model_price table. Also fixes the "Page is not found" ReferenceError on token create and the residual-id-leak path in pricing forms.

中文

✨ 新增功能

  • 渠道编辑弹窗:模型字段改为支持搜索的多选选择器web/default-pro/src/views/channel/Channel.vue):
    • 把原本「逗号分隔字符串」输入框替换为 arco a-selectmultiple + allow-search + allow-clear),挂载时拉取 /api/channel/models 作为候选。
    • form.modelsstring 改为 []string,提交时 join(',') 复用后端逗号存储格式;编辑时 parseModelsField 把后端返回字符串拆回数组。
    • 当前表单里已存在的模型名会自动合入候选,保留历史「野模型」可见性。
  • 渠道编辑弹窗:新增「模型重定向」输入框Channel.vue):
    • 新增 model_mapping 字段(对应后端 channels.model_mapping),使用 a-textarea 接受 JSON 对象,提交前用 JSON.parse + typeof === 'object' 校验必须为普通对象。
    • 后端字段早已存在(model/channel.go:38),前端之前未暴露 — 现在与 web/default 老前端 EditChannel 表单对齐。
  • 渠道编辑弹窗:分组改为下拉选择器并移到名称下方Channel.vue):
    • 把逗号分隔 a-input 改为 arco a-selectmultiple + allow-search + allow-clear),候选来自 /api/group/(后端 controller/group.go)。
    • form-item 从「模型」下方移到「名称」下方,对齐 web/default 老前端顺序。
    • form.groups 改为数组,提交时 join(',') 复用后端存储格式。
  • 渠道编辑弹窗:模型下拉数据源切换到 model_priceChannel.vue:fetchAvailableModels):
    • 下拉候选改读 GET /api/model_price/options(admin 权限即可访问),与计费数据源对齐。
    • 运营无需改代码就能给下拉里加新模型,只要在「定价管理」里配好对应 model_name 即可。
    • 仍保留 form.models 合入候选的兜底,编辑历史渠道时已选模型不会丢。
  • 新增只读接口 GET /api/model_price/optionscontroller/model_price.go):
    • 仅返回 enabled=truemodel_name 字符串切片,按名称升序;空 model_name 自动跳过避免脏数据。
    • AdminAuth 即可访问,与 RootAuth 保护的写接口隔离;旧 GET /api/model_price/// Deprecated: 注释保留给 root 查看完整字段。
  • 令牌编辑弹窗:过期时间新增「永不过期」checkboxToken.vue):
    • 过期时间输入框右侧增加「永不过期」checkbox,勾选后 date-picker 禁用。
    • 新增 form.never_expire 字段;编辑时若现有 expired_time=0 自动勾选。
    • 提交时若勾选或时间留空,expired_time 上报 0(对齐后端「0 = 永不过期」约定);表单底部 #extra 文案随勾选状态切换。
  • 令牌编辑弹窗:额度限制右侧增加「不限制额度」checkboxToken.vue):
    • 把原本独立成行的 checkbox 折叠到额度限制输入框内右侧,引入通用 .input-with-checkbox 横排布局供「永不过期」复用。

🐛 问题修复

  • 修复新建令牌后弹「Page is not found」报错Token.vue:handleSubmit):
    • 原代码误写为 page.value = 0,实际变量是 activePage,触发 ReferenceError 后错误冒泡为「Page is not found」提示。
    • 改为 activePage.value = 1,新建成功后回到第一页。
  • 修复定价弹窗新建时残留 id 写入数据库(4 层防御):
    • 前端 PricingSetting.vue 新建时清空 mpForm.id / gpForm.id,避免把编辑态的旧 id 拼到 POST 请求体。
    • controller.AddModelPrice / AddGroupPricec.ShouldBindJSON 之后强制 price.Id = 0,即便前端漏改也不会用客户端 id 写入。
    • model.ModelPrice.Insert / GroupPrice.Insert 兜底清零 p.Id = 0,防御外部调用方 / 未来 caller 把编辑态旧 id 误传到 Insert() 时撞主键冲突。
    • 这条链路任何一层失守都不会污染数据,但三层全设防是因为该 bug 一旦发生就是主键冲突级别的数据事故。

🔧 重构

  • 额度限制 checkbox 移入输入框右侧Token.vue):合并独立 <a-form-item> 到输入框内,引入通用 .input-with-checkbox flex row 样式(12px gap,checkbox 强制不换行)。
  • 模型重定向输入框与提示改为上下排列Channel.vue):用 .form-stack 容器把 textarea 与示例提示竖向排列,新增 .form-hint-block 修饰符覆盖 .form-hintmargin-left、改 margin-top,其他 inline hint(key / fallback / fallback_priority)的样式不受影响。

🎨 样式优化

  • 过期时间日期选择器移除右侧日历图标Token.vue):给 <a-date-picker> 传空 <template #suffix-icon></template> 槽覆盖 arco 默认的 IconCalendar,让 date-picker 视觉宽度与下方 <a-input-number> 对齐。
  • 模型重定向提示改用 pre-wrap + break-allChannel.vue):JSON 示例文本较长时自动换行,不再被单行截断。

🧪 测试

  • ModelPrice / GroupPrice Insert 强制清零 Id 兜底行为model/model_price_test.go):新增单测覆盖两条 Insert 路径在 Id != 0 时被清零的兜底。
  • AddModelPrice / AddGroupPrice HTTP 层 Id 清零端到端路径controller/model_price_test.go):seed 一个 id=9999 的预存行,再用同样的 id=9999 提交新增,断言:旧行未被覆盖,新行拿到自增 id 而不是 9999。
  • user_test TestMain 迁移 ModelPrice / GroupPrice 表controller/user_test.go):让 ModelPriceGroupPrice 在 in-memory SQLite 里参与 AutoMigrate,供定价单测复用。
  • ListModelPriceOptions enabled 过滤与空名兜底controller/model_price_test.go):seed 2 条 enabled + 1 条 disabled(用 raw SQL 绕开 GORM default:true;not null 对显式 false 的覆盖),断言只有 enabled 行出现在响应里;另一条 seed 空 model_name 断言响应里不出现空字符串。

⚠️ 升级注意事项

  • 零数据库迁移:本次不涉及任何表结构变更。
  • 需要重新构建前端web/default-pro/ 的修改全部需要 npm run build 后重新 //go:embed 进二进制,升级时记得打包新前端。
  • 后端二进制必须重启:仅新增 ListModelPriceOptions handler 与对应路由,运行中的旧进程不会暴露 /api/model_price/options
  • 行为变更
    • 渠道编辑弹窗「模型」下拉候选从「编译期硬编码 catalogue」切换为「运行时 model_price 表中 enabled=true 的 model_name」。历史渠道如果选了 catalogue 有但 model_price 没有的「野模型」(如 gpt-4-0314text-davinci-003 等历史快照),下拉里将看不到这些选项 — 但前端仍会把 form.models 合入候选,编辑现有渠道时已选模型仍可见,保存链路无破坏。如需把历史模型纳入下拉,请在「定价管理」里手动添加对应 model_name 行。
    • 渠道编辑弹窗「分组」字段改为下拉选择器,候选来自后端 model.GroupPrice 表(/api/group/ 接口),默认 3 个分组(default / vip / svip)。如需更多分组,请在「定价管理」里给 group_prices 表加行。
    • 令牌编辑弹窗「永不过期」与「不限制额度」checkbox 默认不勾选;编辑现有令牌时,后端值为 0 / -1 会自动反推勾选对应 checkbox(never_expire = !expunlimited_quota = !!record.unlimited_quota)。
  • 构建与测试
    • go build ./... 通过。
    • go test ./controller/ ./model/ ./middleware/ 全绿(新增 4 个定价 / 模型选项相关断言)。
    • cd web/default-pro && npm run build 通过。

English

✨ New Features

  • Channel modal: searchable multi-select for the model field (web/default-pro/src/views/channel/Channel.vue):
    • Replaced the comma-separated a-input with an arco a-select (multiple + allow-search + allow-clear); the option source is /api/channel/models on mount.
    • form.models switched from string to []string; submissions join(',') to keep the backend's stored format; parseModelsField splits the backend string back into an array on edit.
    • Models already present on the form are unioned into the option set so legacy "wild" models stay visible when reopening the dialog.
  • Channel modal: new "Model Redirect" textarea (Channel.vue):
    • Adds the model_mapping field (maps to backend channels.model_mapping) backed by a-textarea accepting a JSON object; submission is gated by JSON.parse + typeof === 'object' validation.
    • The backend column has always existed (model/channel.go:38); the front-end simply never exposed it. Now aligned with the reference web/default EditChannel form.
  • Channel modal: groups converted to a multi-select dropdown and moved under the name field (Channel.vue):
    • Replaced the comma-separated a-input with arco a-select (multiple + allow-search + allow-clear) sourced from /api/group/ (controller/group.go).
    • The form-item moved from under "Models" to under "Name", matching the reference web/default order.
    • form.groups switched to an array; submissions join(',') to preserve the backend's stored format.
  • Channel modal: model dropdown source switched to the model_price table (Channel.vue:fetchAvailableModels):
    • The dropdown now reads GET /api/model_price/options (admin-accessible), aligning with the billing source of truth.
    • Operators can add a model to the dropdown without code changes — just add a row in Pricing with the desired model_name.
    • The form.models union fallback is preserved so re-opening the edit dialog on a historical channel still shows its saved selection.
  • New read-only endpoint GET /api/model_price/options (controller/model_price.go):
    • Returns only enabled=true model_name strings, sorted ascending; rows with empty model_name are silently skipped to avoid dirty data in the dropdown.
    • AdminAuth-accessible; isolated from the RootAuth-protected write endpoints. The old GET /api/model_price/ now carries a // Deprecated: comment and stays for root admins to view the full pricing payload.
  • Token modal: "Never Expire" checkbox next to the expiry picker (Token.vue):
    • Adds a "永不过期" checkbox to the right of the expiry picker; checking it disables the picker.
    • Adds form.never_expire to the form state; editing auto-checks it when the existing expired_time=0.
    • On submit, expired_time is sent as 0 when "Never Expire" is checked or the picker is empty (matching the backend's 0 = never expire convention); the helper text under the field toggles with the checked state.
  • Token modal: "Unlimited Quota" checkbox folded inline with the quota input (Token.vue):
    • The previously standalone checkbox form-item is collapsed into the right side of the remain_quota input. The shared .input-with-checkbox flex row layout is reused for the new "Never Expire" checkbox.

🐛 Bug Fixes

  • "Page is not found" ReferenceError after creating a token (Token.vue:handleSubmit):
    • The code mistakenly wrote page.value = 0 (the actual variable is activePage); the ReferenceError surfaced as a stray "Page is not found" toast.
    • Replaced with activePage.value = 1 so a successful create lands on page 1.
  • Residual-id leakage when creating pricing entries via the pricing modal (4-layer defence):
    • Frontend PricingSetting.vue clears mpForm.id / gpForm.id on a fresh "Add" click so the POST body never carries the previously-edited id.
    • controller.AddModelPrice / AddGroupPrice force price.Id = 0 after c.ShouldBindJSON, so even a buggy client cannot persist a client-supplied id.
    • model.ModelPrice.Insert / GroupPrice.Insert defensively zero p.Id = 0 as a final guard against future callers passing an edit-state id into Insert().
    • Any single layer being skipped is harmless on its own; we keep all three because a failure here is a primary-key collision level data incident.

🔧 Refactor

  • "Unlimited Quota" checkbox moved inline with the quota input (Token.vue): the standalone <a-form-item> is folded into the input row; the shared .input-with-checkbox flex row (12px gap, nowrap checkbox) is also reused by the new "Never Expire" checkbox.
  • **Model Redirect input and hint stacked vert...
Read more

v0.0.17

Choose a tag to compare

@github-actions github-actions released this 11 Sep 08:16

v0.0.17 — 管理员运营仪表盘与图表数据统一重构

为管理员引入全新的「数据」运营仪表盘:全站 KPI(用户/资源/营收/配额/收入)+ 7 日请求量/额度/Token 三张趋势折线 + 模型用量分布堆叠柱图 + 使用明细表 + 活跃用户排行榜;图表数据统一由 /api/admin/dashboard/charts 一份接口驱动,结构与 /api/user/dashboard 完全一致([]LogStatistic),方便前后端共享聚合逻辑。同时修复 /api/* 未匹配路由误返回 OpenAI 风格错误的问题。

中文

✨ 新增功能

后端(Go)

  • 新增管理员仪表盘聚合层model/admin_dashboard.go,issue #15):
    • GetAdminDashboardOverview(rawRange):一次性聚合 KPI — 用户(总数/今日新增/7 日/30 日/活跃/禁用/已删/今日占比)、资源(令牌/渠道/套餐/兑换码/订阅 各自总数 + 启用/活跃)、配额(今日/7 日/30 日/总)、收入(总营收/充值/订阅/退款),6 段聚合单次往返,每段单条 SQL 避免 N+1。
    • GetAdminTopUsers(rawRange, limit):活跃用户排行榜(近 range 内有 consume 日志 + request_count > 0,按请求数 desc、quota desc 排序),一次性 WHERE IN 批量补齐当前套餐名(batchTopActivePlanNames)。
    • GetAdminModelDistribution(rawRange, topN) + AdminModelDistribution:全站按 quota 降序的 Top N 模型 + 7 日 day 序列,供堆叠柱图渲染(此接口随后被 /charts 取代,详见 🔧 重构)。
    • GetAdminUsageDetails(rawRange, topN) + AdminUsageDetails:Top N 模型 × 每日的「透视前」明细行,供「使用明细」表渲染(此接口随后被 /charts 取代)。
    • ParseAdminDashboardRange / ParseAdminChartsRange:统一的 range 预设解析(today/7d/30d/all),后者输出 day 对齐的窗口(all 收敛为近 30 天避免全量响应过大)。
    • SearchAdminLogsByDayAndModel(start, end):全站 day × model 聚合,结构与 model.SearchLogsByDayAndModelapi/user/dashboard 用)完全一致,但不过滤 user_id;前端可直接复用同一套图表构建逻辑。
  • 新增 /api/admin/dashboard/charts 接口controller/admin/dashboard.go::GetChartsrouter/api.go,AdminAuth):
    • 返回 []*model.LogStatistic,字段 Day / ModelName / RequestCount / Quota / PromptTokens / CompletionTokens,与 /api/user/dashboard 完全一致。
    • 一份数据驱动前端全部图表:请求量/额度/Token 三张折线 + 模型分布堆叠柱 + 使用明细表。
  • 运营仪表盘配套测试model/admin_dashboard_test.go):
    • TestParseAdminDashboardRange:覆盖 4 种 range 边界。
    • TestParseAdminChartsRange:验证 day 对齐窗口与天数。
    • TestGetAdminDashboardOverview_Smoke:覆盖 6 段聚合与 active_7d。
    • TestGetAdminTopUsers:覆盖 request_count > 0 过滤 + 排序 + 套餐名嵌入。
    • TestSearchAdminLogsByDayAndModel:覆盖 []LogStatistic 结构 + 多用户合并 + 字段齐全。
    • 早期 TestGetAdminModelDistribution / TestGetAdminUsageDetails 随 API 重构移除。

前端(web/default-pro/

  • 新增管理员仪表盘页面 views/admin/AdminDashboard.vue(issue #15):
    • 顶部欢迎条:标题「运营数据」+ 副标题「全站 KPI · 趋势 · 排行 — YYYY-MM-DD」+ 右侧 range 单选(今日/7 日/30 日/全部)+ 最后刷新时间 + 刷新按钮。
    • 左侧 16/24 主栏(6 个 panel):
      • 用户 KPI:8 张统计卡(总用户/今日新增/7 日/30 日/7 日活跃/禁用/已删/今日占比),带 arco icon + 调色板。
      • 营收:总营收/充值/订阅/退款 4 张卡(带「营收」脚注),移至资源上方。
      • 资源 / 用量:令牌/渠道/套餐(→/setting/plan)/兑换码(→/redemption)/订阅 5 张可点击卡。
      • Token / 请求消耗 + 三张折线:4 张配额卡 + 分隔条 + 请求量/额度/Token 三张 160px 折线图(vue-echarts),样式与仪表盘一致。
      • 模型分布(近 7 日):独立全宽 panel,堆叠柱图(Top 8 模型 × 7 天),legend 底部滚动。
      • 使用明细:表格分页 8 行(日期 / 模型 / 请求数 / 消耗 / Token),dash-table 样式与仪表盘一致。
    • 右侧 8/24 栏
      • 广告位占位:渐变蓝紫 AD 卡片。
      • 用户排行:卡片式(rank 徽章 + 用户名/邮箱 + 请求/消耗双列 + 套餐 tag),radio 切换今日/本周/本月。
      • 系统公告:3 条公告列表。
      • 更新日志:3 条 changelog 条目。
      • 资源:官方文档 + GitHub 外链。
    • 样式:所有 panel/grid/trend-cell/dash-table 样式与 Dashboard.vue 同构(行高/间距/字号一致)。
  • 新增侧边栏「数据」菜单layouts/AdminLayout.vue,admin only):
    • 位置:渠道之上,icon icon-bar-chart,与「渠道/订单/兑换码/用户/订阅/设置」并列。
    • 路由 /admin/dashboardrouter/index.js 注册,isAdminRoute allow-list 加入 AdminDashboard,非管理员访问被重定向到 /dashboard
  • 新增前端 API 模块 src/api/admin.js
    • adminApi.overview(range)GET /api/admin/dashboard/overview
    • adminApi.topUsers(range, limit)GET /api/admin/dashboard/top-users
    • adminApi.charts(range)GET /api/admin/dashboard/charts(统一图表接口)。
    • adminApi.modelDistribution / adminApi.usageDetails 在重构后移除(被 charts 取代)。
  • 新增 i18n 文案(zh/en,src/i18n/locales.jsadmin.* 命名空间):
    • 页面标题 pageTitle: '运营数据' / 'Overview'
    • 5 个分区标题:sectionUsers / sectionRevenue / sectionResources / sectionQuota / sectionLeaderboard
    • 卡片 label:用户 KPI、营收、资源、Token/请求消耗 等约 40 个 key。
    • 时间筛选 label:todayLabel: '今日' / 'Today'weekLabel: '本周' / 'This Week'monthLabel: '本月' / 'This Month'rangeToday/7d/30d/all
    • 排行榜 tag:sectionLeaderboard: '用户排行'、列名 colUsername/colRequestCount/colQuota/colBalance/colPlan
    • 图表标题精简:chartRequests: '请求量'chartQuota: '额度'chartTokens: 'Token'chartModelDist: '模型分布(近 7 日)'
    • 公告/更新日志/资源/广告位等辅助文案。
  • 网页 Title 动态化
    • 落地页默认 Title 改为 ONE-API-PRO—企业级AI API 网关web/default-pro/index.html)。
    • 路由切换时 router.afterEach 钩子按 meta.title 拼接 {菜单名}—ONE-API-PRO,如「控制台—ONE-API-PRO」。

🐛 问题修复

  • 修复 /api/* 未匹配路由误返回 OpenAI 风格 invalid_request_error 误导排查router/web.go):
    • 原来 NoRoute/v1/*/api/* 都返回 RelayNotFound(OpenAI invalid_request_error),导致缺失的管理接口被误判为 relay 路由问题。
    • 现仅 /v1/* 保留 OpenAI 错误(relay 客户端需要兼容),/api/* 未匹配返回标准 {success:false, message:"接口不存在: METHOD PATH"} 404 信封,便于定位路由缺失。
  • 修复 admin_dashboard 结构体 GORM column 推断错位model/admin_dashboard.go):
    • AdminDashboardUsers / AdminDashboardResources / AdminDashboardTopUserRow 等聚合行结构体默认按 GORM 字段名 snake_case 推断列名(New7dnew7d),与 SQL 别名 new_7d 不匹配,导致聚合全部填充 0。
    • 显式补齐 gorm:"column:..." 标签后正常。
    • TestGetAdminDashboardOverview_Smoke 触发并修复。
  • 修复 Vue <style scoped> 跨组件不生效:AdminDashboard 引用的 .panel / .stat-grid / .trend-cell / .dash-table 等与 Dashboard.vue 同名样式,因 scoped hash 不同不会跨文件共享;将 AdminDashboard 实际用到的样式全部内联到本组件的 <style scoped>,并去除 .admin-dashboard 容器多余 padding,改用 .dashboard { display: flex; flex-direction: column; gap: 16px } 与 Dashboard.vue 布局一致。
  • 修复 admin 折线图无数据时全 0 看不见线yAxis.min = 0 + max = maxV > 0 ? undefined : 1(旧版在 0 数据时 max = undefined 导致 auto-scale 把线压成一条不可见线);areaStyle 透明度由 02 提到 30symbolSize = 6 + itemStyle.borderWidth = 2 强化折线点。
  • 修复 admin 折线图「9-11 标签被右边界裁切」grid.right: 16 + xAxis.axisLabel.margin: 8 + hideOverlap: false,确保最新日期可见。
  • 修复 admin 折线图缺今日AdminTrendPoint / aggregateTrends 在某日无数据时只回 {day}(因 omitempty)导致前端 undefined;改用 []LogStatistic 一致结构 + 前端本地补全 N 天日期序列(含今天),彻底解决「9-11 今天的没显示」。
  • 修复 AdminDashboard 残留 IconHistogram / IconRollback 引用npm run build 不报错(运行时才崩),统一替换为 arco icon set 内可用名称 IconBarChart / IconArrowFall
  • 修复 9-11 类型 UTC 对齐:Go todayStart = now - (now%daySec) 按 UTC 边界 + SQLite strftime UTC + CST 本地 9:00 之间偏差 1 天的隐患,统一在测试日志时间戳 +100s 保证 UTC 与本地对齐。

🔧 重构

后端(Go)

  • 图表数据从 overview 拆分独立
    • 移除 AdminTrendPoint / AdminDashboardTrends / aggregateTrends 三个旧结构/函数。
    • AdminDashboardOverview 移除 Trends 字段,专注 KPI。
    • 移除 AdminModelDistribution* / GetAdminModelDistributionAdminUsageDetails* / GetAdminUsageDetails,由 /charts 统一驱动。
    • 移除对应 controller handlers + router 路由(/api/admin/dashboard/model-distribution/api/admin/dashboard/usage-details)。
    • model/admin_dashboard.go 总行数减少 ~320 行;删除 sort / time 等不再需要的 import。
  • 响应结构统一:单一 /charts 接口返回与 /api/user/dashboard 同构的 []LogStatistic,前端无需分别请求 3 个端点拼装数据。

前端

  • AdminDashboard 全面重构:从 trend row 内嵌模型分布改为独立全宽模型分布 panel;折线图数量从「请求量/Quota/模型分布」改为「请求量/额度/Token」3 张折线(与 Dashboard.vue 一致)。
  • adminApi.modelDistribution / adminApi.usageDetails 移除,统一为 adminApi.charts 一份接口。
  • AdminDashboard.vue 新增 chartData / daySeries / distModels / usageRows 四个 computed,本地构建日期序列 + 客户端聚合 Top 8 模型,避免依赖后端预聚合。
  • lineOptionoverview.trends 改读 chartData[{date, value}])。
  • modelBarOptiondistDays + distItems 改读 distModels(前端 Top N 排序)。
  • 使用明细表从 usageDetails 改读 usageRows(扁平 day×model,日期 desc + 消耗 desc 排序)。

🚀 性能 / 运维

  • 修复 release-docker workflow arm64 emulated npm install 触发 SIGILLDockerfile + .github/workflows/release-docker.yml):
    • 改为在 release-docker job 中预构建 web/build,docker build 不再 npm install,避免 QEMU 模拟的 Node.js 二进制崩溃。
    • 详见 ci(release-docker): 预构建 web/build,避免 arm64 emulated npm install 触发 SIGILL

⚠️ 升级注意事项

  • 零数据库迁移:所有 admin dashboard 新增字段都是聚合响应或新接口,无表结构变更。
  • 后端路由变更router/api.go,admin 路由组):
    • 新增 GET /api/admin/dashboard/charts(AdminAuth)。
    • 移除 GET /api/admin/dashboard/model-distributionGET /api/admin/dashboard/usage-details(被 /charts 取代)。
    • 如有外部监控/脚本调用旧路由,请改用 /charts + 前端聚合逻辑(或直接用 /api/admin/dashboard/overview)。
  • 后端二进制必须重启/charts 是新路由,编译进二进制后才生效。升级后必须重启 one-api-pro 进程。
  • 前端 chunk 缓存:浏览器需要硬刷(Cmd+Shift+R)以加载新 AdminDashboard chunk;旧的 AdminDashboard-*.js chunk 仍可能引用 /usage-details 导致 404。
  • 未匹配 /api/* 错误格式变化:升级后若遗漏注册路由,前端会收到 {success:false, message:"接口不存在: GET /api/..."}(HTTP 404)而非 OpenAI 风格错误,更易定位。
  • 后端构建与测试
    • go build ./... 通过。
    • go test ./model/ ./controller/ ./middleware/ 全绿(含 3 个新增 admin_dashboard 测试 + 已有测试)。
    • 前端:cd web/default-pro && npm run build 通过。
  • 页面标题变更:浏览器 tab 标题从「One Api Pro——企业级 API 网关」变为 ONE-API-PRO—企业级AI API 网关,后台路由切换时动态显示「{菜单名}—ONE-API-PRO」。如有外部监控依赖旧标题文案请知悉。

English

✨ New Features

Go backend

  • New admin dashboard aggregate layer (model/admin_dashboard.go, issue #15):
    • GetAdminDashboardOverview(rawRange): one-shot KPI aggregation — users (total / today / 7d / 30d / active / disabled / deleted / today-ratio), resources (tokens / channels / plans / redemptions / subscriptions with enabled/active counts), quota (today / 7d / 30d / total), revenue (total / top-up / subscription / refund). Six sections in one round-trip; one SQL per section, no N+1.
    • GetAdminTopUsers(rawRange, limit): active-user leaderboard (consumed logs in window + request_count > 0, sorted by request count desc then quota desc); current plan names batch-embedded via a single WHERE IN.
    • GetAdminModelDistribution(rawRange, topN) + AdminModelDistribution: whole-site top-N models by quota + 7-day series for stacked bar (later superseded by /charts; see 🔧 Refactor).
    • GetAdminUsageDetails(rawRange, topN) + AdminUsageDetails: top-N model × per-day breakdown for the "usage details" table (later superseded by /charts).
    • ParseAdminDashboardRange / ParseAdminChartsRange: unified range preset parsing (today/7d/30d/all); the latter emits a day-aligned window and caps all to the last 30 days to keep responses bounded.
    • `Se...
Read more

v0.0.16

Choose a tag to compare

@github-actions github-actions released this 09 Sep 09:21

v0.0.16 — 修复 Anthropic 流式计费 usage 双计与缓存语义

修复 Anthropic / Vertex Claude / AWS Bedrock Claude 渠道流式请求中 usage 被双计(input/cache 令牌按约两倍扣费)的问题,并统一 Anthropic 缓存计费语义:cache read 折入 prompt 计价、补齐 AWS 渠道此前完全缺失的 cache 统计。

中文

🐛 问题修复

  • 修复 Anthropic 流式计费「usage 双计」(issue #13):
    • 新版 Messages API 下 message_startmessage_delta 都携带整段请求的累计 usage(message_delta 重复 message_start 的 input/cache 字段并带上最终 output_tokens)。
    • 原实现按 += 当作增量累加,导致 input_tokens / cache_read_input_tokens 被计两遍,典型场景多扣约 50%(如 3.5-sonnet 下 4522 vs 正确 3000)。
    • 现改为对累计值取 max 合并,并兼容旧形态(message_delta 仅带 output、input 为 0)不丢 message_start 的 input 计数。
  • 修复 cache read 语义错配造成的欠费:Anthropic 的 input_tokenscache_read/cache_creation_input_tokens 互斥且不含彼此,而共享计费公式按 OpenAI「cached ⊆ prompt」扣减(input×(prompt−cached)),导致 cache read 被从 prompt 中重复扣减、按 read 价计费的同时又丢了一次 input 价;现归一化把 read 折入 prompt,公式自动还原出 input×input + readPrice×read 的正确金额。
  • 修复 AWS Bedrock Claude 渠道 cache 完全不参与计费:流式与非流式路径此前都没有把 cache_read_input_tokens 写入 PromptTokensDetails.CachedTokens,现统一补齐。
  • 修复 cache_creation(写入令牌)解析后从不计费cache_creation_input_tokens 此前仅被解析、未进入 usage;现折入 prompt 按输入价计费(真实写入价约为 1.25×input,此为已知近似,见 #13)。

🔧 重构

  • 新增 relay/adaptor/anthropic/usage.goClaudeUsage2OpenAI(Claude→OpenAI usage 归一化)与 MergeClaudeUsage(累计流式事件取 max 合并)。
  • native Anthropic / Vertex AI Claude / AWS Bedrock Claude 三条消费路径统一复用上述 helper,消除三处手写累计逻辑的漂移。

🧪 测试

  • relay/adaptor/anthropic/main_test.gomessage_delta fixture 从旧形态(input_tokens: 0)更新为真实累计形态,避免继续掩盖双计 bug。
  • 新增 TestClaudeUsage2OpenAI / TestMergeClaudeUsage 行为单测:覆盖累计序列不双计、旧形态兼容、read/creation 折入、cached ⊆ prompt 不变式。

⚠️ 升级注意事项

  • 零数据库迁移、零配置变更
  • 计费口径调整:修复后 Anthropic 渠道不再双计,扣费恢复正常;prompt_tokens 消费日志将包含折入的 cache read/creation,口径与 OpenAI 一致(历史消费记录不追溯修正)。
  • 运行验证go build ./...go test ./relay/adaptor/anthropic/ ./relay/billing/ratio/ ./model/ ./controller/ ./middleware/

English

🐛 Bug Fixes

  • Fixed double-counted streaming usage for Anthropic (issue #13):
    • Under the current Messages API, both message_start and message_delta carry cumulative usage for the whole request (message_delta repeats message_start's input/cache fields and adds the final output_tokens).
    • The old code accumulated with += as if each event were incremental, so input_tokens / cache_read_input_tokens were counted twice — typically ~50% over-billing (e.g. 4522 vs the correct 3000 on claude-3.5-sonnet).
    • Events are now merged by max of the cumulative values, and the legacy shape (delta with only output_tokens, zeroed input) still keeps message_start's input count.
  • Fixed cache-read semantic mismatch causing under-billing: Claude's input_tokens is disjoint from cache_read/cache_creation_input_tokens, yet the shared billing formula assumes OpenAI's "cached ⊆ prompt" (input×(prompt−cached)). Cache reads were therefore subtracted twice — charged at read price while also dropping one input-price charge. Reads are now folded into PromptTokens, so the formula yields the correct input×input + readPrice×read.
  • Fixed AWS Bedrock Claude channels never billing cache: neither the streaming nor the non-streaming path wrote cache_read_input_tokens into PromptTokensDetails.CachedTokens; both now do.
  • Fixed cache-creation tokens being parsed but never billed: cache_creation_input_tokens was unmarshalled yet dropped from usage; it is now folded into PromptTokens and billed at input price (actual write price is ≈1.25×input — a documented approximation, see #13).

🔧 Refactor

  • New relay/adaptor/anthropic/usage.go: ClaudeUsage2OpenAI (Claude→OpenAI usage normalization) and MergeClaudeUsage (max-merge of cumulative stream events).
  • The three consumer paths — native Anthropic, Vertex AI Claude, and AWS Bedrock Claude — now share these helpers, removing three divergent hand-written accumulators.

🧪 Tests

  • relay/adaptor/anthropic/main_test.go: the message_delta fixture was updated from the legacy zeroed-input_tokens shape to the real cumulative shape so the double-count bug can no longer hide.
  • Added TestClaudeUsage2OpenAI / TestMergeClaudeUsage behavior tests covering cumulative sequences, legacy-shape compatibility, read/creation folding, and the cached ⊆ prompt invariant.

⚠️ Upgrade Notes

  • Zero database migration and zero configuration changes.
  • Billing behavior: Anthropic channels no longer double count and charge correctly; prompt_tokens in usage logs now includes folded cache read/creation, consistent with OpenAI semantics (historical logs are not retroactively adjusted).
  • Verification: go build ./...; go test ./relay/adaptor/anthropic/ ./relay/billing/ratio/ ./model/ ./controller/ ./middleware/.

v0.0.15

Choose a tag to compare

@github-actions github-actions released this 09 Sep 01:30

v0.0.15 — 管理员订单管理后台与订阅诊断端点

管理员可在后台「订单」页面集中管理全部用户订单(套餐订阅 + 充值),支持多维筛选、状态流转(标记已付 / 退款 / 删除)与详情查看;同步新增 /api/diag/subscriptions 诊断端点,配套 UserPlan.Insert 防御性校验,方便排查 plan_id 异常问题。

中文

✨ 新增功能

后端(Go)

  • 新增管理员诊断端点 GET /api/diag/subscriptionscontroller/diag.go,AdminAuth):
    • 拉取 user_plans 表前 100 行(GetAllUserPlans(0, 100, 0, -1)),逐行对比 orders 表中对应订单的 plan_id / type,并嵌入用户摘要。
    • 返回体含 total_user_plans(扫描行数)、missing_plan_idplan_id<=0 的疑似脏数据行数)、rows(逐行明细:user_plan + order_no + order_plan_id + order_type + user)。
    • 用户信息复用 GetUsersBriefByIds 单次 WHERE IN 查询,无 N+1。
    • router/api.go 注册 /api/diag/subscriptions 路由(AdminAuth 中间件兜底)。
  • model/user 增加 UserBrief 与批量查询model/user.go):
    • 新增 UserBrief 结构体(id / username / display_name 三个字段),用于列表嵌入场景。
    • 新增 GetUsersBriefByIds(ids []int) (map[int]*UserBrief, error):空 ids 短路不查 DB,否则单条 WHERE IN 查询,并对缺失 id 容错。
  • 订单管理接口支持多维过滤 + 用户嵌入controller/order + model/order):
    • 新增 model.OrderAdminFilter 结构体与 applyTo helper;GetAllOrders / SearchOrders 改用 *OrderAdminFilter 参数。
    • model.Order 新增 User *UserBriefgorm:"-")瞬时字段,admin 列表响应中作为 user 字段返回。
    • controller 新增 parseOrderAdminFilter:从 query 解析 type / status / source / user_id / plan_id / keyword 六个过滤维度。
    • status 空字符串表示「全部」,避免与 OrderStatusPending(0) 值重叠。
    • 新增 enrichOrdersWithUserBrief 批量补全 User 字段:单次 WHERE IN 查询全部目标 user id,无 N+1。
  • UserPlan.Insert 防御性校验拒绝 plan_id<=0model/user_plan.go):
    • Insert 入口新增前置校验:PlanId <= 0 时直接返回错误 user_plan.plan_id 不能为空,杜绝脏数据写入。
    • 配套新增 model/subscription_defense_test.go,覆盖 InsertActivatePackageByOrder 两处防御路径。

前端(web/default-pro/

  • 新增管理员订单列表页 views/admin/AdminOrders.vue
    • 顶部欢迎条显示总条数 meta-chip。
    • 独立搜索卡:订单号 / 支付流水号搜索 + 类型(套餐 / 充值)/ 状态(待支付 / 已支付 / 已取消 / 已退款)/ 来源(用户自助 / 管理员)三个 <a-select> 下拉筛选 + 重置 / 刷新按钮。
    • 11 列 grid 行展示:ID / 订单号 / 类型 / 用户 / 套餐 / 金额 / 支付方式 / 状态 / 来源 / 创建时间 / 操作,用户列渲染嵌入的 user.display_name
    • 操作列支持:查看详情 / 标记已付(status=0→1)/ 退款(status=1→3)/ 删除(root 限定,仅 status≠1 可删)。
    • 详情弹窗展示完整字段,并异步拉取最新 user 信息;标记已付弹窗选择 pay_method + 可选 pay_trade_no
    • <a-spin> 设置 style="width:100%" 避免布局塌陷(AGENTS.md §10.4)。
  • 注册 /admin/orders 路由与侧边栏菜单
    • router/index.js:注册 /admin/orders 路由;isAdminRoute 加入 AdminOrders 判断。
    • layouts/AdminLayout.vue:在渠道与兑换码之间插入「订单」菜单项(icon icon-storage)。
    • api/order.js 扩展:orderApi.list(params) 接受分页与多过滤参数;search 扩展支持 status / source / user_id / plan_id
    • 新增 buildAdminParams helper,仅透传非零 / 非空字段,避免污染 query。

🐛 问题修复

  • 修复 UserPlan.Insert 写入 plan_id=0 导致 admin 列表套餐列空白的脏数据问题:通过新增 PlanId<=0 前置校验防御(见上),异常来源追溯到 ActivatePackageByOrder 流程同样增加防御。
  • 修复 dashboard 与 admin 之间因缺乏统一订单管理入口导致的运维盲区:通过新增 AdminOrders 页面 + /api/diag/subscriptions 端点,提供集中可视化的订单与订阅诊断能力。

🧪 测试

  • 新增 model/order_admin_filter_test.goorder_admin_filter_test 测试集):
    • setupOrderAdminTestDBglebarez/sqlite 内存库 + gorm.AutoMigrateusers + orders 表。
    • seedUsersAndOrders 通用夹具 helper:每个 user 必须设 AccessToken / AffCode 以满足 unique 索引。
    • TestOrderAdminFilter_ApplyTo:验证零值全字段为 no-op。
    • TestGetAllOrders_FilterMatrix9 个 subtest 覆盖 type / status / source / user_id / plan_id / keyword 五个过滤维度及组合。
    • TestSearchOrders_StatusFilter:验证搜索结果也按 status 过滤。
    • TestGetUsersBriefByIds_EmptyAndIn:验证空 ids 短路 + WHERE IN 查询 + 缺失 id 容错。
  • 新增 model/subscription_defense_test.go:覆盖 UserPlan.InsertActivatePackageByOrder 两处防御性校验路径。

📚 文档

  • README.md 新增 QQ 群信息979158b add qq group)。

⚠️ 升级注意事项

  • 零数据库迁移UserBrief 是新类型、Order.Usergorm:"-" 瞬时字段、OrderAdminFilter 仅是 controller 层的解析结构;不涉及表结构变更。
  • 新增 admin 路由
    • GET /api/diag/subscriptions(AdminAuth):诊断端点,生产环境视情况可关闭或仅暴露给 root。
    • admin 订单接口 /api/order//api/order/search 新增可选 query 参数 status / source / user_id / plan_id / keyword,向后兼容(不传则忽略)。
  • 防御性变更UserPlan.InsertActivatePackageByOrderplan_id<=0 时会直接拒绝;若有历史代码路径依赖此容错行为,需同步修复上游调用方(理论上不应有此类调用)。
  • 前端:新增 views/admin/AdminOrders.vue 与侧边栏菜单项;api/order.js 扩展,无破坏性变更。
  • 后端构建go build ./...;测试运行 go test ./model/ ./controller/ ./middleware/

English

✨ New Features

Go backend

  • New admin diagnostic endpoint GET /api/diag/subscriptions (controller/diag.go, AdminAuth):
    • Pulls up to 100 rows from user_plans via GetAllUserPlans(0, 100, 0, -1), joins each with the source order's plan_id / type, and embeds a user brief.
    • Response includes total_user_plans (rows scanned), missing_plan_id (rows with plan_id<=0), and rows (per-row details: user_plan + order_no + order_plan_id + order_type + user).
    • User data is fetched via GetUsersBriefByIds with a single WHERE IN query — no N+1.
    • router/api.go registers /api/diag/subscriptions behind the AdminAuth middleware.
  • model/user adds UserBrief and batch lookup (model/user.go):
    • New UserBrief struct (id / username / display_name) for list-embedding scenarios.
    • New GetUsersBriefByIds(ids []int) (map[int]*UserBrief, error): short-circuits on empty ids; otherwise runs a single WHERE IN query and tolerates missing ids.
  • Order-management endpoints gain multi-dimensional filters + user embedding (controller/order + model/order):
    • New model.OrderAdminFilter struct + applyTo helper; GetAllOrders / SearchOrders now take *OrderAdminFilter.
    • model.Order gains a transient User *UserBrief field (gorm:"-") that admin list responses serialize as user.
    • controller adds parseOrderAdminFilter, reading type / status / source / user_id / plan_id / keyword from query params.
    • Empty status means "all", avoiding the OrderStatusPending(0) value clash.
    • New enrichOrdersWithUserBrief helper batches user embedding via a single WHERE IN.
  • UserPlan.Insert defensively rejects plan_id<=0 (model/user_plan.go):
    • Insert now refuses to write when PlanId<=0, returning the error user_plan.plan_id 不能为空.
    • Companion test model/subscription_defense_test.go covers both Insert and ActivatePackageByOrder defensive paths.

Frontend (web/default-pro/)

  • New admin order-list page views/admin/AdminOrders.vue:
    • Welcome bar with total-count meta-chip.
    • Standalone search card: order-no / pay-trade-no search + three <a-select> filters (type: plan / recharge, status: pending / paid / cancelled / refunded, source: user / admin) + reset / refresh buttons.
    • 11-column grid row: ID / order-no / type / user / plan / amount / pay-method / status / source / created-at / actions; user column renders embedded user.display_name.
    • Actions: view details / mark-paid (status=0→1) / refund (status=1→3) / delete (root-only, only when status≠1).
    • Detail modal shows full fields and lazily fetches the latest user info; mark-paid modal picks pay_method and optional pay_trade_no.
    • <a-spin> carries style="width:100%" per AGENTS.md §10.4 to avoid layout collapse.
  • Register /admin/orders route and sidebar menu:
    • router/index.js: registers /admin/orders; isAdminRoute includes AdminOrders.
    • layouts/AdminLayout.vue: inserts the "订单" menu item (icon icon-storage) between Channels and Redemption.
    • api/order.js extension: orderApi.list(params) accepts paging + multi-filter params; search extends with status / source / user_id / plan_id.
    • New buildAdminParams helper only forwards truthy fields, keeping query strings clean.

🐛 Bug Fixes

  • Fixed dirty plan_id=0 writes causing empty plan columns in the admin list: now blocked at the UserPlan.Insert boundary (see above), with the source flow ActivatePackageByOrder also defensively guarded.
  • Closed the operations blind-spot between the dashboard and admin by adding a unified order-management entry point: the new AdminOrders page plus /api/diag/subscriptions endpoint provides a single, visual place to inspect orders and diagnose subscriptions.

🧪 Tests

  • New model/order_admin_filter_test.go (the order_admin_filter_test suite):
    • setupOrderAdminTestDB spins up glebarez/sqlite in-memory + gorm.AutoMigrate for users and orders.
    • seedUsersAndOrders shared fixture helper; every user must set AccessToken / AffCode to satisfy unique indexes.
    • TestOrderAdminFilter_ApplyTo: verifies zero-value filter is a no-op.
    • TestGetAllOrders_FilterMatrix: 9 subtests covering type / status / source / user_id / plan_id / keyword and combinations.
    • TestSearchOrders_StatusFilter: verifies the search path also honours status.
    • TestGetUsersBriefByIds_EmptyAndIn: short-circuit on empty ids + WHERE IN query + missing-id tolerance.
  • New model/subscription_defense_test.go: covers both the UserPlan.Insert and ActivatePackageByOrder defensive paths.

📚 Documentation

  • README.md adds QQ group info (979158b add qq group).

⚠️ Upgrade Notes

  • Zero database migration: UserBrief is a new type, Order.User is a gorm:"-" transient field, and OrderAdminFilter is a controller-layer parsing struct — no schema changes.
  • New admin routes:
    • GET /api/diag/subscriptions (AdminAuth): diagnostic endpoint; consider restricting or root-onlying it in production as needed.
    • Admin order endpoints /api/order/ and /api/order/search now accept optional query params status / source / user_id / plan_id / keyword. Fully backward-compatible — absent params are ignored.
  • Defensive change: UserPlan.Insert and ActivatePackageByOrder now refuse plan_id<=0. If any legacy code path relied on this tolerance, the upstream caller must be fixed (none should).
  • Frontend: new views/admin/AdminOrders.vue plus a sidebar entry; api/order.js extended, no breaking changes...
Read more

v0.0.13

Choose a tag to compare

@github-actions github-actions released this 07 Sep 16:16

v0.0.13 — 套餐 features 字段重构 & 用户时间戳自动填充

套餐 features 字段从字符串重构为 array<string>(新增 StringSlice 类型,三种历史形态零迁移兼容),后台套餐弹窗改为动态行编辑器避免用户手敲 JSON;同时修复 User 模型 created_at 始终为 0、updated_at 不刷新的历史 bug,并在用户列表新增「注册时间」列。

中文

✨ 新增功能

后端(Go)

  • 套餐 features 字段重构为 array<string>Issue #11):
    • model/plan.go 新增 StringSlice 类型,同时实现 MarshalJSON / UnmarshalJSON / driver.Valuer / sql.Scanner 四个接口,作为 []string 在 JSON wire 与 DB text 列之间的双向桥接。
    • JSON wire 格式统一为 ["A","B","C"];DB 仍以 JSON 字符串写入 text 列,AutoMigrate 无 schema 变更
    • UnmarshalJSON / Scan 兼容历史三种形态:JSON 数组字符串 / 换行分隔纯文本 / JSON 对象,统一归一为 []string
    • 新增 Plan.GetFeatures() 返回拷贝后的 []string,供 controller 序列化使用,避免外部修改共享底层 slice。
    • 新增 model/plan_test.go16 个用例 覆盖 Marshal/Unmarshal/Value/Scan 往返、nil、空、空白、CRLF、非法 JSON 等边界。
  • GetCurrentPlan 返回 features 数组形态controller/plan.go):data["features"] 从裸字符串改为 Plan.GetFeatures(),与 /api/plan/detail 一致。前端 Plans.vue::parseFeaturesArray.isArray 兼容,无需联动修改
  • User 模型时间戳自动填充model/user.go):
    • 新增 User.BeforeCreate 钩子:CreatedAt / UpdatedAt 为 0 时自动写入 helper.GetTimestamp()
    • 新增 User.BeforeUpdate 钩子:任何 Updates(...) 路径都会刷新 UpdatedAt
    • 一并覆盖 Register / CreateUser / UpdateUser / ManageUser / EmailBind / GenerateAccessToken 等所有写入路径。
    • 修复 /api/user/?p=0 接口中 created_at 始终为 0、updated_at 不刷新的历史 bug。

前端(web/default-pro/

  • 后台套餐弹窗「特性说明」改为动态行编辑器views/setting/PlanSetting.vue):
    • 删除原 <a-textarea>,替换为 .features-editor 容器:每行 <a-input allow-clear> + 删除按钮,末尾「添加特性」虚线按钮。
    • 新增 formFeatures ref 与 form.features 解耦,专管动态行;提供 addFeature / addFeatureAfter / removeFeature / updateFeature 四个操作函数。
    • 某行 <a-input>Enter 自动新增一行;末行空且唯一时禁用删除(防误清空)。
    • openModalutils/plan.featuresFromRecord 回填(向后兼容旧数据形态);handleSavesanitizeFeaturesList 过滤空行后直接以数组形态 POST/PUT /api/plan/,零格式歧义。
    • 新增 .features-editor / .features-editor-row / .features-editor-add / .features-editor-remove 样式。
  • 后台用户列表新增「注册时间」列views/user/User.vue):
    • 表头新增「注册时间」列,渲染 u.created_at;单元格显示 YYYY-MM-DD 短日期,hover tooltip 显示完整本地时间。
    • 新增 formatDate / formatDateTime 工具函数,无值时回退 -
    • 调整 .list-head / .list-row 的 grid 模板,新增 120px 列宽。
  • 新增 src/utils/plan.js 纯函数工具 + 14 个 node:test 单测
    • normalizeFeaturesRaw(raw):把后端可能返回的多种形态(数组 / JSON 字符串 / 换行文本 / CRLF / JSON 对象 / null / 非法 JSON / 数字)统一归一为 []string,丢弃空项。
    • sanitizeFeaturesList(list):清洗表单输入,去空白、返回新数组不修改入参。
    • featuresFromRecord(raw):把后端字段转成弹窗表单数组(空记录回退到 1 行空输入)。
    • buildEmptyFeaturesForm():返回 1 个空字符串的初始表单。

🐛 问题修复

  • 修复 Usercreated_at 始终为 0、updated_at 不刷新的历史 bug:通过新增 User.BeforeCreate / BeforeUpdate 钩子(见上)覆盖所有写入路径。
  • 修复后台套餐弹窗「特性说明」易填错导致前端为空:原本要求 admin 手敲 ["A","B","C"] JSON 字符串,用户格式填错前端就显示空;改为每行 <a-input> 动态行编辑器,提交时由 sanitizeFeaturesList 清洗,零格式歧义。

📚 文档

  • docs/API.md 同步更新套餐 features 字段类型Issue #11):
    • 字段说明表 features 行:stringarray<string>,措辞改为「功能特性列表,每项一行展示在用户端套餐卡」。
    • GET /api/plan/ 响应体示例:"features": "功能特性描述"["API 调用 1000 次/月", "支持 GPT-4o"]
    • POST /api/plan/ 请求体示例同步为数组形式。

⚠️ 升级注意事项

  • 零数据库迁移Plan.Features 字段类型未变(仍是 text 列),StringSlice 仅在 Go 层做 JSON ↔ DB 双向桥接;现有数据的 features 文本("A\nB\nC""{\"API\":true}" 等历史形态)会在首次读取时自动归一化为数组。
  • API 兼容性GET /api/plan/GET /api/plan/currentfeatures 字段由字符串改为数组;前端 Plans.vue::parseFeatures 早已 Array.isArray 兼容,现有用户端无感知。
  • 管理端操作:admin 进入「设置 → 套餐管理」编辑任意套餐时,特性说明将以「每行一个 input + 添加/删除」形式展示,老数据自动按行展开。
  • 后端编译:新增 model/plan_test.gomodel/user_test.go,纯单元测试使用 glebarez/sqlite 内存库,不依赖外部 DB。
  • 前端构建:无新增依赖;建议 pnpm install && npm run build 后发布。

English

✨ New Features

Go backend

  • Refactored the plan features field to array<string> (Issue #11):
    • Added a new StringSlice type in model/plan.go that implements MarshalJSON, UnmarshalJSON, driver.Valuer, and sql.Scanner — it bridges []string between the JSON wire format and the DB text column.
    • JSON wire format unified to ["A","B","C"]; the DB still stores a JSON string in the text column, so AutoMigrate is a no-op (zero schema migration).
    • UnmarshalJSON / Scan accept all three historical shapes (JSON array string, newline-delimited plain text, JSON object) and normalize them to []string.
    • Added Plan.GetFeatures() returning a copy-safe []string for controller serialization so external code never mutates the underlying slice.
    • Added model/plan_test.go: 16 cases covering Marshal/Unmarshal/Value/Scan round-trips plus nil, empty, whitespace, CRLF, and invalid JSON edge cases.
  • GetCurrentPlan returns features as an array (controller/plan.go): replaced data["features"] = up.Plan.Features (raw string) with data["features"] = up.Plan.GetFeatures() so it matches /api/plan/detail. The user-side Plans.vue::parseFeatures already handled Array.isArray, no coordinated front-end change required.
  • User model auto-fills timestamps (model/user.go):
    • Added User.BeforeCreate: when CreatedAt / UpdatedAt are 0 they are auto-filled via helper.GetTimestamp().
    • Added User.BeforeUpdate: every Updates(...) path now refreshes UpdatedAt.
    • Covers every write path: Register, CreateUser, UpdateUser, ManageUser, EmailBind, GenerateAccessToken, etc.
    • Fixes the historical bug where /api/user/?p=0 returned created_at=0 and never-refreshed updated_at.

Frontend (web/default-pro/)

  • Admin plan modal's "Feature description" is now a dynamic row editor (views/setting/PlanSetting.vue):
    • Replaced the <a-textarea> with a .features-editor container: one <a-input allow-clear> per row + a delete button, with a trailing dashed "Add feature" button.
    • Introduced a formFeatures ref decoupled from form.features; added addFeature / addFeatureAfter / removeFeature / updateFeature for row manipulation.
    • Pressing Enter on any row auto-appends an empty row; when the last row is empty and the only one, delete is disabled to prevent accidental emptying.
    • openModal backfills via utils/plan.featuresFromRecord (backward-compatible with legacy shapes); handleSave POSTs/PUTs /api/plan/ with a sanitizeFeaturesList-cleaned array — zero format ambiguity.
    • Added .features-editor / .features-editor-row / .features-editor-add / .features-editor-remove styles.
  • Added a "Registration time" column to the admin user list (views/user/User.vue):
    • New header column rendering u.created_at; cell shows YYYY-MM-DD, hover tooltip reveals the full local datetime.
    • Added formatDate / formatDateTime helpers; falls back to - on missing/zero timestamps.
    • Adjusted .list-head / .list-row grid template to add a 120px column.
  • New src/utils/plan.js pure-function helpers + 14 node:test unit tests:
    • normalizeFeaturesRaw(raw): normalize any of the historical shapes (array / JSON string / newline text / CRLF / JSON object / null / invalid JSON / numbers) to []string, dropping blanks.
    • sanitizeFeaturesList(list): trim, filter blanks, return a fresh array without mutating the input.
    • featuresFromRecord(raw): convert the backend field into modal form rows (empty record falls back to a single empty row).
    • buildEmptyFeaturesForm(): returns a single empty-string initial form.

🐛 Bug Fixes

  • Fixed the historical User bug where created_at was always 0 and updated_at never refreshed: addressed by the new User.BeforeCreate / BeforeUpdate hooks above, covering every write path.
  • Fixed the admin plan modal "Feature description" being easy to mistype and rendering empty on the user side: the textarea required admin to hand-type a JSON ["A","B","C"] string, and any typo produced an empty list. The new dynamic-row editor with sanitizeFeaturesList removes the format ambiguity.

📚 Documentation

  • docs/API.md updated to reflect the new features type (Issue #11):
    • Field-table row features: stringarray<string>; wording updated to "list of features, one per row in the user-facing plan card".
    • GET /api/plan/ response sample: "features": "功能特性描述"["API 调用 1000 次/月", "支持 GPT-4o"].
    • POST /api/plan/ request sample synced to the array form.

⚠️ Upgrade Notes

  • Zero database migration: Plan.Features column type is unchanged (still text); StringSlice is purely a Go-side bridge between JSON and the DB column. Existing data — whether plain text like "A\nB\nC" or legacy JSON-object shapes — is normalized to []string automatically on first read.
  • API compatibility: GET /api/plan/ and GET /api/plan/current now return features as an array. The user-side Plans.vue::parseFeatures already handled Array.isArray, so existing user-facing code is unaffected.
  • Admin UX: when an admin opens any plan in Settings → Plan Management, the feature list now renders as one <a-input> per row with add/delete buttons; legacy data is split into rows automatically.
  • Backend build: added model/plan_test.go and model/user_test.go, both pure unit tests against glebarez/sqlite in-memory + gorm.AutoMigrate, no external DB required.
  • Frontend build: no new dependencies. Recommend pnpm install && npm run build before publishing.

v0.0.12

Choose a tag to compare

@github-actions github-actions released this 06 Sep 09:03

v0.0.11 — 在线充值(topup)全链路打通

用户可在控制台「我的余额」卡片直接发起在线充值,复用同一套微信 / 支付宝支付通道;后台新增独立的「充值」设置 Tab,管理员可配置快捷金额、自定义金额与换算比例;同步沉淀《AGENTS.md》开发规范并更新全语种 README。

中文

✨ 新增功能

后端(Go)

  • 新增「在线充值」业务模块(OrderTypeTopup=2
    • 新增 model/topup.go,定义 TopupPresetCreateTopupOrderInputTopupOrderPlanInfo 等结构,提供 CreateTopupOrderActivateTopupByOrderResolveTopupAmountGetTopupSettingsSaveTopupSettings 等业务函数。
    • 复用 GenerateOrderNo("TP") 生成充值订单号(TP 前缀),写入与套餐订单同一张 orders 表的 type=2 记录,节省一张表。
    • ActivateTopupByOrder 幂等:订单已支付直接返回;激活时调 IncreaseUserQuota 加额度并 RecordTopupLogTODO 标注退款时不回退 quota(待统一订单管理上线)。
    • 启动时通过 model.AutoMigrate 自动建表,无需手写 DDL
  • 新增充值系统设置键model/system_setting.go):
    • topup.enabled(总开关)、topup.allow_custom(是否允许自定义金额)、topup.presets(JSON 数组:[{amount, bonus_quota}, ...])、topup.exchange_rate(默认 1,1 元 = = 1 quota,仅作用于自定义金额)。
    • 分类常量 SystemSettingCategoryTopup = "topup"
  • 新增 HTTP 接口
    • POST /api/topup/orderCreateTopupOrder,用户认证):参数 { amount, preset_amount, pay_method },前置校验开关 / 通道 / 金额,命中预设或自定义规则后写订单,复用 controller/buildPayInfo 拿 pay_url / qr_code。
    • GET /api/setting/topupPUT /api/setting/topupGetTopupSettings / PutTopupSettings,Root 权限):读写上述 4 个 key。
  • 支付回调按订单类型分发controller/payment.go::processNotify):
    • order.Type == OrderTypeTopup → 调 ActivateTopupByOrder 给用户加 quota;
    • 其余 → 调 ActivatePackageByOrder 激活套餐。
    • MockPay(admin 测试接口)同步按 type 分发,提示文案分别显示「余额已到账」/「套餐已激活」。

前端(web/default-pro/

  • 新增可复用组件 components/TopupModal.vue
    • props:modelValuesettingstitle;emits:update:modelValuesuccesserrorpay
    • 顶部「选择金额」chip 列表:依次渲染所有 settings.presets最后一个 chip 固定为「自定义」(当 settings.allow_custom=true 时显示),点击后才显示金额输入框。
    • 中部支付方式选择器(pay-picker-item 微信 #07C160 / 支付宝 #1677FF 官方品牌色),复用 Plans.vue 既有样式。
    • 底部「支付金额」+「确认充值」按钮;选中自定义但未填金额时禁用提交。
  • 新增设置页 views/setting/TopupSetting.vue(root 可见):表单竖排三段:①总开关(enabled)②允许自定义金额(allow_custom)③自定义金额 1 元 = X quota(exchange_rate,默认 1);快捷金额用 <a-table> 行行内编辑(amount / bonus_quota),支持新增 / 删除;提交前前端预校验金额重复validateTopupPresets)。
  • 新增路由 /setting/topuprouter/index.js 注册子路由,Setting.vue 菜单新增「充值」项(root 可见,icon icon-subscribe-add)。
  • 新增 api/topup.jstopupApi.createOrder({ amount, preset_amount, pay_method })
  • 扩展 api/setting.jsgetTopup() / putTopup(data)
  • 新增 utils/topup.js 纯函数工具formatNumber / formatAmount / validateTopupPresets / calcCustomBonus,可在 Node 环境下用 node:test 单测。
  • 仪表盘接入充值弹窗
    • 控制台 Dashboard.vue 余额卡的「充值」按钮改为弹出 TopupModal,点击前预拉 /api/setting/topup + /api/payment/status,任一失败给出明确 toast;<a-modal> 使用 :visible + @update:visible踩坑修复:曾误用 :model-value,导致弹窗永远不显示)。
    • 同时弹出二维码 / 转账信息 <a-modal,刷新余额(/api/user/self)。
  • 订单管理表格新增「订单类型」列Orders.vue):套餐 / 充值 两种两色 chip(type-plan 蓝 / type-topup 橙),方便用户区分。
  • 移除运营 Tab 内的「允许余额充值」占位开关OperationSetting.vue):该字段已迁移到新充值 Tab,UI 与后端 GetPlanSettings / PutPlanSettings 同步删除读写;DB 中旧 plan.allow_topup 行保留无害。
  • 修复套餐界面硬编码套餐名白名单Plans.vue):删除 VALID_PLAN_NAMES = ['lite','air','pro','max'] 过滤逻辑,改为显示后端返回的所有套餐(用户反映该过滤导致测试套餐不可见)。

🐛 问题修复

  • 修复仪表盘充值按钮点击无反应:原 <TopupModal>TopupModal.vue 内部把 arco <a-modal> 的可见性绑定到了 :model-value,而 arco 的 <a-modal> 可见性属性是 visible,导致 v-model="topupModalVisible=true 时弹窗永远不显示。已改为 :visible="modelValue" + @update:visible="(v) => emit('update:modelValue', v)"
  • 修复充值按钮缓存导致的「管理员开启充值但仪表盘无反应」:原代码仅在 topupSettings.value 为 null 时拉取一次,admin 刚开启充值但仪表盘缓存旧值时按钮不会再次请求。已改为每次点击都强制重新拉取 /api/setting/topup,并把错误提示文案改成「请联系管理员在「设置-充值」中开启」。
  • 修复 <a-spin> 在 TopupSetting 设置页父容器宽度异常:补齐 style="width: 100%"
  • 修复 TopupSetting 表单横排三列不对齐:改为 layout="vertical" 三段竖排。

🚀 数据完整性 / 校验

  • 快捷金额金额不能重复:后端 model.SaveTopupSettingsseenAmounts map 检测重复金额,重复时返回「快捷金额重复:XX 元已存在」并整体拒绝保存;前端 utils/topup.js::validateTopupPresets 提前预校验,避免一次无效请求。

📚 文档

  • 新增项目根目录 AGENTS.md(480 行):面向 Agent / 贡献者的开发规范速查,覆盖 TL;DR / 项目结构 / 开发环境 / 提交规范(Conventional Commits + 中英双语 body + 一文件一 commit 粒度)/ 版本号 / 命名(topup 全局统一)/ 注释约定(文件级 4 行注释 + 导出符号注释 + TODO/XXX + 修改时如何更新)/ 后端规范(响应格式、model 不依赖 Gin、订单号前缀 TB/UP/TP)/ 前端规范(arco <a-modal>:visible 而非 :model-value<a-spin> 必须 style="width:100%"、工具函数用 node:test)/ 数据库 / 踩坑清单 / 工作流程(先 Plan 模式输出方案再编码)。
  • 同步更新 README.md 与 7 个语言版本(readme/README.{en,zh-TW,ja,ko,ar,de,ru}.md):新增「💰 在线充值(余额)」功能亮点章节;订单与支付章节补充订单类型列 + processNotify 分发说明;开发计划「在线充值」「充值设置中心」从规划移入已完成,新增「充值退款闭环」为进行中。

🔧 重构 / 工程

  • 移除运营 Tab 的 plan.allow_topup 字段读写controller/setting_payment.go):精简 GetPlanSettings / PutPlanSettings 返回结构,DB 行保留(不会主动删除)。
  • 清理调试日志:移除为排查充值按钮不响应问题加在 Dashboard.vue::onRechargeClickconsole.log

⚠️ 升级注意事项

  • 数据库迁移:新增 4 个 system_settings 键(topup.enabled / topup.allow_custom / topup.presets / topup.exchange_rate),无需迁移脚本,AutoMigrate + 首次调用会按需写入。
  • orders 表复用:本次新增 type=2 的充值订单与既有 type=1 套餐订单存同一张表,无 DDL 变更。
  • 运营 Tab 「套餐运营」小节:UI 上不再有「允许余额充值(仅占位)」开关;如需启用充值,请进入设置 → 充值
  • 充值功能默认关闭topup.enabled 未设置时为 false),管理员需到「设置 → 充值」手动开启并配置至少 1 个 preset 或允许自定义金额,前端充值按钮才会触发弹窗。
  • 退款:本期不支持;充值订单 status=3 仅标记,quota 不回退,待统一订单管理上线后补全。
  • 前端构建:新增依赖 qrcode(已在 pnpm-lock.yaml),建议 pnpm install && npm run build 后发布。

English

✨ New Features

Go backend

  • New online recharge module (OrderTypeTopup=2):
    • Added model/topup.go defining TopupPreset, CreateTopupOrderInput, TopupOrderPlanInfo, plus business functions CreateTopupOrder, ActivateTopupByOrder, ResolveTopupAmount, GetTopupSettings, SaveTopupSettings.
    • Reuses GenerateOrderNo("TP") for recharge order numbers (prefix TP); writes them to the same orders table as type=2, avoiding an extra table.
    • ActivateTopupByOrder is idempotent (paid orders return immediately); on activation it calls IncreaseUserQuota and writes RecordTopupLog. A TODO marks quota non-reversal on refund (awaiting unified admin order management).
    • model.AutoMigrate creates the table at startup — no manual DDL.
  • New recharge system-setting keys (model/system_setting.go):
    • topup.enabled (master switch), topup.allow_custom (allow custom amount), topup.presets (JSON array of [{amount, bonus_quota}, ...]), topup.exchange_rate (default 1, i.e. 1 CNY = 1 quota; applies only to custom amounts).
    • Category constant SystemSettingCategoryTopup = "topup".
  • New HTTP endpoints:
    • POST /api/topup/order (CreateTopupOrder, user-auth): body { amount, preset_amount, pay_method }; pre-validates the switch / channel / amount, then persists the order and reuses controller/buildPayInfo for pay_url / qr_code.
    • GET /api/setting/topup & PUT /api/setting/topup (GetTopupSettings / PutTopupSettings, root only): read/write the four keys above.
  • Payment callback dispatched by order type (controller/payment.go::processNotify):
    • order.Type == OrderTypeTopupActivateTopupByOrder to credit quota.
    • Otherwise → ActivatePackageByOrder to activate the plan.
    • MockPay (admin test endpoint) dispatches the same way and surfaces "balance credited" / "plan activated" copy.

Frontend (web/default-pro/)

  • New reusable component components/TopupModal.vue:
    • Props: modelValue, settings, title; emits: update:modelValue, success, error, pay.
    • Top "Choose amount" chip row: renders all settings.presets in order; the last chip is always "Custom" when settings.allow_custom=true. The amount input is shown only after clicking Custom.
    • Payment-method picker (pay-picker-item) with WeChat (#07C160) and Alipay (#1677FF) brand colors, reusing the styles from Plans.vue.
    • Footer shows "Pay amount" + "Confirm recharge"; submission is disabled when Custom is selected but no amount is entered.
  • New settings view views/setting/TopupSetting.vue (root-only): vertical layout with three sections — ① master switch (enabled), ② allow custom amount (allow_custom), ③ custom-amount 1 CNY = X quota (exchange_rate, default 1); preset rows use an <a-table> with inline-editable amount / bonus_quota columns, plus add / delete buttons. Front-end pre-validates duplicate amounts via validateTopupPresets before submission.
  • New route /setting/topup: registered in router/index.js; sidebar menu entry "充值" added in Setting.vue (root-only, icon icon-subscribe-add).
  • New api/topup.js: topupApi.createOrder({ amount, preset_amount, pay_method }).
  • Extended api/setting.js: getTopup() / putTopup(data).
  • New utils/topup.js pure-function helpers: formatNumber, formatAmount, validateTopupPresets, calcCustomBonus — usable in Node.js via node:test.
  • Dashboard recharge button wired up:
    • Dashboard.vue now opens TopupModal from the "Recharge" button on the balance card, prefetching /api/setting/topup + /api/payment/status to give precise error copy when either is missing. The underlying <a-modal> uses :visible + @update:visible (bug fix: the previous :model-value binding never showed the dialog because Arco's <a-modal> prop is visible).
    • Also opens a QR-code / bank-transfer modal and refreshes the balance via /api/user/self.
  • Order-management table gets an "Order type" column (Orders.vue): two-color chips for "Plan" / "Recharge" (type-plan blue, type-topup orange).
  • Removed the "Allow balance recharge" placeholder switch from the Operations tab (OperationSetting.vue): that field has migrated to the new Top-up tab; the UI and backend GetPlanSettings / PutPlanSettings no longer read or write it. The DB row is kept for backwards compatibility.
  • Removed the hard-coded plan-name whitelist from Plans.vue: the VALID_PLAN_NAMES = ['lite','air','pro','max'] filter was hiding test plans reported by the user; we now render ...
Read more

v0.0.10

Choose a tag to compare

@github-actions github-actions released this 03 Sep 15:06

v0.0.10 — Docker 镜像发布能力

新增官方 Docker 镜像构建与发布能力:每次推送形如 v*.*.* 的 tag 时,自动构建并推送多架构(linux/amd64 + linux/arm64)镜像到 GitHub Container Registry;提供 docker run 一键部署、约定挂载目录 /app/config + /app/data、自动加载 .env 文件,并支持通过 CLI 参数自定义启动行为。

中文

✨ 新增功能

官方 Docker 镜像

  • 新增根目录 Dockerfile,采用三阶段多阶段构建:
    • 阶段一使用 node:20-alpine 构建 web/default-pro 前端;
    • 阶段二使用 golang:1.22-alpineCGO_ENABLED=0 静态编译 Go 二进制(项目使用 glebarez/sqlite 纯 Go 实现,无需 CGO),并自动下载 go.mod 声明的 1.25 toolchain;
    • 阶段三以 alpine:latest 为运行时基础镜像,仅安装 ca-certificatestzdatawget,体积小、安全性高。
  • 默认以非 root 用户 app 运行,内置 /api/status HTTP 健康检查,运行时声明 VOLUME [/app/config, /app/data]
  • 默认环境变量:PORT=3000LOG_DIR=/app/data/logsSQLITE_PATH=/app/data/one-api-pro.dbCONFIG_DIR=/app/config

智能启动入口(docker-entrypoint.sh)

  • 新增 docker-entrypoint.sh,作为容器 ENTRYPOINT,提供三项能力:
    1. 自动加载 $CONFIG_DIR/.env:容器启动时若检测到该文件,自动作为 --env <path> 参数传入 one-api-pro,用户挂载 .env 即可生效,无需修改 docker run 命令;
    2. 透传用户 CLI 参数docker run image --port 8080 --log-dir /xxx 这类参数会被原样转发到 one-api-pro;
    3. 调试模式直通:当 CMD 首参既不是 one-api-pro 也不是其绝对路径时(例如 docker run image bash),入口直接 exec 透传给用户命令,方便进 shell 排查。
  • 使用 exec 替换当前进程,保证 SIGTERM 等信号正确传递到 one-api-pro。

🔧 工程 / CI

  • 新增 .github/workflows/release-docker.yml,与现有 release.yml(二进制发布)解耦:
    • 触发条件:push 推送形如 v*.*.* 的 tag,或手动 workflow_dispatch(支持手动指定 tag);
    • 强制校验 CHANGELOG/<tag>.md 存在,与 release.yml 保持一致的发布流程;
    • 通过 docker/setup-qemu-action + docker/setup-buildx-action 启用多架构构建,linux/amd64 + linux/arm64 并行出图;
    • 使用 docker/metadata-action 自动生成 semver tag(:0.0.10:0.0:0),latest=auto 策略自动跳过预发布后缀(-rc / -beta 等);
    • 注入 OCI 元数据:org.opencontainers.image.{title,description,source,licenses,revision,created}
    • 启用 GHA 层缓存(cache-from: type=gha / cache-to: type=gha,mode=max),后续构建秒级复用;
    • 通过 ${{ secrets.GITHUB_TOKEN }} 直接登录 ghcr.io无需额外配置 PAT);
    • 构建结束后调用 gh api PATCH /users/{owner}/packages/container/{repo}/orgs/{owner}/packages/container/{repo},自动把包设为 publiccontinue-on-error: true 保证权限受限时不影响镜像推送。
  • 新增 .dockerignore:与 .gitignore 对齐,排除 .git.githubnode_modulesdistlogs*.dbweb/airweb/berryweb/default 等已废弃主题与构建产物。

📚 文档

  • README.md 新增「🐳 Docker 部署」章节,位于「手动部署」与「多机部署」之间,包含:
    • 镜像地址表(latest / 指定版本 / 大版本);
    • 挂载目录约定(/app/config 配置 + /app/data 数据);
    • 快速开始(SQLite 单文件)、.env 配置示例;
    • 切换 MySQL / PostgreSQL(SQL_DSN)、修改端口的三种等价方式(-e / .env / CLI 参数);
    • 全部 CLI 参数表(--port--log-dir--env--version--help);
    • docker-compose.yml 完整示例、调试模式(进 shell)、升级流程。

⚠️ 升级注意事项

  • 本次为纯工程 / CI / 文档变更,无后端代码改动,无数据库迁移,可直接升级。
  • 现有二进制用户完全不受影响:未升级到 Docker 部署方式的二进制部署流程与之前一致。
  • 首次发布 Docker 镜像后请到 GitHub 仓库的 Packages 页面确认包已设为 public(CI 会自动尝试,少数组织仓库可能需要管理员手动确认)。
  • 若使用 docker-compose,请使用 docker compose pull && docker compose up -d 拉取新镜像并重启。

English

✨ New Features

Official Docker image

  • Added a root-level Dockerfile using a three-stage multi-stage build:
    • Stage 1 uses node:20-alpine to build the web/default-pro frontend;
    • Stage 2 uses golang:1.22-alpine, builds the Go binary statically with CGO_ENABLED=0 (the project uses the pure-Go glebarez/sqlite, so no CGO toolchain is needed), and auto-downloads the go.mod-declared 1.25 toolchain via GOTOOLCHAIN=auto;
    • Stage 3 uses alpine:latest as the runtime base, installing only ca-certificates, tzdata, and wget for a small, secure footprint.
  • Runs as non-root user app by default, ships with an HTTP health check against /api/status, and declares VOLUME [/app/config, /app/data].
  • Default environment variables: PORT=3000, LOG_DIR=/app/data/logs, SQLITE_PATH=/app/data/one-api-pro.db, CONFIG_DIR=/app/config.

Smart entrypoint (docker-entrypoint.sh)

  • New docker-entrypoint.sh serves as the container ENTRYPOINT and provides three behaviors:
    1. Auto-load $CONFIG_DIR/.env: if the file exists at startup it is forwarded to one-api-pro as --env <path>, so users can mount a .env and have it take effect without touching the docker run command;
    2. CLI argument passthrough: arguments such as docker run image --port 8080 --log-dir /xxx are forwarded verbatim to one-api-pro;
    3. Debug passthrough: when the CMD's first argument is neither one-api-pro nor its absolute path (e.g. docker run image bash), the entrypoint execs the user-provided command directly so it is easy to drop into a shell.
  • Uses exec to replace the current process so that signals such as SIGTERM are correctly delivered to one-api-pro.

🔧 Tooling / CI

  • Added .github/workflows/release-docker.yml, decoupled from the existing release.yml (binary release):
    • Triggers: push of any tag matching v*.*.*, or manual workflow_dispatch (with optional tag input);
    • Enforces that CHANGELOG/<tag>.md exists, keeping the release process consistent with release.yml;
    • Enables multi-arch builds via docker/setup-qemu-action + docker/setup-buildx-action, producing linux/amd64 and linux/arm64 images in parallel;
    • Uses docker/metadata-action to auto-generate semver tags (:0.0.10, :0.0, :0); the latest=auto flavor skips pre-release suffixes (e.g. -rc, -beta);
    • Injects OCI metadata: org.opencontainers.image.{title,description,source,licenses,revision,created};
    • Enables GHA layer caching (cache-from: type=gha / cache-to: type=gha,mode=max) so subsequent builds reuse cached layers in seconds;
    • Logs in to ghcr.io directly with ${{ secrets.GITHUB_TOKEN }}no extra PAT needed;
    • After build/push, calls gh api to PATCH either /users/{owner}/packages/container/{repo} or /orgs/{owner}/packages/container/{repo} to flip the package visibility to public; continue-on-error: true ensures that a permission failure never blocks the image push.
  • Added .dockerignore: aligned with .gitignore to exclude .git, .github, node_modules, dist, logs, *.db, the deprecated web/air / web/berry / web/default themes, and other build artifacts.

📚 Documentation

  • Added a new "🐳 Docker 部署" section to README.md, placed between "手动部署" and "多机部署", covering:
    • Image address reference (latest / pinned version / major);
    • Mount-point convention (/app/config for config, /app/data for data);
    • Quick start (SQLite single-file) and .env configuration example;
    • Switching to MySQL / PostgreSQL via SQL_DSN, and three equivalent ways to change the listen port (-e / .env / CLI arg);
    • A full CLI argument table (--port, --log-dir, --env, --version, --help);
    • A complete docker-compose.yml example, debug-mode entry, and upgrade workflow.

⚠️ Upgrade Notes

  • This is a purely engineering / CI / documentation release — no backend code changes, no database migration, safe to upgrade.
  • Existing binary users are completely unaffected: the binary deployment flow is unchanged.
  • After the first Docker image is published, please verify on the GitHub repo's Packages page that the package is public (the CI attempts this automatically; some organization repositories may require a maintainer to confirm).
  • docker-compose users should run docker compose pull && docker compose up -d to pull the new image and restart.

v0.0.9

Choose a tag to compare

@github-actions github-actions released this 03 Sep 11:53

v0.0.9 — 套餐订阅支付流程全面优化

重构「套餐 → 下单 → 支付 → 兑换」整条链路:套餐界面支持微信/支付宝切换并展示官方品牌色图标,订单界面不再跳转而是选择支付方式直接发起支付,兑换成功后以绿色内联提示替代弹窗;同时后端新增支付通道状态查询与「重新支付已有订单」接口,并以 pay.status 为前端提供明确的下单结果反馈,未开通支付通道时不再错误生成订单。

中文

✨ 新增功能

后端(/api)

  • 新增公开接口 GET /api/payment/status:返回所有支付通道(微信 / 支付宝 / 银行)的启用状态与 any_enabled 汇总,前端据此判断是否展示购买入口或直接提示管理员先配置支付。
  • 新增接口 POST /api/order/self/:id/pay:用户可对自己名下的未支付订单重新发起支付,并可通过 pay_method 切换支付方式;返回结构复用下单接口,前端可直接接入二维码/转账弹窗。
  • CreatePlanOrder / PayMyOrder 返回的 pay 对象新增 status 字段:取值 success(二维码 / 转账说明已就绪)或 warning(通道未注册 / 未启用 / SDK 调用失败,附 warning 文案),供前端明确区分「可以支付」与「配置有问题」,不再靠 pay_url 是否为空来猜测。
  • 未开通任何支付通道时不再创建订单:下单 / 重新支付请求会直接返回「系统尚未开通任何支付通道,请设置后开启支付」,订单不落库,避免订单列表出现无效记录。
  • 抽取 buildPayInfo() 辅助函数,统一 CreatePlanOrderPayMyOrder 的支付参数拼装逻辑,消除重复代码。

套餐界面(Plans)

  • 点击「立即订阅 / 升级」前预检支付通道:未启用任何通道时弹出 toast 提示且不打开确认弹窗,杜绝「确认后才发现无法支付」的体验。
  • 购买 / 升级确认弹窗内新增支付方式选择器:微信支付(IconWechatpay)与支付宝支付(IconAlipayCircle),使用各自官方品牌色(微信 #07C160、支付宝 #1677FF),用户可点击切换,点击「确认购买 / 确认升级」时携带所选 pay_method 提交。
  • 支付弹窗随所选方式动态切换:标题与提示文案(「微信扫码支付 / 支付宝扫码支付」)跟随用户选择变化。
  • 根据 pay.status 驱动行为success 时展示二维码弹窗(或银行转账说明);warning 时仅弹出错误 toast,不再跳转到订单列表页

订单界面(Orders)

  • 「支付」按钮不再跳转套餐页:改为弹出「选择支付方式」弹窗,选中后调用 POST /api/order/self/:id/pay 对既有未支付订单直接发起支付。
  • 支付方式选择器改用 Arco 官方品牌图标:微信 / 支付宝图标以官方色渲染,选中项高亮描边并显示同色勾选。
  • 根据 pay.status 驱动展示success 时展示二维码或转账信息弹窗;warning 时弹出错误提示,不再出现误导性的「跳转支付中」文案或强制跳转。

兑换界面(Redeem)

  • 移除「兑换成功」弹窗:原弹窗含「前往套餐」按钮,现改为在输入框下方以绿色内联文本展示成功提示(与错误提示同构),兑换成功后立即重新拉取「当前额度 / 已使用」数据,额度数字动态递增并带轻微放大动效。

🐛 问题修复

  • 修复套餐界面支付图标不显示Plans.vue 未显式 import IconWechatpay / IconAlipayCircle,Arco 的 SVG 图标不会像组件库那样全局注册,导致 icon-wechatpay / icon-alipay-circle 渲染为空白;现已显式导入。
  • 修复支付方式勾选符号位于右上角:将勾选符号从 position: absolute 角落定位改为 flex 垂直居中,与按钮内图标、名称对齐。
  • 修复「无支付通道」时错误生成订单并跳转订单页:此前即使未配置任何支付通道也会创建订单并跳转,现改为后端拒绝创建、前端仅弹错误提示。

📚 文档

  • 为多语言 README 补充在线 Demo 链接(英 / 繁中 / 日 / 俄 / 韩 / 阿 / 德)。

⚠️ 升级注意事项

  • 本次为代码 / 前端改动,无数据库迁移,可直接升级。
  • 前端已新增依赖(@arco-design/web-vue/es/icon 品牌图标),建议重新执行 npm install && npm run build 后发布。
  • 若希望用户可自助「选择支付方式 + 重新支付已有订单」,请确保后台「系统设置 - 支付」中至少启用一个在线支付通道(微信 / 支付宝);否则套餐页会直接提示需先开通支付。

English

✨ New Features

Backend (/api)

  • Added public endpoint GET /api/payment/status: returns the enabled state of every payment channel (WeChat / Alipay / Bank) plus an any_enabled summary so the frontend can decide whether to show the purchase flow or prompt the admin to configure payments first.
  • Added endpoint POST /api/order/self/:id/pay: lets a user re-invoke payment for one of their own pending orders, optionally switching the pay_method; the response reuses the order-creation shape so the frontend can reuse the same QR / transfer modal.
  • Added a pay.status field to CreatePlanOrder / PayMyOrder responses: values are success (QR / transfer note is ready) or warning (channel not registered / disabled / SDK failure, with a warning message). The frontend can now tell "ready to pay" from "misconfigured" instead of guessing based on whether pay_url is empty.
  • No order is created when no payment channel is enabled: order / re-pay requests now return "No payment channel has been enabled yet" and the row is not persisted, keeping the order list clean.
  • Extracted a buildPayInfo() helper that unifies payment-parameter assembly for both CreatePlanOrder and PayMyOrder, removing duplicated code.

Plans page

  • Pre-flight payment check before opening the subscribe / upgrade flow: if no channel is enabled, a toast is shown and the confirm modal never opens — the user is never led to discover "can't pay" after confirming.
  • Added a payment-method picker inside the subscribe / upgrade confirm modals: WeChat Pay (IconWechatpay) and Alipay (IconAlipayCircle), each rendered in its official brand color (WeChat #07C160, Alipay #1677FF). Users can switch and the chosen pay_method is sent on "Confirm".
  • The payment modal adapts to the chosen method: title and tip ("WeChat QR pay / Alipay QR pay") follow the user's selection.
  • Driven by pay.status: on success it shows the QR (or bank-transfer note) modal; on warning it only shows an error toast — no more auto-redirect to the orders list.

Orders page

  • The "Pay" button no longer redirects to the plans page: it now opens a "Choose payment method" modal; after selection it calls POST /api/order/self/:id/pay to initiate payment for the existing pending order directly.
  • Payment picker uses Arco official brand icons: WeChat / Alipay icons are rendered in official colors, and the selected option is highlighted with a matching colored outline and check mark.
  • Driven by pay.status: on success it shows the QR or transfer-info modal; on warning it shows an error — the misleading "redirecting to pay…" message and forced navigation are gone.

Redeem page

  • Removed the "Redeemed!" modal: the former modal (with a "View Plans" button) is replaced by a green inline success message below the input (mirroring the error style). After redeeming, the "Current Quota / Used" data is re-fetched immediately so the number visibly increases with a subtle scale animation.

🐛 Bug Fixes

  • Fixed the payment icons not rendering on the Plans page: Plans.vue was not importing IconWechatpay / IconAlipayCircle, and Arco SVG icons are not globally registered like the component library, so <icon-wechatpay> / <icon-alipay-circle> rendered blank; the icons are now explicitly imported.
  • Fixed the check mark sitting in the top-right corner: it was absolutely positioned; it now uses flexbox to center vertically, aligned with the icon and label inside the option.
  • Fixed wrongly creating an order and redirecting when no channel is enabled: previously the backend would still create an order and the frontend would navigate away; now the backend refuses to create it and the frontend only shows an error toast.

📚 Documentation

  • Added online demo links to the multi-language READMEs (English / Traditional Chinese / Japanese / Russian / Korean / Arabic / German).

⚠️ Upgrade Notes

  • Code/frontend-only release — no database migration; safe to upgrade in place.
  • New front-end dependency on Arco brand icons (@arco-design/web-vue/es/icon); please re-run npm install && npm run build before publishing.
  • To let users self-serve "pick a payment method and re-pay an existing order", make sure at least one online channel (WeChat / Alipay) is enabled under System Settings → Payment; otherwise the Plans page will prompt that payment must be configured first.

v0.0.8

Choose a tag to compare

@github-actions github-actions released this 01 Sep 02:45

v0.0.8 — 安全加固与登录页改版

修复 3 个可导致提权 / 凭据泄露的安全问题(指定渠道越权、用户列表暴露 access_token),并全面改版登录注册页:新增《服务条款》《隐私政策》页面、注册需同意条款、补充无障碍支持;同时把 README 国际化扩展至 8 种语言,并加入"启动时校验内嵌主题"的防御性检查,避免升级后因数据库残留旧主题字段导致后台空白页。

中文

🔒 安全

  • 修复指定渠道越权漏洞(one-api#2410):此前任意已登录用户均可通过 /v1/oneapi/proxy/:channelid/*target 指定任意上游渠道,绕过分组隔离与模型白名单并触发上游凭据转发。现在该 URL 参数路径与 token 后缀路径一样,仅管理员可用。
  • 指定渠道时重新校验分组 / 模型白名单(one-api#2410):即使管理员使用指定渠道功能,转发前也会再次确认该渠道确实服务于当前用户分组与所请求模型,作为纵深防御。
  • 修复用户列表接口泄露 access_token(one-api#2425):管理员批量用户接口(GET /api/user/GET /api/user/search)不再返回用户的 access_token,避免被重放后提权为 root(现有单用户读取路径的保护保持一致)。
  • 为上述修复补充模型层辅助方法 Channel.ContainsGroup / Channel.ContainsModel 及相应回归测试。

✨ 新增功能

  • 登录 / 注册 / 找回密码 / 重置密码四个鉴权页全面改版:抽出新的 AuthLayout(带装饰性渐变光晕背景与品牌位 Logo),统一语义化标题(h1 + 副标题)与表单无障碍 aria-labelforgot-link / form-alert 等局部样式整理后视觉与交互更一致。
  • 新增 《服务条款》页面(/terms《隐私政策》页面(/privacy,使用新的 LegalLayout(顶部导航 + 法律文件版式)排版;路由免登录即可访问,并已在 router/index.js 的白名单中放行,避免跳转登录页。
  • 注册流程新增「我同意《服务条款》和《隐私政策》」勾选(带 aria-label),未勾选不可提交注册,并在条款文案上提供 /terms/privacy 直达链接。
  • 套餐页新增空状态:未配置任何套餐时展示友好的插画 + 提示文案("暂无可用套餐 / 请联系管理员配置套餐后再来查看"),替代空白网格。
  • 新增 Channel.ContainsGroup / Channel.ContainsModel 白名单辅助方法(按 , 拆分、精确匹配,空配置视为全部放行),为下游的鉴权与重校验逻辑提供复用基础。
  • 启动时校验前端主题:新增 common.ValidateEmbeddedTheme(buildFS, themesRoot, theme)common.ListEmbeddedThemes(buildFS, themesRoot) 辅助函数;main.gousing theme <name> 日志之后调用校验器,如果当前主题未被打入二进制,会输出 [ERROR] 日志并指出 web/build/<theme>/index.html 缺失、列出实际已内嵌的全部主题,并给出三种修复建议(修改 options.theme / 调整 THEME 环境变量 / 重新打包时把期望主题加入 web/THEMES)。该检查非致命,服务器仍会启动以便运维修复,但能立刻把"后台空白"问题暴露在日志里。

🐛 问题修复

  • 修复订单页多了一层 page-container 内边距导致与其它页面边距不一致的问题。
  • 修复普通用户可绕过分组隔离 / 模型白名单指定上游渠道的安全问题(详见「安全」)。
  • 修复管理员用户列表 / 搜索接口可能暴露 access_token 的安全问题(详见「安全」)。
  • router/web.go 不再静默吞掉 web/build/<theme>/index.html 读取失败的错误,改为通过 logger.SysError 打印明确错误("theme %q is not embedded ...")作为兜底,确保即使绕过启动检查也能在路由层看到失败原因。

📚 文档

  • README 国际化扩展至 8 种语言:新增德语、阿拉伯语、韩语、俄语、日语、繁体中文 README,主 README 与英文 README 的语言导航同步更新。

🔧 工程 / CI

  • 补充三组回归测试:中间件指定渠道鉴权(middleware/auth_test.go)、用户列表 access_token 脱敏(controller/user_test.go)、模型白名单辅助方法(model/channel_contains_test.go)。
  • 为内嵌主题校验辅助函数补充 5 个单元测试(common/embed_theme_test.go,含 embed_theme_testdata/ 测试资源),覆盖:列出主题、主题存在、主题缺失、空主题、根路径带尾斜杠容错。

⚠️ 升级注意事项

  • 本次为代码与前端改动,无数据库迁移,可直接升级。
  • 登录 / 注册 / 找回密码页面已改版,建议升级后清除浏览器缓存或以无痕模式验证。
  • 若你在开放注册场景下依赖「匿名指定渠道代理」能力,请注意该能力现已被移除:仅管理员可通过 URL 参数或 token 后缀指定渠道。
  • 数据库残留的旧 options.theme 会被新校验器捕获:如果你从较早版本(例如内置 default 主题的旧 one-api / one-api-pro)升级,启动日志中若出现 theme "default" is not embedded in this binary ...[ERROR],说明 MySQL options 表里仍保留着旧的 theme=default 记录。请执行 UPDATE options SET value='default-pro' WHERE \key`='theme';`(或直接删除该行)后重启即可。

English

🔒 Security

  • Fixed privilege escalation via URL-parameter channel pinning (one-api#2410): any authenticated user could previously call /v1/oneapi/proxy/:channelid/*target to pin an arbitrary upstream channel, bypassing group isolation and per-channel model allowlists while triggering upstream-credential forwarding. The URL-parameter path now requires an admin, matching the token-suffix path.
  • Re-validate group / model allowlist on pinned channels (one-api#2410): even when an admin pins a channel, the distributor now re-checks that the channel actually serves the caller's user group and requested model before forwarding — defense in depth.
  • Stopped leaking access_token in admin user APIs (one-api#2425): the admin batch endpoints (GET /api/user/, GET /api/user/search) no longer return users' access_tokens, preventing token replay that could escalate to root (consistent with the existing single-user read path).
  • Added Channel.ContainsGroup / Channel.ContainsModel model helpers and regression tests covering all of the above.

✨ New Features

  • Reworked all four auth pages — Login, Register, Password Reset, and Password Reset Confirm — around a new AuthLayout (decorative gradient-orb background + branded logo slot), unified semantic headings (h1 + subtitle) and form aria-labels; tidied up forgot-link / form-alert styles so all four pages look and behave consistently.
  • Added Terms of Service (/terms) and Privacy Policy (/privacy) pages rendered via a new LegalLayout (top nav + legal document layout). Both routes are whitelisted in the router so visitors can reach them without first logging in.
  • Registration now requires checking "I agree to the Terms of Service and Privacy Policy" (with aria-label) before submitting, and the copy includes direct /terms / /privacy links.
  • Added an empty state to the Plans page so users see a friendly illustration + hint ("暂无可用套餐 / 请联系管理员配置套餐后再来查看") instead of a blank grid when no plans are configured.
  • Added Channel.ContainsGroup / Channel.ContainsModel allowlist helpers (comma-split, exact match; empty config allows all), providing the reusable building block for downstream auth and re-validation logic.
  • Embedded-theme check at startup: added common.ValidateEmbeddedTheme(buildFS, themesRoot, theme) and common.ListEmbeddedThemes(buildFS, themesRoot) helpers. main.go invokes the validator right after logging using theme <name>; if the configured theme is not embedded in the binary, it emits a loud [ERROR] that names the missing web/build/<theme>/index.html, lists every theme that IS embedded, and points at three fixes (update options.theme / adjust the THEME env / add the theme to web/THEMES and rebuild). The check is non-fatal — the server still starts so the operator can fix the misconfiguration — but it puts the "blank admin page" failure mode straight into the log.

🐛 Bug Fixes

  • Removed an extra page-container padding in the Orders page so its margins match the other pages.
  • Fixed the security issue where ordinary users could bypass group isolation / model allowlists by pinning an upstream channel (see Security).
  • Fixed the security issue where admin user list / search responses could expose access_token (see Security).
  • router/web.go no longer silently swallows the error from reading web/build/<theme>/index.html; it now logs theme %q is not embedded ... via logger.SysError as a safety net, so the failure is still visible even if SetWebRouter is ever reached before the startup check (tests, future refactors).

📚 Documentation

  • README i18n now covers 8 languages: added German, Arabic, Korean, Russian, Japanese and Traditional Chinese READMEs, and updated the language navigation in both the main and English READMEs.

🔧 Tooling / CI

  • Added three groups of regression tests: middleware channel-pinning auth (middleware/auth_test.go), user-list access_token redaction (controller/user_test.go), and model allowlist helpers (model/channel_contains_test.go).
  • Added 5 unit tests for the embedded-theme validator (common/embed_theme_test.go, with synthetic fixtures in common/embed_theme_testdata/): list themes, theme present, theme missing, empty theme, and trailing-slash tolerance on the themes root.

⚠️ Upgrade Notes

  • Code/frontend-only release — no database migration; safe to upgrade in place.
  • Login / register / password-reset pages have been redesigned; please clear the browser cache or verify in an incognito window after upgrading.
  • If you relied on anonymous "pin channel by URL parameter" proxying in open-registration deployments, note that this is no longer allowed: only admins can pin a channel (via URL parameter or token suffix).
  • Stale options.theme from older releases will now be caught: when upgrading from an older one-api / one-api-pro that shipped the legacy default theme, you may see [ERROR] theme "default" is not embedded in this binary ... at startup — that means the MySQL options table still holds the old theme=default row, which overrides the env default. Run UPDATE options SET value='default-pro' WHERE \key`='theme';` (or delete the row) and restart.

v0.0.7

Choose a tag to compare

@github-actions github-actions released this 25 Aug 12:59

Full Changelog: v0.0.6...v0.0.7