chore: api 采用v1版本控制方式
This commit is contained in:
322
src/routes/v1/services/queryEngine.ts
Normal file
322
src/routes/v1/services/queryEngine.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
// 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());
|
||||
}
|
||||
Reference in New Issue
Block a user