Files
wechatWeb/api/service/biz_auth_verify.js
张成 aa8eaa6ccd init
2026-03-24 16:07:02 +08:00

108 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 + 用户 + 有效订阅 + 功能点 + 可选用量上报与额度
* body: { token, feature?, usage_delta?: { msg?, mass?, ... } }
*/
async function verifyRequest(body) {
const { token, feature } = 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: "功能未在套餐内" };
}
const statMonth = usageSvc.currentStatMonth();
let usageRow = await usageSvc.getOrCreateUsage(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,
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),
},
},
};
}
module.exports = { verifyRequest };