From adc22c7d1973783321baf0ec08638560400420ca Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Tue, 4 Nov 2025 17:48:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/ai/config/route.php | 2 +- Server/application/ai/controller/OpenAi.php | 12 +- Server/application/chukebao/config/route.php | 12 +- .../chukebao/controller/AiPushController.php | 505 ++++++++++++++++++ .../controller/AutoGreetingsController.php | 409 ++++++++++++-- Server/application/chukebao/model/AiPush.php | 17 + .../chukebao/model/AiPushRecord.php | 16 + Server/application/common/TaskServer.php | 8 + .../common/controller/PaymentService.php | 2 - .../store/controller/LoginController.php | 3 + .../Adapters/ChuKeBao/Adapter.php | 496 +++++++++++++++++ 11 files changed, 1444 insertions(+), 38 deletions(-) create mode 100644 Server/application/chukebao/controller/AiPushController.php create mode 100644 Server/application/chukebao/model/AiPush.php create mode 100644 Server/application/chukebao/model/AiPushRecord.php diff --git a/Server/application/ai/config/route.php b/Server/application/ai/config/route.php index 4fd9f4f6..704b7d09 100644 --- a/Server/application/ai/config/route.php +++ b/Server/application/ai/config/route.php @@ -7,7 +7,7 @@ Route::group('v1/ai', function () { //openai、chatGPT Route::group('openai', function () { - Route::post('text', 'app\ai\controller\OpenAi@text'); + Route::post('text', 'app\ai\controller\OpenAI@text'); }); diff --git a/Server/application/ai/controller/OpenAi.php b/Server/application/ai/controller/OpenAi.php index b71491f6..ac0ff245 100644 --- a/Server/application/ai/controller/OpenAi.php +++ b/Server/application/ai/controller/OpenAi.php @@ -3,14 +3,19 @@ namespace app\ai\controller; use think\facade\Env; -class OpenAI +use think\Controller; + + +class OpenAI extends Controller { protected $apiUrl; protected $apiKey; protected $headers; - public function __init() + public function __construct() { + parent::__construct(); + $this->apiUrl = Env::get('openAi.apiUrl'); $this->apiKey = Env::get('openAi.apiKey'); @@ -24,7 +29,7 @@ class OpenAI public function text() { - $this->__init(); + $params = [ 'model' => 'gpt-3.5-turbo-0125', 'input' => 'DHA 从孕期到出生到老年都需要,助力大脑发育🧠/减缓脑压力有助记忆/给大脑动力#贝蒂喜藻油DHA 双标认证每粒 150毫克,高含量、高性价比从小吃到老,长期吃更健康 重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除' @@ -46,7 +51,6 @@ class OpenAI */ public function bedtimeStory() { - $this->__init(); // API请求参数 $params = [ diff --git a/Server/application/chukebao/config/route.php b/Server/application/chukebao/config/route.php index 491f16e4..353f4877 100644 --- a/Server/application/chukebao/config/route.php +++ b/Server/application/chukebao/config/route.php @@ -141,8 +141,18 @@ Route::group('v1/', function () { Route::get('stats', 'app\chukebao\controller\AutoGreetingsController@stats'); }); + //AI智能推送 + Route::group('aiPush/', function () { + Route::get('list', 'app\chukebao\controller\AiPushController@getList'); // 获取推送列表 + Route::post('add', 'app\chukebao\controller\AiPushController@add'); // 添加推送 + Route::get('details', 'app\chukebao\controller\AiPushController@details'); // 推送详情 + Route::delete('del', 'app\chukebao\controller\AiPushController@del'); // 删除推送 + Route::post('update', 'app\chukebao\controller\AiPushController@update'); // 更新推送 + Route::get('setStatus', 'app\chukebao\controller\AiPushController@setStatus'); // 修改状态 + Route::get('stats', 'app\chukebao\controller\AiPushController@stats'); // 统计概览 + }); + - //自动问候 Route::group('notice/', function () { Route::get('list', 'app\chukebao\controller\NoticeController@getList'); Route::put('readMessage', 'app\chukebao\controller\NoticeController@readMessage'); diff --git a/Server/application/chukebao/controller/AiPushController.php b/Server/application/chukebao/controller/AiPushController.php new file mode 100644 index 00000000..b1cecbd6 --- /dev/null +++ b/Server/application/chukebao/controller/AiPushController.php @@ -0,0 +1,505 @@ +request->param('page', 1); + $limit = $this->request->param('limit', 10); + $keyword = $this->request->param('keyword', ''); + $userId = $this->getUserInfo('id'); + $companyId = $this->getUserInfo('companyId'); + + $where = [ + ['companyId', '=', $companyId], + ['userId', '=', $userId], + ['isDel', '=', 0], + ]; + + if (!empty($keyword)) { + $where[] = ['name', 'like', '%' . $keyword . '%']; + } + + $query = AiPush::where($where); + $total = $query->count(); + $list = $query->where($where)->page($page, $limit)->order('id desc')->select(); + + // 处理数据 + $list = is_array($list) ? $list : $list->toArray(); + foreach ($list as &$item) { + // 解析标签数组 + $item['tags'] = json_decode($item['tags'], true); + if (!is_array($item['tags'])) { + $item['tags'] = []; + } + // 格式化推送时机显示文本 + $timingTypes = [ + 1 => '立即推送', + 2 => 'AI最佳时机', + 3 => '定时推送' + ]; + $item['timingText'] = $timingTypes[$item['pushTiming']] ?? '未知'; + // 处理定时推送时间 + if ($item['pushTiming'] == 3 && !empty($item['scheduledTime'])) { + $item['scheduledTime'] = date('Y-m-d H:i:s', $item['scheduledTime']); + } else { + $item['scheduledTime'] = ''; + } + // 从记录表计算实际成功率 + $pushId = $item['id']; + $totalCount = Db::name('kf_ai_push_record') + ->where('pushId', $pushId) + ->count(); + $sendCount = Db::name('kf_ai_push_record') + ->where('pushId', $pushId) + ->where('isSend', 1) + ->count(); + $item['successRate'] = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 1) : 0; + $item['totalPushCount'] = $totalCount; // 推送总数 + $item['sendCount'] = $sendCount; // 成功发送数 + } + unset($item); + + return ResponseHelper::success(['list' => $list, 'total' => $total]); + } + + /** + * 添加 + * @return \think\response\Json + * @throws \Exception + */ + public function add() + { + $name = $this->request->param('name', ''); + $tags = $this->request->param('tags', ''); // 标签,支持逗号分隔的字符串或数组 + $content = $this->request->param('content', ''); + $pushTiming = $this->request->param('pushTiming', 1); // 1=立即推送,2=最佳时机(AI决定),3=定时推送 + $scheduledTime = $this->request->param('scheduledTime', ''); // 定时推送的时间 + $status = $this->request->param('status', 1); + $userId = $this->getUserInfo('id'); + $companyId = $this->getUserInfo('companyId'); + + if (empty($name) || empty($content)) { + return ResponseHelper::error('推送名称和推送内容不能为空'); + } + + // 验证推送时机 + if (!in_array($pushTiming, [1, 2, 3])) { + return ResponseHelper::error('无效的推送时机类型'); + } + + // 如果是定时推送,需要验证时间 + if ($pushTiming == 3) { + if (empty($scheduledTime)) { + return ResponseHelper::error('定时推送需要设置推送时间'); + } + // 验证时间格式 + $timestamp = strtotime($scheduledTime); + if ($timestamp === false || $timestamp <= time()) { + return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间'); + } + } else { + $scheduledTime = ''; + } + + // 处理标签 + $tagsArray = []; + if (!empty($tags)) { + if (is_string($tags)) { + // 如果是字符串,按逗号分割 + $tagsArray = array_filter(array_map('trim', explode(',', $tags))); + } elseif (is_array($tags)) { + $tagsArray = array_filter(array_map('trim', $tags)); + } + } + + if (empty($tagsArray)) { + return ResponseHelper::error('目标用户标签不能为空'); + } + + Db::startTrans(); + try { + $aiPush = new AiPush(); + $aiPush->name = $name; + $aiPush->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE); + $aiPush->content = $content; + $aiPush->pushTiming = $pushTiming; + $aiPush->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0; + $aiPush->status = $status; + $aiPush->successRate = 0; // 初始成功率为0 + $aiPush->userId = $userId; + $aiPush->companyId = $companyId; + $aiPush->createTime = time(); + $aiPush->updateTime = time(); + $aiPush->save(); + Db::commit(); + return ResponseHelper::success(['id' => $aiPush->id], '创建成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('创建失败:' . $e->getMessage()); + } + } + + /** + * 详情 + * @return \think\response\Json + * @throws \Exception + */ + public function details() + { + $id = $this->request->param('id', ''); + $userId = $this->getUserInfo('id'); + $companyId = $this->getUserInfo('companyId'); + + if (empty($id)) { + return ResponseHelper::error('参数缺失'); + } + + $data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find(); + if (empty($data)) { + return ResponseHelper::error('该推送已被删除或者不存在'); + } + + $data = $data->toArray(); + // 解析标签数组 + $data['tags'] = json_decode($data['tags'], true); + if (!is_array($data['tags'])) { + $data['tags'] = []; + } + // 标签转为逗号分隔的字符串(用于编辑时回显) + $data['tagsString'] = implode(',', $data['tags']); + + // 处理定时推送时间 + if ($data['pushTiming'] == 3 && !empty($data['scheduledTime'])) { + $data['scheduledTime'] = date('Y-m-d H:i:s', $data['scheduledTime']); + } else { + $data['scheduledTime'] = ''; + } + + // 成功率保留一位小数 + $data['successRate'] = isset($data['successRate']) ? round($data['successRate'], 1) : 0; + + return ResponseHelper::success($data, '获取成功'); + } + + /** + * 删除 + * @return \think\response\Json + * @throws \Exception + */ + public function del() + { + $id = $this->request->param('id', ''); + $userId = $this->getUserInfo('id'); + $companyId = $this->getUserInfo('companyId'); + + if (empty($id)) { + return ResponseHelper::error('参数缺失'); + } + + $data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find(); + if (empty($data)) { + return ResponseHelper::error('该推送已被删除或者不存在'); + } + + Db::startTrans(); + try { + $data->isDel = 1; + $data->delTime = time(); + $data->save(); + Db::commit(); + return ResponseHelper::success('', '删除成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('删除失败:' . $e->getMessage()); + } + } + + /** + * 更新 + * @return \think\response\Json + * @throws \Exception + */ + public function update() + { + $id = $this->request->param('id', ''); + $name = $this->request->param('name', ''); + $tags = $this->request->param('tags', ''); + $content = $this->request->param('content', ''); + $pushTiming = $this->request->param('pushTiming', 1); + $scheduledTime = $this->request->param('scheduledTime', ''); + $status = $this->request->param('status', 1); + $userId = $this->getUserInfo('id'); + $companyId = $this->getUserInfo('companyId'); + + if (empty($id) || empty($name) || empty($content)) { + return ResponseHelper::error('参数缺失'); + } + + // 验证推送时机 + if (!in_array($pushTiming, [1, 2, 3])) { + return ResponseHelper::error('无效的推送时机类型'); + } + + // 如果是定时推送,需要验证时间 + if ($pushTiming == 3) { + if (empty($scheduledTime)) { + return ResponseHelper::error('定时推送需要设置推送时间'); + } + // 验证时间格式 + $timestamp = strtotime($scheduledTime); + if ($timestamp === false || $timestamp <= time()) { + return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间'); + } + } else { + $scheduledTime = ''; + } + + // 处理标签 + $tagsArray = []; + if (!empty($tags)) { + if (is_string($tags)) { + $tagsArray = array_filter(array_map('trim', explode(',', $tags))); + } elseif (is_array($tags)) { + $tagsArray = array_filter(array_map('trim', $tags)); + } + } + + if (empty($tagsArray)) { + return ResponseHelper::error('目标用户标签不能为空'); + } + + $query = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find(); + if (empty($query)) { + return ResponseHelper::error('该推送已被删除或者不存在'); + } + + Db::startTrans(); + try { + $query->name = $name; + $query->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE); + $query->content = $content; + $query->pushTiming = $pushTiming; + $query->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0; + $query->status = $status; + $query->updateTime = time(); + $query->save(); + Db::commit(); + return ResponseHelper::success('', '修改成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('修改失败:' . $e->getMessage()); + } + } + + /** + * 修改状态 + * @return \think\response\Json + * @throws \Exception + */ + public function setStatus() + { + $id = $this->request->param('id', ''); + $status = $this->request->param('status', 1); + $userId = $this->getUserInfo('id'); + $companyId = $this->getUserInfo('companyId'); + + if (empty($id)) { + return ResponseHelper::error('参数缺失'); + } + + if (!in_array($status, [0, 1])) { + return ResponseHelper::error('状态值无效'); + } + + $data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find(); + if (empty($data)) { + return ResponseHelper::error('该推送已被删除或者不存在'); + } + + Db::startTrans(); + try { + $data->status = $status; + $data->updateTime = time(); + $data->save(); + Db::commit(); + return ResponseHelper::success('', $status == 1 ? '启用成功' : '禁用成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('操作失败:' . $e->getMessage()); + } + } + + /** + * 统计概览(整合自动问候和AI推送) + * - 活跃规则(自动问候规则,近30天) + * - 总触发次数(自动问候记录总数) + * - AI推送成功率(AI推送的成功率) + * - AI智能推送(AI推送规则,近30天活跃) + * - 规则效果排行(自动问候规则,按使用次数排序) + * @return \think\response\Json + */ + public function stats() + { + $companyId = $this->getUserInfo('companyId'); + $userId = $this->getUserInfo('id'); + + $start30d = time() - 30 * 24 * 3600; + + try { + // 公司维度(用于除排行外的统计) + $companyWhere = [ + ['companyId', '=', $companyId], + ]; + // 排行维度(限定个人) + $rankingWhere = [ + ['companyId', '=', $companyId], + ['userId', '=', $userId], + ]; + + // ========== 自动问候统计 ========== + + // 1) 活跃规则(自动问候规则,近30天有记录的) + $activeRules = Db::name('kf_auto_greetings_record') + ->where($companyWhere) + ->where('createTime', '>=', $start30d) + ->distinct(true) + ->count('autoId'); + + // 2) 总触发次数(自动问候记录总数) + $totalTriggers = Db::name('kf_auto_greetings_record') + ->where($companyWhere) + ->count(); + + // ========== AI推送统计 ========== + + // 3) AI推送成功率 + $totalPushes = Db::name('kf_ai_push_record') + ->where($companyWhere) + ->count(); + $sendCount = Db::name('kf_ai_push_record') + ->where($companyWhere) + ->where('isSend', '=', 1) + ->count(); + // 成功率:百分比,保留整数(75%) + $aiPushSuccessRate = $totalPushes > 0 ? round(($sendCount * 100) / $totalPushes, 0) : 0; + + // 4) AI智能推送(AI推送规则,近30天活跃的) + $aiPushCount = Db::name('kf_ai_push_record') + ->where($companyWhere) + ->where('createTime', '>=', $start30d) + ->distinct(true) + ->count('pushId'); + + // ========== 规则效果排行(自动问候规则,按使用次数排序)========== + $ruleRanking = Db::name('kf_auto_greetings_record') + ->where($rankingWhere) + ->field([ + 'autoId AS id', + 'COUNT(*) AS usageCount' + ]) + ->group('autoId') + ->order('usageCount DESC') + ->limit(20) + ->select(); + + // 附加规则名称和触发类型 + $autoIds = array_values(array_unique(array_column($ruleRanking, 'id'))); + $autoIdToRule = []; + if (!empty($autoIds)) { + $rules = AutoGreetings::where([['id', 'in', $autoIds]]) + ->field('id,name,trigger') + ->select(); + foreach ($rules as $rule) { + $triggerTypes = [ + 1 => '新好友', + 2 => '首次发消息', + 3 => '时间触发', + 4 => '关键词', + 5 => '生日触发', + 6 => '自定义' + ]; + $autoIdToRule[$rule['id']] = [ + 'name' => $rule['name'], + 'trigger' => $rule['trigger'], + 'triggerText' => $triggerTypes[$rule['trigger']] ?? '未知', + ]; + } + } + + foreach ($ruleRanking as &$row) { + $row['usageCount'] = (int)($row['usageCount'] ?? 0); + $row['name'] = $autoIdToRule[$row['id']]['name'] ?? ''; + $row['trigger'] = $autoIdToRule[$row['id']]['trigger'] ?? null; + $row['triggerText'] = $autoIdToRule[$row['id']]['triggerText'] ?? ''; + // 格式化使用次数显示 + $row['usageCountText'] = $row['usageCount'] . ' 次'; + } + unset($row); + + // 更新主表中的成功率字段(异步或定期更新) + $this->updatePushSuccessRate($companyId); + + return ResponseHelper::success([ + 'activeRules' => (int)$activeRules, + 'totalTriggers' => (int)$totalTriggers, + 'aiPushSuccessRate' => (int)$aiPushSuccessRate, + 'aiPushCount' => (int)$aiPushCount, + 'ruleRanking' => $ruleRanking, + ], '统计成功'); + } catch (\Exception $e) { + return ResponseHelper::error('统计失败:' . $e->getMessage()); + } + } + + /** + * 更新推送表的成功率字段 + * @param int $companyId + * @return void + */ + private function updatePushSuccessRate($companyId) + { + try { + // 获取所有启用的推送 + $pushes = AiPush::where([ + ['companyId', '=', $companyId], + ['isDel', '=', 0] + ])->field('id')->select(); + + foreach ($pushes as $push) { + $pushId = $push['id']; + $totalCount = Db::name('kf_ai_push_record') + ->where('pushId', $pushId) + ->count(); + $sendCount = Db::name('kf_ai_push_record') + ->where('pushId', $pushId) + ->where('isSend', 1) + ->count(); + + $successRate = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 2) : 0.00; + + AiPush::where('id', $pushId)->update([ + 'successRate' => $successRate, + 'updateTime' => time() + ]); + } + } catch (\Exception $e) { + // 静默失败,不影响主流程 + } + } +} + diff --git a/Server/application/chukebao/controller/AutoGreetingsController.php b/Server/application/chukebao/controller/AutoGreetingsController.php index 88b66754..ef186b3c 100644 --- a/Server/application/chukebao/controller/AutoGreetingsController.php +++ b/Server/application/chukebao/controller/AutoGreetingsController.php @@ -9,15 +9,19 @@ use think\Db; class AutoGreetingsController extends BaseController { + /** + * 获取问候规则列表 + * @return \think\response\Json + */ public function getList(){ $page = $this->request->param('page', 1); $limit = $this->request->param('limit', 10); $keyword = $this->request->param('keyword', ''); $is_template = $this->request->param('is_template', 0); + $triggerType = $this->request->param('triggerType', ''); // 触发类型筛选 $userId = $this->getUserInfo('id'); $companyId = $this->getUserInfo('companyId'); - if($is_template == 1){ $where = [ ['is_template','=',1], @@ -31,20 +35,46 @@ class AutoGreetingsController extends BaseController ]; } - - - if(!empty($keyword)){ $where[] = ['name','like','%'.$keyword.'%']; } + if(!empty($triggerType)){ + $where[] = ['trigger','=',$triggerType]; + } + $query = AutoGreetings::where($where); $total = $query->count(); - $list = $query->where($where)->page($page,$limit)->order('id desc')->select(); + $list = $query->where($where)->page($page,$limit)->order('level asc,id desc')->select(); + // 获取使用次数 + $list = is_array($list) ? $list : $list->toArray(); + $ids = array_column($list, 'id'); + $usageCounts = []; + if (!empty($ids)) { + $counts = Db::name('kf_auto_greetings_record') + ->where('autoId', 'in', $ids) + ->field('autoId, COUNT(*) as count') + ->group('autoId') + ->select(); + foreach ($counts as $count) { + $usageCounts[$count['autoId']] = (int)$count['count']; + } + } foreach ($list as &$item) { - $item['trigger'] = json_decode($item['trigger'],true); + $item['condition'] = json_decode($item['condition'], true); + $item['usageCount'] = $usageCounts[$item['id']] ?? 0; + // 格式化触发类型显示文本 + $triggerTypes = [ + 1 => '新好友', + 2 => '首次发消息', + 3 => '时间触发', + 4 => '关键词触发', + 5 => '生日触发', + 6 => '自定义' + ]; + $item['triggerText'] = $triggerTypes[$item['trigger']] ?? '未知'; } unset($item); @@ -52,6 +82,315 @@ class AutoGreetingsController extends BaseController } + /** + * 校验trigger类型对应的condition + * @param int $trigger 触发类型 + * @param mixed $condition 条件参数 + * @return array|string 返回处理后的condition数组,或错误信息字符串 + */ + private function validateTriggerCondition($trigger, $condition) + { + // trigger类型:1=新好友,2=首次发消息,3=时间触发,4=关键词触发,5=生日触发,6=自定义 + switch ($trigger) { + case 1: // 新好友 + // 不需要condition + return []; + + case 2: // 首次发消息 + // 不需要condition + return []; + + case 3: // 时间触发 + // 需要condition,格式为:{"type": "daily_time|yearly_datetime|fixed_range|workday", "value": "..."} + if (empty($condition)) { + return '时间触发类型需要配置具体的触发条件'; + } + $condition = is_array($condition) ? $condition : json_decode($condition, true); + if (empty($condition) || !is_array($condition)) { + return '时间触发类型的条件格式不正确,应为数组格式'; + } + + // 验证必须包含type字段 + if (!isset($condition['type']) || empty($condition['type'])) { + return '时间触发类型必须指定触发方式:daily_time(每天固定时间)、yearly_datetime(每年固定日期时间)、fixed_range(固定时间段)、workday(工作日)'; + } + + $timeType = $condition['type']; + $allowedTypes = ['daily_time', 'yearly_datetime', 'fixed_range', 'workday']; + // 兼容旧版本的 fixed_time,自动转换为 daily_time + if ($timeType === 'fixed_time') { + $timeType = 'daily_time'; + } + if (!in_array($timeType, $allowedTypes)) { + return '时间触发类型无效,必须为:daily_time(每天固定时间)、yearly_datetime(每年固定日期时间)、fixed_range(固定时间段)、workday(工作日)'; + } + + // 根据不同的type验证value + switch ($timeType) { + case 'daily_time': // 每天固定时间(每天的几点几分) + // value应该是时间字符串,格式:HH:mm,如 "14:30" + if (!isset($condition['value']) || empty($condition['value'])) { + return '每天固定时间类型需要配置具体时间,格式:HH:mm(如 14:30)'; + } + $timeValue = $condition['value']; + if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) { + return '每天固定时间格式不正确,应为 HH:mm 格式(如 14:30)'; + } + return [ + 'type' => 'daily_time', + 'value' => $timeValue + ]; + + case 'yearly_datetime': // 每年固定日期时间(每年的几月几号几点几分) + // value应该是日期时间字符串,格式:MM-dd HH:mm,如 "12-25 14:30" + if (!isset($condition['value']) || empty($condition['value'])) { + return '每年固定日期时间类型需要配置具体日期和时间,格式:MM-dd HH:mm(如 12-25 14:30)'; + } + $datetimeValue = $condition['value']; + // 验证格式:MM-dd HH:mm + if (!preg_match('/^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) ([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $datetimeValue)) { + return '每年固定日期时间格式不正确,应为 MM-dd HH:mm 格式(如 12-25 14:30)'; + } + // 进一步验证日期是否有效(例如2月30日不存在) + list($datePart, $timePart) = explode(' ', $datetimeValue); + list($month, $day) = explode('-', $datePart); + if (!checkdate((int)$month, (int)$day, 2000)) { // 使用2000年作为参考年份验证日期有效性 + return '日期无效,请检查月份和日期是否正确(如2月不能有30日)'; + } + return [ + 'type' => 'yearly_datetime', + 'value' => $datetimeValue + ]; + + case 'fixed_range': // 固定时间段 + // value应该是时间段数组,格式:["09:00", "18:00"] + if (!isset($condition['value']) || !is_array($condition['value'])) { + return '固定时间段类型需要配置时间段,格式:["开始时间", "结束时间"](如 ["09:00", "18:00"])'; + } + $rangeValue = $condition['value']; + if (count($rangeValue) !== 2) { + return '固定时间段应为包含两个时间点的数组,格式:["09:00", "18:00"]'; + } + // 验证时间格式 + foreach ($rangeValue as $time) { + if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) { + return '时间段格式不正确,应为 HH:mm 格式(如 09:00)'; + } + } + // 验证开始时间小于结束时间 + $startTime = strtotime('2000-01-01 ' . $rangeValue[0]); + $endTime = strtotime('2000-01-01 ' . $rangeValue[1]); + if ($startTime >= $endTime) { + return '开始时间必须小于结束时间'; + } + return [ + 'type' => 'fixed_range', + 'value' => $rangeValue + ]; + + case 'workday': // 工作日 + // 工作日需要配置时间,格式:HH:mm(如 09:00) + if (!isset($condition['value']) || empty($condition['value'])) { + return '工作日触发类型需要配置时间,格式:HH:mm(如 09:00)'; + } + $timeValue = trim($condition['value']); + // 验证格式:HH:mm + if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) { + return '工作日时间格式不正确,应为 HH:mm 格式(如 09:00)'; + } + return [ + 'type' => 'workday', + 'value' => $timeValue + ]; + + default: + return '时间触发类型无效'; + } + + case 4: // 关键词触发 + // 需要condition,格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"} + if (empty($condition)) { + return '关键词触发类型需要配置至少一个关键词'; + } + + // 如果是字符串,尝试解析JSON + if (is_string($condition)) { + $decoded = json_decode($condition, true); + if (json_last_error() === JSON_ERROR_NONE) { + $condition = $decoded; + } else { + return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}'; + } + } + + // 必须是对象格式 + if (!is_array($condition) || !isset($condition['keywords'])) { + return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}'; + } + + $keywords = $condition['keywords']; + $matchType = isset($condition['match_type']) ? $condition['match_type'] : 'fuzzy'; + + // 验证match_type + if (!in_array($matchType, ['exact', 'fuzzy'])) { + return '匹配类型无效,必须为:exact(精准匹配)或 fuzzy(模糊匹配)'; + } + + // 处理keywords + if (is_string($keywords)) { + $keywords = explode(',', $keywords); + } + if (!is_array($keywords)) { + return '关键词格式不正确,应为数组格式'; + } + + // 过滤空值并去重 + $keywords = array_filter(array_map('trim', $keywords)); + if (empty($keywords)) { + return '关键词触发类型需要配置至少一个关键词'; + } + + // 验证每个关键词不为空 + foreach ($keywords as $keyword) { + if (empty($keyword)) { + return '关键词不能为空'; + } + } + + return [ + 'keywords' => array_values($keywords), + 'match_type' => $matchType + ]; + + case 5: // 生日触发 + // 需要condition,格式支持: + // 1. 月日字符串:'10-10' 或 '10-10 09:00'(MM-DD格式,不包含年份) + // 2. 对象格式:{'month': 10, 'day': 10, 'time': '09:00'} 或 {'month': '10', 'day': '10', 'time_range': ['09:00', '10:00']} + if (empty($condition)) { + return '生日触发类型需要配置日期条件'; + } + + // 如果是字符串,只接受 MM-DD 格式(不包含年份) + if (is_string($condition)) { + // 检查是否包含时间部分 + if (preg_match('/^(\d{1,2})-(\d{1,2})\s+(\d{2}:\d{2})$/', $condition, $matches)) { + // 格式:'10-10 09:00' + $month = (int)$matches[1]; + $day = (int)$matches[2]; + if ($month < 1 || $month > 12 || $day < 1 || $day > 31) { + return '生日日期格式不正确,月份应为1-12,日期应为1-31'; + } + return [ + 'month' => $month, + 'day' => $day, + 'time' => $matches[3] + ]; + } elseif (preg_match('/^(\d{1,2})-(\d{1,2})$/', $condition, $matches)) { + // 格式:'10-10'(不指定时间,当天任何时间都可以触发) + $month = (int)$matches[1]; + $day = (int)$matches[2]; + if ($month < 1 || $month > 12 || $day < 1 || $day > 31) { + return '生日日期格式不正确,月份应为1-12,日期应为1-31'; + } + return [ + 'month' => $month, + 'day' => $day + ]; + } else { + return '生日日期格式不正确,应为 MM-DD 或 MM-DD HH:mm 格式(如 10-10 或 10-10 09:00),不包含年份'; + } + } + + // 如果是数组,可能是对象格式或旧格式 + if (is_array($condition)) { + // 检查是否是旧格式(仅兼容 MM-DD 格式的数组) + if (isset($condition[0]) && is_string($condition[0])) { + $dateStr = $condition[0]; + // 只接受 MM-DD 格式:'10-10' 或 '10-10 09:00' + if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $dateStr, $matches)) { + $month = (int)$matches[1]; + $day = (int)$matches[2]; + if ($month < 1 || $month > 12 || $day < 1 || $day > 31) { + return '生日日期格式不正确,月份应为1-12,日期应为1-31'; + } + if (isset($matches[3])) { + return [ + 'month' => $month, + 'day' => $day, + 'time' => $matches[3] + ]; + } else { + return [ + 'month' => $month, + 'day' => $day + ]; + } + } else { + return '生日日期格式不正确,应为 MM-DD 格式(如 10-10),不包含年份'; + } + } + + // 新格式:{'month': 10, 'day': 10, 'time': '09:00'} + if (isset($condition['month']) && isset($condition['day'])) { + $month = (int)$condition['month']; + $day = (int)$condition['day']; + + if ($month < 1 || $month > 12) { + return '生日月份格式不正确,应为1-12'; + } + if ($day < 1 || $day > 31) { + return '生日日期格式不正确,应为1-31'; + } + + $result = [ + 'month' => $month, + 'day' => $day + ]; + + // 检查是否配置了时间 + if (isset($condition['time']) && !empty($condition['time'])) { + $time = trim($condition['time']); + if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) { + return '生日时间格式不正确,应为 HH:mm 格式(如 09:00)'; + } + $result['time'] = $time; + } + + // 检查是否配置了时间范围 + if (isset($condition['time_range']) && is_array($condition['time_range']) && count($condition['time_range']) === 2) { + $startTime = trim($condition['time_range'][0]); + $endTime = trim($condition['time_range'][1]); + if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $startTime) || + !preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $endTime)) { + return '生日时间范围格式不正确,应为 ["HH:mm", "HH:mm"] 格式'; + } + $result['time_range'] = [$startTime, $endTime]; + } + + return $result; + } + + return '生日触发条件格式不正确,需要提供month和day字段'; + } + + return '生日触发条件格式不正确'; + + case 6: // 自定义 + // 自定义类型,condition可选,如果有则必须是数组格式 + if (!empty($condition)) { + $condition = is_array($condition) ? $condition : json_decode($condition, true); + if (!is_array($condition)) { + return '自定义类型的条件格式不正确,应为数组格式'; + } + return $condition; + } + return []; + + default: + return '无效的触发类型'; + } + } + /** * 添加 * @return \think\response\Json @@ -71,17 +410,18 @@ class AutoGreetingsController extends BaseController return ResponseHelper::error('参数缺失'); } - if (in_array($trigger,[2,3]) && empty($condition)){ - return ResponseHelper::error('具体条件不能为空'); + // 校验trigger类型 + if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) { + return ResponseHelper::error('无效的触发类型'); } - if ($trigger == 2){ - $condition = !empty($condition) ? $condition : []; - } - - if ($trigger == 3){ - $condition = explode(',',$condition); + // 校验并处理condition + $conditionResult = $this->validateTriggerCondition($trigger, $condition); + if (is_string($conditionResult)) { + // 返回的是错误信息 + return ResponseHelper::error($conditionResult); } + $condition = $conditionResult; Db::startTrans(); @@ -97,9 +437,10 @@ class AutoGreetingsController extends BaseController $AutoGreetings->companyId = $companyId; $AutoGreetings->updateTime = time(); $AutoGreetings->createTime = time(); + $AutoGreetings->usageCount = 0; // 初始化使用次数为0 $AutoGreetings->save(); Db::commit(); - return ResponseHelper::success(' ','创建成功'); + return ResponseHelper::success(['id' => $AutoGreetings->id],'创建成功'); } catch (\Exception $e) { Db::rollback(); return ResponseHelper::error('创建失败:'.$e->getMessage()); @@ -128,11 +469,13 @@ class AutoGreetingsController extends BaseController $data['condition'] = json_decode($data['condition'],true); - if ($data['trigger'] == 3){ - $data['condition'] = implode(',',$data['condition']); - } + + // 获取使用次数 + $usageCount = Db::name('kf_auto_greetings_record') + ->where('autoId', $id) + ->count(); + $data['usageCount'] = (int)$usageCount; - unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']); return ResponseHelper::success($data,'获取成功'); } @@ -175,7 +518,7 @@ class AutoGreetingsController extends BaseController $id = $this->request->param('id', ''); $name = $this->request->param('name', ''); $trigger = $this->request->param('trigger', 0); - $condition = $this->request->param('condition', []); + $condition = $this->request->param('condition', ''); $content = $this->request->param('content', ''); $level = $this->request->param('level', 0); $status = $this->request->param('status', 1); @@ -186,17 +529,18 @@ class AutoGreetingsController extends BaseController return ResponseHelper::error('参数缺失'); } - if (in_array($trigger,[2,3]) && empty($condition)){ - return ResponseHelper::error('具体条件不能为空'); + // 校验trigger类型 + if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) { + return ResponseHelper::error('无效的触发类型'); } - if ($trigger == 2){ - $condition = !empty($condition) ? $condition : []; - } - - if ($trigger == 3){ - $condition = explode(',',$condition); + // 校验并处理condition + $conditionResult = $this->validateTriggerCondition($trigger, $condition); + if (is_string($conditionResult)) { + // 返回的是错误信息 + return ResponseHelper::error($conditionResult); } + $condition = $conditionResult; $query = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find(); @@ -244,11 +588,16 @@ class AutoGreetingsController extends BaseController } Db::startTrans(); try { - $query->status = !empty($query['status']) ? 0 : 1; + $status = $this->request->param('status', ''); + if ($status !== '') { + $query->status = (int)$status; + } else { + $query->status = $query->status == 1 ? 0 : 1; + } $query->updateTime = time(); $query->save(); Db::commit(); - return ResponseHelper::success(' ','修改成功'); + return ResponseHelper::success(['status' => $query->status],'修改成功'); } catch (\Exception $e) { Db::rollback(); return ResponseHelper::error('修改失败:'.$e->getMessage()); diff --git a/Server/application/chukebao/model/AiPush.php b/Server/application/chukebao/model/AiPush.php new file mode 100644 index 00000000..567c132f --- /dev/null +++ b/Server/application/chukebao/model/AiPush.php @@ -0,0 +1,17 @@ +handleAutoGreetings(); + }); + } + // 更多其他后台任务 // ...... diff --git a/Server/application/common/controller/PaymentService.php b/Server/application/common/controller/PaymentService.php index d2a02861..9c951c8c 100644 --- a/Server/application/common/controller/PaymentService.php +++ b/Server/application/common/controller/PaymentService.php @@ -112,8 +112,6 @@ class PaymentService $parsed = $this->parseXmlOrRaw($response); - exit_data($parsed); - if ($parsed['status'] == 0 && $parsed['result_code'] == 0) { Db::commit(); diff --git a/Server/application/store/controller/LoginController.php b/Server/application/store/controller/LoginController.php index ea364f4e..8dc571d2 100644 --- a/Server/application/store/controller/LoginController.php +++ b/Server/application/store/controller/LoginController.php @@ -21,6 +21,9 @@ class LoginController extends Controller ->join('device d', 'du.deviceId = d.id and u.companyId = du.companyId') ->where(['d.deviceImei' => $deviceId, 'u.deleteTime' => 0, 'du.deleteTime' => 0, 'd.deleteTime' => 0]) ->find(); + if (empty($user)) { + return errorJson('用户不存在'); + } $member = array_merge($user, [ 'lastLoginIp' => $this->request->ip(), 'lastLoginTime' => time() diff --git a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php index a1ef4e3e..adafc321 100644 --- a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php +++ b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php @@ -1537,4 +1537,500 @@ class Adapter implements WeChatServiceInterface } while ($affected > 0); } + /** + * 处理自动问候功能 + * 根据不同的触发类型检查并发送问候消息 + */ + public function handleAutoGreetings() + { + try { + // 获取所有启用的问候规则 + $rules = Db::name('kf_auto_greetings') + ->where(['status' => 1, 'isDel' => 0]) + ->order('level asc, id asc') + ->select(); + + if (empty($rules)) { + return; + } + + foreach ($rules as $rule) { + $trigger = $rule['trigger']; + $condition = json_decode($rule['condition'], true); + + switch ($trigger) { + case 1: // 新好友 + $this->handleNewFriendGreeting($rule); + break; + case 2: // 首次发消息 + $this->handleFirstMessageGreeting($rule); + break; + case 3: // 时间触发 + $this->handleTimeTriggerGreeting($rule, $condition); + break; + case 4: // 关键词触发 + $this->handleKeywordTriggerGreeting($rule, $condition); + break; + case 5: // 生日触发 + $this->handleBirthdayTriggerGreeting($rule, $condition); + break; + case 6: // 自定义 + $this->handleCustomTriggerGreeting($rule, $condition); + break; + } + } + } catch (\Exception $e) { + Log::error('自动问候处理失败:' . $e->getMessage()); + } + } + + /** + * 处理新好友触发 + */ + private function handleNewFriendGreeting($rule) + { + // 获取最近24小时内添加的好友(避免重复处理) + $last24h = time() - 24 * 3600; + + // 查询该用户/公司最近24小时内新添加的好友 + // 通过 s2_wechat_account -> s2_company_account 关联获取 companyId + $friends = Db::table('s2_wechat_friend') + ->alias('wf') + ->join(['s2_wechat_account' => 'wa'], 'wf.wechatAccountId = wa.id') + ->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id') + ->where([ + ['wf.isPassed', '=', 1], + ['wf.isDeleted', '=', 0], + ['wf.passTime', '>=', $last24h], + ['ca.departmentId', '=', $rule['companyId']], + ]) + ->field('wf.id, wf.wechatAccountId') + ->select(); + + foreach ($friends as $friend) { + // 检查是否已经发送过问候 + $exists = Db::name('kf_auto_greetings_record') + ->where([ + 'autoId' => $rule['id'], + 'friendIdOrGroupId' => $friend['id'], + 'wechatAccountId' => $friend['wechatAccountId'], + ]) + ->find(); + + if (!$exists) { + $this->sendGreetingMessage($rule, $friend['wechatAccountId'], $friend['id'], 0); + } + } + } + + /** + * 处理首次发消息触发 + */ + private function handleFirstMessageGreeting($rule) + { + // 获取最近1小时内收到的消息 + $last1h = time() - 3600; + + // 查询消息表,找出首次发消息的好友 + // 通过 s2_wechat_account -> s2_company_account 关联获取 companyId + $messages = Db::table('s2_wechat_message') + ->alias('wm') + ->join(['s2_wechat_account' => 'wa'], 'wm.wechatAccountId = wa.id') + ->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id') + ->where([ + ['wm.isSend', '=', 0], // 接收的消息 + ['wm.wechatChatroomId', '=', 0], // 个人消息 + ['wm.createTime', '>=', $last1h], + ['ca.departmentId', '=', $rule['companyId']], + ]) + ->group('wm.wechatFriendId, wm.wechatAccountId') + ->field('wm.wechatFriendId, wm.wechatAccountId, MIN(wm.createTime) as firstMsgTime') + ->select(); + + foreach ($messages as $msg) { + // 检查该好友是否之前发送过消息 + $previousMsg = Db::table('s2_wechat_message') + ->where([ + 'wechatFriendId' => $msg['wechatFriendId'], + 'wechatAccountId' => $msg['wechatAccountId'], + 'isSend' => 0, + ]) + ->where('createTime', '<', $msg['firstMsgTime']) + ->find(); + + // 如果是首次发消息,且没有发送过问候 + if (!$previousMsg) { + $exists = Db::name('kf_auto_greetings_record') + ->where([ + 'autoId' => $rule['id'], + 'friendIdOrGroupId' => $msg['wechatFriendId'], + 'wechatAccountId' => $msg['wechatAccountId'], + ]) + ->find(); + + if (!$exists) { + $this->sendGreetingMessage($rule, $msg['wechatAccountId'], $msg['wechatFriendId'], 0); + } + } + } + } + + /** + * 处理时间触发 + */ + private function handleTimeTriggerGreeting($rule, $condition) + { + if (empty($condition) || !isset($condition['type'])) { + return; + } + + $now = time(); + $currentTime = date('H:i', $now); + $currentDate = date('m-d', $now); + $currentDateTime = date('m-d H:i', $now); + $currentWeekday = date('w', $now); // 0=周日, 1=周一, ..., 6=周六 + + $shouldTrigger = false; + + switch ($condition['type']) { + case 'daily_time': // 每天固定时间 + if ($currentTime === $condition['value']) { + $shouldTrigger = true; + } + break; + + case 'yearly_datetime': // 每年固定日期时间 + if ($currentDateTime === $condition['value']) { + $shouldTrigger = true; + } + break; + + case 'fixed_range': // 固定时间段 + if (is_array($condition['value']) && count($condition['value']) === 2) { + $startTime = strtotime('2000-01-01 ' . $condition['value'][0]); + $endTime = strtotime('2000-01-01 ' . $condition['value'][1]); + $currentTimeStamp = strtotime('2000-01-01 ' . $currentTime); + + if ($currentTimeStamp >= $startTime && $currentTimeStamp <= $endTime) { + $shouldTrigger = true; + } + } + break; + + case 'workday': // 工作日 + // 周一到周五(1-5) + if ($currentWeekday >= 1 && $currentWeekday <= 5 && $currentTime === $condition['value']) { + $shouldTrigger = true; + } + break; + } + + if ($shouldTrigger) { + // 获取该用户/公司的所有好友 + // 通过 s2_wechat_account -> s2_company_account 关联获取 companyId + $friends = Db::table('s2_wechat_friend') + ->alias('wf') + ->join(['s2_wechat_account' => 'wa'], 'wf.wechatAccountId = wa.id') + ->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id') + ->where([ + ['wf.isPassed', '=', 1], + ['wf.isDeleted', '=', 0], + ['ca.departmentId', '=', $rule['companyId']], + ]) + ->field('wf.id, wf.wechatAccountId') + ->select(); + + foreach ($friends as $friend) { + // 检查今天是否已经发送过 + $todayStart = strtotime(date('Y-m-d 00:00:00')); + $exists = Db::name('kf_auto_greetings_record') + ->where([ + 'autoId' => $rule['id'], + 'friendIdOrGroupId' => $friend['id'], + 'wechatAccountId' => $friend['wechatAccountId'], + ]) + ->where('createTime', '>=', $todayStart) + ->find(); + + if (!$exists) { + $this->sendGreetingMessage($rule, $friend['wechatAccountId'], $friend['id'], 0); + } + } + } + } + + /** + * 处理关键词触发 + */ + private function handleKeywordTriggerGreeting($rule, $condition) + { + if (empty($condition) || empty($condition['keywords'])) { + return; + } + + $keywords = $condition['keywords']; + $matchType = $condition['match_type'] ?? 'fuzzy'; + + // 获取最近1小时内收到的消息 + $last1h = time() - 3600; + + // 通过 s2_wechat_account -> s2_company_account 关联获取 companyId + $messages = Db::table('s2_wechat_message') + ->alias('wm') + ->join(['s2_wechat_account' => 'wa'], 'wm.wechatAccountId = wa.id') + ->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id') + ->where([ + ['wm.isSend', '=', 0], // 接收的消息 + ['wm.wechatChatroomId', '=', 0], // 个人消息 + ['wm.msgType', '=', 1], // 文本消息 + ['wm.createTime', '>=', $last1h], + ['ca.departmentId', '=', $rule['companyId']], + ]) + ->field('wm.*') + ->select(); + + foreach ($messages as $msg) { + $content = $msg['content'] ?? ''; + + // 检查关键词匹配 + $matched = false; + foreach ($keywords as $keyword) { + if ($matchType === 'exact') { + // 精准匹配 + if ($content === $keyword) { + $matched = true; + break; + } + } else { + // 模糊匹配 + if (strpos($content, $keyword) !== false) { + $matched = true; + break; + } + } + } + + if ($matched) { + // 检查是否已经发送过问候(同一好友同一规则,1小时内只发送一次) + $last1h = time() - 3600; + $exists = Db::name('kf_auto_greetings_record') + ->where([ + 'autoId' => $rule['id'], + 'friendIdOrGroupId' => $msg['wechatFriendId'], + 'wechatAccountId' => $msg['wechatAccountId'], + ]) + ->where('createTime', '>=', $last1h) + ->find(); + + if (!$exists) { + $this->sendGreetingMessage($rule, $msg['wechatAccountId'], $msg['wechatFriendId'], 0); + } + } + } + } + + /** + * 处理生日触发 + */ + private function handleBirthdayTriggerGreeting($rule, $condition) + { + if (empty($condition)) { + return; + } + + // 解析condition格式 + // 支持格式: + // 1. {'month': 10, 'day': 10} - 当天任何时间都可以触发 + // 2. {'month': 10, 'day': 10, 'time': '09:00'} - 当天指定时间触发 + // 3. {'month': 10, 'day': 10, 'time_range': ['09:00', '10:00']} - 当天时间范围内触发 + // 兼容旧格式:['10-10'] 或 '10-10'(仅支持 MM-DD 格式,不包含年份) + + $birthdayMonth = null; + $birthdayDay = null; + $birthdayTime = null; + $timeRange = null; + + if (is_array($condition)) { + // 新格式:对象格式 {'month': 10, 'day': 10} + if (isset($condition['month']) && isset($condition['day'])) { + $birthdayMonth = (int)$condition['month']; + $birthdayDay = (int)$condition['day']; + $birthdayTime = $condition['time'] ?? null; + $timeRange = $condition['time_range'] ?? null; + } + // 兼容旧格式:['10-10'] 或 ['10-10 09:00'](仅支持 MM-DD 格式) + elseif (isset($condition[0])) { + $dateStr = $condition[0]; + // 只接受月日格式:'10-10' 或 '10-10 09:00' + if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $dateStr, $matches)) { + $birthdayMonth = (int)$matches[1]; + $birthdayDay = (int)$matches[2]; + if (isset($matches[3])) { + $birthdayTime = $matches[3]; + } + } + } + } elseif (is_string($condition)) { + // 字符串格式:只接受 '10-10' 或 '10-10 09:00'(MM-DD 格式,不包含年份) + if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $condition, $matches)) { + $birthdayMonth = (int)$matches[1]; + $birthdayDay = (int)$matches[2]; + if (isset($matches[3])) { + $birthdayTime = $matches[3]; + } + } + } + + if ($birthdayMonth === null || $birthdayDay === null || $birthdayMonth < 1 || $birthdayMonth > 12 || $birthdayDay < 1 || $birthdayDay > 31) { + return; + } + + $todayMonth = (int)date('m'); + $todayDay = (int)date('d'); + + // 检查今天是否是生日(只匹配月日,不匹配年份) + if ($todayMonth !== $birthdayMonth || $todayDay !== $birthdayDay) { + return; + } + + // 如果配置了时间,检查当前时间是否匹配 + $now = time(); + $currentTime = date('H:i', $now); + + if ($birthdayTime !== null) { + // 指定了具体时间,检查是否在指定时间(允许1分钟误差,避免定时任务执行时间不精确) + $birthdayTimestamp = strtotime('2000-01-01 ' . $birthdayTime); + $currentTimestamp = strtotime('2000-01-01 ' . $currentTime); + $diff = abs($currentTimestamp - $birthdayTimestamp); + + // 如果时间差超过2分钟,不触发(允许1分钟误差) + if ($diff > 120) { + return; + } + } elseif ($timeRange !== null && is_array($timeRange) && count($timeRange) === 2) { + // 指定了时间范围,检查当前时间是否在范围内 + $startTime = strtotime('2000-01-01 ' . $timeRange[0]); + $endTime = strtotime('2000-01-01 ' . $timeRange[1]); + $currentTimestamp = strtotime('2000-01-01 ' . $currentTime); + + if ($currentTimestamp < $startTime || $currentTimestamp > $endTime) { + return; + } + } + // 如果没有配置时间或时间范围,则当天任何时间都可以触发 + + // 获取该用户/公司的所有好友 + // 通过 s2_wechat_account -> s2_company_account 关联获取 companyId + $friends = Db::table('s2_wechat_friend') + ->alias('wf') + ->join(['s2_wechat_account' => 'wa'], 'wf.wechatAccountId = wa.id') + ->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id') + ->where([ + ['wf.isPassed', '=', 1], + ['wf.isDeleted', '=', 0], + ['ca.departmentId', '=', $rule['companyId']], + ]) + ->field('wf.id, wf.wechatAccountId') + ->select(); + + foreach ($friends as $friend) { + // 检查今天是否已经发送过 + $todayStart = strtotime(date('Y-m-d 00:00:00')); + $exists = Db::name('kf_auto_greetings_record') + ->where([ + 'autoId' => $rule['id'], + 'friendIdOrGroupId' => $friend['id'], + 'wechatAccountId' => $friend['wechatAccountId'], + ]) + ->where('createTime', '>=', $todayStart) + ->find(); + + if (!$exists) { + $this->sendGreetingMessage($rule, $friend['wechatAccountId'], $friend['id'], 0); + } + } + } + + /** + * 处理自定义触发 + */ + private function handleCustomTriggerGreeting($rule, $condition) + { + // 自定义类型需要根据具体业务需求实现 + // 这里提供一个基础框架,可根据实际需求扩展 + // 暂时不实现,留待后续扩展 + } + + /** + * 发送问候消息 + * @param array $rule 问候规则 + * @param int $wechatAccountId 微信账号ID + * @param int $friendId 好友ID + * @param int $groupId 群ID(0表示个人消息) + */ + private function sendGreetingMessage($rule, $wechatAccountId, $friendId, $groupId = 0) + { + try { + $content = $rule['content']; + + // 创建记录 + $recordId = Db::name('kf_auto_greetings_record')->insertGetId([ + 'autoId' => $rule['id'], + 'userId' => $rule['userId'], + 'companyId' => $rule['companyId'], + 'wechatAccountId' => $wechatAccountId, + 'friendIdOrGroupId' => $friendId, + 'isSend' => 0, + 'sendTime' => 0, + 'receiveTime' => 0, + 'createTime' => time(), + ]); + + // 发送消息(文本消息) + $username = Env::get('api.username', ''); + $password = Env::get('api.password', ''); + $toAccountId = ''; + if (!empty($username) || !empty($password)) { + $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); + } + + $wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); + + $sendTime = time(); + $result = $wsController->sendPersonal([ + 'wechatFriendId' => $friendId, + 'wechatAccountId' => $wechatAccountId, + 'msgType' => 1, // 文本消息 + 'content' => $content, + ]); + + $isSend = 0; + $receiveTime = 0; + + // 解析返回结果 + $resultData = json_decode($result, true); + if (!empty($resultData) && $resultData['code'] == 200) { + $isSend = 1; + $receiveTime = time(); // 简化处理,实际应该从返回结果中获取 + } + + // 更新记录 + Db::name('kf_auto_greetings_record') + ->where('id', $recordId) + ->update([ + 'isSend' => $isSend, + 'sendTime' => $sendTime, + 'receiveTime' => $receiveTime, + ]); + + // 更新规则使用次数 + Db::name('kf_auto_greetings') + ->where('id', $rule['id']) + ->setInc('usageCount'); + + } catch (\Exception $e) { + Log::error('发送问候消息失败:' . $e->getMessage() . ',规则ID:' . $rule['id']); + } + } + }