Files
cunkebao_v3/Moncter/app/service/UserPhoneService.php

538 lines
27 KiB
PHP
Raw Normal View History

2026-01-05 10:16:20 +08:00
<?php
namespace app\service;
use app\repository\UserPhoneRelationRepository;
use app\utils\EncryptionHelper;
use app\utils\LoggerHelper;
use Ramsey\Uuid\Uuid as UuidGenerator;
/**
* 用户手机号服务
*
* 职责:
* - 管理用户与手机号的关联关系
* - 处理手机号的历史记录(支持手机号回收后重新分配)
* - 根据手机号查找当前用户
* - 获取用户的所有手机号
*/
class UserPhoneService
{
public function __construct(
protected UserPhoneRelationRepository $phoneRelationRepository
) {
}
/**
* 为用户添加手机号
*
* @param string $userId 用户ID
* @param string $phoneNumber 手机号
* @param array<string, mixed> $options 可选参数
* - type: 手机号类型personal/work/backup/other
* - is_verified: 是否已验证
* - effective_time: 生效时间(默认当前时间)
* - expire_time: 失效时间默认null表示当前有效
* - source: 来源registration/update/manual/import
* @return string 关联ID
* @throws \InvalidArgumentException
*/
public function addPhoneToUser(string $userId, string $phoneNumber, array $options = []): string
{
\Workerman\Worker::safeEcho("\n");
\Workerman\Worker::safeEcho("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
\Workerman\Worker::safeEcho("[UserPhoneService::addPhoneToUser] 【断点1-方法入口】开始执行\n");
\Workerman\Worker::safeEcho("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
\Workerman\Worker::safeEcho("【断点1】原始传入参数:\n");
\Workerman\Worker::safeEcho(" - userId: {$userId}\n");
\Workerman\Worker::safeEcho(" - phoneNumber: {$phoneNumber}\n");
\Workerman\Worker::safeEcho(" - options: " . json_encode($options, JSON_UNESCAPED_UNICODE) . "\n");
\Workerman\Worker::safeEcho("\n【断点2-参数处理】开始处理参数\n");
$phoneNumber = trim($phoneNumber);
\Workerman\Worker::safeEcho(" - trim后phoneNumber: {$phoneNumber}\n");
// 检查手机号是否为空
if (empty($phoneNumber)) {
\Workerman\Worker::safeEcho("【断点2】❌ 手机号为空,抛出异常\n");
throw new \InvalidArgumentException('手机号不能为空');
}
// 过滤非数字字符
$originalPhone = $phoneNumber;
\Workerman\Worker::safeEcho("\n【断点3-过滤处理】开始过滤非数字字符\n");
$phoneNumber = $this->filterPhoneNumber($phoneNumber);
\Workerman\Worker::safeEcho(" - 原始手机号: {$originalPhone}\n");
\Workerman\Worker::safeEcho(" - 过滤后手机号: {$phoneNumber}\n");
\Workerman\Worker::safeEcho(" - 过滤后长度: " . strlen($phoneNumber) . "\n");
// 检查过滤后是否为空
if (empty($phoneNumber)) {
\Workerman\Worker::safeEcho("【断点3】❌ 手机号过滤后为空,抛出异常\n");
throw new \InvalidArgumentException("手机号过滤后为空: {$originalPhone}");
}
\Workerman\Worker::safeEcho("\n【断点4-格式验证】开始验证手机号格式\n");
// 验证手机号格式(过滤后的手机号)
$isValid = $this->validatePhoneNumber($phoneNumber);
\Workerman\Worker::safeEcho(" - 验证结果: " . ($isValid ? '通过 ✓' : '失败 ✗') . "\n");
if (!$isValid) {
\Workerman\Worker::safeEcho("【断点4】❌ 手机号格式验证失败,抛出异常\n");
\Workerman\Worker::safeEcho(" - 验证规则: /^1[3-9]\\d{9}$/\n");
\Workerman\Worker::safeEcho(" - 实际值: {$phoneNumber}\n");
\Workerman\Worker::safeEcho(" - 长度: " . strlen($phoneNumber) . "\n");
throw new \InvalidArgumentException("手机号格式不正确: {$originalPhone} -> {$phoneNumber} (长度: " . strlen($phoneNumber) . ")");
}
\Workerman\Worker::safeEcho("【断点4】✓ 格式验证通过\n");
\Workerman\Worker::safeEcho("\n【断点5-哈希计算】开始计算手机号哈希\n");
$phoneHash = EncryptionHelper::hash($phoneNumber);
$now = new \DateTimeImmutable('now');
$effectiveTime = $options['effective_time'] ?? $now;
\Workerman\Worker::safeEcho(" - phoneHash: {$phoneHash}\n");
\Workerman\Worker::safeEcho(" - effectiveTime: " . $effectiveTime->format('Y-m-d H:i:s') . "\n");
\Workerman\Worker::safeEcho("\n【断点6-冲突检查】检查是否存在冲突关联\n");
// 检查该手机号在effectiveTime是否已有有效关联
// 使用effectiveTime作为查询时间点查找是否有冲突的关联
$existingActive = $this->phoneRelationRepository->findActiveByPhoneHash($phoneHash, $effectiveTime);
\Workerman\Worker::safeEcho(" - 查询结果: " . ($existingActive ? "找到冲突关联 (user_id: {$existingActive->user_id})" : "无冲突") . "\n");
if ($existingActive && $existingActive->user_id !== $userId) {
\Workerman\Worker::safeEcho("【断点6】⚠ 发现冲突,需要失效旧关联\n");
// 如果手机号在effectiveTime已被其他用户使用需要先失效旧关联
// 过期时间设置为新关联的effectiveTime保证时间连续避免间隙
$existingActive->expire_time = $effectiveTime;
$existingActive->is_active = false;
$existingActive->update_time = $now;
$existingActive->save();
\Workerman\Worker::safeEcho(" - 旧关联已失效expire_time: " . $effectiveTime->format('Y-m-d H:i:s') . "\n");
LoggerHelper::logBusiness('phone_relation_expired_due_to_conflict', [
'phone_number' => $phoneNumber,
'old_user_id' => $existingActive->user_id,
'new_user_id' => $userId,
'expire_time' => $effectiveTime->format('Y-m-d H:i:s'),
'effective_time' => $effectiveTime->format('Y-m-d H:i:s'),
]);
} else {
\Workerman\Worker::safeEcho("【断点6】✓ 无冲突,继续创建新关联\n");
}
\Workerman\Worker::safeEcho("\n【断点7-数据准备】开始准备要保存的数据\n");
try {
\Workerman\Worker::safeEcho(" [7.1] 创建 UserPhoneRelationRepository 对象...\n");
// 创建新关联
$relation = new UserPhoneRelationRepository();
\Workerman\Worker::safeEcho(" [7.1] ✓ 对象创建成功\n");
\Workerman\Worker::safeEcho(" [7.2] 设置 relation_id...\n");
$relation->relation_id = UuidGenerator::uuid4()->toString();
\Workerman\Worker::safeEcho(" [7.2] ✓ relation_id = {$relation->relation_id}\n");
\Workerman\Worker::safeEcho(" [7.3] 设置 phone_number...\n");
$relation->phone_number = $phoneNumber;
\Workerman\Worker::safeEcho(" [7.3] ✓ phone_number = {$relation->phone_number}\n");
\Workerman\Worker::safeEcho(" [7.4] 设置 phone_hash...\n");
$relation->phone_hash = $phoneHash;
\Workerman\Worker::safeEcho(" [7.4] ✓ phone_hash = {$relation->phone_hash}\n");
\Workerman\Worker::safeEcho(" [7.5] 设置 user_id...\n");
$relation->user_id = $userId;
\Workerman\Worker::safeEcho(" [7.5] ✓ user_id = {$relation->user_id}\n");
\Workerman\Worker::safeEcho(" [7.6] 设置 effective_time...\n");
\Workerman\Worker::safeEcho(" - effectiveTime类型: " . get_class($effectiveTime) . "\n");
\Workerman\Worker::safeEcho(" - effectiveTime值: " . $effectiveTime->format('Y-m-d H:i:s') . "\n");
$relation->effective_time = $effectiveTime;
\Workerman\Worker::safeEcho(" [7.6] ✓ effective_time 设置完成\n");
\Workerman\Worker::safeEcho(" [7.7] 设置 expire_time...\n");
$expireTimeValue = $options['expire_time'] ?? null;
\Workerman\Worker::safeEcho(" - expireTime值: " . ($expireTimeValue ? (is_object($expireTimeValue) ? $expireTimeValue->format('Y-m-d H:i:s') : $expireTimeValue) : 'null') . "\n");
$relation->expire_time = $expireTimeValue;
\Workerman\Worker::safeEcho(" [7.7] ✓ expire_time 设置完成\n");
\Workerman\Worker::safeEcho(" [7.8] 设置 is_active...\n");
// 如果 expire_time 为 null 或不存在,则 is_active 为 true
$isActiveValue = ($options['expire_time'] ?? null) === null;
\Workerman\Worker::safeEcho(" - isActive值: " . ($isActiveValue ? 'true' : 'false') . "\n");
$relation->is_active = $isActiveValue;
\Workerman\Worker::safeEcho(" [7.8] ✓ is_active 设置完成\n");
\Workerman\Worker::safeEcho(" [7.9] 设置 type...\n");
$typeValue = $options['type'] ?? 'personal';
\Workerman\Worker::safeEcho(" - type值: {$typeValue}\n");
$relation->type = $typeValue;
\Workerman\Worker::safeEcho(" [7.9] ✓ type 设置完成\n");
\Workerman\Worker::safeEcho(" [7.10] 设置 is_verified...\n");
$isVerifiedValue = $options['is_verified'] ?? false;
\Workerman\Worker::safeEcho(" - isVerified值: " . ($isVerifiedValue ? 'true' : 'false') . "\n");
$relation->is_verified = $isVerifiedValue;
\Workerman\Worker::safeEcho(" [7.10] ✓ is_verified 设置完成\n");
\Workerman\Worker::safeEcho(" [7.11] 设置 source...\n");
$sourceValue = $options['source'] ?? 'manual';
\Workerman\Worker::safeEcho(" - source值: {$sourceValue}\n");
$relation->source = $sourceValue;
\Workerman\Worker::safeEcho(" [7.11] ✓ source 设置完成\n");
\Workerman\Worker::safeEcho(" [7.12] 设置 create_time...\n");
\Workerman\Worker::safeEcho(" - now类型: " . get_class($now) . "\n");
\Workerman\Worker::safeEcho(" - now值: " . $now->format('Y-m-d H:i:s') . "\n");
$relation->create_time = $now;
\Workerman\Worker::safeEcho(" [7.12] ✓ create_time 设置完成\n");
\Workerman\Worker::safeEcho(" [7.13] 设置 update_time...\n");
$relation->update_time = $now;
\Workerman\Worker::safeEcho(" [7.13] ✓ update_time 设置完成\n");
\Workerman\Worker::safeEcho(" [7.14] ✓ 所有属性设置完成,准备打印数据详情\n");
} catch (\Throwable $e) {
\Workerman\Worker::safeEcho("\n【断点7】❌ 数据准备过程中发生异常!\n");
\Workerman\Worker::safeEcho(" - 错误信息: " . $e->getMessage() . "\n");
\Workerman\Worker::safeEcho(" - 错误类型: " . get_class($e) . "\n");
\Workerman\Worker::safeEcho(" - 文件: " . $e->getFile() . ":" . $e->getLine() . "\n");
\Workerman\Worker::safeEcho(" - 堆栈跟踪:\n");
$trace = $e->getTraceAsString();
$traceLines = explode("\n", $trace);
foreach (array_slice($traceLines, 0, 10) as $line) {
\Workerman\Worker::safeEcho(" " . $line . "\n");
}
throw $e;
}
\Workerman\Worker::safeEcho("【断点7】准备保存的数据详情:\n");
\Workerman\Worker::safeEcho(" - relation_id: {$relation->relation_id}\n");
\Workerman\Worker::safeEcho(" - phone_number: {$relation->phone_number}\n");
\Workerman\Worker::safeEcho(" - phone_hash: {$relation->phone_hash}\n");
\Workerman\Worker::safeEcho(" - user_id: {$relation->user_id}\n");
\Workerman\Worker::safeEcho(" - effective_time: " . ($relation->effective_time ? $relation->effective_time->format('Y-m-d H:i:s') : 'null') . "\n");
\Workerman\Worker::safeEcho(" - expire_time: " . ($relation->expire_time ? $relation->expire_time->format('Y-m-d H:i:s') : 'null') . "\n");
\Workerman\Worker::safeEcho(" - is_active: " . ($relation->is_active ? 'true' : 'false') . "\n");
\Workerman\Worker::safeEcho(" - type: {$relation->type}\n");
\Workerman\Worker::safeEcho(" - is_verified: " . ($relation->is_verified ? 'true' : 'false') . "\n");
\Workerman\Worker::safeEcho(" - source: {$relation->source}\n");
\Workerman\Worker::safeEcho(" - create_time: " . ($relation->create_time ? $relation->create_time->format('Y-m-d H:i:s') : 'null') . "\n");
\Workerman\Worker::safeEcho(" - update_time: " . ($relation->update_time ? $relation->update_time->format('Y-m-d H:i:s') : 'null') . "\n");
\Workerman\Worker::safeEcho("\n【断点8-数据库配置检查】检查数据库配置\n");
\Workerman\Worker::safeEcho("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
try {
// 获取表名
$tableName = $relation->getTable();
\Workerman\Worker::safeEcho(" ✓ 目标表名: {$tableName}\n");
// 获取连接名
$connectionName = $relation->getConnectionName();
\Workerman\Worker::safeEcho(" ✓ 数据库连接名: {$connectionName}\n");
// 获取连接对象
$connection = $relation->getConnection();
\Workerman\Worker::safeEcho(" ✓ 连接对象获取成功\n");
// 获取数据库名
$databaseName = $connection->getDatabaseName();
\Workerman\Worker::safeEcho(" ✓ 数据库名: {$databaseName}\n");
// 获取配置信息
$config = config('database.connections.' . $connectionName, []);
\Workerman\Worker::safeEcho("\n 数据库配置详情:\n");
\Workerman\Worker::safeEcho(" - driver: " . ($config['driver'] ?? 'unknown') . "\n");
\Workerman\Worker::safeEcho(" - dsn: " . ($config['dsn'] ?? 'unknown') . "\n");
\Workerman\Worker::safeEcho(" - database: " . ($config['database'] ?? 'unknown') . "\n");
\Workerman\Worker::safeEcho(" - username: " . (isset($config['username']) ? $config['username'] : 'null') . "\n");
\Workerman\Worker::safeEcho(" - has_password: " . (isset($config['password']) ? 'yes' : 'no') . "\n");
// 尝试获取MongoDB客户端信息
try {
$mongoClient = $connection->getMongoClient();
if ($mongoClient) {
\Workerman\Worker::safeEcho(" - MongoDB客户端: 已获取 ✓\n");
}
} catch (\Throwable $e) {
\Workerman\Worker::safeEcho(" - MongoDB客户端获取失败: " . $e->getMessage() . "\n");
}
// 测试连接
try {
$testCollection = $connection->getCollection($tableName);
\Workerman\Worker::safeEcho(" - 集合对象获取: 成功 ✓\n");
\Workerman\Worker::safeEcho(" - 集合名: {$tableName}\n");
} catch (\Throwable $e) {
\Workerman\Worker::safeEcho(" - 集合对象获取失败: " . $e->getMessage() . "\n");
}
\Workerman\Worker::safeEcho("\n 最终写入目标:\n");
\Workerman\Worker::safeEcho(" - 数据库: {$databaseName}\n");
\Workerman\Worker::safeEcho(" - 集合: {$tableName}\n");
\Workerman\Worker::safeEcho(" - 连接: {$connectionName}\n");
\Workerman\Worker::safeEcho(" - 连接状态: 已连接 ✓\n");
} catch (\Throwable $e) {
\Workerman\Worker::safeEcho(" ❌ 数据库配置检查失败!\n");
\Workerman\Worker::safeEcho(" - 错误信息: " . $e->getMessage() . "\n");
\Workerman\Worker::safeEcho(" - 错误类型: " . get_class($e) . "\n");
\Workerman\Worker::safeEcho(" - 文件: " . $e->getFile() . ":" . $e->getLine() . "\n");
\Workerman\Worker::safeEcho(" - 堆栈: " . $e->getTraceAsString() . "\n");
}
\Workerman\Worker::safeEcho("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
\Workerman\Worker::safeEcho("\n【断点9-执行保存】开始执行 save() 操作\n");
\Workerman\Worker::safeEcho(" - 调用: \$relation->save()\n");
// 执行保存
try {
$saveResult = $relation->save();
\Workerman\Worker::safeEcho("【断点9】save() 执行完成\n");
\Workerman\Worker::safeEcho(" - save() 返回值: " . ($saveResult ? 'true ✓' : 'false ✗') . "\n");
if (!$saveResult) {
\Workerman\Worker::safeEcho(" - ❌ 警告save() 返回 false数据可能未保存\n");
}
\Workerman\Worker::safeEcho("\n【断点10-保存后验证】验证数据是否真的写入数据库\n");
\Workerman\Worker::safeEcho(" - 查询条件: relation_id = {$relation->relation_id}\n");
// 验证是否真的保存成功(尝试查询)
$savedRelation = $this->phoneRelationRepository->findByRelationId($relation->relation_id);
if ($savedRelation) {
\Workerman\Worker::safeEcho(" - ✅ 验证成功:查询到保存的数据\n");
\Workerman\Worker::safeEcho(" - 查询到的 relation_id: {$savedRelation->relation_id}\n");
\Workerman\Worker::safeEcho(" - 查询到的 user_id: {$savedRelation->user_id}\n");
\Workerman\Worker::safeEcho(" - 查询到的 phone_number: {$savedRelation->phone_number}\n");
} else {
\Workerman\Worker::safeEcho(" - ❌ 验证失败save()返回true但查询不到数据\n");
\Workerman\Worker::safeEcho(" - 可能原因:\n");
\Workerman\Worker::safeEcho(" 1. MongoDB写入确认问题w=0模式\n");
\Workerman\Worker::safeEcho(" 2. 数据库连接问题\n");
\Workerman\Worker::safeEcho(" 3. 事务未提交\n");
\Workerman\Worker::safeEcho(" 4. 写入延迟\n");
}
} catch (\Throwable $e) {
\Workerman\Worker::safeEcho("\n【断点9】❌ 保存过程中发生异常!\n");
\Workerman\Worker::safeEcho(" - 错误信息: " . $e->getMessage() . "\n");
\Workerman\Worker::safeEcho(" - 错误类型: " . get_class($e) . "\n");
\Workerman\Worker::safeEcho(" - 文件: " . $e->getFile() . ":" . $e->getLine() . "\n");
\Workerman\Worker::safeEcho(" - 堆栈跟踪:\n");
$trace = $e->getTraceAsString();
$traceLines = explode("\n", $trace);
foreach (array_slice($traceLines, 0, 5) as $line) {
\Workerman\Worker::safeEcho(" " . $line . "\n");
}
throw $e;
}
\Workerman\Worker::safeEcho("\n【断点11-日志记录】记录业务日志\n");
LoggerHelper::logBusiness('phone_relation_created', [
'relation_id' => $relation->relation_id,
'user_id' => $userId,
'phone_number' => $phoneNumber,
'type' => $relation->type,
'effective_time' => $effectiveTime->format('Y-m-d H:i:s'),
]);
\Workerman\Worker::safeEcho("【断点11】✓ 业务日志已记录\n");
\Workerman\Worker::safeEcho("\n【断点12-方法返回】准备返回结果\n");
\Workerman\Worker::safeEcho(" - 返回 relation_id: {$relation->relation_id}\n");
\Workerman\Worker::safeEcho("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
\Workerman\Worker::safeEcho("[UserPhoneService::addPhoneToUser] ✅ 方法执行完成\n");
\Workerman\Worker::safeEcho("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
return $relation->relation_id;
}
/**
* 移除用户的手机号(失效关联)
*
* @param string $userId 用户ID
* @param string $phoneNumber 手机号
* @param \DateTimeInterface|null $expireTime 过期时间(默认当前时间)
* @return bool 是否成功
*/
public function removePhoneFromUser(string $userId, string $phoneNumber, ?\DateTimeInterface $expireTime = null): bool
{
// 过滤非数字字符
$phoneNumber = $this->filterPhoneNumber(trim($phoneNumber));
if (empty($phoneNumber)) {
return false;
}
$phoneHash = EncryptionHelper::hash($phoneNumber);
$expireTime = $expireTime ?? new \DateTimeImmutable('now');
$relations = $this->phoneRelationRepository->newQuery()
->where('phone_hash', $phoneHash)
->where('user_id', $userId)
->where('is_active', true)
->where(function($q) use ($expireTime) {
$q->whereNull('expire_time')
->orWhere('expire_time', '>', $expireTime);
})
->get();
if ($relations->isEmpty()) {
return false;
}
foreach ($relations as $relation) {
$relation->expire_time = $expireTime;
$relation->is_active = false;
$relation->update_time = new \DateTimeImmutable('now');
$relation->save();
}
LoggerHelper::logBusiness('phone_relation_removed', [
'user_id' => $userId,
'phone_number' => $phoneNumber,
'expire_time' => $expireTime->format('Y-m-d H:i:s'),
]);
return true;
}
/**
* 根据手机号查找当前用户
*
* @param string $phoneNumber 手机号
* @param \DateTimeInterface|null $atTime 查询时间点(默认为当前时间)
* @return string|null 用户ID
*/
public function findUserByPhone(string $phoneNumber, ?\DateTimeInterface $atTime = null): ?string
{
// 过滤非数字字符
$phoneNumber = $this->filterPhoneNumber(trim($phoneNumber));
if (empty($phoneNumber)) {
return null;
}
$phoneHash = EncryptionHelper::hash($phoneNumber);
$relation = $this->phoneRelationRepository->findActiveByPhoneHash($phoneHash, $atTime);
return $relation ? $relation->user_id : null;
}
/**
* 获取用户的所有手机号
*
* @param string $userId 用户ID
* @param bool $includeHistory 是否包含历史记录
* @return array<array<string, mixed>> 手机号列表
*/
public function getUserPhones(string $userId, bool $includeHistory = false): array
{
$relations = $this->phoneRelationRepository->findByUserId($userId, $includeHistory);
return array_map(function($relation) {
return [
'phone_number' => $relation->phone_number,
'type' => $relation->type,
'is_verified' => $relation->is_verified,
'effective_time' => $relation->effective_time,
'expire_time' => $relation->expire_time,
'is_active' => $relation->is_active,
'source' => $relation->source,
];
}, $relations);
}
/**
* 获取用户的所有手机号号码(仅号码列表)
*
* @param string $userId 用户ID
* @param bool $includeHistory 是否包含历史记录
* @return array<string> 手机号列表
*/
public function getUserPhoneNumbers(string $userId, bool $includeHistory = false): array
{
$relations = $this->phoneRelationRepository->findByUserId($userId, $includeHistory);
return array_map(function($relation) {
return $relation->phone_number;
}, $relations);
}
/**
* 获取手机号的历史关联记录
*
* @param string $phoneNumber 手机号
* @return array<array<string, mixed>> 历史关联记录
*/
public function getPhoneHistory(string $phoneNumber): array
{
// 过滤非数字字符
$phoneNumber = $this->filterPhoneNumber(trim($phoneNumber));
if (empty($phoneNumber)) {
return [];
}
$phoneHash = EncryptionHelper::hash($phoneNumber);
$relations = $this->phoneRelationRepository->findHistoryByPhoneHash($phoneHash);
return array_map(function($relation) {
return [
'relation_id' => $relation->relation_id,
'user_id' => $relation->user_id,
'effective_time' => $relation->effective_time,
'expire_time' => $relation->expire_time,
'is_active' => $relation->is_active,
'type' => $relation->type,
'is_verified' => $relation->is_verified,
'source' => $relation->source,
];
}, $relations);
}
/**
* 检查手机号是否已被使用(当前有效)
*
* @param string $phoneNumber 手机号
* @return bool
*/
public function isPhoneInUse(string $phoneNumber): bool
{
// 过滤非数字字符
$phoneNumber = $this->filterPhoneNumber(trim($phoneNumber));
if (empty($phoneNumber)) {
return false;
}
$phoneHash = EncryptionHelper::hash($phoneNumber);
$relation = $this->phoneRelationRepository->findActiveByPhoneHash($phoneHash);
return $relation !== null;
}
/**
* 过滤手机号中的非数字字符
*
* @param string $phoneNumber 原始手机号
* @return string 过滤后的手机号(只包含数字)
*/
protected function filterPhoneNumber(string $phoneNumber): string
{
// 移除所有非数字字符
return preg_replace('/\D/', '', $phoneNumber);
}
/**
* 验证手机号格式(内部使用,假设已经过滤过非数字字符)
*
* @param string $phoneNumber 已过滤的手机号(只包含数字)
* @return bool
*/
protected function validatePhoneNumber(string $phoneNumber): bool
{
// 中国大陆手机号11位数字以1开头
return preg_match('/^1[3-9]\d{9}$/', $phoneNumber) === 1;
}
}