diff --git a/Server/application/api/controller/FriendTaskController.php b/Server/application/api/controller/FriendTaskController.php index 09a21f0b..893a8109 100644 --- a/Server/application/api/controller/FriendTaskController.php +++ b/Server/application/api/controller/FriendTaskController.php @@ -3,6 +3,7 @@ namespace app\api\controller; use app\api\model\FriendTaskModel; +use app\common\model\WechatRestricts; use think\facade\Request; class FriendTaskController extends BaseController @@ -153,11 +154,39 @@ class FriendTaskController extends BaseController // 使用taskId作为唯一性判断 $task = FriendTaskModel::where('id', $item['id'])->find(); - if ($task) { $task->save($data); } else { FriendTaskModel::create($data); } + + //创建非法记录 + if ($item['status'] == 2){ + $data = [ + 'level' => 2, + 'taskId' => $item['id'], + 'reason' => '', + 'memo' => '', + 'wechatId' => $item['wechatId'], + 'companyId' => '', + 'restrictTime' => time(), + 'recoveryTime' => time() + 3600 * 72, + ]; + if (strpos('操作过于频繁', $item['extra']) !== false){ + $data['reason'] = '频繁添加好友'; + $data['memo'] = '操作过于频繁,请稍后再试'; + } + + if (strpos('当前账号存在安全风险', $item['extra']) !== false){ + $data['reason'] = '账号风险'; + $data['memo'] = '当前账号存在安全风险,需先到「微信团队」进行安全验证后才能继续使用当前功能'; + } + $res = WechatRestricts::where('taskId', $item['id'])->find(); + if (empty($res)) { + WechatRestricts::create($data); + } + } + + } } \ No newline at end of file diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 430c9516..c0a1ba26 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -26,8 +26,14 @@ Route::group('v1/', function () { Route::get('', 'app\cunkebao\controller\wechat\GetWechatsOnDevicesV1Controller@index'); Route::get(':id/summary', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index'); Route::get(':id/friends', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendsV1Controller@index'); + Route::get('getWechatInfo', 'app\cunkebao\controller\wechat\GetWechatController@getWechatInfo'); Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index'); + + + + + Route::get('count', 'app\cunkebao\controller\DeviceWechat@count'); Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量 Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态 @@ -52,13 +58,18 @@ Route::group('v1/', function () { // 流量池相关 Route::group('traffic/pool', function () { Route::get('', 'app\cunkebao\controller\traffic\GetPotentialListWithInCompanyV1Controller@index'); + Route::get('getUserJourney', 'app\cunkebao\controller\traffic\GetPotentialListWithInCompanyV1Controller@getUserJourney'); + + + + Route::get('converted', 'app\cunkebao\controller\traffic\GetConvertedListWithInCompanyV1Controller@index'); Route::get('types', 'app\cunkebao\controller\traffic\GetPotentialTypeSectionV1Controller@index'); Route::get('sources', 'app\cunkebao\controller\traffic\GetTrafficSourceSectionV1Controller@index'); Route::get('statistics', 'app\cunkebao\controller\traffic\GetPoolStatisticsV1Controller@index'); - Route::get('list', 'app\cunkebao\controller\TrafficController@getList'); + }); // 工作台相关 diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php index 0d28937c..86cbf7de 100644 --- a/Server/application/cunkebao/controller/ContentLibraryController.php +++ b/Server/application/cunkebao/controller/ContentLibraryController.php @@ -935,8 +935,11 @@ class ContentLibraryController extends Controller ]) ->order('createTime', 'desc') //->where('create_time', '>=', time() - 86400) + ->page(1,20) ->select(); + + if (empty($moments)) { continue; } @@ -990,7 +993,17 @@ class ContentLibraryController extends Controller if ($excludeMatch) { continue; } - + + // 如果启用了AI处理 + if (!empty($library['aiEnabled']) && !empty($content)) { + $contentAi = $this->aiRewrite($library,$content); + if (!empty($content)){ + $moment['contentAi'] = $contentAi; + }else{ + $moment['contentAi'] = ''; + } + } + // 保存到内容库的content_item表 $this->saveMomentToContentItem($moment, $library['id'], $friend, $nickname); @@ -1009,10 +1022,7 @@ class ContentLibraryController extends Controller } } - // 如果启用了AI处理 - if ($library['aiEnabled'] == 1 && !empty($library['aiPrompt']) && $totalMomentsCount > 0) { - // 此处实现AI处理逻辑,暂未实现 - } + if (empty($collectedData)) { return [ @@ -1171,6 +1181,18 @@ class ContentLibraryController extends Controller continue; } + + // 如果启用了AI处理 + if (!empty($library['aiEnabled']) && !empty($content)) { + $contentAi = $this->aiRewrite($library,$content); + if (!empty($content)){ + $moment['contentAi'] = $contentAi; + }else{ + $moment['contentAi'] = ''; + } + } + + // 保存消息到内容库 $this->saveMessageToContentItem($message, $library['id'], $groupInfo); @@ -1199,11 +1221,6 @@ class ContentLibraryController extends Controller } } - // 如果启用了AI处理 - if ($library['aiEnabled'] == 1 && !empty($library['aiPrompt']) && $totalMessagesCount > 0) { - // 此处实现AI处理逻辑,暂未实现 - } - if (empty($collectedData)) { return [ 'status' => 'warning', @@ -1477,6 +1494,7 @@ class ContentLibraryController extends Controller $item->friendId = $friend['id']; $item->createMomentTime = $moment['createTime'] ?? 0; $item->content = $moment['content'] ?? ''; + $item->contentAi = $moment['contentAi'] ?? ''; $item->coverImage = $coverImage; $item->contentType = $contentType; // 设置内容类型 @@ -1929,6 +1947,47 @@ class ContentLibraryController extends Controller } + public function aiRewrite($library = [],$content = '') + { + if (empty($library['aiEnabled']) && empty($content)){ + return false; + } + // 此处实现AI处理逻辑,暂未实现 + $utl = Env::get('doubaoAi.api_url', ''); + $apiKey = Env::get('doubaoAi.api_key', ''); + $model = Env::get('doubaoAi.model', 'doubao-1-5-pro-32k-250115'); + if (empty($apiKey)){ + return false; + } + if (!empty($library['aiPrompt'])) { + $aiPrompt = $library['aiPrompt']; + }else{ + $aiPrompt = '重写这条朋友圈 要求: +1、原本的字数和意思不要修改超过10% +2、出现品牌名或个人名字就去除'; + } + + $content = $aiPrompt .' ' . $content; + $headerData = ['Authorization:Bearer ' . $apiKey]; + $header = setHeader($headerData); + + // 发送请求 + $params = [ + 'model' => $model, + 'messages' => [ + ['role' => 'system','content' => '你是人工智能助手.'], + ['role' => 'user','content' => $content], + ] + ]; + $result = requestCurl($utl, $params, 'POST', $header,'json'); + $result = json_decode($result,true); + if (!empty($result['choices'])){ + $contentAI = $result['choices'][0]['message']['content']; + return $contentAI; + }else{ + return false; + } + } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php b/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php index 09442050..0ebfe936 100644 --- a/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php +++ b/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php @@ -7,6 +7,7 @@ use app\common\model\TrafficSource as TrafficSourceModel; use app\common\model\WechatFriendShip as WechatFriendShipModel; use app\cunkebao\controller\BaseController; use library\ResponseHelper; +use think\Db; /** * 流量池控制器 @@ -22,21 +23,32 @@ class GetPotentialListWithInCompanyV1Controller extends BaseController protected function makeWhere(array $params = []): array { if (!empty($keyword = $this->request->param('keyword'))) { - $where[] = ['exp', "p.identifier LIKE '%{$keyword}%'"]; + $where[] = ['p.identifier|wa.nickname|wa.phone|wa.wechatId|wa.alias','like', '%'. $keyword .'%']; } // 状态筛选 - if ($status = $this->request->param('status')) { + if ($status = $this->request->param('addStatus')) { $where['s.status'] = $status; } else { $where['s.status'] = array('<>', TrafficSourceModel::STATUS_PASSED); } // 来源的筛选 - if ($fromd = $this->request->param('fromd')) { - $where['s.fromd'] = $fromd; + if ($fromd = $this->request->param('packageId')) { + if ($fromd != -1){ + $where['tsp.id'] = $fromd; + }else{ + $where[] = ['tsp.id',null]; + } + } + if ($device = $this->request->param('device')) { + $where['d.deviceId'] = $device; + } + + + $where['s.companyId'] = $this->getUserInfo('companyId'); return array_merge($where, $params); @@ -48,33 +60,71 @@ class GetPotentialListWithInCompanyV1Controller extends BaseController * @param array $where * @return \think\Paginator */ - protected function getPoolListByCompanyId(array $where): \think\Paginator + protected function getPoolListByCompanyId(array $where) { $query = TrafficPoolModel::alias('p') ->field( [ - 'p.identifier nickname', 'p.mobile', 'p.wechatId', 'p.identifier', - 's.id', 's.fromd', 's.status', 's.createTime' + 'p.id','p.identifier', 'p.mobile', 'p.wechatId', 'p.identifier', + 's.fromd', 's.status', 's.createTime','s.companyId','s.sourceId','s.type', + 'wa.nickname', 'wa.avatar', 'wa.gender', 'wa.phone', ] ) - ->join('traffic_source s', 'p.identifier=s.identifier') - ->order('s.id desc'); + ->join('traffic_source s', 'p.identifier=s.identifier','left') + ->join('wechat_account wa', 'p.identifier=wa.wechatId','left') + ->join('traffic_source_package_item tspi', 'p.identifier = tspi.identifier AND s.companyId = tspi.companyId','left') + ->join('traffic_source_package tsp', 'tspi.packageId=tsp.id','left') + ->join('device_wechat_login d', 's.sourceId=d.wechatId','left') + ->order('p.id DESC,s.id DESC') + ->group('p.identifier'); foreach ($where as $key => $value) { if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') { $query->whereExp('', $value[1]); continue; } - if (is_array($value)) { $query->where($key, ...$value); continue; } - $query->where($key, $value); } - return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]); + $result = $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]); + $list = $result->items(); + $total = $result->total(); + + foreach ($list as &$item) { + //流量池筛选 + $package = Db::name('traffic_source_package_item')->alias('tspi') + ->join('traffic_source_package p', 'tspi.packageId=p.id AND tspi.companyId=p.companyId') + ->where(['tspi.companyId' => $item->companyId,'tspi.identifier' => $item->identifier]) + ->column('p.name'); + + $package2 = Db::name('traffic_source_package_item')->alias('tspi') + ->join('traffic_source_package p', 'tspi.packageId=p.id') + ->where(['tspi.companyId' => $item->companyId,'tspi.identifier' => $item->identifier,'p.isSys' => 1]) + ->column('p.name'); + $packages = array_merge($package, $package2); + $item['packages'] = $packages; + + + if ($item->type == 1){ + $tag = Db::name('wechat_friendship')->where(['wechatId' => $item->wechatId])->column('tags'); + $tags = []; + foreach ($tag as $k => $v) { + $v = json_decode($v,true); + $tags = array_merge($tags, $v); + } + $item['tags'] = $tags; + } + + + + } + unset($item); + $data = ['list' => $list, 'total' => $total]; + return json_encode($data, JSON_UNESCAPED_UNICODE); } /** @@ -86,15 +136,54 @@ class GetPotentialListWithInCompanyV1Controller extends BaseController { try { $result = $this->getPoolListByCompanyId( $this->makeWhere() ); - + $result = json_decode($result, true); return ResponseHelper::success( [ - 'list' => $result->items(), - 'total' => $result->total(), + 'list' => $result['list'], + 'total' => $result['total'], ] ); } catch (\Exception $e) { return ResponseHelper::error($e->getMessage(), $e->getCode()); } } + + + + + /** + * 用户旅程 + * @return false|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getUserJourney() + { + $page = $this->request->param('page',1); + $pageSize = $this->request->param('pageSize',10); + $userId = $this->request->param('userId',''); + if(empty($userId)){ + return json_encode(['code' => 500, 'msg' => '用户id不能为空']); + } + + $query = Db::name('user_portrait') + ->field('id,type,trafficPoolId,remark,count,createTime,updateTime') + ->where(['trafficPoolId' => $userId]); + + $total = $query->count(); + + $list = $query->order('createTime desc') + ->page($page,$pageSize) + ->select(); + + + foreach ($list as $k=>$v){ + $list[$k]['createTime'] = date('Y-m-d H:i:s',$v['createTime']); + $list[$k]['updateTime'] = date('Y-m-d H:i:s',$v['updateTime']); + } + return ResponseHelper::success(['list' => $list,'total'=>$total]); + + } + } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/wechat/GetWechatController.php b/Server/application/cunkebao/controller/wechat/GetWechatController.php new file mode 100644 index 00000000..4f935060 --- /dev/null +++ b/Server/application/cunkebao/controller/wechat/GetWechatController.php @@ -0,0 +1,189 @@ +WechatCustomerModel)) { + $this->WechatCustomerModel = WechatCustomerModel::where( + [ + 'wechatId' => $wechatId, + 'companyId' => $this->getUserInfo('companyId') + ] + ) + ->find(); + } + + return $this->WechatCustomerModel; + } + + + /** + * 获取昨日聊天次数 + * + * @param WechatCustomerModel $customer + * @return int + */ + protected function getChatTimesPerDay(?WechatCustomerModel $customer): int + { + return $customer->activity->yesterdayMsgCount ?? 0; + } + + /** + * 总聊天数量 + * + * @param WechatCustomerModel $customer + * @return int + */ + protected function getChatTimesTotal(?WechatCustomerModel $customer): int + { + return $customer->activity->totalMsgCount ?? 0; + } + + /** + * 计算活跃程度(根据消息数) + * + * @param string $wechatId + * @return string + */ + protected function getActivityLevel(string $wechatId): array + { + $customer = $this->getWechatCustomerModel($wechatId); + + return [ + 'allTimes' => $this->getChatTimesTotal($customer), + 'dayTimes' => $this->getChatTimesPerDay($customer), + ]; + } + + /** + * 获取限制记录 + * + * @param string $wechatId + * @return array + */ + protected function getRestrict(string $wechatId): array + { + return WechatRestrictsModel::alias('r') + ->field( + [ + 'r.id', 'r.restrictTime date', 'r.level', 'r.reason' + ] + ) + ->where('r.wechatId', $wechatId)->select() + ->toArray(); + } + + /** + * 获取账号权重 + * + * @param string $wechatId + * @return array + */ + protected function getAccountWeight(string $wechatId): array + { + $customer = $this->getWechatCustomerModel($wechatId); + $seeders = $customer ? (array)$customer->weight : array(); + + // 严谨返回 + return ArrHelper::getValue('ageWeight,activityWeigth,restrictWeight,realNameWeight,scope', $seeders, 0); + } + + /** + * 获取当日最高添加好友记录 + * + * @param string $wechatId + * @return int + * @throws \Exception + */ + protected function getAccountWeightAddLimit(string $wechatId): int + { + return $this->getWechatCustomerModel($wechatId)->weight->addLimit ?? 20; + } + + /** + * 计算今日新增好友数量 + * + * @param string $ownerWechatId + * @return int + */ + protected function getTodayNewFriendCount(string $ownerWechatId): int + { + + return Db::table('s2_friend_task') + ->where('wechatId',$ownerWechatId) + ->whereBetween('createTime', [strtotime(date('Y-m-d 00:00:00')), strtotime(date('Y-m-d 23:59:59'))]) + ->count(); + } + + /** + * 获取账号加友统计数据. + * + * @param string $wechatId + * @return array + */ + protected function getStatistics(string $wechatId): array + { + return [ + 'todayAdded' => $this->getTodayNewFriendCount($wechatId), + 'addLimit' => $this->getAccountWeightAddLimit($wechatId) + ]; + } + + + + + public function getWechatInfo() + { + $wechatId = $this->request->param('wechatId',''); + if (empty($wechatId)) { + return ResponseHelper::error('微信id不能为空'); + } + $userInfo = Db::name('wechat_customer')->alias('wc') + ->join('wechat_account wa','wc.wechatId = wa.wechatId') + ->where(['wc.wechatId' => $wechatId]) + ->field('wc.*,wa.nickname,wa.alias,wa.avatar,wa.gender') + ->find(); + + if (empty($userInfo)){ + return ResponseHelper::error('该微信不存在'); + } + $accountAge= !empty($userInfo['createTime']) ? date('Y-m-d H:i:s',$userInfo['createTime']) : date('Y-m-d H:i:s'); + unset($userInfo['basic'],$userInfo['companyId'],$userInfo['createTime'],$userInfo['updateTime'],$userInfo['id']); + + $userInfo['weight'] = json_decode($userInfo['weight'],true); + $userInfo['activity'] = json_decode($userInfo['activity'],true); + $userInfo['friendShip'] = json_decode($userInfo['friendShip'],true); + + + $newData = [ + 'userInfo' => $userInfo, + 'accountAge' => $accountAge, + 'activityLevel' => $this->getActivityLevel($wechatId), + 'accountWeight' => $this->getAccountWeight($wechatId), + 'statistics' => $this->getStatistics($wechatId), + 'restrictions' => $this->getRestrict($wechatId), + ]; + + + + return ResponseHelper::success($newData); + } +} \ No newline at end of file diff --git a/Server/application/cunkebao/validate/Workbench.php b/Server/application/cunkebao/validate/Workbench.php index f55df5e5..7f801503 100644 --- a/Server/application/cunkebao/validate/Workbench.php +++ b/Server/application/cunkebao/validate/Workbench.php @@ -28,7 +28,7 @@ class Workbench extends Validate 'contentTypes' => 'requireIf:type,1|array|contentTypeEnum:text,image,video', //'targetGroups' => 'requireIf:type,1|array', // 朋友圈同步特有参数 - 'syncInterval' => 'requireIf:type,2|number|min:1', + //'syncInterval' => 'requireIf:type,2|number|min:1', 'syncCount' => 'requireIf:type,2|number|min:1', 'syncType' => 'requireIf:type,2|in:1,2,3,4', 'startTime' => 'requireIf:type,2|dateFormat:H:i', @@ -85,9 +85,9 @@ class Workbench extends Validate 'contentTypes.array' => '点赞内容类型必须是数组', 'contentTypes.contentTypeEnum' => '点赞内容类型只能是text、image、video', // 朋友圈同步相关提示 - 'syncInterval.requireIf' => '请设置同步间隔', + /* 'syncInterval.requireIf' => '请设置同步间隔', 'syncInterval.number' => '同步间隔必须为数字', - 'syncInterval.min' => '同步间隔必须大于0', + 'syncInterval.min' => '同步间隔必须大于0',*/ 'syncCount.requireIf' => '请设置同步数量', 'syncCount.number' => '同步数量必须为数字', 'syncCount.min' => '同步数量必须大于0', diff --git a/Server/application/job/WorkbenchMomentsJob.php b/Server/application/job/WorkbenchMomentsJob.php index 41ed4701..d1d74e06 100644 --- a/Server/application/job/WorkbenchMomentsJob.php +++ b/Server/application/job/WorkbenchMomentsJob.php @@ -173,7 +173,7 @@ class WorkbenchMomentsJob 'poiAddress' => '', 'poiName' => '', 'publicMode' => '', - 'text' => $contentLibrary['content'], + 'text' => !empty($contentLibrary['contentAi']) ? $contentLibrary['contentAi'] : $contentLibrary['content'], 'timingTime' => date('Y-m-d H:i:s', $sendTime), 'beginTime' => date('Y-m-d H:i:s', $sendTime), 'endTime' => date('Y-m-d H:i:s', $sendTime + 600), diff --git a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php index ffe44918..feed3dee 100644 --- a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php +++ b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php @@ -1000,6 +1000,7 @@ class Adapter implements WeChatServiceInterface // 1. 获取要处理的wechatId和companyId列表 $customerList = Db::table('ck_device_wechat_login') ->field('DISTINCT wechatId, companyId') + ->order('id DESC') ->select(); $totalAffected = 0; @@ -1097,6 +1098,7 @@ class Adapter implements WeChatServiceInterface 'activity' => json_encode($activity), 'friendShip' => json_encode($friendShip), // 'weight' => json_encode($weight), + 'createTime' => $accountInfo['createTime'], 'updateTime' => time() ]; @@ -1228,13 +1230,15 @@ class Adapter implements WeChatServiceInterface public function syncTrafficSourceUser() { - $sql = "insert into ck_traffic_source(`identifier`,companyId,`fromd`,`sourceId`,`createTime`) + $sql = "insert into ck_traffic_source(`identifier`,companyId,`fromd`,`sourceId`,`createTime`,`type`, `status`) SELECT f.wechatId identifier, c.departmentId companyId, f.ownerNickname fromd, f.ownerWechatId sourceId, - f.createTime createTime + f.createTime createTime, + 1 as type, + CASE WHEN f.isDeleted = 1 THEN -3 ELSE 3 END as status FROM s2_wechat_friend f LEFT JOIN s2_wechat_account a ON f.wechatAccountId = a.id @@ -1261,13 +1265,15 @@ class Adapter implements WeChatServiceInterface public function syncTrafficSourceGroup() { - $sql = "insert into ck_traffic_source(`identifier`,companyId,`fromd`,`sourceId`,`createTime`) + $sql = "insert into ck_traffic_source(`identifier`,companyId,`fromd`,`sourceId`,`createTime`,`type`, `status`) SELECT m.wechatId identifier, c.departmentId companyId, r.nickname fromd, m.chatroomId sourceId, - m.createTime createTime + m.createTime createTime, + 2 as type, + CASE WHEN m.friendType = 1 THEN 3 ELSE 1 END as status FROM s2_wechat_chatroom_member m JOIN s2_wechat_chatroom r ON m.chatroomId = r.chatroomId