feat(order): 禁止自己接自己发布的订单
- claimOrder 校验 buyer_id 与大神登录用户 id,相同直接 403「不能接自己发布的订单」 - 派单池支持 excludeBuyerId:已登录大神不会看到自己发布的待接单 - 115 套件/753 用例全绿;线上实测自接 403、他人可抢、自己的单不出现在自己的池子
这个提交包含在:
@@ -1940,7 +1940,8 @@ export class MysqlRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getOrdersPool(opts: { fromLat?: number | null; fromLng?: number | null; radiusKm?: number | null } = {}): Promise<any[]> {
|
||||
async getOrdersPool(opts: { fromLat?: number | null; fromLng?: number | null; radiusKm?: number | null; excludeBuyerId?: number | null } = {}): Promise<any[]> {
|
||||
const excludeBuyerId = Number(opts.excludeBuyerId) || 0;
|
||||
const [rows]: any = await pool.query(`
|
||||
SELECT
|
||||
id, order_no as orderNo, client_name as clientName,
|
||||
@@ -1951,9 +1952,10 @@ export class MysqlRepository {
|
||||
FROM orders
|
||||
WHERE status = '待接单'
|
||||
AND (claimed_by IS NULL OR claimed_by = 0)
|
||||
${excludeBuyerId ? 'AND buyer_id <> ?' : ''}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 50
|
||||
`);
|
||||
`, excludeBuyerId ? [excludeBuyerId] : []);
|
||||
|
||||
// 默认池保障
|
||||
const maskClientName = (n: any): string => {
|
||||
@@ -2043,7 +2045,7 @@ export class MysqlRepository {
|
||||
// orders.expert_id 统一存「大神档案 id」;claimed_by 存登录用户 id
|
||||
const companionIdForOrder = comp ? Number(comp.id) || expertId : expertId;
|
||||
|
||||
const [curRows]: any = await pool.query('SELECT id, status, claimed_by FROM orders WHERE id = ? OR order_no = ? LIMIT 1', [orderId, orderId]);
|
||||
const [curRows]: any = await pool.query('SELECT id, buyer_id, status, claimed_by FROM orders WHERE id = ? OR order_no = ? LIMIT 1', [orderId, orderId]);
|
||||
const cur = curRows && curRows[0];
|
||||
if (!cur) {
|
||||
const err: any = new Error('订单不存在');
|
||||
@@ -2051,6 +2053,15 @@ export class MysqlRepository {
|
||||
err.code = 40400;
|
||||
throw err;
|
||||
}
|
||||
// 禁止自己接自己发布的订单(刷单/套券风险)
|
||||
const buyerId = Number(cur.buyer_id) || 0;
|
||||
const actorUserId = Number(comp && comp.user_id) || expertId;
|
||||
if (buyerId > 0 && buyerId === actorUserId) {
|
||||
const err: any = new Error('不能接自己发布的订单');
|
||||
err.status = 403;
|
||||
err.code = 40300;
|
||||
throw err;
|
||||
}
|
||||
// 状态闸门:仅「待接单」可被抢单,严禁抢已在履约/已服务/已核销的订单
|
||||
if (String(cur.status) !== '待接单') {
|
||||
const err: any = new Error(`当前订单状态(${cur.status})不可接单(仅待接单可抢)`);
|
||||
|
||||
@@ -1490,9 +1490,11 @@ app.get('/api/orders/pool', async (req: Request, res: Response) => {
|
||||
const lng = Number(req.query.lng);
|
||||
const radius = Number(req.query.radius);
|
||||
const hasFrom = Number.isFinite(lat) && Number.isFinite(lng) && Math.abs(lat) <= 90 && Math.abs(lng) <= 180 && !(lat === 0 && lng === 0);
|
||||
// 已登录的大神不展示自己发布的订单(禁止自接自单)
|
||||
const excludeBuyerId = resolveRequestUserId(req) || 0;
|
||||
|
||||
// 先取不带半径的全量池:为历史无坐标订单做地理编码兜底(每轮最多 5 条,结果回写入库)
|
||||
let pre = await repo.getOrdersPool(hasFrom ? { fromLat: lat, fromLng: lng } : {});
|
||||
let pre = await repo.getOrdersPool({ fromLat: hasFrom ? lat : null, fromLng: hasFrom ? lng : null, excludeBuyerId });
|
||||
if (hasFrom) {
|
||||
const missing = pre.filter((o: any) => !o.hasGeo && o.location).slice(0, 5);
|
||||
let changed = false;
|
||||
@@ -1503,7 +1505,7 @@ app.get('/api/orders/pool', async (req: Request, res: Response) => {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) pre = await repo.getOrdersPool({ fromLat: lat, fromLng: lng });
|
||||
if (changed) pre = await repo.getOrdersPool({ fromLat: lat, fromLng: lng, excludeBuyerId });
|
||||
}
|
||||
|
||||
const list = hasFrom && Number.isFinite(radius) && radius > 0
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('订单与活动资金一致性护栏 (报名超卖 / 抢单 CAS / 评
|
||||
const code = fs.readFileSync(serverRepoPath, 'utf-8');
|
||||
const start = code.indexOf('async claimOrder(');
|
||||
expect(start).toBeGreaterThan(-1);
|
||||
const block = code.slice(start, start + 2400);
|
||||
const block = code.slice(start, start + 3600);
|
||||
expect(block).toContain("String(cur.status) !== '待接单'");
|
||||
expect(block).toContain("AND status = '待接单'");
|
||||
});
|
||||
|
||||
@@ -60,4 +60,11 @@ describe('第九十五批平台派单闭环', () => {
|
||||
expect(columns).toBe(24);
|
||||
expect(placeholders + 2).toBe(columns);
|
||||
});
|
||||
|
||||
it('禁止自己接自己发布的订单,派单池也不展示自己的单', () => {
|
||||
expect(repo).toContain('不能接自己发布的订单');
|
||||
expect(repo).toContain('const actorUserId = Number(comp && comp.user_id) || expertId;');
|
||||
expect(repo).toContain('AND buyer_id <> ?');
|
||||
expect(indexCode).toContain('excludeBuyerId');
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户