管理后台
定位
面向 Owner / 管理员的企业级权限与配置中心,覆盖用户管理、API 供应商、模型配置、激活码、请求日志五大模块。
入口
访问 /admin(需管理员权限):

| 子页面 | 路径 | 主要职能 |
|---|---|---|
| 用户管理 | /admin | 创建用户、分配配额、调整角色 |
| API 供应商 | /admin/api | Gemini / OpenAI / Veo 等供应商配置 |
| 模型配置 | /admin/model-configs | 文案 / 视频 / 选品三类模型独立配置 |
| 激活码 | /admin/activation-keys | 批量生成、限额、过期 |
| 请求日志 | /admin/logs | 完整审计链 |
一、用户管理
1.1 用户字段
| 字段 | 类型 | 说明 |
|---|---|---|
username | string | 唯一用户名 |
password_hash | string | bcrypt 哈希(不存明文) |
role | enum | admin / user |
status | enum | active / disabled |
image_quota | int | 图片剩余配额 |
copy_quota | int | 文案剩余配额 |
video_quota | int | 视频剩余配额 |
created_at | datetime | 创建时间 |
last_login_at | datetime | 最后登录 |
1.2 三类配额独立计量
设计巧思
为什么三类配额分开计?
不同生成类型的成本差异极大:
- 1 次图片 ≈ $0.10(Gemini 3 Pro)
- 1 次文案 ≈ $0.02(GPT-5.5)
- 1 次视频 ≈ $1.50(Veo 3.1,8s)
如果用统一"次数",要么图片用尽视频还在浪费,要么视频用尽图片不够用。三类独立计量让运营可以精细化分配预算。
1.3 RBAC 权限
| 权限点 | admin | user |
|---|---|---|
| 自己的图片生成 | ✅ | ✅ |
| 自己的文案生成 | ✅ | ✅ |
| 自己的视频生成 | ✅ | ✅ |
| 自己的历史 | ✅ | ✅ |
| 兑换激活码 | ✅ | ✅ |
| 看其他用户数据 | ✅ | ❌ |
| 配置供应商 | ✅ | ❌ |
| 创建/禁用用户 | ✅ | ❌ |
| 生成激活码 | ✅ | ❌ |
| 查看请求日志 | ✅ | ❌ |
二、API 供应商管理
2.1 字段设计
sql
CREATE TABLE api_providers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL, -- 自定义名称
type TEXT NOT NULL, -- gemini / openai / veo
base_url TEXT NOT NULL,
api_key TEXT NOT NULL, -- 加密存储
priority INTEGER DEFAULT 1, -- 优先级(升序)
weight INTEGER DEFAULT 100, -- 同优先级权重
model_aliases TEXT, -- JSON:模型别名映射
status TEXT DEFAULT 'active',
created_at TEXT NOT NULL
);2.2 优先级 + 权重路由算法
text
请求到达 → 取所有 status=active 的供应商
→ 按 priority 升序分组
→ 取 priority 最小的组
→ 在该组内按 weight 加权随机选择
→ 调用失败 → 降级到下一 priority 组
→ 全部失败 → 返回 5032.3 多 Key 轮询场景
yaml
provider_1:
type: gemini
api_key: AIza...A
priority: 1
weight: 50
provider_2:
type: gemini
api_key: AIza...B
priority: 1
weight: 30
provider_3:
type: gemini
api_key: AIza...C
priority: 1
weight: 20
# 同 priority=1,按 50/30/20 加权随机yaml
gemini_main:
type: gemini
priority: 1
weight: 100
openai_fallback:
type: openai
priority: 2 # 降级备用
weight: 100
# 优先用 Gemini,连续失败自动降级到 OpenAI2.4 模型别名映射
允许统一前端调用,灵活后端切换:
json
{
"model_aliases": {
"NanoBananaPro": "gemini-3-pro-image-preview",
"NanoBanana2": "gemini-3.1-flash-image-preview",
"GPTImage2": "gpt-image-2"
}
}三、模型配置
文案、视频、选品三类后端代理调用模型独立配置:
json
{
"copy": {
"provider_id": "openai_main",
"model": "gpt-5.5",
"max_tokens": 4096,
"temperature": 0.7,
"system_prompt_template": "amazon_listing_v2"
},
"video": {
"provider_id": "veo_main",
"model": "veo-3.1",
"default_duration": 8,
"default_aspect": "16:9"
},
"product_analysis": {
"provider_id": "openai_main",
"model": "gpt-5.5",
"context_window": 16384,
"system_prompt_template": "selection_advisor_v2"
}
}与 api_providers 的关系
api_providers:面向图片直连模式,前端拿配置后直接调用model_configs:面向后端代理调用(文案/视频/选品需要服务端处理上下文、流式返回)
两者解耦,互不干扰。
四、激活码管理
4.1 业务设计
激活码用于充值配额,常见场景:
- 卖家团队 Owner 给员工分发
- 比赛 / 活动赠送配额
- 第三方分销
4.2 字段设计
sql
CREATE TABLE activation_keys (
id TEXT PRIMARY KEY,
code TEXT UNIQUE NOT NULL, -- HYZC-2026-IMG50-CPY30-VID10-A8D2
image_quota INTEGER DEFAULT 0,
copy_quota INTEGER DEFAULT 0,
video_quota INTEGER DEFAULT 0,
max_uses INTEGER DEFAULT 1, -- 可被多个用户兑换的次数
used_count INTEGER DEFAULT 0,
expires_at TEXT, -- NULL = 永不过期
status TEXT DEFAULT 'active',
created_at TEXT NOT NULL,
created_by TEXT NOT NULL -- admin user id
);
CREATE TABLE activation_key_redemptions (
id TEXT PRIMARY KEY,
key_id TEXT NOT NULL,
user_id TEXT NOT NULL,
redeemed_at TEXT NOT NULL,
UNIQUE(key_id, user_id) -- 同一用户不可重复兑换同一码
);4.3 批量生成
bash
# 生成 50 个激活码(每码:图 50 / 文 30 / 视频 10)
POST /api/admin/activation-keys/batch
{
"count": 50,
"image_quota": 50,
"copy_quota": 30,
"video_quota": 10,
"expires_at": "2026-12-31"
}
# 返回
[
"HYZC-2026-IMG50-CPY30-VID10-A8D2",
"HYZC-2026-IMG50-CPY30-VID10-7K3F",
...
]4.4 防重复兑换
text
用户兑换 code → 校验码状态:
1. status = active
2. expires_at > now
3. used_count < max_uses
4. (key_id, user_id) NOT IN redemptions
全部通过 → 事务:
- quotas 配额 += code.quotas
- redemptions 写记录
- activation_keys.used_count += 1任一校验失败返回明确错误码:
| HTTP | 含义 |
|---|---|
| 200 | 兑换成功 |
| 400 | 激活码格式错误 |
| 404 | 激活码不存在 |
| 409 | 您已兑换过此激活码 |
| 410 | 激活码已过期 |
| 429 | 激活码使用次数已达上限 |
五、请求日志审计
5.1 字段
sql
CREATE TABLE request_logs (
id TEXT PRIMARY KEY,
user_id TEXT,
username TEXT,
route TEXT NOT NULL, -- /api/generate/image
method TEXT NOT NULL,
model TEXT,
provider TEXT,
status_code INTEGER,
latency_ms INTEGER,
ip TEXT,
user_agent TEXT,
error TEXT, -- 失败原因
created_at TEXT NOT NULL
);
CREATE INDEX idx_logs_user ON request_logs(user_id, created_at);
CREATE INDEX idx_logs_route ON request_logs(route, created_at);5.2 审计场景
sql
-- 1. 某用户最近 7 天的所有调用
SELECT * FROM request_logs
WHERE user_id = ?
AND created_at > datetime('now', '-7 days')
ORDER BY created_at DESC;
-- 2. 某模型的成功率
SELECT model,
SUM(CASE WHEN status_code < 400 THEN 1 ELSE 0 END) AS success,
COUNT(*) AS total,
AVG(latency_ms) AS avg_latency
FROM request_logs
WHERE created_at > datetime('now', '-1 day')
GROUP BY model;
-- 3. 异常请求告警
SELECT * FROM request_logs
WHERE status_code >= 500
AND created_at > datetime('now', '-1 hour');六、安全设计
6.1 密钥保护
安全要点
- ✅
password_hash用 bcrypt(不存明文) - ✅
token_hash仅存哈希,明文 token 仅存 HttpOnly Cookie - ✅
api_key在 SQLite 中存储时考虑加密(生产部署建议加密) - ✅ Cookie 设置
SameSite=Strict + Secure + HttpOnly - ✅ SQL 全部用参数化查询,杜绝 SQL 注入
6.2 RBAC 中间件
go
// Go 后端中间件
func RequireAdmin(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := getUserFromContext(r.Context())
if user == nil {
http.Error(w, "Unauthorized", 401)
return
}
if user.Role != "admin" {
http.Error(w, "Forbidden", 403)
return
}
next(w, r)
}
}
// 路由注册
mux.HandleFunc("/api/admin/users", RequireAdmin(handleAdminUsers))