jj.js 0.8.1 → 0.8.4

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,14 +6,22 @@ A simple and lightweight MVC framework built on nodejs+koa2(一个基于nodejs
6
6
 
7
7
  ## 项目介绍
8
8
 
9
- > 框架依赖koa2、koa-router、art-template、mysql,基于proxy实现了代码自动加载及懒加载技术,最低依赖仅仅为koa和koa-router,非常轻量。系统架构类似Thinkphp5,很容易上手。支持类库自动加载、手工引入两种开发模式。支持应用、路由、控制器三级中间件,方便插件及二次开发。支持单应用和多应用两种运行模式。
9
+ 本框架依赖koa2、koa-router、art-template、mysql,基于proxy实现了代码自动加载及懒加载技术,最低运行依赖仅仅为koa和koa-router,非常轻量。
10
10
 
11
+ ### 项目特性
12
+
13
+ 1. 系统架构模仿Thinkphp5,很容易上手
14
+ 2. 系统类库、用户类库都支持自动加载、懒加载、自动生成单实例
15
+ 3. 支持应用级、路由级、控制器级三级中间件,方便插件及二次开发
16
+ 4. 支持单应用和多应用两种运行模式
17
+
18
+ ### 项目地址
11
19
 
12
20
  项目地址:[https://github.com/yafoo/jj.js](https://github.com/yafoo/jj.js "https://github.com/yafoo/jj.js")
13
21
 
14
22
  码云镜像:[https://gitee.com/yafu/jj.js](https://gitee.com/yafu/jj.js "https://gitee.com/yafu/jj.js")
15
23
 
16
- 官网地址:[https://me.i-i.me/jjjs/](https://me.i-i.me/jjjs/ "https://me.i-i.me/jjjs/")
24
+ 官网地址:[https://me.i-i.me/special/jj.html](https://me.i-i.me/special/jj.html "https://me.i-i.me/special/jj.html")
17
25
 
18
26
 
19
27
  ## 安装
@@ -22,9 +30,11 @@ A simple and lightweight MVC framework built on nodejs+koa2(一个基于nodejs
22
30
  npm i jj.js
23
31
  ```
24
32
 
33
+ > 运行环境要求:node.js >= v12 mysql >= 5.3
34
+
25
35
  ## Hello world !
26
36
 
27
- 1、创建文件 `./app/controller/index.js`
37
+ 1、创建控制器文件 `./app/controller/index.js`
28
38
 
29
39
  ```javascript
30
40
  const {Controller} = require('jj.js');
@@ -39,7 +49,7 @@ class Index extends Controller
39
49
  module.exports = Index;
40
50
  ```
41
51
 
42
- 2、创建文件 `./server.js`
52
+ 2、创建应用入口文件 `./server.js`
43
53
 
44
54
  ```javascript
45
55
  const {app, Logger} = require('jj.js');
@@ -57,9 +67,9 @@ node server.js
57
67
 
58
68
  4、浏览器访问 `http://127.0.0.1:3000`,页面输出 `Hello jj.js, hello world !`
59
69
 
60
- 5、或者执行命令 `npm test`,快速打开测试程序。
70
+ ## 开发手册
61
71
 
62
- ## 应用结构
72
+ ### 应用目录结构
63
73
 
64
74
  ```
65
75
  ├── app //应用目录 (非必需,可改名)
@@ -88,101 +98,211 @@ node server.js
88
98
  ├── server.js //应用入口文件 (必需,可改名)
89
99
  └── package.json //npm package.json
90
100
  ```
101
+ #### 应用目录介绍:
102
+ - **config**: 应用配置目录,系统的所有配置参数都放这里,也可以简化为一个config
103
+ .js文件,所有配置参数会覆盖框架默认参数。其中config这个名字不允许修改。
104
+ - **app、app2、common**: 为应用目录,common为公共应用目录,可以存放公共的model、logic或其他的文件,app为默认访问的应用。jj.js框架支持单应用和多应用运行模式,在`./config/app.js`中把`app_multi`设置为`true`即为多应用模式,此时可以创建app2、app3更多应用。框架默认为单应用模式。
105
+ - **public**:静态资源目录,主要存放css文件、js文件、图片等静态资源。在`./config/app.js`中通过`static_dir`参数可以设置或更改静态目录名字,为空时,则关闭静态访问(系统不会加载静态资源访问的逻辑,最大节省内容,也即所谓的轻量)
106
+ - **server.js**:应用入口文件,名字可以任意改。
91
107
 
92
- ## 系统类库
108
+ >
109
+ ### 系统类库
93
110
 
94
111
  ```javascript
95
- const {app, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, Upload, Url, Middleware, Context} = require('jj.js');
112
+ const {app, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, Upload, Url, Middleware, Cache, Context, View} = require('jj.js');
113
+ ```
114
+
115
+ ![jj.js类库继承关系图](https://me.i-i.me/static/images/jj_class.png "jj.js类库继承关系图")
116
+
117
+ 系统类库除了`app`,其他都是`Class`类型,其中`Logger`和`Cache`是静态类,开发时建议继承系统类库,这样可以在类内使用`$`开头的属性,实现自动加载功能,链式调用类方法时会自动实例化一个单例。例如,在控制器内使用 `this.$logger` 会返回系统Logger类,使用 `this.$logger.info()`,会自动生成一个`logger`单例,并调用`info`方法,其他系统类库及类库内可以以这种方法调用。
118
+
119
+ ### 类库自动加载
120
+
121
+ > 类库自动加载、懒加载是整个框架的核心,这里以hello world!示例程序先简单做个介绍。
122
+
123
+ 1、假如想在`./app/controller/index.js`控制器的`user`方法中读取`user`数据表中`id`为1的用户,我们直接上代码:
124
+
125
+ ```javascript
126
+ const {Controller} = require('jj.js');
127
+
128
+ class Index extends Controller
129
+ {
130
+ async index() {
131
+ this.$show('Hello jj.js, hello world !');
132
+ }
133
+
134
+ async user() {
135
+ const user_info = await this.$db.table('user').find({id: 1});
136
+ this.$show(user_info);
137
+ }
138
+ }
139
+
140
+ module.exports = Index;
96
141
  ```
97
142
 
98
- ## 开发手册(待继续完善)
143
+ - 访问url:`http://127.0.0.1:3000/index/user`,即可看到打印的json用户信息。
144
+
145
+ - 其中`async user() {}`为异步方法,在控制器中,对外可以以url访问的方法必须设置为异步函数。使用`this.$db`调用框架db类,这里控制器必须继承框架`Controller`类,否则会调用失败。
146
+
147
+ - 在控制器中,前缀为`$`字符的属性为特殊属性,框架首先会检测本类中即`this`实例中是否有`$db`属性,有的话,会直接调用。如果没有,会检测本类文件所在应用目录即`./app/`目录下,是否用`db`目录或文件,如果有,则`$db`即代表那个目录或文件,`this.$db.table`会继续在db目录下寻找table目录或文件。如果`db`目录或文件不存在,框架会在应用根目录`./`下找,是否有`db`目录或文件,如果有,和上面一样。如果还不存在,框架会在jj.js框架`lib`目录下找`db`文件或目录,而框架`lib`中有`db.js`文件,至此,`this.$db`成功访问到框架`db`类。
148
+
149
+ - 如果将`this.$db`赋值为给一个变量,`const db = this.$db;`,得到的将是一个`db` Class类,可以进行new操作,`const db = this.$db; const db1 = new this.$db(); const db2 = new this.$db();`创建多个实例。
99
150
 
100
- > 系统类库除了app,其他都是Class类型,开发时建议继承系统类库,这样可以在类内使用$开头的属性,可以自动加载系统类,链式调用类方法时会自动实例化一个单例。例如,在控制器内使用 `this.$logger` 会返回系统Logger类,使用 `this.$logger.info()`,会自动生成一个logger单例,并调用info方法,其他系统类库及类库内可以以这种方法调用。
151
+ - 但上面演示代码并没有new一个实例,而是直接调用`db`类的`table()`方法,这正是框架的智能之处。当调用`this.$db.table('user')`时,框架首先会检测db类是否有`table`静态属性,有的话会直接调用。没有,则会自动new一个`db`实例,而且这个实例是个单例,然后调用此`db`实例的`table`方法,`table('user')`设置数据表名后返回实例本身,然后紧接着调用`find()`方法,读取用户ID为1的数据。
152
+
153
+ > 注意:虽然系统加载很复杂, 但是基于node的常驻内存特性,上面的文件路径判断,处理一次就会被缓存下来,在下个生命周期不用重复判断,节省性能。
154
+
155
+ > 关于单例:通过`this.$xxx`调用的类实例,在单个生命周期内是单例,即不管调用几次,都是用的同一个实例,这样非常节省内存开销。如果有多个db实例需求,可以用上面的方法,自己`new`创建。
156
+
157
+ > 生命周期:应用生命周期以url访问为单位,一次url访问到访问结束,是一个独立的生命周期,在这个周期内自动生成的单实例都是共用的。
158
+
159
+ 2、假如自己创建了数据表模型`./app/model/user.js`,然后想在控制器中使用,模型文件代码如下:
160
+
161
+ ```javascript
162
+ const {Model} = require('jj.js');
163
+
164
+ class User extends Model
165
+ {
166
+ async getUserInfo(condition) {
167
+ const user_info = await this.db.find(condition);
168
+ return user_info;
169
+ }
170
+ }
171
+
172
+ module.exports = User;
173
+ ```
174
+
175
+ 这时在控制器的user方法中读取user数据表中id为1的用户,我们直接上代码:
176
+
177
+ ```javascript
178
+ const {Controller} = require('jj.js');
179
+
180
+ class Index extends Controller
181
+ {
182
+ async index() {
183
+ this.$show('Hello jj.js, hello world !');
184
+ }
185
+
186
+ async user() {
187
+ const user_info = await this.$model.user.getUserInfo({id: 1});
188
+ this.$show(user_info);
189
+ }
190
+ }
191
+
192
+ module.exports = Index;
193
+ ```
194
+
195
+ - 访问url:`http://127.0.0.1:3000/index/user`,同样看到打印的json用户信息。
196
+
197
+ - 根据示例1的加载检测机制,`this.$model`会自动定位到`./app/model/`目录,`this.$model.user`会自动加载`./app/model/user.js`类文件,调用方法`getUserInfo`,会自动生成一个`user`模型的单实例,并调此单实例的`getUserInfo`方法。
198
+
199
+ - 可以看到,不管是系统类库,还是自定义类库,都可以实现自动加载、懒加载、自动生成单实例。
200
+
201
+ > 注意:在`user`模型内调用的是`this.db`,`db`没有`$`前缀,这个`db`是专属于`user`模型的独立实例,与示例1生成的`db`实例不是同一个。当然也可以使用带`$`前缀的`db`,但这样,在一个生命周期同时调用多个不同的模型的话,容易造成混淆。
202
+
203
+ 3、除了框架db类、自定义模型类,整个应用和框架的所有自定义类库、配置文件,都支持同过`this.$xxxx`调用。
101
204
 
102
205
  ### Controller控制器
103
206
 
104
207
  系统控制器类继承自系统中间件类Middleware,包含所有Middleware方法。
105
208
 
106
- > 属性:**middleware**
209
+ > 属性:**middleware** 定义控制器中间件
107
210
 
108
211
  数组,定义控制器中间件,一个元素为一个中间件,元素为字符串或对象。
109
212
 
110
- 案例1: `this.middleware = ['index']`,则控制器内所有方法访问之前都会调用当前应用目录(app)下中间件目录(middleware )下的控制器同名中间件的index方法。
213
+ 示例1: `this.middleware = ['index']`,则控制器内所有方法访问之前都会调用当前应用目录(app)下中间件目录(middleware )下的控制器同名中间件的index方法。
111
214
 
112
- 案例2: `this.middleware = ['index', {middleware: 'auth/test', accept: 'middleTest'}]`,则控制器内所有方法访问之前都会先调用当前应用目录(app)下中间件目录(middleware )下的控制器同名中间件的index方法。仅当访问控制器middleTest方法之前,还会再调用当前应用下中间件目录下的auth中间件的test方法(需index中间件内调用`this.$next()`方法,否则后续程序不会执行)。
215
+ 示例2: `this.middleware = ['index', {middleware: 'auth/test', accept: 'middleTest'}]`,则控制器内所有方法访问之前都会先调用当前应用目录(app)下中间件目录(middleware )下的控制器同名中间件的index方法。仅当访问控制器middleTest方法之前,还会再调用当前应用下中间件目录下的auth中间件的test方法(需index中间件内调用`this.$next()`方法,否则后续程序不会执行)。
113
216
 
114
- > 方法:**$assign(name, value)**
217
+ > 方法:**$assign(name, value)** 赋值模版变量
115
218
 
116
- 同步方法,赋值模版变量。
117
-
118
- 案例1:`this.$assign('title', 'Hello jj.js !')`,在模版内使用变量 `{{title}}`
219
+ 示例1:`this.$assign('title', 'Hello jj.js !')`,在模版内使用变量 `{{title}}`
119
220
 
120
221
  如果name为一个对象,则清除之前赋值的模板变量,并将name设置为模板变量对象。
121
222
 
122
- 案例2:`this.$assign({'title': 'jj.js', 'content': 'Hello jj.js !'})`,在模版内可使用变量 `{{title}}`、`{{content}}`
223
+ 示例2:`this.$assign({'title': 'jj.js', 'content': 'Hello jj.js !'})`,在模版内可使用变量 `{{title}}`、`{{content}}`
123
224
 
124
- > 方法:**$data(name)**
225
+ > 方法:**$data(name)** 获取已赋模版变量
125
226
 
126
- 同步方法,获取已赋模版变量。
127
227
  如果name为空,则获取全部变量。
128
228
 
129
- > 方法:**$show(content)**
229
+ > 方法:**async $fetch(template)** 渲染模板文件并输出
230
+
231
+ 异步方法,来自`View`类,渲染模板文件,并输出内容。
130
232
 
131
- 同步方法,继承自Middleware类,输出content,如果content为对象,则输出json字符串。
233
+ 其中template会自动自动定位模板文件,如果name为空,则定位到当前应用下view目录下的控制器同名目录下的方法同名htm文件。
132
234
 
133
- > 方法:**$fetch(template)**
235
+ 示例1:`this.$fetch()`,在index控制器类的index方法调用,则模板定位到`/app/view/index/index.htm`
134
236
 
135
- 异步方法,渲染模板文件,并输出。
237
+ 示例2:`this.$fetch('list/show')`,则模板定位到`/app/view/list/show.htm`
136
238
 
137
- 其中template会自动自动定位模板文件,如果name为空,则定位到当前应用下view目录下的控制器同名目录下的方法同名htm文件。
239
+ > 方法:**async $load(template)** 加载并输出模板文件
240
+
241
+ 异步方法,来自`View`类,直接加载模板文件,并输出文件内容。模板定位规则同$fetch方法。
242
+
243
+ > 方法:**async $render(data)** 渲染字符串模板
244
+
245
+ 来自`View`类,渲染字符串模板,并输出。data为字符串模版内容。
246
+
247
+ 示例1:`this.$render('<div>{{title}}</div>')`,会输出`<div>jj.js</div>`
138
248
 
139
- 案例1:`this.$fetch()`,在index控制器类的index方法调用,则模板定位到`/app/view/index/index.htm`
249
+ > 方法:**$show(content)** 输出字符串内容或转换后的json字符串
140
250
 
141
- 案例2:`this.$fetch('list/show')`,则模板定位到`/app/view/list/show.htm`
251
+ 继承自`Middleware`类,输出content内容,如果content为对象或数组,则输出json字符串。
142
252
 
143
- > 方法:**$load(template)**
253
+ > 方法:**$redirect(name, status = 302)** 302或其他跳转
144
254
 
145
- 异步方法,直接加载模板,并输出。模板定位规则同$fetch方法。
255
+ 继承自`Middleware`类,网页跳转,name解析同$fetch方法。
146
256
 
147
- > 方法:**$render(data)**
257
+ 示例1:`this.$redirect('test')`,在index控制器类的index方法调用,则跳转到到`/app/index/test`地址
148
258
 
149
- 异步方法,渲染字符串模板,并输出。data为字符串模版内容。
259
+ 示例2:`this.$redirect('/show')`,name包含'/'前缀,则跳转到地址`/show`
150
260
 
151
- 案例1:`this.$render('<div>{{title}}</div>')`,会输出`<div>jj.js</div>`
261
+ > 方法:**$success(msg, name)** 成功跳转或输出
152
262
 
153
- > 方法:**$redirect(name, status = 302)**
263
+ 继承自`Middleware`类,返回成功提示,name解析同$redirect方法,如果是ajax请求,则返回json数据。
154
264
 
155
- 同步方法,继承自Middleware类,网页跳转,name解析同$fetch方法。
265
+ 示例1:`this.$success('操作成功!', 'test')`,返回成功提示,并跳转到`/app/index/test`地址
156
266
 
157
- 案例1:`this.$fetch('test')`,在index控制器类的index方法调用,则跳转到到`/app/index/test`地址
267
+ 示例2:`this.$success({ajax: 'data'})`,如果是ajax请求,则返回json `{state: 1, msg: '操作成功!', data: {ajax: 'data'}}`
158
268
 
159
- 案例2:`this.$fetch('/show')`,name包含'/'前缀,则跳转到地址`/show`
269
+ > 方法:**$error(msg, name)** 错误跳转或输出
160
270
 
161
- > 方法:**$success(msg, name)**
271
+ 继承自`Middleware`类,返回失败提示,name解析同$success方法,name为空,则跳转来源网址。
162
272
 
163
- 同步方法,继承自Middleware类,返回成功提示,name解析同$redirect方法,如果是ajax请求,则返回json数据。
273
+ > 方法:**async $next()** 执行下一个路由匹配
164
274
 
165
- 案例1:`this.$success('操作成功!', 'test')`,返回成功提示,并跳转到`/app/index/test`地址
275
+ 异步方法,继承自`Middleware`类,调用后,会等待执行全局路由配置里后面能匹配的路由。
166
276
 
167
- 案例2:`this.$success({ajax: 'data'})`,ajax返回json `{state: 1, msg: '操作成功!', data: {ajax: 'data'}}`
277
+ > 注意:所有的输出和跳转并不会阻止后续代码执行,所以要终止代码执行,需在前面加上`return`。
168
278
 
169
- > 方法:**$error(msg, name)**
279
+ > 方法:**async _init()** 控制器初始化
170
280
 
171
- 同步方法,继承自Middleware类,返回成功提示,name解析同$success方法,name为空,则跳转来源网址。
281
+ 异步方法,除了使用控制器中间件执行一些初始化或验证外,控制器提供了这个更简单的方法。一旦控制器定义了这个方法,在通过url访问控制器其他公开方法时,在执行完控制器中间件后,都会先执行这个函数,如果想终止后续代码执行,可以在方法里返回`false`。
282
+
283
+ > 方法:**async _end()** 控制器结束
284
+
285
+ 如果定义了这个函数,在通过url访问控制器其他公开方法后,会调用这个函数。
286
+
287
+ > 注意:因为系统默认注册了 `应用/控制器/方法` 的全局路由,所以控制器里的方法都能被直接访问,如果想作为私有方法不被访问,可以在方法前加下划线`_`前缀。
172
288
 
173
289
  ### Middleware中间件类
174
290
 
175
- > 方法:**$show(content)** 参照控制器类
291
+ > 方法:**$show(content)** 参照控制器类介绍
176
292
 
177
- > 方法:**$redirect(url, status)** 参照控制器类
293
+ > 方法:**$redirect(url, status)** 参照控制器类介绍
178
294
 
179
- > 方法:**$success(msg, url)** 参照控制器类
295
+ > 方法:**$success(msg, url)** 参照控制器类介绍
180
296
 
181
- > 方法:**$error(msg, url)** 参照控制器类
297
+ > 方法:**$error(msg, url)** 参照控制器类介绍
298
+
299
+ > 方法:**async $next()** 执行下一个中间件
182
300
 
183
301
  ### Db数据库类
184
302
 
185
- > 方法:**async startTrans(fun)** 开启事务
303
+ > 方法:**async startTrans(async fun)** 开启事务
304
+
305
+ 异步方法,如果fun不为空,并且是函数,则开启事务后,会自动执行此函数,并提交事务,不用再手工提交或回滚事务。
186
306
 
187
307
  > 方法:**async commit()** 提交事务
188
308
 
@@ -192,10 +312,40 @@ const {app, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, U
192
312
 
193
313
  > 方法:**table(table)** 设置数据表
194
314
 
315
+ table参数:数据表名字,不带前缀
316
+
195
317
  > 方法:**field(field)** 设置查询字段
196
318
 
319
+ 支持字符串或数组,支持多次调用
320
+
321
+ 示例1:`this.field('id,name');`
322
+
323
+ 示例2:`this.field(['id', 'name']).field('mobile');`
324
+
197
325
  > 方法:**where(where, logic)** 设置查询条件
198
326
 
327
+ 支持多次调用,`logic`设置多次调用之间的连接条件,默认为`and`,where参数为对象
328
+
329
+ 示例1:设置查询id为1的条件
330
+
331
+ `this.where({id: 1});` // where id = 1
332
+
333
+ 示例2:设置查询name为'aaa',并且sex为1的条件
334
+
335
+ `this.where({name: 'aaa', sex: 1});` // where name = 'aaa' and sex = 1
336
+
337
+ 示例3:多次调用`where`,设置查询name为'aaa',并且sex为1的条件,并且age大于18的条件
338
+
339
+ `this.where({name: 'aaa', sex: 1}).where({age: ['>', 18]}); // where (name = 'aaa' and sex = 1) and (age > 18)`
340
+
341
+ 可以看到,当字段值不是等于(=)时,字段值使用数组来标识,其中数组第一项为表达式(>),数组第二项为表达式要对比的值。数组的第三项为一个where内的连接逻辑,不设置的话默认为`and`,或连接的话,设置为`or`。
342
+
343
+ 示例4:设置查询name为'aaa',或着sex为1的条件
344
+
345
+ `this.where({name: 'aaa', sex: ['=', 1, 'or']});` // where name = 'aaa' or sex = 1
346
+
347
+ > 说明:where支持所有表达式('=', '<>', '!=', '>', '>=', '<', '<=', 'like', 'not like', 'in', 'not in', 'between', 'not between', 'is', 'is not', 'exp')
348
+
199
349
  > 方法:**distinct()** 数据去重
200
350
 
201
351
  > 方法:**group(field)** 数据分组
@@ -204,24 +354,64 @@ const {app, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, U
204
354
 
205
355
  > 方法:**order(field, order='asc')** 查询排序
206
356
 
357
+ order方法支持多次调用。
358
+
359
+ 示例:`this.order('id', 'desc').order('sort');` // order by id desc, sort asc
360
+
207
361
  > 方法:**limit(offset, rows)** 查询数据限制
208
362
 
363
+ 示例1:`this.limit(1, 10);` // limit 1, 10
364
+
365
+ 示例2:`this.limit(10);` // limit 10
366
+
209
367
  > 方法:**page(page, pageSize)** 分页查询
210
368
 
369
+ 当要进行分页查询时,用这个方法会更方便。
370
+
371
+ 示例1:`this.page(1, 10);` // limit 0, 10
372
+
373
+ 示例2:`this.page(2, 10);` // limit 10, 10
374
+
211
375
  > 方法:**cache(time)** 设置缓存时间
212
376
 
213
- > 方法:**getSql(fetch = true)** 设置返回sql语句
377
+ `time`单位为秒,设置缓存的话,在这个时间内再次执行相同条件的查询,将直接返回缓存的数据。
378
+
379
+ 示例:`this.cache(600);` // 设置缓存时间为10分钟
214
380
 
215
381
  > 方法:**join(table, on, type='left')** 设置表连接
216
382
 
383
+ 假如设置文章表和用户表以用户id为条件连接:
384
+
385
+ 示例:`this.table('article').join('user', 'article.user_id=user.id', 'left');` // from article article left join user user on article=user.id
386
+
387
+ > 方法:**getSql(fetch = true)** 设置是否返回sql语句
388
+
389
+ 设置后,调用查询方法不会再进行真正的查询,而是直接返回编译后的sql语句字符串。
390
+
391
+ 示例:`this.table('user').getSql().select();` // 返回:'select * from user'
392
+
217
393
  > 方法:**async select(condition)** 查询多条数据
218
394
 
395
+ 如果`condition`不为空,则会清空前面使用`where`方法设置的查询条件,然后以`condition`为参数调用一次`where`方法。
396
+
397
+ 示例:`this.where({name: 'aaa', sex: 1}).where({id: 1}).select({id: 2});` // 等效于:this.where({id: 2}).select();
398
+
219
399
  > 方法:**async find(condition)** 查询单条数据
220
400
 
401
+ 使用方法同`select()`
402
+
221
403
  > 方法:**async value(field)** 查询单个值
222
404
 
405
+ 返回单条数据里某个字段的值,假如查询user表id为1的用户的年龄值:
406
+
407
+ 示例:`this.table('user').where({id: 1}).value('age');` // 返回age值
408
+
223
409
  > 方法:**async count(field='*')** 查询记录数
224
410
 
411
+ 以某个字段查询记录总数,假如查询user表age为18的用户数:
412
+
413
+ 示例:`this.table('user').where({age: 18}).count();` // 返回记录总数
414
+
225
415
  > 方法:**async max(field)** 查询最大值
226
416
 
227
417
  > 方法:**async min(field)** 查询最小值
@@ -230,44 +420,80 @@ const {app, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, U
230
420
 
231
421
  > 方法:**async sum(field)** 对列求和
232
422
 
233
- > 方法:**async column(field, key)** 获取一列数据,如果设置key则获取一列键值对
423
+ > 方法:**async column(field, key)** 获取一列数据
424
+
425
+ 如果设置key则获取一列键值对
426
+
427
+ 示例1:`this.column('age');` // [16, 18, 20] 数字为年龄
234
428
 
235
- > 方法:**async pagination(page, page_size, pagination)** 分页查询并返回分页实例
429
+ 示例2:`this.column('age','id');` // {1: 16, 2: 18, 3: 20} 数字为年龄,1 2 3为id值
430
+
431
+ > 方法:**async pagination({page, page_size, pagination})** 分页查询并返回分页实例
432
+
433
+ 相当于`page()+Pagination`类分页,如果传入`pagination`实例,则以这个实例渲染分页,否则会自动创建一个分页实例。如果没有传page、page_size,会使用`pagination`实例的`page()`方法`pageSize()`自动生成。
434
+
435
+ 示例:`this.pagination();` // 返回[data_list, pagination]
236
436
 
237
437
  > 方法:**data(data)** 设置写入数据
238
438
 
439
+ 此方法用于更新或写入数据,提前设置需要的数据,支持多次调用
440
+
441
+ 示例:`this.data({age: 18}).data({sex: 1}).insert();` // 返回[data_list, pagination]
442
+
239
443
  > 方法:**allowField(field = true)** 设置过滤非数据表字段
240
444
 
445
+ 更新或写入数据时,会过滤掉非数据表里的字段
446
+
241
447
  > 方法:**async insert(data)** 插入一条数据
242
448
 
449
+ 如果data参数不为空,会清除前面使用`data()`方法设置的数据,并以此处传入数据为准
450
+
243
451
  > 方法:**async update(data, condition)** 更新数据
244
452
 
453
+ 如果`data`参数不为空,会清除前面使用`data()`方法设置的数据,并以此处传入数据为准。如果`condition`参数不为空,会清除前面设置`where()`设置的条件
454
+
245
455
  > 方法:**async inc(field, step)** 数据表字段自增
246
456
 
457
+ 设置字段field自增,step默认为1
458
+
247
459
  > 方法:**async dec(field, step)** 数据表字段自减
248
460
 
461
+ 设置字段field自减,step默认为1
462
+
249
463
  > 方法:**async exp(field, step)** 数据表字段执行自定义方法
250
464
 
251
465
  > 方法:**async delete(condition)** 删除数据
252
466
 
467
+ 如果`condition`参数不为空,会清除前面设置`where()`设置的条件,并以此处传入条件为准。
468
+
253
469
  > 方法:**async execute(sql, params, reset=true)** 解析并执行sql语句
254
470
 
471
+ 执行sql语句查询
472
+
473
+ > 方法:**format(sql, params)** 解析sql语句
474
+
255
475
  > 方法:**async tableInfo(table)** 获取表信息
256
476
 
257
477
  > 方法:**async tableField(table)** 获取表字段信息
258
478
 
259
- > 方法:**format(sql, params)** 解析sql语句
260
-
261
479
  > 方法:**deleteCache()** 清空数据库查询缓存
262
480
 
263
481
  ### Model模型类
264
482
 
265
483
  > 属性:**db** 模型的db实例
266
484
 
485
+ 每个模型文件,会懒自动创建一个独有的db实例
486
+
487
+ > 属性:**table** 数据表名字,默认为模型文件名
488
+
489
+ > 属性:**pk** 数据表主键字段,默认为`id`
490
+
267
491
  > 方法:**async add(data)** 同Db类insert方法
268
492
 
269
493
  > 方法:**async save(data, condition = {})** 智能调用Db类insert或update方法
270
494
 
495
+ 当data含有主键字段或condition不为空,则执行db实例`update()`方法,否则执行`insert()`方法
496
+
271
497
  > 方法:**async del(condition)** 同Db类delete方法
272
498
 
273
499
  > 方法:**async get(condition)** 同Db类find方法
@@ -276,26 +502,392 @@ const {app, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, U
276
502
 
277
503
  ### Pagination分页类
278
504
 
505
+ > 方法:**init(options)** 初始化
506
+
507
+ options参数默认继承自`./config/page.js`或框架`config.page`参数,框架page参数如下:
508
+
509
+ ```javascript
510
+ const page = {
511
+ page_key : 'page', // 默认分页标识
512
+ key_origin : 'query', // query 或 params
513
+ page_size : 10, // 默认分页大小
514
+ page_length : 5, // 默认分页长度,数字页码链接数量
515
+
516
+ //网址规则,可为空,可为路由名字,可用参数:页码${page}
517
+ //样例:':name'
518
+ //样例:'/list_${page}.html'
519
+ url_page : '',
520
+ url_index : '',
521
+
522
+ //模块样式 可用参数:网址${url},页码${page},总数${total_page},总页数${total_page}
523
+ index_tpl : '<li class="index"><a href="${url}">首页</a></li>',
524
+ end_tpl : '<li class="end"><a href="${url}">末页</a></li>',
525
+ prev_tpl : '<li class="prev"><a href="${url}">上一页</a></li>',
526
+ next_tpl : '<li class="next"><a href="${url}">下一页</a></li>',
527
+ list_tpl : '<li><a href="${url}">${page}</a></li>',
528
+ active_tpl : '<li class="active"><a href="${url}">${page}</a></li>',
529
+ info_tpl : '<span class="info">共${total_page}页,${total}条记录</span>',
530
+
531
+ //渲染模版
532
+ template : '<div class="pagination"><ul class="page">${index}${prev}${list}${next}${end}</ul>${info}</div>'
533
+ }
534
+ ```
535
+ - key_origin:设置分页参数获取位置,url或params
536
+ - page_key:设置分页标识,即设置或获取当前分页的字段
537
+ - url_page、url_index:设置生成的分页url模板或规则,网址规则可以为路由名字,可用参数为`${page}`
538
+
539
+ > 方法:**page(page)** 设置或获取当前页
540
+
541
+ > 方法:**pageSize(page_size)** 设置或获取分页大小
542
+
543
+ > 方法:**total(total)** 设置或获取总数
544
+
545
+ > 方法:**render(total, page, page_size)** 生成分页html代码
546
+
547
+ 传参,可以快速设置page、page_size、total
548
+
279
549
  ### View模板引擎类
280
550
 
551
+ > 方法:**assign(name, value)** 赋值模版变量,用法参考Controller类
552
+
553
+ > 方法:**data(name)** 获取已赋模版变量,用法参考Controller类
554
+
555
+ > 方法:**async fetch(template)** 渲染模板文件并输出,用法参考Controller类
556
+
557
+ > 方法:**async load(template)** 加载并输出模板文件,用法参考Controller类
558
+
559
+ > 方法:**async render(data)** 渲染字符串模板,用法参考Controller类
560
+
561
+ > 方法:**setFilter(fun_obj, fun)** 动态设置模版函数
562
+
563
+ 设置一个或多个模板函数:
564
+
565
+ 示例1:`this.setFilter('sum', (a, b) => {return a + b;});` // 设置一个函数sum
566
+
567
+ 示例2:`this.setFilter({'sum': (a, b) => {return a + b;}, 'fun2': () => {}});` // 同时设置sum、fun2两个函数,设置后可以在模板中使用
568
+
569
+ > 提示:View类初始化时,默认会设置一个`url`的模板函数,即在模板文件里可以直接使用`url`函数生成网址,示例:
570
+
571
+ ```javascript
572
+ // 首页模板代码
573
+ {{url('user')}}
574
+
575
+ // 生成网址(单应用模式)
576
+ '/index/user'
577
+ ```
578
+ `url`函数具体用法,请参考Url类的`build`方法
579
+
580
+ > 方法:**setFolder(view_folder)** 动态设置模版函数
581
+
582
+ > 方法:**setDepr(view_depr)** 动态设置文件分割符
583
+
584
+ > 本框架默认使用的模板引擎为`art-template`,关于模板语法,可以参考[art-template文档](http://aui.github.io/art-template/zh-cn/docs/ "art-template文档")
585
+
281
586
  ### Logger日志类
282
587
 
588
+ > 说明:日志类是一个静态类,方法都为静态方法,但同时也支持new创建一个新的静态实例。
589
+
590
+ > 提示:目前日志类输出没有具体的代码实现,只是数据格式化后,用node自带log打印。
591
+
592
+ > 方法:**static log(msg, level='info')** 输出日志
593
+
594
+ > 方法:**static error(...args)** 输出错误日志
595
+
596
+ > 方法:**static warning(...args)** 输出错误日志
597
+
598
+ > 方法:**static info(...args)** 输出错误日志
599
+
600
+ > 方法:**static debug(...args)** 输出错误日志
601
+
602
+ > 方法:**static sql(...args)** 输出错误日志
603
+
604
+ > 方法:**static http(...args)** 输出错误日志
605
+
606
+ > 方法:**static setHandle(...args)** 输出错误日志
607
+
283
608
  ### Cookie类
284
609
 
285
- ### Response跳转相应类
610
+ > 方法:**set(key, value, options)** 设置cookie
611
+
612
+ options默认继承`config.cookie`参数
613
+
614
+ > 方法:**get(key)** 获取cookie
615
+
616
+ > 方法:**delete(key)** 删除一个cookie
617
+
618
+ > 方法:**all()** 获取所有cookie
619
+
620
+ > 方法:**clear()** 清除所有cookie
621
+
622
+ > 方法:**keys()** 获取所有cookie的key
623
+
624
+ ### Response跳转响应类
625
+
626
+ > 方法:**show(data)** 渲染模板文件并输出,用法参考Controller类
627
+
628
+ > 方法:**redirect(url, status = 302)** 302或其他跳转,用法参考Controller类
629
+
630
+ > 方法:**success(msg='操作成功!', name)** 成功跳转或输出,用法参考Controller类
631
+
632
+ > 方法:**error(msg='操作失败!', name)** 错误跳转或输出,用法参考Controller类
633
+
634
+ > 方法:**jump(msg, url, state=1)** 跳转或输出
635
+
636
+ > 方法:**exception(err)** 输出异常页面
637
+
638
+ > 方法:**wait(time)** 设置页面跳转时等待时间
286
639
 
287
640
  ### Upload上传类
288
641
 
642
+ > 注意:文件上传,需要先开启`config.app.koa_body`参数,具体设置可参考[koa-body文档](https://github.com/koajs/koa-body#readme)
643
+
644
+ > 方法:**file(file)** 设置file文件
645
+
646
+ 当参数`file`为字符串时,会调用`getFile(name)`方法获取上传文件并配置,如果为文件,则直接赋值。
647
+
648
+ > 方法:**getFile(name)** 获取一个上传文件
649
+
650
+ > 方法:**validate(rule={})** 设置文件验证规则参数
651
+
652
+ - rule.size,文件大小限制
653
+ - rule.ext,文件后缀
654
+ - rule.type,文件MIME类型
655
+ - rule.size,文件大小限制
656
+ - 默认会对图片后缀的文件做图片MIME验证
657
+
658
+ > 方法:**rule(name)** 设置文件保存名字或规则
659
+
660
+ 参数name为字符串或函数,不传的话按内部规则。
661
+
662
+ > 方法:**checkExt(ext)** 验证文件后缀
663
+
664
+ > 方法:**checkType(type)** 验证文件MIME
665
+
666
+ > 方法:**checkImg()** 验证图片文件
667
+
668
+ > 方法:**check()** 对设置的规则执行验证
669
+
670
+ > 方法:**getError()** 获取验证错误或上传文件失败信息
671
+
672
+ > 方法:**async save(dir)** 保存上传文件
673
+
674
+ 参数dir为文件保存目录,相对应用的根目录。内部会自动执行`check()`方法。
675
+
676
+ 示例1:`await this.file('img').save('upload');` // 会将上传的图片保存到upload目录下
677
+
678
+ 示例2:`await this.file('img').validate({size: 1024, ext: 'png', type: 'png'}).save('upload');` // 设置只能上传小于1M的png图片
679
+
680
+ 上传成功返回参数:
681
+
682
+ ```javascript
683
+ return {
684
+ filename, // 保存后的文件名
685
+ extname, // 文件后缀名
686
+ savename, // 除去上传目录的完成名字
687
+ filepath, // 文件保存目录
688
+ name, // 文件原始名字
689
+ size, // 文件大小
690
+ mimetype, // mimetype
691
+ hash // hash
692
+ };
693
+ ```
694
+
289
695
  ### Url网址解析类
290
696
 
291
- ### Context自动加载基类
697
+ > 方法:**build(url='', vars, ext='', domain='')** 生成url网址
698
+
699
+ Url类build方法,会根据当前访问url参数,智能生成需要的网址,假如单应用模式访问`127.0.0.1/user/info`
700
+
701
+ 示例1:`this.build()` // '/user/list'
702
+
703
+ 示例2:`this.build('list')` // '/user/list'
704
+
705
+ 示例3:`this.build('article/list')` // '/article/list'
706
+
707
+ 示例4:`this.build('list', {type: 'hot', order: 'click'})` // '/user/list?type=host&order=click'
708
+
709
+ 示例5:`this.build('list', '.html')` // '/user/list.html'
710
+
711
+ 示例6:`this.build('list', '.html', 'localhost')` // 'localhost/user/list.html'
712
+
713
+ 如果`url`参数包含`/`前缀,则直接做为网址使用
714
+
715
+ 示例7:`this.build('/list', {type: 'hot'})` // '/list?type=host'
716
+
717
+ 如果自定义的有路由地址,通过路由名字可以反向编译地址,假如有路由定义`./config/routes.js`
718
+
719
+ ```javascript
720
+ route = [
721
+ {url: '/article/:id.html', path: 'article/article', name: 'article'},
722
+ ];
723
+
724
+ module.exports = route;
725
+ ```
726
+ 这是一个自定义文章页路由地址,当访问`127.0.0.1/article/123.html`时会匹配到这个地址,并且这条路由的名字`name`为`article`,在url反向编译路由时,通过带`:`号的名字来定义:
727
+
728
+ 示例8:`this.build(':article', {id: 456})` // '/article/456.html'
729
+
730
+ ### Context配置上下文类
731
+
732
+ > 属性:**ctx** 整个框架的上下文,具体参数,可以参考[Koa上下文(Context)ctx文档](https://www.itying.com/koa/)
292
733
 
293
734
  ### config配置
294
735
 
736
+ 应用的配置可以为`.config/`目录+`.config/xxx.js`文件的形式,也可以直接写到一个`.config.js`文件里。
737
+
738
+ 应用配置不用每项都设置,只设置自己需要改的,默认会继承框架的默认配置,在控制器、中间件、模板类、模型类里都可以通过`this.$config.xxx`使用。
739
+
740
+ - app配置`.config/app.js`:
741
+ ```javascript
742
+ const app = {
743
+ app_debug: true, // 调试模式
744
+ app_multi: false, // 是否开启多应用
745
+
746
+ default_app: 'app', // 默认应用
747
+ default_controller: 'index', //默认控制器
748
+ default_action: 'index', // 默认方法
749
+
750
+ common_app: 'common', // 公共应用,存放公共模型及逻辑
751
+ controller_folder: 'controller', //控制器目录名
752
+
753
+ static_dir: '', // 静态文件目录,相对于应用根目录,为空或false时,关闭静态访问
754
+
755
+ koa_body: null // koa-body配置参数,为''、null、false时,关闭koa-body,此时upload类将不可用,并且系统接收不到post参数。启用的话,建议配置:{multipart: true, formidable: {keepExtensions: true, maxFieldsSize: 10 * 1024 * 1024}}
756
+ }
757
+ module.exports = app;
758
+ ```
759
+ - 模板配置`.config/view.js`:
760
+ ```javascript
761
+ const view = {
762
+ view_folder: 'view', // 模板目录名
763
+ view_depr: '/', // 模版文件名分割符,'/'代表二级目录
764
+ view_ext: '.htm', // 模版文件后缀
765
+ view_engine: 'art-template', // 默认模版引擎,字符串或引擎类,咱不支持更换
766
+ view_filter: {}, // 模版函数,配置注入模板的函数,默认会自动注入url函数
767
+ }
768
+ module.exports = view;
769
+ ```
770
+ - 数据库配置`.config/db.js`:可以配置多个,方便程序里切换使用。
771
+ ```javascript
772
+ const db = {
773
+ default: {
774
+ type : 'mysql', // 数据库类型
775
+ host : '127.0.0.1', // 服务器地址
776
+ database : 'jj', // 数据库名
777
+ user : 'root', // 数据库用户名
778
+ password : '', // 数据库密码
779
+ port : '', // 数据库连接端口
780
+ charset : 'utf8', // 数据库编码默认采用utf8
781
+ prefix : 'jj_' // 数据库表前缀
782
+ }
783
+ }
784
+ module.exports = db;
785
+ ```
786
+ - 日志配置`.config/log.js`:log_handle可以自定义日志handle
787
+ ```javascript
788
+ const log = {
789
+ log_level: [], // [error, warning, info, debug, http, sql]
790
+ log_handle: function(msg, level) {console.log(`[${format('YY-mm-dd HH:ii:ss')}] [${level}] ${typeof msg == 'String' ? msg : JSON.stringify(msg)}`);} //function(msg, level) {}
791
+ }
792
+ module.exports = log;
793
+ ```
794
+ - 缓存配置`.config/cache.js`:
795
+ ```javascript
796
+ const cache = {
797
+ cache_time: 60 * 60 * 24, // 默认缓存时间(1天),为空或false则为10年
798
+ clear_time: undefined // (undefined: 一天清理一次, 0: 关闭自动清理, >0: 为自动清理周期)
799
+ }
800
+ module.exports = cache;
801
+ ```
802
+ - 分页配置`.config/page.js`:
803
+ ```javascript
804
+ const page = {
805
+ page_key : 'page', // 默认分页标识
806
+ key_origin : 'query', // query 或 params
807
+ page_size : 10, // 默认分页大小
808
+ page_length : 5, // 默认分页长度,数字页码链接数量
809
+
810
+ //网址规则,可为空,可为路由名字,可用参数:页码${page}
811
+ //样例:':name'
812
+ //样例:'/list_${page}.html'
813
+ url_page : '',
814
+ url_index : '',
815
+
816
+ //模块样式 可用参数:网址${url},页码${page},总数${total_page},总页数${total_page}
817
+ index_tpl : '<li class="index"><a href="${url}">首页</a></li>',
818
+ end_tpl : '<li class="end"><a href="${url}">末页</a></li>',
819
+ prev_tpl : '<li class="prev"><a href="${url}">上一页</a></li>',
820
+ next_tpl : '<li class="next"><a href="${url}">下一页</a></li>',
821
+ list_tpl : '<li><a href="${url}">${page}</a></li>',
822
+ active_tpl : '<li class="active"><a href="${url}">${page}</a></li>',
823
+ info_tpl : '<span class="info">共${total_page}页,${total}条记录</span>',
824
+
825
+ //渲染模版
826
+ template : '<div class="pagination"><ul class="page">${index}${prev}${list}${next}${end}</ul>${info}</div>'
827
+ }
828
+ module.exports = page;
829
+ ```
830
+ - 跳转模板配置`.config/tpl.js`:模板可以配置为自定义的
831
+ ```javascript
832
+ const tpl = {
833
+ jump: require('./tpl/jump'), // 跳转模板
834
+ exception: require('./tpl/exception') // 异常页面模板
835
+ }
836
+ module.exports = tpl;
837
+ ```
838
+ - 自定义配置`.config/self.js`:自定义配置同样可以直接通过`this.$config.self`使用。
839
+ ```javascript
840
+ const self = {
841
+ option1: ''
842
+ option2: ''
843
+ ...
844
+ }
845
+ module.exports = self;
846
+ ```
847
+ - 路由配置`.config/routes.js`:
848
+ 路由功能基于`@koa/router`开发,关于url匹配规则可以参考官方文档:[文档地址](https://www.npmjs.com/package/@koa/router)
849
+
850
+ > 本框架默认内置 `应用/控制器/方法` 的全局路由,即如果不需要定制url,可以直接访问,无需配置路由。
851
+
852
+ > 路由配置为一个数组,每一项为一条规则,项属性包含url规则,path访问路径,name规则名字,type路由类型。url一旦匹配,即会停止,如果需要继续向下匹配,需在程序内调用 `await this.$next();`,路由配置参考示例:
853
+ ```javascript
854
+ routes = [
855
+ {url: '/', path: 'app/index/index2'}, // 访问'/',会定位到app应用的index控制的index2方法
856
+ {url: '/article/:id.html', path: 'app/article/article', name: 'article'}, // 访问'/article/123.html',会定位到app应用的article控制的article方法;通过this.ctx.parrams.id可以获取到参数id;通过article可以反编译网址,执行this.$url.build(':article', {id: 123}); 生成'/article/123.html'
857
+ {url: '/admin', path: 'app/admin/check', type: 'middleware'}, // 访问'/admin',会定位到app应用的admin中间件的check方法
858
+ {url: '/about', path: 'app/about/index', type: 'view'}, // 访问'/about',会定位到app应用的view模板目录about目录下的index.htm文件,并直接输出文件内容
859
+ {url: '/:cate/list_:page.html', path: 'cate/cate', name: 'cate_page'}, // 多参数分页示例
860
+ {url: '/hello', path: async (ctx, next) => {ctx.body = 'hello world, hello jj.js!';}} // 直接路由到函数
861
+ ];
862
+
863
+ module.exports = routes;
864
+ ```
865
+ > 注意:本路由配置示例前两条规则为常规用法,后面的非常规用法,不建议使用。如果是但应用模式,可以去掉path参数里的app。
866
+
867
+ ### 应用入口文件
868
+
869
+ 应用入口文件放在项目的根目录,名字可以任意改。
870
+
871
+ 示例文件 `./server.js`
872
+ ```javascript
873
+ const {app, Logger} = require('jj.js');
874
+
875
+ app.run(3000, '127.0.0.1', function(err){
876
+ !err && Logger.info('http server is ready on 3000');
877
+ });
878
+ ```
879
+ 其中`app`是一个koa实例,可以使用`use`方法添加应用级中间件,使用方法参考koa文档。`app`的`run`方法经过了封装,仅允许调用一次。其中`Logger`因为是一个静态类,所以可以直接调用`info`方法输出日志。
880
+
881
+ ## 总结
882
+
883
+ 通过以上文档,可以看到:
884
+ 1. jj.js是一个轻量的mvc框架,几乎所有文件不使用就不会调用。
885
+ 2. 同时也是个功能强大的框架,支持应用级、路由级、控制器级三级中间件。
886
+ 3. 类库自动加载,只要是继承自`Context`的类库,都可以在方法内使用包含`$`前缀的属性自动加载其他资源。
295
887
 
296
888
  ## 应用案例
297
889
 
298
- - [基于jj.js的轻量博客系统:Melog](https://me.i-i.me/melog/)
890
+ - [基于jj.js的轻量博客系统:Melog](https://me.i-i.me/special/melog.html)
299
891
 
300
892
  ### Nginx代理
301
893
 
package/lib/db.js CHANGED
@@ -16,16 +16,33 @@ class Db extends Context
16
16
  super(ctx);
17
17
  this._config = null;
18
18
  this._table = '';
19
- this._prefix = '';
20
19
  this._options = {};
21
20
  this._queryStr = '';
22
21
  this._tableField = {};
23
22
  this.connect();
24
23
  }
25
24
 
25
+ reset() {
26
+ this._options = {
27
+ distinct: '',
28
+ field: [],
29
+ join: {},
30
+ where: [],
31
+ group: '',
32
+ having: '',
33
+ order: {},
34
+ limit: '',
35
+ page: {},
36
+ getSql: false,
37
+ data: {},
38
+ allowField: false,
39
+ prefix: this._config.prefix || ''
40
+ };
41
+ return this;
42
+ }
43
+
26
44
  connect(options='default') {
27
45
  this._config = typeof options === 'string' ? cfg_db[options] : options;
28
- this._prefix = this._config.prefix || '';
29
46
  this.reset();
30
47
 
31
48
  let cur_pool = pool.get(this._config);
@@ -218,24 +235,6 @@ class Db extends Context
218
235
  });
219
236
  }
220
237
 
221
- reset() {
222
- this._options = {
223
- distinct: '',
224
- field: [],
225
- join: {},
226
- where: [],
227
- group: '',
228
- having: '',
229
- order: {},
230
- limit: '',
231
- page: null,
232
- getSql: false,
233
- data: {},
234
- allowField: false
235
- };
236
- return this;
237
- }
238
-
239
238
  table(table) {
240
239
  if(table) {
241
240
  this._table = table.trim().replace(/ +/g, ' ');
@@ -245,7 +244,7 @@ class Db extends Context
245
244
 
246
245
  prefix(prefix) {
247
246
  if(typeof prefix !== 'undefined') {
248
- this._prefix = prefix.trim();
247
+ this._options.prefix = prefix.trim();
249
248
  }
250
249
  return this;
251
250
  }
@@ -450,11 +449,13 @@ class Db extends Context
450
449
  }
451
450
 
452
451
  async pagination({page, page_size, pagination} = {}) {
452
+ !page && (page = this._options.page.page);
453
+ !page_size && (page_size = this._options.page.pageSize);
453
454
  if(!pagination) {
454
455
  pagination = this.$pagination.__node.isClass ? this.$pagination : this.$.pagination;
455
456
  }
456
457
 
457
- const options = {...this._options};
458
+ const options = {...this._options}; // 暂存options
458
459
  const total = await this.count();
459
460
  pagination.total(total);
460
461
 
@@ -462,7 +463,7 @@ class Db extends Context
462
463
  page_size ? pagination.pageSize(page_size) : (page_size = pagination.pageSize());
463
464
 
464
465
  if(total) {
465
- this._options = options;
466
+ this._options = options; // 恢复options
466
467
  const result = await this.page(page, page_size).select();
467
468
  return [result, pagination];
468
469
  } else {
@@ -590,13 +591,13 @@ class Db extends Context
590
591
 
591
592
  _parseTable() {
592
593
  const table = this._table.replace(' ', '` `');
593
- return `\`${this._prefix}${table}\``;
594
+ return `\`${this._options.prefix}${table}\``;
594
595
  }
595
596
 
596
597
  _parseJoin() {
597
598
  let join = '';
598
599
  Object.keys(this._options.join).forEach(key => {
599
- const table = '`' + this._prefix + key.replace(' ', '` `') + '`';
600
+ const table = '`' + this._options.prefix + key.replace(' ', '` `') + '`';
600
601
  const value = this._options.join[key];
601
602
  join += (join ? ' ' : '') + value.type + ' join ' + table + ' on ' + value.on;
602
603
  });
@@ -725,7 +726,7 @@ class Db extends Context
725
726
 
726
727
  let allowField = this._options.allowField;
727
728
  if(allowField === true) {
728
- const table_name = this._prefix + this._table;
729
+ const table_name = this._options.prefix + this._table;
729
730
  if(!this._tableField[table_name]) {
730
731
  this._tableField[table_name] = await this.tableField();
731
732
  }
package/lib/url.js CHANGED
@@ -12,7 +12,7 @@ const {toLine} = require('./utils/str');
12
12
  class Url extends Context
13
13
  {
14
14
  build(url='', vars, ext='', domain='') {
15
- if(vars && typeof vars != 'object') {
15
+ if(typeof vars != 'object') {
16
16
  [vars, ext, domain] = [{}, vars, ext];
17
17
  }
18
18
  const query = {...vars};
package/lib/view.js CHANGED
@@ -14,10 +14,7 @@ class View extends Context
14
14
 
15
15
  // 加载文件
16
16
  async load(template) {
17
- const file_name = this.parsePath(template);
18
- if(!isFileSync(file_name)) {
19
- throw new Error(`文件不存在(${file_name})`);
20
- }
17
+ const file_name = this.parseTplName(template);
21
18
  return await readFile(file_name);
22
19
  }
23
20
 
@@ -28,10 +25,7 @@ class View extends Context
28
25
 
29
26
  // 渲染文件
30
27
  async fetch(template) {
31
- const file_name = this.parsePath(template);
32
- if(!isFileSync(file_name)) {
33
- throw new Error(`文件不存在(${file_name})`);
34
- }
28
+ const file_name = this.parseTplName(template);
35
29
  return await this._engine(file_name, this._data || {});
36
30
  }
37
31
 
@@ -52,20 +46,36 @@ class View extends Context
52
46
  }
53
47
 
54
48
  //解析模板地址
55
- parsePath(template=this.ctx.ACTION) {
49
+ parsePath(template=this.ctx.ACTION, view_folder, view_depr) {
50
+ !view_folder && (view_folder = cfg_view.view_folder);
51
+ !view_depr && (view_depr = cfg_view.view_depr);
56
52
  let view_file = template;
53
+
57
54
  if(view_file.indexOf('/') !== 0) {
58
55
  const temp = view_file.replace(/^\/|\/$/g, '').split('/').reverse().map(u => toLine(u));
59
- const folder = this._folder;
60
- const depr = this._depr;
61
56
  if(temp.length <= 3) {
62
- view_file = `${temp[2] || this.ctx.APP}/${folder || cfg_view.view_folder}/${temp[1] || this.ctx.CONTROLLER}${depr || cfg_view.view_depr}${temp[0]}`;
57
+ view_file = `${temp[2] || this.ctx.APP}/${view_folder}/${temp[1] || this.ctx.CONTROLLER}${view_depr}${temp[0]}`;
63
58
  }
64
59
  }
65
60
  path.extname(view_file) || (view_file += cfg_view.view_ext);
66
61
  return path.join(this.$config.app.base_dir, view_file);
67
62
  }
68
63
 
64
+ //解析模板名字
65
+ parseTplName(template) {
66
+ let file_name = this.parsePath(template, this._folder, this._depr);
67
+ let is_file = isFileSync(file_name);
68
+ if(!is_file && (this._folder || this._depr)) {
69
+ file_name = this.parsePath(template);
70
+ is_file = isFileSync(file_name);
71
+ }
72
+ if(!is_file) {
73
+ file_name = this.parsePath(template, this._folder, this._depr);
74
+ throw new Error(`文件不存在(${file_name})`);
75
+ }
76
+ return file_name;
77
+ }
78
+
69
79
  // 设置模版引擎
70
80
  setEngine(engine) {
71
81
  this._engine = typeof engine == 'string' ? require(engine) : engine;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jj.js",
3
- "version": "0.8.1",
3
+ "version": "0.8.4",
4
4
  "description": "A simple and lightweight MVC framework built on nodejs+koa2",
5
5
  "main": "jj.js",
6
6
  "scripts": {