Files
sass-admin-agent-api/src/routes/v1/services/queryEngine.ts

322 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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());
}