This page lists files in the current directory. You can view content, get download/execute commands for Wget, Curl, or PowerShell, or filter the list using wildcards (e.g., `*.sh`).
wget 'https://sme10.lists2.roe3.org/kodbox/plugins/msgWarning/controller/sys/notice.class.php'
<?php
/**
* 发送通知
* 短信、邮件添加到队列,第三方直接发送
*/
class msgWarningSysNotice extends Controller {
protected $pluginName;
protected $evntInfo;
public function __construct() {
parent::__construct();
$this->pluginName = 'msgWarningPlugin';
// TODO 暂不支持短信通知——需要申请模板
}
/**
* 按事件发送通知
* @param [type] $evntInfo
* @param [type] $msg
* @return void
*/
public function send($evntInfo) {
// 通知方式
$methods = _get($evntInfo, 'notice.method', '');
$methods = explode(',', $methods);
if (empty($methods)) return;
// 通知目标
// $target = _get($evntInfo, 'notice.target', array()); // 原始值
$target = _get($evntInfo, 'target', array());
if (empty($target)) return;
// 通知内容
$message = _get($evntInfo, 'message', '');
if (empty($message)) return;
// 通知日志相关数据
$logs = array(
'event' => $evntInfo['event'],
'title' => $evntInfo['title'],
'userID' => 0,
'method' => '',
'target' => '',
);
$this->evntInfo = $evntInfo;
foreach ($methods as $method) {
switch ($method) {
case 'email':
case 'sms':
$alias = array('email' => 'email', 'sms' => 'phone');
$check = $alias[$method];
// 获取用户列表
$where = array(
'userID' => array('in', $target),
$check => array('<>',''), // sms/email分别查询,无需用or
);
$users = Model('User')->where($where)->field('userID,name,nickName,email,phone')->select();
if (empty($users)) continue;
// 发送消息
$logs['method'] = $method;
foreach ($users as $user) {
$name = !empty($user['nickName']) ? $user['nickName'] : $user['name'];
$value = $user[$check];
if (empty($value)) continue;
// if (!Input::check($value, $check)) continue; // TODO 国外手机号检查不通过,暂不处理——实际发送时也会检查,此处无需处理
$logs['userID'] = $user['userID'];
$logs['target'] = $value;
// 发送消息
$user = array('name' => $name, $check => $value);
$func = 'by' . ucfirst($method); // bySms、byEmail
$this->$func($user, $message, $logs);
}
break;
case 'weixin':
case 'dding':
$logs['method'] = $method;
$logs['target'] = '';
$this->byThird($target, $message, $logs);
break;
}
}
TaskQueue::addSubmit();
}
/**
* 通过第三方(钉钉、企业微信)发送通知
* @param [type] $user
* @param [type] $content ['','']
* @param [type] $logs
* @return void
*/
public function byThird($user, $content, $logs){
// 获取有效发送用户;无效的写失败日志
$userDef = $user;
$this->filterThirdUsers($user);
$userDiff = array_diff($userDef, $user);
if (!empty($userDiff)) {
$logs['status'] = 0;
$logs['desc'] = LNG('msgWarning.main.invalidUser');
$this->addUsersLog($userDiff, $logs);
}
if (empty($user)) return;
// 发送消息
$typeArr = array(
'weixin' => 'weChat',
'dding' => 'dingTalk'
);
$title = $logs['title'];
$content = array_merge(array('**'.$title.'**'), $content);
$data = array(
'msg' => array(
'type' => 'markdown', // text、markdown
'title' => $title, // 企业微信用不上
'content' => implode("\n\n", $content) // 一个\n无效
),
'target' => array(
'user' => implode(',', $user)
),
'type' => $typeArr[$logs['method']]
);
$rest = Action('msgGatewayPlugin')->send($data);
// 记录日志
$logs['status'] = $rest['code'] ? 1 : 0;
$logs['desc'] = _get($rest, 'data', '');
$this->addUsersLog($user, $logs);
}
// 钉钉/企微 过滤非绑定用户——前面已经筛选过一次,这里没必要关联主表(user)查询
private function filterThirdUsers(&$user) {
$where = array(
'userID' => array('in', $user),
'key' => 'uid'
);
$data = Model('user_meta')->where($where)->field('userID,value')->select();
$data = array_to_keyvalue($data, '', 'userID');
$user = array_intersect($user, $data);
}
private function addUsersLog($user, $logs) {
foreach ($user as $userID) {
$logs['userID'] = $userID;
$this->addLog($logs);
}
}
/**
* 通过邮件发送通知
* @param [type] $user
* @param [type] $content ['','']
* @return void
*/
public function byEmail($user, $content, $logs){
$title = $logs['title'];
$content = array_merge(array($title), $content);
$systemName = Model('SystemOption')->get('systemName');
$data = array(
'type' => 'email',
'input' => $user['email'], // 邮箱地址
'action' => 'email_sys_notice',
'config' => array(
'address' => $user['email'],
'subject' => "[{$systemName}]".$title,
'content' => array(
'type' => 'notice',
'data' => array(
'user' => $user['name'],
'text' => $content // 数组
)
),
'system' => array( // 系统信息
'icon' => STATIC_PATH.'images/icon/fav.png',
'name' => $systemName,
'desc' => Model('SystemOption')->get('systemDesc')
),
)
);
return $this->addTaskQueue($user, $data, $logs);
}
/**
* 通过短信发送通知——暂不支持
* @param [type] $user
* @param [type] $content [msg]
* @return void
*/
public function bySms($user, $content, $logs) {
// content为变量名(模板中固定),对应内容长度不能超过35个字符
$content = array('content' => msubstr($content[0], 0, 35));
$data = array(
'type' => 'sms',
'input' => $user['phone'], // 手机号码
'action' => 'phone_sys_notice',
'config' => array(
'content' => array(
'type' => 'notice', // code/notice
'data' => $content, // 变量不能超过35个字符
'code' => 'xxxxxx', // TODO 模板ID,待完善
),
)
);
return $this->addTaskQueue($user, $data, $logs);
}
/**
* 旧消息发送添加到任务队列
* @return void
*/
public function oldTaskQueue(){
$key = $this->pluginName.'.msgQueue';
$cache = Cache::get($key);
if (!$cache) return;
$call = $this->pluginName.'.sys.notice.byQueue';
foreach ($cache as $i => $args) {
$rest = TaskQueue::add($call, $args);
if (!$rest) break; // 添加失败直接终止
unset($cache[$i]);
}
$cache = array_filter($cache);
Cache::set($key, $cache, 3600*24);
}
/**
* 消息发送添加到任务队列
* @param [type] $data 消息发送参数
* @return void
*/
public function addTaskQueue($user, $data, $logs){
$call = $this->pluginName.'.sys.notice.byQueue';
$args = array($user, $data, $logs);
$rest = TaskQueue::add($call, $args);
if ($rest) return;
// 添加失败,存入缓存,下次任务开始时读取然后继续添加
$key = $this->pluginName.'.msgQueue';
$cache = Cache::get($key);
if (!$cache) $cache = array();
$cache[] = $args;
Cache::set($key, $cache, 3600*24);
}
/**
* 通过任务队列发送sms、email消息
* @param [type] $user [name=>'',phone/email=>'']
* @param [type] $data 消息参数
* @param [type] $logs 日志参数
* @return void
*/
public function byQueue($user, $data, $logs){
// 发送消息
$func = $data['type']; // sms、email,不直接使用send,避免再次检测格式
$rest = Action('user.msg')->$func($data);
// 写入日志
$data = array(
'status' => $rest['code'] ? 1 : 0,
'desc' => _get($rest, 'data', ''),
);
$data = array_merge($logs, $data);
$this->addLog($data);
return;
}
/**
* 写入通知日志
* @param [type] $data
* @return void
*/
public function addLog($data) {
Action($this->pluginName)->loadLib('logs')->add($data);
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/plugins/msgWarning/controller/sys/storage.class.php'
<?php
/**
* 存储相关:通知任务检查、存储列表状态更新
*/
class msgWarningSysStorage extends Controller {
protected $pluginName;
protected $stCacheKey;
public function __construct() {
parent::__construct();
$this->pluginName = 'msgWarningPlugin';
$this->stCacheKey = 'io.error.list.get';
}
public function index() {
// return array();
}
/**
* 检查存储列表,返回不可用存储
* @param boolean $task 是否为计划任务调用,如果是,则不使用缓存
* @return void
*/
public function checkStoreList ($task=false) {
$cckey = $this->stCacheKey;
$cache = Cache::get($cckey);
if ($cache !== false && !$task) return $cache;
// 获取有数据的存储
$ids = Model('File')->field('ioType')->group('ioType')->select();
$ids = array_to_keyvalue($ids, '', 'ioType');
// 判断存储是否可访问
$model = Model('Storage');
$data = array();
$list = $model->driverListSystem();
foreach($list as $item) {
$id = $item['id'];
$driver = strtolower($item['driver']);
if (in_array($driver, array('baidu','onedrive'))) continue; // 百度、onedrive不检查
// 1.检查(网络)存储是否可访问
$chks = $this->checkStoreUrl($item);
// 2.实际检查存储是否可用
if ($chks === true) {
try {
$chks = $model->checkConfig($item,true);
} catch (Exception $e) {$chks = false;}
}
if ($chks !== true) {
unset($item['config']);
$item['sysdata'] = in_array($id, $ids) ? 1 : 0; // 是否含有系统数据
$data[$id] = $item;
}
}
// TODO 如果去掉taskFreq(频率跟随计划任务),则此处需要调整
Cache::set($cckey, $data); // 5分钟——不限定5分钟,由计划任务刷新(存储检查事件默认为5分钟)
return $data;
}
// 检查(网络)存储是否可访问
public function checkStoreUrl($info, $timeout = 10) {
$url = _get($info, 'config.domain', ''); // 对象存储
if (empty($url)) {
$url = _get($info, 'config.server', '');// ftp
}
if (empty($url)) {
$url = _get($info, 'config.host', ''); // webdav
}
if (empty($url)) return true;
$res = parse_url($url);
$port = (empty($res["port"]) || $res["port"] == '80')?'':':'.$res["port"];
$path = preg_replace("/\/+/","/",$res["path"]);
$opt = array();
// ftp额外处理:name/pass、默认端口
if (strtolower($info['driver']) == 'ftp') {
if (!$port) $port = ':21';
$opt[CURLOPT_USERPWD] = $info['config']['username'].':'.$info['config']['userpass'];
}
$url = _get($res,'scheme',http_type())."://".$res["host"].$port.$path;
return $this->url_request_check($url, $opt, $timeout);
}
// 检查地址是否可访问
private function url_request_check($url, $opt=false, $timeout = 10) {
if (!filter_var($url, FILTER_VALIDATE_URL)) {return false;}
$ch = curl_init($url);
$op = array(
CURLOPT_NOBODY => true, // 不获取响应体
CURLOPT_FOLLOWLOCATION => false, // 不跟随重定向——3xx
CURLOPT_TIMEOUT => $timeout,// 超时时间
CURLOPT_CONNECTTIMEOUT => $timeout,// 连接超时时间
CURLOPT_RETURNTRANSFER => true, // 返回响应体
CURLOPT_SSL_VERIFYPEER => false, // 忽略 SSL 验证
CURLOPT_HEADER => true, // 返回响应头
CURLOPT_USERAGENT => 'URL Accessibility Checker',
);
if ($opt) $op = $opt + $op;
curl_setopt_array($ch, $op);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// $error = curl_error($ch);
curl_close($ch);
// 非 0 状态码表示服务器已响应,说明地址可访问(包括4xx、5xx)
return ($response !== false && $httpCode > 0);
}
/**
* 存储管理(修改、删除)之后,清除缓存
* @param [type] $result
* @return void
*/
public function editAfter($result) {
if (!$result || !$result['code']) return;
$id = $this->in['id'];
if (!$id) return;
$cache = Cache::get($this->stCacheKey);
if (!$cache || !isset($cache[$id])) return;
// 删除当前存储的状态缓存
unset($cache[$id]);
Cache::set($this->stCacheKey, $cache);
}
/**
* 存储列表,检查状态
* @param [type] $list
* @return void
*/
public function listAfter($list) {
// if (!isset($this->in['usage'])) return; // 因为缓存的关系,并非每次都有该请求
$cache = Cache::get($this->stCacheKey);
if (!$cache) return; // TODO 没有的时候也许应该重新获取——但是注意耗时问题
foreach ($list as &$item) {
if (isset($cache[$item['id']])) {
$item['status'] = 0;
}
}
return $list;
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/plugins/msgWarning/controller/sys/task.class.php'
<?php
class msgWarningSysTask extends Controller {
public function __construct() {
parent::__construct();
$this->pluginName = 'msgWarningPlugin';
}
public function getConfig() {
return Action($this->pluginName)->getConfig();
}
// 更新同步计划任务
public function updateTask($status){
// 删除旧任务
$this->delTask($this->pluginName.'.warning');
// 任务不存在:状态为0,返回;否则新增
if(!$task = $this->getTask()) {
return $this->addTask($status);
}
// 任务已存在:更新
$data = array(
'name' => $task['name'],
'enable' => $status
);
Model('SystemTask')->update($task['id'], $data);
}
private function addTask($status){
$data = array (
'name' => LNG('msgWarning.meta.name'),
'type' => 'method',
'event' => $this->pluginName.'.autoTask',
'time' => '{"type":"minute","month":"1","week":"1","day":"08:00","minute":"1"}',
'desc' => LNG('msgWarning.main.taskDesc'),
'enable' => $status,
'system' => 1,
);
return Model('SystemTask')->add($data);
}
// 获取计划任务,通过id查找有问题(卸载时可能没有删除,导致无法查找也无法新增)
public function getTask($event=false){
if (!$event) $event = $this->pluginName.'.autoTask';
return Model('SystemTask')->findByKey('event', $event);
}
// 删除计划任务
public function delTask($event=false){
if(!$task = $this->getTask($event)) return;
Model('SystemTask')->remove($task['id'], true);
}
/**
* 获取通知事件列表,执行通知
* 计划任务定期获取通知消息,有前端方式则写入缓存;后端则发送消息通知;
* 前端通知:前端只从缓存中获取消息,获取后清除自己(直到下次计划任务再次写入);前端请求同计划任务,每分钟请求一次
* @return void
*/
public function notice($runTask){
if (!$runTask) $this->webNotice();
$plugin = Action($this->pluginName);
$ntcApi = $plugin->apiAct('sys', 'notice'); // 发送通知类
// 旧数据处理——重新写入队列
$ntcApi->oldTaskQueue();
// 给通知事件列表分类
$evntData = array();
$evntList = $plugin->loadLib('evnt')->listData();
foreach ($evntList as $evntInfo) {
if ($evntInfo['status'] != '1') continue;
$clsKey = $evntInfo['class'];
if (!isset($evntData[$clsKey])) {
$evntData[$clsKey] = array();
}
$evntData[$clsKey][] = $evntInfo;
}
unset($evntList);
// 通知消息缓存,用于前端获取
$wbcache = array();
// TODO 前端提醒好像有点问题,存储异常3分钟提醒一次
// 获取通知消息
foreach ($evntData as $clsKey => $evntList) {
$msgAct = $plugin->apiAct('msg', $clsKey); // 获取通知(内容)类
foreach ($evntList as $evntInfo) {
$event = $evntInfo['event'];
// 每次计划任务执行时,清空前端缓存——不能清空,清空则消息只保留1分钟,1分钟内没通知到的(没登录),就接收不到此次通知了
// $wbcache[$event] = array();
// 1.通知对象过滤
if (!$this->filterTarget($evntInfo)) continue;
// 更新任务执行时间
$this->updateNtcEvnt($evntInfo, 'tskTime');
// 2.获取通知消息
$message = $msgAct->index($evntInfo);
$tempMsg = Hook::trigger('msgWarning.msg.'.$event, $evntInfo); // 附加通知;eg: msgWarning.msg.sysStoreDefErr
if($tempMsg && is_array($tempMsg)){
$message = $tempMsg;
}
if (!$message) continue;
$evntInfo['message'] = $message;
// 检查通知频率,判断是否需要通知
$timeFreq = intval(_get($evntInfo, 'notice.timeFreq', 0));
$timeLast = intval(_get($evntInfo, 'result.ntcTime', 0));
if ($timeFreq < 1) $timeFreq = 1; // 时间间隔至少1分钟
if (($timeLast + ($timeFreq * 60)) > time()) continue;
// 更新通知时间、次数
$this->updateNtcEvnt($evntInfo, 'ntcTime');
// 有前端通知方式,则写入缓存,供前端请求获取
// 3.前端通知:写入缓存,前端用户只获取一次(清除自己的id);
$methods = explode(',', $evntInfo['notice']['method']);
$wbcache[$event] = $this->ntcCache($evntInfo, $methods);
// 4.计划任务通知
if (!array_intersect($methods, array('sms', 'email', 'weixin', 'dding', 'feishu'))) {
continue;
}
// 4.1 预警级+ 写入log日志
if ($evntInfo['level'] >= 3) {
write_log('【系统通知】'.$evntInfo['title'].': '.implode('; ', $message), 'warning');
}
// 4.2 发送通知
$ntcApi->send($evntInfo);
}
}
// 存前端缓存——未被覆盖的继续保留,用户23点登录系统接收到的可能是8点的通知
$cckey = $this->pluginName.'.webNtcList.'.date('Ymd');
$cache = Cache::get($cckey);
if (!is_array($cache)) $cache = array();
$cache = array_merge($cache, $wbcache);
Cache::set($cckey, $cache, 3600*24);
return true;
}
// 通知事件过滤
public function filterTarget(&$evntInfo){
// 是否启用
if (!$evntInfo['status']) return false;
$notice = _get($evntInfo, 'notice', array()); // 通知设置
$result = _get($evntInfo, 'result', array()); // 通知结果
$target = json_decode($notice['target'], true);
if (empty($target)) $target = array();
// 通知次数
$cntMax = intval(_get($notice, 'cntMax', 0));
$cntMaxDay = intval(_get($notice, 'cntMaxDay', 0));
if ($cntMax > 0 && intval($result['cntTotal']) >= $cntMax) return false;
if ($cntMaxDay > 0 && intval($result['cntToday']) >= $cntMaxDay) return false;
// 通知时段
$timeNow = time();
if (strtotime($notice['timeFrom']) > $timeNow || strtotime($notice['timeTo']) < $timeNow) return false;
// 任务执行频率——注意这里不是通知频率:事件任务执行后,如果有消息,再根据通知频率判断是否通知
$taskFreq = intval(_get($evntInfo, 'taskFreq', 0));
$timeLast = intval(_get($result, 'tskTime', 0));
// 频率为1分钟的不用检查,避免和计划任务冲突
if ($taskFreq > 1) {
if (($timeLast + ($taskFreq * 60)) > $timeNow) return false;
}
// 通知方式
if (empty($notice['method'])) return false;
// toAll:将[系统通知]添加到通知方式中——前端限制取消后,此处没有必要强制添加
if ($evntInfo['toAll'] == '1') {
if (stripos($notice['method'], 'kwarn') === false) {
$evntInfo['notice']['method'] .= ',kwarn';
}
}
// 通知对象
if (empty($target['user']) && empty($target['group'])) return false;
$users = $this->getTargetUsers($target);
if (empty($users)) return false;
$evntInfo['target'] = $users; // 实际通知对象,用于邮件短信;企微和钉钉需传原始值通知
return true;
}
// 通过form.userGroup获取用户id列表:{user:[1,2,3],group:[1,2,3]}
private function getTargetUsers($target) {
// 缓存不同条件下的部门用户
static $groupUsers = array();
$users = _get($target, 'user', array());
$group = _get($target, 'group', array());
if (empty($group)) return array_filter(array_unique($users));
// 获取部门下的用户:有根部门则为全部用户
$list = array();
sort($group);
if (in_array('1', $group)) {
if (isset($groupUsers['1'])) {
$list = $groupUsers['1'];
} else {
$list = Model('User')->where(array('status' => 1))->field('userID')->select();
$list = array_to_keyvalue($list, '', 'userID');
$groupUsers['1'] = $list;
}
} else {
$tmpKey = implode(',', $group);
if (isset($groupUsers[$tmpKey])) {
$list = $groupUsers[$tmpKey];
} else {
$list = Model('user_group')->alias('g')->field('u.userID')
->join('INNER JOIN user as u ON u.userID = g.userID AND u.status = 1')
->where(array('g.groupID' => array('in', $group)))
->select();
$list = array_to_keyvalue($list, '', 'userID');
$groupUsers[$tmpKey] = $list;
}
}
$users = array_merge($users, $list);
if (empty($users)) return array();
return array_filter(array_unique($users));
}
// 获取用于缓存的数据:{ktips:1,2,3,kwarn:1,2,3,msg:{title,level,message}}
private function ntcCache($evntInfo, $methods) {
$cache = array();
$users = implode(',', $evntInfo['target']);
if (in_array('ktips', $methods)) {
$cache['ktips'] = $users;
}
if (in_array('kwarn', $methods)) {
if ($evntInfo['toAll'] == '1') {
$users = 'all'; // 前端获取时,将当前用户id追加到后面,后续再获取时,如果已在其中则不提示
}
$cache['kwarn'] = $users;
}
if (!empty($cache)) {
$cache['msg'] = array(
'title' => $evntInfo['title'],
'level' => intval($evntInfo['level']),
'message' => $evntInfo['message'],
);
}
return $cache;
}
// 更新任务执行时间
// TODO 应该写一个批量更新的方法
private function updateNtcEvnt(&$evntInfo, $type) {
$time = time();
$evntInfo['result']['tskTime'] = $time;
if ($type == 'ntcTime') {
$evntInfo['result']['ntcTime'] = $time;
$evntInfo['result']['cntToday'] = intval(_get($evntInfo, 'result.cntToday', 0)) + 1;
$evntInfo['result']['cntTotal'] = intval(_get($evntInfo, 'result.cntTotal', 0)) + 1;
}
$update = array(
'event' => $evntInfo['event'],
'data' => array (
'result' => $evntInfo['result'],
)
);
Action($this->pluginName)->loadLib('evnt')->setConfig($update, true);
}
/**
* 前端通知
* @return void [ktips=>[event=>[title,level,message],...],kwarn=>[event=>[title,level,message],...]]
*/
public function webNotice() {
if(!defined('USER_ID') || !USER_ID) show_json(array());
$cckey = $this->pluginName.'.webNtcList.'.date('Ymd');
$cache = Cache::get($cckey);
if (empty($cache)) show_json(array());
$data = array();
foreach ($cache as $event => &$item) {
if (!$item) continue;
// kwarn通知范围为all时,获取并追加自己的id,后续再获取时,如果已存在则忽略
if (stripos($item['kwarn'], 'all') !== false) {
$users = explode(',', $item['kwarn']);
if (!in_array(USER_ID, $users)) {
$data['kwarn'][$event] = $item['msg'];
$item['kwarn'] .= ','.USER_ID;
}
continue;
}
foreach (array('ktips', 'kwarn') as $key) {
$users = explode(',', $item[$key]);
if (in_array(USER_ID, $users)) {
$data[$key][$event] = $item['msg'];
$users = array_diff($users, array(USER_ID));
$item[$key] = implode(',', $users);
}
}
}
unset($item);
if (empty($data)) show_json(array());
// 更新缓存
Cache::set($cckey, $cache, 3600*24);
show_json($data);
}
}