# Router 中间件

# 使用

router中间件是最为重要也是最为基础的一个中间件,它的功能是用来进行路由分发

以下是一个简单的使用

import { Sener, Router } from 'sener';

const router = new Router({
    '/demo': ({ query }) => {
        // or: 'get:/demo': ({ query }) => { // get: prefix can be ignored
        query.message = 'from get';
        return { data: query };
        // Custom headers or statusCode
        // return { data: query, headers: {}, statusCode: 200  };
    },
    'post:/demo': async ({ body }) => {
        body.message = 'from post'
        return { data: body };
    },
});

new Sener({
  port: 9000,
  middlewares: [router],
});

也可以使用json声明,以方便按模块定义路由规则,如果使用ts可以搭配接口使用

import { IRouter } from 'sener';
const user: IRouter = {/* ... */};
const comment: IRouter = {/* ... */};

const router = new Router(user, comment);

# 路由映射

Router 中间件接受一个路由映射,路由映射的值可以为函数或者对象

# key

映射的key值为路由的url,url的格式如下:

[MetaInfo][Method:]<Url>
  1. MetaInfo

路由元信息为可选部分,如果有的话会被解析成context中的meta属性,可供所有的中间件来进行处理

meta的格式为 [name1=value1&name2=value2],其中value部分如果为空,则会被赋值为 true,如 [a&b=2] 会被解析为 {a: true, b: '2'}

以下是一个代码示例

const router = new Router({
    '[a&b=2]/demo': ({ meta }) => {
        // meta: {a: true, b: '2'}
    },
});

注:当路由映射的值为对象时,key上不需要加入meta部分

  1. Method 为路由方法,也是可选参数,默认值为get,需要使用:分割
const router = new Router({
    '/aa': (ctx) => {},
    'get:/bb': (ctx) => {},
    'post:/cc': (ctx) => {},
    'delete:/dd': (ctx) => {},
});
  1. Url就是路由的path,该参数为必选

# value

路由映射的值可以使函数或对象,当为对象时,key中不可以加入meta部分,使用对象定义路由的好处是meta可以传入复杂类型的数据

// 当为函数时
export type IRouterHandler = (
    context: ISenerContext,
) => IPromiseMayBe<IHookReturn>; // IHookReturn 可以参考中间件章节

// 当为对象时
export interface IRouterHandlerData {
    handler: IRouterHandler;
    meta?: IJson;
    alias?: string[]; // 路由别名,用于将不同的url指向同一个路由
}

IRouterHandler 的参数是 context 对象,返回值即为中间件的返回值,处理逻辑与中间件一致

以下简单的例子

const router = new Router({
    '/aa': (ctx) => {},
    '/bb': {
        meta: {},
        alias: ['/bb1', '/bb2'], // bb1 和 bb2 也会指向当前路由
        handler: (ctx) => {},
    },
});

# 私有路由

私有路由为一类特殊的路由,该种路由的入参与返回值与一般路有类似,区别是该种路由更类似于工具方法,不会被外部请求所访问,主要专门用于内部使用 route helper来调用

const router = new Router({
    '#userCheck': (ctx) => {},
});

# 模糊匹配

Router 中间件支持模糊匹配,即可以匹配一类路由,然后在 handler 中根据参数进行分发

const router = new Router({
    '/api/:userId': ({params}) => {
        return {data: params.userId};
    },
    // 正则条件
    '/api2/:userId(\d+)': ({params}) => {
        // 后跟括号表示需要参数满足正则表达式,否则路由会报错
        return {data: params.userId};
    },
    // 带#前缀表示数字
    '/api3/:#userId': ({params}) => {},
    // 带!前缀表示布尔类型
    '/api4/:!isTrue': ({params}) => {},
});

# 复用路由前缀

Router 中间件支持复用路由前缀,即可以将一类路由的前缀提取出来

使用 createRoute 方法来创建一组具有相同前缀的路由,子路由使用与完整路由一致,支持method前缀、私有路由、meta、模糊匹配等,使用方式如下

import { createRoute } from 'sener';
const router = new Router({
    ...createRoute('/api/user', {
        '/info': ()=>{
            // ...
        },
        'post:/update': ()=>{
            // ...
        },
        // ...
    })
});

# router helper

router 中间件会注入以下 helper

interface IRouterHelper {
    meta: IJson; // 获取路由元信息
    params: IJson; // 获取路由模糊匹配中的参数
    index: ()=>number;
    route<T extends IHookReturn = IHookReturn>(
        url: string, data?: Partial<ISenerContext>,
    ): IPromiseMayBe<T>; // 调用其他路由,返回路由结果,一般用于复用路由逻辑
    redirect: (url: string, query?: IJson, header?: IJson) => void, // 路由重定向 (302)
}

# meta

meta在前文中已经做过了介绍,helper中的meta用来获取当前路由的元信息

const router = new Router({
    '[db]/test': ({meta}) => {
        return {data: meta};
    },
});

# params

params在前文中已经做过了介绍,helper中的params是用来获取当前路由的参数

const router = new Router({
    '/test/:id': ({params}) => {
        return {data: params};
    },
});

# route

route方法用来调用其他路由拿到返回结果,该方法可以访问私有路由

const router = new Router({
    '#test': () => {},
    'get:/test': () => {},

    '/route-test': ({route}) => {
        return route('/route-test')
    },
    '/route-test2': ({route}) => {
        const data = route('/route-test');
        // ... do something
        return {data};
    },
});

# redirect

redirect方法用进行路由重定向

const router = new Router({
    '/test1': () => {return {data: 'test1'}},
    '/test2': ({redirect}) => {
        return redirect('/test1')
    },
});

# index

index 方法会生成一个当前请求过程中递增的id,一般可以用来生成一些表示index的场景

const router = new Router({
    '/test': ({index}) => {
        const data1 = {index: index()};
        const data2 = {index: index()};
        return success({data1, data2})
    },
});

# 工具方法

  1. error & success

路由中可以使用 error 和 success 方法封装路由的返回值,定义如下

function error<T = null>(msg?: string, code?: number, data?: T): IMiddleWareResponseReturn<IRouterReturn<T>>
function success<T = any>(data?: T, msg?: string, extra?: {}): IMiddleWareResponseReturn<IRouterReturn<T>>

以下是一个简单的使用例子

import {Router, error, success} from 'sener'
const router = new Router({
    '/test': () => {
        const data = something();
        if(data === null) return error();
        return success({data});
    },
});
  1. responseXX 方法

路由handler中可以使用 responseXX 方法标识请求已经被响应,且返回值将响应结果注入 context 中。

import {Router, error, success} from 'sener'
const router = new Router({
    '/test': ({send404}) => {
        const isLogin = something();
        if(!isLogin) {
            return responseXX();
        }
        return success({data});
    },
});

后续中间件遇到已被响应的标识之后会跳过hook,如果要处理已经标识过的请求,可以将 acceptResponded 值设置为 true。

class CustomMiddle extends MiddleWare {
    acceptResponded = true;
    enter(ctx){
    }
}
  1. markReturned 方法

如果第三方中间件已经自行使用 response 对象进行了发送请求响应,那么后续 sener 再次发送或者设置header会打印一个错误。

为了防止这种情况,可以使用 markReturned 方法表示请求已提前返回响应,不需要由sener统一发送响应。同时后续中间件遇到已被发送响应的标识之后会跳过hook,如果要处理已经标识过的请求,可以将 acceptReturned 值设置为 true。

class CustomMiddle1 extends MiddleWare {

    enter({response, markReturned}){
        response.write('xxx');
        response.end();
        markReturned();
    }
}
class CustomMiddle2 extends MiddleWare {
    acceptReturned = true;
    enter({response, markReturned}){
    }
}
文档更新时间: 5/29/2024, 12:05:22 AM