Files
cunkebao_v3/Moncter/app/service/UserPhoneService.php
2026-01-05 10:16:20 +08:00

538 lines
27 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}