jj.js 0.20.0 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,9 @@
6
6
 
7
7
  ## 📖 项目介绍
8
8
 
9
- jj.js 是一个模仿 ThinkPHP5 设计的轻量级 Node.js MVC 框架。基于 Proxy 实现了**类库自动加载**、**懒加载**和 **Class 自动实例化及单例化**技术,所有类库想用直接调用,系统会自动导入,无需手动 require/import。
9
+ jj.js 是一个模仿 ThinkPHP5 设计的轻量级 Node.js MVC 框架。基于 Proxy 实现了**类库自动加载**、**懒加载**和 **Class 自动实例化及单例化**技术,所有类库想用就能直接调用,系统会自动导入,无需手动 require/import。
10
+
11
+ 框架采用**单应用无限子级**架构,在 `app/` 目录下可自由创建任意层级的子模块(如 `app/admin/`、`app/api/`),每个子级拥有独立的 controller、model、middleware 等,子级自动向上继承父级类库。
10
12
 
11
13
  ### ✨ 核心特性
12
14
 
@@ -16,7 +18,7 @@ jj.js 是一个模仿 ThinkPHP5 设计的轻量级 Node.js MVC 框架。基于 P
16
18
  | 🔄 **自动加载** | 系统类库、用户类库都支持自动加载、懒加载 |
17
19
  | 🎯 **智能实例化** | Class 自动生成单实例,节省内存开销 |
18
20
  | 🧩 **三级中间件** | 应用级、路由级、控制器级,方便插件及二次开发 |
19
- | 🌐 **多应用模式** | 支持单应用和多应用两种运行模式 |
21
+ | 🌳 **单应用多级模式** | 单应用架构,支持无限子级分层,模块化更清晰 |
20
22
  | 📝 **完整类型提示** | 基于 JSDoc,自动生成应用端 types 类型文件 |
21
23
 
22
24
  ### 📍 项目地址
@@ -84,18 +86,24 @@ node server.js
84
86
 
85
87
  ```
86
88
  project/
87
- ├── app/ # 应用目录(默认应用,可改名)
89
+ ├── app/ # 应用目录(根级,不可改名)
88
90
  │ ├── controller/ # 控制器目录(可改名)
89
91
  │ │ └── index.js # 首页控制器
90
92
  │ ├── view/ # 模板目录(可改名)
91
93
  │ │ └── index/ # index 控制器模板
92
94
  │ │ └── index.htm # 模板文件
93
- │ ├── middleware/ # 中间件目录(不可改名)
95
+ │ ├── middleware/ # 中间件目录(可改名)
94
96
  │ ├── model/ # 模型目录(可改名)
95
97
  │ ├── pagination/ # 分页目录(可改名)
96
98
  │ ├── logic/ # 逻辑目录(可改名)
97
- └── ... # 其他自定义目录
98
- ├── common/ # 公共应用目录(可选)
99
+ ├── admin/ # 子级模块(示例)
100
+ │ │ ├── controller/ # 子级控制器
101
+ │ │ ├── model/ # 子级模型
102
+ │ │ ├── middleware/ # 子级中间件
103
+ │ │ └── view/ # 子级模板
104
+ │ ├── api/
105
+ │ │ └── controller/ # API 子级控制器
106
+ │ └── ... # 其他自定义目录或子级
99
107
  ├── config/ # 配置目录(不可改名)
100
108
  │ ├── app.js # 应用配置
101
109
  │ ├── db.js # 数据库配置
@@ -117,13 +125,10 @@ project/
117
125
  | 目录 | 必需 | 说明 |
118
126
  |------|------|------|
119
127
  | `config/` | ❌ | 应用配置目录,所有配置参数放在这里,可简化为 `config.js` |
120
- | `app/` | ✅ | 默认应用目录,支持多应用时可创建 `app2`、`app3` |
121
- | `common/` | ❌ | 公共应用目录,存放公共 model、logic 等 |
128
+ | `app/` | ✅ | 应用根目录,支持无限子级分层(如 `app/admin/`、`app/api/`) |
122
129
  | `public/` | ❌ | 静态资源目录,在 `config/app.js` 中通过 `static_dir` 配置 |
123
130
  | `server.js` | ✅ | 应用入口文件,名字可任意修改 |
124
131
 
125
- > **多应用模式**:在 `./config/app.js` 中设置 `app_multi: true` 即可启用。
126
-
127
132
  ---
128
133
 
129
134
  ### 🏛️ 系统类库
@@ -213,25 +218,42 @@ module.exports = Index;
213
218
 
214
219
  **访问**:`http://127.0.0.1:3000/index/user`
215
220
 
216
- **加载流程**:
221
+ #### 加载流程
217
222
 
218
223
  ```
219
224
  this.$db
220
225
 
221
- 1. 检查当前类实例是否有 $db 属性
222
- ↓ (没有)
223
- 2. 检查应用目录 ./app/db.js 或 ./app/db/
226
+ 1. 检查当前层级目录 ./app/[deep]/db.js 或 ./app/[deep]/db/
227
+ ↓ (没有,向上遍历父级)
228
+ 2. 检查父级目录 ./app/db.js 或 ./app/db/
224
229
  ↓ (没有)
225
- 3. 检查应用根目录 ./db.js ./db/
226
- ↓ (没有)
227
- 4. 检查框架 lib/db.js ✅ 找到!
230
+ 3. 检查框架 lib/db.js 找到!
228
231
 
229
232
  自动实例化(单例)并返回 Db 实例
230
233
  ```
231
234
 
235
+ > **向上继承**:子级模块可以访问父级定义的同名类库,自动向上查找最近的定义。系统类库(如 `$db`、`$model` 等)在应用目录找不到时,自动回退到框架内置类库。
236
+
232
237
  > **智能实例化**:调用 `this.$db.table('user')` 时,框架先检测是否有 `table` 静态方法,没有则自动 new 实例,然后调用实例方法。
233
238
 
234
- #### 示例 2:使用自定义模型
239
+ #### 示例 2:子级模块
240
+
241
+ 在 `app/` 下创建子级目录即可实现模块化分层,每个子级拥有独立的 controller、model、middleware 等:
242
+
243
+ ```
244
+ app/
245
+ ├── controller/index.js # 根级控制器 → /index/index
246
+ ├── model/user.js # 根级模型
247
+ ├── admin/
248
+ │ ├── controller/index.js # 子级控制器 → /admin/index/index
249
+ │ └── model/admin.js # 子级模型(可覆盖根级同名模型)
250
+ └── api/
251
+ └── controller/index.js # API 控制器 → /api/index/index
252
+ ```
253
+
254
+ > **URL 规则**:URL 路径自动映射到子级,如 `/admin/index/index` 对应 `app/admin/controller/index.js` 的 `index` 方法。
255
+
256
+ #### 示例 3:使用自定义模型
235
257
 
236
258
  **模型** `./app/model/user.js`:
237
259
 
@@ -270,12 +292,11 @@ async user() {
270
292
  ```javascript
271
293
  module.exports = {
272
294
  app_debug: false, // 调试模式
273
- app_multi: false, // 是否开启多应用
274
- default_app: 'app', // 默认应用
295
+ default_deep: '', // 默认应用深度(子级路径),为空表示 app 根目录
275
296
  default_controller: 'index', // 默认控制器
276
297
  default_action: 'index', // 默认方法
277
- common_app: 'common', // 公共应用
278
298
  controller_folder: 'controller', // 控制器目录名
299
+ middleware_folder: 'middleware', // 中间件目录名
279
300
  static_dir: '', // 静态文件目录(为空关闭静态访问)
280
301
  koa_body: null // koa-body 配置(null 关闭)
281
302
  };
@@ -286,6 +307,11 @@ module.exports = {
286
307
  ```javascript
287
308
  module.exports = {
288
309
  default: {
310
+ type: 'sqlite',
311
+ database: ':memory:', // 数据库文件绝对路径,支持 :memory: 内存数据库
312
+ prefix: 'jj_'
313
+ },
314
+ mysql: {
289
315
  type: 'mysql',
290
316
  host: '127.0.0.1',
291
317
  database: 'jj',
@@ -295,11 +321,6 @@ module.exports = {
295
321
  charset: 'utf8mb4',
296
322
  prefix: 'jj_'
297
323
  },
298
- sqlite: {
299
- type: 'sqlite',
300
- database: ':memory:',
301
- prefix: 'jj_'
302
- },
303
324
  mongodb: {
304
325
  type: 'mongodb',
305
326
  host: '127.0.0.1',
package/jj.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * jj.js核心库<br/>
3
- * {App, Cache, Context, Controller, Cookie, Ctx, Db, Logger, Middleware, Model, Pagination, Request, Response, Upload, Url, View, utils, config}
3
+ * {App, Cache, Context, Controller, Cookie, Ctx, Db, Logger, Middleware, Model, Pagination, Request, Response, Upload, Url, View, utils, config, loader}
4
4
  * @module core
5
5
  * @type {import('./types').Core}
6
6
  */
package/lib/app.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const path = require('path');
2
2
  const Koa = require('koa');
3
- const {app: cfg_app, cookie: cfg_cookie} = require('./config');
3
+ const config = require('./config');
4
+ const {app: cfg_app, cookie: cfg_cookie} = config;
4
5
  const loader = require('./loader');
5
6
  const Logger = require('./logger');
6
7
  const Response = require('./response');
@@ -46,9 +47,16 @@ class App extends Koa
46
47
  this.use(async (ctx, next) => {
47
48
  ctx.APP_TIME = Date.now();
48
49
  ctx.APP_VERSION = pjson.version;
50
+ const _ = loader(cfg_app.base_dir, ctx);
51
+ _.config || (_.config = {});
52
+ Object.entries(config).forEach(([key, value]) => {
53
+ _.config[key] = value;
54
+ });
49
55
  await storage.run({
50
56
  $: loader(__dirname, ctx),
51
- _: loader(cfg_app.base_dir, ctx)
57
+ $$: _.app,
58
+ $config: _.config,
59
+ DEEP: '', // 初始化,防止后面报错
52
60
  }, async() => {
53
61
  await next();
54
62
  });
package/lib/cache.js CHANGED
@@ -75,7 +75,7 @@ class Cache
75
75
  this.cache.delete(key);
76
76
  } else {
77
77
  // @ts-ignore
78
- this.cache = new Map();
78
+ this.cache.clear();
79
79
  }
80
80
  }
81
81
 
package/lib/config.js CHANGED
@@ -4,17 +4,15 @@ const {format} = require('./utils/date');
4
4
 
5
5
  const app = {
6
6
  app_debug: false, // 调试模式
7
- app_multi: false, // 是否开启多应用
8
7
 
9
- default_app: 'app', // 默认应用
8
+ default_deep: '', // 默认应用深度,为空表示app根目录
10
9
  default_controller: 'index', //默认控制器
11
10
  default_action: 'index', // 默认方法
12
11
 
13
- common_app: 'common', // 公共应用,存放公共模型及逻辑
14
12
  controller_folder: 'controller', //控制器目录名
13
+ middleware_folder: 'middleware', //中间件目录名
15
14
 
16
15
  static_dir: '', // 静态文件目录,相对于应用根目录,为空时,关闭静态访问
17
-
18
16
  koa_body: null // koa-body配置参数,为null或空时,关闭koa-body
19
17
  }
20
18
 
@@ -22,12 +20,22 @@ const view = {
22
20
  view_folder: 'view', // 模板目录名
23
21
  view_depr: '/', // 模版文件名分割符,'/'代表二级目录
24
22
  view_ext: '.htm', // 模版后缀
25
- view_engine: 'art-template', // 默认模版引擎,字符串或引擎类
23
+ view_engine: '@yafoo/art-template', // 默认模版引擎,字符串或引擎类
26
24
  view_filter: {}, // 模版函数
27
25
  }
28
26
 
29
27
  const db = {
30
28
  default: {
29
+ type : 'sqlite', // 数据库类型
30
+ database : ':memory:', // 数据库文件绝对地址,支持:memory:内存数据库
31
+ prefix : 'jj_' // 数据库表前缀
32
+ },
33
+ sqlite: {
34
+ type : 'sqlite', // 数据库类型
35
+ database : ':memory:', // 数据库文件绝对地址,支持:memory:内存数据库
36
+ prefix : 'jj_' // 数据库表前缀
37
+ },
38
+ mysql: {
31
39
  type : 'mysql', // 数据库类型
32
40
  host : '127.0.0.1', // 服务器地址
33
41
  database : 'jj', // 数据库名
@@ -37,11 +45,6 @@ const db = {
37
45
  charset : 'utf8mb4', // 数据库编码默认采用utf8mb4
38
46
  prefix : 'jj_' // 数据库表前缀
39
47
  },
40
- sqlite: {
41
- type : 'sqlite', // 数据库类型
42
- database : ':memory:', // 数据库文件绝对地址,支持:memory:内存数据库
43
- prefix : 'jj_' // 数据库表前缀
44
- },
45
48
  mongodb: {
46
49
  type : 'mongodb', // 数据库类型
47
50
  host : '127.0.0.1', // 服务器地址
@@ -56,7 +59,7 @@ const db = {
56
59
  const log = {
57
60
  log_level: ['system', 'error'], // [system, error, warning, info, debug, http, sql]
58
61
  // @ts-ignore
59
- log_handle: function(level, ...args) {console.log(`[${format('YY-mm-dd HH:ii:ss')}] [${level}]`, ...args.map(msg => typeof msg == 'object' ? JSON.stringify(msg) : String(msg)));}
62
+ log_handle: function(level, ...args) {console[level in console ? level : 'log'](`[${format('YY-mm-dd HH:ii:ss')}] [${level}]`, ...args.map(msg => typeof msg == 'object' ? JSON.stringify(msg) : String(msg)));}
60
63
  }
61
64
 
62
65
  const cache = {
@@ -100,20 +103,30 @@ const tpl = {
100
103
  }
101
104
  // @ts-ignore
102
105
  const base_dir = require.main.path;
103
- const base_config = loader(path.join(base_dir, './config'));
106
+ const {
107
+ app: config_app,
108
+ view: config_view,
109
+ db: config_db,
110
+ log: config_log,
111
+ cache: config_cache,
112
+ page: config_page,
113
+ routes: config_routes,
114
+ cookie: config_cookie,
115
+ tpl: config_tpl
116
+ } = loader(path.join(base_dir, './config')) || {};
104
117
 
105
118
  /**
106
119
  * @module config
107
120
  * @type {import('../types').Config}
108
121
  */
109
122
  module.exports = {
110
- app: {...app, ...base_config.app, base_dir},
111
- view: {...view, ...base_config.view},
112
- db: {...db, ...base_config.db},
113
- log: {...log, ...base_config.log},
114
- cache: {...cache, ...base_config.cache},
115
- page: {...page, ...base_config.page},
116
- routes: base_config.routes,
117
- cookie: {...cookie, ...base_config.cookie},
118
- tpl: {...tpl, ...base_config.tpl}
123
+ app: {...app, ...config_app, base_dir},
124
+ view: {...view, ...config_view},
125
+ db: {...db, ...config_db},
126
+ log: {...log, ...config_log},
127
+ cache: {...cache, ...config_cache},
128
+ page: {...page, ...config_page},
129
+ routes: config_routes,
130
+ cookie: {...cookie, ...config_cookie},
131
+ tpl: {...tpl, ...config_tpl}
119
132
  };
package/lib/controller.js CHANGED
@@ -1,14 +1,29 @@
1
1
  const Middleware = require('./middleware');
2
2
 
3
3
  /**
4
- * @typedef {import('../types').ViewInstance} ViewInstance
4
+ * @typedef {import('../types').ControllerMiddleware} ControllerMiddleware - Controller中间件
5
+ * @typedef {import('../types').ViewInstance} ViewInstance - View实例
5
6
  */
6
7
 
7
8
  /**
8
9
  * @extends Middleware
10
+ * @property {ControllerMiddleware[]} [middleware] - 控制器中间件
9
11
  */
10
12
  class Controller extends Middleware
11
13
  {
14
+ /**
15
+ * 初始化方法,在控制器方法执行前自动执行
16
+ * @protected
17
+ * @returns {any} - 返回Bool值false,则不会执行控制器及后续方法
18
+ */
19
+ _init() {}
20
+
21
+ /**
22
+ * 结束方法,在控制器方法执行后自动执行。如果控制器方法返回Bool值false,则不会执行此方法
23
+ * @protected
24
+ */
25
+ _end() {}
26
+
12
27
  /**
13
28
  * 模版数据赋值
14
29
  * @public
package/lib/ctx.js CHANGED
@@ -1,58 +1,81 @@
1
- // @ts-nocheck
2
- const storage = require('./storage');
3
- // 允许$访问的系统类库
4
- const libs = ['ctx', 'context', 'cache', 'cookie', 'db', 'logger', 'middleware', 'model', 'pagination', 'request', 'response', 'upload', 'url', 'view', 'utils', 'config'];
5
-
6
- /**
7
- * 开发jj.js库本身时,需改为import('../types')
8
- * type {typeof import('../types')}
9
- * @type {typeof import('../../../types')}
10
- */
11
- const Ctx = new Proxy(class Ctx {}, {
12
- construct() {
13
- return new Proxy({__proto__: arguments[2].prototype}, {
14
- get: (target, $prop, receiver) => {
15
- if($prop in target || typeof $prop == 'symbol' || $prop == 'inspect' || $prop == 'ctx' || $prop == '$next') {
16
- return Reflect.get(target, $prop, receiver);
17
- }
18
-
19
- const store = storage.getStore();
20
- if($prop == '$' || $prop == '_') {
21
- return store[$prop];
22
- }
23
- if($prop.slice(0, 1) != '$') {
24
- return undefined;
25
- }
26
-
27
- const {$, _, APP, COMMON} = store;
28
- const prop = $prop.slice(1);
29
-
30
- if(!_[APP]) {
31
- return libs.includes(prop) ? $[prop] : undefined;
32
- }
33
-
34
- if(!_[APP][prop] && !(_[COMMON] && _[COMMON][prop]) && !libs.includes(prop)) {
35
- return undefined;
36
- }
37
-
38
- return _[APP][$prop] = new Proxy(function() {}, {
39
- construct(target, args, newTarget) {
40
- const node = _[APP][prop] && _[APP][prop].__ISCLASS__ && _[APP][prop]
41
- || _[COMMON] && _[COMMON][prop].__ISCLASS__ && _[COMMON][prop]
42
- || libs.includes(prop) && $[prop].__ISCLASS__ && $[prop]
43
- || undefined;
44
- return node ? Reflect.construct(node, args, newTarget) : undefined;
45
- },
46
- get: (...args) => {
47
- return res = _[APP][prop] && _[APP][prop][args[1]]
48
- || _[COMMON] && _[COMMON][prop][args[1]]
49
- || libs.includes(prop) && $[prop][args[1]]
50
- || undefined;
51
- }
52
- });
53
- }
54
- });
55
- }
56
- });
57
-
1
+ // @ts-nocheck
2
+ const storage = require('./storage');
3
+ const path = require('path');
4
+ const {app: cfg_app} = require('./config');
5
+ const libs = require('./utils/libs').filter(lib => lib != 'config'); //排除config
6
+ const {pathToDeep} = require('./utils/internal');
7
+
8
+ /**
9
+ * 开发jj.js库本身时,需改为import('../types')
10
+ * type {typeof import('../types')}
11
+ * @type {typeof import('../../../types')}
12
+ */
13
+ const Ctx = new Proxy(class Ctx {
14
+ _setDeep(mod) {
15
+ this.DEEP = pathToDeep(path.dirname(mod.path), cfg_app.base_dir);
16
+ }
17
+ }, {
18
+ construct() {
19
+ return new Proxy({__proto__: arguments[2].prototype}, {
20
+ get: (target, $prop, receiver) => {
21
+ if($prop in target || typeof $prop == 'symbol' || $prop == 'inspect' || $prop == 'ctx' || $prop == '$next' || $prop == 'DEEP') {
22
+ return Reflect.get(target, $prop, receiver);
23
+ }
24
+
25
+ const store = storage.getStore();
26
+ if($prop == '$' || $prop == '$$') {
27
+ return store[$prop];
28
+ }
29
+ if($prop.slice(0, 1) != '$') {
30
+ return undefined;
31
+ }
32
+
33
+ const {$, $$, $config, DEEP} = store;
34
+ const prop = $prop.slice(1);
35
+
36
+ const DEEPS = (target.DEEP !== undefined ? target.DEEP : DEEP).split('/').filter(Boolean);
37
+ const $nodes = [];
38
+
39
+ DEEPS.reduce((node, deep) => {
40
+ if(!node) return;
41
+ if(node[deep] && node[deep][prop]) $nodes.unshift(node[deep][prop]);
42
+ return node[deep];
43
+ }, $$);
44
+
45
+ if(prop == 'config' && $config) $nodes.push($config);
46
+ if($$ && $$[prop]) $nodes.push($$[prop]);
47
+
48
+ if(!libs.includes(prop) && !$nodes.length) return undefined;
49
+ if(libs.includes(prop) && !$nodes.length) return $[prop];
50
+ if(!libs.includes(prop) && $nodes.length == 1) return $nodes[0];
51
+
52
+ target[$prop] = new Proxy(function() {}, {
53
+ construct(_, args, newTarget) {
54
+ const node = $nodes.find(node => node.__ISCLASS__)
55
+ || libs.includes(prop) && $[prop].__ISCLASS__ && $[prop]
56
+ || undefined;
57
+ return node ? Reflect.construct(node, args, newTarget) : undefined;
58
+ },
59
+ get: (...args) => {
60
+ const node = $nodes.find(node => args[1] in node) || libs.includes(prop) && args[1] in $[prop] && $[prop] || {};
61
+ return Reflect.get(node, args[1], args[2]);
62
+ },
63
+ set: (...args) => {
64
+ const node = $nodes.find(node => args[1] in node);
65
+ if(node) {
66
+ node[args[1]] = args[2];
67
+ } else if(libs.includes(prop)) {
68
+ $[prop][args[1]] = args[2];
69
+ } else {
70
+ return false;
71
+ }
72
+ return true;
73
+ },
74
+ });
75
+ return Reflect.get(target, $prop, receiver);
76
+ }
77
+ });
78
+ }
79
+ });
80
+
58
81
  module.exports = Ctx;
package/lib/db/sqlite.js CHANGED
@@ -34,8 +34,34 @@ class Sqlite extends Sql
34
34
 
35
35
  let connection = connections.get(this._config);
36
36
  if(!connection) {
37
- const sqlite3 = cfg_app.app_debug ? require('sqlite3').verbose() : require('sqlite3');
37
+ const sqlite3 = require('sqlite3');
38
+ cfg_app.app_debug && sqlite3.verbose();
38
39
  connection = new sqlite3.Database(this._config.database);
40
+
41
+ // 性能优化:启用WAL模式,提升读写并发性能
42
+ await new Promise((resolve, reject) => {
43
+ connection.run('PRAGMA journal_mode = WAL;', (err) => {
44
+ if(err) reject(err);
45
+ else resolve();
46
+ });
47
+ });
48
+
49
+ // 性能优化:配合WAL模式,减少不必要的磁盘同步
50
+ await new Promise((resolve, reject) => {
51
+ connection.run('PRAGMA synchronous = NORMAL;', (err) => {
52
+ if(err) reject(err);
53
+ else resolve();
54
+ });
55
+ });
56
+
57
+ // 性能优化:设置锁等待超时时间,避免并发写入时直接报错
58
+ await new Promise((resolve, reject) => {
59
+ connection.run('PRAGMA busy_timeout = 5000;', (err) => {
60
+ if(err) reject(err);
61
+ else resolve();
62
+ });
63
+ });
64
+
39
65
  connections.set(this._config, connection);
40
66
  this.logger.sql(`SQLITE数据库连接创建成功:{all: ${connections.size}}`);
41
67
  }
@@ -289,4 +315,4 @@ class Sqlite extends Sql
289
315
  }
290
316
  }
291
317
 
292
- module.exports = Sqlite;
318
+ module.exports = Sqlite;