# 进阶功能
# 模型扩展
模型是项目开发中一个重要的部分,框架提供了模型功能,为此我们方便操作数据库。本文主要介绍项目自定义模型基类的常用的方法。自定义模型基类可以让我们更好的对数据进行增删改查操作,基类会自动加入基本数据,如加入添加时间,主键id等
1.模型中使用
如下所示是在模型中调用封装的自定义模型基类,
//注意正常情况下如果表名和模型一致是不用设置表名的,
//也可以通过tableName配置模型表名,具体使用方式查看thinkjs文档
//使用封装基类
const BaseModel = require('./utils/lgBase');
module.exports = class extends BaseModel {
// 设置表名(建议用这种方法,但是要注意需要自己手动写上表名前缀)
// get tableName() {
// return 'zee_device'
// }
// 添加
setAdd() {
return this.table('device').add({id_device_type: '测试添加'});
}
// 修改
setUpdate() {
return this.table('device').where({id:'431887248539781'})
.update({id_device_type: '修改测试添加'});
}
// 删除(逻辑删除)
setDelete() {
return this.table('device').where({id:'431887248539781'}).delete();
}
// 分页查询
getCountSelect() {
return this.table('device').page(1).countSelect()
}
}
//---------------------------------------------------------------
//使用原生方法
module.exports = class extends think.Model{
// 设置表名(建议用这种方法,但是要注意需要自己手动写上表名前缀)
// get tableName() {
// return 'zee_device'
// }
// 添加
setAdd() {
return this.table('device')
.add({id:"123",create_by:think.User.id,create_at:think.datetime(),
id_device_type: '测试添加'});
}
// 修改
setUpdate() {
return this.table('device').where({id:'123'})
.update({updated_by:think.User.id,updated_at:think.datetime(),
id_device_type: '修改测试添加'});
}
// 删除(逻辑删除)
setDelete() {
return this.table('device').where({id:'431887248539781'})
.update({delete_by:think.User.id,delete_at:think.datetime(),is_rec:true});
}
// 分页查询
getCountSelect() {
// 注,如果使用原生方法,模型名称和表名不一致无法使用countSelect,
//因为使用countSelect不可以设置table
return this.page(1).countSelect()
}
}
注意事项
如果在继承的自定义基类中复写了after钩子需要调用基类的他们方法,具体方法如下所示
const BaseModel = require('./utils/lgBase');
module.exports = class extends BaseModel {
async afterFind(...args) {
await super.afterFind(...args);
//do something...
}
}
2.其他地方使用
如下所示是在控制层中调用封装的自定义模型基类,通过调用think.modelPro方法可以使用自定义基类
module.exports = class extends think.Controller {
async indexAction() {
let data = await think.modelPro('device')
.setUpdate({id_device_type: '测试添加2'})
return this.success(data);
}
};
/**
* 可传入值
* @param {String} 表名
* @param {obj} 修改并添加其他的模型实例参数
*/
注意事项
1.使用 think.modelPro()不能访问model里面的模型文件,只能访问表模型。
2.使用 think.model()可以访问model里面的模型文件,也可以访问表模型,但是直接访问表模型,则无法使用扩展基类。
3.推荐直接访问表模型时使用think.modelPro(),访问模型文件时,使用think.model()。
如果你使用了自定义模型基类,但又不想自定义基类默认加入参数,如在查询时默认筛选is_rec,可使用下面方法
module.exports = class extends think.Controller {
async indexAction() {
let data = await think.modelPro('device'{no_rec:true,no_create:true})
.setUpdate({id_device_type: '测试添加2'})
let data1 = await think.model('device'{no_rec:true,no_create:true})
.setUpdate({id_device_type: '测试添加2'})
return this.success(data);
}
};
//no_rec 为true,则表示不过滤逻辑删除的数据
//no_create 为true,则表示默认不进行添加时间排序
# 拦截配置
有时候我们希望某些接口不进行token等检验,所以本篇主要介绍如何在项目在对接口进行放行。 首先我们进入中间件auth文件,在这里进行配置拦截。我们使用正则进行拦截匹配,下面我会对4个拦截方法进行介绍。
匹配规则
1.在正则中如果要把特殊字符作为常规字符来使用,只需要在它前面加个反斜杠 \,
所以在我们匹配的路径中出现特殊符合如/ ,则需要进行使用反斜杠进行转义。
2.如果想要匹配多个路径则使用|进行拼接。
3.条件不能为空,否则会认为没有条件,匹配所有。
条件不能为空,否则会认为没有条件,匹配所有。
条件不能为空,否则会认为没有条件,匹配所有。
重要的事情说三遍
- one_matching 第一级开始匹配
//第一级开始匹配
//假设我们的路径是 /web/school/teacher/list,如果需要让符合/web/school进行放行则如下设置
//需要注意括号不能为空,也不能设置|后面为空,否则正则会认为你没有条件
const one_matching= /^\/(web\/school|demo1)/
- two_matching 第二级开始匹配
// 第二级开始匹配
//假设我们的路径是 /web/school/teacher/list,如果需要让符合/school/teacher进行放行则如下设置
//需要注意括号不能为空,也不能设置|后面为空,否则正则会认为你没有条件
const one_matching= /\/\w+\/(school\/teacher|demo2)/
- finally_matching 最后一级往前匹配
//最后一级往前匹配,但不匹配最前面的一级
//假设我们的路径是 /web/school/teacher/list,如果需要让符合list进行放行则如下设置
const one_matching= /\/[\w-]+\/(list)$/
- wilfully_matching 任意匹配
//任意匹配
//假设我们的路径是 /web/school/teacher/list,如果需要让符合school进行放行则如下设置
//需要注意括号不能为空,也不能设置|后面为空,否则正则会认为你没有条件
const wilfully_matching= /\/(school)+/
案例:
module.exports = (options) => {
return async (ctx, next) => {
//需要注意括号不能为空,,也不能设置|后面为空,否则正则会认为你没有条件
//第一级开始匹配
const one_matching= /^\/(common\/socket)/
// 第二级开始匹配
const two_matching= /\/\w+\/(school|~)/
// 最后一级往前匹配,但不匹配最前面的一级
const finally_matching= /\/[\w-]+\/(school\/teacher\/list)$/
//任意匹配
const wilfully_matching= /\/(~)+/
if(one_matching.test(ctx.url)||two_matching.test(ctx.url)||
finally_matching.test(ctx.url)||wilfully_matching.test(ctx.url))
{
await next();
}else{
const {token,source,version}=ctx.headers
if(source&&version&&version=='1.0'){
if(!token) return ctx.fail(403, '请登录')
let token_check= await think.service('utils/utils').verifyToken(token)
if(!token_check) return ctx.fail(401, '无效登录或登录已经过期,请重新登录')
let user={id:111}
if(source=='app'){
// user = await app.mysql.get('v_seg__user_login', { token:authorization});
}else if(source=='web'){
// user = await app.mysql.get('v_seg__user_login', { token:authorization});
}
if (think.isEmpty(user)){
ctx.fail(401,'你已被登出,请重新登录');
return false;
} else {
think.User= await {id:1111}
await next();
}
}else{
ctx.fail(500, '请求错误');
return false;
}
}
}
};
# 定时任务
项目在线上运行时,经常要定时去执行某个功能(如:定时去远程拉取一些数据、定时计算数据库里的一些数据进行汇总),这时候就需要使用定时任务来处理了。
定时任务的配置文件为src/config/crontab.js
在开始配置之前我们先了解一下crontab 的格式,因为正常开发中我们通常并不会使用定时器去执行定时任务,
使用 cron 参数来配置定时任务的执行时机,定时任务将会按照 cron 表达式在特定的时间点执行。
* * * * *
- - - - -
| | | | |
| | | | ----- 星期几(0 - 7)(星期日为 0 或 7)
| | | ------- 月份(1 - 12)
| | --------- 日(1 - 31)
| ----------- 小时(0 - 23)
----------------- 分钟(0 - 59)
配置案例
module.exports = [
{
cron: '50 23 * * *',
handle: async () => {
think.logger.info('开启更新体温统计定时任务',think.datetime());
try {
await think.service('task/task').updateTemperatureCount();
} catch (e) {
think.logger.error(e);
}
}
},
{
cron: '0 0 * * *',
handle:'crontab/test'
}]
/**
* 可传入值
* @param {String | Number} interval 执行的时间间隔
* @param {String} cron crontab 的格式
* @param {String} type 任务执行方式, one 或者 all, 默认是 one
* @param {String} handle 执行任务,执行相应函数或者是路由地址
* @param {String} immediate 是否在启动项目时就立即执行一次,默认是 false
* @param {String} enable 是否开启,默认是 true
*/
# 数据校验
项目开发中我们需要对用户提交的数据进行校验,所有我们需要通过Logic进行校验。
Logic 目录在 src/logic,我们只需要保证Logic 里的 Action 和控制器里的 Action 一一对应,
系统在调用控制器里的 Action 之前会自动调用 Logic 里的 Action。本文主要介绍常用的和一些扩展的校验方法,更多使用方法请查看Logic (opens new window)
案例1
module.exports = class extends think.Logic {
indexAction(){
this.rules = {
username: {
required: true, // 字段必填
}
}
}
//此时如果获取不到则会返回输出提示username不能为空
案例1
module.exports = class extends think.Logic {
indexAction(){
this.rules = {
username: {
regexp:/^1[3|4|5|6|7|8|9][0-9]\d{8}$/, //正则
aliasName:'手机号码错误' //输出提示
}
}
}
//此时如果正则检验失败则会返回输出提示手机号码错误
案例3
module.exports = class extends think.Logic {
indexAction(){
this.rules = {
username: {
validator:(rule, value,data)=>{
return value==123
}, //自定义方法
aliasName:'检验失败' //输出提示
}
}
}
//此时如果正则检验失败则会返回输出提示检验失败
/**
* 可调用值
* @param {obj} rule 当前校验字段在 rules 中所对应的校验规则
* @param {String} value 当前校验字段的值
* @param {obj} data 获取所以字段值
* @return {Boolean} 输出结果,true为通过,false为不通过
*/
注意事项
1.required会在提示后面加上不能为空。
2.validator和regexp需要传入aliasName输出检验提示,否则显示为字段名。
3.其他方法则按框架默认提示。
# Redis
开发中。。
# 通信功能
# Socket.IO
一、使用Socket.IO之前请确定项目中是否安装了这两个依赖包
npm install think-websocket
npm install think-websocket-socket.io
二、src/config/adapter.js 中配置如下
const socketio = require('think-websocket-socket.io');
exports.websocket = {
type: 'socketio',
common: {
// common config 配置通用的一些参数,项目启动时会跟具体的 adapter 参数作合并
},
socketio: {
handle: socketio,
// allowOrigin:'127.0.0.1:8360', // 默认所有的域名都允许访问
path: '/socket.io', // 默认 '/socket.io'
adapter: null, // 默认无 adapter
messages: {
open: '/common/socket/open', //建立连接时处理对应到 websocket Controller 下的 open Action
close: '/common/socket/close', // 关闭连接时处理的 Action
addUser: '/common/socket/addUser', // addUser 事件处理的 Action
}
}
}
// 其中 open 和 close 事件名固定,表示建立连接和断开连接的事件,
//其他事件均为自定义,项目里可以根据需要添加
/**
* 可传入值
* @param {String} allowOrigin 默认情况下 socket.io 允许所有域名的访问。可配置
* @param {String} path 设置被 socket.io 处理的路径,默认为 /socket.io
* @param {String} adapter 使用多节点来部署 WebSocket 时,多节点之间可以借助 Redis
* 进行通信,这时可以设置 adapter 来实现。
* @param {String} messages 响应事件处理
*/
三、src/config/extend.js 中配置如下
const websocket = require('think-websocket');
module.exports = [
websocket(think.app),
// ...
];
四、src/config/router.js中配置如下
module.exports = [
['/common/socket/:action','/common/socket/:action','get,websocket'],
];
五、编写控制层文件
module.exports = class extends think.Controller {
constructor(...arg) {
super(...arg);
}
openAction() {
console.log('开启了');
// this.emit('opend', 'This client opened successfully!')
// this.broadcast('joined', 'There is a new client joined successfully!')
}
openAction() {
console.log('关闭了');
// this.emit('opend', 'This client opened successfully!')
// this.broadcast('joined', 'There is a new client joined successfully!')
}
addUserAction() {
console.log('获取客户端 addUser 事件发送的数据', this.wsData);
// console.log('获取当前 WebSocket 对象', this.websocket);
console.log('判断当前请求是否是 WebSocket 请求', this.isWebsocket);
}
}
六、常用命令
//通过messages触发的事件可以通过下面方法 发送事件
this.emit('opend', 'This client opened successfully!')
//在 Action 里可以通过 this.ctx.req.io/this.ctx.app.websocket.io 来获取 io 对象,
//该对象为 socket.io 的一个实例
const socket = this.ctx.app.websocket.io;
socket.emit('carNum',"This client opened successfully!");
// 监听
const socket = this.ctx.app.websocket.io;
socket.on('test', function(socket){
console.log('a user connected');
});
// 关闭监听
const socket = this.ctx.app.websocket.io;
socket.off('test', function(socket){
console.log('a user connected');
});
// broadcast方法给所有的 socket 广播事件
const socket = this.ctx.app.websocket.io;
socket.broadcast('test', function(socket){
console.log('a user connected');
});
# mqtt
# 1.使用mqtt作为服务端
由于实际项目中很少使用该项目作为服务端,所以本文只介绍一些比较简单的使用方法,更多使用方法见mosca (opens new window)
一、安装mqtt 服务器必要依赖项 mosca
npm install mosca
二、在根目录下创建 script/mosca.js,并编写如下代码。
let mosca = require('mosca');
let settings = {
port: 5112, //mqtt端口号
};
let server = new mosca.Server(settings);
// 监听启动
server.on('ready', function() {
// 判断用户密码
server.authenticate = function(client, username, password, callback) {
var authorized = (username === 'alice' && password.toString() === 'secret');
if (authorized) client.user = username;
callback(null, authorized);
}
console.log('Mosca server is up and running');
});
server.on('clientConnected', function(client) {
console.log('Client connected: ', client.id);
});
server.on('clientDisconnected', function(client) {
console.log('Client disconnected: ', client.id);
});
// 事件监听
server.on('published', function(packet, client) {
console.log(packet.payload.toString());
console.log('Published: ', packet.payload);
});
三、在 package.json中添加一条新的启动命令。
"scripts": {
...
"mosca": "node script/mosca.js",
},
四、打开一个新的命令窗口启动
npm run mosca
注意事项
若启动报错报错SchemaError: Expected schema to be an object or boolean有以下两种解决方法。
1.找到错误地址所在文件,将错误的的三行代码进行注释。
2.将jsonschema版本安装为1.2.6
# 2.使用mqtt作为客户端
一、 安装依赖
npm install mqtt
二、创建连接
let mqtt = require('mqtt')
module.exports = class extends think.Controller {
async listAction() {
const host = '127.0.0.1'
const port = '5112'
const clientId = `mqtt_${Math.random().toString(16).slice(3)}`
const connectUrl = `mqtt://${host}:${port}`
// 基础配置
const client = mqtt.connect(connectUrl, {
clientId: clientId ,
clean: true,
connectTimeout: 4000,
username: 'alice',
password: 'secret',
reconnectPeriod: 1000,
})
// 监听连接
client.on('connect', () => {
console.log('连接成功')
})
// 发布mqtt消息
client.publish('callNum', '32222222')
// 订阅消息
client.subscribe('topic1', function (err) {
if (!err) {
client.on('message', function (topic, message) {
// message is Buffer
console.log(message.toString())
})
}
})
// 取消订阅
client.unsubscribe('topic1');
// 关闭连接
client.end(function () {
console.log('disconnected');
});
return this.success("data");
}
};