/************************************************************************ * Copyright (c) 2023 sunking framework * Author : Shao * Mail : yi-shaoye@163.com * Date : 2021-10-28 * Use : 加载模块 ************************************************************************/ import * as fs from 'fs'; import myLogger from '@wingyi8/sk-logger'; const logger = myLogger.getLogger('sunking/util/loader'); const httpMethodRegex = /\.(connect|delete|get|head|options|patch|post|put|trace)/; /** * 加载路径下的模块. * @param {String} mpath 模块的路径 加载路径下的所有文件,如果路径包含子目录,则递归加载。 * @param {String} prefix 默认路由前缀。 * @param {String} method 默认通讯方式。 */ export function load(mpath: string, prefix: string = "/", method: string = 'all', callback: (handler: ServerEventHandler, index: number) => void) { if (!mpath) { logger.error('加载路由模块为空'); return; } let path = `${process.cwd()}/${mpath}`; try { path = fs.realpathSync(path); } catch (err: any) { logger.error(err.stack); return; } if (!fs.statSync(path).isDirectory()) { logger.error(`${path} 不是文件夹.`); return; } loadPath(path, prefix, method, callback, 0); } /** * 加载文件 * @param dir 文件路径 */ function loadPath(dir: string, parent: string, method: string, callback: (handler: ServerEventHandler, index: number) => void, total: number) { let files = fs.readdirSync(dir); if (files.length === 0) { logger.error(`${dir} 目录为空`); return 0; } if (dir.charAt(dir.length - 1) !== '/') { dir += '/'; } let fp: string, fn: string, m, count = 0; for (let i = 0, l = files.length; i < l; ++i) { fn = files[i]; fp = dir + fn; // 遍历子文件夹 if (fs.statSync(fp).isDirectory()) { count += loadPath(fp, `${parent}/${fn}`, method, callback, total + count); continue; } // 只加载 js/ts 文件 if (!checkFileType(fn, 'ts,mjs,js,cjs')) { continue; } try { m = require(fp); } catch (err: any) { logger.error(err.stack); continue; } if (!m) { continue; } // 过滤文件名 let fileName = fn .replace(/\.[A-Za-z]+$/, "") .replace(/\[\.{3}]/g, "**") .replace(/\[\.{3}(\w+)]/g, "**:$1") .replace(/\[(\w+)]/g, ":$1"); // 获取通讯方式类型 let curMethod = method; const methodMatch = fileName.match(httpMethodRegex); if (methodMatch) { fileName = fileName.slice(0, Math.max(0, methodMatch.index)); curMethod = methodMatch[1]; } // 过滤路由 let url = `${parent}/${fileName}` url = url.replace(/\/index$/, "").replace(/\/\//, "/") || "/"; // 获取描述 let description = m['description'] || ''; // 获取处理函数 let cls = m['default']; if (!cls) { logger.error(`获取路由处理函数为空 ${url}`); continue; } callback({ 'url': url, 'handler': cls, 'method': curMethod, 'description': description }, total + count); count++ } return count; } /** * 检测文件后缀 * @param fn {String} 文件名 * @param patterns {String} 后缀名 比如 .js .ts 等 */ function checkFileType(fn: string, patterns: string) { let ary = patterns.split(','); let res = false; for (let n in ary) { let suffix = ary[n]; if (suffix.charAt(0) !== '.') { suffix = '.' + suffix; } let str = fn.substring(fn.length - suffix.length).toLowerCase(); if (fn.length <= suffix.length) { return false; } suffix = suffix.toLowerCase(); if (str === suffix) { res = true; break; } } return res; } /** * 服务器路由处理 */ export interface ServerEventHandler { /** * 路由 */ url: string; /** * 事件处理函数 */ handler: Function; /** * 路由器方法 */ method: string; /** * 描述 */ description: string; }