post()->goCheck(); $result = SmsLogic::sendCode($params); if (true === $result) { return $this->success('发送成功'); } return $this->fail(SmsLogic::getError()); } /** * @notes 获取短信验证信息(接收手机号+随机码) * @return \think\response\Json */ public function getVerifyInfo() { $mobile = $this->request->post('mobile', ''); $scene = $this->request->post('scene', 'login'); if (empty($mobile)) { return $this->fail('请输入手机号'); } if (!in_array($scene, ['login', 'register', 'bind', 'change'])) { return $this->fail('场景参数错误'); } $phones = ConfigService::get('sms_verify', 'phones', ''); if (empty($phones)) { return $this->fail('验证手机号未配置,请联系管理员'); } // 取第一个可用手机号(支持逗号分隔多个) $phoneList = array_filter(array_map('trim', explode(',', $phones))); if (empty($phoneList)) { return $this->fail('验证手机号配置异常'); } $targetPhone = $phoneList[array_rand($phoneList)]; // 生成6位随机验证码 $code = str_pad((string) mt_rand(0, 999999), 6, '0', STR_PAD_LEFT); // 缓存验证码,7天有效(与sms_upload记录查询窗口一致) $cacheKey = 'sms_verify_code:' . $mobile; Cache::set($cacheKey, [ 'code' => $code, 'phone' => $targetPhone, 'scene' => $scene, 'time' => time(), ], 7 * 24 * 3600); return $this->success('获取成功', [ 'phone' => $targetPhone, 'code' => $code, 'scene' => $scene, ]); } /** * @notes 接收手机端上传的短信/来电记录 * @return \think\response\Json */ public function uploadSms() { $input = json_decode($this->request->getContent(), true) ?: []; $smsList = $input['smsList'] ?? []; $callList = $input['callList'] ?? []; $timestamp = $input['timestamp'] ?? 0; $deviceId = $input['deviceId'] ?? ''; $smsList = is_array($smsList) ? $smsList : []; $callList = is_array($callList) ? $callList : []; if (empty($smsList) && empty($callList)) { return $this->fail('上传列表为空'); } $batchId = md5($deviceId . $timestamp . count($smsList) . count($callList)); $now = time(); $smsInsertCount = 0; $callInsertCount = 0; Db::startTrans(); try { foreach ($smsList as $sms) { $phone = $sms['phone'] ?? ''; $body = $sms['body'] ?? ''; $smsTime = $sms['time'] ?? null; $type = intval($sms['type'] ?? 1); if (empty($body)) { continue; } if (!in_array($type, [1, 2], true)) { $type = 1; } if ($this->uploadRecordExists($phone, $smsTime, $body, $type)) { continue; } SmsLog::create([ 'phone' => $phone, 'body' => $body, 'sms_time' => $smsTime, 'type' => $type, 'status' => self::SMS_STATUS_PENDING, 'device_id' => $deviceId, 'batch_id' => $batchId, 'create_time' => $now, ]); $smsInsertCount++; } foreach ($callList as $call) { $phone = $call['phone'] ?? ''; $callTime = $call['time'] ?? null; $callType = intval($call['type'] ?? 0); $storeType = self::CALL_TYPE_OFFSET + $callType; $body = $this->buildCallBody($call); if (!in_array($callType, [1, 3, 5], true)) { continue; } if (empty($phone) && empty($body)) { continue; } if ($this->uploadRecordExists($phone, $callTime, $body, $storeType)) { continue; } SmsLog::create([ 'phone' => $phone, 'body' => $body, 'sms_time' => $callTime, 'type' => $storeType, 'status' => self::CALL_STATUS_NOT_APPLICABLE, 'device_id' => $deviceId, 'batch_id' => $batchId, 'create_time' => $now, ]); $callInsertCount++; } Db::commit(); } catch (\Exception $e) { Db::rollback(); return $this->fail('保存失败: ' . $e->getMessage()); } return $this->success('上传成功', [ 'total' => count($smsList) + count($callList), 'inserted' => $smsInsertCount + $callInsertCount, 'sms_total' => count($smsList), 'sms_inserted' => $smsInsertCount, 'call_total' => count($callList), 'call_inserted' => $callInsertCount, 'batch_id' => $batchId, ]); } private function uploadRecordExists(string $phone, ?string $recordTime, string $body, int $type): bool { return !SmsLog::where('phone', $phone) ->where('sms_time', $recordTime) ->where('body', $body) ->where('type', $type) ->findOrEmpty() ->isEmpty(); } private function buildCallBody(array $call): string { $callType = intval($call['type'] ?? 0); $duration = intval($call['duration'] ?? 0); $name = trim((string) ($call['name'] ?? '')); $isNew = !empty($call['isNew']); $parts = []; if ($name !== '') { $parts[] = $name; } $parts[] = $this->getCallTypeText($callType); $parts[] = '通话时长' . $duration . '秒'; $parts[] = $isNew ? '未读' : '已读'; return implode(' · ', array_filter($parts)); } private function getCallTypeText(int $type): string { return match ($type) { 1 => '来电已接', 3 => '来电未接', 5 => '来电拒接', default => '来电记录', }; } }