2021SC@SDUSC
此次分析文件src/model/csmwing/action.js
该文件中的方法主要是对行为进行处理。
一、get_action()
此方法用于获取行为数据。
此方法有两个参数,参数 id 是行为id,参数 field 是需要获取的字段。
方法 think.isEmpty(obj) 用于判断参数是否为空,方法 think.isNumberString(str) 用于判断输入是不是一个字符串类型的数字。
id 需不为空且为字符串类型的数字。首先对 id 进行判断,若不满足以上任意一点,方法无法执行,返回false。
通过 map 限定的条件,在模型中查找数据。若 field 即需要获取的字段不为空,返回查找到的数据中的字段;若 field 为空,返回完整数据。
async get_action(id, field) {
id = id || null, field = field || null;
if (think.isEmpty(id) && !think.isNumberString(id)) {
return false;
}
const list = {};
const map = {'status': ['>', -1], 'id': id};
list[id] = await this.where(map).field(true).find();
return think.isEmpty(field) ? list[id] : list[id][field];
}
二、log()
此方法用于记录行为日志,并执行该行为的规则。
此方法有六个参数,参数 action 是行为标识,参数 model 是触发行为的模型名,参数 record_id 是触发行为的记录id,参数 user_id 是执行行为的用户id。
首先进行参数检查。若 action、model、record_id 任意一个为空,方法无法执行,返回字符串 ‘参数不能为空’。
查询行为,判断行为是否执行。若行为状态参数不为1,说明行为未执行,返回字符串 ‘该行为被禁用’。随后插入行为日志并解析行为规则,生成日志备注。若未定义日志规则,日志中会记录操作url。
async log(action, model, record_id, user_id, ip, url) {
if (think.isEmpty(action) || think.isEmpty(model) || think.isEmpty(record_id)) {
return '参数不能为空';
}
if (think.isEmpty(user_id)) {
const user = await think.session('userInfo');
const id = user.id;
user_id = id;
}
const action_info = await this.where({name: action}).find();
if (action_info.status != 1) {
return '该行为被禁用';
}
const data = {
action_id: action_info.id,
user_id: user_id,
action_ip: _ip2int(ip),
model: model,
record_id: record_id,
create_time: new Date().valueOf()
};
data.remark = '';
if (!think.isEmpty(action_info.log)) {
const match = action_info.log.match(/\[(\S+?)\]/g);
if (!think.isEmpty(match)) {
const log = {
user: user_id,
record: record_id,
model: model,
time: new Date().valueOf(),
data: {
user: user_id,
record: record_id,
model: model,
time: new Date().valueOf()
}
};
const replace = [];
for (let val of match) {
val = val.replace(/(^\[)|(\]$)/g, '');
const param = val.split('|');
if (!think.isEmpty(param[1])) {
if (param[0] == 'user') {
replace.push(await call_user_func(param[1], log[param[0]]));
} else {
replace.push(call_user_func(param[1], log[param[0]]));
}
} else {
replace.push(log[param[0]]);
}
}
data.remark = str_replace(match, replace, action_info.log);
} else {
data.remark = action_info.log;
}
} else {
data.remark = '操作url:' + url;
}
if (!think.isNumber(record_id)) {
data.record_id = 0;
}
await this.model('action_log').add(data);
if (!think.isEmpty(action_info.rule)) {
const rules = await this.parse_action(action, user_id);
const res = await this.execute_action(rules, action_info.id, user_id);
}
}
三、parse_action()
此方法用于解析行为规则。
此方法共有两个参数,参数 action 是行为id或者name,参数 self 用于替换规则里的变量为执行用户的id。若解析出错,返回布尔值false;成功返回规则数组。
下面是对项目中规则定义和规则字段的解释。
规则定义 table:table|field:field|condition:condition|rule:rule[|cycle:cycle|max:max][;…]
规则字段解释:
table->要操作的数据表,不需要加表前缀;
field->要操作的字段;
condition->操作的条件,目前支持字符串,默认变量 ${self} 为执行行为的用户;
rule->对字段进行的具体操作,目前支持加或者减,如:10,-10;
cycle->执行周期,单位(小时),表示cycle小时内最多执行max次;
max->单个周期内的最大执行次数(cycle和max必须同时定义,否则无效)。
单个行为后可加 ; 连接其他规则
首先确认行为不为空。若行为为空,方法无法执行,返回false。
查询行为信息。这里的行为信息仍通过设置一个 map 来进行查询,在数据库中查找到的 id 为 action、name 为 action 的数据即为我们要查找的信息。将查找到的信息赋值给 info。
解析规则。方法 str_replace() 是在文件 src/bootstrap/global.js 中定义的方法,在这里用来将规则 rules 中的参数 self 设置为此方法中传入的参数 self,即执行用户的id。for 循环通过 split 方法将规则按照字符 “|” 和 “:” 分隔开,并push到数组变量 ret 中。
方法最后返回 ret ,即解析完成的规则。
async parse_action(action, self) {
if (think.isEmpty(action)) {
return false;
}
let map;
if (think.isNumberString(action)) {
map = {'id': action};
} else {
map = {'name': action};
}
const info = await this.where(map).find();
if (!info || info.status != 1) {
return false;
}
let rules = info.rule;
rules = str_replace('${self}', self, rules);
rules = rules.split(';');
const ret = [];
for (const val of rules) {
if (val) {
const obj = {};
const rule = val.split('|');
for (const v of rule) {
const field = think.isEmpty(v) ? [] : v.split(':');
console.log(field);
if (!think.isEmpty(field)) {
obj[field[0]] = field[1];
}
}
ret.push(obj);
}
}
return ret;
}
四、execute_action()
此方法用于执行行为。
此方法有三个参数,参数 rules 是解析后的规则数组,参数 action_id 是行为id,参数 user_id 是执行的用户id。若执行成功返回布尔值 true;执行失败返回布尔值 false。
首先确认规则可执行,行为id和执行的用户id不为空。若以上任意条件不满足,方法无法执行,返回false。
对规则数组中的每一条规则进行执行周期的检查。若规则不处于执行周期内,那么for循环内执行 continue 语句,跳过该规则转而检查下一条规则。若规则处于执行周期内,那么执行规则要求的数据库操作。
async execute_action(rules, action_id, user_id) {
if (!rules || think.isEmpty(action_id) || think.isEmpty(user_id)) {
return false;
}
let ret = true;
for (const rule of rules) {
const map = {
action_id: action_id,
user_id: user_id
};
if (!think.isEmpty(rule.cycle) && !think.isEmpty(rule.max)) {
map.create_time = ['>', new Date().valueOf() - rule.cycle * 3600 * 1000];
const exec_count = await this.model('action_log').where(map).count();
if (exec_count > rule.max) {
continue;
}
}
const model = this.model(rule.table);
const field = rule.field;
let step = parseInt(rule.rule);
let res;
if (step >= 0) {
res = await model.where(rule.condition).increment(field, step);
} else {
step = Math.abs(step);
res = await model.where(rule.condition).decrement(field, step);
}
if (!res) {
ret = false;
}
}
return ret;
}