wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import sql from "mssql";
import { loadDataSrcConfig, loadSqlServerConfig } from "../config/loadConfig.js";
 
/** 提取 SQL 中的 @参数名(忽略 @@ 系统符号) */
export function extractNamedSqlParams(sqlText: string): string[] {
  const re = /(?<![@])@([a-zA-Z_][a-zA-Z0-9_]*)/g;
  const seen = new Set<string>();
  let m: RegExpExecArray | null;
  while ((m = re.exec(sqlText)) !== null) {
    seen.add(m[1]!);
  }
  return [...seen];
}
 
let pool: sql.ConnectionPool | null = null;
 
export async function getSqlPool(): Promise<sql.ConnectionPool> {
  if (!pool) {
    const cfg = loadSqlServerConfig();
    pool = await new sql.ConnectionPool(cfg as sql.config).connect();
  }
  return pool;
}
 
function bindInput(request: sql.Request, name: string, value: unknown): void {
  if (value === undefined) {
    throw new Error(`缺少 SQL 参数: @${name}`);
  }
  if (value instanceof Date) {
    /** 与 SQL 中 CONVERT(DATE, …) 比较,避免与 varchar/datetime2 隐式混用报「从字符串转换」错误 */
    if (name === "startDate" || name === "endDate") {
      request.input(name, sql.Date, value);
    } else {
      request.input(name, sql.DateTime2, value);
    }
    return;
  }
  if (typeof value === "number") {
    if (Number.isInteger(value)) {
      request.input(name, sql.Int, value);
    } else {
      request.input(name, sql.Float, value);
    }
    return;
  }
  if (typeof value === "boolean") {
    request.input(name, sql.Bit, value);
    return;
  }
  request.input(name, sql.NVarChar(sql.MAX), String(value));
}
 
/**
 * 按 datasrc.json 中的 id 执行 SQL,参数与语句中的 @名称 一一对应,返回行数组(可 JSON 序列化)。
 */
export async function executeDataSourceById(
  id: string,
  params: Record<string, unknown>
): Promise<Record<string, unknown>[]> {
  const { sources } = loadDataSrcConfig();
  const src = sources.find((s) => s.id === id);
  if (!src) {
    throw new Error(`datasrc 未定义 id: ${id}`);
  }
  const paramNames = extractNamedSqlParams(src.sql);
  const p = await getSqlPool();
  const request = p.request();
  for (const name of paramNames) {
    bindInput(request, name, params[name]);
  }
  const result = await request.query(src.sql);
  return (result.recordset ?? []) as Record<string, unknown>[];
}
 
/**
 * 将 GET/POST 混合格式转为 SQL 参数(字符串中的日期字段尝试解析为 Date)。
 */
export function coerceParamsFromRequest(
  raw: Record<string, unknown>
): Record<string, unknown> {
  const o: Record<string, unknown> = {};
  for (const [k, val] of Object.entries(raw)) {
    if (val === undefined || val === "") {
      continue;
    }
    if (typeof val === "number" || typeof val === "boolean") {
      o[k] = val;
      continue;
    }
    if (val instanceof Date) {
      o[k] = val;
      continue;
    }
    const s = Array.isArray(val) ? String(val[0]) : String(val);
    if (/date$/i.test(k) || /^(start|end)Date$/i.test(k)) {
      const d = new Date(s);
      if (!Number.isNaN(d.getTime())) {
        o[k] = d;
        continue;
      }
    }
    if (/^-?\d+$/.test(s)) {
      o[k] = Number(s);
      continue;
    }
    if (s === "true" || s === "false") {
      o[k] = s === "true";
      continue;
    }
    o[k] = s;
  }
  return o;
}