npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tarsus

v1.5.2

Published

new ts-decorator for express server

Downloads

67

Readme

目录

快速入门

// TODO

Http

快速启动

先看代码

import TarsusHttpApplication from "../../../lib/decorator/app/http";
import {LoadController, LoadInit, LoadServer, LoadStruct, LoadTaro} from "../../../lib/main_control/load_server/load_web_app";
import UserController from "./controller/UserController";
import ValidateController from "./controller/ValidateControrller";

@TarsusHttpApplication
class HttpServer{
    static main(){
        LoadController([UserController,ValidateController])
        // init
        LoadInit((app)=>{
            console.log("hello world")
        });
        LoadStruct()
        LoadTaro()
        // load
        LoadServer({
            load_ms:false
        })
    }
}

HttpServer.main()

下面是对上述代码段的解释,用 README 文件的形式来说明它是什么以及如何工作的:


这是一个 TypeScript 项目中的 HTTP 服务器应用示例,使用了 Tarsus 框架。下面将逐步解释代码的关键部分。

import TarsusHttpApplication from "../../../lib/decorator/app/http";
import {LoadController, LoadInit, LoadServer, LoadStruct, LoadTaro} from "../../../lib/main_control/load_server/load_web_app";
import UserController from "./controller/UserController";
import ValidateController from "./controller/ValidateControrller";
  • 首先,我们导入了所需的依赖项,包括 Tarsus 框架中的装饰器、函数和一些自定义的控制器。
@TarsusHttpApplication
class HttpServer {
  • 紧接着,我们定义了一个名为 HttpServer 的类,并使用装饰器 @TarsusHttpApplication 来将这个类标记为 Tarsus HTTP 应用程序。
static main() {
  • 在类中,我们定义了一个名为 main 的静态方法。这个方法将作为应用程序的入口点。
LoadController([UserController, ValidateController])
  • LoadController 函数用于加载控制器,这里传入了 UserControllerValidateController,它们将用于处理 HTTP 请求。
LoadInit((app) => {
    console.log("hello world")
});
  • LoadInit 函数用于初始化应用程序,这里我们简单地打印 "hello world",你可以在这里执行应用程序的初始化操作。
LoadStruct()
LoadTaro()
  • 接下来,我们调用 LoadStructLoadTaro 函数,这些函数用于加载后续微服务的结构体和相关的类。
LoadServer({
    load_ms: false 
})
  • 最后,我们调用 LoadServer 函数,启动 HTTP 服务器。这里传入了一个配置对象,load_ms 设置为 false,可能是用来控制是否加载微服务。
HttpServer.main()
  • 最后,我们调用 main 方法来启动整个应用程序。

这段代码展示了一个使用 Tarsus 框架的简单 HTTP 服务器应用程序,其中包括加载控制器、初始化应用程序和启动服务器等步骤。你可以根据你的需求自定义控制器和初始化操作。

路由

import {Controller, Get, INVOKE, Post} from "../../../../lib/decorator/http/router";
import {Limit, limitType} from "../../../../lib/decorator/interceptor/Limit";
import Ret from '../utils/ret'
import { $Transmit } from "../../../../lib/main_control/proto_base";
@Controller("/user")
class UserController {

    @Get("/list")
    getUserList(req) {
        return Ret.success("hello world")
    }

    @INVOKE("/invoke")
    invoke(req,res){
        debugger;
        $Transmit(req,res);
    }

    @Get("/limit_test")
    @Limit(limitType.ROUTER,2,10000) // 10秒内2次最大限制 单个路由
    async limitTest(){
        return {
            code:0,
            message:"success"
        }
    }

    @Post("/ip_limit_test")
    @Limit(limitType.IP,2,10000) // 10秒内2次最大限制
    async IpLimitTest(){
        return {
            code:0,
            message:"success"
        }
    }
}

export default UserController;

以下是对上述代码的进一步解释:

import { Controller, Get, INVOKE, Post } from "../../../../lib/decorator/http/router";
import { Limit, limitType } from "../../../../lib/decorator/interceptor/Limit";
import Ret from '../utils/ret'
import { $Transmit } from "../../../../lib/main_control/proto_base";
  • 在这一部分,我们导入了一些依赖项。具体包括 Tarsus 框架中的装饰器和拦截器,还有一些自定义的实用工具。
@Controller("/user")
class UserController {
  • 接下来,我们定义了一个名为 UserController 的类,并使用 @Controller("/user") 装饰器来将这个类标记为一个控制器,该控制器将处理与 "/user" 路径相关的 HTTP 请求。
@Get("/list")
getUserList(req) {
    return Ret.success("hello world")
}
  • UserController 类中,我们定义了一个名为 getUserList 的方法,使用 @Get("/list") 装饰器来指定这个方法处理 GET 请求,并且与 "/list" 路径相关。在这个方法中,它返回一个成功响应,内容为 "hello world"。
@INVOKE("/invoke")
invoke(req, res) {
    $Transmit(req, res);
}
  • invoke 方法使用 @INVOKE("/invoke") 装饰器,这个接口与微服务有关,我们后续再接受。在方法内部调用 $Transmit 函数来处理请求和响应。
@Get("/limit_test")
@Limit(limitType.ROUTER, 2, 10000) // 10秒内2次最大限制 单个路由
async limitTest() {
    return {
        code: 0,
        message: "success"
    }
}
  • limitTest 方法使用了 @Get("/limit_test") 装饰器,表示它处理 GET 请求,并且与 "/limit_test" 路径相关。此外,它还使用了 @Limit(limitType.ROUTER, 2, 10000) 装饰器,这表示对于单个路由,它设置了一个限制,即在 10 秒内最多允许 2 次请求。该方法返回一个包含 code 和 message 的对象,表示成功响应。
@Post("/ip_limit_test")
@Limit(limitType.IP, 2, 10000) // 10秒内2次最大限制
async IpLimitTest() {
    return {
        code: 0,
        message: "success"
    }
}
  • IpLimitTest 方法使用了 @Post("/ip_limit_test") 装饰器,表示它处理 POST 请求,并且与 "/ip_limit_test" 路径相关。它也使用了 @Limit(limitType.IP, 2, 10000) 装饰器,这表示在 10 秒内最多允许 2 次 IP 地址相关的请求。该方法同样返回一个包含 code 和 message 的对象,表示成功响应。

管道

// ValidateController.ts
import { Request } from "express";
import { Controller, Post } from "../../../../lib/decorator/http/router";
import { UsePipe } from "../../../../lib/decorator/http/pipe";
import { TestValidatePipe } from "../pipe/ValidatePipe";

@Controller("validate")
class ValidateController {
    @Post("list")
    @UsePipe(new TestValidatePipe())
    getList(req:Request){
        const body = req.body;
        return body;
    }
}


export default ValidateController
// ValidatePipe.ts
import { Request } from 'express';
import {TarsusPipe} from '../../../../lib/decorator/http/pipe';
import {TarsusValidate, plainToInstance} from '../../../../lib/decorator/interceptor/Validate';
import UserValidateObj from '../dto/User';
import { PipeError } from '../../../../lib/decorator/http/error';

class TestValidatePipe implements TarsusPipe{
    handle(req:Request){
        try{
            req.body = plainToInstance(req.body,UserValidateObj)
            const check = TarsusValidate(req.body)
            console.log(check);
            if(!check){
                throw PipeError();
            }
        }catch(e){
            return e
        }
    }
}

export {
    TestValidatePipe
}
// User.ts
import { DataTransferOBJ, IsNumber, IsString, MinLen, MaxLen } from "../../../../lib/decorator/interceptor/Validate";

@DataTransferOBJ()
class UserValidateObj{
    @IsNumber()
    age:number;

    @IsString()
    @MinLen(1)
    @MaxLen(10)
    name:string;
}

export default UserValidateObj

以下是关于管道(TestValidatePipe)如何校验参数的详细解释:

ValidatePipe.ts 文件中,TestValidatePipe 类实现了 TarsusPipe 接口,这是 Tarsus 框架中用于自定义请求数据验证和处理的关键部分。

handle 方法中,TestValidatePipe 对请求进行以下操作:

  1. 转换请求数据: 首先,它尝试将请求体数据(req.body)转换为一个具体的对象,这个对象是 UserValidateObj 类的实例。这是通过以下代码实现的:

    req.body = plainToInstance(req.body, UserValidateObj)

    这一行代码使用 plainToInstance 函数,将请求体数据转换为 UserValidateObj 对象。这是数据传输对象(DTO),用于定义预期的数据结构。

  2. 数据验证: 一旦数据转换完成,它使用以下代码进行数据验证:

    const check = TarsusValidate(req.body)

    这一行代码调用了 TarsusValidate 函数,对请求体数据进行验证。在验证过程中,框架会根据 UserValidateObj 类中的装饰器规则(如 @IsNumber()@IsString()@MinLen(1)@MaxLen(10))来检查请求数据是否满足这些规则。如果数据验证失败,将返回 false,否则将返回 true

  3. 处理验证结果: 最后,它检查验证的结果 check,如果验证失败(即 checkfalse),则抛出一个自定义的错误,可能是 PipeError

整个过程是一个自定义的请求数据验证和处理管道。如果请求数据不满足定义的规则,这个管道将阻止请求继续处理,并可能返回一个错误响应。这有助于确保请求数据的有效性和一致性。

拦截器

MicroService

快速启动

协议

在进行远程函数调用时,我们首先需要确定相互之间调用的参数,返回的数据结构,调用的接口等。为此,我们需要先定义其协议。 协议命名为taro, 代表 tarsus-object.该协议兼容 java,nodejs。

开发该协议的步骤

  1. 定义数据结构:首先,需要按照协议定义的数据结构来创建相关的类和结构。在这个示例中,需要创建 Basic、User、GetUserByIdReq、GetUserByIdRes、GetUserListReq 和 GetUserListRes 类。

  2. 生成对应的协议文件:使用命令 taro to ts || java ./xxx.taro

  3. 实现接口:在生成协议文件以后,同时会看到对应的协议接口,根据接口定义,实现 TaroInterFace 接口中的方法,即 getUserById 和 getUserList。这些方法应该执行与协议规定的数据交换操作。

  4. 客户端和服务器实现:通常,协议将在客户端和服务器之间用于通信。因此,需要在客户端和服务器上实现协议的数据解析和构建逻辑。客户端将使用协议发送请求,服务器将解析请求并生成响应。

  5. 数据交换:客户端和服务器将使用协议定义的数据结构来构建请求和响应对象,并进行数据的序列化和反序列化,以确保数据正确传递。

  6. 测试和验证:在实现协议之后,需要进行测试和验证,以确保客户端和服务器能够按照协议进行正确的通信。

完整的协议代码如下

// TaroUser  Created By leemulus 2023.3.21
// before cmd  taro to ts ./TaroUser.taro 

struct CommParams  {

    Basic               :   {
        1   token       :   string;
    };

    User                :   {
        1   id          :   string;
        2   name        :   string;
        3   age         :   string;
        4   fullName    :   string;
        5   address     :   string;
    };

    GetUserByIdReq      :   {
        1   id          :   int;
        2   basic       :   Basic;
    };

    GetUserByIdRes      :   {
        1   code        :   int;
        2   data        :   User;
        3   message     :   string;
    };

    GetUserListReq      :   {
        1   basic       :   Basic;
        2   ids         :   List<int>;
    };

    GetUserListRes      :   {
        1   code        :   int;
        2   data        :   List<User>;
        3   message     :   string;
    };

};


// 用户接口
interface TaroInterFace  {
    int getUserById(Request : GetUserByIdReq, Response : GetUserByIdRes);
    int getUserList(Request : GetUserListReq, Response : GetUserListRes);
};

生成后的代码如下

// after cmd taro to ts ./TaroUser.taro 
const { TarsusReadStream } = require("tarsus-cli/taro");
export class Basic {
  public token: string;
  constructor(...args: any[]) {
    const _TarsusReadStream = new TarsusReadStream("Basic", args);
    this.token = _TarsusReadStream.read_string(1);
  }
}
export class User {
  public id: string;
  public name: string;
  public age: string;
  public fullName: string;
  public address: string;
  constructor(...args: any[]) {
    const _TarsusReadStream = new TarsusReadStream("User", args);
    this.id = _TarsusReadStream.read_string(1);
    this.name = _TarsusReadStream.read_string(2);
    this.age = _TarsusReadStream.read_string(3);
    this.fullName = _TarsusReadStream.read_string(4);
    this.address = _TarsusReadStream.read_string(5);
  }
}
export class GetUserByIdReq {
  public id: number;
  public basic: Basic;
  constructor(...args: any[]) {
    const _TarsusReadStream = new TarsusReadStream("GetUserByIdReq", args);
    this.id = _TarsusReadStream.read_int(1);
    this.basic = _TarsusReadStream.read_struct(2, "Basic");
  }
}
export class GetUserByIdRes {
  public code: number;
  public data: User;
  public message: string;
  constructor(...args: any[]) {
    const _TarsusReadStream = new TarsusReadStream("GetUserByIdRes", args);
    this.code = _TarsusReadStream.read_int(1);
    this.data = _TarsusReadStream.read_struct(2, "User");
    this.message = _TarsusReadStream.read_string(3);
  }
}
export class GetUserListReq {
  public basic: Basic;
  public ids: Array<number>;
  constructor(...args: any[]) {
    const _TarsusReadStream = new TarsusReadStream("GetUserListReq", args);
    this.basic = _TarsusReadStream.read_struct(1, "Basic");
    this.ids = _TarsusReadStream.read_list(2, "List<int>");
  }
}
export class GetUserListRes {
  public code: number;
  public data: Array<User>;
  public message: string;
  constructor(...args: any[]) {
    const _TarsusReadStream = new TarsusReadStream("GetUserListRes", args);
    this.code = _TarsusReadStream.read_int(1);
    this.data = _TarsusReadStream.read_list(2, "List<User>");
    this.message = _TarsusReadStream.read_string(3);
  }
}

注册接口

我们希望微服务后台能像注册HTTP路由一样快速的注册接口,屏蔽底层的实现。为此,Tarsus提供了一系列的方法来支持这一点。 同样的,在上面的协议文件里面,执行 taro inf ts ./TaroUser.taro后, 会得到如下接口,一切看起来都像提前定义好了。

interface TaroInterFace {
  getUserById(
    Request: GetUserByIdReq,
    Response: GetUserByIdRes
  ): Promise<GetUserByIdRes>;
  getUserList(
    Request: GetUserListReq,
    Response: GetUserListRes
  ): Promise<GetUserListRes>;
}

我们只需要手动实现这些接口即可。 此外,我们还需要手动对这些类,接口进行注解定义,这看起来有些复杂,但是在熟练的使用后会发现一切都是那么自然。

@TarsusInterFace用来注册RPC接口前缀。 @TarsusMethod用来注册RPC接口方法。 **@Stream("Request", "Response")**用来注册序列化的请求和响应。

@TarsusInterFace("TaroInterFaceTest")
class TaroInterFaceImpl implements TaroInterFace {
    @TarsusMethod
    @Stream("GetUserByIdReq", "GetUserByIdRes")
    getUserById(
        Request: GetUserByIdReq,
        Response: GetUserByIdRes
    ): Promise<GetUserByIdRes> {
        return new Promise((resolve, reject) => {
            Response.code = 0;
            Response.data = {
                address: "1",
                age: "11",
                id: "11",
                fullName: "11",
                name: "11",
            };
            Response.message = Request.basic.token;
            resolve(Response);
        });
    }

    @TarsusMethod
    @Stream("GetUserListReq", "GetUserListRes")
    getUserList(
        Request: GetUserListReq,
        Response: GetUserListRes
    ): Promise<GetUserListRes> {
        return new Promise((resolve, reject) => {
            Response.code = 0;
            Response.data = Request.ids.map(el => {
                let user = new User()
                user.address = el + "address";
                user.id = el + "id";
                user.fullName = el + "fullName";
                user.name = el + "name";
                user.age = el + "age";
                return user;
            })
            Response.message = Request.basic.token;
            resolve(Response);
        });
    }
}

远程调用

还记得路由部分讲的 INVOKE$Transmit 吗? 他们作为一个网关,用来执行远程调用微服务的接口方法,如下,是调用一个RPC方法所需要的参数 当然我们不建议这么做,因为这样会有安全问题。我们可以注册对应的client端的路由,然后一个一个注册对应的方法。 避免所有参数都是明文参数。

{
    "interFace": "TaroInterFaceTest",
    "method": "getUserById",
    "data": {
        "id":"1",
        "basic":{
            "token":"testToken"
        }
    },
    "proxy":"NodeServer",
    "timeout": "60000",
    "request":"GetUserByIdReq"
}

其他

定时器

很多情况下我们需要定时器去计算一些数据,Tarsus封装了一些定时任务的装饰器。 具体用法如下:

@Schedule
class ScheduleServer {
    public userMap: UserMap = {}
    public wordMap :WordMap = {}
    public userSql = 'select * from users'
    public wordSql = 'select en_name as en_word,own_mark,user_id from words '
    @Cron("*/20 * * * *", true)
    public async UserCacheMethod() {
        const conn = await $PoolConn();
        const that = this;
        conn.query(that.userSql, function (_, resu) {
            if (!resu.length) {
                return
            }
            delete that.userMap;
            console.log("START-----------开始同步用户表", moment().format("YYYY-MM-DD"))
            that.userMap = lodash.keyBy(resu, "id")
            console.log('同步数据',JSON.stringify(that.userMap))
            console.log('同步数据',resu.length,"条")
            console.log("END-----------同步用户表结束", moment().format("YYYY-MM-DD"))

        })
    }
    @Cron("*/30 * * * *", false)
    public async WordCacheMethod() {
        const conn = await $PoolConn();
        const that = this;
        conn.query(that.wordSql, function (_, resu) {
            console.log('resu',resu);
            
            if (!resu.length) {
                return
            }
            delete that.wordMap;
            console.log("START-----------开始同步单词表", moment().format("YYYY-MM-DD"))
            const ret  = resu.map(item=>{
                item.user_name = that.userMap[item.user_id].username
                return item;
            })
            that.wordMap = lodash.keyBy(ret, "id")
            console.log('同步数据',JSON.stringify(that.wordMap))
            console.log('同步数据',resu.length,"条")
            console.log("END-----------同步单词表结束", moment().format("YYYY-MM-DD"))
        })
    }
}

配置

提供两套配置,仅供参考

// HttpClient 或者 RPC-Client端的配置
// 当纯作为http服务时,servant可以不需要
// 同时 load_Server 的 ms 配置也需要设置为 false
// server.project 为该服务的配置,用来定义 服务组/服务名 -l 语言 node java可选 @tarsus/协议 http ms 可选 -h ip地址 -p 端口地址
// server.servant 为网关层所需要的后台微服务的地址,写法与上面一致。
// server.database 不多做介绍
module.exports = {
    server: {
        project: "@TarsusDemoProject/NodeProxyDemo -l node -t @tarsus/http -h 127.0.0.1 -p 12011",
        servant: [
            "@TarsusDemoProject/NodeServer -l node -t @tarsus/ms -h 127.0.0.1 -p 12012 -w 10",
            '@TarsusDemoProject/JavaServer -l java -t @tarsus/ms -h 127.0.0.1 -p 12013 -w 10'
        ],
        database: {
            default: true,
            type: "mysql",
            host: "localhost",
            username: "root",
            password: "123456",
            database: "test_db", //所用数据库
            port: 3306,
            connectionLimit: 10,
        },
    },
};
// RPC-Server端的配置
module.exports = {
    server: {
        project: "@TarsusDemoProject/NodeServer -l node -t @tarsus/ms -h 127.0.0.1 -p 12012",
        database: {
            default: true,
            type: "mysql",
            host: "localhost",
            username: "root",
            password: "123456",
            database: "test_db", //所用数据库
            port: 3306,
            connectionLimit: 10,
        },
    },
};

维护者