forked from yoone/API
1
0
Fork 0
API/src/middleware/query-normalize.middleware.ts

141 lines
5.7 KiB
TypeScript

import { Middleware, IMiddleware } from '@midwayjs/core';
import { NextFunction, Context } from '@midwayjs/koa';
import * as qs from 'qs';
@Middleware()
export class QueryNormalizeMiddleware implements IMiddleware<Context, NextFunction> {
// 数值与布尔转换函数,用于将字符串转换为合适的类型
private toPrimitive(value: any): any {
const s = String(value);
if (s === 'true') return true;
if (s === 'false') return false;
const n = Number(s);
return Number.isFinite(n) && s !== '' ? n : value;
}
// 深度遍历对象并对字符串进行trim
private trimDeep(input: any): any {
if (input === null || input === undefined) return input;
if (typeof input === 'string') return input.trim();
if (Array.isArray(input)) return input.map(v => this.trimDeep(v));
if (typeof input === 'object') {
const out: Record<string, any> = {};
for (const key of Object.keys(input)) {
out[key] = this.trimDeep((input as any)[key]);
}
return out;
}
return input;
}
// 将路径数组对应的值赋到对象中,支持构建嵌套结构与数组
private assignByPath(target: Record<string, any>, path: string[], value: any): void {
let cur: any = target;
for (let i = 0; i < path.length; i++) {
const key = path[i];
const isLast = i === path.length - 1;
if (isLast) {
if (key === '') {
if (!Array.isArray(cur)) return;
cur.push(value);
} else {
if (cur[key] === undefined) cur[key] = value;
else if (Array.isArray(cur[key])) cur[key].push(value);
else cur[key] = [cur[key], value];
}
} else {
if (!cur[key] || typeof cur[key] !== 'object') cur[key] = {};
cur = cur[key];
}
}
}
// 解析可能为 JSON 字符串或鍵值串的输入为对象
private parseLooseObject(input: any): Record<string, any> {
if (!input) return {};
if (typeof input === 'object') return input as Record<string, any>;
const str = String(input).trim();
try {
if (str.startsWith('{') || str.startsWith('[')) {
const json = JSON.parse(str);
if (json && typeof json === 'object') return json as Record<string, any>;
}
} catch {}
const obj: Record<string, any> = {};
const pairs = str.split(/[&;,]/).map(s => s.trim()).filter(Boolean);
for (const pair of pairs) {
const idxEq = pair.indexOf('=');
const idxColon = pair.indexOf(':');
const idx = idxEq >= 0 ? idxEq : idxColon;
if (idx < 0) continue;
const key = decodeURIComponent(pair.slice(0, idx)).trim();
const valueRaw = decodeURIComponent(pair.slice(idx + 1)).trim();
obj[key] = this.toPrimitive(valueRaw);
}
return obj;
}
resolve() {
return async (ctx: Context, next: NextFunction) => {
const raw = String((ctx.request as any).querystring || '');
const parsed = qs.parse(raw, { allowDots: true, depth: 10, ignoreQueryPrefix: false, comma: true });
const query = { ...(ctx.request.query || {}), ...(parsed as any) } as Record<string, any>;
const trimmedTop: Record<string, any> = {};
for (const k of Object.keys(query)) {
const v = (query as any)[k];
trimmedTop[k] = typeof v === 'string' ? String(v).trim() : v;
}
Object.assign(query, trimmedTop);
// 解析 where 对象,支持 JSON 字符串与括号或点号语法
const hasWhereInput = (query as any).where !== undefined;
let whereObj: Record<string, any> = this.parseLooseObject((query as any).where);
for (const k of Object.keys(query)) {
if (k === 'where') continue;
if (k.startsWith('where[') || k.startsWith('where.')) {
const pathStr = k.replace(/^where\.?/, '').replace(/\]/g, '').replace(/\[/g, '.');
const path = pathStr.split('.');
const val = this.toPrimitive((query as any)[k]);
this.assignByPath(whereObj, path, val);
}
}
const hasWhereBracketKeys = Object.keys(query).some(k => k.startsWith('where[') || k.startsWith('where.'));
if (hasWhereInput || hasWhereBracketKeys) (query as any).where = this.trimDeep(whereObj);
// 解析 order 对象,支持 JSON 字符串与括号或点号语法
const hasOrderInput = (query as any).order !== undefined;
let orderObj: Record<string, any> = this.parseLooseObject((query as any).order);
for (const k of Object.keys(query)) {
if (k === 'order') continue;
if (k.startsWith('order[') || k.startsWith('order.')) {
const pathStr = k.replace(/^order\.?/, '').replace(/\]/g, '').replace(/\[/g, '.');
const path = pathStr.split('.');
const val = this.toPrimitive((query as any)[k]);
this.assignByPath(orderObj, path, val);
}
}
const hasOrderBracketKeys = Object.keys(query).some(k => k.startsWith('order[') || k.startsWith('order.'));
if (hasOrderInput || hasOrderBracketKeys) (query as any).order = this.trimDeep(orderObj);
// 将常见分页参数转换为数字类型
if (query.page !== undefined) (query as any).page = Number(query.page);
if ((query as any).page_size !== undefined) (query as any).page_size = Number((query as any).page_size);
if ((query as any).per_page !== undefined) (query as any).per_page = Number((query as any).per_page);
if ((query as any).customer_id !== undefined) (query as any).customer_id = Number((query as any).customer_id);
ctx.request.query = query as any;
(ctx as any).query = query as any;
return await next();
};
}
static getName(): string {
return 'queryNormalize';
}
static getPriority(): number {
// 优先级靠前,优先处理查询参数
return -1;
}
}