feat(admin): 用户列表新增「调余额」人工充值/扣减入口

- 新接口 POST /api/admin/users/:id/balance(requireAdmin):事务锁行、扣减不可为负、单次上限 100 万、写 finance_records 并推送站内通知

- 后台用户列表每行新增「调余额」按钮与弹窗(当前余额/方向/金额/备注)

- 新增 admin-balance-adjust 护栏;113 套件/737 用例全绿
这个提交包含在:
xcbyy
2026-10-21 17:41:54 +08:00
父节点 4e3283ff01
当前提交 14ce4b25f5
共有 4 个文件被更改,包括 238 次插入 和 1 次删除

查看文件

@@ -2961,7 +2961,7 @@ async function loadDemoAccounts(): Promise<Array<{ userId: number; roleTag: stri
roleDesc: String(a.roleDesc || ''),
desc: String(a.desc || ''),
}))
.filter((a) => a.userId > 0)
.filter((a: any) => a.userId > 0)
.slice(0, 10);
} catch (e) {
logServerError('loadDemoAccounts', e);
@@ -3793,6 +3793,36 @@ app.get('/api/users', requireAdmin, async (req: Request, res: Response) => {
}
});
// 管理后台:人工调整用户钱包余额(运营/测试用,写入财务流水)
app.post('/api/admin/users/:id/balance', requireAdmin, async (req: Request, res: Response) => {
try {
const userId = Number(req.params.id);
const amount = Number(req.body?.amount);
const direction = String(req.body?.direction || 'IN') === 'OUT' ? 'OUT' : 'IN';
const reason = String(req.body?.reason || '').trim().slice(0, 80);
if (!userId || !Number.isFinite(amount) || amount <= 0) {
return res.status(400).json({ code: 40000, message: '请填写有效的调整金额' });
}
const result = await repo.adjustUserBalance(userId, amount, direction as 'IN' | 'OUT', reason, '平台运营');
pushUserNotification(
userId,
'SYSTEM',
direction === 'IN' ? '余额已充值' : '余额已扣减',
`平台${direction === 'IN' ? '充值' : '扣减'} ¥${result.amount}${reason ? `(${reason})` : ''},当前余额 ¥${result.after}。`,
'/pages/user/recharge'
);
res.json({
code: 20000,
data: result,
message: direction === 'IN' ? `充值成功,当前余额 ¥${result.after}` : `扣减成功,当前余额 ¥${result.after}`
});
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message || '余额调整失败' });
}
});
app.post('/api/users/:id/status', requireAdmin, async (req: Request, res: Response) => {
try {
const userId = Number(req.params.id);