diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index 5064fe1c4..92b20da94 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -592,13 +592,15 @@ class DialogController extends AbstractController Base::checkClientVersion('0.19.0'); $user = User::auth(); // - $chat_information = Base::settingFind('system', 'chat_information'); - if ($chat_information == 'required') { - if (empty($user->getRawOriginal('nickname'))) { - return Base::retError('请设置昵称', [], -2); - } - if (empty($user->getRawOriginal('tel'))) { - return Base::retError('请设置联系电话', [], -3); + if (!$user->bot) { + $chatInformation = Base::settingFind('system', 'chat_information'); + if ($chatInformation == 'required') { + if (empty($user->getRawOriginal('nickname'))) { + return Base::retError('请设置昵称', [], -2); + } + if (empty($user->getRawOriginal('tel'))) { + return Base::retError('请设置联系电话', [], -3); + } } } // diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 0c9e73bf5..d1190ad6d 100755 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -365,7 +365,8 @@ class UsersController extends AbstractController * * @apiParam {Object} keys 搜索条件 * - keys.key 昵称、邮箱关键字 - * - keys.disable 0-排除禁止(默认),1-含禁止,2-仅禁止 + * - keys.disable 0-排除禁止(默认),1-仅禁止,2-含禁止 + * - keys.bot 0-排除机器人(默认),1-仅机器人,2-含机器人 * - keys.project_id 在指定项目ID * - keys.no_project_id 不在指定项目ID * - keys.dialog_id 在指定对话ID @@ -403,9 +404,14 @@ class UsersController extends AbstractController } if (intval($keys['disable']) == 0) { $builder->whereNull("disable_at"); - } elseif (intval($keys['disable']) == 2) { + } elseif (intval($keys['disable']) == 1) { $builder->whereNotNull("disable_at"); } + if (intval($keys['bot']) == 0) { + $builder->where("bot", 0); + } elseif (intval($keys['bot']) == 1) { + $builder->where("bot", 1); + } if ($updatedTime > 0) { $builder->where("updated_at", ">=", Carbon::createFromTimestamp($updatedTime)); } @@ -505,6 +511,10 @@ class UsersController extends AbstractController * - yes: 已认证 * - no: 未认证 * - 其他值: 全部(默认) + * - keys.bot 是否包含机器人 + * - yes: 仅机器人 + * - all: 全部 + * - 其他值: 非机器人(默认) * - keys.department 部门ID(0表示默认部门,不赋值获取所有部门) * - keys.checkin_mac 签到mac地址(get_checkin_mac=1时有效) * @@ -569,6 +579,11 @@ class UsersController extends AbstractController } elseif ($keys['email_verity'] === 'no') { $builder->whereEmailVerity(0); } + if ($keys['bot'] === 'yes') { + $builder->where('bot', 1); + } elseif ($keys['bot'] !== 'all') { + $builder->where('bot', 0); + } if (isset($keys['department'])) { if ($keys['department'] == '0') { $builder->where(function($query) { @@ -585,6 +600,7 @@ class UsersController extends AbstractController } } else { $builder->whereNull('disable_at'); + $builder->where('bot', 0); } $list = $builder->orderByDesc('userid')->paginate(Base::getPaginate(50, 20)); // diff --git a/app/Models/User.php b/app/Models/User.php index 503c423dd..e49d62eac 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -33,6 +33,7 @@ use Carbon\Carbon; * @property string|null $created_ip 注册IP * @property string|null $disable_at 禁用时间(离职时间) * @property int|null $email_verity 邮箱是否已验证 + * @property int|null $bot 是否机器人 * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @method static \Database\Factories\UserFactory factory(...$parameters) @@ -40,6 +41,7 @@ use Carbon\Carbon; * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() * @method static \Illuminate\Database\Eloquent\Builder|User query() * @method static \Illuminate\Database\Eloquent\Builder|User whereAz($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereBot($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereChangepass($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedIp($value) @@ -77,7 +79,7 @@ class User extends AbstractModel public static $defaultAvatarMode = 'auto'; // 基本信息的字段 - public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'az', 'pinyin', 'line_at', 'disable_at']; + public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at']; /** * 更新数据校验 @@ -109,17 +111,7 @@ class User extends AbstractModel */ public function getUserimgAttribute($value) { - if ($value && !str_contains($value, 'avatar/')) { - // 自定义头像 - return Base::fillUrl($value); - } else if (self::$defaultAvatarMode === 'auto') { - // 自动生成头像 - return url("avatar/" . urlencode($this->nickname) . ".png"); - } else { - // 系统默认头像 - $name = ($this->userid - 1) % 21 + 1; - return url("images/avatar/default_{$name}.png"); - } + return self::getAvatar($this->userid, $value, $this->email, $this->nickname); } /** @@ -171,7 +163,7 @@ class User extends AbstractModel */ public function getOnlineStatus() { - $online = intval(Cache::get("User::online:" . $this->userid, 0)); + $online = $this->bot || intval(Cache::get("User::online:" . $this->userid, 0)) > 0; if ($online) { return true; } @@ -432,9 +424,12 @@ class User extends AbstractModel if ($authInfo['userid'] > 0) { $loginValid = floatval(Base::settingFind('system', 'loginValid')) ?: 720; $loginValid *= 3600; - if ($authInfo['timestamp'] + $loginValid > time()) { + if ($authInfo['timestamp'] + $loginValid > time() || $authInfo['timestamp'] === -1) { $row = self::whereUserid($authInfo['userid'])->whereEmail($authInfo['email'])->whereEncrypt($authInfo['encrypt'])->first(); if ($row) { + if (!$row->bot && $authInfo['timestamp'] === -1) { + return $_A["__static_auth"] = false; // 非机器人token时间不允许-1 + } $upArray = []; if (Base::getIp() && $row->line_ip != Base::getIp()) { $upArray['line_ip'] = Base::getIp(); @@ -461,7 +456,8 @@ class User extends AbstractModel */ public static function token($userinfo) { - $userinfo->token = base64_encode($userinfo->userid . '#$' . $userinfo->email . '#$' . $userinfo->encrypt . '#$' . time() . '#$' . Base::generatePassword(6)); + $time = $userinfo->bot ? -1 : time(); + $userinfo->token = base64_encode($userinfo->userid . '#$' . $userinfo->email . '#$' . $userinfo->encrypt . '#$' . $time . '#$' . Base::generatePassword(6)); unset($userinfo->encrypt); unset($userinfo->password); return $userinfo->token; @@ -538,6 +534,37 @@ class User extends AbstractModel } } + /** + * 获取头像 + * @param $userid + * @param $userimg + * @param $email + * @param $nickname + * @return string + */ + public static function getAvatar($userid, $userimg, $email, $nickname) + { + // 自定义头像 + if ($userimg && !str_contains($userimg, 'avatar/')) { + return Base::fillUrl($userimg); + } + // 机器人头像 + if ($email == 'system-msg@bot.system') { + return url("images/avatar/default_system.png"); + } elseif ($email == 'task-alert@bot.system') { + return url("images/avatar/default_task.png"); + } elseif ($email == 'bot-manager@bot.system') { + return url("images/avatar/default_bot.png"); + } + // 生成文字头像 + if (self::$defaultAvatarMode === 'auto') { + return url("avatar/" . urlencode($nickname) . ".png"); + } + // 系统默认头像 + $name = ($userid - 1) % 21 + 1; + return url("images/avatar/default_{$name}.png"); + } + /** * 检测密码策略是否符合 * @param $password @@ -568,4 +595,51 @@ class User extends AbstractModel } } } + + /** + * 获取机器人或创建 + * @param $key + * @param $update + * @param $userid + * @return self + */ + public static function botGetOrCreate($key, $update = [], $userid = 0) + { + $email = "{$key}@bot.system"; + $botUser = self::whereEmail($email)->first(); + if (empty($botUser)) { + $encrypt = Base::generatePassword(6); + $botUser = self::createInstance([ + 'bot' => 1, + 'encrypt' => $encrypt, + 'email' => $email, + 'password' => Base::md52(Base::generatePassword(32), $encrypt), + 'created_ip' => Base::getIp(), + ]); + $botUser->save(); + if ($userid > 0) { + UserBot::createInstance([ + 'userid' => $userid, + 'bot_id' => $botUser->userid, + ])->save(); + } + // + if ($key === 'system-msg') { + $update['nickname'] = '系统消息'; + } elseif ($key === 'task-alert') { + $update['nickname'] = '任务提醒'; + } elseif ($key === 'bot-manager') { + $update['nickname'] = '机器人管理'; + } + } + if ($update) { + $botUser->updateInstance($update); + if (isset($update['nickname'])) { + $botUser->az = Base::getFirstCharter($botUser->nickname); + $botUser->pinyin = Base::cn2pinyin($botUser->nickname); + } + $botUser->save(); + } + return $botUser; + } } diff --git a/app/Models/UserBot.php b/app/Models/UserBot.php new file mode 100644 index 000000000..4c03d3628 --- /dev/null +++ b/app/Models/UserBot.php @@ -0,0 +1,26 @@ +dialog_user = null; $this->group_info = null; $this->top_at = $this->top_at ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('top_at'); + $this->bot = 0; switch ($this->type) { case "user": $dialog_user = WebSocketDialogUser::whereDialogId($this->id)->where('userid', '!=', $userid)->first(); @@ -96,6 +97,7 @@ class WebSocketDialog extends AbstractModel $basic = User::userid2basic($dialog_user->userid); if ($basic) { $this->name = $basic->nickname; + $this->bot = $basic->bot; } else { $this->name = 'non-existent'; $this->dialog_delete = 1; diff --git a/app/Models/WebSocketDialogUser.php b/app/Models/WebSocketDialogUser.php index acf7c1177..1f48cf039 100644 --- a/app/Models/WebSocketDialogUser.php +++ b/app/Models/WebSocketDialogUser.php @@ -11,7 +11,7 @@ namespace App\Models; * @property string|null $top_at 置顶时间 * @property int|null $mark_unread 是否标记为未读:0否,1是 * @property int|null $inviter 邀请人 - * @property int|null $important 是否不可移出(项目、任务人员) + * @property int|null $important 是否不可移出(项目、任务、部门人员) * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery() diff --git a/app/Module/Base.php b/app/Module/Base.php index ab83bccd2..151597c64 100755 --- a/app/Module/Base.php +++ b/app/Module/Base.php @@ -1838,7 +1838,7 @@ class Base $onlineip = '0,0,0,0'; } preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $onlineip, $match); - $_A["__static_ip"] = $match[0] ?: 'unknown'; + $_A["__static_ip"] = $match ? ($match[0] ?: 'unknown') : ''; } return $_A["__static_ip"]; } diff --git a/app/Tasks/AppPushTask.php b/app/Tasks/AppPushTask.php index e10028c88..86709c7dc 100644 --- a/app/Tasks/AppPushTask.php +++ b/app/Tasks/AppPushTask.php @@ -5,16 +5,15 @@ namespace App\Tasks; use App\Models\ProjectTask; use App\Models\ProjectTaskPushLog; use App\Models\User; +use App\Models\WebSocketDialog; +use App\Models\WebSocketDialogMsg; use App\Module\Base; use Carbon\Carbon; -use Hhxsv5\LaravelS\Swoole\Task\Task; @error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); class AppPushTask extends AbstractTask { - protected $endArray = []; - public function __construct() { parent::__construct(); @@ -73,9 +72,7 @@ class AppPushTask extends AbstractTask public function end() { - foreach ($this->endArray as $task) { - Task::deliver($task); - } + } /** @@ -95,7 +92,17 @@ class AppPushTask extends AbstractTask return; } + $botUser = User::botGetOrCreate('task-alert'); + if (empty($botUser)) { + return; + } + $setting = Base::setting('appPushSetting'); + $text = view('push.task', [ + 'type' => str_replace([0, 1, 2], ['start', 'before', 'after'], $type), + 'task' => $task, + 'setting' => $setting, + ])->render(); /** @var User $user */ foreach ($users as $user) { @@ -108,25 +115,12 @@ class AppPushTask extends AbstractTask if ($pushLog) { continue; } - $title = match ($type) { - 1 => "任务提醒", - 2 => "任务过期提醒", - default => "任务开始提醒", - }; - $body = view('push.task', [ - 'type' => str_replace([0, 1, 2], ['start', 'before', 'after'], $type), - 'user' => $user, - 'task' => $task, - 'setting' => $setting, - ])->render(); - $this->endArray[] = new PushUmengMsg($data['userid'], [ - 'title' => $title, - 'body' => $body, - 'description' => "TID:{$data['task_id']}", - 'seconds' => 3600, - 'badge' => 1, - ]); - ProjectTaskPushLog::createInstance($data)->save(); + // + $dialog = WebSocketDialog::checkUserDialog($botUser->userid, $data['userid']); + if ($dialog) { + ProjectTaskPushLog::createInstance($data)->save(); + WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid); // todo 未能在任务end事件来发送任务 + } } } } diff --git a/app/Tasks/BotReceiveMsgTask.php b/app/Tasks/BotReceiveMsgTask.php new file mode 100644 index 000000000..17e95fb08 --- /dev/null +++ b/app/Tasks/BotReceiveMsgTask.php @@ -0,0 +1,250 @@ +userid = $userid; + $this->msgId = $msgId; + } + + public function start() + { + $botUser = User::whereUserid($this->userid)->whereBot(1)->first(); + if (empty($botUser)) { + return; + } + $msg = WebSocketDialogMsg::find($this->msgId); + if (empty($msg)) { + return; + } + $msg->readSuccess($botUser->userid); + // + $dialog = WebSocketDialog::find($msg->dialog_id); + if (empty($dialog)) { + return; + } + if ($dialog->type !== 'user') { + return; + } + if ($botUser->email === 'bot-manager@bot.system') { + $this->botManagerReceive($msg); + } + } + + public function end() + { + + } + + /** + * 机器人管理处理消息 + * @param WebSocketDialogMsg $msg + * @return void + */ + private function botManagerReceive(WebSocketDialogMsg $msg) + { + if ($msg->type === 'text') { + $text = trim(strip_tags($msg->msg['text'])); + if (empty($text)) { + return; + } + $array = Base::newTrim(explode(" ", "{$text} ")); + $type = $array[0]; + $data = []; + $notice = ""; + switch ($type) { + /** + * 列表 + */ + case '/list': + $data = User::select(['users.*']) + ->join('user_bots', 'users.userid', '=', 'user_bots.bot_id') + ->where('users.bot', 1) + ->where('user_bots.userid', $msg->userid) + ->take(50) + ->orderByDesc('id') + ->get(); + if ($data->isEmpty()) { + $type = "notice"; + $notice = "您没有创建机器人。"; + } + break; + + /** + * 创建 + */ + case '/newbot': + if (User::select(['users.*']) + ->join('user_bots', 'users.userid', '=', 'user_bots.bot_id') + ->where('users.bot', 1) + ->where('user_bots.userid', $msg->userid) + ->count() >= 50) { + $type = "notice"; + $notice = "超过最大创建数量。"; + break; + } + if (strlen($array[1]) < 2 || strlen($array[1]) > 20) { + $type = "notice"; + $notice = "机器人名称由2-20个字符组成。"; + break; + } + $data = User::botGetOrCreate("user-" . Base::generatePassword(), [ + 'nickname' => $array[1] + ], $msg->userid); + if (empty($data)) { + $type = "notice"; + $notice = "创建失败。"; + break; + } + $dialog = WebSocketDialog::checkUserDialog($data->userid, $msg->userid); + if ($dialog) { + $text = "你好,我是你的机器人:{$data->nickname}, 我的机器人ID是:{$data->userid}"; + WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $data->userid); // todo 未能在任务end事件来发送任务 + } + break; + + /** + * 修改名字 + */ + case '/setname': + if (strlen($array[2]) < 2 || strlen($array[2]) > 20) { + $type = "notice"; + $notice = "机器人名称由2-20个字符组成。"; + break; + } + $data = $this->botManagerOne($array[1], $msg->userid); + if ($data) { + $data->nickname = $array[2]; + $data->az = Base::getFirstCharter($array[2]); + $data->pinyin = Base::cn2pinyin($array[2]); + $data->save(); + } else { + $type = "notice"; + $notice = "机器人不存在。"; + } + break; + + + /** + * 删除 + */ + case '/deletebot': + $data = $this->botManagerOne($array[1], $msg->userid); + if ($data) { + $data->deleteUser('delete bot'); + } else { + $type = "notice"; + $notice = "机器人不存在。"; + } + break; + + /** + * 获取Token + */ + case '/token': + $data = $this->botManagerOne($array[1], $msg->userid); + if ($data) { + User::token($data); + } else { + $type = "notice"; + $notice = "机器人不存在。"; + } + break; + + /** + * 更新Token + */ + case '/revoke': + $data = $this->botManagerOne($array[1], $msg->userid); + if ($data) { + $data->encrypt = Base::generatePassword(6); + $data->password = Base::md52(Base::generatePassword(32), $data->encrypt); + $data->save(); + } else { + $type = "notice"; + $notice = "机器人不存在。"; + } + break; + + /** + * 会话搜索 + */ + case '/dialog': + $data = $this->botManagerOne($array[1], $msg->userid); + if ($data) { + $list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread']) + ->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id') + ->where('web_socket_dialogs.name', 'LIKE', "%{$array[2]}%") + ->where('u.userid', $data->userid) + ->orderByDesc('u.top_at') + ->orderByDesc('web_socket_dialogs.last_at') + ->take(20) + ->get(); + if ($list->isEmpty()) { + $type = "notice"; + $notice = "没有搜索到相关会话。"; + } else { + $list->transform(function (WebSocketDialog $item) use ($data) { + return $item->formatData($data->userid); + }); + $data->list = $list; + } + } else { + $type = "notice"; + $notice = "机器人不存在。"; + } + break; + } + // + $text = view('push.bot', [ + 'type' => $type, + 'data' => $data, + 'notice' => $notice, + 'version' => Base::getVersion() + ])->render(); + $text = preg_replace("/^\x20+/", "", $text); + $text = preg_replace("/\n\x20+/", "\n", $text); + WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $this->userid); // todo 未能在任务end事件来发送任务 + } + } + + /** + * @param $botId + * @param $userid + * @return User + */ + private function botManagerOne($botId, $userid) + { + $botId = intval($botId); + $userid = intval($userid); + if ($botId > 0) { + return User::select(['users.*']) + ->join('user_bots', 'users.userid', '=', 'user_bots.bot_id') + ->where('users.bot', 1) + ->where('user_bots.bot_id', $botId) + ->where('user_bots.userid', $userid) + ->first(); + } + return null; + } +} diff --git a/app/Tasks/WebSocketDialogMsgTask.php b/app/Tasks/WebSocketDialogMsgTask.php index ceb937225..833855fb4 100644 --- a/app/Tasks/WebSocketDialogMsgTask.php +++ b/app/Tasks/WebSocketDialogMsgTask.php @@ -5,6 +5,7 @@ namespace App\Tasks; @error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); use App\Models\User; +use App\Models\UserBot; use App\Models\WebSocketDialog; use App\Models\WebSocketDialogMsg; use App\Models\WebSocketDialogMsgRead; @@ -120,6 +121,11 @@ class WebSocketDialogMsgTask extends AbstractTask 'mention' => $mention, ])->saveOrIgnore(); $array[$userid] = $mention; + // 机器人收到消处理 + $botUser = User::whereUserid($userid)->whereBot(1)->first(); + if ($botUser) { + $this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id); + } } } // 更新已发送数量 diff --git a/database/migrations/2022_12_17_111737_create_user_bots_table.php b/database/migrations/2022_12_17_111737_create_user_bots_table.php new file mode 100644 index 000000000..815033a77 --- /dev/null +++ b/database/migrations/2022_12_17_111737_create_user_bots_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->bigInteger('userid')->nullable()->default(0)->comment('所属人ID'); + $table->bigInteger('bot_id')->nullable()->default(0)->comment('机器人ID'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_bots'); + } +} diff --git a/database/migrations/2022_12_17_123811_add_users_bot.php b/database/migrations/2022_12_17_123811_add_users_bot.php new file mode 100644 index 000000000..126d9abb4 --- /dev/null +++ b/database/migrations/2022_12_17_123811_add_users_bot.php @@ -0,0 +1,40 @@ +tinyInteger('bot')->nullable()->default(0)->after('email_verity')->comment('是否机器人'); + } + }); + if ($isAdd) { + User::botGetOrCreate('bot-manager'); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn("bot"); + }); + } +} diff --git a/resources/assets/js/components/UserAvatar.vue b/resources/assets/js/components/UserAvatar.vue index 3fde2f7c8..e885fed9a 100755 --- a/resources/assets/js/components/UserAvatar.vue +++ b/resources/assets/js/components/UserAvatar.vue @@ -3,7 +3,7 @@ v-if="user" class="common-avatar" :open-delay="openDelay" - :disabled="windowSmall || tooltipDisabled" + :disabled="windowSmall || tooltipDisabled || isBot" :placement="tooltipPlacement">