userInfo['id'] ?? 0; $companyId = $this->userInfo['companyId'] ?? 0; if (empty($userId) || empty($companyId)) { return json(['code' => 401, 'msg' => '请先登录']); } // 获取设备信息 $device = $this->device; if (empty($device) || empty($device['wechatId'])) { return json(['code' => 404, 'msg' => '设备未绑定微信']); } $wechatId = $device['wechatId']; // 获取微信账号ID $wechatAccount = Db::table('s2_wechat_account') ->where('wechatId', $wechatId) ->field('id') ->find(); if (empty($wechatAccount)) { return json(['code' => 404, 'msg' => '微信账号不存在']); } $accountId = $wechatAccount['id']; // 分页参数 $page = intval($this->request->param('page', 1)); $limit = intval($this->request->param('limit', 10)); $pageSize = intval($this->request->param('pageSize', 10)); if ($page <= 0) $page = 1; if ($limit <= 0) $limit = $pageSize > 0 ? $pageSize : 10; if ($limit > 100) $limit = 100; // 搜索关键词 $keyword = $this->request->param('keyword', ''); // 筛选条件 $status = $this->request->param('status', ''); // 状态:潜在、活跃、沉默、流失 $value = $this->request->param('value', ''); // 价值:高、中、低 $lifecycle = $this->request->param('lifecycle', ''); // 生命周期 // 构建查询条件 // 从流量池公司表查询,关联流量池总表和微信好友表 // 注意:s2_wechat_friend 表没有 ck_ 前缀,使用数组形式 join 可以避免自动添加前缀 $query = Db::name('traffic_pool_company') ->alias('tpc') ->join('traffic_pool tp', 'tp.id = tpc.poolId', 'left') ->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = tp.wechatId AND wf.ownerWechatId = \'' . $wechatId . '\'', 'left') ->where([ ['tpc.companyId', '=', $companyId], ['tpc.ownerAccountId', '=', $accountId], // 归属当前微信账号 ['tpc.status', '=', TrafficPoolCompany::STATUS_NORMAL], // 正常状态 ]); // 关键词搜索(昵称、微信号、手机号) if (!empty($keyword)) { $query->where(function($query) use ($keyword) { $query->where('tp.nickname', 'like', '%' . $keyword . '%') ->whereOr('tp.wechatAlias', 'like', '%' . $keyword . '%') ->whereOr('tp.mobile', 'like', '%' . $keyword . '%') ->whereOr('tpc.realName', 'like', '%' . $keyword . '%') ->whereOr('tpc.phone', 'like', '%' . $keyword . '%'); }); } // 状态筛选(根据生命周期) if (!empty($lifecycle)) { $lifecycleMap = [ '潜在' => TrafficPoolCompany::LIFECYCLE_NEW, '活跃' => TrafficPoolCompany::LIFECYCLE_FOLLOWING, '沉默' => TrafficPoolCompany::LIFECYCLE_SILENT, '流失' => TrafficPoolCompany::LIFECYCLE_LOST, ]; if (isset($lifecycleMap[$lifecycle])) { $query->where('tpc.lifecycle', '=', $lifecycleMap[$lifecycle]); } } // 价值筛选(根据意向度或等级) if (!empty($value)) { $valueMap = [ '高' => TrafficPoolCompany::INTENTION_HIGH, '中' => TrafficPoolCompany::INTENTION_MEDIUM, '低' => TrafficPoolCompany::INTENTION_LOW, ]; if (isset($valueMap[$value])) { $query->where('tpc.intentionLevel', '=', $valueMap[$value]); } } // 统计总数 $total = $query->count(); // 获取列表数据 $list = $query->field('tpc.id,tpc.poolId,tpc.companyId,tpc.ownerAccountId,tpc.realName,tpc.phone,tpc.email,tpc.lifecycle,tpc.intentionLevel,tpc.level,tpc.remark,tpc.createTime,tp.nickname,tp.avatar,tp.wechatId,tp.wechatAlias,tp.mobile,tp.gender,tp.region,tp.signature,wf.id as friendId,wf.alias as friendAlias,wf.nickname as friendNickname') ->order('tpc.id desc') ->page($page, $limit) ->select(); // 格式化数据 $result = []; foreach ($list as $item) { // 获取标签 $tags = Db::name('traffic_pool_tag') ->where([ ['poolCompanyId', '=', $item['id']], ['isDel', '=', 0] ]) ->column('tagName'); // 获取最后互动时间(从行为记录表) $lastBehavior = Db::name('traffic_pool_behavior') ->where('poolCompanyId', $item['id']) ->order('behaviorTime desc') ->find(); $lastContact = ''; if (!empty($lastBehavior) && !empty($lastBehavior['behaviorTime'])) { $lastContact = date('Y-m-d H:i:s', intval($lastBehavior['behaviorTime'])); } // 获取价值评估(从RFM或估值相关表,这里先使用模拟数据) $valuation = $this->calculateCustomerValuation($item['id']); // 状态映射 $lifecycleMap = [ TrafficPoolCompany::LIFECYCLE_NEW => '潜在', TrafficPoolCompany::LIFECYCLE_FOLLOWING => '活跃', TrafficPoolCompany::LIFECYCLE_CONVERTED => '已成交', TrafficPoolCompany::LIFECYCLE_SILENT => '沉默', TrafficPoolCompany::LIFECYCLE_LOST => '流失', ]; // 价值映射 $intentionMap = [ TrafficPoolCompany::INTENTION_HIGH => '高', TrafficPoolCompany::INTENTION_MEDIUM => '中', TrafficPoolCompany::INTENTION_LOW => '低', TrafficPoolCompany::INTENTION_UNKNOWN => '低', ]; $result[] = [ 'id' => intval($item['id']), 'poolCompanyId' => intval($item['id']), 'name' => $item['realName'] ?? $item['nickname'] ?? '未知', 'nickname' => $item['nickname'] ?? '', 'wechatId' => $item['wechatAlias'] ?? $item['wechatId'] ?? '', 'avatar' => $item['avatar'] ?? '', 'phone' => $item['phone'] ?? $item['mobile'] ?? '', 'email' => $item['email'] ?? '', 'status' => $lifecycleMap[$item['lifecycle'] ?? TrafficPoolCompany::LIFECYCLE_NEW] ?? '潜在', 'value' => $intentionMap[$item['intentionLevel'] ?? TrafficPoolCompany::INTENTION_UNKNOWN] ?? '低', 'tags' => $tags ?: [], 'lastContact' => $lastContact, 'nextFollow' => !empty($item['nextFollowTime']) && is_numeric($item['nextFollowTime']) ? date('Y-m-d', intval($item['nextFollowTime'])) : '', 'notes' => $item['remark'] ?? '', 'addedDate' => !empty($item['createTime']) && is_numeric($item['createTime']) ? date('Y-m-d', intval($item['createTime'])) : '', 'valuation' => $valuation, ]; } return json([ 'code' => 200, 'msg' => '获取成功', 'data' => [ 'list' => $result, 'total' => $total, 'page' => $page, 'limit' => $limit ] ]); } catch (\Exception $e) { Log::error('获取客户列表失败: ' . $e->getMessage()); return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]); } } /** * 获取客户详情 * GET /v2/store/customers/:id * * @return \think\response\Json */ public function detail() { try { $userId = $this->userInfo['id'] ?? 0; $companyId = $this->userInfo['companyId'] ?? 0; if (empty($userId) || empty($companyId)) { return json(['code' => 401, 'msg' => '请先登录']); } // 获取设备信息 $device = $this->device; if (empty($device) || empty($device['wechatId'])) { return json(['code' => 404, 'msg' => '设备未绑定微信']); } $wechatId = $device['wechatId']; // 获取微信账号ID $wechatAccount = Db::table('s2_wechat_account') ->where('wechatId', $wechatId) ->field('id') ->find(); if (empty($wechatAccount)) { return json(['code' => 404, 'msg' => '微信账号不存在']); } $accountId = $wechatAccount['id']; // 获取客户ID $customerId = intval($this->request->param('id', 0)); if (empty($customerId)) { return json(['code' => 400, 'msg' => '客户ID不能为空']); } // 查询客户详情 // 注意:s2_wechat_friend 表没有 ck_ 前缀,使用数组形式 join 可以避免自动添加前缀 $customer = Db::name('traffic_pool_company') ->alias('tpc') ->join('traffic_pool tp', 'tp.id = tpc.poolId', 'left') ->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = tp.wechatId AND wf.ownerWechatId = \'' . $wechatId . '\'', 'left') ->where([ ['tpc.id', '=', $customerId], ['tpc.companyId', '=', $companyId], ['tpc.ownerAccountId', '=', $accountId], ]) ->field('tpc.*,tp.*,wf.id as friendId,wf.alias as friendAlias,wf.nickname as friendNickname') ->find(); if (empty($customer)) { return json(['code' => 404, 'msg' => '客户不存在']); } // 获取标签 $tags = Db::name('traffic_pool_tag') ->where([ ['poolCompanyId', '=', $customerId], ['isDel', '=', 0] ]) ->column('tagName'); // 获取流量池标签(系统标签或微信标签) // 注意:从表结构看,isSystem字段在tagDefineId关联的标签定义表中 // 这里先获取所有标签,后续可以根据tagType区分 $allTags = Db::name('traffic_pool_tag') ->alias('tpt') ->join('traffic_pool_tag_define tptd', 'tpt.tagDefineId = tptd.id', 'left') ->where([ ['tpt.poolCompanyId', '=', $customerId], ['tpt.isDel', '=', 0] ]) ->field('tpt.tagName,tptd.isSystem') ->select(); $trafficPoolTags = []; foreach ($allTags as $tag) { // 系统标签或微信标签(tagType=1)作为流量池标签 if (!empty($tag['isSystem']) || (!empty($tag['tagType']) && $tag['tagType'] == 1)) { $trafficPoolTags[] = $tag['tagName']; } } // 获取来源信息 $sources = Db::name('traffic_pool_source') ->where('poolCompanyId', $customerId) ->order('createTime desc') ->select(); $sourceChannel = '未知'; $addTime = ''; if (!empty($sources)) { $firstSource = $sources[0]; $sourceChannel = $firstSource['sourceName'] ?? '未知'; $addTime = !empty($firstSource['createTime']) && is_numeric($firstSource['createTime']) ? date('Y-m-d', intval($firstSource['createTime'])) : ''; } // 获取互动统计 $interactionStats = $this->getInteractionStats($customerId); // 获取价值评估 $valueEvaluation = $this->getValueEvaluation($customerId); // 获取用户旅程(最近记录) $journey = $this->getCustomerJourney($customerId, 10); // 获取消费偏好(从行为记录分析) $preferences = $this->getCustomerPreferences($customerId); // 状态映射 $lifecycleMap = [ TrafficPoolCompany::LIFECYCLE_NEW => '潜在', TrafficPoolCompany::LIFECYCLE_FOLLOWING => '活跃', TrafficPoolCompany::LIFECYCLE_CONVERTED => '已成交', TrafficPoolCompany::LIFECYCLE_SILENT => '沉默', TrafficPoolCompany::LIFECYCLE_LOST => '流失', ]; $conversionStatus = $lifecycleMap[$customer['lifecycle'] ?? TrafficPoolCompany::LIFECYCLE_NEW] ?? '潜在'; // 生成首字母 $name = $customer['realName'] ?? $customer['nickname'] ?? '未知'; $initials = mb_substr($name, 0, 1, 'UTF-8'); return json([ 'code' => 200, 'msg' => '获取成功', 'data' => [ // 基础信息 'id' => intval($customer['id']), 'poolCompanyId' => intval($customer['id']), 'initials' => $initials, // 好友概览 'nickname' => $customer['nickname'] ?? '', 'remarkName' => $customer['realName'] ?? '', 'wechatId' => $customer['wechatAlias'] ?? $customer['wechatId'] ?? '', 'wechatPhone' => $customer['mobile'] ?? '', 'wechatLocation' => $customer['region'] ?? '', 'avatar' => $customer['avatar'] ?? '', 'conversionStatus' => $conversionStatus, 'sourceChannel' => $sourceChannel, 'addTime' => $addTime, // 基础信息 'realName' => $customer['realName'] ?? '', 'sex' => $this->getGenderText($customer['gender'] ?? 0), 'age' => $this->calculateAge($customer['birthday'] ?? ''), 'personalPhone' => $customer['phone'] ?? '', 'email' => $customer['email'] ?? '', 'idNumber' => $this->maskIdNumber($customer['idCard'] ?? ''), 'address' => $customer['address'] ?? '', // 标签 'tags' => $tags ?: [], 'trafficPoolTags' => $trafficPoolTags ?: [], // 互动统计 'interactionStats' => $interactionStats, // 价值评估 'valueEvaluation' => $valueEvaluation, 'valuationRank' => 'TOP 8%', // 需要计算 'valuationTrend' => '+12%', // 需要计算 // 用户旅程 'journey' => $journey, // 消费偏好 'preferences' => $preferences, // AI预测(需要实现) 'aiProfile' => [ 'summary' => '该用户为典型的高净值客户,消费频率高且偏好高端产品。', 'predictions' => [ '预计未来7天内有85%概率下单', '流失风险极低(5%),建议通过会员活动维持粘性', '最佳触达时间:工作日12:00-14:00或周末下午' ] ], ] ]); } catch (\Exception $e) { Log::error('获取客户详情失败: ' . $e->getMessage()); return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]); } } /** * 更新客户信息 * PUT /v2/store/customers/:id * * @return \think\response\Json */ public function update() { try { $userId = $this->userInfo['id'] ?? 0; $companyId = $this->userInfo['companyId'] ?? 0; if (empty($userId) || empty($companyId)) { return json(['code' => 401, 'msg' => '请先登录']); } // 获取设备信息 $device = $this->device; if (empty($device) || empty($device['wechatId'])) { return json(['code' => 404, 'msg' => '设备未绑定微信']); } $wechatId = $device['wechatId']; // 获取微信账号ID $wechatAccount = Db::table('s2_wechat_account') ->where('wechatId', $wechatId) ->field('id') ->find(); if (empty($wechatAccount)) { return json(['code' => 404, 'msg' => '微信账号不存在']); } $accountId = $wechatAccount['id']; // 获取客户ID $customerId = intval($this->request->param('id', 0)); if (empty($customerId)) { return json(['code' => 400, 'msg' => '客户ID不能为空']); } // 验证客户是否存在且归属当前账号 $customer = Db::name('traffic_pool_company') ->where([ ['id', '=', $customerId], ['companyId', '=', $companyId], ['ownerAccountId', '=', $accountId], ]) ->find(); if (empty($customer)) { return json(['code' => 404, 'msg' => '客户不存在']); } // 获取更新参数 $updateType = $this->request->param('updateType', ''); // wechat, personal, tags $updateData = []; $updateFields = []; // 更新微信资料 if ($updateType === 'wechat' || $this->request->has('remarkName')) { $remarkName = $this->request->param('remarkName', ''); if ($remarkName !== '') { $updateData['realName'] = $remarkName; // 备注名存储在realName字段 $updateFields[] = '备注名'; } } // 更新基础信息 if ($updateType === 'personal') { $realName = $this->request->param('realName', ''); $sex = $this->request->param('sex', ''); $age = $this->request->param('age', ''); $phone = $this->request->param('phone', ''); $email = $this->request->param('email', ''); $idNumber = $this->request->param('idNumber', ''); $address = $this->request->param('address', ''); if ($realName !== '') { $updateData['realName'] = $realName; $updateFields[] = '姓名'; } if ($sex !== '') { $updateData['gender'] = $sex === '男' ? 1 : ($sex === '女' ? 2 : 0); $updateFields[] = '性别'; } if ($age !== '') { // 根据年龄计算生日(简化处理) $birthYear = date('Y') - intval($age); $updateData['birthday'] = $birthYear . '-01-01'; $updateFields[] = '年龄'; } if ($phone !== '') { $updateData['phone'] = $phone; $updateFields[] = '手机号'; } if ($email !== '') { $updateData['email'] = $email; $updateFields[] = '邮箱'; } if ($idNumber !== '') { $updateData['idCard'] = $idNumber; $updateFields[] = '身份证号'; } if ($address !== '') { $updateData['address'] = $address; $updateFields[] = '住址'; } } // 更新标签 if ($updateType === 'tags') { $tags = $this->request->param('tags', []); if (is_array($tags)) { // 获取客户信息(用于获取identifier和companyId) $customerInfo = Db::name('traffic_pool_company') ->where('id', $customerId) ->field('identifier,companyId') ->find(); if (!empty($customerInfo)) { // 软删除旧标签(只删除站内标签,保留微信标签和系统标签) // 通过关联标签定义表判断是否为站内标签 Db::name('traffic_pool_tag') ->alias('tpt') ->join('traffic_pool_tag_define tptd', 'tpt.tagDefineId = tptd.id', 'left') ->where([ ['tpt.poolCompanyId', '=', $customerId], ['tptd.tagType', '=', 2], // 站内标签 ['tpt.isDel', '=', 0] ]) ->update([ 'tpt.isDel' => 1, 'tpt.deleteTime' => time() ]); // 添加新标签(站内标签) foreach ($tags as $tag) { if (!empty($tag)) { // 查找或创建标签定义 $tagDefine = Db::name('traffic_pool_tag_define') ->where([ ['companyId', 'in', [$companyId, 0]], ['tagName', '=', $tag], ['tagType', '=', 2], // 站内标签 ['isDel', '=', 0] ]) ->order('companyId desc') // 优先使用公司自定义标签 ->find(); if (empty($tagDefine)) { // 创建标签定义 $tagDefineId = Db::name('traffic_pool_tag_define')->insertGetId([ 'companyId' => $companyId, 'tagType' => 2, // 站内标签 'tagCode' => 'custom_' . time() . '_' . rand(1000, 9999), 'tagName' => $tag, 'isSystem' => 0, 'status' => 1, 'createTime' => time(), ]); } else { $tagDefineId = $tagDefine['id']; } // 检查标签是否已存在 $existTag = Db::name('traffic_pool_tag') ->where([ ['poolCompanyId', '=', $customerId], ['tagDefineId', '=', $tagDefineId], ['isDel', '=', 0] ]) ->find(); if (empty($existTag)) { Db::name('traffic_pool_tag')->insert([ 'poolCompanyId' => $customerId, 'identifier' => $customerInfo['identifier'], 'companyId' => $customerInfo['companyId'], 'tagDefineId' => $tagDefineId, 'tagType' => 2, // 站内标签 'tagName' => $tag, 'source' => 1, // 手动 'operatorId' => $userId, 'createTime' => time(), ]); } } } } $updateFields[] = '标签'; } } // 更新客户信息 if (!empty($updateData)) { $updateData['updateTime'] = time(); Db::name('traffic_pool_company') ->where('id', $customerId) ->update($updateData); } return json([ 'code' => 200, 'msg' => '更新成功', 'data' => [ 'updatedFields' => $updateFields ] ]); } catch (\Exception $e) { Log::error('更新客户信息失败: ' . $e->getMessage()); return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]); } } /** * 计算客户估值 * * @param int $poolCompanyId 客户ID * @return int */ private function calculateCustomerValuation($poolCompanyId) { // TODO: 实现真实的估值计算逻辑 // 可以从订单表、行为记录表等计算 return 50000; // 模拟数据 } /** * 获取互动统计 * * @param int $poolCompanyId 客户ID * @return array */ private function getInteractionStats($poolCompanyId) { // 统计聊天消息数 $chatCount = Db::name('traffic_pool_behavior') ->where([ ['poolCompanyId', '=', $poolCompanyId], ['behaviorType', '=', 1] // 发送消息 ]) ->count(); // 统计朋友圈互动数 $momentsCount = Db::name('traffic_pool_behavior') ->where([ ['poolCompanyId', '=', $poolCompanyId], ['behaviorType', 'in', [9, 10]] // 点赞朋友圈、评论朋友圈 ]) ->count(); // 统计红包转账总额(从行为记录中获取) $redPacketTotal = Db::name('traffic_pool_behavior') ->where([ ['poolCompanyId', '=', $poolCompanyId], ['behaviorType', '=', 7] // 支付 ]) ->sum('amount'); $redPacketTotal = round(floatval($redPacketTotal ?? 0), 2); // 计算活跃度评分(简化计算) $activeScore = min(100, ($chatCount * 2 + $momentsCount * 3 + $redPacketTotal / 10)); // 获取最后互动时间 $lastBehavior = Db::name('traffic_pool_behavior') ->where('poolCompanyId', $poolCompanyId) ->order('behaviorTime desc') ->find(); $lastInteraction = '从未互动'; if (!empty($lastBehavior) && !empty($lastBehavior['behaviorTime'])) { $time = intval($lastBehavior['behaviorTime']); $diff = time() - $time; if ($diff < 3600) { $lastInteraction = '刚刚'; } elseif ($diff < 86400) { $lastInteraction = '今天 ' . date('H:i', $time); } elseif ($diff < 172800) { $lastInteraction = '昨天 ' . date('H:i', $time); } else { $lastInteraction = date('Y-m-d H:i', $time); } } return [ 'lastInteraction' => $lastInteraction, 'chatCount' => intval($chatCount), 'momentsCount' => intval($momentsCount), 'redPacketTotal' => number_format($redPacketTotal, 2), 'activeScore' => intval($activeScore) ]; } /** * 获取价值评估 * * @param int $poolCompanyId 客户ID * @return array */ private function getValueEvaluation($poolCompanyId) { // TODO: 实现真实的价值评估计算 // 可以从RFM模型、CLV模型、社交裂变模型等计算 return [ 'totalValuation' => 58600, 'models' => [ [ 'name' => 'RFM 贡献模型', 'value' => 52000, 'weight' => 0.5, 'score' => 92 ], [ 'name' => 'CLV 终身价值模型', 'value' => 78000, 'weight' => 0.3, 'score' => 88 ], [ 'name' => '社交/裂变模型', 'value' => 15000, 'weight' => 0.2, 'score' => 75 ] ] ]; } /** * 获取用户旅程 * * @param int $poolCompanyId 客户ID * @param int $limit 限制数量 * @return array */ private function getCustomerJourney($poolCompanyId, $limit = 10) { // 从行为记录表获取 $behaviors = Db::name('traffic_pool_behavior') ->where('poolCompanyId', $poolCompanyId) ->order('behaviorTime desc') ->limit($limit) ->select(); $journey = []; $typeMap = [ 1 => '发送消息', 2 => '接收消息', 3 => '浏览', 4 => '点击', 5 => '咨询', 6 => '下单', 7 => '支付', 8 => '退款', 9 => '点赞朋友圈', 10 => '评论朋友圈', ]; foreach ($behaviors as $behavior) { $type = $typeMap[$behavior['behaviorType']] ?? '未知行为'; $content = $behavior['behaviorName'] ?? $type; if (!empty($behavior['targetName'])) { $content .= ': ' . $behavior['targetName']; } $journey[] = [ 'type' => $type, 'content' => $content, 'time' => !empty($behavior['behaviorTime']) && is_numeric($behavior['behaviorTime']) ? date('Y-m-d H:i:s', intval($behavior['behaviorTime'])) : '', 'source' => '存客宝', 'actionType' => $this->getActionType($behavior['behaviorType']), 'amount' => !empty($behavior['amount']) && floatval($behavior['amount']) > 0 ? '¥' . number_format(floatval($behavior['amount']), 2) : '', ]; } return $journey; } /** * 获取行为类型 * * @param int $behaviorType 行为类型 * @return string */ private function getActionType($behaviorType) { if (in_array($behaviorType, [6, 7, 8])) { return 'transaction'; // 交易 } elseif (in_array($behaviorType, [1, 2, 9, 10])) { return 'social'; // 社交 } elseif (in_array($behaviorType, [3, 4, 5])) { return 'footprint'; // 轨迹 } else { return 'flow'; // 流量 } } /** * 获取消费偏好 * * @param int $poolCompanyId 客户ID * @return array */ private function getCustomerPreferences($poolCompanyId) { // TODO: 从行为记录和订单记录分析消费偏好 return [ 'categories' => ['智能数码', '精品咖啡', '商务休闲'], 'recentItems' => ['iPhone 16 Pro', 'iPad Air'], 'coreInterest' => '数码发烧友 & 品质生活追求者' ]; } /** * 获取性别文本 * * @param int $gender 性别代码 * @return string */ private function getGenderText($gender) { $map = [ 0 => '保密', 1 => '男', 2 => '女', ]; return $map[$gender] ?? '未知'; } /** * 计算年龄 * * @param string $birthday 生日 * @return int */ private function calculateAge($birthday) { if (empty($birthday)) { return 0; } $birthTimestamp = strtotime($birthday); if ($birthTimestamp === false) { return 0; } $age = date('Y') - date('Y', $birthTimestamp); if (date('md', $birthTimestamp) > date('md')) { $age--; } return $age; } /** * 脱敏身份证号 * * @param string $idNumber 身份证号 * @return string */ private function maskIdNumber($idNumber) { if (empty($idNumber) || strlen($idNumber) < 8) { return $idNumber; } return substr($idNumber, 0, 4) . str_repeat('*', strlen($idNumber) - 8) . substr($idNumber, -4); } }