middleware
最后更新于:2022-04-01 04:35:10
[TOC=2,4]
`think.middleware.base` 类继承自 [think.http.base](https://thinkjs.org/zh-CN/doc/2.0/api_think_http_base.html)。
##### ES6 方式
~~~
export default class extends think.middleware.base {
run(){
}
}
~~~
##### 动态创建类的方式
~~~
module.exports = think.middleware({
run: function(){
}
})
~~~
### 方法
#### middleare.run()
* `return` {Promise}
middleware 对外的方法入口,调用 middleware 时会自动调用该方法。
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_middleware.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_middleware.md)
model.mongo
最后更新于:2022-04-01 04:35:07
[TOC=2,4]
`think.model.mongo` 继承类 [think.model.base](https://thinkjs.org/zh-CN/doc/2.0/api_model.html)。
##### 使用 ES6 的语法继承该类
~~~
export default class extends think.model.mongo {
getList(){
}
}
~~~
##### 使用普通方式继承该类
~~~
module.exports = think.model("mongo", {
getList: function(){
}
})
~~~
### 属性
#### model.indexes
设置字段索引,数据操作之前会自动创建索引。
~~~
export default class extends think.model.mongo {
init(...args){
super.init(...args);
//配置索引
this.indexes = {
}
}
}
~~~
##### 单字段索引
~~~
export default class extends think.model.mongo {
init(...args){
super.init(...args);
//配置索引
this.indexes = {
name: 1
}
}
}
~~~
##### 唯一索引
通过 `$unique` 来指定为唯一索引,如:
~~~
export default class extends think.model.mongo {
init(...args){
super.init(...args);
//配置索引
this.indexes = {
name: {$unique: 1}
}
}
}
~~~
##### 多字段索引
可以将多个字段联合索引,如:
~~~
export default class extends think.model.mongo {
init(...args){
super.init(...args);
//配置索引
this.indexes = {
email: 1
test: {
name: 1,
title: 1,
$unique: 1
}
}
}
}
~~~
#### model.pk
主键名,默认为 `_id`,可以通过 `this.getPk` 方法获取。
### 方法
#### model.where(where)
mongo 模型中的 where 条件设置和关系数据库中不太一样。
##### 等于判断
~~~
export default class extends think.model.mongo {
where1(){
return this.where({ type: "snacks" }).select();
}
}
~~~
##### AND 条件
~~~
export default class extends think.model.mongo {
where1(){
return this.where({ type: "food", price: { $lt: 9.95 } }).select();
}
}
~~~
##### OR 条件
~~~
export default class extends think.model.mongo {
where1(){
return this.where({
$or: [ { qty: { $gt: 100 } }, { price: { $lt: 9.95 } } ]
}).select();
}
where2(){
return this.where({
type: "food",
$or: [ { qty: { $gt: 100 } }, { price: { $lt: 9.95 } } ]
}).select();
}
}
~~~
##### 内嵌文档
~~~
export default class extends think.model.mongo {
where1(){
return this.where( {
producer:
{
company: "ABC123",
address: "123 Street"
}
}).select();
}
where2(){
return this.where({ "producer.company": "ABC123" } ).select();
}
}
~~~
##### IN 条件
~~~
export default class extends think.model.mongo {
where1(){
return this.where({ type: { $in: [ "food", "snacks" ] } }).select();
}
}
~~~
* * *
更多文档请见 [https://docs.mongodb.org/manual/reference/operator/query/#query-selectors](https://docs.mongodb.org/manual/reference/operator/query/#query-selectors)。
#### model.collection()
* `return` {Promise}
获取操作当前表的句柄。
~~~
export default class extends think.model.mongo {
async getIndexes(){
let collection = await this.collection();
return collection.indexes();
}
}
~~~
#### model.aggregate(options)
聚合查询。具体请见 [https://docs.mongodb.org/manual/core/aggregation-introduction/](https://docs.mongodb.org/manual/core/aggregation-introduction/)。
#### model.mapReduce(map, reduce, out)
mapReduce 操作,具体请见 [https://docs.mongodb.org/manual/core/map-reduce/](https://docs.mongodb.org/manual/core/map-reduce/)。
#### model.createIndex(indexes, options)
* `indexes` {Object} 索引配置
* `options` {Object}
创建索引。
#### model.getIndexes()
* `return` {Promise}
获取索引。
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_model_mongo.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_model_mongo.md)
model
最后更新于:2022-04-01 04:35:05
[TOC=2,4]
`think.model.base`继承自 [think.base](https://thinkjs.org/zh-CN/doc/2.0/api_think_base.html) 类。
##### 使用 ES6 的语法继承该类
~~~
export default class extends think.model.base {
getList(){
}
}
~~~
##### 使用普通方式继承该类
~~~
module.exports = think.model({
getList: function(){
}
})
~~~
### 属性
#### model.pk
数据表主键,默认为`id`。
#### model.name
模型名,默认从当前文件名中解析。
当前文件路径为 for/bar/app/home/model/user.js,那么解析的模型名为`user`。
#### model.tablePrefix
数据表名称前缀,默认为`think_`。
#### model.tableName
数据表名称,不包含前缀。默认等于模型名。
#### model.fields
数据表字段,默认自动从数据表分析。
#### model.indexes
数据表索引,默认自动从数据表分析。
#### model.config
配置,实例化的时候指定。
#### model._db
连接数据库句柄。
#### model._data
操作的数据。
#### model._options
操作选项。
### 方法
#### model.model(name, options, module)
* `name` {String} 模型名称
* `options` {Object} 配置项
* `module` {String} 模块名
* `return` {Object}
获取模型实例,可以跨模块获取。
~~~
export default class extends think.model.base {
* getList(){
//获取 user 模型实例
let instance = this.model("user");
let list = yield instance.select();
let ids = list.map(item => {
return item.id;
});
let data = yield this.where({id: ["IN", ids]}).select();
return data;
}
}
~~~
#### model.getTablePrefix()
* `return` {string}
获取表名前缀。
#### model.getConfigKey()
* `return` {String}
获取配置对应的 key,缓存 db 句柄时使用。
#### model.db()
* `return` {Object}
根据当前的配置获取 db 实例,如果已经存在则直接返回。
#### model.getModelName()
* `return` {String} 模型名称
如果已经配置则直接返回,否则解析当前的文件名。
#### model.getTableName()
* `return` {String} 获取表名,包含表前缀
获取表名,包含表前缀。
#### model.cache(key, timeout)
* `key` {String} 缓存 key
* `timeout` {Number} 缓存有效时间,单位为秒
* `return` {this}
设置缓存选项。
##### 设置缓存 key 和时间
~~~
export default class extends think.model.base {
getList(){
return this.cache("getList", 1000).where({id: {">": 100}}).select();
}
}
~~~
##### 只设置缓存时间,缓存 key 自动生成
~~~
export default class extends think.model.base {
getList(){
return this.cache(1000).where({id: {">": 100}}).select();
}
}
~~~
##### 设置更多的缓存信息
~~~
export default class extends think.model.base {
getList(){
return this.cache({
key: "getList",
timeout: 1000,
type: "file" //使用文件方式缓存
}).where({id: {">": 100}}).select();
}
}
~~~
#### model.limit(offset, length)
* `offset` {Number} 设置查询的起始位置
* `length` {Number} 设置查询的数据长度
* `return` {this}
设置查询结果的限制条件。
##### 限制数据长度
~~~
export default class extends think.model.base {
getList(){
//查询20条数据
return this.limit(20).where({id: {">": 100}}).select();
}
}
~~~
##### 限制数据起始位置和长度
~~~
export default class extends think.model.base {
getList(){
//从起始位置100开始查询20调数据
return this.limit(100, 20).where({id: {">": 100}}).select();
}
}
~~~
#### model.page(page, listRows)
* `page` {Number} 当前页,从 1 开始
* `listRows` {Number} 每页的条数
* `return` {this}
设置查询分页数据,自动转化为 `limit` 数据。
~~~
export default class extends think.model.base {
getList(){
//查询第 2 页数据,每页 10 条数据
return this.page(2, 10).where({id: {">": 100}}).select();
}
}
~~~
#### model.where(where)
* `where` {String | Object} where 条件
* `return` {this}
设置 where 查询条件。可以通过属性 `_logic` 设置逻辑,默认为 `AND`。可以通过属性 `_complex` 设置复合查询。
`注:` 1、以下示例不适合 mongo model,mongo 中设置 where 条件请见 model.mongo 里的 where 条件设定。2、where 条件中的值需要在 Logic 里做数据校验,否则可能会有漏洞。
##### 普通条件
~~~
export default class extends think.model.base {
where1(){
//SELECT * FROM `think_user`
return this.where().select();
}
where2(){
//SELECT * FROM `think_user` WHERE ( `id` = 10 )
return this.where({id: 10}).select();
}
where3(){
//SELECT * FROM `think_user` WHERE ( id = 10 OR id < 2 )
return this.where("id = 10 OR id < 2").select();
}
where4(){
//SELECT * FROM `think_user` WHERE ( `id` != 10 )
return this.where({id: ["!=", 10]}).select();
}
}
~~~
##### null 条件
~~~
export default class extends think.model.base {
where1(){
//SELECT * FROM `think_user` where ( title IS NULL );
return this.where({title: null}).select();
}
where2(){
//SELECT * FROM `think_user` where ( title IS NOT NULL );
return this.where({title: ["!=", null]}).select();
}
}
~~~
##### EXP 条件
ThinkJS 默认会对字段和值进行转义,防止安全漏洞。有时候一些特殊的情况不希望被转义,可以使用 EXP 的方式,如:
~~~
export default class extends think.model.base {
where1(){
//SELECT * FROM `think_user` WHERE ( (`name` ="name") )
return this.where({name: ["EXP", "=\"name\""]}).select();
}
}
~~~
##### LIKE 条件
~~~
export default class extends think.model.base {
where1(){
//SELECT * FROM `think_user` WHERE ( `title` NOT LIKE "welefen" )
return this.where({title: ["NOTLIKE", "welefen"]}).select();
}
where2(){
//SELECT * FROM `think_user` WHERE ( `title` LIKE "%welefen%" )
return this.where({title: ["like", "%welefen%"]}).select();
}
//like 多个值
where3(){
//SELECT * FROM `think_user` WHERE ( (`title` LIKE "welefen" OR `title` LIKE "suredy") )
return this.where({title: ["like", ["welefen", "suredy"]]}).select();
}
//多个字段或的关系 like 一个值
where4(){
//SELECT * FROM `think_user` WHERE ( (`title` LIKE "%welefen%") OR (`content` LIKE "%welefen%") )
return this.where({"title|content": ["like", "%welefen%"]}).select();
}
//多个字段与的关系 Like 一个值
where5(){
//SELECT * FROM `think_user` WHERE ( (`title` LIKE "%welefen%") AND (`content` LIKE "%welefen%") )
return this.where({"title&content": ["like", "%welefen%"]}).select();
}
}
~~~
##### IN 条件
~~~
export default class extens think.model.base {
where1(){
//SELECT * FROM `think_user` WHERE ( `id` IN ("10","20") )
return this.where({id: ["IN", "10,20"]}).select();
}
where2(){
//SELECT * FROM `think_user` WHERE ( `id` IN (10,20) )
return this.where({id: ["IN", [10, 20]]}).select();
}
where3(){
//SELECT * FROM `think_user` WHERE ( `id` NOT IN (10,20) )
return this.where({id: ["NOTIN", [10, 20]]}).select();
}
}
~~~
##### BETWEEN 查询
~~~
export default class extens think.model.base {
where1(){
//SELECT * FROM `think_user` WHERE ( (`id` BETWEEN 1 AND 2) )
return this.where({id: ["BETWEEN", 1, 2]}).select();
}
where2(){
//SELECT * FROM `think_user` WHERE ( (`id` BETWEEN "1" AND "2") )
return this.where({id: ["between", "1,2"]}).select();
}
}
~~~
##### 多字段查询
~~~
export default class extends think.model.base {
where1(){
//SELECT * FROM `think_user` WHERE ( `id` = 10 ) AND ( `title` = "www" )
return this.where({id: 10, title: "www"}).select();
}
//修改逻辑为 OR
where2(){
//SELECT * FROM `think_user` WHERE ( `id` = 10 ) OR ( `title` = "www" )
return this.where({id: 10, title: "www", _logic: "OR"}).select();
}
//修改逻辑为 XOR
where2(){
//SELECT * FROM `think_user` WHERE ( `id` = 10 ) XOR ( `title` = "www" )
return this.where({id: 10, title: "www", _logic: "XOR"}).select();
}
}
~~~
##### 多条件查询
~~~
export default class extends think.model.base {
where1(){
//SELECT * FROM `think_user` WHERE ( `id` > 10 AND `id` < 20 )
return this.where({id: {">": 10, "<": 20}}).select();
}
//修改逻辑为 OR
where2(){
//SELECT * FROM `think_user` WHERE ( `id` < 10 OR `id` > 20 )
return this.where({id: {"<": 10, ">": 20, _logic: "OR"}}).select()
}
}
~~~
##### 复合查询
~~~
export default class extends think.model.base {
where1(){
//SELECT * FROM `think_user` WHERE ( `title` = "test" ) AND ( ( `id` IN (1,2,3) ) OR ( `content` = "www" ) )
return this.where({
title: "test",
_complex: {id: ["IN", [1, 2, 3]],
content: "www",
_logic: "or"
}
}).select()
}
}
~~~
#### model.field(field)
* `field` {String | Array} 设置要查询的字段,可以是字符串,也可以是数组
* `return` {this}
设置要查询的字段。
##### 字符串方式
~~~
export default class extends think.controller.base {
async indexAction(){
let model = this.model("user");
//设置要查询的字符串,字符串方式,多个用逗号隔开
let data = await model.field("name,title").select();
}
}
~~~
##### 调用 SQL 函数
~~~
export default class extends think.controller.base {
//字段里调用 SQL 函数
async listAction(){
let model = this.model("user");
let data = await model.field("id, INSTR(\"30,35,31,\",id + \",\") as d").select();
}
}
~~~
##### 数组方式
~~~
export default class extends think.controller.base {
async indexAction(){
let model = this.model("user");
//设置要查询的字符串,数组方式
let data = await model.field(["name","title"]).select();
}
}
~~~
#### model.fieldReverse(field)
* `field` {String | Array} 反选字段,即查询的时候不包含这些字段
* `return` {this}
设置反选字段,查询的时候会过滤这些字段,支持字符串和数组 2 种方式。
#### model.table(table, hasPrefix)
* `table` {String} 表名
* `hasPrefix` {Boolean} 是否已经有了表前缀,如果 table 值含有空格,则不在添加表前缀
* `return` {this}
设置表名,可以将一个 SQL 语句设置为表名。
##### 设置当前表名
~~~
export default class extends think.model.base {
getList(){
return this.table("test", true).select();
}
}
~~~
##### SQL 语句作为表名
~~~
export default class extends think.model.base {
async getList(){
let sql = await this.model("group").group("name").buildSql();
let data = await this.table(sql).select();
return data;
}
}
~~~
#### model.union(union, all)
* `union` {String | Object} 联合查询 SQL 或者表名
* `all` {Boolean} 是否是 UNION ALL 方式
* `return` {this}
联合查询。
##### SQL 联合查询
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` UNION (SELECT * FROM think_pic2)
return this.union("SELECT * FROM think_pic2").select();
}
}
~~~
##### 表名联合查询
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` UNION ALL (SELECT * FROM `think_pic2`)
return this.union({table: "think_pic2"}, true).select();
}
}
~~~
#### model.join(join)
* `join` {String | Object | Array} 要组合的查询语句,默认为 `LEFT JOIN`
* `return` {this}
组合查询,支持字符串、数组和对象等多种方式。
##### 字符串
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` LEFT JOIN think_cate ON think_group.cate_id=think_cate.id
return this.join("think_cate ON think_group.cate_id=think_cate.id").select();
}
}
~~~
##### 数组
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` LEFT JOIN think_cate ON think_group.cate_id=think_cate.id RIGHT JOIN think_tag ON think_group.tag_id=think_tag.id
return this.join([
"think_cate ON think_group.cate_id=think_cate.id",
"RIGHT JOIN think_tag ON think_group.tag_id=think_tag.id"
]).select();
}
}
~~~
##### 对象:单个表
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` INNER JOIN `think_cate` AS c ON think_user.`cate_id`=c.`id`
return this.join({
table: "cate",
join: "inner", //join 方式,有 left, right, inner 3 种方式
as: "c", // 表别名
on: ["cate_id", "id"] //ON 条件
}).select();
}
}
~~~
##### 对象:多次 JOIN
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM think_user AS a LEFT JOIN `think_cate` AS c ON a.`cate_id`=c.`id` LEFT JOIN `think_group_tag` AS d ON a.`id`=d.`group_id`
return this.alias("a").join({
table: "cate",
join: "left",
as: "c",
on: ["cate_id", "id"]
}).join({
table: "group_tag",
join: "left",
as: "d",
on: ["id", "group_id"]
}).select()
}
}
~~~
##### 对象:多个表
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` LEFT JOIN `think_cate` ON think_user.`id`=think_cate.`id` LEFT JOIN `think_group_tag` ON think_user.`id`=think_group_tag.`group_id`
return this.join({
cate: {
on: ["id", "id"]
},
group_tag: {
on: ["id", "group_id"]
}
}).select();
}
}
~~~
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM think_user AS a LEFT JOIN `think_cate` AS c ON a.`id`=c.`id` LEFT JOIN `think_group_tag` AS d ON a.`id`=d.`group_id`
return this.alias("a").join({
cate: {
join: "left", // 有 left,right,inner 3 个值
as: "c",
on: ["id", "id"]
},
group_tag: {
join: "left",
as: "d",
on: ["id", "group_id"]
}
}).select()
}
}
~~~
##### 对象:ON 条件含有多个字段
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` LEFT JOIN `think_cate` ON think_user.`id`=think_cate.`id` LEFT JOIN `think_group_tag` ON think_user.`id`=think_group_tag.`group_id` LEFT JOIN `think_tag` ON (think_user.`id`=think_tag.`id` AND think_user.`title`=think_tag.`name`)
return this.join({
cate: {on: "id, id"},
group_tag: {on: ["id", "group_id"]},
tag: {
on: { // 多个字段的 ON
id: "id",
title: "name"
}
}
}).select()
}
}
~~~
##### 对象:table 值为 SQL 语句
~~~
export default class extends think.model.base {
async getList(){
let sql = await this.model("group").buildSql();
//SELECT * FROM `think_user` LEFT JOIN ( SELECT * FROM `think_group` ) ON think_user.`gid`=( SELECT * FROM `think_group` ).`id`
return this.join({
table: sql,
on: ["gid", "id"]
}).select();
}
}
~~~
#### model.order(order)
* `order` {String | Array | Object} 排序方式
* `return` {this}
设置排序方式。
##### 字符串
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` ORDER BY id DESC, name ASC
return this.order("id DESC, name ASC").select();
}
getList1(){
//SELECT * FROM `think_user` ORDER BY count(num) DESC
return this.order("count(num) DESC").select();
}
}
~~~
##### 数组
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` ORDER BY id DESC,name ASC
return this.order(["id DESC", "name ASC"]).select();
}
}
~~~
##### 对象
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` ORDER BY `id` DESC,`name` ASC
return this.order({
id: "DESC",
name: "ASC"
}).select();
}
}
~~~
#### model.alias(tableAlias)
* `tableAlias` {String} 表别名
* `return` {this}
设置表别名。
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM think_user AS a;
return this.alias("a").select();
}
}
~~~
#### model.having(having)
* `having` {String} having 查询的字符串
* `return` {this}
设置 having 查询。
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` HAVING view_nums > 1000 AND view_nums < 2000
return this.having("view_nums > 1000 AND view_nums < 2000").select();
}
}
~~~
#### model.group(group)
* `group` {String} 分组查询的字段
* `return` {this}
设定分组查询。
~~~
export default class extends think.model.base {
getList(){
//SELECT * FROM `think_user` GROUP BY `name`
return this.group("name").select();
}
}
~~~
#### model.distinct(distinct)
* `distinct` {String} 去重的字段
* `return` {this}
去重查询。
~~~
export default class extends think.model.base {
getList(){
//SELECT DISTINCT `name` FROM `think_user`
return this.distinct("name").select();
}
}
~~~
#### model.explain(explain)
* `explain` {Boolean} 是否添加 explain 执行
* `return` {this}
是否在 SQL 之前添加 explain 执行,用来查看 SQL 的性能。
#### model.optionsFilter(options)
操作选项过滤。
#### model.dataFilter(data)
* `data` {Object | Array} 要操作的数据
数据过滤。
#### model.beforeAdd(data)
* `data` {Object} 要添加的数据
添加前置操作。
#### model.afterAdd(data)
* `data` {Object} 要添加的数据
添加后置操作。
#### model.afterDelete(data)
删除后置操作。
#### model.beforeUpdate(data)
* `data` {Object} 要更新的数据
更新前置操作。
#### model.afterUpdate(data)
* `data` {Object} 要更新的数据
更新后置操作。
#### model.afterFind(data)
* `data` {Object} 查询的单条数据
* `return` {Object | Promise}
`find` 查询后置操作。
#### model.afterSelect(data)
* `data` [Array] 查询的数据数据
* `return` {Array | Promise}
`select` 查询后置操作。
#### model.data(data)
* `data` {Object}
添加和更新操作时设置操作的数据。
#### model.options(options)
* `options` {Object}
设置操作选项。如:
~~~
export default class extends think.model.base {
getList(){
return this.options({
where: "id = 1",
limit: [10, 1]
}).select();
}
}
~~~
#### model.close()
关于数据库连接,一般情况下不要直接调用。
#### model.getTableFields(table)
* `table` {String} 表名
* `return` {Promise}
获取表的字段信息,自动从数据库中读取。
#### model.getLastSql()
* `return` {String}
获取最后执行的 SQL 语句。
#### model.buildSql()
* `return` {Promise}
将当前的查询条件生成一个 SQL 语句。
#### model.parseOptions(oriOpts, extraOptions)
* `oriOpts` {Object}
* `extraOptions` {Object}
* `return` {Promise}
根据已经设定的一些条件解析当前的操作选项。
#### model.getPk()
* `return` {Promise}
返回 `pk` 的值,返回一个 Promise。
#### model.parseType(field, value)
* `field` {String} 数据表中的字段名称
* `value` {Mixed}
* `return` {Mixed}
根据数据表中的字段类型解析 value。
#### model.parseData(data)
* `data` {Object} 要解析的数据
* `return` {Object}
调用 `parseType` 方法解析数据。
#### model.add(data, options, replace)
* `data` {Object} 要添加的数据
* `options` {Object} 操作选项
* `replace` {Boolean} 是否是替换操作
* `return` {Promise} 返回插入的 ID
添加一条数据。
#### model.thenAdd(data, where)
* `data` {Object} 要添加的数据
* `where` {Object} where 条件
* `return` {Promise}
当 where 条件未命中到任何数据时才添加数据。
#### model.addMany(dataList, options, replace)
* `dataList` {Array} 要添加的数据列表
* `options` {Object} 操作选项
* `replace` {Boolean} 是否是替换操作
* `return` {Promise} 返回插入的 ID
一次添加多条数据。
#### model.delete(options)
* `options` {Object} 操作选项
* `return` {Promise} 返回影响的行数
删除数据。
#### model.update(data, options)
* `data` {Object} 要更新的数据
* `options` {Object} 操作选项
* `return` {Promise} 返回影响的行数
更新数据。
#### updateMany(dataList, options)
* `dataList` {Array} 要更新的数据列表
* `options` {Object} 操作选项
* `return` {Promise}
更新多条数据,dataList 里必须包含主键的值,会自动设置为更新条件。
#### model.increment(field, step)
* `field` {String} 字段名
* `step` {Number} 增加的值,默认为 1
* `return` {Promise}
字段值增加。
#### model.decrement(field, step)
* `field` {String} 字段名
* `step` {Number} 增加的值,默认为 1
* `return` {Promise}
字段值减少。
#### model.find(options)
* `options` {Object} 操作选项
* `return` {Promise} 返回单条数据
查询单条数据,返回的数据类型为对象。如果未查询到相关数据,返回值为 `{}`。
#### model.select(options)
* `options` {Object} 操作选项
* `return` {Promise} 返回多条数据
查询单条数据,返回的数据类型为数组。如果未查询到相关数据,返回值为 `[]`。
#### model.countSelect(options, pageFlag)
* `options` {Object} 操作选项
* `pageFlag` {Boolean} 当页数不合法时处理,true 为修正到第一页,false 为修正到最后一页,默认不修正
* `return` {Promise}
分页查询,一般需要结合 `page` 方法一起使用。如:
~~~
export default class extends think.controller.base {
async listAction(){
let model = this.model("user");
let data = await model.page(this.get("page")).countSelect();
}
}
~~~
返回值数据结构如下:
~~~
{
numsPerPage: 10, //每页显示的条数
currentPage: 1, //当前页
count: 100, //总条数
totalPages: 10, //总页数
data: [{ //当前页下的数据列表
name: "thinkjs",
email: "admin@thinkjs.org"
}, ...]
}
~~~
#### model.getField(field, one)
* `field` {String} 字段名,多个字段用逗号隔开
* `one` {Boolean | Number} 获取的条数
* `return` {Promise}
获取特定字段的值。
#### model.count(field)
* `field` {String} 字段名
* `return` {Promise} 返回总条数
获取总条数。
#### model.sum(field)
* `field` {String} 字段名
* `return` {Promise}
对字段值进行求和。
#### model.min(field)
* `field` {String} 字段名
* `return` {Promise}
求字段的最小值。
#### model.max(field)
* `field` {String} 字段名
* `return` {Promise}
求字段的最大值。
#### model.avg(field)
* `field` {String} 字段名
* `return` {Promise}
求字段的平均值。
#### model.query(...args)
* `return` {Promise}
指定 SQL 语句执行查询。
#### model.execute(...args)
* `return` {Promise}
执行 SQL 语句。
#### model.parseSql(sql, ...args)
* `sql` {String} 要解析的 SQL 语句
* `return` {String}
解析 SQL 语句,调用 `util.format` 方法解析 SQL 语句,并将 SQL 语句中的 `__TABLENAME__` 解析为对应的表名。
~~~
export default class extends think.model.base {
getSql(){
let sql = "SELECT * FROM __GROUP__ WHERE id=%d";
sql = this.parseSql(sql, 10);
//sql is SELECT * FROM think_group WHERE id=10
}
}
~~~
#### model.startTrans()
* `return` {Promise}
开启事务。
#### model.commit()
* `return` {Promise}
提交事务。
#### model.rollback()
* `return` {Promise}
回滚事务。
#### model.transaction(fn)
* `fn` {Function} 要执行的函数
* `return` {Promise}
使用事务来执行传递的函数,函数要返回 Promise。
~~~
export default class extends think.model.base {
updateData(data){
return this.transaction(async () => {
let insertId = await this.add(data);
let result = await this.model("user_cate").add({user_id: insertId, cate_id: 100});
return result;
})
}
}
~~~
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_model.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_model.md)
rest controller
最后更新于:2022-04-01 04:35:03
[TOC=2,4]
`think.controller.rest` 继承自 [think.controller.base](https://thinkjs.org/zh-CN/doc/2.0/api_controller.html),用来处理 Rest 接口。
##### 使用 ES6 的语法继承该类
~~~
export default class extends think.controller.rest {
}
~~~
##### 使用普通方式继承该类
~~~
module.exports = think.controller("rest", {
})
~~~
### 属性
#### controller._isRest
标识此 controller 对应的是 Rest 接口。如果在 `init` 方法里将该属性设置为`false`,那么该 controller 不再是一个 Rest 接口。
#### controller._method
获取 method 方式。默认从 http method 中获取,但有些客户端不支持发送 delete, put 之类的请求,所以可以设置为从 GET 里的一个参数获取。
~~~
export default class extends think.controller.rest {
init(http){
super.init(http);
//设置 _method,表示从 GET 参数获取 _method 字段的值
//如果没有取到,则从 http method 中获取
this._method = "_method";
}
}
~~~
#### controller.resource
当前 Rest 对应的 Resource 名称。
#### controller.id
资源 ID
#### controller.modelInstance
资源对应 model 的实例。
### 方法
#### controller.__before()
可以在魔术方法`__before`中进行字段过滤,分页,权限校验等功能。
~~~
export default class extends think.controller.rest{
__before(){
//过滤 password 字段
this.modelInstance.field("password", true);
}
}
~~~
#### controller.getAction()
获取资源数据,如果有 id,拉取一条,否则拉取列表。
~~~
//方法实现,可以根据需要修改
export default class extends think.controller.rest {
* getAction(){
let data;
if (this.id) {
let pk = yield this.modelInstance.getPk();
data = yield this.modelInstance.where({[pk]: this.id}).find();
return this.success(data);
}
data = yield this.modelInstance.select();
return this.success(data);
}
}
~~~
#### controller.postAction()
添加数据
~~~
//方法实现,可以根据需要修改
export default class extends think.controller.rest {
* postAction(){
let pk = yield this.modelInstance.getPk();
let data = this.post();
delete data[pk];
if(think.isEmpty(data)){
return this.fail("data is empty");
}
let insertId = yield this.modelInstance.add(data);
return this.success({id: insertId});
}
}
~~~
#### controller.deleteAction()
删除数据
~~~
//方法实现,可以根据需要修改
export default class extends think.controller.rest {
* deleteAction(){
if (!this.id) {
return this.fail("params error");
}
let pk = yield this.modelInstance.getPk();
let rows = yield this.modelInstance.where({[pk]: this.id}).delete();
return this.success({affectedRows: rows});
}
}
~~~
#### controller.putAction()
更新数据
~~~
//方法实现,可以根据需要修改
export default class extends think.controller.rest {
* putAction(){
if (!this.id) {
return this.fail("params error");
}
let pk = yield this.modelInstance.getPk();
let data = this.post();
delete data[pk];
if (think.isEmpty(data)) {
return this.fail("data is empty");
}
let rows = yield this.modelInstance.where({[pk]: this.id}).update(data);
return this.success({affectedRows: rows});
}
}
~~~
#### controller.__call()
找不到方法时调用
~~~
export default class extends think.controller.rest {
__call(){
return this.fail(think.locale("ACTION_INVALID", this.http.action, this.http.url));
}
}
~~~
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_controller_rest.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_controller_rest.md)
controller
最后更新于:2022-04-01 04:35:01
[TOC=2,4]
`think.controller.base` 继承自 [think.http.base](https://thinkjs.org/zh-CN/doc/2.0/api_think_http_base.html) 类。项目里的控制器需要继承该类。
##### 使用 ES6 的语法继承该类
~~~
export default class extends think.controller.base {
indexAction(){
}
}
~~~
##### 使用普通方式继承该类
~~~
module.exports = think.controller({
indexAction(){
}
})
~~~
### 属性
#### controller.http
传递进来的 [http](https://thinkjs.org/zh-CN/doc/2.0/api_http.html) 对象。
### 方法
#### controller.ip()
* `return` {String}
获取当前请求用户的 ip,等同与 http.ip 方法。
~~~
export default class extends think.controller.base {
indexAction(){
let ip = this.ip();
}
}
~~~
#### controller.method()
* `return` {String}
获取当前请求的类型,转化为小写。
~~~
export default class extends think.controller.base {
indexAction(){
let method = this.method(); //get or post ...
}
}
~~~
#### controller.isMethod(method)
* `method` {String} 类型
* `return` {Boolean}
判断当前的请求类型是否是指定的类型。
#### controller.isGet()
* `return` {Boolean}
判断是否是 GET 请求。
#### controller.isPost()
* `return` {Boolean}
判断是否是 POST 请求。
#### controller.isAjax(method)
* `method` {String}
* `return` {Boolean}
判断是否是 Ajax 请求。如果指定了 method,那么请求类型也要相同。
~~~
export default class extends think.controller.base {
indexAction(){
//是ajax 且请求类型是 POST
let isAjax = this.isAjax("post");
}
}
~~~
#### controller.isWebSocket()
* `return` {Boolean}
是否是 websocket 请求。
#### controller.isCli()
* `return` {Boolean}
是否是命令行下调用。
#### controller.isJsonp(callback)
* `callback` {String} callback 名称
* `return` {Boolean}
是否是 jsonp 请求。
#### controller.get(name)
* `name` {String} 参数名
获取 GET 参数值。
~~~
export default class extends think.controller.base {
indexAction(){
//获取一个参数值
let value = this.get("xxx");
//获取所有的参数值
let values = this.get();
}
}
~~~
#### controller.post(name)
* `name` {String} 参数名
获取 POST 提交的参数。
~~~
export default class extends think.controller.base {
indexAction(){
//获取一个参数值
let value = this.post("xxx");
//获取所有的 POST 参数值
let values = this.post();
}
}
~~~
#### controller.param(name)
* `name` {String} 参数名
获取参数值,优先从 POST 里获取,如果取不到再从 GET 里获取。
#### controller.file(name)
* `name` {String} 上传文件对应的字段名
获取上传的文件,返回值是个对象,包含下面的属性:
~~~
{
fieldName: "file", //表单字段名称
originalFilename: filename, //原始的文件名
path: filepath, //文件保存的临时路径,使用时需要将其移动到项目里的目录,否则请求结束时会被删除
size: 1000 //文件大小
}
~~~
如果文件不存在,那么值为一个空对象 `{}`。
#### controller.header(name, value)
* `name` {String} header 名
* `value` {String} header 值
获取或者设置 header。
~~~
export default class extends think.controller.base {
indexAction(){
let accept = this.header("accept"); //获取 header
this.header("X-NAME", "thinks"); //设置 header
}
}
~~~
#### controller.expires(time)
* `time` {Number} 过期时间,单位为秒
强缓存,设置 `Cache-Control` 和 `Expires` 头信息。
~~~
export default class extends think.controller.base {
indexAction(){
this.expires(86400); //设置过期时间为 1 天。
}
}
~~~
#### controller.userAgent()
获取 userAgent。
#### controller.referrer(onlyHost)
* `referrer` {Boolean} 是否只需要 host
获取 referrer。
#### controller.cookie(name, value, options)
* `name` {String} cookie 名
* `value` {String} cookie 值
* `options` {Object}
获取或者设置 cookie。
~~~
export default class extends think.controller.base {
indexAction(){
//获取 cookie 值
let value = this.cookie("think_name");
}
}
~~~
~~~
export default class extends think.controller.base {
indexAction(){
//设置 cookie 值
this.cookie("think_name", value, {
timeout: 3600 * 24 * 7 //有效期为一周
});
}
}
~~~
#### controller.session(name, value)
* `name` {String} session 名
* `value` {Mixed} session 值
* `return` {Promise}
读取、设置和清除 session。
##### 读取 Session
~~~
export default class extends think.controller.base {
* indexAction(){
//获取session
let value = yield this.session("userInfo");
}
}
~~~
##### 设置 Session
~~~
export default class extends think.controller.base {
* indexAction(){
//设置 session
yield this.session("userInfo", data);
}
}
~~~
##### 清除 Session
~~~
export default class extends think.controller.base {
* indexAction(){
//清除当前用户的 session
yield this.session();
}
}
~~~
#### controller.lang(lang, asViewPath)
* `lang` {String} 要设置的语言
* `asViewPath` {Boolean} 是否在模版目录添加一层语言目录
读取或者设置语言。
#### controller.locale(key)
* `key` {String}
根据 language 获取对应的语言文本。
#### controller.redirect(url, statusCode)
* `url` {String} 要跳转的 url
* `statusCode` {Number} 状态码,默认为 302
页面跳转。
#### controller.assign(name, value)
* `name` {String | Object} 变量名
* `value` {Mixed} 变量值
将变量赋值到模版中。
~~~
export default class extends think.controller.base {
indexAction(){
//单个赋值
this.assign("title", "thinkjs");
//批量赋值
this.assign({
name: "xxx",
desc: "yyy"
})
}
}
~~~
#### controller.fetch(templateFile)
* `templateFile` {String} 模版文件地址
* `return` {Promise}
获取解析后的模版内容。
##### 直接获取
~~~
// 假设文件路径为 /foo/bar/app/home/controller/index.js
export default class extends think.controller.base {
* indexAction(){
// home/index_index.html
let content = yield this.fetch();
}
}
~~~
##### 改变 action
~~~
// 假设文件路径为 /foo/bar/app/home/controller/index.js
export default class extends think.controller.base {
* indexAction(){
// home/index_detail.html
let content = yield this.fetch("detail");
}
}
~~~
##### 改变 controller 和 action
~~~
// 假设文件路径为 /foo/bar/app/home/controller/index.js
export default class extends think.controller.base {
* indexAction(){
// home/user_detail.html
let content = yield this.fetch("user/detail");
}
}
~~~
##### 改变 module, controller 和 action
~~~
// 假设文件路径为 /foo/bar/app/home/controller/index.js
export default class extends think.controller.base {
* indexAction(){
// admin/user_detail.html
let content = yield this.fetch("admin/user/detail");
}
}
~~~
##### 改变文件后缀名
~~~
// 假设文件路径为 /foo/bar/app/home/controller/index.js
export default class extends think.controller.base {
* indexAction(){
// home/index_detail.xml
let content = yield this.fetch("detail.xml");
}
}
~~~
##### 获取绝对路径文件
~~~
// 假设文件路径为 /foo/bar/app/home/controller/index.js
export default class extends think.controller.base {
* indexAction(){
// /home/xxx/aaa/bbb/c.html
let content = yield this.fetch("/home/xxx/aaa/bbb/c.html");
}
}
~~~
#### controller.display(templateFile)
* `templateFile` {String} 模版文件路径
输出模版内容到浏览器端。查找模版文件策略和`controller.fetch`相同。
#### controller.jsonp(data)
* `data` {Mixed} 要输出的内容
jsonp 的方法输出内容,获取 callback 名称安全过滤后输出。
~~~
export default class extends think.controller.base {
indexAction(){
this.jsonp({name: "thinkjs"});
//writes
"callback_fn_name({name: "thinkjs"})"
}
}
~~~
#### controller.json(data)
* `data` {Mixed} 要输出的内容
json 的方式输出内容。
#### controller.status(status)
* `status` {Number} 状态码,默认为 404
设置状态码。
#### controller.deny(status)
* `status` {String} 状态码,默认为 403
拒绝当前请求。
#### controller.write(data, encoding)
* `data` {mixed} 要输出的内容
* `encoding` {String} 编码
输出内容
#### controller.end(data, encoding)
* `data` {mixed} 要输出的内容
* `encoding` {String} 编码
输出内容后结束当前请求。
#### controller.type(type, charset)
* `type` {String} Content-Type
* `charset` {Boolean} 是否自动追加 charset
设置 Content-Type。
#### controller.download(filePath, contentType, fileName)
* `filePath` {String} 下载文件的具体路径
* `content-Type` {String} Content-Type
* `fileName` {String} 报错的文件名
下载文件。
~~~
export default class extends think.controller.base {
indexAction(){
let filePath = think.RESOUCE_PATH + "/a.txt";
//自动识别 Content-Type,保存的文件名为 a.txt
this.download(filePath);
}
}
~~~
~~~
export default class extends think.controller.base {
indexAction(){
let filePath = think.RESOUCE_PATH + "/a.log";
//自动识别 Content-Type,保存的文件名为 b.txt
this.download(filePath, "b.txt");
}
}
~~~
~~~
export default class extends think.controller.base {
indexAction(){
let filePath = think.RESOUCE_PATH + "/a.log";
//指定 Content-Type 为 text/html,保存的文件名为 b.txt
this.download(filePath, "text/html", "b.txt");
}
}
~~~
#### controller.success(data, message)
* `data` {Mixed} 要输出的数据
* `message` {String} 追加的message
格式化输出一个正常的数据,一般是操作成功后输出。
~~~
http.success({name: "thinkjs"});
//writes
{
errno: 0,
errmsg: "",
data: {
name: "thinkjs"
}
}
~~~
这样客户端就可以根据`errno`是否为`0`为判断当前请求是否正常。
#### controller.fail(errno, errmsg, data)
* `errno` {Number} 错误号
* `errmsg` {String} 错误信息
* `data` {Mixed} 额外的数据
格式化输出一个异常的数据,一般是操作失败后输出。
`注`:字段名`errno`和`errmsg`可以在配置里进行修改。
~~~
http.fail(100, "fail")
//writes
{
errno: 100,
errmsg: "fail",
data: ""
}
~~~
这样客户端就可以拿到具体的错误号和错误信息,然后根据需要显示了。
`注`:字段名`errno`和`errmsg`可以在配置里进行修改。
#### controller.sendTime(name)
* `name` {String} header key
发送请求的执行时间,使用 header 的方式发出。
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_controller.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_controller.md)
http
最后更新于:2022-04-01 04:34:58
[TOC=2,4]
这里的 http 对象并不是 Node.js 里的 http 模块,而是对 request 和 response 对象包装后一个新的对象。
~~~
var http = require("http");
http.createServer(function (request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello World\n");
}).listen(8124);
~~~
如上面的代码所示,Node.js 创建服务时,会传递 request 和 response 2个对象给回调函数。为了后续调用方便, ThinkJS 对这2个对象进行了包装,包装成了 http 对象,并且提供很多有用的方法。
http 对象会在 middleware, logic, controller, view 中传递。
`注`:http 对象是 EventEmitter 的一个实例,所以可以对其进行事件监听和执行。
### 属性
#### http.req
系统原生的 request 对象
#### http.res
系统原生的 response 对象
#### http.startTime
请求的开始时间,是个`unix`时间戳。
#### http.url
当前请求的 url 。
#### http.version
当前请求的 http 版本。
#### http.method
当前请求的类型。
#### http.headers
当前请求的所有头信息。
#### http.pathname
当前请求的 pathname,路由识别会依赖该值,会在后续的处理中对其进行改变。所以在 action 拿到值可能跟初始解析出来的值不一致。
#### http.query
当前请求的所有 query 数据。
#### http.host
当前请求的 host, 包含端口。
#### http.hostname
当前请求的 hostname,不包含端口。
#### http.payload
当前请求的 payload 数据,提交型的请求才含有该值。
#### http._payloadParsed
表示当前请求的 payload 数据是否已经解析。
#### http._get
存放 GET 参数值。
#### http._post
存放 POST 参数值
#### http._file
存放上传的文件数据
#### http._cookie
存放 cookie 数据。
### 方法
#### http.config(name)
* `name` {String} 参数名
* `return` {Mixed} 返回对应的参数值
获取当前请求下对应的参数值。
#### http.referrer()
* `return` {String} 请求的 referrer
返回当前请求的 referrer。
#### http.userAgent()
* `return` {String} 请求的 userAgent
返回当前请求的 userAgent。
#### http.isGet()
* `return` {Boolean}
返回当前请求是否是 GET 请求。
#### http.isPost()
* `return` {Boolean}
返回当前请求是否是 POST 请求。
#### http.isAjax(method)
* `method` {String} 请求类型
* `return` {Boolean}
返回当前请求是否是 Ajax 请求。
~~~
http.isAjax(); //判断是否是Ajax请求
http.isAjax("GET"); //判断是否是Ajax请求,且请求类型是GET
~~~
#### http.isJsonp(name)
* `name` {String} callback 参数名称,默认为 callback
* `return` {Boolean}
返回当前请求是否是 jsonp 请求。
~~~
//url is /index/test?callback=testxxx
http.isJsonp(); //true
http.isJsonp("cb"); //false
~~~
#### http.get(name, value)
* `name` {String} 参数名称
* `value` {Mixed} 参数值
获取或者设置 GET 参数值。可以通过该方法设置 GET 参数值,方便后续的逻辑里获取。
~~~
// url is /index/test?name=thinkjs
http.get("name"); // returns "thinkjs"
http.get("name", "other value");
http.get("name"); // returns "other value"
~~~
#### http.post(name, value)
* `name` {String} 参数名称
* `value` {Mixed} 参数值
获取或者设置 POST 值。可以通过该方法设置 POST 值,方便后续的逻辑里获取。
~~~
http.post("email"); //获取提交的email
~~~
#### http.param(name)
* `name` {String} 参数名称
* `return` {Mixed}
获取参数值,优先从 POST 里获取,如果值为空,则从 URL 参数里获取。
#### http.file(name)
* `name` {String} 文件对应的字段名称
* `return` {Object}
获取上传的文件。
~~~
http.file("image");
//returns
{
fieldName: "image", //表单里的字段名
originalFilename: filename, //原始文件名
path: filepath, //文件临时存放的路径
size: size //文件大小
}
~~~
#### http.header(name, value)
* `name` {String} header 名称
* `value` {String} header 值
获取或者设置 header 信息。
~~~
http.header("accept"); //获取accept
http.header("X-NAME", "thinkjs"); //设置header
~~~
#### http.expires(time)
* `time` {Number} 过期时间,单位为秒
强缓存,设置 `Cache-Control` 和 `Expires` 头信息。
~~~
http.header(86400); //设置过期时间为 1 天。
~~~
#### http.status(status)
设置状态码。如果头信息已经发送,则无法设置状态码。
~~~
http.status(400); //设置状态码为400
~~~
#### http.ip()
获取用户的 ip 。如果使用了代理,获取的值可能不准。
#### http.lang(lang, asViewPath)
* `lang` {String} 要设置的语言
* `asViewPath` {Boolean} 是否添加一层模版语言目录
获取或者设置国际化的语言,可以支持模版路径要多一层语言的目录。
##### 获取语言
~~~
let lang = http.lang();
~~~
获取语言的循序为 `http._lang` -> `从 cookie 中获取` -> `从 header 中获取`,如果需要从 url 中解析语言,可以获取后通过 `http.lang(lang)` 方法设置到属性 `http._lang` 中。
##### 设置语言
~~~
let lang = getFromUrl();
http.lang(lang, true); //设置语言,并指定模版路径中添加一层语言目录
~~~
#### http.theme(theme)
获取或者设置主题,设置后模版路径要多一层主题的目录。
#### http.cookie(name, value)
* `name` {String} cookie 名称
* `value` {String} cookie 值
读取或者设置 cookie 值。
~~~
http.cookie("think_test"); //获取名为 think_test 的 cookie
http.cookie("name", "value"); //设置 cookie,如果头信息已经发送则设置无效
~~~
#### http.session(name, value)
* `name` {String} session 名
* `value` {Mixed} session 值
* `return` {Promise}
读取、设置和清除 session。
##### 读取 Session
~~~
let value = yield http.session("userInfo");
~~~
##### 设置 Session
~~~
yield http.session("userInfo", data);
~~~
##### 清除 Session
~~~
yield http.session();
~~~
#### http.redirect(url, status)
* `url` {String} 要跳转的 url
* `status` {Number} 状态码, 301 或者 302,默认为302
页面跳转。
~~~
http.redirect("/login"); //跳转到登录页面
~~~
#### http.type(contentType, encoding)
* `contentType` {String} 要设置的 contentType
* `encoding` {String} 要设置的编码
获取或者设置 Content-Type。
~~~
http.type(); //获取Content-Type
http.type("text/html"); //设置Content-Type,会自动加上charset
http.type("audio/mpeg", false); //设置Content-Type,不追加charset
~~~
#### http.write(content, encoding)
* `content` {Mixed} 要输出的内容
* `encoding` {String} 编码
输出内容,要调用 http.end 才能结束当前请求。
#### http.end(content, encoding)
* `content` {Mixed} 要输出的内容
* `encoding` {String} 编码
输出内容并结束当前请求。
#### http.success(data, message)
* `data` {Mixed} 要输出的数据
* `message` {String} 追加的message
格式化输出一个正常的数据,一般是操作成功后输出。
~~~
http.success({name: "thinkjs"});
//writes
{
errno: 0,
errmsg: "",
data: {
name: "thinkjs"
}
}
~~~
这样客户端就可以根据`errno`是否为`0`为判断当前请求是否正常。
#### http.fail(errno, errmsg, data)
* `errno` {Number} 错误号
* `errmsg` {String} 错误信息
* `data` {Mixed} 额外的数据
格式化输出一个异常的数据,一般是操作失败后输出。
`注`:字段名`errno`和`errmsg`可以在配置里进行修改。
~~~
http.fail(100, "fail")
//writes
{
errno: 100,
errmsg: "fail",
data: ""
}
~~~
这样客户端就可以拿到具体的错误号和错误信息,然后根据需要显示了。
`注`:字段名`errno`和`errmsg`可以在配置里进行修改。
#### http.json(data)
* `data` {Object}
json 方式输出数据,会设置 Content-Type 为 `application/json`,该值对应的配置为`json_content_type`。
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_http.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_http.md)
think.http.base
最后更新于:2022-04-01 04:34:56
[TOC=2,4]
`think.http.base`继承自 [think.base](https://thinkjs.org/zh-CN/doc/2.0/api_think_base.html) 类,该类为含有 http 对象处理时的基类。middleware, controller, view 类都继承自该类。
##### 使用 ES6 语法继承该类
~~~
export default class extends think.http.base {
/**
* 初始化方法,实例化时自动被调用,不要写 constructor
* @return {}
*/
init(){
}
}
~~~
##### 使用普通的方式继承该类
~~~
module.exports = think.Class(think.http.base, {
init: function(){
}
});
~~~
### 属性
#### http
封装的 http 对象,包含的属性和方法请见 [API -> http](https://thinkjs.org/zh-CN/doc/2.0/api_http.html)。
### 方法
#### config(name, value)
* `name` {String} 配置名称
* `value` {Mixed} 配置值
读取或者设置配置,value 为 `undefined` 时为读取配置,否则为设置配置。
该方法不仅可以读取系统预设值的配置,也可以读取项目里定义的配置。
`注`:不可将当前请求的用户信息作为配置来设置,会被其他用户给冲掉。
~~~
export default class extends think.controller.base {
indexAction(){
//获取配置值
let value = this.config("name");
}
}
~~~
#### action(controller, action)
* `controller` {Object | String} controller实例
* `action` {String} action名称
* `return` {Promise}
调用 controller 下的 action,返回一个 Promise。自动调用`__before`和`__after`魔术方法。
如果 controller 是字符串,则自动去寻找对应的 controller。
~~~
//调用当前模块下controller里的action
export default class extends think.controller.base {
* indexAction(){
//调用user controller下的detail方法
let value = yield this.action("user", "detail");
}
}
~~~
~~~
//跨模块调用controller里的action
export default class extends think.controller.base {
* indexAction(){
//调用admin模块user controller下的detail方法
let value = yield this.action("admin/user", "detail");
}
}
~~~
#### cache(name, value, options)
* `name` {String} 缓存名称
* `value` {Mixed | Function} 缓存值
* `options` {Object} 缓存配置,具体见缓存配置
读取或者设置缓存,`value`为`undefined`时是读取缓存,否则为设置缓存。默认缓存类型为`file`。
~~~
export default class extends think.controller.base {
* indexAction(){
//获取缓存
let value = yield this.cache("name");
}
}
~~~
当参数`value`为 function 时,表示获取缓存,如果缓存值不存在,则调用该 function,将返回值设置缓存并返回。这样避免在项目开发时要先判断缓存是否存在,然后再从相关地方读取值然后设置缓存的麻烦。
~~~
export default class extends think.controller.base {
* indexAction(){
//获取缓存,缓存不存在时自动调用 function,并设置缓存
let value = yield this.cache("name", () => {
return this.model("user").select();
});
}
}
~~~
设置缓存并修改缓存类型:
~~~
export default class extends think.controller.base {
* indexAction(){
//设置缓存,缓存类型使用redis
yield this.cache("name", "value", {
type: "redis"
});
}
}
~~~
#### hook(event, data)
* `event` {String} 事件名称
* `data` {Mixed} 参数
* `return` {Promise}
执行对应的事件,一个事件包含若干个 middleware,会按顺序执行这些 middleware。
事件可以在配置`config/hook.js`里定义,也可以通过 think.hook 来注册。
~~~
export default class extends think.controller.base {
* indexAction(){
let result = yield this.hook("parse_data");
}
}
~~~
#### model(name, options)
* `name` {String} 模型名称
* `options` {Object} 配置,具体见数据库配置
* `return` {Object} model实例
获取模型实例,默认获取当前模块下对应模型的实例,也可以跨模块获取模型的实例。
~~~
export default class extends think.controller.base {
indexAction(){
//获取当前模块下的 user model 的实例
let model = this.model("user");
//获取admin模块下 article model 的实例
let model1 = this.model("admin/article");
//获取当前模块下的 test model 的实例,并且是 sqlite 的数据库
let model2 = this.model("test", {
type: "sqlite" //设置数据库类型为sqlite,更多参数见数据库配置
})
}
}
~~~
#### controller(name)
* `name` {String} controller名称
* `return` {Object} controller实例
获取 Controller 的实例,如果 Controller 找不到,则报错。
~~~
export default class extends think.controller.base {
indexAction(){
//获取当前模块下 user controller 的实例
let controller = this.controller("user");
//获取admin模块下 user controller 的实例
let controller1 = this.controller("admin/user");
}
}
~~~
#### service(name)
* `name` {String} service 名称
* `return` {Class}
获取对应的 service。service 返回的可能是 class ,也可能直接是个对象,所以不会直接实例化。
~~~
export default class extends think.controller.base {
indexAction(){
//获取对应的service
let service = this.service("user");
//获取service的实例
let instance = new service(...args);
//获取admin模块下的user service
let service1 = this.service("admin/user");
}
}
~~~
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_think_http_base.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_think_http_base.md)
think.base
最后更新于:2022-04-01 04:34:54
[TOC=2,4]
`think.base`是基类,所有的类都会继承该类,该类提供了一些基本的方法。
使用 ES6 语法继承该类:
~~~
export default class extends think.base {
/**
* init method
* @return {} []
*/
init(){
}
}
~~~
`注`: 使用 ES6 里的类时不要写`constructor`,把初始化的一些操作放在`init`方法里,该方法在类实例化时自动被调用,效果等同于`constructor`。
使用普通的方式继承该类:
~~~
module.exports = think.Class(think.base, {
/**
* init method
* @return {} []
*/
init: function(){
}
})
~~~
### init(...args)
* `args` {Array}
初始化方法,这里可以进行一些赋值等操作。
~~~
class a extends think.base {
init(name, value){
this.name = name;
this.value = value;
}
}
~~~
`注`:与`1.x`版本不同的是,`2.x`版本`init`方法不再支持返回一个`Promise`,一些通用操作放在`__before`魔术方法里进行。
### __before()
前置魔术方法,可以将一些通用的行为放在这里进行,如:controller 里检测用户是否登录
~~~
export default class think.controller.base {
/**
* 前置魔术方法
* @return {Promise} []
*/
* __before(){
let userInfo = yield this.session("userInfo");
//如果没有登录,则跳转到登录页面
if(think.isEmpty(userInfo)){
return this.redirect("/logic");
}
this.assign("userInfo", userInfo)
}
}
~~~
### __after()
后置魔术方法,在方法执行完成后在执行。
### filename()
* `return` {String} 返回当前类文件的名称
获取当前类文件的名称,不包含文件具体路径和扩展名。
~~~
//假设当前类文件具体路径为 /home/xxx/project/app/controller/user.js
class a extends think.base {
test(){
var filename = this.filename();
//returns "user"
}
}
~~~
### invoke(method, ...data)
* `method` {String} 要调用的方法名称
* `data` {Array} 传递的参数
* `return` {Promise}
调用一个方法,自动调用 `__before` 和 `__after` 魔术方法。不管方法本身是否返回 `Promise`,该方法始终返回 `Promise`。
方法本身支持是 `*/yield` 和`async/await`。
~~~
//使用 async/await
class Cls extends think.base {
async getValue(){
let value = await this.getValue();
return value;
}
}
let instance = new Cls();
instance.invoke("getValue").then(data => {
});
~~~
~~~
//使用 */yield
class Cls extends think.base {
* getValue(){
let value = yield this.getValue();
return value;
}
}
let instance = new Cls();
instance.invoke("getValue").then(data => {
});
~~~
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_think_base.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_think_base.md)
think
最后更新于:2022-04-01 04:34:51
[TOC=2,4]
`think`是一个全局对象,该对象里包含了大量有用的属性和方法。这些方法在应用的任何地方都可以直接使用,无需再 require。
### 属性
#### think.startTime
服务启动时间,是个`unix`时间戳。
#### think.env
当前项目运行的环境,默认支持下面3个值,可以在项目启动时指定:
* `development` 开发环境,会自动更新修改的文件
* `testing` 测试环境
* `production` 线上环境,代码上线时使用
#### think.dirname
项目的文件夹名称,可以在项目启动时指定,默认值如下:
~~~
think.dirname = {
config: "config", //配置文件目录
controller: "controller", //控制器目录
model: "model", //模型目录
adapter: "adapter", //适配器目录
logic: "logic", //逻辑目录
service: "service", //服务目录
view: "view", //视图目录
middleware: "middleware", //中间件目录
runtime: "runtime", //运行时目录
common: "common", //通用目录
bootstrap: "bootstrap", //启动目录
locale: "locale" //本土化目录
}
~~~
#### think.port
项目运行的端口,可以在项目启动时指定。如果指定,则忽略配置文件里的端口。
#### think.cli
是否是命令行模式在运行项目,默认为`false`。如果是命令行模式,则该值为传递的参数,可以通过下面的方式启动命令行模式。
~~~
node www/index.js /home/index/test
~~~
#### think.lang
系统当前的语言,从环境变量中读取,在`windows`下可能为空。
#### think.mode
项目当前的模式,框架支持3中项目模式:
* `think.mode_mini` 单模块模式,整个项目只有一个模块
* `think.mode_normal` 多模块模式,目录结构只有`Controller`,`View`,`Logic`等分模块
* `think.mode_module` 多模块模式,严格按照模块来划分目录结构
#### think.version
ThinkJS当前的版本
#### think.module
当前项目下的模块列表,如果项目模式是`think.mode_mini`,那么值为空数组。
#### think.THINK_PATH
ThinkJS代码的路径
#### think.THINK_LIB_PATH
ThinkJS代码`lib/`的具体路径
#### think.ROOT_PATH
项目的根目录,在`www/index.js`中定义
#### think.APP_PATH
项目的`app`目录,在`www/index.js`中定义
#### think.RESOURCE_PATH
项目的静态资源根目录,在`www/index.js`中定义
### 方法
#### think.Class(methods, clean)
动态的创建一个类,默认继承自 think.base 。 如果使用 ES6 特性进行开发的话,可以直接使用 ES6 里的 class 来创建类。
~~~
//继承自 think.base
var Cls1 = think.Class({
getName: function(){
}
})
~~~
##### 不继承 think.base
~~~
var Cls2 = think.Class({
getName: function(){
}
}, true);
~~~
##### 继承一个类
~~~
//继承自 Cls2
var Cls3 = think.Class(Cls2, {
init: function(name){
this.name = name;
},
getName: function(){
}
})
~~~
##### 实例化类
~~~
//获取类的实例,自动调用 init 方法
var instance = new Cls3("thinkjs");
~~~
#### think.extend(target, source1, source2, ...)
* `target` {Object} 目录对象
* `source1` {Mixed} 源对象1
* `return` {Object} 目录对象
将 source1, source2 等对象上的属性或方法复制到 target 对象上,类似于 jQuery 里的 $.extend 方法。
默认为深度复制,可以将第一个参数传 `false` 进行浅度复制。
~~~
think.extend({}, {name: "foo"}, {value: "bar"});
// returns
{name: "foo", value: "bar"}
~~~
#### think.isBoolean(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测一个对象是否是布尔值。
~~~
think.isBoolean(true); //true
think.isBoolean(false); //true
think.isBoolean("string"); //false
~~~
#### think.isNumber(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测一个对象是否是数字。
~~~
think.isNumber(1); //true
think.isNumber(1.21); //true
~~~
#### think.isObject(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是对象
~~~
think.isObject({}); //true
think.isObject({name: "welefen"}); //true
think.isObject(new Buffer("welefen")); //false
~~~
#### think.isString(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是字符串
~~~
think.isString("xxx"); // true
think.isString(new String("xxx")); //true
~~~
#### think.isFunction(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是函数
~~~
think.isFunction(function(){}); //true
think.isFunction(new Function("")); //true
~~~
#### think.isDate(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是日期对象
~~~
think.isDate(new Date()); //true
~~~
#### think.isRegexp(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是正则
~~~
think.isRegexp(/\w+/); //true
think.isRegexp(new RegExp("/\\w+/")); //true
~~~
#### think.isError(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是个错误
~~~
think.isError(new Error("xxx")); //true
~~~
#### think.isEmpty(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否为空
~~~
// 检测是否为空
think.isEmpty({}); //true
think.isEmpty([]); //true
think.isEmpty(""); //true
think.isEmpty(0); //true
think.isEmpty(null); //true
think.isEmpty(undefined); //true
think.isEmpty(false); //true
~~~
#### think.isArray(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是数组
~~~
think.isArray([]); //true
think.isArray([1, 2]); //true
think.isArray(new Array(10)); //true
~~~
#### think.isIP4(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是 IP4
~~~
think.isIP4("10.0.0.1"); //true
think.isIP4("192.168.1.1"); //true
~~~
#### think.isIP6(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是 IP6
~~~
think.isIP6("2031:0000:130f:0000:0000:09c0:876a:130b"); //true
think.isIP6("2031:0000:130f::09c0:876a:130b"); //true
~~~
#### think.isIP(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是 IP
~~~
think.isIP("10.0.0.1"); //true
think.isIP("192.168.1.1"); //true
think.isIP("2031:0000:130f:0000:0000:09c0:876a:130b"); //true ip6
~~~
#### think.isFile(file)
* `file` {Mixed} 要检测的文件路径
* `return` {Boolean}
检测是否是文件,如果在不存在则返回 false
~~~
think.isFile("/home/welefen/a.txt"); //true
think.isFile("/home/welefen/dirname"); //false
~~~
#### think.isDir(dir)
* `dir` {Mixed} 要检测的路径
* `return` {Boolean}
检测是否是目录,如果不存在则返回 false
~~~
think.isDir("/home/welefen/dirname"); //true
~~~
#### think.isBuffer(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是 Buffer
~~~
think.isBuffer(new Buffer(20)); //true
~~~
#### think.isNumberString(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
是否是字符串类型的数字
~~~
think.isNumberString(1); //true
think.isNumberString("1"); //true
think.isNumberString("1.23"); //true
~~~
#### think.isPromise(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是个 promise
~~~
think.isPromise(new Promise(function(){})); //true
think.isPromise(getPromise()); //true
~~~
#### think.isHttp(obj)
* `obj` {Mixed} 要检测的对象
* `return` {Boolean}
检测是否是包装的 http 对象
~~~
think.isHttp(http); // true
~~~
#### think.isWritable(path)
* `path` {String} 要写的目录
* `return` {Boolean}
判断文件或者目录是否可写,如果不存在则返回 false
#### think.isPrevent(obj)
* `obj` {Mixed}
* `return` {Boolean}
判断是否是个阻止类型的 promise。通过 think.prevent() 会生成该 promise 。
#### think.mkdir(p, mode)
* `p` {String} 要创建的目录
* `mode` {Number} 要创建的目录权限,默认为 `0777`
递归的创建目录,如果目录已经存在,那么修改目录的权限。
~~~
// 假设 /home/welefen/a/b/ 不存在
think.mkdir("/home/welefen/a/b");
think.mkdir("home/welefne/a/b/c/d/e"); // 递归创建子目录
~~~
#### think.rmdir(p, reserve)
* `p` {String} 要删除的目录
* `reserve` {Boolean} 是否保留该目录。如果为 true,则只删除子目录
* `return` {Promise}
递归的删除目录,如果目录不存在则直接返回。返回是个 Promise,后续操作要在 `then` 里执行
~~~
function rmTmp(){
think.rmdir("/foo/bar").then(function(){
//后续其他操作
})
}
~~~
如果使用 `Generator Function`,则可以使用 `yield`
~~~
function * rmTmp(){
yield think.rmdir("/foo/bar");
//后续其他操作
}
~~~
#### think.chmod(p, mode)
* `p` {String} 要修改的目录
* `mode` {Number} 目录权限,默认为`0777`
修改目录权限,如果目录不存在则直接返回
~~~
think.chmod("/home/welefen/a", 0777);
~~~
#### think.md5(str)
* `str` {String} 要计算md5值的字符串
* `return` {String} md5值
计算字符串的md5值
~~~
think.md5("thinkjs");
// returns
7821eb623e0b1138a47db6a88c3f56bc
~~~
#### think.defer()
* `return` {Object} Deferred对象
创建一个`Deferred`对象,`new Promise`的一种快捷方式。虽然不建议使用`Deferred`这种方式,但有时候不得不使用。如:`setTimeout`, `event`。
~~~
//使用Deferred的方式
var fn = function(){
var deferred = think.defer();
process.nextTick(function(){
if(xxx){
deferred.resolve(data);
}else{
deferred.reject(err);
}
})
return deferred.promise;
}
~~~
使用`Deferred`方式比直接使用`new Promise`的方法代码更加简洁。
~~~
//直接使用new Promise的方式
var fn = function(){
return new Promise(function(resolve, reject){
process.nextTick(function(){
if(xxx){
resolve(data);
}else{
reject(err);
}
})
})
}
~~~
注: 异步`callback`的操作不要使用`Deferred`方式,可以用`think.promisify`方法快速把`callback`包装成`Promise`。
#### think.promisify(fn, receiver)
* `fn` {Function} 要转化的函数
* `receiver` {Object} this指向
将异步方法快速包装成Promise,异步方法必须符合最后一个参数为回调函数,且回调函数的第一个参数为`err`的原则。
~~~
var fs = require("fs");
//获取文件内容
var getContent = function(filePath){
//将readFile方法包装成Promise
var readFilePromise = think.promisify(fs.readFile, fs);
//读取文件内容
return readFilePromise(filePath, "utf8");
}
//获取具体的文件内容
getContent("/foo/bar/file.txt").then(function(content){
console.log(content);
}).catch(function(err){
console.error(err.stack);
})
~~~
#### think.reject(err)
* `err` {Error} Error对象
* `return` {Promise} reject promise
返回一个 reject promise,与`Promise.reject`不同的是,该方法会自动打印错误信息。避免需要调用 catch 方法手工打印错误信息。
~~~
//使用Promise.reject
var fn = function(){
return Promise.reject(new Error("xxx"));
}
//需要手工调用catch方法打印错误信息
fn().catch(function(err){
console.error(err.stack);
})
~~~
~~~
//使用think.reject
var fn = function(){
return think.reject(new Error("xxx"));
}
//会自动打印格式化后的错误信息
fn();
~~~
#### think.co
`co`模块的别名 [https://github.com/tj/co](https://github.com/tj/co)
#### think.lookClass(name, type, module, base)
* `name` {String} 类名
* `type` {String} 类型 (controller | model | logic ...)
* `module` {String} 模块名
* `base` {String} 找不到时找对应的基类
根据类型,名称来查找类。如果找不到会到 common 模块下查找,如果还是找不到,则查找对应类型的基类。
~~~
//查找 home 模块下 user controller
//如果找不到,会找 common 模块下 user controller
//如果还是找不到,会找 base controller
think.lookClass("user", "controller", "home");
//查找 admin 模块下 user controller
think.lookClass("admin/user", "controller");
~~~
#### think.getPath(module, type, prefix)
* `module` {String} 模块名
* `type` {String} 类型,如: controller, model, logic
* `prefix` {String} 前缀
根据当前项目类型获取对应类型的目录。
~~~
let path = think.getPath("home", "controller");
~~~
假如当前项目的根目录是`/foo/bar`,那么获取到的目录为:
* 项目模式`think.mode_mini` 下路径为 `/foo/bar/app/controller`
* 项目模式`think.mode_normal` 下路径为 `/foo/bar/app/controller/home`
* 项目模式`think.mode_module` 下路径为 `/foo/bar/app/home/controller`
#### think.require(name, flag)
* `name` {String}
* `flag` {Boolean}
#### think.safeRequire(file)
* `file` {String} 要加载的文件
安全的加载一个文件,如果文件不存在,则返回null,并打印错误信息。
#### think.prevent()
返回一个特殊的 reject promise 。该 promise 可以阻止后续的行为且不会报错。
#### think.log(msg, type, showTime)
* `msg` {String | Error} 信息
* `type` {String} 类型
* `showTime` {Number | Boolean} 是否显示时间
打印日志,该方法打印出来的日志会有时间,类型等信息,方便查看和后续处理。
~~~
think.log("WebSocket Status: closed", "THINK");
//writes "[2015-09-23 17:43:00] [THINK] WebSocket Status: closed"
~~~
##### 打印错误信息
~~~
think.log(new Error("error"), "ERROR");
//writes "[2015-09-23 17:50:17] [Error] Error: error"
~~~
##### 显示执行时间
~~~
think.log("/static/module/jquery/1.9.1/jquery.js", "HTTP", startTime);
//writes "[2015-09-23 17:52:13] [HTTP] /static/module/jquery/1.9.1/jquery.js 10ms"
~~~
##### 不显示时间
~~~
think.log("/static/module/jquery/1.9.1/jquery.js", "HTTP", null);
//writes "[HTTP] /static/module/jquery/1.9.1/jquery.js"
~~~
##### 自定义
~~~
think.log(function(colors){
return colors.yellow("[WARNING]") + " test";
});
//writes "[WARNING] test"
~~~
其中`colors`为 npm 模块 colors,[https://github.com/Marak/colors.js](https://github.com/Marak/colors.js) 。
#### think.config(name, value, data)
* `name` {String} 配置名称
* `value` {Mixed} 配置值
* `data` {Object} 配置对象
读取或者设置配置,可以指定总的配置对象。
~~~
//获取配置
let value = think.config("name");
//获取 admin 模块下的配置
let value = think.config("name", undefined, "admin");
// 写入配置
think.config("name", "value");
~~~
#### think.getModuleConfig(module)
* `module` {String} 模块名称
* `return` {Object}
获取模块的所有配置。该配置包含模块的配置,通用模块的配置,框架默认的配置。
~~~
//获取 admin 模块的所有配置
let configs = think.getModuleConfig("admin");
~~~
#### think.hook()
注册、获取和执行 hook。
系统默认的 hook 列表:
~~~
export default {
form_parse: ["parse_json_payload"],
resource_check: ["resource"],
resource_output: ["output_resource"],
route_parse: ["rewrite_pathname", "subdomain_deploy", "route"],
app_begin: ["check_csrf", "read_html_cache"],
view_init: [],
view_template: ["locate_template"],
view_parse: ["parse_template"],
view_filter: [],
view_end: ["write_html_cache"],
app_end: []
};
~~~
项目中可以根据需要追加或者修改。
##### 获取事件对应的 middleware 列表
~~~
think.hook("view_template");
//returns
["locate_template"]
~~~
##### 设置 hook
~~~
//替换原有的 hook
think.hook("view_template", ["locate_template1"]);
//将原有的之前追加
think.hook("view_template", ["locate_template1"], "prepend");
//将原有的之后追加
think.hook("view_template", ["locate_template1"], "append");
~~~
##### 删除 hook
~~~
think.hook("view_template", null);
~~~
##### 执行 hook
~~~
let result = think.hook("view_template", http, data);
//result is a promise
~~~
#### think.middleware()
注册、创建、获取和执行 middleware。
##### 创建 middleware
~~~
//解析 XML 示例
var ParseXML = think.middlearea({
run: function(){
var http = this.http;
var payload = http.payload; //payload为上传的post数据
var data = xmlParse.parse(payload); //使用一个xml解析,这里 xmlParse 是示例
http._post = data; //将解析后的数据赋值给 http._post,后续可以通过 http.post("xxx") 获取
}
});
~~~
使用 ES6 创建 middleware。
~~~
let Cls1 = class extends think.middleware.base {
run(){
let http = this.http;
}
}
~~~
##### 注册 middleware
middlearea 可以是个简单的 function,也可以是较为复杂的 class。
~~~
//注册 middleware 为 function
think.middleware("parse_xml", http => {
})
~~~
~~~
//注册 middleware 为 class
//会自动调用 run 执行
let Cls = think.middlearea({
run: function(){
let http = this.http;
}
});
think.middleware("parse_xml", Cls);
~~~
##### 获取 middleware
~~~
let middlearea = think.middleare("parse_xml");
~~~
##### 执行 middleware
~~~
let result = think.middleare("parse_xml", http);
//result is a promise
~~~
#### think.adapter()
创建、注册、获取和执行 adapter。
##### 创建 adapter
~~~
//创建一个 adapter
var Cls = think.adapter({
});
//创建一个 session adapter,继承自 session base 类
var Cls = think.adapter("session", "base", {
})
~~~
~~~
//使用 ES6 创建一个 session adapter
let Cls = class extends think.adapter.session {
}
~~~
##### 注册 adapter
~~~
//注册一个 xxx 类型的 session adapter
think.adapter("session", "xxx", Cls);
~~~
##### 获取 adapter
~~~
//获取 file 类型的 session adapter
let Cls = think.adapter("session", "file");
~~~
##### 执行 adapter
~~~
let Adapter = think.adapter("session", "file");
let instance = new Adapter(options);
~~~
#### think.gc(instance)
* `instance` {Object} 类的实例
注册实例到 gc 队列中。instance 必须含有属性`gcType`和方法`gc`。
像 cache, session 这些功能一般都是有过期时间,过期后需要要进行清除工作。框架提供了一套机制方便清除过期的文件等。
~~~
let Cls = class extends think.adapter.cache {
init(options){
super.init(options);
this.gcType = "xFileCache";
think.gc(this);
}
gc(){
//寻找过期的内容并清除
}
}
~~~
#### think.http(req, res)
* `req` {Object} request 对象
* `res` {Object} response 对象
* `return` {Promise}
根据 req 和 res 包装成 http 对象。req 和 res 可以自定义。
~~~
//根据一个 url 生成一个 http 对象,方便命令行下调用
think.http("/index/test").then(http => {
});
~~~
#### think.uuid(length)
* `length` {Number} 生成字符串的长度,默认为 32
生成一个随机字符串。
#### think.session(http)
* `http` {Object} http对象
生成 session,并写到 http 对象上。如果已经存在,则直接返回。
#### think.controller()
创建、执行 controller
##### 创建 controller
~~~
//创建 controller, 继承 think.controller.base
let Cls = think.controller({
})
//创建 controller, 继承 think.controller.rest
let Cls = think.controller("rest", {
})
~~~
~~~
//使用 ES6 创建 controller
let Cls1 = class extends think.controller.base {
}
~~~
##### 实例化 controller
~~~
//实例化 home 模块下 user controller
let instance = think.controller("user", http, "home");
~~~
#### think.logic()
创建、执行 logic
##### 创建 logic
~~~
//创建 logic, 继承 think.logic.base
let Cls = think.logic({
})
~~~
~~~
//使用 ES6 创建 logic
let Cls1 = class extends think.logic.base {
}
~~~
##### 实例化 logic
~~~
//实例化 home 模块下 user logic
let instance = think.logic("user", http, "home");
~~~
#### think.model()
创建或者获取 model。
##### 创建 model
~~~
//创建一个 model
let model = think.model({
getList: function(){
}
});
//ES6 里直接继承 think.model.base 类
let model = class extends think.model.base {
getList(){
}
}
//创建一个 model 继承自 mongo model
let model = think.model("mongo", {
getList: function(){
}
});
//ES6 里直接继承 think.model.mongo 类
let model = class extends think.model.mongo {
getList(){
}
}
~~~
##### 获取 model 实例
~~~
let configs = {
host: "127.0.0.1",
name: "user"
}
//获取 home 模块下 user model
let instance = think.model("user", configs, "home");
~~~
#### think.service()
创建或者获取 service。
##### 创建 service
~~~
//创建一个 service 类
let service = think.service({
})
//ES6 里直接继承 think.service.base 类
let service = class extends think.service.base {
}
~~~
service 基类继承自 [think.base](https://thinkjs.org/zh-CN/doc/2.0/api_think_base.html),所以可以用 think.base 里的方法。
如果 serivce 不想写成类,那就没必要通过这种方法创建。
##### 获取 service
~~~
//获取 home 模块下 post service,并传递参数 {}
//如果获取到的 service 是个类,则自动实例化
think.service("post", {}, "home");
~~~
#### think.cache(name, value, options)
* `name` {String} 缓存 key
* `value` {Mixed} 缓存值
* `options` {Object} 缓存选项
* `return` {Promise} 操作都是返回 Promise
获取、设置或者删除缓存, value 是 `undefined` 表示读取缓存。 value 是 `null` 时删除缓存。
value 为 `Function` 时表示获取缓存,如果获取不到,则调用该函数,然后将返回值设置到缓存中并返回。
~~~
//获取缓存
think.cache("name").then(data => {});
//指定缓存类型获取,从 redis 里获取缓存
think.cache("name", undefined, {type: "redis"});
//如果缓存 userList 不存在,则查询数据库,并将值设置到缓存中
think.cache("userList", () => {
return think.model("user").select();
});
//设置缓存
think.cache("name", "value");
//删除缓存
think.cache("name", null);
~~~
#### think.locale(key, ...data)
* `key` {String} 要获取的 key
* `data` {Array} 参数
根据语言获取对应的值,当前语言存放在`think.lang`,可以在系统启动时指定。
~~~
think.locale("CONTROLLER_NOT_FOUND", "test", "/index/test");
//returns
"controller `test` not found. url is `/index/test`."
~~~
#### think.validate()
注册、获取或执行检测。
##### 注册检测方法
~~~
//注册检测类型为 not_number
think.validate("not_number", value => {
return !(/^\d+$/.test(value));
})
~~~
##### 获取检测方法
~~~
let fn = think.validate("not_number");
~~~
##### 检测数据
~~~
let result = think.validate({
name: {
value: "name",
required: true,
not_number: true
},
pwd: {
value: "xxx",
required: true,
minLength: 6
}
});
//如果 result 是 isEmpty,表示数据都正常
if(think.isEmpty(result)){
}
~~~
#### think.await(key, callback)
* `key` {String}
* `callback` {Function}
执行等待,避免一个耗时的操作多次被执行。 callback 需要返回一个 promise 。
如:用户访问时,要请求一个远程的接口数据。如果不处理,每个用户请求都会触发这个远程接口的访问,导致有很大的资源浪费。可以让这些用户公用一个远程接口的请求。
~~~
import superagent from "superagent";
export default class extends think.controller.base {
* indexAction(){
let result = yield think.await("get_xxx_data", () => {
let req = superagent.post("xxxx");
let fn = think.promisify(req.end, req);
return fn();
});
this.success(result);
}
}
~~~
#### think.npm(pkg)
* `pkg` {String} 模块名
加载模块。如果模块不存在,则自动安装。这样可以做到动态安装模块。
~~~
//如果mysql模块,则通过npm安装
let mysql = think.npm("mysql");
~~~
~~~
//指定版本加载一个模块
let mysql = think.npm("mysql@2.0.0")
~~~
#### think.error(err, addon)
* `err` {Error | Promise | String} 错误信息
* `addon` {Error | String} 追加的错误信息
格式化错误信息,将部分系统的错误信息描述完整化。
~~~
let error = think.error(new Error("xxx"));
~~~
##### 捕获 promise 的错误信息
~~~
let promise = Project.reject(new Error("xxx"));
promise = think.error(promise)
~~~
自动给 promise 追加 catch,捕获错误信息。
#### think.statusAction(status, http, log)
* `status` {Number} 状态码
* `http` {Object} 包装的http对象
* `log` {Boolean} 是否打印错误信息
当系统出现异常时(系统错误,页面找不到,没权限等),显示对应的错误页面。
创建项目时,会在 common 模块下生成文件 `src/common/controller/error.js`,专门用来处理错误情况。
默认支持的错误类型有:`400`, `403`, `404`, `500`, `503`。
项目里可以根据需要修改错误页面或者扩展。
~~~
export default class extends think.controller.base {
indexAction(){
if(xxxx){
let error = new Error("not found");
//将错误信息写到 http 对象上,用于模版里显示
this.http.error = error;
return think.statusAction(404, this.http);
}
}
}
~~~
### 类
#### think.base
think.base 详细介绍请见 [这里](https://thinkjs.org/zh-CN/doc/2.0/api_think_base.html)
#### think.http.base
think.http.base 详细介绍请见 [这里](https://thinkjs.org/zh-CN/doc/2.0/api_think_http_base.html)
文档地址:[https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_think.md](https://github.com/75team/www.thinkjs.org/tree/master/view/zh-CN/doc/2.0/api_think.md)
API
最后更新于:2022-04-01 04:34:49
推荐模块
最后更新于:2022-04-01 04:34:47
# 推荐模块
[TOC=2,3]
### 网络请求
* superagent
* request
### 日志
* log4js
### 日期处理
* moment
### 编码转化
* iconv-lite
### 图像处理
* gm
### 框架
* thinkjs
* express
* koa
* sails
### 调试
* node-inspector
### 单元测试
* mocha
* istanbul
* muk
### 服务管理
* pm2
### 邮件
* nodemailer
### 定时任务
* node-crontab
线上部署
最后更新于:2022-04-01 04:34:45
# 线上部署
[TOC=2,3]
## 使用 pm2 管理服务
pm2 是一款专业管理 Node.js 服务的模块,非常建议在线上使用。使用 pm2 需要以全局的方式安装,如:`sudo npm install -g pm2`。安装完成后,命令行下会有 pm2 命令。
创建项目时,会在项目目录下创建名为 `pm2.json` 的配置文件,内容类似如下:
~~~
{
"apps": [{
"name": "demo",
"script": "www/production.js",
"cwd": "/Users/welefen/Develop/git/thinkjs/demo",
"max_memory_restart": "1G",
"autorestart": true,
"node_args": [],
"args": [],
"env": {
}
}]
}
~~~
将 `cwd` 配置值改为线上真实的项目路径,然后在项目目录下使用下面的命令来启动/重启服务:
~~~
pm2 startOrGracefulReload pm2.json
~~~
pm2 详细的配置请见 [http://pm2.keymetrics.io/docs/usage/application-declaration/](http://pm2.keymetrics.io/docs/usage/application-declaration/)。
## 使用 nginx 做反向代理
创建项目时,会在项目目录创建一个名为 `nginx.conf` 的 nginx 配置文件。配置文件内容类似如下:
~~~
server {
listen 80;
server_name localhost;
root /Users/welefen/Develop/git/thinkjs/demo/www;
set $node_port 8360;
index index.js index.html index.htm;
if ( -f $request_filename/index.html ){
rewrite (.*) $1/index.html break;
}
if ( !-f $request_filename ){
rewrite (.*) /index.js;
}
location = /index.js {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:$node_port$request_uri;
proxy_redirect off;
}
location ~ /static/ {
etag on;
expires max;
}
}
~~~
将 `server_name localhost` 里的 localhost 修改为对应的域名,将 `set $node_port 8360` 里的 8360 修改和项目里监听的端口一致。
修改完成后,将该配置文件拷贝到 nginx 的配置文件目录中,然后通过 `nginx -s reload` 命令 reload 配置,这样就可以通过域名访问了。
线上建议开启配置 `proxy_on`,这样就可以禁止直接通过 IP + 端口来访问。修改配置文件`src/common/config/env/production.js`,如:
~~~
export default {
proxy_on: true
}
~~~
## 关闭静态资源处理的配置
为了方便开发,ThinkJS 是支持处理静态资源请求的。但代码部署在线上时,是用 nginx 来处理静态资源请求的,这时可以关闭 ThinkJS 里处理静态资源请求的功能来提高性能。
可以在配置文件 `src/common/config/env/production.js` 中加入如下的配置:
~~~
export default {
resource_on: false
}
~~~
## 使用 cluster
线上可以开启 cluster 功能达到利用多核 CPU 来提升性能,提高并发处理能力。
可以在配置文件 `src/common/config/env/production.js` 中加入如下的配置:
~~~
export default {
cluster_on: true
}
~~~
定时任务
最后更新于:2022-04-01 04:34:42
# 定时任务
[TOC=2,3]
项目在线上运行时,经常要定时去执行某个功能,这时候就需要使用定时任务来处理了。ThinkJS 支持命令行方式调用,结合系统的 crontab 功能可以很好的支持定时任务。
## 命令行执行
ThinkJS 除了支持通过 URL 访问来执行外,还可以通过命令行的方式调用执行。使用方式如下:
~~~
node www/production.js home/index/index
~~~
上面的命令表示执行 `home` 模块下 `index` Controller 里的 indexAction。
#### 携带参数
如果需要加参数,只要在后面加上对应的参数即可:
~~~
node www/production.js home/index/index?name=thinkjs
~~~
Action 里就可以通过 `this.get` 方法来获取参数 `name` 了。
#### 修改请求方法
命令行执行默认的请求类型是 GET,如果想改为其他的类型,可以用下面的方法:
~~~
node www/production.js url=home/index/index&method=post
~~~
这样就把请求类型改为了 post。但这种方式下,参数 url 的值里就不能包含 & 字符了(可以通过上面 / 的方式指定参数)。
除了修改请求类型,还可以修改下面的参数:
* `host` 修改请求的 host 默认为 127.0.0.1
* `ip` 修改请求的 ip 默认为 127.0.0.1
#### 修改 header
有时候如果想修改更多的 headers,可以传一个完整的 json 数据,如:
~~~
node www/production.js {"url":"/index/index","ip":"127.0.0.1","method":"POST","headers":{"xxx":"yyyy"}}
~~~
#### 禁止 URL 访问
默认情况下,命令行执行的 Action 通过 URL 也可以访问到。如果禁止 URL 访问到该 Action,可以通过`think.cli` 来判断。如:
~~~
export default class extends think.controller.base {
indexAction(){
//禁止 URL 访问该 Action
if(!think.cli){
this.fail("only invoked in cli mode");
}
...
}
}
~~~
## 执行脚本
可以创建一个简单的执行脚本来调用命令行执行,如:
~~~
cd project_path;
node www/prodution.js home/index/index;
~~~
在项目目录下创建目录 `crontab`,将上面执行脚本存为一个文件放在该目录下。
## 定时执行
借助系统里的 crontab 可以做到定时执行,通过命令 `crontab -e` 来编辑定时任务,如:
~~~
0 */1 * * * /bin/sh project_path/crontab/a.sh # 1 小时执行一次
~~~
## 使用 node-crontab 模块执行定时任务
除了使用 crontab 和命令行联合执行定时任务外,也可以使用 `node-crontab` 模块执行定时任务。如:
~~~
import crontab from "node-crontab";
// 1 小时执行一次
let jobId = crontab.scheduleJob("0 */1 * * *", () => {
});
~~~
将上面代码文件存放在 `src/common/bootstrap` 目录下,这样可以在服务启动时自动执行。
REST API
最后更新于:2022-04-01 04:34:40
# REST API
[TOC=2,3]
项目中,经常要提供一个 API 供第三方使用,一个通用的 API 设计规范就是使用 REST API。REST API 是使用 HTTP 中的请求类型来标识对资源的操作。如:
* `GET` `/ticket` #获取ticket列表
* `GET` `/ticket/12` #查看某个具体的ticket
* `POST` `/ticket` #新建一个ticket
* `PUT` `/ticket/12` #更新ticket 12
* `DELETE` `/ticket/12` #删除ticekt 12
ThinkJS 中提供了很便捷的方式来创建 REST API,创建后无需额外的代码即可响应 REST API 的处理,同时也可以通过定制响应额外的需求。
## 创建 REST API
通过 `thinkjs controller [name] --rest` 即可创建一个 REST API。如:
~~~
thinkjs controller home/ticket --rest
~~~
上面的命令表示在 `home` 模块下创建了一个 `ticket` 的 Rest Controller,该 Controller 用来处理资源 `ticket`的请求。
## 处理 REST API 请求
Rest Controller 创建完成后,无需写任何的代码,即可完成对 REST API 的处理。资源名称和数据表名称是一一对应的,如:资源名为 `ticket`,那么对应的数据表为 `数据表前缀` + `ticket`。
## 请求类型
REST API 默认是从 HTTP METHOD 里获取当前的请求类型的,如:当前请求类型是 `DELETE`,表示对资源进行删除操作。
如果有些客户端不支持发送 `DELETE` 请求类型,那么可以通过属性 `_method` 指定一个参数用来接收请求类型。如:
~~~
export default class extends think.controller.rest {
init(http){
super.init(http);
this._method = "_method"; //指定请求类型从 GET 参数 _method 里获取
}
}
~~~
## 字段过滤
默认情况下,获取资源信息时,会将资源的所有字段都返回。有时候需要隐藏部分字段,可以通过在`__before` 魔术方法里完成此类操作。
~~~
export default class extends think.controller.rest {
__before(){
this.modelInstance.fieldReverse("password,score"); //隐藏 password 和 score 字段
}
}
~~~
## 权限管理
有些 REST API 需要进行权限验证,验证完成后才能获取对应的信息,可以通过在 `__before` 魔术方法里进行验证。
~~~
export default class extends think.controller.rest {
* __before(){
let auth = yield this.checkAuth();
if(!auth){
return this.fail("no permissions"); //没权限时直接返回
}
}
}
~~~
## 更多定制
更多定制方式请参见 [API -> controller.rest](https://thinkjs.org/zh-CN/doc/2.0/api_controller_rest.html)。
路径常量
最后更新于:2022-04-01 04:34:38
# 路径常量
[TOC=2,3]
系统提供了很多常量供项目里使用,利用这些常量可以方便的访问对应的文件。
### think.ROOT_PATH
项目的根目录。
### think.RESOURCE_PATH
静态资源根目录,路径为 `think.ROOT_PATH` + `/www/`。
### think.APP_PATH
APP 代码目录,路径为 `think.ROOT_PATH` + `/app/`。
### think.THINK_PATH
ThinkJS 框架的根目录。
### think.THINK_LIB_PATH
ThinkJS 框架 `lib` 目录。
### think.getPath(module, type)
对于 model,controller,view 等目录,由于每个模块下都有这些目录,所以无法给出一个固定的路径值。可以通过 `think.getPath` 来获取模块下的路径。
~~~
let path1 = think.getPath("common", "model"); //获取 common 下 model 的目录
let path2 = think.getPath("home", "controller"); //获取 home 模块下 controller 的目录
~~~
### 自定义路径常量
除了通过系统给的属性或者方法来获取路径,还可以在项目里定义额外的路径常量。
##### 入口文件里定义
项目的入口文件为 `src/index.js` 或者 `src/production.js` 等,可以在这些入口文件里定义一些路径常量。如:
~~~
var thinkjs = require("thinkjs");
var path = require("path");
var rootPath = path.dirname(__dirname);
var instance = new thinkjs({
APP_PATH: rootPath + "/app",
ROOT_PATH: rootPath,
RESOURCE_PATH: __dirname,
UPLOAD_PATH: __dirname + "/upload", // 定义文件上传的目录
env: "development"
});
instance.run();
~~~
##### 启动文件里定义
定义在 `src/common/bootstrap` 里的文件在项目启动时会自动加载,所以也可以在这些文件里定义路径常量。如:
~~~
// src/common/bootstrap/common.js
think.UPLOAD_PATH = think.RESOURCE_PATH + "/upload"; //定义文件上传的目录
~~~
国际化
最后更新于:2022-04-01 04:34:35
# 国际化
[TOC=2,3]
## 获取语言
可以通过 `http.lang` 方法从 cookie 或者 header 里获取当前用户的语言。如:
~~~
let lang = http.lang();
~~~
如果要支持从 cookie 里获取用户选择的语言,那么需要设置语言在 cookie 里的名称。可以在配置文件`src/common/config/locale.js` 里修改,如:
~~~
export default {
cookie_name: "think_locale", //存放语言的 cookie 名称
default: "en" //默认语言
};
~~~
在 Controller 里可以直接通过 `this.lang` 方法获取对应的语言。
## 从 URL 中解析语言
有些情况下,语言是要从 URL 中解析。比如:当前页面的地址是 `https://www.thinkjs.org/zh-CN/doc/2.0/i18n.html`,就包含了语言 `zh-CN`。
这种情况下需要在项目里通过 middleware 来解析其中的语言,如:
~~~
think.middleware("get_lang", http => {
let supportLangs = think.config("locale.support");
let lang = http.pathname.split("/")[0]; //从 URL 中获取语言
if(supportLangs.indexOf(lang) > -1){
http.pathname = http.pathname.substr(lang.length + 1);
}else{
lang = http.lang(); //从 cookie 或者 header 中获取语言
if(supportLangs.indexOf(lang) === -1){
lang = http.config("locale.default"); //默认支持的语言
}
}
http.lang(lang, true); //设置语言,并设置模版路径中添加语言目录
});
~~~
从 URL 中解析到语言后,通过 `http.lang` 方法设置语言,后续在 Controller 里可以直接通过 `http.lang` 来获取语言了。
定义 middleware `get_lang` 后,添加到对应的 hook 里。如:
~~~
export default {
route_parse: ["prepend", "get_lang"], //将 get_lang 前置添加到 route_parse hook 里
}
~~~
## 语言变量配置
支持国际化的项目需要配置变量在不同语言下的值,配置文件在 `src/common/config/locale/[lang].js`,配置格式如下:
~~~
// src/common/config/locale/zh-CN.js
export default {
"title-home": "ThinkJS官网 - A Node.js MVC Framework Support All Of ES6/7 Features",
"title-changelog": "更新日志 - ThinkJS官网",
}
~~~
~~~
// src/common/config/locale/en.js
export default {
"title-home": "ThinkJS - A Node.js MVC Framework Support All Of ES6/7 Features",
"title-changelog": "Changelog - ThinkJS"
}
~~~
## 获取语言变量
配置语言变量后,可以通过 `http.locale` 方法来获取当前语言对应的变量值。如:
~~~
let homeTitle = http.locale("title-home");
~~~
如果在 Controller 中,可以直接通过 `this.locale` 方法来获取,如:
~~~
export default class extends think.controller.base {
indexAction(){
let homeTitle = this.locale("title-home");
}
}
~~~
## 模版里使用语言变量
模版里可以通过 `_` 函数来获取对应的语言值。下面以 `ejs` 模版引擎为例:
~~~
<%- _("title-home") %>
~~~
## 设置模版语言路径
有些项目中,需要根据不同的语言定制不同的模版,这时模版路径里加一层语言目录来处理就比较合适了。如:`view/zh-CN/home/index_index.html`,路径中添加了一层 `zh-CN` 语言目录。
可以通过 `http.lang` 方法设置语言并设置在模版路径里添加一层语言目录。如:
~~~
http.lang(lang, true); // true 表示在模版路径里添加一层语言目录
~~~
在 Controller 里可以通过 `this.lang` 方法来设定。如:
~~~
export default class extends think.controller.base {
indexAction(){
let lang = getFromUrl();
this.lang(lang, true);
...
}
}
~~~
数据校验
最后更新于:2022-04-01 04:34:33
# 数据校验
[TOC=2,3]
当在 Action 里处理用户的请求时,经常要先获取用户提交过来的数据,然后对其校验,如果校验没问题后才能进行后续的操作。当参数校验完成后,有时候还要进行权限判断,等这些都判断无误后才能进行真正的逻辑处理。如果将这些代码都放在一个 Action 里,势必让 Action 的代码非常复杂且冗长。
为了解决这个问题, ThinkJS 在控制器前面增加了一层 `Logic`,Logic 里的 Action 和控制器里的 Action 一一对应,系统在调用控制器里的 Action 之前会自动调用 Logic 里的 Action。
## Logic 层
Logic 目录在 `src/[module]/logic`,在通过命令 `thinkjs controller [name]` 创建 Controller 时会自动创建对应的 Logic。Logic 代码类似如下:
~~~
"use strict";
/**
* logic
* @param {} []
* @return {} []
*/
export default class extends think.logic.base {
/**
* index action logic
* @return {} []
*/
indexAction(){
}
}
~~~
其中,Logic 里的 Action 和 Controller 里的 Action 一一对应。Logic 里也支持 `__before` 和 `__after` 等魔术方法。
## 数据校验配置
数据校验的配置如下:
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
doc: "string|default:index",
version: "string|in:1.2,2.0|default:2.0"
}
}
}
~~~
### 配置格式
配置格式为 `字段名` -> `配置`,每个字段的配置支持多个校验类型,校验类型之间用 `|` 隔开,校验类型和参数之间用 `:` 隔开,参数之间用 `,` 隔开来支持多个参数。
### 参数格式
校验类型后面可以接参数,除了支持用逗号隔开的简单参数外,还可以支持 JSON 格式的复杂参数。如:
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
field1: "array|default:[1,2]", //参数为数组
field2: "object|default:{\"name\":\"thinkjs\"}" //参数为对象
}
}
}
~~~
### 支持的数据类型
支持的数据类型有:`boolean`、`string`、`int`、`float`、`array`、`object`,默认为 `string`。
### 默认值
使用 `default:value` 来定义字段的默认值,如果当前字段值为空,会将默认值覆盖过去,后续获取到的值为该默认值。
### 获取数据的方式
默认根据当前请求的类型来获取字段对应的值,如果当前请求类型是 GET,那么会通过 `this.get('version')`来获取 `version` 字段的值。如果请求类型是 POST,那么会通过 `this.post` 来获取字段的值。
但有时候在 POST 类型下,可能会获取上传的文件或者获取 URL 上的参数,这时候就需要指定获取数据的方式了。支持的获取数据方式为 `get`,`post` 和 `file`。
~~~
export default class extends think.logic.base {
/**
* 保存数据,POST 请求
* @return {} []
*/
saveAction(){
let rules = {
name: "required",
image: "object|file|required",
version: "string|get|in:1.2,2.0|default:2.0"
}
}
}
~~~
上面示例指定了字段 `name` 通过 `post` 方法来获取值,字段 `image` 通过 `file` 方式来获取值,字段 `version`通过 `get` 方式来获取值。
### 错误信息
上面的配置只是指定了具体的校验规则,并没有指定校验出错后给出的错误信息。错误信息支持国际化,需要在配置文件 `src/common/config/locale/[lang].js` 中定义。如:
~~~
// src/common/config/locale/en.js
export default {
validate_required: "{name} can not be blank",
validate_contains: "{name} need contains {args}",
}
~~~
其中 key 为 `validate_` + `校验类型名称`,值里面支持 `{name}` 和 `{args}` 2个参数,分别代表字段名称和传递的参数。
如果想定义个特定字段某个错误类型的具体信息,可以通过在后面加上字段名。如:
~~~
// src/common/config/locale/en.js
export default {
validate_required: "{name} can not be blank",
validate_required_email: "email can not be blank", //指定字段 email 的 required 错误信息
}
~~~
## 数据校验方法
配置好校验规则后,可以通过 `this.validate` 方法进行校验。如:
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
doc: "string|default:index",
version: "string|in:1.2,2.0|default:2.0"
}
let flag = this.validate(rules);
if(!flag){
return this.fail("validate error", this.errors());
}
}
}
~~~
如果返回值为 `false`,那么可以通过 `this.errors` 方法获取详细的错误信息。拿到错误信息后,可以通过`this.fail` 方法把错误信息以 JSON 格式输出,也可以通过 `this.display` 方法输出一个页面。
错误信息通过 `errors` 字段赋值到模版里,模版里通过下面的方式显示错误信息(以 ejs 模版为例):
~~~
<%for(var field in errors){%>
<%-field%>:<%errors[field]%>
<%}%>
~~~
#### 自动校验
一般情况下,都是校验有问题后,输出一个 JSON 信息。如果每次都要在 Logic 的 Action 手动调用`this.validate` 进行校验,势必比较麻烦。可以通过将校验规则赋值给 `this.rules` 属性进行自动校验。如:
~~~
export default class extends think.logic.base {
indexAction(){
this.rules = {
doc: "string|default:index",
version: "string|in:1.2,2.0|default:2.0"
}
}
}
~~~
将校验规则赋值给 `this.rules` 属性后,会在这个 Action 执行完成后自动校验,如果有错误则直接输出 JSON 格式的错误信息。自动校验是通过魔术方法 `__after` 来完成的。
## 支持的校验类型
### required
必填项。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "required" //name 的值必填
}
}
}
~~~
### requiredIf
当另一个项的值为某些值其中一项时,该项必填。如:
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "requiredIf:email,admin@example.com,admin1@example.com"
}
}
}
~~~
当 `email` 的值为 `admin@example.com`,`admin1@example.com` 等其中一项时, `name` 的值必填。
### requiredNotIf
当另一个项的值不在某些值中时,该项必填。如:
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "requiredNotIf:email,admin@example.com,admin1@example.com"
}
}
}
~~~
当 `email` 的值不为 `admin@example.com`,`admin1@example.com` 等其中一项时, `name` 的值必填。
### requiredWith
当其他几项有一项值存在时,该项必填。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "requiredWith:email,title"
}
}
}
~~~
当 `email`, `title` 等项有一项值存在时,`name` 的值必填。
### requiredWithAll
当其他几项值都存在时,该项必填。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "requiredWithAll:email,title"
}
}
}
~~~
当 `email`, `title` 等项值都存在时,`name` 的值必填。
### requiredWithout
当其他几项有一项值不存在时,该项必填。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "requiredWithout:email,title"
}
}
}
~~~
当 `email`, `title` 等项其中有一项值不存在时,`name` 的值必填。
### requiredWithoutAll
当其他几项值都存在时,该项必填。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "requiredWithoutAll:email,title"
}
}
}
~~~
当 `email`, `title` 等项值都不存在时,`name` 的值必填。
### contains
值需要包含某个特定的值。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "contains:thinkjs" //需要包含字符串 thinkjs。
}
}
}
~~~
### equals
和另一项的值相等。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "equals:firstname"
}
}
}
~~~
`name` 的值需要和 `firstname` 的值相等。
### different
和另一项的值不等。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "different:firstname"
}
}
}
~~~
`name` 的值不能和 `firstname` 的值相等。
### before
值需要在一个日期之后,默认为需要在当前日期之前。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
start_time: "before", //需要在当前日期之前。
start_time1: "before:2015/10/12 10:10:10" //需要在 2015/10/12 10:10:10 之前。
}
}
}
~~~
### after
值需要在一个日期之后,默认为需要在当前日期之后。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
end_time: "after", //需要在当前日期之后。
end_time1: "after:2015/10/10" //需要在 2015/10/10 之后。
}
}
}
~~~
### alpha
值只能是 [a-zA-Z] 组成。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
en_name: "alpha"
}
}
}
~~~
`en_name` 的值只能是 [a-zA-Z] 组成。
### alphaDash
值只能是 [a-zA-Z_] 组成。
### alphaNumeric
值只能是 [a-zA-Z0-9] 组成。
### alphaNumericDash
值只能是 [a-zA-Z0-9_] 组成。
### ascii
值只能是 ascii 字符组成。
### base64
值必须是 base64 编码。
### byteLength
字节长度需要在一个区间内。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "byteLength:10" //字节长度不能小于 10
name1: "byteLength:10,100" //字节长度需要在 10 - 100 之间
}
}
}
~~~
### creditcard
需要是信用卡数字。
### currency
需要是货币。
### date
需要是个日期。
### decimal
需要是个小数。
### divisibleBy
需要被一个数整除。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
count: "divisibleBy:3" //可以被 3 整除
}
}
}
~~~
### email
需要是个 email 格式。
### fqdn
需要是个合格的域名。
### float
需要是个浮点数。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
money: "float" //需要是个浮点数
money1: "float:3.2" //需要是个浮点数,且最小值为 3.2
money2: "float:3.2,10.5" //需要是个浮点数,且最小值为 3.2,最大值为 10.5
}
}
}
~~~
### fullWidth
包含宽字节字符。
### halfWidth
包含半字节字符。
### hexColor
需要是个十六进制颜色值。
### hex
需要是十六进制。
### ip
需要是 ip 格式。
### ip4
需要是 ip4 格式。
### ip6
需要是 ip6 格式。
### isbn
需要是图书编码。
#### isin
需要是证券识别编码。
### iso8601
需要是 iso8601 日期格式。
### in
在某些值中。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
version: "in:1.2,2.0" //需要是 1.2,2.0 其中一个
}
}
}
~~~
### noin
不能在某些值中。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
version: "noin:1.2,2.0" //不能是 1.2,2.0 其中一个
}
}
}
~~~
### int
需要是 int 型。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
value: "int" //需要是 int 型
value1: "int:1" //不能小于1
value2: "int:10,100" //需要在 10 - 100 之间
}
}
}
~~~
### min
不能小于某个值。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
value: "min:10" //不能小于10
}
}
}
~~~
### max
不能大于某个值。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
value: "max:10" //不能大于10
}
}
}
~~~
### length
长度需要在某个范围。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "length:10" //长度不能小于10
name1: "length:10,100" //长度需要在 10 - 100 之间
}
}
}
~~~
### minLength
长度不能小于最小长度。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "minLength:10" //长度不能小于10
}
}
}
~~~
### maxLength
长度不能大于最大长度。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
name: "maxLength:10" //长度不能大于10
}
}
}
~~~
### lowercase
需要都是小写字母。
### uppercase
需要都是大小字母。
### mobile
需要手机号。
~~~
export default class extends think.logic.base {
indexAction(){
let rules = {
mobile: "mobile:zh-CN" //必须为中国的手机号
}
}
}
~~~
### mongoId
是 MongoDB 的 ObjectID。
### multibyte
包含多字节字符。
### url
是个 url。
### order
数据库查询 order,如:name DESC。
### field
数据库查询的字段,如:name,title。
### image
上传的文件是否是个图片。
### startWith
以某些字符打头。
### endWith
以某些字符结束。
### string
值为字符串。
### array
值为数组。
### boolean
值为布尔类型。
### object
值为对象。
## 扩展校验类型
如果默认支持的校验类型不能满足需求,可以通过 `think.validate` 方法对校验类型进行扩展。如:
~~~
// src/common/bootstrap/validate.js
think.validate("validate_name", (value, ...args) => {
//需要返回 true 或者 false
//true 表示校验成功,false 表示校验失败
})
~~~
上面注册了一个名为 `validate_name` 的校验类型,这样在 Logic 里就可以直接使用该校验类型了。
#### 参数解析
如果要解析后面的 `args`,如:该字段值跟其他字段值进行比较,这时拿到的参数是其他字段名称,但比较的时候肯定需要拿到这个字段值,所以需要将字段名称解析为对应的字段值。
可以通过注册一个解析参数函数来完成。如:上面的校验类型名称为 `validate_name`,那么对应的解析参数的名称必须为 `_validate_name`,即:`_` + `校验类型`。
~~~
think.validate("_validate_name", (args, data) => {
let arg0 = args[0];
args[0] = data[arg0].value; //将第一个参数字段名称解析为对应的参数值
return args;
})
~~~
错误信息
最后更新于:2022-04-01 04:34:31
[TOC=2,3]
### EPERM
##### Operation not permitted
An attempt was made to perform an operation that requires appropriate privileges.
### ENOENT
##### No such file or directory
Commonly raised by fs operations; a component of the specified pathname does not exist -- no entity (file or directory) could be found by the given path.
### EACCES
##### Permission denied
An attempt was made to access a file in a way forbidden by its file access permissions.
### EEXIST
##### File exists
An existing file was the target of an operation that required that the target not exist.
### ENOTDIR
##### Not a directory
A component of the given pathname existed, but was not a directory as expected. Commonly raised by fs.readdir.
### EISDIR
##### Is a directory
An operation expected a file, but the given pathname was a directory.
### EMFILE
##### Too many open files in system
Maximum number of file descriptors allowable on the system has been reached, and requests for another descriptor cannot be fulfilled until at least one has been closed.
Commonly encountered when opening many files at once in parallel, especially on systems (in particular, OS X) where there is a low file descriptor limit for processes. To remedy a low limit, run ulimit -n 2048 in the same sh that will run the Node.js process.
### EPIPE
##### Broken pipe
A write on a pipe, socket, or FIFO for which there is no process to read the data. Commonly encountered at the net and http layers, indicative that the remote side of the stream being written to has been closed.
### EADDRINUSE
##### Address already in use
An attempt to bind a server (net, http, or https) to a local address failed due to another server on the local system already occupying that address.
### ECONNRESET
##### Connection reset by peer
A connection was forcibly closed by a peer. This normally results from a loss of the connection on the remote socket due to a timeout or reboot. Commonly encountered via the http and net modules.
### ECONNREFUSED
##### Connection refused
No connection could be made because the target machine actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host.
### ENOTEMPTY
##### Directory not empty
A directory with entries was the target of an operation that requires an empty directory -- usually fs.unlink.
### ETIMEDOUT
##### Operation timed out
A connect or send request failed because the connected party did not properly respond after a period of time. Usually encountered by http or net -- often a sign that a connected socket was not .end()'d appropriately.
错误处理
最后更新于:2022-04-01 04:34:28
# 错误处理
[TOC=2,3]
系统在处理用户请求时,会遇到各种各样的错误情况。如:系统内部错误,url 不存在,没有权限,服务不可用等,这些情况下需要给用户显示对应的错误页面。
## 错误页面
通过 `thinkjs` 命令创建项目时,会自动添加错误处理的逻辑文件以及相应的错误页面。
错误逻辑文件路径为 `src/common/controller/error.js`,该文件内容大致如下:
~~~
"use strict";
/**
* error controller
*/
export default class extends think.controller.base {
/**
* display error page
* @param {Number} status []
* @return {Promise} []
*/
displayErrorPage(status){
let module = "common";
if(think.mode !== think.mode_module){
module = this.config("default_module");
}
let file = `${module}/error/${status}.html`;
let options = this.config("tpl");
options = think.extend({}, options, {type: "ejs"});
return this.display(file, options);
}
/**
* Bad Request
* @return {Promise} []
*/
_400Action(){
return this.displayErrorPage(400);
}
/**
* Forbidden
* @return {Promise} []
*/
_403Action(){
return this.displayErrorPage(403);
}
/**
* Not Found
* @return {Promise} []
*/
_404Action(){
return this.displayErrorPage(404);
}
/**
* Internal Server Error
* @return {Promise} []
*/
_500Action(){
return this.displayErrorPage(500);
}
/**
* Service Unavailable
* @return {Promise} []
*/
_503Action(){
return this.displayErrorPage(503);
}
}
~~~
对应的模版文件路径为 `view/common/error_{Number}.html`。
## 错误类型
系统默认支持的错误类型有 `400`,`403`,`404`,`500` 和 `503`。
#### 400
错误的请求,如:恶意构造一些非法的数据访问、访问的 url 不合法等。
#### 403
当前访问没有权限。
#### 404
访问的 url 不存在。
#### 500
系统内部出现错误,导致当前请求不可用。
#### 503
服务不可用,需要等到恢复后才能访问。
## 扩展错误类型
项目里可以根据需要扩展错误类型,假如添加一个项目特有的错误 `600`,那么可以通过下面步骤进行:
#### 1、添加 _600Action
在 `src/common/controller/error.js` 文件中,合适的位置添加如下的代码:
~~~
_600Action(){
return this.displayErrorPage(600);
}
~~~
#### 2、添加错误页面
添加文件 `view/common/error_600.html`,并在文件里添加显示的错误内容。
#### 3、显示错误页面
添加完错误后,需要在对应地方调用显示错误才能让用户看到,可以通过 `think.statusAction` 方法实现。如:
~~~
export default class extends think.controller.base {
indexAction(){
if(someError){
return think.statusAction(600, this.http); //显示 600 错误,需要将 http 对象传递进去
}
}
}
~~~
## 修改错误页面样式
修改错误页面样式,只需要修改对应的模版文件即可,如:修改 `404` 错误则修改模版文件`view/common/error_404.html`。
Cookie
最后更新于:2022-04-01 04:34:26
# Cookie
[TOC=2,3]
## 获取 cookie
controller 或者 logic 中,可以通过 `this.cookie` 方法来获取。如:
~~~
export default class extends think.controller.base {
indexAction(){
let cookie = this.cookie("theme"); //获取名为 theme 的 cookie
}
}
~~~
http 对象里也提供了 `cookie` 方法来获取 cookie。如:
~~~
let cookie = http.cookie("theme");
~~~
## cookie 配置
cookie 默认配置如下:
~~~
export default {
domain: "",
path: "/",
httponly: false, //是否 http only
secure: false,
timeout: 0 //有效时间,0 为浏览器进程,单位为秒
};
~~~
默认 cookie 是随着浏览器进程关闭而失效,可以在配置文件 `src/common/config/cookie.js` 中进行修改。如:
~~~
export default {
timeout: 7 * 24 * 3600 //将 cookie 有效时间设置为 7 天
};
~~~
## 设置 cookie
controller 或者 logic 中,可以通过 `this.cookie` 方法来设置。如:
~~~
export default class extends think.controller.base {
indexAction(){
this.cookie("theme", "default"); //将 cookie theme 值设置为 default
}
}
~~~
http 对象里也提供了 `cookie` 方法来设置 cookie。如:
~~~
http.cookie("theme", "default");
~~~
如果设置 cookie 时想修改一些参数,可以通过第三个参数来控制,如:
~~~
export default class extends think.controller.base {
indexAction(){
this.cookie("theme", "default", {
timeout: 7 * 24 * 3600 //设置 cookie 有效期为 7 天
}); //将 cookie theme 值设置为 default
}
}
~~~