mirror of
https://github.com/kuaifan/dootask.git
synced 2026-06-11 18:02:22 +00:00
给消息待办增加可选「提醒时间」,到点由 todo-alert 机器人对原消息发起 reply、正文 @ 仍在群内的被指派成员,完全复用原生回复/提及链路(定向未读、 红点、绕过会话免打扰、App 推送);被指派人全部退群则跳过发送并标记已提醒。 设/改/取消提醒的权限沿用 todo_set_permission 开关与 checkTodoOwnerPermission。 后端: - 迁移:web_socket_dialog_msg_todos 增加 remind_at/reminded_at 及索引, 注册为日期字段 - WebSocketDialogMsgTodo::dueReminders() 选取到点(未提醒/未完成)待办(limit 500) - WebSocketDialogMsg::setTodoRemind() 纯数据写入(改时间重置 reminded_at), 接入 toggleTodoMsg($remindAt) 与 msg__todo 透传 - 接口 msg__todoremind 设置/修改/取消提醒(权限闸门、消息类型校验、 pushMsg 同步 todo_done) - TodoRemindTask 到点按消息发提醒(reminded_at 防重复、迟发补发、原消息/ 会话删除兜底),buildRemindText 生成 <span class="mention user"> 文本, 接入 crontab;登记 todo-alert 机器人 - msgJoinGroup 从提醒文本中提取被 @ 成员 前端: - 设待办弹窗新增「提醒时间」(预设 + 自定义 DatePicker) - 待办详情浮层每条待办可查看/修改/取消提醒:DatePicker on-clear「清空」 二次确认后取消,无时间时仅关闭面板不发请求 - 待办浮层窄屏(≤500px)改为 待办/完成 tab 切换,宽屏维持双列;列表为空 展示空状态占位;提醒时间用 Icon 替换 emoji - 时间读写对齐项目任务时间的时区约定 测试:tests/Feature/TodoRemindTest(数据/选取/写入/权限决策/buildRemindText/ text mention 提取),TodoSetPermissionTest 无回归。 任务 #124 后续增强。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87 lines
3.3 KiB
PHP
87 lines
3.3 KiB
PHP
<?php
|
||
|
||
namespace App\Tasks;
|
||
|
||
use App\Models\User;
|
||
use App\Models\WebSocketDialog;
|
||
use App\Models\WebSocketDialogMsg;
|
||
use App\Models\WebSocketDialogMsgTodo;
|
||
use App\Module\Doo;
|
||
use Carbon\Carbon;
|
||
|
||
/**
|
||
* 待办提醒:到点由 todo-alert 机器人在原会话发一条「引用原消息 + @被指派成员」的普通文本
|
||
* (同一消息同批到点的成员合并一条)。
|
||
*/
|
||
class TodoRemindTask extends AbstractTask
|
||
{
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
}
|
||
|
||
/**
|
||
* 构造提醒文本:每个被提醒成员一个 @ span + 提示语。
|
||
* 直接拼 <span class="mention user" data-id> 是因为 sendMsg 不会调用 formatMsg,
|
||
* 文本会原样入库,msgJoinGroup 据此 span 正则提取 @。
|
||
*/
|
||
public static function buildRemindText(array $mentionUserids): string
|
||
{
|
||
$nicknames = User::whereIn('userid', $mentionUserids)->pluck('nickname', 'userid');
|
||
$mentionText = '';
|
||
foreach ($mentionUserids as $uid) {
|
||
$name = $nicknames[$uid] ?? $uid;
|
||
$mentionText .= "<span class=\"mention user\" data-id=\"{$uid}\">@{$name}</span> ";
|
||
}
|
||
return $mentionText . Doo::translate('你有一条待办到提醒时间啦');
|
||
}
|
||
|
||
public function start()
|
||
{
|
||
$rows = WebSocketDialogMsgTodo::dueReminders();
|
||
if ($rows->isEmpty()) {
|
||
return;
|
||
}
|
||
$botUser = User::botGetOrCreate('todo-alert');
|
||
if (empty($botUser)) {
|
||
return;
|
||
}
|
||
foreach ($rows->groupBy('msg_id') as $msgId => $group) {
|
||
$rowIds = $group->pluck('id')->toArray();
|
||
$userids = $group->pluck('userid')->map('intval')->values()->toArray();
|
||
//
|
||
$msg = WebSocketDialogMsg::find($msgId);
|
||
$dialog = $msg ? WebSocketDialog::find($msg->dialog_id) : null;
|
||
if (empty($msg) || empty($dialog)) {
|
||
// 原消息/会话已不存在:标记已提醒,避免空转重复扫描
|
||
WebSocketDialogMsgTodo::whereIn('id', $rowIds)->update(['reminded_at' => Carbon::now()]);
|
||
continue;
|
||
}
|
||
//
|
||
$memberIds = $dialog->dialogUser->pluck('userid')->map('intval')->values()->toArray();
|
||
$mentionUserids = array_values(array_intersect($userids, $memberIds));
|
||
if (empty($mentionUserids)) {
|
||
// 被指派人都已退群:没人可 @,标记已提醒避免空转重复扫描
|
||
WebSocketDialogMsgTodo::whereIn('id', $rowIds)->update(['reminded_at' => Carbon::now()]);
|
||
continue;
|
||
}
|
||
$res = WebSocketDialogMsg::sendMsg(
|
||
"reply-{$msg->id}", // 引用原消息 → reply_data 自动填充
|
||
$dialog->id,
|
||
'text', // 普通文本
|
||
['text' => self::buildRemindText($mentionUserids)],
|
||
$botUser->userid,
|
||
false, false, false // push_self / push_retry / push_silence
|
||
);
|
||
//
|
||
if (\App\Module\Base::isSuccess($res)) {
|
||
WebSocketDialogMsgTodo::whereIn('id', $rowIds)->update(['reminded_at' => Carbon::now()]);
|
||
}
|
||
}
|
||
}
|
||
|
||
public function end()
|
||
{
|
||
}
|
||
}
|