data-query

通用自然语言转SQL与可视化页面生成技能。当用户使用自然语言查询数据,或需要生成带图表的数据看板页面时调用本技能。技能基于挂载的知识库上下文直接生成SQL,验证后生成可部署的HTML图表页面。

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "data-query" with this command: npx skills add ylyuanlu/data-query

data-query 技能

技能概述

本技能提供自然语言转 SQL(NL→SQL)能力,并支持生成基于 ECharts 的可视化 HTML 页面。

核心能力:

  • 将自然语言问题转换为准确的 SQL 查询
  • 验证 SQL 可执行性后返回结果
  • 生成可嵌入的 HTML 图表页面

典型触发场景:

  • 用户问:"某项目的延期任务有哪些?"
  • 用户问:"帮我统计一下这个项目的风险分布"
  • 用户说:"生成一个资源负载分析页面"

🚨 核心原则一:前置配置检查(必须首先执行)

使用本技能前,必须立刻执行此检查。配置缺失时,停止一切操作,提示用户补充。

配置文件位置: skills/data-query/config.json

必需配置

配置项路径说明缺失后果
API 验证账号security.apiAuth.username系统账号,用于验证 SQL❌ 无法验证 SQL
API 验证密码security.apiAuth.password系统密码,用于验证 SQL❌ 无法验证 SQL
数据库类型db.typemysql / dm / oracle⚠️ 无法确定方言规则

根据 db.type 显示相关配置

db.type = dm 时:

配置项路径说明缺失后果
达梦主机db.host达梦数据库地址❌ 无法连接达梦
达梦端口db.port达梦端口,默认 5236/5237❌ 无法连接达梦
达梦数据库名db.database数据库实例名⚠️ 可能连接失败
达梦用户名db.user数据库用户名❌ 无法连接达梦
达梦密码db.password数据库密码❌ 无法连接达梦

db.type = oracle 时:

配置项路径说明缺失后果
Oracle 主机oracle.hostOracle 数据库地址❌ 无法连接 Oracle
Oracle 端口oracle.portOracle 端口,默认 1521❌ 无法连接 Oracle
Oracle 服务名oracle.serviceName数据库服务名❌ 无法连接 Oracle
Oracle 用户名oracle.user数据库用户名❌ 无法连接 Oracle
Oracle 密码oracle.password数据库密码❌ 无法连接 Oracle

API 配置:

配置项路径说明缺失后果
API Base URLapi.baseDashboard 服务地址❌ 无法调用 API 验证
登录密码加密api.loginPasswordEncrypt密码是否加密⚠️ 可能登录失败

缺失时提示模板

配置缺失时,按以下格式提示用户,并直接帮用户设置:

⚠️ 技能配置不完整

配置文件:skills/data-query/config.json

缺失项:
  ❌ security.apiAuth.username — API 验证账号
  ❌ security.apiAuth.password — API 验证密码

请提供:
  1. 系统账号(用于验证 SQL):______
  2. 系统密码:______

我将直接帮你写入配置文件。

校验规则

使用技能时,我会按以下顺序检查:

  1. 检查 security.apiAuth — 缺失则提示用户提供,我直接写入配置文件
  2. 检查 db.type — 缺失则无法确定方言,提示用户提供
  3. 根据 db.type 检查对应数据库连接 — 缺失则提示用户提供,我直接写入
  4. 检查 api.base — 缺失则提示用户提供,我直接写入

注意: verifyLimit(重试次数)、workspace 为可选,不影响核心功能。


工作流程

⚠️ 必读:数据库方言规范

生成 SQL 前,必须先读取对应数据库的规范文档:

数据库规范文档位置
达梦 DMskills/data-query/database_specs/dialect/DM.md
MySQLskills/data-query/database_specs/dialect/MySQL.md
Oracleskills/data-query/database_specs/dialect/Oracle.md
ShardingSphere 路由skills/data-query/database_specs/sharding/ROUTING_RULES.md
ShardingSphere BUGknowledge/shared/sharding/KNOWN_BUGS.md
分片表分布knowledge/shared/sharding/TABLE_DISTRIBUTION.md

达梦关键规范(必须遵守):

  • ❌ 禁用 TO_CHAR(date, 'YYYY-MM') → ✅ 用 SUBSTR(CAST(date AS VARCHAR), 1, 7)
  • ❌ 禁用 GROUP BY + COUNT(*) 在 ShardingSphere 下 → ✅ 返回原始数据,前端聚合
  • ❌ 分片键上禁止使用函数

⚠️ 两套 SQL 必须同步

Cockpit 页面存在两套 SQL,修改时必须同时更新:

对象用途怎么改
ENCRYPTED_SQL[key].ciphertext后端实际执行重新加密后替换
SQL_PLAIN[key]前端参数校验明文替换
CHART_CONFIG[id].scope参数注入逻辑按需调整

修改步骤:

  1. 确定正确 SQL → 2. 用 encrypt_for_page.js 加密 → 3. 同时更新 ENCRYPTED_SQL 和 SQL_PLAIN → 4. 用 src/verify/index.js 验证

流程一:NL→SQL 查询(预览)

输出:无文件,直接返回 JSON 结果

用户自然语言问题
    ↓
主 agent 直接生成 SQL(基于知识库,不调用外部 LLM)
    ↓
调用 src/verify/index.js verify() 验证 SQL 可执行性(unified 模式:DB + API)
    ↓
返回 SQL + 查询结果给用户预览

流程二:生成单图表 HTML 页面

输出路径{workspace}/nl2sql_output/{chartTitle}_{timestamp}.html

用户需求(图表类型 + 数据描述)
    ↓
步骤1:主 agent 直接生成 SQL(基于知识库)
步骤2:调用 src/verify/index.js verify() 验证 SQL(unified 模式)
步骤3:调用 src/security/encryptSql.js 对 SQL 进行 AES-256-CBC 加密
步骤4:填充 html_page_template.html 模板
       替换占位符: {{ENCRYPTED_SQL}}、{{IV}}、{{JWT_TOKEN}}、{{CHART_TITLE}} 等
    ↓
输出完整的 HTML 文件 → {workspace}/nl2sql_output/{chartTitle}_{timestamp}.html

流程三:生成驾驶舱 HTML 页面

驾驶舱是多图表聚合页面,包含 KPI 指标卡、多种 ECharts 图表、项目列表侧边栏和视图切换(仪表盘/看板/甘特图)。

输出路径:两个文件(归集在一起)

  • 本地预览版:{workspace}/nl2sql_output/cockpit_preview.html(CDN URLs)
  • 部署版:{workspace}/nl2sql_output/cockpit.html(CDN→本地相对路径,供部署使用)

⚠️ 不得覆盖模板templates/cockpit_current.html 是模板文件,生成结果必须保存到 nl2sql_output/,不得覆盖模板。

用户需求:生成项目驾驶舱
    ↓
步骤0:resolveCockpitTemplate() 解析模板
       ├─ cockpit_current.html 存在 → 使用用户当前版本
       ├─ cockpit_current.html 不存在 → 使用技能基线模板
       └─ regenerate=true → 强制重新生成并存档旧版本
    ↓
步骤1:resolveDomain() × N 生成 sqlMap + spec(自动进化)
       └─ 或由主 agent 直接构造(显式配置格式)
    ↓
步骤2:verify() × N 验证(source=unified:DB 直连 + API 端到端同时验证)
   > agent 全程参与:验证失败 → 返回错误信息给 agent → agent 决定是否重新生成 SQL → 重试验证(最多 3 次,全失败则停止)
   > ⚠️ **前置条件**:params 数量必须与 SQL 中 `?` 总数完全匹配;禁止 `?` 出现在表达式中
    ↓
步骤3:parseSqlMap() → buildChartConfig() 生成图表配置
    ↓
步骤4:encryptSql() 加密所有 SQL
    ↓
步骤5:注入 cockpit_current.html → 输出 HTML(不修改模板文件)
    ↓
步骤6(可选):applyTweak() 应用局部微调 → 写回 cockpit_current.html(供下次生成使用)
    ↓
步骤7:**完整性校验** — src/generate/page.js 内置 `src/generate/validate_page.js`,自动完成以下 6 项检查 + auto-fix
    ↓
步骤8:**HTML 端到端验证** — src/generate/page.js 内置 `src/verify/index.js` 的 `batchVerifyHtml()`,解析 HTML 中所有加密 SQL,调后端 API 验证全部通过后才写入文件;失败则删除文件并退出
    ↓
步骤9:输出两个文件
  - nl2sql_output/cockpit_preview.html(预览版,保留 CDN URLs)
  - nl2sql_output/cockpit.html(部署版,CDN→本地相对路径)
  - [deploy=true] 同时拷贝到 acm_www/static/cockpit.html

sqlMap 新格式(显式配置,v2):

sqlMap 每个条目使用显式 spec,不再依赖 key 名称推断类型。

const { generateCockpitChart } = require('{skillDir}/src/generate/page.js');

const sqlMap = {
  // ── sidebar:左侧项目列表,不进 CHART_CONFIG ──────────────────
  'projectList': {
    sql: 'SELECT p.ID, p.NAME, p.COMPLETE_PCT, p.STATUS FROM wsd_plan_project p WHERE p.DEL = 0 AND p.STATUS = \'active\'',
    type: 'sidebar',     // 显式声明为 sidebar
    title: '项目列表'
  },

  // ── KPI 类型图表 ───────────────────────────────────────────────
  'kpi_progress': {
    sql: 'SELECT ID, NAME, COMPLETE_PCT, PLAN_SUM, ACT_SUM FROM wsd_plan_project WHERE ID = ? AND DEL = 0',
    type: 'kpi',
    title: '项目进度',
    scope: 'project',
    spec: {
      kpiType: 'avg_percent',
      valueField: 'COMPLETE_PCT',
      unit: '%',
      thresholds: { green: 80, orange: 60 }
    }
  },

  'kpi_health': {
    sql: 'SELECT COUNT(CASE WHEN FEEDBACK_STATUS = \'2\' AND ACT_END_TIME <= PLAN_END_TIME THEN 1 END) * 100.0 / NULLIF(COUNT(*),0) AS health_score FROM wsd_plan_task WHERE PROJECT_ID = ? AND DEL = 0',
    type: 'kpi',
    title: '任务健康度',
    scope: 'project',
    spec: {
      kpiType: 'health_score',
      valueField: 'health_score',
      unit: '分',
      thresholds: { green: 80, orange: 60 }
    }
  },

  // ── chart 类型图表 ──────────────────────────────────────────────
  'riskHeatmap': {
    sql: 'SELECT PROBABILITY_LEVEL, AFTERMATH_LEVEL, COUNT(*) AS count FROM wsd_risk_register WHERE PROJECT_ID = ? AND IS_CLOSE = \'N\' GROUP BY PROBABILITY_LEVEL, AFTERMATH_LEVEL',
    type: 'chart',
    title: '风险四象限',
    scope: 'project',
    spec: {
      chartType: 'scatter',
      xAxis: 'aftermath_level',
      yAxis: 'probability_level',
      valueField: 'count',
      width: 'full'
    }
  },

  'milestone': {
    sql: 'SELECT t.TASK_NAME, t.PLAN_START_TIME, t.PLAN_END_TIME, t.ACT_END_TIME, t.FEEDBACK_STATUS, u.USER_NAME FROM wsd_plan_task t LEFT JOIN wsd_sys_user u ON t.USER_ID = u.ID WHERE t.PROJECT_ID = ? AND t.TASK_TYPE IN (2,3) AND t.DEL = 0 ORDER BY t.PLAN_END_TIME',
    type: 'chart',
    title: '里程碑节点',
    scope: 'project',
    spec: {
      chartType: 'timeline',
      startField: 'plan_start_time',
      endField: 'plan_end_time',
      labelField: 'task_name',
      width: 'full'
    }
  },

  'resourceLoad': {
    sql: 'SELECT r.ORG_ID, o.ORG_NAME, SUM(t.BUDGET_QTY)/8 AS plan_days, COUNT(DISTINCT r.ID) * ? AS capacity_days, SUM(t.BUDGET_QTY)/8 / (COUNT(DISTINCT r.ID) * ?) * 100 AS load_rate FROM wsd_plan_taskrsrc t JOIN wsd_rsrc_user r ON t.RSRC_ID = r.ID LEFT JOIN wsd_sys_org o ON r.ORG_ID = o.ID WHERE r.ORG_ID IS NOT NULL AND t.PLAN_START_TIME >= ? AND t.PLAN_START_TIME <= ? GROUP BY r.ORG_ID, o.ORG_NAME',
    type: 'chart',
    title: '资源负载分布',
    scope: 'global',    // 全局统计,不需要 projectId
    spec: {
      chartType: 'bar',
      xAxis: 'org_name',
      yAxis: 'load_rate',
      valueField: 'plan_days'
    }
  },

  'kanban': {
    sql: 'SELECT t.ID, t.TASK_NAME, t.STATUS, t.FEEDBACK_STATUS, t.COMPLETE_PCT, t.PLAN_END_TIME, u.USER_NAME AS assignee_name FROM wsd_plan_task t LEFT JOIN wsd_sys_user u ON t.USER_ID = u.ID WHERE t.PROJECT_ID = ? AND t.DEL = 0 ORDER BY t.PLAN_END_TIME',
    type: 'chart',
    title: '任务看板',
    scope: 'project',
    spec: {
      chartType: 'kanban',
      kanbanColumns: [
        { status: 'EDIT',      title: '待开始',  color: '#999' },
        { status: 'APPROVAL',  title: '审批中',  color: '#faad14' },
        { status: 'CONFIRM',   title: '进行中',  color: '#1890ff' },
        { status: 'RELEASE',   title: '已发布',  color: '#52c41a' }
      ]
    }
  }
};

const html = await generateCockpitChart(sqlMap, {
  title: '项目驾驶舱',
  projectId: 61,
  verify: true
});

sqlMap 旧格式兼容:条目为纯字符串时,内部自动推断 spec 作为兜底。

ChartSpec 字段说明

字段位置类型说明
typeentrystringkpi / chart / sidebar / table
scopeentrystringproject(需 projectId)/ global(不需要)
sqlentrystringSQL 查询语句
titleentrystring图表标题
spec.chartTypespecstringbar/line/pie/scatter/timeline/kanban/gantt/number
spec.kpiTypespecstringhealth_score/count/avg_percent/sum_money/ratio_percent
spec.valueFieldspecstring值字段名(SQL 结果中的列名)
spec.unitspecstring单位:%///
spec.thresholdsspecobject颜色阈值:{ green, orange }
spec.xAxisspecstringX 轴字段(热力图:aftermath_level)
spec.yAxisspecstringY 轴字段(热力图:probability_level)
spec.widthspecstringthird(1/3宽)/half(半宽)/full(全宽)
spec.kanbanColumnsspecarray看板列定义:[{ status, title, color }]
spec.startFieldspecstring时间线开始字段
spec.endFieldspecstring时间线结束字段
spec.paramsspecarray额外业务参数(追加到 projectId 之后的位置),如 ['2024-01-01', '2024-12-31']
paramsCountentrynumber(推荐声明) 声明该 SQL 需要的参数总数(含 projectId),生成器据此校验 ? 占位符数量,提前发现 params 不匹配问题。例:scope=project 的 SQL 含 2 个 ?,则 paramsCount: 3(1 个 projectId + 2 个额外参数)

params 校验机制(前端运行时)

问题背景:前端生成的 SQL 含 ? 占位符,但 params 数组经常为空或不完整,导致后端报 "parameter not found" 错误。

根本解决方案:技能在生成 HTML 时同时注入两个常量:

常量内容用途
ENCRYPTED_SQLAES-256-CBC 加密后的 SQL传给后端 execute 接口
SQL_PLAIN明文 SQL(与 ENCRYPTED_SQL key 一一对应)供前端 buildParams 运行时校验 ? 数量

buildParams 在每次发请求前做以下校验:

  • ERROR:SQL 有 ?params=[] → 控制台立即报错,指明具体图表和原因
  • WARNparams.length > SQL中?数量 → 控制台警告,多余参数被忽略
  • WARNscope=projectcurrentProjectId=null → 控制台警告,图表数据无法加载

生成者规范:sqlMap 条目应声明 paramsCount,与 SQL 中 ? 数量精确匹配,从源头消除歧义。

Schema 源码src/generate/chartSpecSchema.js

输出文件: {outputPath}/cockpit_{timestamp}.html

核心知识库文件(必须读取)

文件路径用途
字段映射knowledge/{dbType}/field_mapping.json字段→中文名、枚举值映射
KPI公式knowledge/shared/kpi_formulas.jsonKPI计算公式(挣值、进度偏差等)
KPI规格knowledge/shared/kpi_spec_schema.jsonKPI结构化渲染规范(类型、阈值、颜色)
安全规则knowledge/shared/sql_generation_rules.mdSQL生成铁律(必读!)
知识库扫描scripts/scanner.js自动进化引擎(域→表→SQL+spec)

达梦数据库知识库生成

技能支持两种方案生成达梦数据库知识库:

方案一:驱动方案(自动连接数据库)

自动连接达梦数据库,查询系统表生成知识库。

要求

  • Linux/Windows: 安装 dmPython 驱动 (pip install dmPython)
  • macOS: 安装 pyodbc + 达梦 ODBC 驱动

使用

# 设置环境变量
export DB_TYPE=dm
export DB_HOST=192.168.1.100
export DB_PORT=5236
export DB_NAME=ACM
export DB_USER=SYSDBA
export DB_PASSWORD=password

# 生成知识库
cd scripts/knowledge
python generate.py

方案二:转换方案(无需驱动,推荐)

通过导出的表结构文件(SQL/CSV/JSON)转换生成知识库,无需安装任何数据库驱动

适用场景

  • 无法安装驱动的环境(如 macOS、受限服务器)
  • 离线环境
  • 快速部署

使用方法

cd scripts/knowledge

# 只转换表结构(生成 tables.json)
python3 convert_knowledge.py --input /path/to/structure.sql

# 转换表结构并生成字段映射(生成 tables.json + field_mapping.json)
python3 convert_knowledge.py --input /path/to/structure.sql --mapping

# 同时导入字典数据
python3 convert_knowledge.py --input /path/to/structure.sql --mapping --dict /path/to/dict.csv

# 强制覆盖现有文件
python3 convert_knowledge.py --input /path/to/structure.sql --mapping --force

# 合并多个文件
python3 convert_knowledge.py --inputs file1.sql file2.sql --mapping --force

支持的输入格式

格式说明示例来源
.sql达梦 CREATE TABLE 脚本DM Manager 导出
.csv表结构 CSV 文件DM Manager 导出
.json自定义 JSON 格式其他工具导出

输出文件(自动覆盖):

  • knowledge/dm/tables.json - 表结构信息
  • knowledge/dm/field_mapping.json - 字段映射和枚举值

Python 接口(供 agent 调用):

import sys
sys.path.append('.')
from scripts.knowledge.convert_knowledge import convert

# 只转换表结构
result = convert('/path/to/structure.sql')

# 转换表结构并生成字段映射
result = convert('/path/to/structure.sql', generate_mapping=True)

# 同时导入字典数据
result = convert('/path/to/structure.sql', generate_mapping=True, dict_file='/path/to/dict.csv')

if result['success']:
    print(f"✅ 转换成功: {result['tables_count']} 个表")
    if 'field_count' in result:
        print(f"   字段映射: {result['field_count']} 个字段")
else:
    print(f"❌ 转换失败: {result['message']}")

多数据库支持

技能支持 MySQL、Oracle、达梦(DM)三种数据库的 SQL 生成,通过 DB_TYPE 环境变量配置。

配置方式

环境变量可选值默认值说明
DB_TYPEmysql / oracle / dmmysql目标数据库类型
DB_HOSTIP/域名192.168.3.25数据库地址
DB_PORT端口号3306数据库端口
DB_NAME库名acm_cloud_acm数据库名称
DB_USER用户名root数据库用户
DB_PASSWORD密码Wisdom83248380数据库密码
ORACLE_HOSTIP/域名192.168.3.25Oracle 主机
ORACLE_PORT端口号1521Oracle 端口
ORACLE_SERVICE服务名orclOracle 服务名
ORACLE_USER用户名acmOracle 用户
ORACLE_PASSWORD密码acmOracle 密码

方言差异说明

语法MySQLOracle达梦
标识符引号\field``"field""field"
分页LIMIT offset, count三层 ROWNUM 子查询LIMIT count OFFSET offset
字符串拼接CONCAT(a, b) / a || ba || ba || b
当前时间NOW()SYSDATESYSTIMESTAMP
NULL 替换IFNULL(a, b)NVL(a, b)IFNULL(a, b)
日期减法DATE_SUB(col, INTERVAL n DAY)col - ncol - n
日期转字符串DATE_FORMAT(col, '%Y-%m')TO_CHAR(col, 'YYYY-MM')SUBSTR(CAST(col AS VARCHAR), 1, 7) ⚠️
GROUP BY + COUNT(*)⚠️ ShardingSphere 归并 bug,前端聚合

⚠️ 达梦特别警示

  • TO_CHAR 在达梦中不适用于日期格式转换,必须用 SUBSTR(CAST(... AS VARCHAR), 1, 7)
  • GROUP BY + COUNT(*) 在 ShardingSphere 分片下有归并 bug,应返回原始数据由前端聚合

使用示例

# MySQL(默认)
DB_TYPE=mysql node src/verify/index.js "SELECT * FROM wsd_plan_task LIMIT 3"

# Oracle
DB_TYPE=oracle node src/verify/index.js "SELECT * FROM wsd_plan_task WHERE ROWNUM <= 3"

# 达梦
DB_TYPE=dm node src/verify/index.js "SELECT * FROM wsd_plan_task LIMIT 3 OFFSET 0"

生成脚本时的数据库指定

# 指定 Oracle 重新生成知识库
DB_TYPE=oracle python3 scripts/knowledge/generate.py --apply --target tables

内部 Dialect 架构

src/core/dialect.js 封装了所有数据库特定语法:

const { createDialect } = require('./core/dialect.js');
const dialect = createDialect('oracle');

dialect.quote('field_name');           // "field_name"
dialect.applyPagination(sql, 0, 20);    // Oracle 三层分页
dialect.stripPagination(sql);          // 还原为 base SQL
dialect.limit('SELECT * FROM t', 0, 3);  // LIMIT 3

输入参数

必需参数

参数类型说明
questionstring用户的自然语言问题(中文)
projectIdnumber项目上下文ID

可选参数

参数类型说明
epsIdnumberEPS上下文ID
chartTypestring图表类型:line/bar/pie/heatmap/table
chartTitlestring图表标题
outputPathstringHTML输出路径(默认 ./nl2sql_output/

鉴权说明

JWT 获取方式:

POST /api/auth/jwt/token
Body: { "userName": "admin@wisdomidata", "password": "<加密后的密码>" }

密码加密方式(AES-128-CBC):

// 加密算法见 acm_www/utils/AesUtil.js
const encryptedPassword = AesUtil.aesEncrypt('123456');
// 结果如: "fGs/2dJVyyTryPB4pNB8nQ=="

请求头格式(重要!):

Authorization: <JWT字符串>

⚠️ 注意:是直接 Authorization: <JWT>,不是 Authorization: Bearer <JWT>

Token 缓存要求(重要!) 生成的 HTML 页面必须内置 token 缓存机制,确保:

  • 页面生命周期内只请求一次 token
  • 后续请求复用缓存的 token,避免重复调用
  • 使用 Promise 缓存防止并发请求时产生多个 token 请求
// 正确示例
let jwtToken = null;
let tokenPromise = null;

async function getToken() {
    if (jwtToken) return jwtToken;
    if (tokenPromise) return tokenPromise;  // 防止并发请求时产生多个 token 请求
    tokenPromise = fetch(...).then(r => r.json()).then(d => jwtToken = d.data).finally(() => tokenPromise = null);
    return tokenPromise;
}

后端通过 HttpClientUtil.getLoginUserVo().getTenantId() 从 Gateway 设置的请求头中获取租户ID,无需客户端传入。

输入方式

方式1:直接提问

用户:请帮我查一下A项目延期未完成的任务有哪些?
→ question="A项目的延期未完成任务有哪些"

方式2:生成图表页面

用户:请帮我生成一个风险热力图页面
→ chartType="heatmap", chartTitle="风险热力图", question="项目风险四象限分布"

动态参数传递

前端调用 executeQuery 时,通过 params 数组传递动态参数:

// 无参数
executeQuery('staticSql')

// 有参数(按顺序对应 SQL 中的 ?)
executeQuery('dynamicSql', [projectId])
executeQuery('dateRangeSql', ['2026-01-01', '2026-03-27'])

// 动态日期示例
const today = new Date().toISOString().split('T')[0]  // '2026-03-27'
const startOfYear = today.substring(0, 4) + '-01-01'    // '2026-01-01'
executeQuery('resourceLoad', [startOfYear, today])

注意

  • SQL 中 ? 的数量必须与 params 数组长度一致
  • 参数顺序对应 SQL 中 ? 的出现顺序

查询模式:单项目 vs 全局

SQL 分为两种模式,生成前必须先判断用户需求属于哪种:

模式特征关键词典型问题projectId 参数
单项目"某项目"、"这个项目"、"项目61""项目61的延期任务有哪些"必须传入
全局/跨项目"全部项目"、"所有任务"、"公司级"、"全公司"、"本月汇总""本月全公司延期任务有哪些"、"查询所有延期未完成的任务"不需要 projectId

识别规则(优先级从高到低)

  1. 显式指定项目名称/ID → 单项目
  2. 包含"全部"、"所有"、"全局"、"公司级"、"本月"、"本季"、"本年" → 全局
  3. 无明确范围修饰词 → 默认单项目(需 projectId)

全局查询 SQL 示例

-- 查询全公司延期未完成的任务(无 projectId 过滤)
SELECT t.ID, t.TASK_NAME, t.STATUS, t.FEEDBACK_STATUS, t.PLAN_END_TIME,
       p.NAME AS project_name, u.USER_NAME AS assignee_name
FROM wsd_plan_task t
LEFT JOIN wsd_plan_project p ON t.PROJECT_ID = p.ID AND p.DEL = 0
LEFT JOIN wsd_sys_user u ON u.ID = t.USER_ID
WHERE t.DEL = 0
  AND t.FEEDBACK_STATUS IN ('0', '1')
  AND t.PLAN_END_TIME < CURDATE()
ORDER BY t.PLAN_END_TIME
LIMIT 20

-- 按组织统计本月延期任务数(GROUP BY org)
SELECT o.ORG_NAME, COUNT(*) AS delayed_count
FROM wsd_plan_task t
JOIN wsd_sys_org o ON t.ORG_ID = o.ID
WHERE t.DEL = 0
  AND t.FEEDBACK_STATUS IN ('0', '1')
  AND t.PLAN_END_TIME >= DATE_FORMAT(CURDATE(), '%Y-%m-01')
GROUP BY o.ORG_NAME, o.ID
ORDER BY delayed_count DESC

-- EPS级汇总(wsd_plan_project.PARENT_ID = epsId)
SELECT p.ID, p.NAME, COUNT(t.ID) AS task_count,
       SUM(CASE WHEN t.FEEDBACK_STATUS = '2' THEN 1 ELSE 0 END) AS completed_count
FROM wsd_plan_project p
LEFT JOIN wsd_plan_task t ON t.PROJECT_ID = p.ID AND t.DEL = 0
WHERE p.DEL = 0 AND p.PARENT_ID = ?
GROUP BY p.ID, p.NAME

cockpit sqlMap 中的全局 SQL 标记

当驾驶舱需要包含全局数据(如"公司风险TOP10")时,sqlMap 条目标记 scope: 'global',表示不注入 projectId:

const sqlMap = {
  // 项目级看板(需要 projectId)
  'kanban': {
    sql: 'SELECT t.ID, t.TASK_NAME, ... FROM wsd_plan_task t WHERE t.PROJECT_ID = ? AND t.DEL = 0 ...',
    scope: 'project'  // 默认
  },
  // 全局统计(不需要 projectId)
  'companyRiskCount': {
    sql: 'SELECT risk_level, COUNT(*) FROM wsd_risk_register WHERE IS_CLOSE = \'N\' GROUP BY risk_level',
    scope: 'global'
  }
};

自动进化流程(推荐)

当用户的业务需求在 sqlMap 中没有预定义时,使用 knowledgeBaseScanner.js 从知识库动态生成完整的 sqlMap 条目(SQL + 显式 spec)。

自动化入口

const { resolveDomain } = require('{skillDir}/scripts/scanner.js');

// 用户需求:"帮我生成项目驾驶舱,重点关注任务延期和资源负载"
const domain1 = '任务延期情况';
const domain2 = '资源负载分布';

const result1 = resolveDomain(domain1, { projectId: 61 });
const result2 = resolveDomain(domain2, { projectId: 61 });

if (!result1.found) {
  // 返回明确提示,让用户知道哪些不支持
  return { supported: false, suggestion: result1.suggestion };
}

// 合并到 sqlMap
const sqlMap = {
  ...(result1.found ? { [result1.entry.key]: result1.entry } : {}),
  ...(result2.found ? { [result2.entry.key]: result2.entry } : {}),
};

// 调用 cockpit 生成器
const { generateCockpitChart } = require('{skillDir}/src/generate/page.js');
const html = await generateCockpitChart(sqlMap, { projectId: 61, verify: true });

resolveDomain 内部流程

用户业务域描述(如"任务延期情况")
    ↓
classifyDomain()
    ├── DOMAIN_TABLE_MAP 精确/模糊匹配 → 表名
    └── 匹配失败 → searchTables() 扫描 tables.json
    ↓
getTableSchema() → 读取表字段结构
    ↓
inferSpecFromDomain() → 推断 type / chartType / kpiType / thresholds
    ↓
buildSQL() → 生成 SQL(自动 JOIN、WHERE、LIMIT)
    ↓
返回完整 entry: { key, sql, type, title, scope, spec }

域 → 表 映射(DOMAIN_TABLE_MAP)

域关键词说明
任务/延期/taskwsd_plan_task任务主表
项目/projectwsd_plan_project项目主表
风险/riskwsd_risk_register风险登记台账
资源/负载/resourcewsd_plan_taskrsrc资源分配表
里程碑/milestonewsd_plan_task (task_type IN 2,3)里程碑是任务子集
会议/meetingwsd_comu_meeting会议表
交付物/deliverablewsd_plan_delvassign交付物表

spec 推断规则

域关键词typechartType/kpiType说明
健康度/healthkpihealth_score健康度评分
风险数/风险敞口kpicount风险项数计数
完成率指标kpiavg_percent完成率百分比
热力/四象限chartscatter风险热力图
时间线/里程碑charttimeline里程碑时间线
看板/kanbanchartkanban任务看板
甘特/ganttchartgantt甘特图
资源负载chartbar部门资源柱图
占比/分布/piechartpie饼图
趋势/trendchartline趋势折线图

未知域处理(found: false)

const r = resolveDomain('XYZ业务域', {});
if (!r.found) {
  // r.suggestion 包含原因
  // 仍然可以尝试 searchTables(keyword) 手动查找相关表
  const alternatives = searchTables('XYZ关键词');
  if (alternatives.length > 0) {
    // 可以让用户选择对应的表
  }
}

手动扩展 DOMAIN_TABLE_MAP

如需支持新的业务域,在 knowledgeBaseScanner.jsDOMAIN_TABLE_MAP 中添加:

const DOMAIN_TABLE_MAP = {
  // ... 现有映射
  '设备保修期': { table: 'wsd_equip_warranty', comment: '设备保修' },
  '采购合同':   { table: 'wsd_bud_contract',   comment: '合同' },
};

说明knowledgeBaseScanner.js 同时扫描 tables.json,即使不在 DOMAIN_TABLE_MAP 中,也可以通过关键词搜索定位到对应表(searchTables 支持表名/表注释/字段名搜索)。

auto-evolution 持久化

resolveDomain() 首次推理成功时会自动将新条目写入 knowledge/shared/evolved_domains.json,下次相同域请求直接命中,无需重新推理。

如需清除沉淀记录,删除 knowledge/shared/evolved_domains.json 即可(技能会重新初始化为空对象)。

SQL 生成方法

主 agent 直接生成(不调用外部 LLM)

主 agent 基于知识库上下文直接生成 SQL:

  1. 读取安全规则(sql_generation_rules.md)— 了解铁律
  2. 读取字段映射(field_mapping.json)— 了解字段名和枚举值
  3. 读取 KPI 公式(kpi_formulas.json)— 了解健康度、挣值等计算逻辑
  4. 生成 SQL — 根据用户问题结合知识库上下文构造查询
  5. 验证 SQL — 调用 src/verify/index.jsverify() 确认可执行(默认 unified 模式:DB + API 同时验证)
  6. 返回结果 — 输出 SQL 和查询结果

何时使用动态参数

核心原则

  • 凡是前端需要动态传入的值,一律用 ? 占位
  • 凡是用户意图包含动态时间(今天、本月、本年),用 ? 占位
  • 静态的枚举值、确定的项目ID,可直接写死
场景示例SQL 写法
用户说"当前""截至今天的计划工时"WHERE plan_time >= ?
用户说"本月""本月风险分布"WHERE create_time >= ?
用户选择项目项目下任务统计WHERE project_id = ?
前端需动态计算当前日期范围AND plan_time >= ? AND plan_time <= ?

常见错误

  • WHERE create_time >= '2026-01-01' — 硬编码日期,不灵活
  • WHERE create_time >= ? — 前端传参,动态适配

SQL 生成示例

问题: 某项目的延期未完成任务有哪些?

生成的 SQL:

SELECT t.id, t.task_name, t.status, t.feedback_status, t.plan_end_time,
       u.user_name AS assignee_name, t.complete_pct
FROM wsd_plan_task t
LEFT JOIN wsd_sys_user u ON u.id = t.user_id
WHERE t.project_id = ?
  AND t.DEL = 0
  AND t.feedback_status IN ('0', '1')
  AND t.plan_end_time < CURDATE()
ORDER BY t.plan_end_time
LIMIT 20

带动态参数的 SQL 示例

问题:截至今天的资源负载分布

分析

  • 用户说"今天" → 需要动态日期
  • 资源负载按部门统计 → 不用按项目过滤
  • 前端需要传入起始日期和结束日期

生成的 SQL

SELECT r.ORG_ID, o.ORG_NAME as dept_name,
       COUNT(DISTINCT r.ID) as resource_count,
       SUM(t.BUDGET_QTY) / 8 as total_plan_days,
       COUNT(DISTINCT r.ID) * ? as total_capacity_days,
       SUM(t.BUDGET_QTY) / 8 / (COUNT(DISTINCT r.ID) * ?) * 100 as load_rate
FROM wsd_plan_taskrsrc t
JOIN wsd_rsrc_user r ON t.RSRC_ID = r.ID
LEFT JOIN wsd_sys_org o ON r.ORG_ID = o.ID
WHERE r.ORG_ID IS NOT NULL
  AND t.PLAN_START_TIME >= ?
  AND t.PLAN_START_TIME <= ?
GROUP BY r.ORG_ID, o.ORG_NAME
ORDER BY load_rate DESC
LIMIT 10

前端调用

const today = new Date()
const startOfYear = new Date(today.getFullYear() + '-01-01')
const totalDays = Math.ceil((today - startOfYear) / (1000 * 60 * 60 * 24))
const workingDays = Math.round(totalDays * 252 / 365)  // 工作日 = 总天数 × 252/365

executeQuery('resourceLoad', [workingDays, workingDays, startOfYear.toISOString().split('T')[0], today.toISOString().split('T')[0]])
// params[0] → working_days(容量公式)
// params[1] → working_days(负载率公式)
// params[2] → 起始日期
// params[3] → 结束日期

资源负载算法(2026-03-30 更新):

负载率 = Y / (N × X) × 100%
  Y = 预估工时人天 = SUM(BUDGET_QTY) / 8
  N = 资源人员数量 = COUNT(DISTINCT RSRC_ID)
  X = 工作日天数 = 日期范围内总天数 × (252/365)
  阈值:<70% 空闲 / 70-90% 正常 / >90% 过载

调用脚本

src/verify/index.js — SQL 验证(统一验证引擎)

提供三种验证模式,默认使用 unified(DB 直连 + API 端到端同时验证)

# 默认:unified(DB 直连 + API 同时验证,以 DB 为准)
node {skillDir}/src/verify/index.js --sql "SELECT ..."

# 仅 MySQL 直连
node {skillDir}/src/verify/index.js --sql "SELECT ..." --source db

# 仅 HTTP API
node {skillDir}/src/verify/index.js --sql "SELECT ..." --source api

批量 HTML 端到端验证(Cockpit 页面生成后自动调用,无需手动):

const { batchVerifyHtml } = require('{skillDir}/src/verify/index.js');
const result = await batchVerifyHtml('/path/to/cockpit.html', { projectId: 61 });
// result: { passed, failed, skipped, results }
if (result.failed > 0) throw new Error('HTML 端到端验证失败');

统一返回格式

{
  "ok": true,
  "rows": 3,
  "fields": ["ID", "TASK_NAME", "PLAN_END_TIME"],
  "sampleRows": [...],
  "sources": {
    "mysql": { "ok": true, "rows": 3, "fields": [...], "error": null },
    "api":   { "ok": true, "rows": 3, "error": null }
  },
  "error": null
}

src/security/encryptSql.js — AES-256-CBC 加密

对 SQL 进行加密,用于嵌入 HTML 页面:

node {skillDir}/src/security/encryptSql.js encrypt "SELECT * FROM ..."

返回:

{
  "ciphertext": "a1b2c3...",
  "iv": "x9y8z7..."
}

⚠️ 重要:加密逻辑不得自行重写

加密算法为 AES-256-CBC + HMAC-SHA256(Encrypt-then-MAC),实现细节在 src/security/encryptSql.js 中。其他脚本需要批量加密 SQL 时,必须 require 该模块,禁止自行实现加密逻辑。

// ✅ 正确:从 src/security/encryptSql.js 引入
const { encrypt } = require('{skillDir}/src/security/encryptSql.js');
const { ciphertext, iv } = encrypt(sql);

// ❌ 错误:自行实现(会导致 HMAC 验签失败)
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
// ...

src/generate/page.js — 页面生成器(流程二 + 流程三)

支持单图表和驾驶舱两种生成模式:

单图表模式(流程二):

node {skillDir}/src/generate/page.js "<sql>" --title "<标题>" --type <chartType>

驾驶舱模式(流程三):

node {skillDir}/src/generate/page.js \
  --template cockpit \
  --charts projectList.sql,kpi_health.sql,riskHeatmap.sql \
  --title "项目驾驶舱" \
  --project-id 61 \
  --output cockpit.html

注意:--charts 参数接收的是 SQL 文件路径,文件内容由主 agent 预先准备。

模板管理

模板文件位置

文件路径说明
单图表模板skills/templates/html_page_template.html技能自带,始终不变
驾驶舱基线模板skills/templates/cockpit_template.html首次生成后存入,布局重构时更新
驾驶舱当前模板workspace/templates/cockpit_current.html用户当前最新版本,实时跟随用户调整

模板加载优先级

workspace/templates/cockpit_current.html   ← 优先(用户当前版本)
    ↓ 不存在时
skills/templates/cockpit_template.html    ← 降级(技能基线)

模板生命周期

场景触发条件结果
首次生成用户首次提出驾驶舱需求(附带材料)生成 → 存入 cockpit_current.html + cockpit_template.html
数据更新用户只换了数据范围/时间读取 cockpit_current.html → 注入新数据
局部微调用户说"左侧调窄/颜色调深"修改 cockpit_current.html → 下次输出即反映调整
布局重构用户换了布局设计或加 --regenerate重新分析材料 → 覆盖 cockpit_current.html + cockpit_template.html

🚨 模板强制规范(所有模板必须遵守)

无论内置模板还是 regenerate 新生成的模板,无论驾驶舱还是单图表,都必须包含鉴权组件。 这是技能层面的设计约束,不满足则生成流程报错退出。

通用规范(所有模板类型)

必须包含的 JS 函数
函数说明
getToken()三级优先级获取 token:currentToken 缓存 → sessionStorage → 弹窗登录
doLogin()调用 ${API_AUTH_BASE}/jwt/token 登录,写入 sessionStorage.setItem('token')
必须包含的 DOM / 变量
组件说明
id="loginModal"登录弹窗 DOM
const API_AUTH_BASE = '{{API_AUTH_BASE}}'认证接口变量声明
CryptoJS CDN<script src="...crypto-js...">,用于 AES 密码加密
sessionStorage.setItem('token', ...)登录成功后必须写入
sessionStorage.getItem('token')getToken() 必须读取以复用 ACM token
tokenPromise 并发保护if (tokenPromise) return tokenPromise
禁止事项
  • ❌ 在 doLogin() 的 body 中硬编码 password 字符串
  • ❌ 使用字符串拼接构造 ${API_BASE}/jwt/token(须用 ${API_AUTH_BASE} 变量)

驾驶舱模板专用

占位符
占位符说明
{{ENCRYPTED_SQL}}AES-256-CBC 加密后的 SQL,JSON 格式(key → {ciphertext, iv})
{{SQL_PLAIN}}明文 SQL(供 buildParams 运行时校验 ? 数量),JSON 格式
{{CHART_CONFIG}}图表配置数组,JSON 格式
{{API_BASE}}API 基础路径
{{API_AUTH_BASE}}认证 API 基础路径
JS 函数
函数说明
executeQuery(key, params)调后端 execute 接口,携带 Authorization header
buildParams(cfg)组装参数数组(含 ? 占位符数量校验),projectId 置于最前面
loadAllData()加载所有图表数据

单图表模板专用

占位符
占位符说明
{{ENCRYPTED_SQL}}AES-256-CBC 加密后的 SQL
{{IV}}AES 加密 IV
{{API_BASE}}API 基础路径
{{API_AUTH_BASE}}认证 API 基础路径
{{CHARTS_CONFIG}}图表配置
JS 函数
函数说明
fetchData()调 API 获取数据

校验机制

场景校验方式
内置 cockpit 模板validateTemplates() → cockpit_template.html
内置单图表模板validateTemplates() → html_page_template.html
regenerate 驾驶舱新模板validateTemplateContent(html, { type: 'cockpit' })
regenerate 单图表新模板validateTemplateContent(html, { type: 'single-chart' })

模板生成流程

触发:首次生成驾驶舱 或 布局重构

用户提供材料(cockpit.png + cockpit.xlsx 或详细文字描述)
    ↓
agent 分析材料
    ├─ 布局结构(顶部/左侧/右侧/中间区块)
    ├─ 组件类型(KPI gauge / ECharts 类型 / 侧边栏样式)
    └─ 样式定义(配色/字体/间距)
    ↓
生成完整 HTML(含 CSS/ECharts/鉴权逻辑 + 占位符)
    ↓
存入 workspace/templates/cockpit_current.html
同时更新 skills/templates/cockpit_template.html(基线)
↓
[deploy=true] 自动部署到 acm_www/static/(不存在时兜底到 nl2sql_output/;CDN 路径替换为本地相对路径)

局部微调

触发:用户说"把左侧列表宽度调窄一点"

const html = await generateCockpitChart(sqlMap, {
    projectId: 61,
    tweak: '左侧宽度调窄',
    deploy: true  // 自动部署到 acm_www/static/(不存在时兜底到 nl2sql_output/)
});
// cockpit_current.html 被更新,下次直接生效

支持的微调关键词:

关键词效果
左侧宽度调窄侧边栏宽度 → 180px
左侧宽度调宽侧边栏宽度 → 280px
KPI放大KPI 卡片尺寸放大
KPI缩小KPI 卡片尺寸缩小
颜色加深主色调 → #1a3a5c
颜色调浅主色调 → #4a90d9
去掉右侧隐藏右侧活动流区块
去掉顶部隐藏顶部导航区块

微调机制:修改直接作用于 cockpit_current.html,不影响基线模板 cockpit_template.html。用户每次微调都实时更新同一个文件。

src/generate/page.js 调用方式

const { generateCockpitChart, resolveCockpitTemplate, applyTweak } = require('{skillDir}/src/generate/page.js');

// 正常使用(读取 cockpit_current.html)
const html = await generateCockpitChart(sqlMap, {
    title: '项目驾驶舱',
    projectId: 61,
    verify: true,
    deploy: true  // 生成后自动部署(兜底目录:nl2sql_output/)
});

// 局部微调(应用 tweak 并更新 cockpit_current.html)
const html2 = await generateCockpitChart(sqlMap, {
    title: '项目驾驶舱',
    projectId: 61,
    tweak: '左侧宽度调窄'
});

// 强制重新生成模板(覆盖 cockpit_current.html + 存档旧版本)
const html3 = await generateCockpitChart(sqlMap, {
    title: '项目驾驶舱',
    projectId: 61,
    regenerate: true,
    templateContent: newTemplateHtml  // 从用户新材料生成
});

占位符规范

占位符注入内容格式
{{PAGE_TITLE}}页面标题字符串
{{CHART_CONFIG}}图表配置数组JSON 字符串
{{ENCRYPTED_SQL}}SQL 加密结果映射JSON 字符串 {"key": {"ciphertext","iv"}}
{{DEFAULT_PROJECT_ID}}默认项目 ID数字字符串
{{API_BASE}}API 基础路径URL 字符串

输出

NL→SQL 预览输出

{
  "sql": "SELECT t.id, t.task_name, t.status, t.feedback_status, t.plan_end_time FROM wsd_plan_task t WHERE t.project_id = ? AND t.DEL = 0 AND t.feedback_status IN ('0','1') AND t.plan_end_time < CURDATE() LIMIT 20",
  "explanation": "已根据您的问题生成SQL查询,共返回5条延期未完成的任务。延期判定条件:feedback_status IN ('0','1') 且 plan_end_time < 当前日期。",
  "data": [
    {"id": 1, "task_name": "XXX任务", "status": "RELEASE", "feedback_status": "1", "plan_end_time": "2026-03-01"},
    ...
  ],
  "total": 5,
  "ok": true
}

结构化 KPI 输出(给驾驶舱用)

KPI 数据建议返回结构化格式,前端根据 status 直接渲染颜色,无需二次判断:

{
  "kpi_progress": {
    "value": 87.50,
    "formatted": "87.50%",
    "status": "normal",
    "trend": "+2.3%",
    "thresholds": { "warning": 80, "danger": 60 }
  },
  "kpi_health": {
    "value": 85,
    "formatted": "85",
    "status": "normal",
    "thresholds": { "warning": 80, "danger": 60 }
  },
  "kpi_risk": {
    "value": 10,
    "formatted": "10项",
    "status": "danger",
    "thresholds": { "warning": 8, "danger": 3 }
  }
}

status 颜色映射

status颜色含义典型阈值(健康度)
normal🟢 绿色正常/达标≥80分
warning🟡 橙色轻度风险60-80分
danger🔴 红色严重问题<60分

SQL 层返回原始聚合值(不含 status),status 由驾驶舱 JS 根据 kpi_spec_schema.json 的阈值规则计算得出。

详细 Schema 定义见:knowledge/shared/kpi_spec_schema.json

HTML 页面输出

生成完整的自包含 HTML 文件,包含:

  • ECharts 5.x(CDN加载)
  • 已加密的 SQL({{ENCRYPTED_SQL}}
  • AES-256-CBC IV({{IV}}
  • JWT 鉴权令牌({{JWT_TOKEN}}
  • 指定的图表类型和配置
  • 数据获取和渲染逻辑(自动携带 Authorization header 请求后端)

输出路径:{outputPath}/{chartTitle}_{timestamp}.html

图表类型与配置

图表类型适用场景ECharts类型
line趋势图、时间序列line
bar对比图、柱状图bar
pie占比图、饼图pie
heatmap风险热力图(X/Y轴)scatter(热力配色)
table明细数据列表无(纯HTML表格)

数值显示规范

生成 HTML 页面时,数值字段必须遵循以下格式规范:

字段类型显示格式示例
百分比字段(_PCT_RATECOMPLETE2位小数 + %87.50%
金额字段(_SUM_BUDGET_COST2位小数¥1,234.56
比率字段(_RATIO2位小数 + %95.00%
计数字段(_COUNT整数(千分位分隔)1,234
普通数值2位小数123.45

百分比统计规则:

  • 百分比字段统计应计算 平均值(AVG),而非求和(SUM)
  • 示例:COMPLETE_PCT 的统计应显示"平均"而非"合计"

金额统计规则:

  • 金额字段统计应计算 求和(SUM)
  • 示例:PLAN_SUM 的统计应显示"合计"

实现参考(src/generate/page.js):

const NUMERIC_FIELD_PATTERNS = [
    { pattern: /_PCT$/i, type: 'numeric', agg: 'avg' },   // 百分比→平均
    { pattern: /_RATE$/i, type: 'numeric', agg: 'avg' },   // 比率→平均
    { pattern: /COMPLETE/i, type: 'numeric', agg: 'avg' }, // 完成率→平均
    { pattern: /_SUM$/i, type: 'numeric', agg: 'sum' },    // 金额→求和
    { pattern: /_BUDGET$/i, type: 'numeric', agg: 'sum' }, // 预算→求和
    { pattern: /_COUNT$/i, type: 'numeric', agg: 'sum' },   // 计数→求和
];

SQL 生成铁律

生成 SQL 前必须遵守:

  1. 永远不猜测,先求证事实 — 字段名/列名必须对照 tables.json 确认实际名称,字段映射文件(如 field_mapping.json)中的字段名若有误,以 tables.json 为准
  2. 只生成 SELECT 查询 — 禁止 UPDATE/DELETE/INSERT/DROP/TRUNCATE
  3. 强制租户隔离 — 后端通过 getLoginUserVo().getTenantId() 自动注入租户ID,Skill 生成的 SQL 不需要包含 AND TENANT_ID = ?,也不需要传 tenantId 参数。src/verify/index.js 本地验证时不需要 --tenant-id 参数。
  4. 强制 DEL=0 — 几乎所有表加 AND DEL = 0,但 wsd_risk_registerwsd_base_dictwsd_comu_meetingwsd_comu_meetingactionwsd_plan_taskrsrc 等无 DEL 字段的表禁止加此条件
  5. 参数化查询
    • 前端动态值用 ? 占位:WHERE project_id = ?
    • 动态日期范围用 ? 占位:AND plan_time >= ? AND plan_time <= ?
    • 前端传入 params 数组替换占位符
    • 禁止字符串拼接 SQL
  6. LIMIT 限制 — 默认 20 条,最大不超过 100 条
  7. 枚举值正确格式feedback_status='0'(字符串)、status='RELEASE'(大写)
  8. 禁止字段 — 不得查询 CREATOR/PASSWORD/TOKEN 等敏感字段

SQL 验证失败处理流程:

验证失败 → 分析错误原因 → 重新生成 SQL → 再次验证 → 直至通过 → 加密嵌入

详细规则见:knowledge/shared/sql_generation_rules.md

错误处理

错误类型处理方式
SQL 验证失败返回 valid: false + 错误信息,不执行
数据库连接失败返回错误,不生成页面
加密失败返回错误,不生成页面

目录结构

skills/data-query/
├── SKILL.md                       # 本文件
├── database_specs/                 # 数据库规范文档
│   ├── dialect/
│   │   ├── DM.md                  # 达梦数据库规范
│   │   ├── MySQL.md               # MySQL 数据库规范
│   │   └── Oracle.md              # Oracle 数据库规范
│   └── sharding/
│       └── ROUTING_RULES.md       # 分片路由规则
├── knowledge/                      # 知识库数据
│   ├── dm/                        # 达梦数据库
│   │   ├── field_mapping.json     # 字段映射
│   │   └── tables.json            # 表结构
│   ├── mysql/                     # MySQL 数据库
│   │   ├── field_mapping.json     # 字段映射
│   │   └── tables.json            # 表结构
│   ├── oracle/                    # Oracle 数据库
│   │   ├── field_mapping.json     # 字段映射
│   │   └── tables.json            # 表结构
│   └── shared/                    # 共享配置
│       ├── evolved_domains.json   # 自动进化域映射
│       ├── kpi_formulas.json      # KPI计算公式
│       ├── kpi_spec_schema.json   # KPI规格定义
│       ├── sql_generation_rules.md # SQL生成规则
│       └── sharding/
│           ├── KNOWN_BUGS.md      # 已知分片Bug
│           └── TABLE_DISTRIBUTION.md # 表分布
├── prompts/
│   └── nl2sql_prompt.md           # agent 提示词模板
├── scripts/                        # 独立工具(不引用 src/ 模块)
│   ├── encrypt_for_page.js        # SQL 加密工具(Node)
│   ├── scanner.js                 # 知识库扫描器(Node)
│   └── knowledge/                  # 知识库构建脚本(Python)
│       ├── convert_knowledge.py # 通用知识库转换脚本
│       ├── generate.py            # 知识库生成脚本
│       └── requirements.txt       # Python依赖
├── src/
│   ├── core/
│   │   ├── config.js              # 配置加载(读取 config.json)
│   │   ├── dialect.js             # 数据库方言封装
│   │   ├── errors.js              # 错误处理
│   │   └── validator.js           # 验证工具
│   ├── deploy/
│   │   └── index.js               # 部署(优先 acm_www/static/,兜底 nl2sql_output/)
│   ├── generate/
│   │   ├── chartSpecSchema.js     # 图表配置Schema
│   │   ├── page.js                # 页面生成器(CLI 入口)
│   │   └── validate_page.js       # HTML 完整性校验 + auto-fix
│   ├── security/
│   │   ├── encryptSql.js          # AES-256-CBC 加密核心
│   │   └── passwordEncrypt.js     # 密码加密
│   ├── templates/
│   │   ├── index.js               # 模板管理
│   │   └── validate.js            # 模板验证
│   ├── verify/
│   │   └── index.js               # SQL 验证引擎
│   └── index.js                   # 主入口
├── templates/
│   ├── html_page_template.html     # 单图表页面模板(流程二)
│   └── cockpit_template.html       # 驾驶舱页面模板(流程三)
├── .gitignore                      # Git忽略文件
├── CHANGES.md                      # 变更记录
├── README.md                       # 项目说明
├── config.json                     # 配置文件
├── package-lock.json               # NPM依赖锁定
└── package.json                    # NPM配置

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Baoyu Danger Gemini Web

Generates images and text via reverse-engineered Gemini Web API. Supports text generation, image generation from prompts, reference images for vision input,...

Registry SourceRecently Updated
8310wjctim
General

Easypost

EasyPost — shipping labels, rate comparison, package tracking, address verification, and insurance.

Registry SourceRecently Updated
General

Update Advisor

OpenClaw update check and upgrade assistant. Triggers on phrases like "check for updates", "any new version", "is openclaw updated", "run the update", "confi...

Registry SourceRecently Updated
General

Memory Management

Manage and standardize trading decision records, extract lessons, and support history retrieval and comparison within the PAI trading system.

Registry SourceRecently Updated