ToDoReminder.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Model\TodoList;
  4. use App\Model\WxEmployeeOfficial;
  5. use App\Service\Weixin\WxTemplateMessageService;
  6. use Illuminate\Console\Command;
  7. use Illuminate\Support\Facades\Cache;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Log;
  10. class ToDoReminder extends Command
  11. {
  12. /**
  13. * The name and signature of the console command.
  14. *
  15. * @var string
  16. */
  17. protected $signature = 'command:todo_reminder';
  18. /**
  19. * The console command description.
  20. *
  21. * @var string
  22. */
  23. protected $description = 'Command description';
  24. /**
  25. * Create a new command instance.
  26. *
  27. * @return void
  28. */
  29. public function __construct()
  30. {
  31. parent::__construct();
  32. }
  33. /**
  34. * Execute the console command.
  35. *
  36. * @return mixed
  37. */
  38. public function handle()
  39. {
  40. try {
  41. $this->handleReminders();
  42. }catch (\Exception $exception){
  43. Log::error("发送待办提醒异常", ['msg' => $exception->getMessage()]);
  44. }
  45. }
  46. public function handleReminders1()
  47. {
  48. $now = time();
  49. $wxService = new WxTemplateMessageService();
  50. $appid = config("wx_msg.f_appid");
  51. TodoList::where('status', '<', TodoList::status_two)
  52. ->where('del_time', 0)
  53. ->where('remind_start', '<=', $now)
  54. ->select(TodoList::$field)
  55. ->orderBy('id')
  56. ->chunkById(10, function ($data) use ($now, $wxService, $appid) {
  57. $crtIds = $data->pluck('crt_id')->toArray();
  58. // 查找关联员工 openid
  59. $wxInfo = WxEmployeeOfficial::where('employee_id', $crtIds)
  60. ->where('type', WxEmployeeOfficial::login_type_two)
  61. ->where('appid', $appid)
  62. ->pluck('openid','employee_id')
  63. ->toArray();
  64. foreach ($data as $todo) {
  65. $shouldRemind = false;
  66. if ($todo->remind_interval == 0 && empty($todo->last_remind_time)) {
  67. $shouldRemind = true;
  68. } elseif ($todo->remind_interval > 0 && (empty($todo->last_remind_time) || ($now - $todo->last_remind_time) >= $todo->remind_interval)
  69. ) {
  70. $shouldRemind = true;
  71. }
  72. if (! $shouldRemind){
  73. echo $todo->id."|";
  74. continue;
  75. }
  76. // 先发送微信消息(接口可能失败)
  77. $message = "";
  78. try {
  79. list($status, $msg) = $this->sendReminder($todo, $wxService, $wxInfo);
  80. $message = $msg;
  81. if(! $status) {
  82. Log::error("发送待办提醒失败 todo_id: {$todo->id}", ['msg' => $msg]);
  83. }
  84. } catch (\Throwable $e) {
  85. Log::error("发送待办提醒失败 todo_id: {$todo->id}", ['msg' => $e->getMessage()]);
  86. }
  87. // 再更新数据库(单条事务保护即可)
  88. DB::transaction(function () use ($todo, $now,$message) {
  89. $todo->last_remind_time = $now;
  90. $todo->status = TodoList::status_one;
  91. $todo->last_time_result = $message;
  92. $todo->save();
  93. });
  94. }
  95. });
  96. }
  97. public function handleReminders()
  98. {
  99. $now = time();
  100. $appid = config("wx_msg.f_appid");
  101. $wxSrv = new WxTemplateMessageService();
  102. // 推荐按提醒时间来排序
  103. TodoList::where('status', '<', TodoList::status_two)
  104. ->where('del_time', 0)
  105. ->where('remind_start', '<=', $now)
  106. ->orderBy('remind_start')
  107. ->chunkById(20, function ($list) use ($now, $appid, $wxSrv) {
  108. // 批量拿创建人 ID
  109. $crtIds = array_unique($list->pluck('crt_id')->toArray());
  110. // 批量查询 openid(统一用字符串 key)
  111. $wxInfo = WxEmployeeOfficial::where('employee_id', $crtIds)
  112. ->where('type', WxEmployeeOfficial::login_type_two)
  113. ->where('appid', $appid)
  114. ->pluck('openid','employee_id')
  115. ->toArray();
  116. // 处理每一条
  117. foreach ($list as $todo) {
  118. // 分布式锁(避免并发重复提醒)
  119. $lockKey = "todo_remind_lock:" . $todo->id;
  120. if (! Cache::lock($lockKey, 5)->get()) {
  121. continue; // 已有别的任务在处理本记录
  122. }
  123. try {
  124. // 1. 判断是否应提醒
  125. if (! $this->shouldRemind($todo, $now)) {
  126. continue;
  127. }
  128. // 2. 发送消息
  129. list($ok, $msg) = $this->sendReminder($todo, $wxSrv, $wxInfo);
  130. if (! $ok) {
  131. Log::warning("发送待办提醒失败 todo_id={$todo->id}", ['msg' => $msg]);
  132. }
  133. $this->updateTodoStatus($todo, $now, $msg);
  134. } catch (\Throwable $e) {
  135. Log::error("提醒异常 todo_id={$todo->id}", ['msg' => $e->getMessage()]);
  136. } finally {
  137. Cache::lock($lockKey)->release();
  138. }
  139. }
  140. });
  141. }
  142. protected function shouldRemind($todo, $now)
  143. {
  144. // 首次提醒
  145. if ($todo->remind_interval == 0 && empty($todo->last_remind_time)) {
  146. return true;
  147. }
  148. // 间隔提醒
  149. if ($todo->remind_interval > 0) {
  150. if (empty($todo->last_remind_time)) return true;
  151. return ($now - $todo->last_remind_time) >= $todo->remind_interval;
  152. }
  153. return false;
  154. }
  155. protected function updateTodoStatus($todo, $now, $msg)
  156. {
  157. // 防止 msg 是数组,写入数据库报错
  158. $msg = is_scalar($msg) ? $msg : json_encode($msg, JSON_UNESCAPED_UNICODE);
  159. // 乐观锁,防重复写
  160. TodoList::where('id', $todo->id)
  161. ->where('last_remind_time', $todo->last_remind_time ?? 0)
  162. ->update([
  163. 'last_remind_time' => $now,
  164. 'status' => TodoList::status_one,
  165. 'last_time_result' => $msg,
  166. ]);
  167. }
  168. protected function sendReminder($todo, $wxService, $wxInfo)
  169. {
  170. // 查找关联员工 openid
  171. $openid = $wxInfo[$todo->crt_id] ?? "";
  172. if (! $openid) return [false, '无openid'];
  173. // 模板参数
  174. $params = [
  175. 'title' => $todo->title ?? '待办提醒',
  176. 'time' => date('Y-m-d H:i:00', $todo->remind_start),
  177. ];
  178. // 发送消息
  179. list($status, $msg) = $wxService->sendTemplateMessage('to_do', $params, [
  180. 'openid' => $openid,
  181. 'pagepath' => 'pages/todoManage/add?type=4&id=' . $todo->id, // 小程序路径或URL
  182. ]);
  183. return [$status, $msg];
  184. }
  185. }