feat(order): 派单实时通知 + 订单站内聊天
- 新派单(余额支付立即入池 / 微信支付确认后入池)向在线认证大神推送 ORDER_POOL 站内通知;雷达 20s、接单大厅 30s 自动轮询 - 新增 order_messages 表与订单会话接口:仅买家与已认领大神可访问,服务端拦截手机号与违规内容,消息落库并推送 CHAT 通知 - 新增 pages/order/chat 聊天页(3s 轮询、已读、发送),买家订单「联系大神」与大神工作台「联系老板」接入 - 114 套件/744 用例全绿;线上实测聊天/越权/手机号拦截/新单通知均通过,测试数据已清理
这个提交包含在:
@@ -159,7 +159,21 @@ export async function initDatabase(): Promise<void> {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// 动态增加移动端所需的扩展字段 (若历史表已存在则执行防断裂补丁)
|
||||
// 订单维度站内聊天消息(仅交易双方可见,敏感词/联系方式在服务端拦截)
|
||||
await conn.query(`
|
||||
CREATE TABLE IF NOT EXISTS order_messages (
|
||||
id VARCHAR(64) PRIMARY KEY,
|
||||
order_id VARCHAR(64) NOT NULL,
|
||||
sender_id INT NOT NULL,
|
||||
receiver_id INT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
read_at DATETIME NULL,
|
||||
INDEX idx_order_created (order_id, created_at),
|
||||
INDEX idx_receiver_read (receiver_id, read_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
const extraOrderCols = [
|
||||
"ALTER TABLE orders ADD COLUMN buyer_id INT DEFAULT 1001 AFTER id",
|
||||
"ALTER TABLE orders ADD COLUMN expert_id INT DEFAULT 101 AFTER buyer_id",
|
||||
|
||||
@@ -1678,6 +1678,7 @@ export class MysqlRepository {
|
||||
orderNo: r.orderNo,
|
||||
buyerId: Number(r.buyerId) || 0,
|
||||
expertId: Number(r.expertId) || 0,
|
||||
claimedBy: Number(r.claimedBy) || 0,
|
||||
amount: Number(r.amount) || 0,
|
||||
companionName: r.companionName || '',
|
||||
status: r.status,
|
||||
@@ -1685,6 +1686,66 @@ export class MysqlRepository {
|
||||
};
|
||||
}
|
||||
|
||||
/** 订单聊天上下文:仅买家与已认领大神可参与 */
|
||||
async getOrderChatContext(orderId: string): Promise<any | null> {
|
||||
const own = await this.getOrderOwnership(orderId);
|
||||
if (!own) return null;
|
||||
return {
|
||||
orderId: own.id,
|
||||
orderNo: own.orderNo,
|
||||
buyerId: own.buyerId,
|
||||
expertUserId: Number(own.claimedBy) || 0,
|
||||
status: own.status,
|
||||
amount: own.amount,
|
||||
companionName: own.companionName,
|
||||
};
|
||||
}
|
||||
|
||||
async listOrderMessages(orderId: string, limit = 100): Promise<any[]> {
|
||||
const safeLimit = Math.min(200, Math.max(1, Math.round(Number(limit) || 100)));
|
||||
const [rows]: any = await pool.query(`
|
||||
SELECT id, order_id as orderId, sender_id as senderId, receiver_id as receiverId,
|
||||
content, created_at as createdAt, read_at as readAt
|
||||
FROM order_messages
|
||||
WHERE order_id = ?
|
||||
ORDER BY created_at ASC
|
||||
LIMIT ?
|
||||
`, [orderId, safeLimit]);
|
||||
return rows || [];
|
||||
}
|
||||
|
||||
async createOrderMessage(orderId: string, senderId: number, receiverId: number, content: string): Promise<any> {
|
||||
const id = `msg_${Date.now()}_${Math.floor(100 + Math.random() * 900)}`;
|
||||
await pool.query(
|
||||
'INSERT INTO order_messages (id, order_id, sender_id, receiver_id, content) VALUES (?, ?, ?, ?, ?)',
|
||||
[id, orderId, senderId, receiverId, content]
|
||||
);
|
||||
return {
|
||||
id,
|
||||
orderId,
|
||||
senderId: Number(senderId),
|
||||
receiverId: Number(receiverId),
|
||||
content,
|
||||
createdAt: new Date().toISOString(),
|
||||
readAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
async markOrderMessagesRead(orderId: string, readerId: number): Promise<void> {
|
||||
await pool.query(
|
||||
'UPDATE order_messages SET read_at = NOW() WHERE order_id = ? AND receiver_id = ? AND read_at IS NULL',
|
||||
[orderId, readerId]
|
||||
);
|
||||
}
|
||||
|
||||
async countUnreadOrderMessages(orderId: string, receiverId: number): Promise<number> {
|
||||
const [rows]: any = await pool.query(
|
||||
'SELECT COUNT(*) as c FROM order_messages WHERE order_id = ? AND receiver_id = ? AND read_at IS NULL',
|
||||
[orderId, receiverId]
|
||||
);
|
||||
return Number(rows?.[0]?.c || 0);
|
||||
}
|
||||
|
||||
// 大神点击申请完成服务 (服务中 -> 待确认,进入 48 小时买家确认窗口)
|
||||
async applyCompleteOrder(orderId: string): Promise<any> {
|
||||
const [cur]: any = await pool.query('SELECT status FROM orders WHERE id = ? OR order_no = ? LIMIT 1', [orderId, orderId]);
|
||||
|
||||
在新工单中引用
屏蔽一个用户