refactor(data): 全面移除小程序写死的演示/兜底数据,测试数据统一落库

- 删除无引用的 common/mock.js;登录页与个人中心的演示账号改为 DB 配置 demo_accounts + /api/auth/demo-accounts、/api/auth/demo-login(签发真实 token)

- 移除订单/活动/大神/动态/推广/工作台/门票/充值/举报等页面的演示列表与假兜底;新增真实空态

- 修复伪造成功:活动调价、评价回复、核销、海报保存、图片上传失败、提现/核销固定 1001、随机开黑房号、随机即时匹配、假语音播放等

- 服务端移除 101/2006 合成用户与合成大神兜底、移除默认服务包虚构价格;演示账号清单可在后台关闭

- 111 套件/733 用例全绿;已部署 20261021_162041,demo-accounts/demo-login 实测通过
这个提交包含在:
xcbyy
2026-10-21 16:21:10 +08:00
父节点 77aa4cb50b
当前提交 16f6419cba
共有 30 个文件被更改,包括 661 次插入 和 1366 次删除

查看文件

@@ -509,9 +509,9 @@ async function resolveTencentMapKey(): Promise<string> {
return key;
}
function httpsGetJson(url: string, timeoutMs = 4000): Promise<any> {
function httpsGetJsonOnce(url: string, headers: Record<string, string>, timeoutMs = 4000): Promise<any> {
return new Promise((resolve, reject) => {
const req = https.get(url, (resp) => {
const req = https.get(url, { headers }, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
@@ -529,6 +529,15 @@ function httpsGetJson(url: string, timeoutMs = 4000): Promise<any> {
});
}
async function httpsGetJson(url: string, timeoutMs = 4000): Promise<any> {
const referer = String(process.env.TENCENT_MAP_REFERER || 'http://118.184.172.94/').trim();
const data = await httpsGetJsonOnce(url, referer ? { Referer: referer } : {}, timeoutMs);
if (data && (data.status === 110 || data.status === 105) && referer) {
return httpsGetJsonOnce(url, {}, timeoutMs);
}
return data;
}
const geocodeCache = new Map<string, { geo: { lat: number; lng: number } | null; at: number }>();
/** 地址 -> 坐标(腾讯 WebService 地理编码),带进程内缓存 */
@@ -2947,6 +2956,88 @@ app.post('/api/auth/wechat-phone', async (req: Request, res: Response) => {
}
});
// ===== 体验账号(演示/评审):清单与文案全部来自 DB 配置,小程序端不再写死 =====
async function loadDemoAccounts(): Promise<Array<{ userId: number; roleTag: string; roleDesc: string; desc: string }>> {
try {
const record = await repo.getPlatformConfigByKey('demo_accounts');
const value = (record && record.valueJson) || {};
if (value.enabled === false) return [];
const accounts = Array.isArray(value.accounts) ? value.accounts : [];
return accounts
.map((a: any) => ({
userId: Number(a.userId) || 0,
roleTag: String(a.roleTag || ''),
roleDesc: String(a.roleDesc || ''),
desc: String(a.desc || ''),
}))
.filter((a: any) => a.userId > 0)
.slice(0, 10);
} catch (e) {
logServerError('loadDemoAccounts', e);
return [];
}
}
app.get('/api/auth/demo-accounts', async (_req: Request, res: Response) => {
try {
const accounts = await loadDemoAccounts();
const list = [];
for (const acc of accounts) {
const u: any = await repo.getUserById(acc.userId);
if (!u) continue;
if (u.status === '已封禁' || u.status === '封禁') continue;
list.push({
userId: u.id,
nickname: u.nickname || `用户${u.id}`,
avatar: u.avatar || '',
roleTag: acc.roleTag,
roleDesc: acc.roleDesc,
desc: acc.desc,
});
}
res.json({ code: 20000, data: list });
} catch (err: any) {
res.status(500).json({ code: 50000, message: err.message });
}
});
// 体验登录:只对 DB 配置内的账号签发真实 token,避免"无 token 的假登录"造成的 401/身份错配
app.post('/api/auth/demo-login', rateLimit({ name: 'demo_login', windowMs: 60 * 1000, max: 20, key: rateWriteKey }), async (req: Request, res: Response) => {
try {
const userId = Number(req.body?.userId) || 0;
const accounts = await loadDemoAccounts();
if (!accounts.some((a) => a.userId === userId)) {
return res.status(403).json({ code: 40300, message: '该账号未开启体验登录' });
}
const user: any = await repo.getUserById(userId);
if (!user) {
return res.status(404).json({ code: 40400, message: '体验账号不存在或已被清理' });
}
if (user.status === '已封禁' || user.status === '封禁') {
return res.status(403).json({ code: 40300, message: '该体验账号已被封禁' });
}
const token = signUserToken(user.id);
res.json({
code: 20000,
data: {
userId: user.id,
user: {
id: user.id,
nickname: user.nickname,
avatar: user.avatar,
gender: user.gender,
phone: user.phone,
},
userToken: token,
token,
},
message: '体验登录成功',
});
} catch (err: any) {
res.status(500).json({ code: 50000, message: err.message || '体验登录异常' });
}
});
// 微信小程序 JSAPI 统一下单预支付接口 (支持真实商户号配置与沙盒收银台无缝切换)
app.post('/api/pay/wechat/prepay', rateLimit({ name: 'prepay', windowMs: 60 * 1000, max: 30, key: rateWriteKey }), async (req: Request, res: Response) => {
try {
@@ -3790,45 +3881,7 @@ app.get('/api/users/:id', async (req: Request, res: Response) => {
const userId = Number(req.params.id);
let user = await repo.getUserById(userId);
if (!user) {
if (userId === 101) {
user = {
id: 101,
userNo: 'U101',
nickname: '糖心小糯米',
avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200',
phone: '138****0101',
gender: '女',
signature: '人美声甜,擅长各类同城陪伴与开黑互动',
role: '认证大神',
balance: '2580.00',
commission: '128.50',
invitedCount: 6,
orderCount: 88,
activityCount: 16,
isExpert: true,
status: '正常'
};
} else if (userId === 2006) {
user = {
id: 2006,
userNo: 'U2006',
nickname: '苏苏学姐',
avatar: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=120',
phone: '139****2006',
gender: '女',
signature: '工体纯K量贩欢唱局!甜美情歌、粤语老歌、摇滚气氛拉满',
role: '认证大神',
balance: '5690.00',
commission: '560.00',
invitedCount: 12,
orderCount: 120,
activityCount: 35,
isExpert: true,
status: '正常'
};
} else {
return res.status(404).json({ code: 40400, message: '用户未找到' });
}
return res.status(404).json({ code: 40400, message: '用户未找到' });
}
// 敏感字段(手机号 / 余额 / 佣金 / 邀请关系)仅本人或管理员可见
const isAdmin = isVerifiedAdminRequest(req);