diff --git a/_docs/sql/biz_api_token_key.sql b/_docs/sql/biz_api_token_key.sql
new file mode 100644
index 0000000..b8cf6ec
--- /dev/null
+++ b/_docs/sql/biz_api_token_key.sql
@@ -0,0 +1,3 @@
+-- token 绑定账号唯一标识 key(供转发时自动拼到 query.key)
+ALTER TABLE biz_api_token
+ ADD COLUMN `key` VARCHAR(128) NULL COMMENT '账号唯一标识' AFTER token_name;
diff --git a/admin/src/views/subscription/tokens.vue b/admin/src/views/subscription/tokens.vue
index 4385274..d23daec 100644
--- a/admin/src/views/subscription/tokens.vue
+++ b/admin/src/views/subscription/tokens.vue
@@ -59,6 +59,7 @@
+
@@ -104,6 +105,7 @@ export default {
{ title: 'ID', key: 'id', width: 70 },
{ title: '用户', key: 'user_id', width: 90 },
{ title: '套餐', key: 'plan_id', width: 90 },
+ { title: 'Key', key: 'key', minWidth: 140 },
{ title: '名称', key: 'token_name', width: 120 },
{ title: '状态', key: 'status', width: 90 },
{ title: '过期', key: 'expire_at', minWidth: 150 },
@@ -176,7 +178,7 @@ export default {
2,
'0'
)} 23:59:59`
- this.createForm = { user_id: undefined, token_name: 'default', expire_at: fmt }
+ this.createForm = { user_id: undefined, key: '', token_name: 'default', expire_at: fmt }
this.createModal = true
},
submitCreate() {
@@ -194,6 +196,7 @@ export default {
try {
const res = await tokenServer.create({
user_id: Number(uid),
+ key: this.createForm.key || null,
token_name: this.createForm.token_name || 'default',
expire_at: this.createForm.expire_at,
})
diff --git a/api/controller_admin/biz_token.js b/api/controller_admin/biz_token.js
index dfc4192..b1490b8 100644
--- a/api/controller_admin/biz_token.js
+++ b/api/controller_admin/biz_token.js
@@ -38,6 +38,7 @@ module.exports = {
id: result.row.id,
user_id: result.row.user_id,
plan_id: result.row.plan_id,
+ key: result.row.key,
token_name: result.row.token_name,
expire_at: result.row.expire_at,
plain_token: result.plain_token,
diff --git a/api/controller_custom/proxy_api.js b/api/controller_custom/proxy_api.js
index 7c17c5e..9d3876d 100644
--- a/api/controller_custom/proxy_api.js
+++ b/api/controller_custom/proxy_api.js
@@ -65,8 +65,11 @@ function buildProxyRoutes() {
ctx.fail(authResult.message || "鉴权失败");
return;
}
- // 3. 组装 query
+ // 3. 组装 query,并注入 token 对应 key(上游要求参数名为 key)
const query = { ...ctx.query };
+ if (!query.key && authResult.context && authResult.context.token_key) {
+ query.key = authResult.context.token_key;
+ }
// 4. 转发到上游
const result = await proxy.forwardRequest({
diff --git a/api/model/biz_api_token.js b/api/model/biz_api_token.js
index 20232a4..7e78965 100644
--- a/api/model/biz_api_token.js
+++ b/api/model/biz_api_token.js
@@ -18,6 +18,10 @@ module.exports = (db) => {
allowNull: false,
defaultValue: "",
},
+ key: {
+ type: Sequelize.STRING(128),
+ allowNull: true,
+ },
token_hash: {
type: Sequelize.STRING(64),
allowNull: false,
@@ -42,6 +46,6 @@ module.exports = (db) => {
underscored: true,
}
);
- // biz_api_token.sync({ force: true });
+ //biz_api_token.sync({ force: true });
return biz_api_token;
};
diff --git a/api/service/biz_auth_verify.js b/api/service/biz_auth_verify.js
index 1df1646..c67aff6 100644
--- a/api/service/biz_auth_verify.js
+++ b/api/service/biz_auth_verify.js
@@ -109,6 +109,7 @@ async function verifyRequest(body) {
plan_id: sub.plan_id,
subscription_id: sub.id,
token_id: row.id,
+ token_key: row.key || "",
stat_month: statMonth,
usage_snapshot: {
msg_count: usageSvc.num(usageRow.msg_count),
diff --git a/api/service/biz_token_logic.js b/api/service/biz_token_logic.js
index 553ebf9..5a6d7b1 100644
--- a/api/service/biz_token_logic.js
+++ b/api/service/biz_token_logic.js
@@ -36,7 +36,7 @@ async function findActiveSubscriptionForUser(userId) {
}
async function createToken(body) {
- const { user_id, token_name, expire_at } = body;
+ const { user_id, token_name, expire_at, key } = body;
if (!user_id || !expire_at) throw new Error("缺少 user_id 或 expire_at");
const u = await baseModel.biz_user.findByPk(user_id);
if (!u) throw new Error("用户不存在");
@@ -60,6 +60,7 @@ async function createToken(body) {
user_id,
plan_id,
token_name: token_name || "default",
+ key: key || null,
token_hash,
secret_cipher,
status: "active",