elpis-core
注:抖音 “哲玄前端”,《大前端全栈实践课》
这个项目是一个基于 Koa 框架的应用程序,它使用了一系列的 loader 来加载和组织 Koa 应用的各个部分,包括
- middlewareLoader(中间件)
- routerSchemaLoader(路由参数校验)
- controllerLoader(控制器)
- serviceLoader(服务)
- configLoader(配置)
- extendLoader(扩展)
- globalMiddlewareLoader(全局中间件加载)
- routerLoader(路由)
middlewareLoader
- 中间件是 Koa 应用的基础组件,负责处理请求和响应的流程。
- 项目中的中间件加载器会自动扫描
app/middleware目录下的所有中间件文件,并将它们挂载到应用的上下文中。- 中间件的加载和组织方式使得应用可以灵活地扩展和维护,同时保持代码的清晰和可重用性。
async function middleware(app: Elpis.App) {
if (!app.businessPath) {
throw new Error('app.businessPath 不存在')
}
// 读取 app/middleware/**/*.ts 下所有文件
const middlewarePath = path.resolve(app.businessPath, `.${sep}middleware`);
const fileList = globSync(path.resolve(middlewarePath, `.${sep}**${sep}*.ts`), {
windowsPathsNoEscape: true
});
const middlewares: Record<string, any> = {};
const promiseArr = fileList.map(async (file) => {
let fileName = path.resolve(file);
// 截取路径 app/middlewares/custom-module/custom-middleware
const splitName = `middleware${sep}`;
fileName = fileName.substring(fileName.lastIndexOf(splitName) + splitName.length, fileName.lastIndexOf('.ts'))
// `-` 转 驼峰命名
fileName = getCamelCase(fileName);
const fileNames = fileName.split(sep);
let tempMiddleMares = middlewares;
// 创建 middleware 实例挂载到 app
const promiseArr = fileNames.map(async (name, index) => {
if (index === fileNames.length - 1) {
tempMiddleMares[name] = (await import(pathAdapter(file))).default(app);
return;
}
// 初始化目录
if (!tempMiddleMares[name]) tempMiddleMares[name] = {};
tempMiddleMares = tempMiddleMares[name];
})
return Promise.all(promiseArr)
});
await Promise.all(promiseArr)
app.middlewares = middlewares;
}
routerSchema
读取合并所有的 router-shema
async function routerSchema(app: Elpis.App) {
if (!app.businessPath) {
throw new Error('app.businessPath 不存在')
}
// 读取 app/router-schema/*.ts 下所有文件
const middlewarePath = path.resolve(app.businessPath, `.${sep}router-schema`);
const fileList = globSync(path.resolve(middlewarePath, `.${sep}**${sep}*.ts`), {
windowsPathsNoEscape: true
});
const routerSchema = await fileList.reduce(async (pre, cur) => {
return {
...await pre,
...(await import(pathAdapter(cur))).default
}
}, {})
// 挂载到 app
app.routerSchema = routerSchema;
}
controllerLoader
- 控制器负责处理请求,并调用相应的服务方法来完成业务逻辑。
- 项目中的控制器加载器会自动扫描
app/controller目录下的所有控制器文件,并将它们挂载到应用的上下文中。
async function controller(app: Elpis.App) {
...
// 读取 app/controller/**/*.ts 下所有文件
const controllerPath = path.resolve(app.businessPath, `.${sep}controller`);
const fileList = globSync(path.resolve(controllerPath, `.${sep}**${sep}*.ts`), {
windowsPathsNoEscape: true
});
...
// 截取路径 app/controllers/custom-module/custom-controller
...
// `-` 转 驼峰命名
fileName = getCamelCase(fileName);
const fileNames = fileName.split(sep);
let tempMiddleMares = controllers;
// 创建 controller 实例挂载到 app
const promiseArr = fileNames.map(async (name, index) => {
if (index === fileNames.length - 1) {
const controllerModule = (await import(pathAdapter(file))).default;
tempMiddleMares[name] = new controllerModule(app);
return;
}
// 初始化目录
if (!tempMiddleMares[name]) tempMiddleMares[name] = {};
tempMiddleMares = tempMiddleMares[name];
})
...
}
serviceLoader
- 服务负责处理具体的业务逻辑,它们可以被控制器调用。服务负责处理具体的业务逻辑,它们可以被控制器调用。
- 项目中的服务加载器会自动扫描
app/service目录下的所有服务文件,并将它们挂载到应用的上下文中。
async function service(app: Elpis.App) {
...
// 读取 app/service/**/*.ts 下所有文件
const servicePath = path.resolve(app.businessPath, `.${sep}service`);
const fileList = globSync(path.resolve(servicePath, `.${sep}**${sep}*.ts`), {
windowsPathsNoEscape: true
});
...
// 创建 service 实例挂载到 app
const promiseArr = fileNames.map(async (name, index) => {
if (index === fileNames.length - 1) {
const serviceModule = (await import(pathAdapter(file))).default;
tempMiddleMares[name] = new serviceModule(app);
return;
}
// 初始化目录
if (!tempMiddleMares[name]) tempMiddleMares[name] = {};
tempMiddleMares = tempMiddleMares[name];
})
...
}
configLoader
- 配置文件用于定义应用的全局配置,如数据库连接、端口号等。
- 项目中的配置加载器会自动扫描
config目录下的所有配置文件,并将它们合并到应用的上下文中。
async function config(app: Elpis.App) {
...
const configPath = path.resolve(app.baseDir, `.${sep}config`);
// 根据环境配置读取对应的配置
const currentEnv = app.appEnv!.get();
const baseConfigPath = getBaseConfigPath(configPath, currentEnv);
let defaultConfig = {};
if (fs.existsSync(baseConfigPath.default)) {
defaultConfig = (await import(pathAdapter(baseConfigPath.default))).default;
} else {
console.log('[exception]: config.default.ts 文件缺失');
}
let evnConfig = {};
if (fs.existsSync(baseConfigPath[currentEnv])) {
evnConfig = (await import(pathAdapter(baseConfigPath[currentEnv]))).default;
} else {
console.log(`[exception]: config.${currentEnv}.ts 文件缺失`);
}
// 注入到 koa 实例
app.config = Object.assign({}, defaultConfig, evnConfig);
}
routerLoader
- 路由负责将请求分发到相应的控制器方法。
- 项目中的路由加载器会自动扫描
app/router目录下的所有路由文件,并将它们挂载到应用的上下文中。
async function router(app: Elpis.App) {
...
// 读取 app/router/*.ts 下所有文件
const routerPath = path.resolve(app.businessPath, `.${sep}router`);
// 初始化 KoaRouter
const router = new KoaRouter()
const fileList = globSync(path.resolve(routerPath, `.${sep}**${sep}*.ts`), {
windowsPathsNoEscape: true
});
// 注册所有路由
const promiseArr = await fileList.map(async (file) => {
(await import(pathAdapter(file))).default(app, router);
})
// 路由兜底
// router.get(/.*/g, (ctx, next) => {
// ctx.state = 302; // 临时重定向
// ctx.redirect(app?.options?.homePath || '/index');
// });
// 把路由注册到app
app.use(router.routes());
// 用于处理请求的 OPTIONS 方法以及设置响应头中的 Allow 头部,显示允许的 HTTP 方法。
app.use(router.allowedMethods());
...
}
globalMiddlewareLoader
加载和应用全局中间件。在基于 Koa 框架的应用程序中,全局中间件是在应用程序的所有路由和请求处理之前执行的中间件。这些中间件通常用于处理跨域请求、日志记录、错误处理等通用逻辑。
使用
- service
- 定义一个service
- app -> service -> project -> project1.ts
class Controller extends BaseService {
getList(ctx: Elpis.Ctx) {
this.app.logger.info(ctx.request.body);
return [
{
id: '1',
name: 'project1'
},
{
id: '2',
name: 'project2'
},
{
id: '3',
name: 'project3'
},
]
}
}
- controller
- 定义一个控制器
- app -> controller -> project -> project1.ts
class Controller extends BaseController {
/**
* 获取项目列表
* @param ctx 上下文
*/
async getList(ctx: Elpis.Ctx) {
const { project: projectService } = this.app.services!;
const res = await projectService.project1.getList(ctx);
this.success(ctx, res);
}
}
export default Controller;
- router
- 定义一个路由
- app -> router -> project.ts
function router2(app: Elpis.App, router: Elpis.Router) {
const { project: projectController } = app.controller || {};
router.get('/project', projectController.project1.getList.bind(projectController.project1));
router.post('/project', projectController.project1.getList.bind(projectController.project1));
}
- middleeware
- 针对每个路由参数验证中间件使用
- app -> middleware -> api-params-verify.ts
function ApiSignVerify(app: Elpis.App): Elpis.AppUse {
// 使用 avj 验证接收的参数是否符合对应路由的 json-schema 规则
const ajv = new Ajv();
return async (ctx, next) => {
const { path, method, params } = ctx;
if (!path.includes('/api')) {
return await next();
}
const { body, query, headers } = ctx.request;
app.logger?.info(`[${method} ${path}]: body: ${JSON.stringify(body)}`);
app.logger?.info(`[${method} ${path}]: query: ${JSON.stringify(query)}`);
app.logger?.info(`[${method} ${path}]: params: ${JSON.stringify(params)}`);
// 获取对应路径的 schema 规则
const schema = app.routerSchema[path]?.[method.toLowerCase()];
const appendValidate = {
body: body,
query: query,
params,
}
if (!schema) {
return await next();
};
const schemaKeys = Object.keys(schema) as (keyof typeof appendValidate)[];
const validateArr = schemaKeys.map(async (sKey) => {
const validate = await ajv.compile(schema[sKey]);
const flag = await validate(appendValidate[sKey]);
if (!flag) return Promise.reject({
key: sKey,
message: validate.errors
});
return flag;
});
await Promise.all(validateArr).then(() => {
next();
}).catch((err) => {
let message = 'request validata fail';
if ('key' in err) {
const msg = err.message[0];
message = `[${err.key}] request validata fail: ${msg?.instancePath?.slice(1)} ${msg.message}`
}
ctx.status = 200;
ctx.body = {
success: false,
message,
code: 442,
}
return;
});
}
}
- globalMiddleware.ts
app -> middleware.ts
function middleware(app: Elpis.App) {
...
// 参数合法性校验中间件
app.use(app.middlewares?.apiParamsVerify);
...
}
总结
本项目是基于学习的目的搭建的,实现了自动扫描和加载应用中的各种组件,为开发者提供了一个高效、可扩展且易于维护的应用程序框架。