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

@yuo/glede-server

v1.0.2

Published

Web业务服务器

Downloads

4

Readme

GledeServer

单例模式

目前单个项目中只能创建一个Server实例, 暂时无mono-repo的要求。

若后续使用mono-repo, 则考虑新增配置 basePath: '', 路由注册、日志记录、文档校验、数据库等操作基于basePath

基于配置启动

配置类型描述: GledeServerOpts

JSON配置案例: app.json

推荐!TS配置案例: app-config.ts

服务器日志信息 / 注册路由状态树 / api文档 (导入Apifox工具查看)

Benchmark

⚡️ 3~4x faster than Express

MacOS; Intel-i5 2.9GHz; Memory-DDR4 32 GB

压测数据

// types/index.d.ts 有详细的描述,手写配置会覆盖conf文件中的对应字段
// 优先级:代码中手写配置 > conf字段文件配置
interface GledeServerOpts {
    // ...
}

// app.ts
import { Server } from '@yuo/glede-server';

Server({ conf: 'configs/app.json' }, (err, address) => {
    if (err) {
        console.log(err);
    }
    else {
        console.log(`GledeServer is running at ${address}`);
    }
});

路由类

import { GledeRouter, Get, Post } from '@yuo/glede-server';

export class Router extends GledeRouter {
    // 注意方法不要使用箭头函数
    // 1. 依赖原型处理逻辑; 2. 注入依赖工具方便处理请求

    getAllUser(this: GledeThis, data: GledeReqData) {
        // doSomething.

        if (noPass) {
            return {
                // 1 客户端参数校验未通过, 业务无需关心
                // >= 2 自定义
                code: 2,
                data: null,
                msg: '描述错误原因'
            };
        }

        // 以下情景等价于返回 {code:0, data: null}
        // 1. 无return语句
        // 2. return null
        // 3. return;
        // 4. return undefined

        return {
            code: 0, // 0 处理成功
            data: {
                // ...
            }
        };
    }
}

通用方法

路由注册

注册不带前缀的路由

非index文件或目录会保持大小写被记录到路由中,例如示例中./api/user/index.tsuser会被注册到 /api/user/$subpath。一下示例中index是不会注册到路由中的,若注册/index则需装饰器完成需求:@Get('/index')。

routers/open?api|common/index/index.ts

routers/open?api|common/index.ts

严格注册模式

  • 除 '/' 路由外,是否携带 / 需注册不同的 RouterHandler

@Get('')@Get('/')监听的是不同的路由,

localhost:3020/userlocalhost:3020/user/ 是不同的路由

// 目录: routers/api/post
import { Post } from '../controllers';
import { GledeUtil, Get, GledeRouter } from '@yuo/glede-server'

export default class extends GledeRouter {
    /**
     * 删除动态
     */
    @Get('/del/:id') @NeedAuth('admin')
    async delPost(this: GledeThis, data: GledeReqData) {

        // Token鉴权通过, 这里可以看到用户身份
        const token = this.getToken();
        console.log(token.role, token.uid, token.exp);

        // 非身份管理 admin / super / root, 只能删除自己的文章
        if (!['admin', 'super', 'root'].includes(token.role)) {
            Post.updateOne({ uid, postId: data.params.id });
        }

        // 否则直接删除
        else {
            Post.deleteOne({ postId: data.params.id });
        }
    }
}

装饰器介绍

方法装饰器

将Handler装载至路由

  • @Get(url: string, { schema?: GledeGetSchema, version?: string })
  • @Post(url: string, { schema?: GledePostSchema, version?: string })

跨域装饰器

设置需要跨域的域名、方法、是否允许携带cookie

  • @Cors(origin: string | string[], method: string, credential?: boolean)

鉴权装饰器

身份鉴权(noauth | user | admin | super | root), 是否允许Handler处理 Default: noauth

  • @NeedAuth(role: string)

数据库操作

mongoose 操作文档

定义数据模型

📢 参考DEMO: ./tests/controllers/cat.ts cat 对应了数据库中的集合名称 cats, 起名字要使用单数!否则需要指定集合名字

import { Model } from '@/index';

// 模型数据结构
const CatSchema = {};

// 模型自定义
const CatOpts = {
    // 指定集合名, 此时集合链接到了cat, 默认是cats
    collection: 'cat',

    // 添加便捷方法, 注意不要使用箭头函数!
    // 可以这样使用:Cat.findByName('^cool').then(res => {});
    statics: {
        findByName(name: string) {
            return this.find({ name: new RegExp(name, 'i') });
        }
    }
};

export default Model('cat', CatSchema, CatOpts);

操作数据模型

import { Cat } from '@/tests/controllers/cat';

// 1. 在Cat表中插入一条数据, 后面Demo默认包裹在try-catch中
try {
    await Cat.create({
        // 插入数据格式必须是CatSchema中定义, 否则字段会被忽略
    });
}
catch (err) {/* Handle Error */}

// 2. 在Cat表中查找一条数据, 随便找一只名叫 cool_xx 且小于2岁的🐱
// 非常不推荐正则, 除非搜索过滤等场景。一般在任何语言中的实现都是最慢最耗性能的模式匹配。
// 不过有的语言实现了正则的缓存, 可能在某些场景下会快。尽量不用吧!
Cat.findOne({
    name: new RegExp('^cool_', 'i'),
    age: { $lt: 2 }
});

// 3. 在Cat表中找到一条匹配的数据,删除
Cat.deleteOne({});

// 4. 在Cat表中找到所有可以匹配删除的数据
Cat.deleteMany({});

// 5. 在Cat表中找到数据并更新, upsert默认为false, 设置为true不存在就插入
// 注意原子操作, filter, { $set: { name: '小小明' } }, options
Cat.updateOne({ name: '明' }, { $set: { name: '小明' } }, { upsert: true });
Cat.updateOne({ name: '小明' }, {}, { upsert: true });

// 所有男生, 分数 +1
Cat.updateMany({ sex: 'male' }, { $inc: { score: 1 } });

// 6. 多种操作, 一次通信。性能upup!
// [https://mongoosejs.com/docs/api/model.html#model_Model.bulkWrite]
Cat.bulkWrite([
  {
    insertOne: {
      document: {
        name: 'Eddard Stark',
        title: 'Warden of the North'
      }
    }
  },
  {
    updateOne: {
      filter: { name: 'Eddard Stark' },
      update: { title: 'Hand of the King' }
    }
  },
  {
    deleteOne: {
      filter: { name: 'Eddard Stark' }
    }
  }
]).then(({ insertedCount, modifiedCount, deletedCount }) => {
    // 1 1 1
    console.log(insertedCount, modifiedCount, deletedCount);
});

默认记录错误日志

  • 默认记录日志, 需要创建对应的目录路径
  • 根目录创建文件: logs/error.log

请求需通过Schema校验

// 新建或修改路由文件
// mkdir routers/${api | openapi}/${router | routerDir/index.ts}
// api|openapi目录下存放路由可以是ts文件或目录, 文件内和目录内的Schema定义可相互引用
// 示例 /routers/api/user/index.ts
import { getAllUsersSchema, getAllUsersSchemaV2 } from './schema';

export Router extends GledeRouter {
    // version是接口的版本用于线上并行, 可选:默认 '', 如果出现版本区分可填写 v1, v2, ...
    // schema是参数的拦截校验, 必选:1. 客户端字段安全拦截 2. 增加序列化的性能10%~15% 3. 生成接口文档协同开发
    // match: /api/v1/:id
    @Get('/:id', { version: 'v1', schema: getAllUsersSchema }) @Cors()
    getAllUsers(this: GledeThis, data: GledeReqData): GledeResData {
        return {
            code: 0,
            data: {
                // ...
            }
        }; 
    }

    @Get('/:id', { version: 'v2', schema: getAllUsersSchemaV2 })
    @Post('/:id', { schema})
    @NeedAuth('super') @Cors('https://philuo.com', 'GET,POST')
    getAllUsersV2(this: GledeThis, data: GledeReqData): GledeResData {
        return {
            code: 0,
            data: {
                // ...
            }
        };
    }
}

集成功能

Token签发与验证

  • 实现分发(sign, unsign)

  • 实现校验(verify)

if not 过期 -> if not 快过期 -> if match 身份 -> if not 是否篡改 -> if not blklist -> ok
else fail -> else -> else fail -> else fail -> else fail
             if ok then blklist and return data with new token
             if not ok then fail

捆绑数据库链接

  • mongoose

  • ioredis

区域检测

@yuo/ip2region

SMTP邮件发送

nodemailer

黑名单

  • ip blklist

判黑条件:超管手动添加 / 时间段频率 / 单日访问次数

  • token blklist

判黑条件:超管手动添加 / 即将过期且验证通过的Token

定时任务

@yuo/node-cron

TODO

定时任务

  • 黑名单持久化

前期直接覆盖磁盘文件 后面要开线程做去重, 并且可能需要分片

  • 邮件通知警告

触发条件: 程序判定新增IP黑名单