127 lines
4.3 KiB
JavaScript
127 lines
4.3 KiB
JavaScript
const baseModel = require("../../middleware/baseModel");
|
||
const tokenLogic = require("./biz_token_logic");
|
||
const usageSvc = require("./biz_usage_service");
|
||
|
||
function featureAllowed(plan, feature) {
|
||
if (!feature) return true;
|
||
const feats = plan.enabled_features;
|
||
if (feats == null) return true;
|
||
if (Array.isArray(feats)) return feats.includes(feature);
|
||
if (typeof feats === "object") {
|
||
return feats[feature] === true || feats[feature] === 1 || feats[feature] === "1";
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function normalizeUsageDelta(raw) {
|
||
if (!raw || typeof raw !== "object") return {};
|
||
return {
|
||
msg: usageSvc.num(raw.msg ?? raw.msg_count),
|
||
mass: usageSvc.num(raw.mass ?? raw.mass_count),
|
||
friend: usageSvc.num(raw.friend ?? raw.friend_count),
|
||
sns: usageSvc.num(raw.sns ?? raw.sns_count),
|
||
active_user: usageSvc.num(raw.active_user ?? raw.active_user_count),
|
||
};
|
||
}
|
||
|
||
function hasPositiveDelta(delta) {
|
||
return Object.values(delta).some((v) => usageSvc.num(v) > 0);
|
||
}
|
||
|
||
/**
|
||
* 对外鉴权:Token + 用户 + 有效订阅 + 功能点 + 接口权限 + API调用量 + 可选用量上报
|
||
* body: { token, feature?, api_path?, usage_delta?: { msg?, mass?, ... } }
|
||
*/
|
||
async function verifyRequest(body) {
|
||
const { token, feature, api_path } = body || {};
|
||
if (!token) {
|
||
return { ok: false, error_code: "TOKEN_INVALID", message: "缺少 token" };
|
||
}
|
||
|
||
const hash = tokenLogic.hashPlainToken(token);
|
||
const row = await baseModel.biz_api_token.findOne({ where: { token_hash: hash } });
|
||
if (!row) {
|
||
return { ok: false, error_code: "TOKEN_INVALID", message: "Token 不存在" };
|
||
}
|
||
if (row.status === "revoked") {
|
||
return { ok: false, error_code: "TOKEN_REVOKED", message: "Token 已吊销" };
|
||
}
|
||
|
||
const now = new Date();
|
||
if (new Date(row.expire_at) < now) {
|
||
return { ok: false, error_code: "TOKEN_EXPIRED", message: "Token 已过期" };
|
||
}
|
||
|
||
const user = await baseModel.biz_user.findByPk(row.user_id);
|
||
if (!user || user.status !== "active") {
|
||
return { ok: false, error_code: "SUBSCRIPTION_INACTIVE", message: "用户不可用" };
|
||
}
|
||
|
||
const sub = await tokenLogic.findActiveSubscriptionForUser(row.user_id);
|
||
if (!sub) {
|
||
return { ok: false, error_code: "SUBSCRIPTION_INACTIVE", message: "无有效订阅" };
|
||
}
|
||
|
||
const plan = await baseModel.biz_plan.findByPk(sub.plan_id);
|
||
if (!plan || plan.status !== "active") {
|
||
return { ok: false, error_code: "SUBSCRIPTION_INACTIVE", message: "套餐不可用" };
|
||
}
|
||
|
||
if (feature && !featureAllowed(plan, feature)) {
|
||
return { ok: false, error_code: "FEATURE_NOT_ALLOWED", message: "功能未在套餐内" };
|
||
}
|
||
|
||
// 接口路径级权限校验
|
||
if (api_path) {
|
||
const apiCheck = usageSvc.checkApiPathAllowed(plan, api_path);
|
||
if (!apiCheck.ok) {
|
||
return { ok: false, error_code: apiCheck.error_code, message: apiCheck.message };
|
||
}
|
||
}
|
||
|
||
const statMonth = usageSvc.currentStatMonth();
|
||
let usageRow = await usageSvc.getOrCreateUsage(row.user_id, sub.plan_id, statMonth);
|
||
|
||
// API 调用次数配额校验
|
||
if (api_path) {
|
||
const callCheck = usageSvc.checkApiCallQuota(plan, usageRow);
|
||
if (!callCheck.ok) {
|
||
return { ok: false, error_code: callCheck.error_code, message: callCheck.message };
|
||
}
|
||
usageRow = await usageSvc.incrementApiCallCount(row.user_id, sub.plan_id, statMonth);
|
||
}
|
||
|
||
const delta = normalizeUsageDelta(body.usage_delta || body.usage_report);
|
||
if (hasPositiveDelta(delta)) {
|
||
const q = usageSvc.checkQuotaAfterDelta(plan, usageRow, delta);
|
||
if (!q.ok) {
|
||
return { ok: false, error_code: q.error_code || "QUOTA_EXCEEDED", message: q.message || "额度不足" };
|
||
}
|
||
usageRow = await usageSvc.applyDelta(row.user_id, sub.plan_id, statMonth, delta);
|
||
}
|
||
|
||
await row.update({ last_used_at: now });
|
||
|
||
return {
|
||
ok: true,
|
||
context: {
|
||
user_id: row.user_id,
|
||
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),
|
||
mass_count: usageSvc.num(usageRow.mass_count),
|
||
friend_count: usageSvc.num(usageRow.friend_count),
|
||
sns_count: usageSvc.num(usageRow.sns_count),
|
||
active_user_count: usageSvc.num(usageRow.active_user_count),
|
||
api_call_count: usageSvc.num(usageRow.api_call_count),
|
||
},
|
||
},
|
||
};
|
||
}
|
||
|
||
module.exports = { verifyRequest };
|