ToDoReminder.php 7.8 KB

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