feat: 新增智能查数(自然语言转 SQL 只读查询)

新增「智能查数」功能:用户用自然语言提问,由大模型生成一条只读 SQL,经安全校验后在当前用户数据上执行,前端按结果形状自适应渲染为统计卡片、表格或图表。入口位于侧边栏,仅在 AI 启用时显示。

变更内容:
- 后端新增 AiSqlQueryService 与 POST /api/ai/sql,新增 AiCallType.SQL 用量记录与提示词模板 ai-sql-system.tpl
- 新增安全网关 TextToSqlGuard:仅允许单条 SELECT,拒绝写操作、多语句、危险函数与 SELECT INTO/FOR UPDATE;校验表白名单与 schema 前缀;递归注入 user_id 隔离(主表加 WHERE、JOIN 加 ON,覆盖括号连接与 CTE);结果上限 1000
- 表结构改由 TextToSqlSchemaProvider 从数据库元数据动态读取(60 秒缓存),白名单与表列中文说明集中在 TextToSqlTables,字段增删改无需调整提示词
- 新增 AiSqlQueryResultVO、AiSqlColumnVO,返回生成的 SQL、列元数据与行数据
- 前端新增智能查数页 SqlQuery.vue(路由 /smart-query),按结果形状渲染单行统计卡片、多行动态表格并支持柱状图与饼图切换
- 渠道列用 getChannelLabel 本地化展示,生成的 SQL 用 sql-formatter 缩进、highlight.js 高亮并支持复制
- 新增共享 useAiEnabled 状态,侧边栏「智能查数」菜单仅 AI 启用时显示,设置页保存 AI 配置后即时刷新
- 设置页与收支记录页文案补充智能查数相关三语文案

依赖与清理:
- 新增前端依赖 sql-formatter、highlight.js
- 移除仓库内测试文件 LedgerlyApplicationTests、PromptTemplateServiceTest、AiPromptPayloadPrivacyTest,此后测试源码不再纳入提交

说明:
- 表结构动态读取保证字段变更零维护,仅可查表白名单需显式登记
- 安全网关对 JOIN 条件注入 ON 子句以保证 LEFT JOIN 语义,并对括号连接与 CTE 递归处理
This commit is contained in:
2026-09-24 10:38:24 +08:00
parent 5673f6fca2
commit 0e1443c59e
23 changed files with 1947 additions and 216 deletions
+35
View File
@@ -0,0 +1,35 @@
// AI 启用状态共享 composable:全局单例,供侧边栏菜单与页面统一控制 AI 相关元素显隐
import { ref } from 'vue'
import { getAiConfig } from '../api/ai'
/** 当前用户 AI 是否启用;读取失败一律按未启用处理 */
export const aiEnabled = ref(false)
/** 是否已成功加载过一次,避免重复请求 */
let loaded = false
/**
* 从后端刷新 AI 启用状态。
* 设置页保存 AI 配置后调用,可让侧边栏菜单即时显隐。
*/
export async function refreshAiEnabled() {
try {
const res: any = await getAiConfig()
aiEnabled.value = !!res?.data?.enabled
} catch (e) {
aiEnabled.value = false
}
loaded = true
}
/** 确保至少加载过一次 AI 启用状态(已加载则不重复请求) */
export async function ensureAiEnabledLoaded() {
if (loaded) return
await refreshAiEnabled()
}
/** 重置为未启用(退出登录等场景使用) */
export function resetAiEnabled() {
aiEnabled.value = false
loaded = false
}