request->url(true); return function_exists('cms_lang_url') ? cms_lang_url($url) : $url; } protected function resolveNotifyChannel(string $lang): string { $lang = strtolower(trim($lang)); return in_array($lang, ['cn', 'zh-cn', 'zh'], true) ? 'sms' : 'email'; } protected function getTranslatedActivity(int $activityId, string $lang): array { $lang = strtolower(trim($lang)); if ($lang === '') { $lang = 'cn'; } $query = Db::name('cms_yuyue_data')->where('did', $activityId); $row = (clone $query)->where('lang', $lang)->find(); if (!$row && $lang !== 'cn') { $row = (clone $query)->where('lang', 'cn')->find(); } return is_array($row) ? $row : []; } protected function getTranslatedNotifyContent(array $activity, string $field, string $lang): string { return trim((string)($activity[$field] ?? '')); } protected function sendAppointmentEmail(string $email, string $subject, string $content): array { try { $config = get_addon_config('phpmailer'); if (!$config) { return [false, 'NO_CONFIG', '未找到 phpmailer 插件配置']; } $mailer = new \addons\phpmailer\library\Mailer($config); $result = $mailer->to($email)->subject($subject)->message($content, true)->send(); if ($result) { return [true, 'SUCCESS', '邮件发送成功']; } return [false, 'FAIL', $mailer->getError() ?: '邮件发送失败']; } catch (\Throwable $e) { return [false, 'EXCEPTION', $e->getMessage()]; } } public function submit() { if (!$this->request->isPost()) { $this->error('请求方式错误'); } $activityId = (int)$this->request->post('activity_id/d', 0); $mobile = trim((string)$this->request->post('mobile/s', '')); $email = trim((string)$this->request->post('email/s', '')); $name = trim((string)$this->request->post('name/s', '')); $remark = trim((string)$this->request->post('remark/s', '')); $sourcePage = trim((string)$this->request->post('source_page/s', $this->frontendCurrentUrl())); $lang = strtolower(trim((string)$this->request->post('lang/s', ''))); if ($lang === '' && function_exists('cms_current_region_lang')) { $lang = strtolower(trim((string)cms_current_region_lang('cn'))); } if ($lang === '') { $lang = 'cn'; } $notifyChannel = $this->resolveNotifyChannel($lang); if ($activityId <= 0) { $this->error('预约活动信息缺失'); } if ($notifyChannel === 'sms') { if (!$mobile || !Validate::regex($mobile, '^1\d{10}$')) { $this->error('手机号格式不正确'); } } else { if (!$email || !Validate::is($email, 'email')) { $this->error('Email格式不正确'); } } $now = time(); $ip = $this->request->ip(); $activity = Db::name('cms_yuyue')->where('id', $activityId)->find(); if (!$activity) { $this->error('预约活动不存在或已下线'); } $translatedActivity = $this->getTranslatedActivity($activityId, $lang); if ($translatedActivity) { $activity = array_merge($activity, $translatedActivity); } $activityTitle = trim((string)($activity['title'] ?? '')); if ($activityTitle === '') { $this->error('预约活动标题缺失'); } $starttime = isset($activity['starttime']) ? (int)$activity['starttime'] : 0; $endtime = isset($activity['endtime']) ? (int)$activity['endtime'] : 0; if ($starttime > 0 && $now < $starttime) { $this->error('预约尚未开始'); } if ($endtime > 0 && $now > $endtime) { $this->error('预约已结束'); } $ipLimitWindowSeconds = 60; $ipLimitWindowMax = max(0, (int) config('site.sms_rpm', 5)); $ipLimitDailyMax = max(0, (int) config('site.sms_drl', 10)); $dayKey = date('Ymd', $now); $ipWindowKey = 'appointment_submit_ip_window_' . md5($ip); $ipDailyKey = 'appointment_submit_ip_daily_' . md5($ip . '_' . $dayKey); $ipWindowHits = (int) Cache::get($ipWindowKey, 0); if ($ipLimitWindowMax > 0 && $ipWindowHits >= $ipLimitWindowMax) { $this->error('提交过于频繁,请稍后再试'); } $ipDailyHits = (int) Cache::get($ipDailyKey, 0); if ($ipLimitDailyMax > 0 && $ipDailyHits >= $ipLimitDailyMax) { $this->error('今日提交次数已达上限,请明天再试'); } if ($ipLimitWindowMax > 0) { Cache::set($ipWindowKey, $ipWindowHits + 1, $ipLimitWindowSeconds); } if ($ipLimitDailyMax > 0) { Cache::set($ipDailyKey, $ipDailyHits + 1, 86400); } if ($notifyChannel === 'sms') { $exists = AppointmentRecord::where(['activity_id' => $activityId, 'mobile' => $mobile])->find(); } else { $exists = AppointmentRecord::where(['activity_id' => $activityId, 'email' => $email])->find(); } if ($exists) { $duplicateMsg = $notifyChannel === 'sms' ? '您已提交过该活动预约,请勿重复提交' : 'You have already submitted this reservation for the activity.'; $this->success($duplicateMsg, null, [ 'record_id' => $exists->id, 'duplicate' => 1, ]); } $submitNotifyTpl = $this->getTranslatedNotifyContent($activity, 'submit_sms_content', $lang); $autoNotify = !empty($activity['appointment_auto_sms']) ? 1 : 0; Db::startTrans(); try { $record = new AppointmentRecord(); $record->save([ 'activity_id' => $activityId, 'activity_title' => $activityTitle, 'lang' => $lang, 'notify_channel' => $notifyChannel, 'mobile' => $mobile, 'email' => $email, 'name' => $name, 'remark' => $remark, 'status' => 1, 'submit_sms_status' => 0, 'submit_sms_time' => 0, 'batch_sms_status' => 0, 'batch_sms_time' => 0, 'source_page' => $sourcePage, 'ip' => $ip, 'admin_remark' => '', 'createtime' => $now, 'updatetime' => $now, ]); if ($notifyChannel === 'sms') { $submitContent = $submitNotifyTpl !== '' ? str_replace('{title}', $activityTitle, $submitNotifyTpl) : '您已成功预约【' . $activityTitle . '】,新品动态发布后我们将第一时间短信通知您。'; $smsLog = new AppointmentSmsLog(); $smsLog->save([ 'record_id' => $record->id, 'activity_id' => $activityId, 'mobile' => $mobile, 'sms_type' => 'appointment_submit', 'template_code' => '', 'content' => $submitContent, 'status' => 0, 'response_code' => '', 'response_msg' => $autoNotify ? '待自动发送' : '当前活动未开启提交后自动短信', 'retry_count' => 0, 'send_time' => 0, 'createtime' => $now, 'updatetime' => $now, ]); if ($autoNotify) { if (Event::hasListener('sms_notice')) { $sendResult = SmsLib::notice($mobile, $submitContent, null); $smsLog->status = $sendResult ? 1 : 2; $smsLog->response_code = $sendResult ? 'SUCCESS' : 'FAIL'; $smsLog->response_msg = $sendResult ? '提交后自动短信发送成功' : '提交后自动短信发送失败'; $smsLog->send_time = $sendResult ? $now : 0; $smsLog->updatetime = $now; $smsLog->save(); $record->submit_sms_status = $sendResult ? 1 : 2; $record->submit_sms_time = $sendResult ? $now : 0; $record->updatetime = $now; $record->save(); } else { $smsLog->status = 2; $smsLog->response_code = 'NO_PLUGIN'; $smsLog->response_msg = '未检测到短信通知插件监听 sms_notice'; $smsLog->updatetime = $now; $smsLog->save(); $record->submit_sms_status = 2; $record->updatetime = $now; $record->save(); } } } else { $submitContent = $submitNotifyTpl !== '' ? str_replace('{title}', $activityTitle, $submitNotifyTpl) : 'You have successfully reserved [' . $activityTitle . ']. We will notify you by email when updates are available.'; $subject = $activityTitle . ' Reservation Confirmation'; $emailLog = new AppointmentEmailLog(); $emailLog->save([ 'record_id' => $record->id, 'activity_id' => $activityId, 'email' => $email, 'mail_type' => 'appointment_submit', 'subject' => $subject, 'content' => $submitContent, 'status' => 0, 'response_code' => '', 'response_msg' => $autoNotify ? '待自动发送' : '当前活动未开启提交后自动邮件', 'retry_count' => 0, 'send_time' => 0, 'createtime' => $now, 'updatetime' => $now, ]); if ($autoNotify) { [$sendResult, $responseCode, $responseMsg] = $this->sendAppointmentEmail($email, $subject, $submitContent); $emailLog->status = $sendResult ? 1 : 2; $emailLog->response_code = $responseCode; $emailLog->response_msg = $responseMsg ?: ($sendResult ? '提交后自动邮件发送成功' : '提交后自动邮件发送失败'); $emailLog->send_time = $sendResult ? $now : 0; $emailLog->updatetime = $now; $emailLog->save(); $record->submit_sms_status = $sendResult ? 1 : 2; $record->submit_sms_time = $sendResult ? $now : 0; $record->updatetime = $now; $record->save(); } } Db::commit(); } catch (\Throwable $e) { Db::rollback(); $this->error('预约提交失败:' . $e->getMessage()); } if ($notifyChannel === 'sms') { $msg = $autoNotify ? ($record->submit_sms_status == 1 ? '预约成功,短信已发送,请留意手机通知' : '预约成功,但自动短信发送未完成,请稍后关注后续通知') : '预约成功,我们会在合适时间统一发送短信通知'; } else { $msg = $autoNotify ? ($record->submit_sms_status == 1 ? 'Reservation successful. A confirmation email has been sent.' : 'Reservation successful, but the automatic notification was not completed.') : 'Reservation successful. We will contact you later by email.'; } $this->success($msg, null, [ 'record_id' => $record->id, 'lang' => $lang, 'notify_channel' => $notifyChannel, 'submit_sms_status' => $record->submit_sms_status, 'batch_sms_status' => $record->batch_sms_status, 'appointment_auto_sms' => $autoNotify, ]); } }