// src/routes/v1/services/queryEngine.ts import pool from '../../../db'; type QueryContext = { current_tenant_id?: string; operator_role?: string; }; export async function executeNaturalLanguageQuery( question: string, context: QueryContext = {} ) { const q = question.toLowerCase().trim(); // 工具函数:安全转义 LIKE const escapeLike = (str: string) => str.replace(/[%_]/g, '\\$&'); // 场景 1: 商家 GMV(支持时间范围) if ((q.includes('gmv') || q.includes('销售额')) && q.includes('商家')) { const nameMatch = q.match(/商家\s*["']?([a-zA-Z0-9\u4e00-\u9fa5]+)["']?/); const merchantName = nameMatch ? nameMatch[1] : null; if (!merchantName) throw new Error('请指定商家名称'); let timeFilter = ''; let params: any[] = [`%${escapeLike(merchantName)}%`]; if (q.includes('昨天')) { timeFilter = 'AND DATE(FROM_UNIXTIME(o.create_time)) = CURDATE() - INTERVAL 1 DAY'; } else if (q.includes('上周')) { timeFilter = 'AND YEARWEEK(FROM_UNIXTIME(o.create_time), 1) = YEARWEEK(CURDATE() - INTERVAL 1 WEEK, 1)'; } else if (q.includes('上月')) { timeFilter = 'AND DATE_FORMAT(FROM_UNIXTIME(o.create_time), "%Y-%m") = DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, "%Y-%m")'; } const sql = ` SELECT m.merch_name AS merchant, COALESCE(SUM(o.order_money), 0) AS gmv, COUNT(o.order_id) AS order_count FROM lucky_merch m LEFT JOIN lucky_order o ON m.site_id = o.site_id WHERE m.merch_name LIKE ? ${timeFilter} GROUP BY m.merch_id, m.merch_name `; const [rows] = await pool.execute(sql, params); return (rows as any[])[0] || null; } // 场景 2: 订单状态 if (q.includes('订单') && (q.includes('状态') || q.includes('详情'))) { const idMatch = q.match(/订单\s*["']?([a-zA-Z0-9]+)["']?/); const orderId = idMatch ? idMatch[1] : null; if (!orderId) throw new Error('请提供订单编号或ID'); const sql = ` SELECT order_no, order_status_name AS status, pay_status, delivery_status, order_money, name AS buyer_name, mobile, create_time FROM lucky_order WHERE order_no = ? OR order_id = ? `; const [rows] = await pool.execute(sql, [orderId, isNaN(Number(orderId)) ? null : Number(orderId)]); return (rows as any[])[0] || null; } // 场景 3: 用户信息 if (q.includes('用户') && (q.includes('信息') || q.includes('详情') || q.includes('资料'))) { const idMatch = q.match(/用户\s*["']?(\d+)["']?/); const userId = idMatch && idMatch[1] !== undefined ? parseInt(idMatch[1]) : null; if (!userId) throw new Error('请提供用户ID'); const sql = ` SELECT member_id, nickname, mobile, email FROM lucky_member WHERE member_id = ? `; const [rows] = await pool.execute(sql, [userId]); return (rows as any[])[0] || null; } // 场景 4: 商品销量 if (q.includes('商品') && q.includes('销量')) { const nameMatch = q.match(/商品\s*["']?([a-zA-Z0-9\u4e00-\u9fa5\s]+)["']?/); const goodsName = nameMatch && nameMatch[1] ? nameMatch[1].trim() : null; if (!goodsName) throw new Error('请指定商品名称'); const sql = ` SELECT g.goods_name, SUM(og.num) AS total_sold FROM lucky_goods g JOIN lucky_order_goods og ON g.goods_id = og.goods_id WHERE g.goods_name LIKE ? GROUP BY g.goods_id, g.goods_name `; const [rows] = await pool.execute(sql, [`%${escapeLike(goodsName)}%`]); return Array.isArray(rows) && rows.length > 0 ? rows[0] : null; } // 场景 5: 退款率(按商家) if (q.includes('退款率') && q.includes('商家')) { const nameMatch = q.match(/商家\s*["']?([a-zA-Z0-9\u4e00-\u9fa5]+)["']?/); const merchantName = nameMatch ? nameMatch[1] : null; if (!merchantName) throw new Error('请指定商家名称'); const sql = ` SELECT m.merch_name, COUNT(o.order_id) AS total_orders, SUM(CASE WHEN o.refund_status > 0 THEN 1 ELSE 0 END) AS refunded_orders, ROUND( SUM(CASE WHEN o.refund_status > 0 THEN 1 ELSE 0 END) * 100.0 / COUNT(o.order_id), 2 ) AS refund_rate_percent FROM lucky_merch m JOIN lucky_order o ON m.site_id = o.site_id WHERE m.merch_name LIKE ? GROUP BY m.merch_id, m.merch_name `; const [rows] = await pool.execute(sql, [`%${escapeLike(merchantName)}%`]); return (rows as any)[0] || null; } // 场景 6: 未发货订单 if (q.includes('未发货') && q.includes('订单')) { const sql = ` SELECT order_no, name AS buyer, order_money, create_time FROM lucky_order WHERE delivery_status = 0 -- 假设 0=未发货 ORDER BY create_time DESC LIMIT 20 `; const [rows] = await pool.execute(sql); return rows; } // 场景 7: 正常营业商户 if (q.includes('商户') && (q.includes('列表') || q.includes('所有'))) { const sql = ` SELECT merch_id, merch_name, status, balance, create_time FROM lucky_merch WHERE status = 1 -- 1=正常 ORDER BY sort ASC `; const [rows] = await pool.execute(sql); return rows; } // 场景 8: 商户提现记录 if (q.includes('提现') && q.includes('商户')) { const nameMatch = q.match(/商户\s*["']?([a-zA-Z0-9\u4e00-\u9fa5]+)["']?/); const merchantName = nameMatch ? nameMatch[1] : null; if (!merchantName) throw new Error('请指定商户名称'); const sql = ` SELECT withdraw_no, apply_money, money AS arrived_amount, status_name, apply_time FROM lucky_merch_withdraw w JOIN lucky_merch m ON w.merch_id = m.merch_id WHERE m.merch_name LIKE ? ORDER BY apply_time DESC LIMIT 10 `; const [rows] = await pool.execute(sql, [`%${escapeLike(merchantName)}%`]); return rows; } // 场景 9: 用户充值记录 if (q.includes('充值') && q.includes('用户')) { const idMatch = q.match(/用户\s*["']?(\d+)["']?/); const userId = idMatch && idMatch[1] ? parseInt(idMatch[1]) : null; if (!userId) throw new Error('请提供用户ID'); const sql = ` SELECT order_no, money AS amount, status, create_time, pay_time FROM lucky_member_recharge_order WHERE member_id = ? ORDER BY create_time DESC `; const [rows] = await pool.execute(sql, [userId]); return rows; } // 场景 10: 邀请奖励 if (q.includes('邀请') && q.includes('奖励')) { const idMatch = q.match(/用户\s*["']?(\d+)["']?/); const userId = idMatch && idMatch[1] ? parseInt(idMatch[1]) : null; if (!userId) throw new Error('请提供用户ID'); const sql = ` SELECT recommend_name AS activity, point, balance, create_time FROM lucky_member_recommend_award WHERE member_id = ? ORDER BY create_time DESC `; const [rows] = await pool.execute(sql, [userId]); return rows; } // 场景 11: 系统公告 if (q.includes('公告') || q.includes('通知')) { const sql = ` SELECT title, content, create_time, is_top FROM lucky_notice ORDER BY is_top DESC, create_time DESC LIMIT 5 `; const [rows] = await pool.execute(sql); return rows; } // 场景 12: 店铺笔记 if (q.includes('笔记') || q.includes('文章')) { const titleMatch = q.match(/笔记\s*["']?([a-zA-Z0-9\u4e00-\u9fa5\s]+)["']?/); const noteTitle = titleMatch && titleMatch[1] ? titleMatch[1].trim() : null; let sql = ` SELECT note_title, note_abstract, release_time, read_num FROM lucky_notes WHERE status = 1 -- 已发布 `; let params: any[] = []; if (noteTitle) { sql += ' AND note_title LIKE ?'; params.push(`%${escapeLike(noteTitle)}%`); } sql += ' ORDER BY release_time DESC LIMIT 5'; const [rows] = await pool.execute(sql, params); return rows; } // 场景 13: 通过登录用户或者商家名称获得 uniacid if ((q.includes('uniacid') || q.includes('平台id')) && (q.includes('商家') || q.includes('用户'))) { // 处理通过用户ID查询 if (q.includes('用户')) { const idMatch = q.match(/用户\s*["']?(\d+)["']?/); const userId = idMatch && idMatch[1] !== undefined ? parseInt(idMatch[1]) : null; if (!userId) throw new Error('请指定用户ID'); const sql = ` SELECT u.uniacid, u.uid, u.username, u.site_id FROM lucky_user u WHERE u.uid = ? LIMIT 1 `; const [rows] = await pool.execute(sql, [userId]); return (rows as any[])[0] || null; } // 处理通过商家名称查询 else { const nameMatch = q.match(/商家\s*["']?([a-zA-Z0-9\u4e00-\u9fa5]+)["']?/); const merchantName = nameMatch ? nameMatch[1] : null; if (!merchantName) throw new Error('请指定商家名称'); const sql = ` SELECT DISTINCT u.uniacid, m.merch_name, m.site_id FROM lucky_merch m JOIN lucky_user u ON m.site_id = u.site_id WHERE m.merch_name LIKE ? LIMIT 1 `; const [rows] = await pool.execute(sql, [`%${escapeLike(merchantName)}%`]); return (rows as any[])[0] || null; } } // 场景 14: 获得当前有多少商家用户 if ((q.includes('多少') || q.includes('数量') || q.includes('个数')) && q.includes('商家') && q.includes('用户')) { const sql = ` SELECT COUNT(DISTINCT u.uid) AS merchant_user_count FROM lucky_user u JOIN lucky_merch m ON u.site_id = m.site_id WHERE u.status = 1 AND m.status = 1 `; const [rows] = await pool.execute(sql); return (rows as any[])[0] || { merchant_user_count: 0 }; } // 默认兜底 throw new Error(` 无法理解该问题。支持的查询包括: - 商家 GMV(如“TechStore 昨天的 GMV”) - 订单状态(如“订单 L20251127001 状态”) - 用户/商品/提现/充值/公告等信息 - 通过商家名称查询商家的 uniacid - 获得当前有多少商家用户 `.trim()); }