feat(server): 完成 P0+P1 后端能力(工作流、RBAC、付款归档)

P0 MVP:

- 认证:login/refresh/logout/forgot/reset + tokenVersion

- RBAC:角色到权限映射 + requirePermission

- 发票池:upload/bind/signed-url + 判重(VALID_003/VALID_005)

- 报销:create/view/submit + 单号生成

- 工作流:approve + department_manager 解析

P1 扩展:

- 动作:reject/return/withdraw(含 comment)

- transfer:任务转审 + 链路上限 3

- pay:approved -> paid + 发票归档 used_complete

- 动态审批链:approval_rules 驱动

回归脚本:

- smoke-test.sh / regression-dedup.sh / regression-workflow-actions.sh
这个提交包含在:
2026-02-07 16:43:31 +08:00
当前提交 f527633716
共修改 44 个文件,包含 11100 行新增和 0 行删除
+45
查看文件
@@ -0,0 +1,45 @@
const crypto = require("node:crypto");
function toBase64Url(value) {
const source = typeof value === "string" ? value : JSON.stringify(value);
return Buffer.from(source).toString("base64url");
}
function fromBase64Url(value) {
return Buffer.from(value, "base64url").toString("utf8");
}
function signPayload(payload, secret) {
const body = toBase64Url(payload);
const signature = crypto.createHmac("sha256", secret).update(body).digest("base64url");
return `${body}.${signature}`;
}
function verifyToken(token, secret) {
if (typeof token !== "string") {
return { ok: false, reason: "invalid_format" };
}
const segments = token.split(".");
if (segments.length !== 2) {
return { ok: false, reason: "invalid_format" };
}
const [body, signature] = segments;
const expected = crypto.createHmac("sha256", secret).update(body).digest("base64url");
if (signature !== expected) {
return { ok: false, reason: "invalid_signature" };
}
try {
const payload = JSON.parse(fromBase64Url(body));
return { ok: true, payload };
} catch {
return { ok: false, reason: "invalid_payload" };
}
}
module.exports = {
signPayload,
verifyToken,
};