import { Middleware, IMiddleware } from '@midwayjs/core'; import { NextFunction, Context } from '@midwayjs/koa'; import * as qs from 'qs'; @Middleware() export class QueryNormalizeMiddleware implements IMiddleware { // 数值与布尔转换函数,用于将字符串转换为合适的类型 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 = {}; for (const key of Object.keys(input)) { out[key] = this.trimDeep((input as any)[key]); } return out; } return input; } // 将路径数组对应的值赋到对象中,支持构建嵌套结构与数组 private assignByPath(target: Record, 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 { if (!input) return {}; if (typeof input === 'object') return input as Record; 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; } } catch {} const obj: Record = {}; 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; const trimmedTop: Record = {}; 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 = 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 = 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; } }