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

testfornapm

v5.0.0-r4

Published

the lib for mapping your javascript class to api service. support map Class's static methods merge into graphQL resolver

Downloads

4

Readme

概要

  • class2api帮把Javascript Class类的静态方法自动向外映射为API接口,创建独立的应用
  • 也可以整合到现有的Express应用中,挂载到指定到路由下

Table of Contents generated with DocToc

从脚手架快速创建项目

    //先全局安装class2api
    $ npm i class2api -g 
    
    //从脚手架初始化新项目
    $ class2api init 

根据提示输入

$ class2api init
 脚手架模版类型:
 - base   ——精简型,不带before/after拦截器
 - normal ——普通型,带before/after拦截器、API缓存机制
 - super  ——增强型,带before/after拦截器、API缓存机制、数据库访问
 - admin  ——管理后台权限认证型,super的基础上附带管理身份权限校验
选择以上哪种模版?(base/normal/super/admin): super
给创建的新项目取个名字(superDemo27366): class2api_scaffold_super

 远程提取模版文件 ...

 请配置数据库链接:
    数据库IP(127.0.0.1): 
    数据库端口(3306): 
    数据库访问用户(root): 
    数据库密码(默认为空): 
    创建新数据库的名称/存在则忽略(class2api_demo): class2api_oooooo
    数据库编码(utf8_general_ci): 

 √ 数据库配置成功!

 √ 项目创建成功!

 开始体验:

 $ cd class2api_scaffold_super && npm install && npm start 

最终:

 //curl请求
 $ curl -d 'name=huangyong' 'http://127.0.0.1:3002/a/hello'
 
 //运行单元测试,需全局安装mocha: $ npm i mocha -g
 $ mocha test/test.run.js

class2api的由来:

  • 目前还没有一套开源的流行的、将业务类映射为API输出的框架
  • 让团队专注于业务类的逻辑实现
  • 基于Express(添加了很薄的一层路径映射),内核稳定、插件生态丰富
  • 省去了繁琐的项目环境配置,ES6、Babel等
  • Sequelize的Model定义与加载器
  • 内置缓存服务,支持缓存API方法输出的结果,并可反向控制
  • 平滑扩展,后续将扩展支持其他更高性能webServer框架

业务静态类的编写约定:

  • 类名需为命名类,不能匿名
  • 业务静态类,不能拥有实例方法(通常其实也没有需求和场景),所以请参考官方代码,在构造器中throw异常,以确保业务静态类不会被实例化
  • 类静态方法实现各业务逻辑,一个方法对应一个业务逻辑
  • 类静态方法只有一个形参,并以ES6对象解构的方式书写,调用代码可以获得命名参数形式的可读性益处
  • 类静态方法的参数(首个参数)中,框架还注入了几个属性:req:当前的请求信息对象,express的标准request,供方法内部读取;uID:用户的唯一编码,当modelSetting修饰器中指定的身份验证函数(__Auth)运行通过时出现;__nocache:调用方传入的特殊指令,当__nocache有值时,cacheAble修饰器内部会在本次请求中忽略cacheAble缓存策略,继续运行方法。
  • 为了增加API返回数据值的可扩展能力,类静态方法的返回值必须用对象形式,不能使用字符串、整数、浮点、布尔等简单值类型。
  • 某些场景,只需要API返回操作是否成功的信息,可以使用内置的GKSUCCESS([props])函数,它封装一个简单结构{success: true,...props},其中props参数是附加信息,当props是对象时,自动扩展到结构里,当props是非对象时,以msg属性扩展到结构里

如何使用

创建全新独立的接口应用

  • createServer(opts) 创建服务器(使用内建的独立Express实例) ,opts为参数项,具体如下:
    createServer({
        config:{
           apiroot:'/', //[可选],挂载微服务的根路径,如:apiroot设置为'apiv1",则业务类ClassA的method1方法对应的访问入口为: http://yourdomain/apiv1/classa/method1
           redis,       //[可选],内置API方法缓存所使用的redis实例配置参数,使用clearCache、cacheAble修饰器时必须
           cros:true,   //[可选],是否允许跨域访问
           cros_headers:[], //[可选],允许跨域访问时的headers白名单
           cros_origin:[],  //[可选],允许跨域访问时的授权源配置,默认为*。当传入有效的cros_origin参数时,以cros_origin中指定的为准
           frontpage_default: ''    //[可选],放在API方法内部获取前端的域名,与从前端请求传过来header中的frontpage合并,优先获取客户端的,其实采用此默认值。最后封装为标准url对象并绑定到API方法回调的params对象的___frontpageURL属性上
        }, 
        modelClasses,   //必需,映射的业务类列表,如:[ClassA, {model:XXX,as:'abc'}],建议以mode-as的方式修改业务类暴露的访问路径,以隐藏实际的类命名(建议)
        beforeCall,     //[可选],API接口请求之前的拦截事件,可以修改、监听post信息,以及身份的验证判断
        afterCall,  //[可选],API接口请求完成后的拦截事件,可以修改、监听返回结果,典型场景就是记录请求的日志
        custom:(expressInstence)=>{ 
            return expressInstence
        },          //[可选],对expressInstence实例进行自定义扩展,注:微服务通常是无状态的,所以不建议增加session机制
        method404       //[可选],自定义的express的路由中间件,在404场景时,可输出自己定制的返回值
    })

在现有Express应用中扩展API路由

  • createServerInRouter(opts) 创建服务器(使用外部的Express,只返回router供绑定路由),opts参数与createServer函数基本相同,除了自动忽略custom参数。可参考项目中的demo-src/server-router.js源码

接口返回值的默认结构

class2api对所有请求的返回数据结构,统一为:

{  
    err,
    result
}

其中err代表内部异常或错误,凡是GKErrors和GKErrorWrap result抛出的错误都会捕捉err ,而result保存的是业务类静态方法的执行返回结果,且约定为必须为对象结构,不能是数字、字符串、布尔值等简单数据类型

接口返回值的结构自定义

*特殊情况:如果你的系统有特殊原因,需要接口返回自定义的数据结构,可以使用以下方式来控制反转:

    class ClassB {
        constructor() {
            throw '静态业务功能类无法实例化'
        }
        static async customResponseResultStruck() {
            //TODO:.....
    
            //class2api内部约定,如API方法返回的是Function,则框架会调用函数并把其运行结果返回给客户端,以实现自定义特殊的response结构
            return () => {
                return {data: {name: 'huangyong'}, errorCode: 123}
            }
        }
    }

接管res输出操作(谨慎使用)

    class ClassB {
        constructor() {
            throw '静态业务功能类无法实例化'
        }
        static async customResponseResultStruck() {
            //TODO:.....res.write(fileStream) 
            //class2api内部约定,如API方法返回__customResp标记,则框架终止res操作,由方法内部自行控制res操作
            return {__customResp:true}
        }
    } 

自定义Appi路径

    //创建微服务对象
    createServer({
        config:{
            apiroot: '/api_v2',//如需要时,可以指定API服务的起始路径,特别是在映射路径的方式中 
        },
        //... 
    }) 

跨域配置

    //创建微服务对象
    createServer({
        config:{ 
            cros:true,
            cros_headers:['customHeader'], 
            cros_origin:['http://web.domain.com'], 
        },
        //... 
    }) 

访问内置的Redis缓存实例

  • setting_redisConfig 设置内置redis的连接参数,因为考虑到import声明自动提前的问题,为确保其他自定义业务类内部初始化时依赖redis实例的场景。建议的,最佳方法是定义个独立的init.js,并在server.js顶部第一行引用
//init.js
import {setting_redisConfig} from 'class2api'
setting_redisConfig({
    host: "127.0.0.1",
    port: 6379,
    cache_prefx:'dev_class2api_',//必须的参数,且针对每个应用不同配置,以避免各应用之间发生key碰撞与覆盖
    defaultExpireSecond:10*60   //可缺省,class2api内部的默认混存时长为1分钟,可自定义
})
  • getRedisClient 获得redis访问实例,以进行直接读取操作。注:读写时,在redis中实际存取的key会携带redis配置中cache_prefx属性定义的前缀
    let cache = getRedisClient()
    cache.set('keyABC','this is message!', (err,data)=>{})
    await cache.setAsync('keyABC','this is message!')

自定义错误常量对象

  • GKErrorWrap 错误包装器,以快速创建以下约定结构的错误信息:
return {
           _gankao: 1,//固定标志位,以区别普通的error对象
           code: errCode,//错误码,内置错误类型为负数,通过GKErrorWrap自定义的code请务必为正数
           message: `...`,//错误信息
           more: ''//详细的错误信息
       }

业务类的修饰器扩展

  • modelSetting(props) 类修饰器,将传入的props对象赋值到 Class.__modelSetting 上,并传入beforeCall事件函数中,供beforeCall函数内部访问调用 目前class2api内部约定的porps属性有:
    • __Auth:Function,与业务类相关的身份验证函数,内部进行身份判断,并返回带有uID的用户信息对象。通常在beforeCall中执行调用完成身份验证
    • __ruleCategory:{name//权限组名称,desc//权限组的备注描述}
    import {parseAdminAccountFromJWToken} from "class2api/rulehelper";
    @modelSetting({
        __Auth:async ({req})=>{
            //后台的用户验证,解析header中的jwtoken信息,调用class2api/rulehelper的解析,注意与非后台常规用户验证的区别
            let jwtoken = req.header('jwtoken') || ''
            return await parseAdminAccountFromJWToken({jwtoken})
        },
        __ruleCategory:{
            name:"文章管理",
            desc:"对文章进行新建、编辑、删除等操作"
        }
    })
    class ArticleManager {
        constructor() {
            throw '静态业务功能类无法实例化'
        }
    }

API方法缓存机制

启用缓存

  • cacheAble({cacheKeyGene:()=>{}})
    类静态方法修饰器,对修饰的API方法的调用结果进行缓存,缓存的key由cacheKeyGene函数运行时动态返回指定
    class GKModelA {
    
        @cacheAble({
            cacheKeyGene: ({name}) => {
                return `getArticle-${name}`
            }
        })
        static async getArticle({uID, name}) { 
            return {message: `getArticle.${name},from user. ${uID}`}
        }
    }

Q:如何判断某次API请求的结果是命中了缓存? A:命中了缓存的调用,在其请求的返回结果中,带有__fromCache=1属性,如:

{
    err:null,
    result:{
        name:'huangyong',
        __fromCache:1 //额外的输出标记
    }
}

Q:如果需要在某次调用接口时强制禁用(绕过)API缓存机制? A:传入__nocache=1参数,组件内部即会判断并绕过缓存,示例如:

        let options = {
            uri: remote_api,  
            body: {
                fID:123,
                __nocache:1
            },
            json: true, 
        }
        let {body} = await request.postAsync(options)

清除缓存

  • clearCache({cacheKeyGene:()=>{}}) 类静态方法修饰器, 实现在API请求完成之后清空指定key的缓存,删除的key由cacheKeyGene函数运行时动态返回指定;如果需要清除多个key,可以用反转控制的方式,交给类静态方法内部来处理,当cacheKeyGene函数返回''空字符串,会开启反转控制模式,修饰器内部会在类静态方法的第一个调用参数(按约定,类静态方法使用第一个复合对象获取所有的参数)中增加__cacheManage属性,__cacheManage是一个轻量cache访问器,提供get(akey)、set(akey, avalue, expireTimeSeconds)、delete(akey)三个方法。
    //常规的控制方式
    class GKModelA {
        @clearCache({
            cacheKeyGene: (args) => {
                let {aID} = args[0]
                return `article-${aID}`
            }
        })
        static async deleteArticle({aID}) {
            //...
            return GKSUCCESS()
        }
    }
    
    //需要清除多个关联key时,可使用反转控制机制
    class GKModelA {
        @clearCache({
            cacheKeyGene: (args) => { 
                return ''
            }
        })
        static async deleteArticle({aID, __cacheManage}) {
            //...
            if(__cacheManage){
                await __cacheManage.delete(`article-${aID}`) //删除文章缓存
                await __cacheManage.delete(`articleCategory-1`) //删除文章类别的缓存
            }
            return GKSUCCESS()
        }
    }
    

执行中断设置(开发调试用)

  • crashAfterMe(hintMsg) 修饰器,运行完类方法就人为抛出异常中断程序,调试用,生产环境下自动失效

常用内置的预设错误(code值统一为负数)

  • 位于 class2api/gkerrors 下
  • 预设错误有:
    import {GKErrors} from 'class2api/gkerrors' 
    GKErrors._TOKEN_PARSE_FAIL      //token解析失败 
    GKErrors._RULE_VALIDATE_ERROR   //权限认证过程中发生异常
    GKErrors._TOKEN_LOGIN_INVALID   //请先登录
    GKErrors._NOT_ACCESS_PERMISSION //无访问权限
    GKErrors._NOT_SERVICE       //功能即将实现
    GKErrors._PARAMS_VALUE_EXPECT//参数不符合预期
    GKErrors._NO_RESULT         //无匹配结果
    GKErrors._SERVER_ERROR      //服务发生异常
    GKErrors._NOT_PARAMS        //缺少参数

sequelize辅助方法

sequelize表Model定义

  • 位于class2api/dbhelper下
  • 模型创建与管理的助手函数空间,可参考项目中的tableloader.js示例文件
  • TableSetting
    sequelizeModel定义model时的几个扩展设置,包括: 1、TableSetting.tabelOption设置,含model的几个默认设置,paranoid默认为true,时间相关字段名定义为created_at、updated_at、deleted_at,字符集collate为utf8_general_ci 2、TableSetting.extendDateTimeVirtualFields(DataTypes, [customfields])设置,为时间字段扩展出可读性强的时间格式化后的虚字段,名称约定为'*****_display',设置内默认为created_at、updated_at扩展,第二个[customfields]参数追加需要扩展格式化的自定义时间字段
    //DemoUser.js
    import {TableSetting} from 'class2api/dbhelper';
    
    export default (sequelize, DataTypes)=> {
        const User = sequelize.define('demouser', {
            name: {type: DataTypes.STRING, allowNull: false, defaultValue: '', comment: `用户的姓名`},
            birthday: {type: DataTypes.DATE},
            ...TableSetting.extendDateTimeVirtualFields(DataTypes, ['birthday'])
        }, {
            ...TableSetting.tabelOption,
            classMethods: {
                associate: function (DataModel, ass) {
                    //User.belongsTo(DataModel.Student)
                }
            },
            comment: '学生信息'
        });
        return User;
    }

重置初始化DB

  • ResetDB
    重置数据库,即执行sequelize.sync({force: (process.env.FORCE=="1")}),内部有启动环境变量的校验,只有当传入的启动环境变量与config中mysql.reset_key的{key1,key2}相符时才执行。默认为软重置,可以通过传入FORCE=1的启动环境变量来强制重置

sequelize模型Model加载器

  • DBModelLoader 加载Model定义文件, 参考示例项目中的tableloader.js文件,代码段:
    import {DBModelLoader} from 'class2api/dbhelper'
    import _config from './config'
    import DemoUser from './tables/DemoUser'
    
    //模型定义,在aloader.init内部会动态加载指定的定位文件,替换为真实的object的value值
    export const DataModel = {
        DemoUser: DBModelLoader.define(DemoUser),
    }
    //绑定模型关系时,可能需要定义的别名
    export const ass = {
        subComment: "subComment",
        replyToUser: "replyToUser"
    }
    (async()=>{
        await DBModelLoader.INIT(_config.mysql, {model:DataModel, ass})
    })();

打印sequelize实例的方法列表

  • DBUtils
    数据库的辅助工具方法: DBUtils.dumpModelFuns(sequelizeModelClass) 打印指定模型类上的sequelize扩展的操作自身数据实例的、以及操作关联对象的各类函数方法

sequelize内置函数的引用

  • fn:Function
    sequezeli的聚合函数引用

  • col:Function
    sequezeli的列函数引用

  • literal:Function
    sql语句字面量包装函数,确保sequelize不解析此SQL字符串

  • where:Function
    sequezeli的where函数的引用

  • createTransaction:Function 创建一个sequezeli事务

执行自定义SQL语句

  • excuteSQL(sql,[replaceparam1,replaceparam2,...]) 执行指定的SQL语句,一般适用于无法用sequelize表达式的复杂查询或更新操作

权限访问控制相关

在 class2api/rulehelper 下,适用于后台管理系统的身份检验、权限校验的辅助函数库

对API方法施加控制点

  • accessRule({ruleName, ruleDesc}) 类静态方法修饰器,提供后台级的权限校验,同时为当前修饰的类静态方法标记了权限名称、描述信息。 运行原理:修饰器内部会拦截类静态方法的调用,并先向class2api.config.js中指定的远程权限认证微服务发送请求进行身份验证,根据请求返回结果来判断是否有权限继续运行对应的方法。 class2api.config.js配置中定义相关信息:
exports.config = { 
    name: 'courseService', 
    admin_rule_center: { 
        auth:"http://127.0.0.1:3002/gkrulemanager/auth", //管理用户的身份验证接口
        validator: "http://127.0.0.1:3002/gkrulemanager/validate",  //管理用户对指定权限的验证接口
        register: "http://127.0.0.1:3002/gkrulemanager/register" //权限配置表的上传注册接口(待完善),在IDE环境下使用
    }
}

修饰器向权限中心的权限校验接口(config.admin_rule_center.validator)提交以下参数信息: 1、jwtoken:jsonwebtoken版的管理员身份签名,从req参数中提取,并在header中发送,权限中心会识别解密jwtoken,从而分辨身份并校验权限 2、categoryName:权限组名称,从modelSetting修饰器的配置信息中获得 3、categoryDesc:权限组的描述信息,从modelSetting修饰器的配置信息中获得 4、ruleName:权限名称,从accessRule修饰器函数的参数中获得 5、ruleDesc:权限的描述信息,从accessRule修饰器函数的参数中获得 6、codePath:代码路径,格式如:ClassA.methodA,在框架内部获得 7、sysName:调用方系统名称,在class2api.config.js中配置

    class GKModelA {
        constructor() {
            throw '静态业务功能类无法实例化'
        }
     
        @accessRule({ruleName: '删除文章', ruleDesc: '对文章进行删除'})
        static async deleteArticle({aID}) {
            //...
            return GKSUCCESS()
        }
    }

后台管理jwtoken的身份查询

  • parseAdminAccountFromJWToken({jwtoken}) class2api内置的管理员身份校验函数,函数内将jwtoken参数会以header的方式发送给class2api.config.js中指定的config.admin_rule_center.auth接口,auth接口端返回详细的用户信息(按约定,用户信息中含有标识唯一的uID属性)

测试辅助工具

在class2api/testhelper中,测试辅助函数库,为了接近真实环境,以及确保接口调用的全流程,class2api的单元测试原则上都以http调用的方式执行,而避免用类调用。 注:当需要测试业务功能类本身时,还是可以在业务类层面直接调用,以避开微服务框架以及http通讯的干扰

  • setApiRoot 设定微服务API的domain,在WebInvokeHepler内部需要使用
  • WebInvokeHepler(usertoken)(apiPath, postParams, [apiDesc])
    向api发起一个post-API请求, postParams是post的JSON内容,apiDesc是API方法描述的函数封装(只是提高可读性,没有实际功能意义),声明了apiDesc的调用的请求的post与res结果数据,将被内部缓存,供save2Doc使用保存输出
  • ApiDesc API方法描述的函数封装,只是提高在真实入参列表中的可读性,没有实际功能意义
  • save2Doc 保存本次mocha用例中所有提供了ApiDesc参数的API请求的post入参与res返回结果
    //testhelper的最小化案例 
    import {ApiDesc, WebInvokeHepler, setApiRoot, save2Doc} from 'class2api/testhelper'
    
    let _run = {
        accounts: {
            user1: {
                token: 'token-111'
            }
        }
    };
    //通过setApiRoot,除了本地开发环境,还可以调用测试环境、正式环境的接口,这会带来极大的线上排查与校验的便利性
    const remote_api = process.env.ONLINE==='1'? `https://comment_api_test.gankao.com`
        :(process.env.ONLINE==='2'? `https://comment_api.gankao.com`
            :`http://127.0.0.1:3002`);
    //配置远程请求endpoint
    setApiRoot(remote_api);
    
    describe('接口服务', function () {
     
        after(function () {
            save2Doc({save2File:'api.MD'})
        }); 
    
        it('/a/hello', async () => {
            let response = await WebInvokeHepler(_run.accounts.user1)('/a/hello',
                {name: "haungyong"},
                ApiDesc(`hello测试方法`)
            )
            let {err, result} = response
            let {message} = result
            message.lastIndexOf('haungyong').should.be.above(-1)
        })  
    })
   

启动环境变量

  • $ process.env.NO_API_CACHE=1 强制关闭静态类的API调用缓存,使cacheAble、clearCache失效
  • $ process.env.SQL_PRINT=1 开启sequelize的SQL执行日志的打印
  • $ process.env.PRINT_API_RESULT=1 开启API方法执行的结果日志打印
  • $ process.env.StopOnAnyException=1 在发生任何Error时,暂停进程,包括Gankao自定义错误
  • $ process.env.PRINT_API_CACHE=1 打印API缓存相关的调试信息

下一步TODO:

权限控制点信息的自动采集与上传

业务类的方法注释,文档自动化