@code2rich/jpage 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +68 -0
- package/.dockerignore +8 -0
- package/.env.example +56 -0
- package/.github/workflows/ci.yml +43 -0
- package/CLAUDE.md +280 -0
- package/Dockerfile +44 -0
- package/LICENSE +21 -0
- package/README.md +433 -0
- package/README_EN.md +399 -0
- package/bin/args.js +64 -0
- package/bin/client.js +93 -0
- package/bin/commands/_shared.js +54 -0
- package/bin/commands/cat.js +23 -0
- package/bin/commands/ls.js +44 -0
- package/bin/commands/mv.js +20 -0
- package/bin/commands/rm.js +22 -0
- package/bin/commands/skills.js +70 -0
- package/bin/commands/star.js +23 -0
- package/bin/commands/tags.js +97 -0
- package/bin/commands/upload.js +84 -0
- package/bin/commands/url.js +25 -0
- package/bin/commands/whoami.js +29 -0
- package/bin/config.js +85 -0
- package/bin/jpage.js +168 -0
- package/build.js +112 -0
- package/docker-compose.yml +26 -0
- package/docs/api.md +438 -0
- package/docs/design/005-custom-modal.md +296 -0
- package/docs/design/013-file-version-history.md +324 -0
- package/docs/design/billing-system.md +600 -0
- package/docs/design/db-index-and-healthcheck.md +176 -0
- package/docs/design/loading-states.md +209 -0
- package/docs/virtual-hosting-feasibility.md +453 -0
- package/eslint.config.mjs +172 -0
- package/lib/auth-state.js +15 -0
- package/lib/categories.js +20 -0
- package/lib/crypto.js +85 -0
- package/lib/csp.js +66 -0
- package/lib/db.js +53 -0
- package/lib/dispatch.js +103 -0
- package/lib/fts.js +81 -0
- package/lib/middleware/auth.js +114 -0
- package/lib/middleware/files.js +42 -0
- package/lib/paths.js +9 -0
- package/lib/render-cache.js +48 -0
- package/lib/render.js +157 -0
- package/lib/templates.js +149 -0
- package/lib/util.js +66 -0
- package/lib/view-counts.js +59 -0
- package/lib/zip.js +192 -0
- package/logger.js +16 -0
- package/mailer.js +34 -0
- package/mcp/constants.js +16 -0
- package/mcp/resources.js +74 -0
- package/mcp/server.js +43 -0
- package/mcp/tools-categories.js +56 -0
- package/mcp/tools-content-templates.js +59 -0
- package/mcp/tools-files.js +245 -0
- package/mcp/tools-tags.js +41 -0
- package/mcp/tools-versions.js +57 -0
- package/mcp/transport.js +183 -0
- package/mcp/util.js +63 -0
- package/mcp-server.js +20 -0
- package/migrations/001_init_schema.js +25 -0
- package/migrations/002_add_share_key.js +33 -0
- package/migrations/003_add_roles_and_tokens.js +28 -0
- package/migrations/004_add_version_history.js +32 -0
- package/migrations/005_tags_starred_categories.js +49 -0
- package/migrations/006_zip_bundle.js +17 -0
- package/migrations/007_add_file_type_uploaded_by_indexes.js +7 -0
- package/migrations/008_add_fts5.js +6 -0
- package/migrations/009_add_link_visits.js +20 -0
- package/migrations/010_add_templates_system.js +34 -0
- package/migrations/011_content_templates.js +233 -0
- package/migrations/012_add_email_and_verification.js +35 -0
- package/migrations/013_add_token_encrypted.js +14 -0
- package/migrations.js +65 -0
- package/package.json +63 -0
- package/public/css/style.css +2915 -0
- package/public/index.html +855 -0
- package/public/js/api.js +22 -0
- package/public/js/app.js +94 -0
- package/public/js/components/dialog.js +106 -0
- package/public/js/components/toast.js +13 -0
- package/public/js/pages/content-templates.js +330 -0
- package/public/js/pages/home.js +1903 -0
- package/public/js/pages/landing.js +158 -0
- package/public/js/pages/login.js +175 -0
- package/public/js/pages/preview.js +713 -0
- package/public/js/theme.js +44 -0
- package/public/js/utils.js +67 -0
- package/routes/admin.js +136 -0
- package/routes/auth.js +365 -0
- package/routes/categories.js +90 -0
- package/routes/content-templates.js +215 -0
- package/routes/files/_shared.js +112 -0
- package/routes/files/associations.js +94 -0
- package/routes/files/crud.js +139 -0
- package/routes/files/detail-serve.js +178 -0
- package/routes/files/index.js +38 -0
- package/routes/files/list.js +200 -0
- package/routes/files/overwrite.js +114 -0
- package/routes/files/upload.js +204 -0
- package/routes/files/versions.js +166 -0
- package/routes/files.js +16 -0
- package/routes/skills.js +93 -0
- package/routes/tags.js +65 -0
- package/routes/tokens.js +110 -0
- package/routes/users.js +120 -0
- package/server.js +372 -0
- package/skills/jpage-content-template/SKILL.md +98 -0
- package/skills/jpage-upload/SKILL.md +247 -0
- package/skills-registry.js +135 -0
- package/templates/academic.html +41 -0
- package/templates/dark-pro.html +41 -0
- package/templates/default.html +56 -0
- package/templates/github.html +67 -0
- package/test/browser-harness.js +125 -0
- package/test/dispatch-bench.js +74 -0
- package/test/helpers/setup.js +45 -0
- package/test/integration/admin.test.js +108 -0
- package/test/integration/auth.test.js +93 -0
- package/test/integration/categories.test.js +103 -0
- package/test/integration/cli.test.js +310 -0
- package/test/integration/content-templates.test.js +147 -0
- package/test/integration/files-security.test.js +248 -0
- package/test/integration/files.test.js +139 -0
- package/test/integration/share.test.js +79 -0
- package/test/integration/skills.test.js +104 -0
- package/test/integration/tags.test.js +84 -0
- package/test/integration/tokens.test.js +89 -0
- package/test/integration/users.test.js +138 -0
- package/test/mcp-harness.js +152 -0
- package/test/perf-bench.js +108 -0
- package/test/perf-harness.js +198 -0
- package/test/run-server.sh +15 -0
- package/test/unit/cli-args.test.js +88 -0
- package/test/unit/cli-config.test.js +89 -0
- package/test/unit/crypto.test.js +100 -0
- package/test/unit/fts.test.js +52 -0
- package/test/unit/render-cache.test.js +76 -0
- package/test/unit/util.test.js +81 -0
- package/test/unit/zip.test.js +164 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
# 即页计费体系设计文档
|
|
2
|
+
|
|
3
|
+
> 版本:v2.0 | 日期:2026-06-10 | 状态:设计中
|
|
4
|
+
>
|
|
5
|
+
> v2.0 变更:移除在线支付集成,改为激活码(赞助解锁)模式。零资质门槛,验证付费需求后再接入正式支付。
|
|
6
|
+
|
|
7
|
+
## 1. 概述
|
|
8
|
+
|
|
9
|
+
为即页引入多层级套餐体系,通过 **存储容量、文件数量、API 调用量、功能权限** 等维度区分 Free / Pro / Max 三个套餐。当前采用 **激活码** 模式——管理员生成激活码,用户输入后解锁对应套餐,以此验证付费需求,后续再接入在线支付。
|
|
10
|
+
|
|
11
|
+
### 1.1 设计目标
|
|
12
|
+
|
|
13
|
+
- **零资质门槛**:激活码模式无需商户资质,立刻可用
|
|
14
|
+
- **验证需求**:通过激活码分发数量观察真实付费意愿
|
|
15
|
+
- **低门槛体验**:Free 版足够个人体验,降低获客成本
|
|
16
|
+
- **渐进演进**:激活码模式的数据结构和配额检查机制可无缝迁移到在线支付
|
|
17
|
+
|
|
18
|
+
### 1.2 名词定义
|
|
19
|
+
|
|
20
|
+
| 术语 | 含义 |
|
|
21
|
+
|------|------|
|
|
22
|
+
| Plan(套餐) | Free / Pro / Max 三档,定义各类配额上限 |
|
|
23
|
+
| Activation Code(激活码) | 管理员生成的一次性兑换码,用户输入后升级套餐 |
|
|
24
|
+
| Quota(配额) | 某维度的使用量上限(如存储 50MB) |
|
|
25
|
+
| Usage(用量) | 用户在某周期内的实际消耗量 |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. 套餐定义
|
|
30
|
+
|
|
31
|
+
### 2.1 价格(参考价,激活码模式不实际收费)
|
|
32
|
+
|
|
33
|
+
| 套餐 | 参考月价 | 参考年价(约 8 折) | 说明 |
|
|
34
|
+
|------|---------|-------------------|------|
|
|
35
|
+
| **Free** | 免费 | 免费 | 注册即得 |
|
|
36
|
+
| **Pro** | ¥9/月 | ¥86/年 | 激活码兑换,有效期按码设定 |
|
|
37
|
+
| **Max** | ¥29/月 | ¥278/年 | 激活码兑换,有效期按码设定 |
|
|
38
|
+
|
|
39
|
+
### 2.2 配额对比
|
|
40
|
+
|
|
41
|
+
| 维度 | Free | Pro | Max |
|
|
42
|
+
|------|------|-----|-----|
|
|
43
|
+
| 存储容量 | 50 MB | 500 MB | 10 GB |
|
|
44
|
+
| 文件个数 | 20 | 500 | 不限 |
|
|
45
|
+
| 单文件大小 | 2 MB | 10 MB | 50 MB |
|
|
46
|
+
| 版本历史(每文件) | 2 个 | 10 个 | 30 个 |
|
|
47
|
+
| MCP/API 日调用量 | 100 次 | 1,000 次 | 10,000 次 |
|
|
48
|
+
| 上传频率(15 min 窗口) | 10 次 | 30 次 | 100 次 |
|
|
49
|
+
| API Token 数 | 1 | 5 | 20 |
|
|
50
|
+
| 批量操作(单次上限) | 不支持 | 20 个 | 100 个 |
|
|
51
|
+
| ZIP 上传 | 不支持 | 支持 | 支持 |
|
|
52
|
+
| 全文搜索 | 不支持 | 支持 | 支持 |
|
|
53
|
+
|
|
54
|
+
### 2.3 功能对比
|
|
55
|
+
|
|
56
|
+
| 功能 | Free | Pro | Max |
|
|
57
|
+
|------|------|-----|-----|
|
|
58
|
+
| HTML / Markdown 预览 | ✓ | ✓ | ✓ |
|
|
59
|
+
| 短链接分享 | ✓(随机 8 位) | ✓(可自定义别名) | ✓(可自定义别名) |
|
|
60
|
+
| 公开 / 私有控制 | ✓ | ✓ | ✓ |
|
|
61
|
+
| 标签 / 分类 / 收藏 | ✓ | ✓ | ✓ |
|
|
62
|
+
| 在线编辑 + 实时预览 | ✓ | ✓ | ✓ |
|
|
63
|
+
| Markdown 渲染增强(代码高亮 / KaTeX / Mermaid) | ✓ | ✓ | ✓ |
|
|
64
|
+
| 渲染模板 | 仅 default | 4 种内置模板 | 内置 + 自定义上传模板 |
|
|
65
|
+
| 内容模板市场 | 只读使用 | 可上传私有模板 | 可上传 + 市场管理 |
|
|
66
|
+
| 分享链接有效期 | 永久 | 可设过期时间 | 可设过期时间 + 密码保护 |
|
|
67
|
+
| 自定义域名 | ✗ | ✗ | ✓ |
|
|
68
|
+
| 数据备份 / 恢复 | ✗ | 导出自己的文件 | 全量备份 / 恢复 |
|
|
69
|
+
| 访问统计 | 近 7 天概览 | 近 30 天详情 | 近 90 天详情 + CSV 导出 |
|
|
70
|
+
| 子账号 | ✗ | ✗ | 最多 5 人 |
|
|
71
|
+
| 优先支持 | ✗ | ✗ | 优先工单 |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 3. 数据库设计
|
|
76
|
+
|
|
77
|
+
### 3.1 新增表
|
|
78
|
+
|
|
79
|
+
#### `plans` — 套餐定义表
|
|
80
|
+
|
|
81
|
+
```sql
|
|
82
|
+
CREATE TABLE IF NOT EXISTS plans (
|
|
83
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
|
+
name TEXT UNIQUE NOT NULL, -- 'free', 'pro', 'max'
|
|
85
|
+
display_name TEXT NOT NULL, -- '免费版', 'Pro', 'Max'
|
|
86
|
+
price_monthly INTEGER NOT NULL DEFAULT 0, -- 参考月价(单位:分)
|
|
87
|
+
price_yearly INTEGER NOT NULL DEFAULT 0, -- 参考年价(单位:分)
|
|
88
|
+
storage_mb INTEGER NOT NULL, -- 存储上限 MB
|
|
89
|
+
max_files INTEGER, -- 文件数上限(NULL = 不限)
|
|
90
|
+
max_file_size_mb INTEGER NOT NULL, -- 单文件上限 MB
|
|
91
|
+
max_versions INTEGER, -- 版本数上限(NULL = 不限)
|
|
92
|
+
max_api_calls_daily INTEGER NOT NULL, -- API 日调用量上限
|
|
93
|
+
max_upload_rate INTEGER NOT NULL, -- 15 min 上传次数上限
|
|
94
|
+
max_tokens INTEGER NOT NULL, -- API Token 数上限
|
|
95
|
+
max_batch_ops INTEGER NOT NULL DEFAULT 0, -- 批量操作上限(0 = 不支持)
|
|
96
|
+
allow_zip INTEGER NOT NULL DEFAULT 0, -- ZIP 上传
|
|
97
|
+
allow_search INTEGER NOT NULL DEFAULT 0, -- 全文搜索
|
|
98
|
+
allow_custom_template INTEGER NOT NULL DEFAULT 0, -- 自定义渲染模板
|
|
99
|
+
allow_custom_domain INTEGER NOT NULL DEFAULT 0, -- 自定义域名
|
|
100
|
+
allow_share_expiry INTEGER NOT NULL DEFAULT 0, -- 分享链接有效期
|
|
101
|
+
allow_share_password INTEGER NOT NULL DEFAULT 0, -- 分享链接密码
|
|
102
|
+
allow_template_upload INTEGER NOT NULL DEFAULT 0, -- 上传内容模板
|
|
103
|
+
allow_template_manage INTEGER NOT NULL DEFAULT 0, -- 管理内容模板市场
|
|
104
|
+
allow_backup INTEGER NOT NULL DEFAULT 0, -- 数据备份
|
|
105
|
+
stats_days INTEGER NOT NULL DEFAULT 7, -- 统计天数
|
|
106
|
+
max_sub_accounts INTEGER NOT NULL DEFAULT 0, -- 子账号数上限
|
|
107
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**初始数据**:
|
|
112
|
+
|
|
113
|
+
```sql
|
|
114
|
+
INSERT INTO plans (name, display_name, price_monthly, price_yearly,
|
|
115
|
+
storage_mb, max_files, max_file_size_mb, max_versions,
|
|
116
|
+
max_api_calls_daily, max_upload_rate, max_tokens, max_batch_ops,
|
|
117
|
+
allow_zip, allow_search, allow_custom_template, allow_custom_domain,
|
|
118
|
+
allow_share_expiry, allow_share_password, allow_template_upload,
|
|
119
|
+
allow_template_manage, allow_backup, stats_days, max_sub_accounts)
|
|
120
|
+
VALUES
|
|
121
|
+
('free', '免费版', 0, 0,
|
|
122
|
+
50, 20, 2, 2,
|
|
123
|
+
100, 10, 1, 0,
|
|
124
|
+
0, 0, 0, 0,
|
|
125
|
+
0, 0, 0,
|
|
126
|
+
0, 0, 7, 0),
|
|
127
|
+
('pro', 'Pro', 900, 8600,
|
|
128
|
+
500, 500, 10, 10,
|
|
129
|
+
1000, 30, 5, 20,
|
|
130
|
+
1, 1, 0, 0,
|
|
131
|
+
1, 0, 1,
|
|
132
|
+
0, 1, 30, 0),
|
|
133
|
+
('max', 'Max', 2900, 27800,
|
|
134
|
+
10240, NULL, 50, 30,
|
|
135
|
+
10000, 100, 20, 100,
|
|
136
|
+
1, 1, 1, 1,
|
|
137
|
+
1, 1, 1,
|
|
138
|
+
1, 1, 90, 5);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### `activation_codes` — 激活码表
|
|
142
|
+
|
|
143
|
+
```sql
|
|
144
|
+
CREATE TABLE IF NOT EXISTS activation_codes (
|
|
145
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
146
|
+
code TEXT UNIQUE NOT NULL, -- 激活码(如 'JP-A1B2C3D4E5')
|
|
147
|
+
plan_id INTEGER NOT NULL REFERENCES plans(id),
|
|
148
|
+
duration_days INTEGER NOT NULL, -- 有效天数(30=月, 365=年)
|
|
149
|
+
status TEXT NOT NULL DEFAULT 'active', -- active / used / expired / revoked
|
|
150
|
+
created_by INTEGER REFERENCES users(id), -- 创建者(admin)
|
|
151
|
+
used_by INTEGER REFERENCES users(id), -- 使用者
|
|
152
|
+
used_at TEXT, -- 使用时间
|
|
153
|
+
expires_at TEXT, -- 激活码本身过期时间(未使用则作废)
|
|
154
|
+
note TEXT, -- 备注(如「张三 赞助 Pro 年付」)
|
|
155
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
CREATE INDEX idx_activation_codes_code ON activation_codes(code);
|
|
159
|
+
CREATE INDEX idx_activation_codes_status ON activation_codes(status);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `user_plans` — 用户套餐记录表
|
|
163
|
+
|
|
164
|
+
```sql
|
|
165
|
+
CREATE TABLE IF NOT EXISTS user_plans (
|
|
166
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
167
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
168
|
+
plan_id INTEGER NOT NULL REFERENCES plans(id),
|
|
169
|
+
source TEXT NOT NULL, -- 'activation_code' / 'admin_grant' / 'payment'
|
|
170
|
+
source_id INTEGER, -- activation_codes.id 或 payments.id
|
|
171
|
+
status TEXT NOT NULL DEFAULT 'active', -- active / expired
|
|
172
|
+
activated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
173
|
+
expires_at TEXT NOT NULL, -- 套餐到期时间
|
|
174
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
CREATE INDEX idx_user_plans_user ON user_plans(user_id, status);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### `usage_daily` — 日用量追踪表
|
|
181
|
+
|
|
182
|
+
```sql
|
|
183
|
+
CREATE TABLE IF NOT EXISTS usage_daily (
|
|
184
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
185
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
186
|
+
date TEXT NOT NULL, -- '2026-06-10'
|
|
187
|
+
api_calls INTEGER NOT NULL DEFAULT 0, -- 当日 API 调用次数
|
|
188
|
+
upload_count INTEGER NOT NULL DEFAULT 0, -- 当日上传次数
|
|
189
|
+
UNIQUE(user_id, date)
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
CREATE INDEX idx_usage_daily_user_date ON usage_daily(user_id, date);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### `usage_storage` — 存储用量快照
|
|
196
|
+
|
|
197
|
+
```sql
|
|
198
|
+
CREATE TABLE IF NOT EXISTS usage_storage (
|
|
199
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
200
|
+
user_id INTEGER UNIQUE NOT NULL REFERENCES users(id),
|
|
201
|
+
total_bytes INTEGER NOT NULL DEFAULT 0, -- 当前已用存储字节数
|
|
202
|
+
file_count INTEGER NOT NULL DEFAULT 0, -- 当前文件数
|
|
203
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
204
|
+
);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 3.2 修改现有表
|
|
208
|
+
|
|
209
|
+
#### `users` 表新增字段
|
|
210
|
+
|
|
211
|
+
```sql
|
|
212
|
+
ALTER TABLE users ADD COLUMN plan_id INTEGER NOT NULL DEFAULT 1 REFERENCES plans(id);
|
|
213
|
+
ALTER TABLE users ADD COLUMN plan_expires_at TEXT; -- 套餐到期时间(NULL=永不过期,free 用户为 NULL)
|
|
214
|
+
ALTER TABLE users ADD COLUMN storage_bytes INTEGER NOT NULL DEFAULT 0;
|
|
215
|
+
ALTER TABLE users ADD COLUMN file_count INTEGER NOT NULL DEFAULT 0;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
> `plan_id` 和 `plan_expires_at` 冗余到 users 表,方便快速判断当前套餐,无需每次 JOIN。注册时默认 `plan_id = 1`(free)、`plan_expires_at = NULL`。
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 4. 激活码机制
|
|
223
|
+
|
|
224
|
+
### 4.1 整体流程
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
用户看到定价页 → 点击「赞助解锁」→ 引导联系管理员(显示联系方式/二维码)
|
|
228
|
+
↓
|
|
229
|
+
用户完成赞助 → 管理员在后台生成激活码(指定套餐 + 有效期)
|
|
230
|
+
↓
|
|
231
|
+
管理员将激活码发送给用户
|
|
232
|
+
↓
|
|
233
|
+
用户在「兑换激活码」页面输入 → 验证 → 升级套餐
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 4.2 激活码格式
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
JP-{10位大写字母数字}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
示例:`JP-A1B2C3D4E5`
|
|
243
|
+
|
|
244
|
+
生成规则:`'JP-' + crypto.randomBytes(6).toString('hex').toUpperCase()`
|
|
245
|
+
|
|
246
|
+
### 4.3 兑换逻辑
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
POST /api/activation/redeem { code: 'JP-A1B2C3D4E5' }
|
|
250
|
+
1. 查找激活码(status=active)
|
|
251
|
+
2. 检查激活码是否过期(expires_at)
|
|
252
|
+
3. 检查用户当前套餐是否低于目标套餐(不允许降级兑换)
|
|
253
|
+
4. 更新 activation_codes: status=used, used_by=userId, used_at=now
|
|
254
|
+
5. 创建 user_plans 记录
|
|
255
|
+
6. 更新 users: plan_id, plan_expires_at
|
|
256
|
+
7. 返回套餐信息
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 4.4 套餐到期与续期
|
|
260
|
+
|
|
261
|
+
| 场景 | 处理 |
|
|
262
|
+
|------|------|
|
|
263
|
+
| 激活码到期 | 定时任务检测 `plan_expires_at < now()`,降级为 free |
|
|
264
|
+
| 激活码叠加(已有 Pro,再兑 Pro) | 新到期时间 = max(当前到期时间, now) + duration_days |
|
|
265
|
+
| 升级兑换(已有 Pro,兑 Max) | 立即升级,新到期时间 = now + duration_days(Pro 剩余时间不折算) |
|
|
266
|
+
| 降级兑换(已有 Max,兑 Pro) | 拒绝,提示「当前套餐已高于目标套餐」 |
|
|
267
|
+
|
|
268
|
+
### 4.5 降级处理
|
|
269
|
+
|
|
270
|
+
降级为 free 时:
|
|
271
|
+
- **不删除文件**,超限文件标记为 `read_only`(可下载,不可编辑/覆盖)
|
|
272
|
+
- **API Token 保留前 1 个**,多余的禁用(不删除)
|
|
273
|
+
- **版本历史保留前 2 个**,多余的标记归档
|
|
274
|
+
- 用户可随时兑换新激活码恢复
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 5. 配额检查机制
|
|
279
|
+
|
|
280
|
+
### 5.1 检查流程
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
用户发起操作(上传 / API 调用 / Token 创建 …)
|
|
284
|
+
↓
|
|
285
|
+
quota.check(userId, dimension)
|
|
286
|
+
↓
|
|
287
|
+
┌─ 读取 users.plan_id → JOIN plans 获取配额上限
|
|
288
|
+
│ (套餐信息缓存在内存,plan_id 变更时刷新)
|
|
289
|
+
│
|
|
290
|
+
├─ 检查 users.plan_expires_at(过期则先降级)
|
|
291
|
+
│
|
|
292
|
+
├─ 读取当前用量(usage_daily / usage_storage)
|
|
293
|
+
│
|
|
294
|
+
└─ 比较:用量 < 配额?
|
|
295
|
+
↓
|
|
296
|
+
是 → 放行,用量 +1
|
|
297
|
+
否 → 返回 403 QuotaExceeded + 提示升级
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 5.2 检查维度与时机
|
|
301
|
+
|
|
302
|
+
| 维度 | 检查时机 | 数据来源 |
|
|
303
|
+
|------|---------|---------|
|
|
304
|
+
| 存储容量 | 文件上传前 | `usage_storage.total_bytes` + 新文件 size |
|
|
305
|
+
| 文件个数 | 文件上传前 | `usage_storage.file_count` |
|
|
306
|
+
| 单文件大小 | 文件上传前 | `request.file.size` vs `plans.max_file_size_mb` |
|
|
307
|
+
| API 日调用 | 每次 MCP/API 调用 | `usage_daily.api_calls` WHERE date=today |
|
|
308
|
+
| 上传频率 | 上传请求时 | `usage_daily.upload_count` WHERE date=today |
|
|
309
|
+
| 版本数 | 覆盖上传前 | `SELECT COUNT(*) FROM file_versions` |
|
|
310
|
+
| Token 数 | 创建 Token 前 | `SELECT COUNT(*) FROM tokens WHERE user_id=?` |
|
|
311
|
+
| 功能权限 | 功能入口 | `plans.allow_*` 字段直接判断 |
|
|
312
|
+
|
|
313
|
+
### 5.3 用量更新策略
|
|
314
|
+
|
|
315
|
+
| 指标 | 更新方式 |
|
|
316
|
+
|------|---------|
|
|
317
|
+
| API 调用计数 | 内存缓冲 + 每 60 秒批量 flush 到 `usage_daily` |
|
|
318
|
+
| 上传计数 | 上传成功后 `usage_daily.upload_count + 1` |
|
|
319
|
+
| 存储用量 | 增量更新:上传成功 `+size`,删除文件 `-size` |
|
|
320
|
+
| 文件计数 | 增量更新:上传成功 +1,删除文件 -1 |
|
|
321
|
+
| 日用量清理 | 保留 90 天,超期记录定时删除 |
|
|
322
|
+
|
|
323
|
+
> API 调用计数使用内存缓冲,避免每次请求都写 SQLite。Node 进程重启时会丢失当批未 flush 的计数(可接受,最多少算 60 秒的调用量)。
|
|
324
|
+
|
|
325
|
+
### 5.4 quota 模块 API 设计
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
// quota.js
|
|
329
|
+
module.exports = {
|
|
330
|
+
async check(userId, dimension, extra = {}),
|
|
331
|
+
async increment(userId, dimension, value = 1),
|
|
332
|
+
async getUsageOverview(userId),
|
|
333
|
+
};
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
支持的 `dimension` 值:
|
|
337
|
+
|
|
338
|
+
| dimension | 说明 |
|
|
339
|
+
|-----------|------|
|
|
340
|
+
| `storage` | 存储容量(extra: { bytesToAdd }) |
|
|
341
|
+
| `files` | 文件个数 |
|
|
342
|
+
| `file_size` | 单文件大小(extra: { fileSize }) |
|
|
343
|
+
| `api_calls` | API 日调用 |
|
|
344
|
+
| `upload_rate` | 上传频率 |
|
|
345
|
+
| `versions` | 版本数(extra: { fileId }) |
|
|
346
|
+
| `tokens` | Token 数 |
|
|
347
|
+
| `feature:zip` | ZIP 上传权限 |
|
|
348
|
+
| `feature:search` | 全文搜索权限 |
|
|
349
|
+
| `feature:custom_domain` | 自定义域名权限 |
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 6. REST API 新增
|
|
354
|
+
|
|
355
|
+
### 6.1 套餐与用量
|
|
356
|
+
|
|
357
|
+
| 方法 | 路径 | 说明 | 鉴权 |
|
|
358
|
+
|------|------|------|------|
|
|
359
|
+
| GET | `/api/plans` | 列出所有套餐及参考价 | 无 |
|
|
360
|
+
| GET | `/api/plans/current` | 当前用户的套餐 + 各维度用量 | 登录 |
|
|
361
|
+
|
|
362
|
+
**`GET /api/plans/current` 响应示例**:
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"plan": {
|
|
367
|
+
"name": "pro",
|
|
368
|
+
"displayName": "Pro",
|
|
369
|
+
"storageMb": 500,
|
|
370
|
+
"maxFiles": 500
|
|
371
|
+
},
|
|
372
|
+
"usage": {
|
|
373
|
+
"storageMb": 120.5,
|
|
374
|
+
"fileCount": 87,
|
|
375
|
+
"apiCallsToday": 156,
|
|
376
|
+
"tokens": 3
|
|
377
|
+
},
|
|
378
|
+
"expiresAt": "2026-07-10T00:00:00.000Z"
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 6.2 激活码(用户端)
|
|
383
|
+
|
|
384
|
+
| 方法 | 路径 | 说明 | 鉴权 |
|
|
385
|
+
|------|------|------|------|
|
|
386
|
+
| POST | `/api/activation/redeem` | 兑换激活码 | 登录 |
|
|
387
|
+
|
|
388
|
+
**请求体**:`{ "code": "JP-A1B2C3D4E5" }`
|
|
389
|
+
|
|
390
|
+
**响应**:
|
|
391
|
+
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"success": true,
|
|
395
|
+
"plan": { "name": "pro", "displayName": "Pro" },
|
|
396
|
+
"expiresAt": "2026-07-10T00:00:00.000Z"
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 6.3 激活码管理(管理员)
|
|
401
|
+
|
|
402
|
+
| 方法 | 路径 | 说明 | 鉴权 |
|
|
403
|
+
|------|------|------|------|
|
|
404
|
+
| GET | `/api/admin/codes` | 列出所有激活码(分页、筛选状态) | admin |
|
|
405
|
+
| POST | `/api/admin/codes` | 批量生成激活码 | admin |
|
|
406
|
+
| POST | `/api/admin/codes/revoke/:id` | 撤销激活码(未使用的) | admin |
|
|
407
|
+
| GET | `/api/admin/codes/stats` | 激活码统计(已用/未用/过期数) | admin |
|
|
408
|
+
|
|
409
|
+
**`POST /api/admin/codes` 请求体**:
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"planId": 2,
|
|
414
|
+
"durationDays": 30,
|
|
415
|
+
"count": 5,
|
|
416
|
+
"expiresInDays": 90,
|
|
417
|
+
"note": "张三 赞助 Pro 月付"
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
> `count` 批量生成数量(1-50);`expiresInDays` 激活码本身的有效期(未使用则作废)。
|
|
422
|
+
|
|
423
|
+
**响应**:
|
|
424
|
+
|
|
425
|
+
```json
|
|
426
|
+
{
|
|
427
|
+
"codes": [
|
|
428
|
+
{ "code": "JP-A1B2C3D4E5", "planId": 2, "durationDays": 30 },
|
|
429
|
+
{ "code": "JP-F6G7H8I9J0", "planId": 2, "durationDays": 30 }
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 6.4 管理员直接授权
|
|
435
|
+
|
|
436
|
+
| 方法 | 路径 | 说明 | 鉴权 |
|
|
437
|
+
|------|------|------|------|
|
|
438
|
+
| POST | `/api/admin/grant` | 直接给用户授权套餐 | admin |
|
|
439
|
+
|
|
440
|
+
**请求体**:
|
|
441
|
+
|
|
442
|
+
```json
|
|
443
|
+
{
|
|
444
|
+
"userId": 5,
|
|
445
|
+
"planId": 2,
|
|
446
|
+
"durationDays": 365,
|
|
447
|
+
"note": "内测用户赠送"
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
> 用于内测用户、合作伙伴赠送等场景,不经过激活码。
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 7. 前端改动
|
|
456
|
+
|
|
457
|
+
### 7.1 新增页面
|
|
458
|
+
|
|
459
|
+
#### 定价页(`#/pricing`)
|
|
460
|
+
|
|
461
|
+
- 三栏对比表格(Free / Pro / Max)
|
|
462
|
+
- Pro/Max 的操作按钮为「赞助解锁」,点击弹出联系方式(微信二维码 / 邮箱)
|
|
463
|
+
- 底部「已有激活码?点此兑换」链接
|
|
464
|
+
|
|
465
|
+
#### 兑换激活码弹窗
|
|
466
|
+
|
|
467
|
+
- 输入框 + 兑换按钮
|
|
468
|
+
- 成功/失败提示
|
|
469
|
+
- 显示新的套餐信息和到期时间
|
|
470
|
+
|
|
471
|
+
#### 订阅管理(用户设置内)
|
|
472
|
+
|
|
473
|
+
- 当前套餐显示 + 各维度用量进度条
|
|
474
|
+
- 到期时间倒计时
|
|
475
|
+
- 「兑换激活码」按钮
|
|
476
|
+
- 套餐历史记录列表
|
|
477
|
+
|
|
478
|
+
### 7.2 管理员新增
|
|
479
|
+
|
|
480
|
+
#### 激活码管理(Settings 内)
|
|
481
|
+
|
|
482
|
+
- 生成激活码表单(选套餐、选时长、数量、备注)
|
|
483
|
+
- 激活码列表(状态筛选:未用/已用/过期/已撤销)
|
|
484
|
+
- 撤销操作
|
|
485
|
+
- 统计概览
|
|
486
|
+
|
|
487
|
+
### 7.3 修改页面
|
|
488
|
+
|
|
489
|
+
| 页面 | 改动 |
|
|
490
|
+
|------|------|
|
|
491
|
+
| **主页** | 超限时上传按钮灰化 + tooltip 提示升级;右上角显示当前套餐 badge |
|
|
492
|
+
| **上传** | 超限文件上传返回 403 时弹出升级引导 |
|
|
493
|
+
| **预览页** | 版本历史超出配额时提示;统计图表按套餐显示天数 |
|
|
494
|
+
| **设置** | Token 创建超限时提示;新增「套餐管理」入口 |
|
|
495
|
+
| **落地页** | Header 新增「定价」链接;CTA 按钮引导注册 |
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## 8. 定时任务
|
|
500
|
+
|
|
501
|
+
| 任务 | 频率 | 说明 |
|
|
502
|
+
|------|------|------|
|
|
503
|
+
| 套餐过期检查 | 每小时 | `users.plan_expires_at < now() AND plan_id > 1` → 降级为 free |
|
|
504
|
+
| 激活码过期 | 每小时 | `activation_codes.status=active AND expires_at < now()` → 标记 expired |
|
|
505
|
+
| API 用量 flush | 每 60 秒 | 内存缓冲的 API 调用计数写入 `usage_daily` |
|
|
506
|
+
| 日用量清理 | 每天 1 次 | 删除 90 天前的 `usage_daily` 记录 |
|
|
507
|
+
| 存储用量校准 | 每天 1 次 | 重新计算 `usage_storage`(防止增量误差累积) |
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## 9. 配额超限响应格式
|
|
512
|
+
|
|
513
|
+
所有配额超限返回统一格式:
|
|
514
|
+
|
|
515
|
+
```json
|
|
516
|
+
{
|
|
517
|
+
"error": "QuotaExceeded",
|
|
518
|
+
"message": "存储空间不足",
|
|
519
|
+
"dimension": "storage",
|
|
520
|
+
"current": {
|
|
521
|
+
"usedMb": 50.2,
|
|
522
|
+
"limitMb": 50
|
|
523
|
+
},
|
|
524
|
+
"upgradeHint": {
|
|
525
|
+
"plan": "pro",
|
|
526
|
+
"displayName": "Pro",
|
|
527
|
+
"priceMonthly": "¥9"
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
HTTP 状态码统一使用 **403 Forbidden**(非 429,429 用于速率限制)。
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 10. 安全考虑
|
|
537
|
+
|
|
538
|
+
| 风险 | 措施 |
|
|
539
|
+
|------|------|
|
|
540
|
+
| 激活码暴力枚举 | 格式 `JP-` + 10 位(36^10 = 3.6 万亿种),接口限流 5 次/分钟 |
|
|
541
|
+
| 激活码重复使用 | `status=used` 后不可再用,数据库 UNIQUE 约束 |
|
|
542
|
+
| 配额绕过 | 所有文件操作入口(REST API + MCP tool)统一经过 `quota.check()` |
|
|
543
|
+
| 并发超额 | 存储用量使用 SQLite 事务保证原子性 |
|
|
544
|
+
| Token 滥用 | MCP tool 调用同样计入 API 调用配额 |
|
|
545
|
+
| 管理员接口泄露 | 激活码管理 API 强制 `requireAdmin` |
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 11. 向在线支付迁移
|
|
550
|
+
|
|
551
|
+
当激活码模式验证了付费需求后,迁移到在线支付只需:
|
|
552
|
+
|
|
553
|
+
1. **新增 `payments` 表**(订单号、金额、支付方式、状态)
|
|
554
|
+
2. **`user_plans.source` 新增 `'payment'`** 值,`source_id` 指向 payments 记录
|
|
555
|
+
3. **新增支付相关 API**(创建订单、回调通知)
|
|
556
|
+
4. **激活码机制保留**,作为促销/赠品渠道
|
|
557
|
+
|
|
558
|
+
现有表结构(plans / user_plans / usage_daily / usage_storage)和 quota 模块无需改动。
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## 12. 分期实施计划
|
|
563
|
+
|
|
564
|
+
### Phase 1:配额体系(约 2-3 天)
|
|
565
|
+
|
|
566
|
+
1. 创建 migration:plans / activation_codes / user_plans / usage_daily / usage_storage 表 + users 新字段
|
|
567
|
+
2. 实现 `quota.js` 模块(含内存缓冲的 API 调用计数)
|
|
568
|
+
3. 在现有 API 入口插入配额检查(上传、Token 创建、MCP tool)
|
|
569
|
+
4. 前端配额超限提示(通用 toast)
|
|
570
|
+
|
|
571
|
+
### Phase 2:激活码(约 2-3 天)
|
|
572
|
+
|
|
573
|
+
1. 管理员激活码生成/管理 API
|
|
574
|
+
2. 用户兑换激活码 API + 到期降级定时任务
|
|
575
|
+
3. 前端定价页 + 兑换弹窗 + 管理员后台
|
|
576
|
+
4. 用量可视化(进度条)
|
|
577
|
+
|
|
578
|
+
### Phase 3:完善体验(约 2 天)
|
|
579
|
+
|
|
580
|
+
1. 降级处理(超限文件只读)
|
|
581
|
+
2. 激活码叠加/升级逻辑
|
|
582
|
+
3. 管理员直接授权
|
|
583
|
+
4. 前端套餐管理页
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## 13. 文件结构(新增/修改)
|
|
588
|
+
|
|
589
|
+
```
|
|
590
|
+
quota.js # 配额检查模块
|
|
591
|
+
migrations/
|
|
592
|
+
007_plans_and_billing.js # 套餐/激活码/用量表
|
|
593
|
+
public/
|
|
594
|
+
js/pages/pricing.js # 定价页
|
|
595
|
+
public/js/app.js # 新增路由 + 配额提示
|
|
596
|
+
public/index.html # 新增 template(定价页)
|
|
597
|
+
public/css/style.css # 定价页样式
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
> 现有文件改动:`server.js`(新增路由 + 配额中间件)、`mcp-server.js`(MCP tool 配额检查)。
|