0.14.0 • Published 2 months ago

jj.js v0.14.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

jj.js

jj.js

A super simple lightweight NodeJS MVC framework(一个超级简单轻量的NodeJS MVC框架)

项目介绍

无论什么语言什么框架的程序,类库的使用都离不开require、import、include等导入模块,不用时,还得删除。而本框架基于proxy实现了类库自动加载、懒加载和Class自动实例化及单例化技术,所有的类库,想用直接调用,系统会自动导入。

项目特性

  1. 系统架构为经典的MVC模式,模仿Thinkphp5,很容易上手
  2. 系统类库、用户类库都支持自动加载、懒加载、Class自动生成单实例
  3. 支持应用级、路由级、控制器级三级中间件,方便插件及二次开发
  4. 支持单应用和多应用两种运行模式
  5. 基于jsdoc,提供完整的代码提示,自动生成应用端types类型文件

项目地址

项目地址:https://github.com/yafoo/jj.js

码云镜像:https://gitee.com/yafu/jj.js

官网地址:https://me.i-i.me/special/jj.html

安装

npm i jj.js

运行环境要求:node.js >= v12.7.0

Hello world !

1、创建控制器文件 ./app/controller/index.js

const {Controller} = require('jj.js');

class Index extends Controller
{
    async index() {
        this.$show('Hello jj.js, hello world !');
    }
}

module.exports = Index;

2、创建应用入口文件 ./server.js

const {App, Logger} = require('jj.js');
const app = new App();

app.run(3000, '0.0.0.0', function(err){
    !err && Logger.log('app', 'http server is ready on 3000');
});

3、运行命令

node server.js

4、浏览器访问 http://127.0.0.1:3000,页面输出 Hello jj.js, hello world !

5、在Stackblitz中打开 Hello world !

开发手册

应用目录结构

├── app             //应用目录 (非必需,可改名)
│  ├── controller   //控制器目录 (非必需,可改名)
│  │  └── index.js  //控制器
│  ├── view         //模板目录 (非必需,可改名)
│  │  └── index     //index控制器模板目录 (非必需,可改名)
│  │     └── index.htm //模板
│  ├── middleware //中间件目录 (非必需,不可改名)
│  ├── model        //模型目录 (非必需,可改名)
│  ├── pagination   //分页目录 (非必需,可改名)
│  ├── logic        //逻辑目录 (非必需,可改名)
│  └── ****         //其他目录 (非必需,可改名)
├── app2            //应用2目录 (非必需,可改名)
├── common          //公共应用目录 (非必需,可改名)
├── config          //配置目录 (非必需,不可改名)
│  ├── app.js       //APP配置 (非必需,不可改名)
│  ├── db.js        //数据库配置 (非必需,不可改名)
│  ├── routes.js     //路由配置 (非必需,不可改名)
│  ├── view.js     //模版配置 (非必需,不可改名)
│  ├── cache.js     //缓存配置 (非必需,不可改名)
│  └── ****         //其他配置 (非必需,可改名)
├── public          //静态访问目录 (非必需,可改名)
│  └── static       //css image文件目录 (非必需,可改名)
├── node_modules    //nodejs模块目录
├── server.js       //应用入口文件 (必需,可改名)
└── package.json    //npm package.json

应用目录介绍:

  • config: 应用配置目录,系统的所有配置参数都放这里,也可以简化为一个config .js文件,所有配置参数会覆盖框架默认参数。其中config这个名字不允许修改。
  • app、app2、common: 为应用目录,common为公共应用目录,可以存放公共的model、logic或其他的文件,app为默认访问的应用。jj.js框架支持单应用和多应用运行模式,在./config/app.js中把app_multi设置为true即为多应用模式,此时可以创建app2、app3更多应用。框架默认为单应用模式。
  • public:静态资源目录,主要存放css文件、js文件、图片等静态资源。在./config/app.js中通过static_dir参数可以设置或更改静态目录名字,为空时,则关闭静态访问(系统不会加载静态资源访问的逻辑,最大节省内容,也即所谓的轻量)
  • server.js:应用入口文件,名字可以任意改。

系统类库

const {App, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, Upload, Url, Middleware, Cache, Context, View} = require('jj.js');

jj.js类库继承关系图

系统类库都是Class类型,其中LoggerCache是静态类;开发时建议继承系统类库,这样可以在类内使用$开头的属性,实现自动加载功能,链式调用类方法时会自动实例化一个单例。例如,在控制器内使用 this.$logger 会返回系统Logger类,使用 this.$logger.info(),会自动生成一个logger单例,并调用info方法,其他系统类库及类库内可以以这种方法调用。

类库自动加载

类库自动加载、懒加载是整个框架的核心,这里以hello world!示例程序先简单做个介绍。

1、假如想在./app/controller/index.js控制器的user方法中读取user数据表中id为1的用户,我们直接上代码:

const {Controller} = require('jj.js');

class Index extends Controller
{
    async index() {
        this.$show('Hello jj.js, hello world !');
    }

    async user() {
        const user_info = await this.$db.table('user').find({id: 1});
        this.$show(user_info);
    }
}

module.exports = Index;
  • 访问url:http://127.0.0.1:3000/index/user,即可看到打印的json用户信息。

  • 其中async user() {}为异步方法,在控制器中,对外可以以url访问的方法必须设置为异步函数。使用this.$db调用框架db类,这里控制器必须继承框架Controller类,否则会调用失败。

  • 在控制器中,前缀为$字符的属性为特殊属性,框架首先会检测本类中即this实例中是否有$db属性,有的话,会直接调用。如果没有,会检测本类文件所在应用目录即./app/目录下,是否用db目录或文件,如果有,则$db即代表那个目录或文件,this.$db.table会继续在db目录下寻找table目录或文件。如果db目录或文件不存在,框架会在应用根目录./下找,是否有db目录或文件,如果有,和上面一样。如果还不存在,框架会在jj.js框架lib目录下找db文件或目录,而框架lib中有db.js文件,至此,this.$db成功访问到框架db类。

  • 如果将this.$db赋值为给一个变量,const db = this.$db;,得到的将是一个db Class类,可以进行new操作,const db = this.$db; const db1 = new db(this.ctx, options); const db2 = new this.$db(this.ctx, options);创建多个实例。

  • 但上面演示代码并没有new一个实例,而是直接调用db类的table()方法,这正是框架的智能之处。当调用this.$db.table('user')时,框架首先会检测db类是否有table静态属性,有的话会直接调用。没有,则会自动new一个db实例,而且这个实例是个单例,然后调用此db实例的table方法,table('user')设置数据表名后返回实例本身,然后紧接着调用find()方法,读取用户ID为1的数据。

注意:虽然系统加载很复杂, 但是基于node的常驻内存特性,上面的文件路径判断,处理一次就会被缓存下来,在下个生命周期不用重复判断,节省性能。

关于单例:通过this.$xxx调用的类实例,在单个生命周期内是单例,即不管调用几次,都是用的同一个实例,这样非常节省内存开销。如果有多个db实例需求,可以用上面的方法,自己new创建,自己创建时注意第一个参数需传入ctx,否则部分类库使用会出现异常。

生命周期:应用生命周期以url访问为单位,一次url访问到访问结束,是一个独立的生命周期,在这个周期内自动生成的单实例都是共用的。

2、假如自己创建了数据表模型./app/model/user.js,然后想在控制器中使用,模型文件代码如下:

const {Model} = require('jj.js');

class User extends Model
{
    async getUserInfo(condition) {
        const user_info = await this.db.find(condition);
        return user_info;
    }
}

module.exports = User;

这时在控制器的user方法中读取user数据表中id为1的用户,我们直接上代码:

const {Controller} = require('jj.js');

class Index extends Controller
{
    async index() {
        this.$show('Hello jj.js, hello world !');
    }

    async user() {
        const user_info = await this.$model.user.getUserInfo({id: 1});
        this.$show(user_info);
    }
}

module.exports = Index;
  • 访问url:http://127.0.0.1:3000/index/user,同样看到打印的json用户信息。

  • 根据示例1的加载检测机制,this.$model会自动定位到./app/model/目录,this.$model.user会自动加载./app/model/user.js类文件,调用方法getUserInfo,会自动生成一个user模型的单实例,并调此单实例的getUserInfo方法。

  • 可以看到,不管是系统类库,还是自定义类库,都可以实现自动加载、懒加载、自动生成单实例。

注意:在user模型内调用的是this.dbdb没有$前缀,这个db是专属于user模型的独立实例,与示例1生成的db实例不是同一个。当然也可以使用带$前缀的db,但这样,在一个生命周期同时调用多个不同的模型的话,容易造成混淆。

3、除了框架db类、自定义模型类,整个应用和框架的所有自定义类库、配置文件,都支持同过this.$xxxx调用。

config配置

应用的配置可以为./config/目录+./config/xxx.js文件的形式,也可以直接写到一个./config.js文件里。

应用配置不用每项都设置,只设置自己需要改的,默认会继承框架的默认配置,在控制器、中间件、模板类、模型类里都可以通过this.$config.xxx使用。

  • app配置./config/app.js
const app = {
    app_debug: true, // 调试模式
    app_multi: false, // 是否开启多应用

    default_app: 'app', // 默认应用
    default_controller: 'index', //默认控制器
    default_action: 'index', // 默认方法

    common_app: 'common', // 公共应用,存放公共模型及逻辑
    controller_folder: 'controller', //控制器目录名

    static_dir: '', // 静态文件目录,相对于应用根目录,为空或false时,关闭静态访问

    koa_body: null // koa-body配置参数,为''、null、false时,关闭koa-body,此时upload类将不可用,并且系统接收不到post参数。启用的话,建议配置:{multipart: true, formidable: {keepExtensions: true, maxFieldsSize: 10 * 1024 * 1024}}
}
module.exports = app;
  • 数据库配置./config/db.js:可以配置多个,方便程序里切换使用。
const db = {
    default: {
        type      : 'mysql', // 数据库类型
        host      : '127.0.0.1', // 服务器地址
        database  : 'jj', // 数据库名
        user      : 'root', // 数据库用户名
        password  : '', // 数据库密码
        port      : '', // 数据库连接端口
        charset   : 'utf8', // 数据库编码默认采用utf8
        prefix    : 'jj_' // 数据库表前缀
    }
}
module.exports = db;
  • 路由配置./config/routes.js: 路由功能基于@koa/router开发,关于url匹配规则可以参考官方文档:文档地址

本框架默认内置 应用/控制器/方法 的全局路由,即如果不需要定制url,可以直接访问,无需配置路由。

路由配置为一个数组,每一项为一条规则,项属性包含url规则,path访问路径,name规则名字,type路由类型。url一旦匹配,即会停止,如果需要继续向下匹配,需在程序内调用 await this.$next();,路由配置参考示例:

routes = [
    {url: '/', path: 'app/index/index2'}, // 访问'/',会定位到app应用的index控制的index2方法
    {url: '/article/:id.html', path: 'app/article/article', name: 'article'}, // 访问'/article/123.html',会定位到app应用的article控制的article方法;通过this.ctx.params.id可以获取到参数id;通过article可以反编译网址,执行this.$url.build(':article', {id: 123}); 生成'/article/123.html'
    {url: '/admin', path: 'app/admin/check', type: 'middleware'}, // 访问'/admin',会定位到app应用的admin中间件的check方法
    {url: '/about', path: 'app/about/index', type: 'view'}, // 访问'/about',会定位到app应用的view模板目录about目录下的index.htm文件,并直接输出文件内容
    {url: '/:cate/list_:page.html', path: 'cate/cate', name: 'cate_page'}, // 多参数分页示例
    {url: '/hello', path: async (ctx, next) => {ctx.body = 'hello world, hello jj.js!';}} // 直接路由到函数
];

module.exports = routes;

> 注意:本路由配置示例前两条规则为常规用法,后面的非常规用法,不建议使用。如果是单应用模式,可以去掉path参数里的app。

- 自定义配置`./config/self.js`:自定义配置同样可以直接通过`this.$config.self`使用。
```javascript
const self = {
    option1: ''
    option2: ''
    ...
}
module.exports = self;
  • 其他配置项,请参考系统配置文件jj.js/lib/config.js

编码命名规范

类名使用大驼峰,方法名使用小驼峰,私有方法使用下划线前缀。 控制器文件名使用小写下划线。

应用案例

Nginx代理

location / {
    proxy_pass       http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

License

MIT

0.14.0

2 months ago

0.13.0

2 months ago

0.11.0

2 months ago

0.12.0

2 months ago

0.10.0

2 months ago

0.9.0

3 months ago

0.8.8

2 years ago

0.8.5

2 years ago

0.8.4

2 years ago

0.8.7

2 years ago

0.8.6

2 years ago

0.8.3

2 years ago

0.7.6

2 years ago

0.7.5

2 years ago

0.8.1

2 years ago

0.8.0

2 years ago

0.7.4

2 years ago

0.8.2

2 years ago

0.7.2

3 years ago

0.7.3

3 years ago

0.7.1

3 years ago

0.7.0

3 years ago

0.6.1

3 years ago

0.6.0

3 years ago

0.5.0

3 years ago

0.4.4

3 years ago

0.4.1

3 years ago

0.4.3

3 years ago

0.4.2

3 years ago

0.4.0

3 years ago

0.2.4

3 years ago

0.2.3

3 years ago

0.2.1

3 years ago

0.2.2

3 years ago

0.2.0

3 years ago

0.1.0

3 years ago

0.0.1

3 years ago