Merge branch 'yongpxu-master' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-master
This commit is contained in:
@@ -23,9 +23,15 @@ class AccountController extends BaseController
|
|||||||
* @param bool $isInner 是否为定时任务调用
|
* @param bool $isInner 是否为定时任务调用
|
||||||
* @return \think\response\Json
|
* @return \think\response\Json
|
||||||
*/
|
*/
|
||||||
public function getlist($pageIndex = '', $pageSize = '', $isInner = false)
|
public function getlist($data = [], $isInner = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
$pageIndex = !empty($data['pageIndex']) ? $data['pageIndex'] : 0;
|
||||||
|
$pageSize = !empty($data['pageSize']) ? $data['pageSize'] : 20;
|
||||||
|
$showNormalAccount = !empty($data['showNormalAccount']) ? $data['showNormalAccount'] : '';
|
||||||
|
$keyword = !empty($data['keyword']) ? $data['keyword'] : '';
|
||||||
|
$departmentId = !empty($data['departmentId']) ? $data['departmentId'] : '';
|
||||||
|
|
||||||
// 获取授权token
|
// 获取授权token
|
||||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||||
if (empty($authorization)) {
|
if (empty($authorization)) {
|
||||||
@@ -39,11 +45,11 @@ class AccountController extends BaseController
|
|||||||
try {
|
try {
|
||||||
// 构建请求参数
|
// 构建请求参数
|
||||||
$params = [
|
$params = [
|
||||||
'showNormalAccount' => $this->request->param('showNormalAccount', ''),
|
'showNormalAccount' => $showNormalAccount,
|
||||||
'keyword' => $this->request->param('keyword', ''),
|
'keyword' => $keyword,
|
||||||
'departmentId' => $this->request->param('departmentId', ''),
|
'departmentId' => $departmentId,
|
||||||
'pageIndex' => !empty($pageIndex) ? $pageIndex : $this->request->param('pageIndex', 0),
|
'pageIndex' => $pageIndex,
|
||||||
'pageSize' => !empty($pageSize) ? $pageSize : $this->request->param('pageSize', 20)
|
'pageSize' => $pageSize
|
||||||
];
|
];
|
||||||
|
|
||||||
// 设置请求头
|
// 设置请求头
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class AutomaticAssign extends BaseController
|
|||||||
* @param int $optFrom 操作来源
|
* @param int $optFrom 操作来源
|
||||||
* @return \think\response\Json
|
* @return \think\response\Json
|
||||||
*/
|
*/
|
||||||
public function allotWechatFriend($data = [],$isInner = false)
|
public function allotWechatFriend($data = [],$isInner = false,$errorNum = 0)
|
||||||
{
|
{
|
||||||
// 获取授权token
|
// 获取授权token
|
||||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||||
@@ -188,23 +188,24 @@ class AutomaticAssign extends BaseController
|
|||||||
$wechatFriendId = !empty($data['wechatFriendId']) ? $data['wechatFriendId'] : input('wechatFriendId', 0);
|
$wechatFriendId = !empty($data['wechatFriendId']) ? $data['wechatFriendId'] : input('wechatFriendId', 0);
|
||||||
$toAccountId = !empty($data['toAccountId']) ? $data['toAccountId'] : input('toAccountId', 0);
|
$toAccountId = !empty($data['toAccountId']) ? $data['toAccountId'] : input('toAccountId', 0);
|
||||||
$comment = !empty($data['comment']) ? $data['comment'] : input('comment', '');
|
$comment = !empty($data['comment']) ? $data['comment'] : input('comment', '');
|
||||||
$notifyReceiver = !empty($data['notifyReceiver']) ? $data['notifyReceiver'] : input('notifyReceiver', false);
|
$notifyReceiver = !empty($data['notifyReceiver']) ? $data['notifyReceiver'] : input('notifyReceiver', 'false');
|
||||||
$optFrom = !empty($data['optFrom']) ? $data['optFrom'] : input('optFrom', 4); // 默认操作来源为4
|
$optFrom = !empty($data['optFrom']) ? $data['optFrom'] : input('optFrom', 4); // 默认操作来源为4
|
||||||
|
|
||||||
// 参数验证
|
// 参数验证
|
||||||
if (empty($wechatFriendId)) {
|
if (empty($wechatFriendId)) {
|
||||||
return errorJson('微信好友ID不能为空');
|
return json_encode(['code'=>500,'msg'=>'微信好友ID不能为空']);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($toAccountId)) {
|
if (empty($toAccountId)) {
|
||||||
return errorJson('目标账号ID不能为空');
|
return json_encode(['code'=>500,'msg'=>'目标账号ID不能为空']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 设置请求头
|
// 设置请求头
|
||||||
$headerData = ['client:system'];
|
$headerData = ['client:system'];
|
||||||
$header = setHeader($headerData, $authorization, 'json');
|
$header = setHeader($headerData, $authorization, 'json');
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
$url = $this->baseUrl . 'api/WechatFriend/allot?wechatFriendId='.$wechatFriendId.'¬ifyReceiver='.$notifyReceiver.'&comment='.$comment.'&toAccountId='.$toAccountId.'&optFrom='.$optFrom;
|
$url = $this->baseUrl . 'api/WechatFriend/allot?wechatFriendId='.$wechatFriendId.'¬ifyReceiver='.$notifyReceiver.'&comment='.$comment.'&toAccountId='.$toAccountId.'&optFrom='.$optFrom;
|
||||||
$result = requestCurl($url, [], 'PUT', $header, 'json');
|
$result = requestCurl($url, [], 'PUT', $header, 'json');
|
||||||
@@ -226,8 +227,59 @@ class AutomaticAssign extends BaseController
|
|||||||
if($isInner){
|
if($isInner){
|
||||||
return json_encode(['code'=>500,'msg'=>'微信好友分配失败:' . $e->getMessage()]);
|
return json_encode(['code'=>500,'msg'=>'微信好友分配失败:' . $e->getMessage()]);
|
||||||
}else{
|
}else{
|
||||||
|
Cache::rm('system_authorization_token');
|
||||||
|
Cache::rm('system_refresh_token');
|
||||||
|
$errorNum ++;
|
||||||
|
if ($errorNum <= 3) {
|
||||||
|
$this->allotWechatFriend($data,$isInner,$errorNum);
|
||||||
|
}
|
||||||
|
return json_encode(['code'=>500,'msg'=> $result]);
|
||||||
return errorJson('微信好友分配失败:' . $e->getMessage());
|
return errorJson('微信好友分配失败:' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function multiAllotFriendToAccount($data = [],$errorNum = 0){
|
||||||
|
// 获取授权token
|
||||||
|
$authorization = $this->authorization;
|
||||||
|
if (empty($authorization)) {
|
||||||
|
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$wechatFriendIds = !empty($data['wechatFriendIds']) ? $data['wechatFriendIds'] : input('wechatFriendIds', []);
|
||||||
|
$toAccountId = !empty($data['toAccountId']) ? $data['toAccountId'] : input('toAccountId', 0);
|
||||||
|
$notifyReceiver = !empty($data['notifyReceiver']) ? $data['notifyReceiver'] : input('notifyReceiver', 'false');
|
||||||
|
// 参数验证
|
||||||
|
if (empty($wechatFriendIds)) {
|
||||||
|
return json_encode(['code'=>500,'msg'=>'微信好友ID不能为空']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($toAccountId)) {
|
||||||
|
return json_encode(['code'=>500,'msg'=>'目标账号ID不能为空']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
$headerData = ['client:system'];
|
||||||
|
$header = setHeader($headerData, $authorization, 'json');
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
$url = $this->baseUrl . 'api/WechatFriend/multiAllotFriendToAccount?wechatFriendIds='.$wechatFriendIds.'&toAccountId='.$toAccountId.'¬ifyReceiver='.$notifyReceiver;
|
||||||
|
$result = requestCurl($url, [], 'PUT', $header, 'json');
|
||||||
|
if (empty($result)) {
|
||||||
|
return json_encode(['code'=>200,'msg'=>'微信好友分配成功']);
|
||||||
|
} else {
|
||||||
|
Cache::rm('system_authorization_token');
|
||||||
|
Cache::rm('system_refresh_token');
|
||||||
|
$errorNum ++;
|
||||||
|
if ($errorNum <= 3) {
|
||||||
|
$this->multiAllotFriendToAccount($data,$errorNum);
|
||||||
|
}
|
||||||
|
return json_encode(['code'=>500,'msg'=> $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace app\api\controller;
|
namespace app\api\controller;
|
||||||
|
|
||||||
use app\api\model\WechatMessageModel;
|
use app\api\model\WechatMessageModel;
|
||||||
|
use think\Db;
|
||||||
use think\facade\Request;
|
use think\facade\Request;
|
||||||
|
|
||||||
class MessageController extends BaseController
|
class MessageController extends BaseController
|
||||||
@@ -386,6 +387,51 @@ class MessageController extends BaseController
|
|||||||
'wechatTime' => $wechatTime
|
'wechatTime' => $wechatTime
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//已被删除
|
||||||
|
if ($item['msgType'] == 10000 && strpos($item['content'],'开启了朋友验证') !== false) {
|
||||||
|
Db::table('s2_wechat_friend')->where('id',$item['wechatFriendId'])->update(['isDeleted'=> 1,'deleteTime' => $wechatTime]);
|
||||||
|
}else{
|
||||||
|
//优先分配在线客服
|
||||||
|
$friend = Db::table('s2_wechat_friend')->where('id',$item['wechatFriendId'])->find();
|
||||||
|
if (!empty($friend)){
|
||||||
|
$accountId = $item['accountId'];
|
||||||
|
$accountData = Db::table('s2_company_account')->where('id',$accountId)->find();
|
||||||
|
if (!empty($accountData)){
|
||||||
|
$account = new AccountController();
|
||||||
|
$account->getlist(['pageIndex' => 0,'pageSize' => 100,'departmentId' => $accountData['departmentId']]);
|
||||||
|
$accountIds = Db::table('s2_company_account')->where(['id' => $accountId,'alive' => 1])->column('id');
|
||||||
|
if (!empty($accountIds)){
|
||||||
|
if (!in_array($friend['accountId'],$accountData)){
|
||||||
|
// 执行切换好友命令
|
||||||
|
$randomKey = array_rand($accountIds, 1);
|
||||||
|
$toAccountId = $accountIds[$randomKey];
|
||||||
|
$toAccountData = Db::table('s2_company_account')->where('id',$toAccountId)->find();
|
||||||
|
$automaticAssign = new AutomaticAssign();
|
||||||
|
$automaticAssign->allotWechatFriend([
|
||||||
|
'wechatFriendId' => $friend['id'],
|
||||||
|
'toAccountId' => $toAccountId
|
||||||
|
], true);
|
||||||
|
Db::table('s2_wechat_friend')
|
||||||
|
->where('id',$friend['id'])
|
||||||
|
->update([
|
||||||
|
'accountId' => $toAccountId,
|
||||||
|
'accountUserName' => $toAccountData['userName'],
|
||||||
|
'accountRealName' => $toAccountData['realName'],
|
||||||
|
'accountNickname' => $toAccountData['nickname'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 创建新记录
|
// 创建新记录
|
||||||
WechatMessageModel::create($data);
|
WechatMessageModel::create($data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ class WebSocketController extends BaseController
|
|||||||
'username' => $userData['userName'],
|
'username' => $userData['userName'],
|
||||||
'password' => $userData['password']
|
'password' => $userData['password']
|
||||||
];
|
];
|
||||||
|
|
||||||
// 调用登录接口获取token
|
// 调用登录接口获取token
|
||||||
$headerData = ['client:kefu-client'];
|
$headerData = ['client:kefu-client'];
|
||||||
$header = setHeader($headerData, '', 'plain');
|
$header = setHeader($headerData, '', 'plain');
|
||||||
$result = requestCurl('https://kf.quwanzhi.com:9991/token', $params, 'POST', $header);
|
$result = requestCurl('https://kf.quwanzhi.com:9991/token', $params, 'POST', $header);
|
||||||
$result_array = handleApiResponse($result);
|
$result_array = handleApiResponse($result);
|
||||||
|
|
||||||
if (isset($result_array['access_token']) && !empty($result_array['access_token'])) {
|
if (isset($result_array['access_token']) && !empty($result_array['access_token'])) {
|
||||||
|
|||||||
@@ -72,7 +72,11 @@ class WechatChatroomController extends BaseController
|
|||||||
return successJson($response);
|
return successJson($response);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return errorJson('获取微信群聊列表失败:' . $e->getMessage());
|
if($isInner){
|
||||||
|
return json_encode(['code'=>200,'msg'=>'获取微信群聊列表失败' . $e->getMessage()]);
|
||||||
|
}else{
|
||||||
|
return errorJson('获取微信群聊列表失败:' . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
225
Server/application/command/SwitchFriendsCommand.php
Normal file
225
Server/application/command/SwitchFriendsCommand.php
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\command;
|
||||||
|
|
||||||
|
use app\job\WorkbenchAutoLikeJob;
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Option;
|
||||||
|
use think\console\Output;
|
||||||
|
use think\Db;
|
||||||
|
use think\facade\Cache;
|
||||||
|
use think\facade\Log;
|
||||||
|
use think\Queue;
|
||||||
|
use app\api\controller\AutomaticAssign;
|
||||||
|
|
||||||
|
class SwitchFriendsCommand extends Command
|
||||||
|
{
|
||||||
|
// 队列名称
|
||||||
|
protected $queueName = 'switch_friends';
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->setName('switch:friends')
|
||||||
|
->setDescription('切换好友命令');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(Input $input, Output $output)
|
||||||
|
{
|
||||||
|
//处理流量分过期数据
|
||||||
|
$expUserData = Db::name('workbench_traffic_config_item')
|
||||||
|
->where('expTime','<=',time())
|
||||||
|
->where('isRecycle',0)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
// 根据accountId对数组进行归类
|
||||||
|
$groupedByAccount = [];
|
||||||
|
foreach ($expUserData as $friend) {
|
||||||
|
$accountId = $friend['wechatAccountId'];
|
||||||
|
if (!isset($groupedByAccount[$accountId])) {
|
||||||
|
$groupedByAccount[$accountId] = [];
|
||||||
|
}
|
||||||
|
$friendId = $friend['wechatFriendId'];
|
||||||
|
$groupedByAccount[$accountId][] = $friendId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对每个账号的好友进行20个为一组的分组
|
||||||
|
foreach ($groupedByAccount as $accountId => $accountFriends) {
|
||||||
|
//检索主账号
|
||||||
|
$account = Db::name('users')->where('s2_accountId',$accountId)->find();
|
||||||
|
if (empty($account)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$account2 = Db::name('users')
|
||||||
|
->where('s2_accountId','>',0)
|
||||||
|
->where('companyId',$account['companyId'])
|
||||||
|
->order('s2_accountId ASC')
|
||||||
|
->find();
|
||||||
|
if (empty($account2)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$newaAccountId = $account2['s2_accountId'];
|
||||||
|
|
||||||
|
$chunks = array_chunk($accountFriends, 20);
|
||||||
|
$output->writeln('账号 ' . $newaAccountId . ' 共有 ' . count($accountFriends) . ' 个好友,分为 ' . count($chunks) . ' 组');
|
||||||
|
|
||||||
|
$automaticAssign = new AutomaticAssign();
|
||||||
|
foreach ($chunks as $chunkIndex => $chunk) {
|
||||||
|
$output->writeln('处理账号 ' . $newaAccountId . ' 第 ' . ($chunkIndex + 1) . ' 组,共 ' . count($chunk) . ' 个好友');
|
||||||
|
try {
|
||||||
|
$friendIds = implode(',', $chunk);
|
||||||
|
$res = $automaticAssign->multiAllotFriendToAccount([
|
||||||
|
'wechatFriendIds' => $friendIds,
|
||||||
|
'toAccountId' => $newaAccountId,
|
||||||
|
]);
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
if ($res['code'] == 200){
|
||||||
|
//修改数据库
|
||||||
|
Db::table('s2_wechat_friend')
|
||||||
|
->where('id',$friendIds)
|
||||||
|
->update([
|
||||||
|
'accountId' => $account2['s2_accountId'],
|
||||||
|
'accountUserName' => $account2['account'],
|
||||||
|
'accountRealName' => $account2['username'],
|
||||||
|
'accountNickname' => $account2['username'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Db::name('workbench_traffic_config_item')
|
||||||
|
->whereIn('wechatFriendId',$friendIds)
|
||||||
|
->where('wechatAccountId',$accountId)
|
||||||
|
->update([
|
||||||
|
'isRecycle' => 1,
|
||||||
|
'recycleTime' => time(),
|
||||||
|
]);
|
||||||
|
$output->writeln('✓ 成功切换好友:' . $friendIds . ' 到账号:' . $newaAccountId);
|
||||||
|
} else {
|
||||||
|
$output->writeln('✗ 切换失败 - 好友:' . $friendIds . ' 到账号:' . $newaAccountId . ' 结果:' . $res['msg']);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln('✗ 切换异常 - 好友:' . implode(',', $chunk) . ' 到账号:' . $newaAccountId . ' 错误:' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每组处理完后稍作延迟,避免请求过于频繁
|
||||||
|
if ($chunkIndex < count($chunks) - 1) {
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$cacheKey = 'allotWechatFriend';
|
||||||
|
$now = time();
|
||||||
|
$maxRetry = 5;
|
||||||
|
$retry = 0;
|
||||||
|
$switchedIds = [];
|
||||||
|
$totalProcessed = 0;
|
||||||
|
$totalSuccess = 0;
|
||||||
|
$totalFailed = 0;
|
||||||
|
|
||||||
|
$output->writeln('开始执行好友切换任务...');
|
||||||
|
|
||||||
|
do {
|
||||||
|
$friends = Cache::get($cacheKey, []);
|
||||||
|
$toSwitch = [];
|
||||||
|
foreach ($friends as $friend) {
|
||||||
|
if (isset($friend['time']) && $friend['time'] < $now) {
|
||||||
|
$toSwitch[] = $friend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($toSwitch)) {
|
||||||
|
$output->writeln('没有需要切换的好友');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('找到 ' . count($toSwitch) . ' 个需要切换的好友');
|
||||||
|
|
||||||
|
$automaticAssign = new AutomaticAssign();
|
||||||
|
|
||||||
|
// 根据accountId对数组进行归类
|
||||||
|
$groupedByAccount = [];
|
||||||
|
foreach ($toSwitch as $friend) {
|
||||||
|
$accountId = $friend['accountId'];
|
||||||
|
if (!isset($groupedByAccount[$accountId])) {
|
||||||
|
$groupedByAccount[$accountId] = [];
|
||||||
|
}
|
||||||
|
$friendId = !empty($friend['friendId']) ? $friend['friendId'] : $friend['id'];
|
||||||
|
$groupedByAccount[$accountId][] = $friendId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 对每个账号的好友进行20个为一组的分组
|
||||||
|
foreach ($groupedByAccount as $accountId => $accountFriends) {
|
||||||
|
$chunks = array_chunk($accountFriends, 20);
|
||||||
|
$output->writeln('账号 ' . $accountId . ' 共有 ' . count($accountFriends) . ' 个好友,分为 ' . count($chunks) . ' 组');
|
||||||
|
$accountSuccess = 0;
|
||||||
|
$accountFailed = 0;
|
||||||
|
|
||||||
|
foreach ($chunks as $chunkIndex => $chunk) {
|
||||||
|
$output->writeln('处理账号 ' . $accountId . ' 第 ' . ($chunkIndex + 1) . ' 组,共 ' . count($chunk) . ' 个好友');
|
||||||
|
try {
|
||||||
|
$friendIds = implode(',', $chunk);
|
||||||
|
$res = $automaticAssign->multiAllotFriendToAccount([
|
||||||
|
'wechatFriendIds' => $friendIds,
|
||||||
|
'toAccountId' => $accountId,
|
||||||
|
]);
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
if ($res['code'] == 200){
|
||||||
|
$output->writeln('✓ 成功切换好友:' . $friendIds . ' 到账号:' . $accountId);
|
||||||
|
$switchedIds = array_merge($switchedIds, $chunk);
|
||||||
|
$accountSuccess += count($chunk);
|
||||||
|
$totalSuccess += count($chunk);
|
||||||
|
} else {
|
||||||
|
$output->writeln('✗ 切换失败 - 好友:' . $friendIds . ' 到账号:' . $accountId . ' 结果:' . $res['msg']);
|
||||||
|
$accountFailed += count($chunk);
|
||||||
|
$totalFailed += count($chunk);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln('✗ 切换异常 - 好友:' . implode(',', $chunk) . ' 到账号:' . $accountId . ' 错误:' . $e->getMessage());
|
||||||
|
Log::error('切换好友异常: ' . $e->getMessage() . ' 好友IDs: ' . implode(',', $chunk) . ' 账号ID: ' . $accountId);
|
||||||
|
$accountFailed += count($chunk);
|
||||||
|
$totalFailed += count($chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalProcessed += count($chunk);
|
||||||
|
|
||||||
|
// 每组处理完后稍作延迟,避免请求过于频繁
|
||||||
|
if ($chunkIndex < count($chunks) - 1) {
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('账号 ' . $accountId . ' 处理完成 - 成功:' . $accountSuccess . ',失败:' . $accountFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤掉已切换的,保留未切换和新进来的
|
||||||
|
$newFriends = Cache::get($cacheKey, []);
|
||||||
|
$updated = [];
|
||||||
|
foreach ($newFriends as $friend) {
|
||||||
|
$friendId = !empty($friend['friendId']) ? $friend['friendId'] : $friend['id'];
|
||||||
|
if (!in_array($friendId, $switchedIds)) {
|
||||||
|
$updated[] = $friend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按time升序排序
|
||||||
|
usort($updated, function($a, $b) {
|
||||||
|
return ($a['time'] ?? 0) <=> ($b['time'] ?? 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
$success = Cache::set($cacheKey, $updated);
|
||||||
|
$retry++;
|
||||||
|
} while (!$success && $retry < $maxRetry);
|
||||||
|
|
||||||
|
$output->writeln('=== 切换任务完成 ===');
|
||||||
|
$output->writeln('总处理数量:' . $totalProcessed);
|
||||||
|
$output->writeln('成功切换:' . $totalSuccess);
|
||||||
|
$output->writeln('切换失败:' . $totalFailed);
|
||||||
|
$output->writeln('成功率:' . ($totalProcessed > 0 ? round(($totalSuccess / $totalProcessed) * 100, 2) : 0) . '%');
|
||||||
|
$output->writeln('缓存已更新并排序');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -45,6 +45,7 @@ Route::group('v1/', function () {
|
|||||||
Route::post('updateStatus', 'app\cunkebao\controller\plan\PlanSceneV1Controller@updateStatus');
|
Route::post('updateStatus', 'app\cunkebao\controller\plan\PlanSceneV1Controller@updateStatus');
|
||||||
Route::get('detail', 'app\cunkebao\controller\plan\GetAddFriendPlanDetailV1Controller@index');
|
Route::get('detail', 'app\cunkebao\controller\plan\GetAddFriendPlanDetailV1Controller@index');
|
||||||
Route::PUT('update', 'app\cunkebao\controller\plan\PostUpdateAddFriendPlanV1Controller@index');
|
Route::PUT('update', 'app\cunkebao\controller\plan\PostUpdateAddFriendPlanV1Controller@index');
|
||||||
|
Route::get('getWxMinAppCode', 'app\cunkebao\controller\plan\PlanSceneV1Controller@getWxMinAppCode');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 流量池相关
|
// 流量池相关
|
||||||
@@ -100,4 +101,18 @@ Route::group('v1/', function () {
|
|||||||
});
|
});
|
||||||
})->middleware(['jwt']);
|
})->middleware(['jwt']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::group('v1/frontend', function () {
|
||||||
|
|
||||||
|
Route::group('business/poster', function () {
|
||||||
|
Route::post('getone', 'app\cunkebao\controller\plan\PosterWeChatMiniProgram@getPosterTaskData');
|
||||||
|
Route::post('decryptphone', 'app\cunkebao\controller\plan\PosterWeChatMiniProgram@getPhoneNumber');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
@@ -4,9 +4,11 @@ namespace app\cunkebao\controller;
|
|||||||
|
|
||||||
use app\cunkebao\model\ContentLibrary;
|
use app\cunkebao\model\ContentLibrary;
|
||||||
use app\cunkebao\model\ContentItem;
|
use app\cunkebao\model\ContentItem;
|
||||||
|
use library\s2\titleFavicon;
|
||||||
use think\Controller;
|
use think\Controller;
|
||||||
use think\Db;
|
use think\Db;
|
||||||
use app\api\controller\WebSocketController;
|
use app\api\controller\WebSocketController;
|
||||||
|
use think\facade\Cache;
|
||||||
use think\facade\Env;
|
use think\facade\Env;
|
||||||
use app\api\controller\AutomaticAssign;
|
use app\api\controller\AutomaticAssign;
|
||||||
|
|
||||||
@@ -329,7 +331,7 @@ class ContentLibraryController extends Controller
|
|||||||
$library->timeEnd = isset($param['endTime']) ? strtotime($param['endTime']) : 0;
|
$library->timeEnd = isset($param['endTime']) ? strtotime($param['endTime']) : 0;
|
||||||
$library->status = isset($param['status']) ? $param['status'] : 0;
|
$library->status = isset($param['status']) ? $param['status'] : 0;
|
||||||
$library->updateTime = time();
|
$library->updateTime = time();
|
||||||
|
|
||||||
|
|
||||||
$library->save();
|
$library->save();
|
||||||
|
|
||||||
@@ -418,7 +420,7 @@ class ContentLibraryController extends Controller
|
|||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
$list = ContentItem::where($where)
|
$list = ContentItem::where($where)
|
||||||
->order('createTime', 'desc')
|
->order('createMomentTime DESC,createTime DESC')
|
||||||
->page($page, $limit)
|
->page($page, $limit)
|
||||||
->select();
|
->select();
|
||||||
|
|
||||||
@@ -751,7 +753,26 @@ class ContentLibraryController extends Controller
|
|||||||
/************************************
|
/************************************
|
||||||
* 数据采集相关功能
|
* 数据采集相关功能
|
||||||
************************************/
|
************************************/
|
||||||
|
|
||||||
|
function getExternalPageDetails($url)
|
||||||
|
{
|
||||||
|
$html = file_get_contents($url);
|
||||||
|
$dom = new \DOMDocument();
|
||||||
|
@$dom->loadHTML($html);
|
||||||
|
$xpath = new \DOMXPath($dom);
|
||||||
|
|
||||||
|
// 获取标题
|
||||||
|
$titleNode = $xpath->query('//title');
|
||||||
|
$title = $titleNode->length > 0 ? $titleNode->item(0)->nodeValue : '';
|
||||||
|
|
||||||
|
// 获取图标链接
|
||||||
|
$iconNode = $xpath->query('//link[@rel="shortcut icon"]/@href');
|
||||||
|
$icon = $iconNode->length > 0 ? $iconNode->item(0)->nodeValue : '';
|
||||||
|
|
||||||
|
return ['title' => $title, 'icon' => $icon];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行朋友圈采集任务
|
* 执行朋友圈采集任务
|
||||||
* @return \think\response\Json
|
* @return \think\response\Json
|
||||||
@@ -761,7 +782,7 @@ class ContentLibraryController extends Controller
|
|||||||
// 查询条件:未删除且已开启的内容库
|
// 查询条件:未删除且已开启的内容库
|
||||||
$where = [
|
$where = [
|
||||||
['isDel', '=', 0], // 未删除
|
['isDel', '=', 0], // 未删除
|
||||||
['status', '=', 1] // 已开启
|
['status', '=', 1], // 已开启
|
||||||
];
|
];
|
||||||
|
|
||||||
// 查询符合条件的内容库
|
// 查询符合条件的内容库
|
||||||
@@ -862,10 +883,11 @@ class ContentLibraryController extends Controller
|
|||||||
'message' => '没有指定要采集的好友'
|
'message' => '没有指定要采集的好友'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
$friendData = [];
|
||||||
try {
|
try {
|
||||||
$toAccountId = '';
|
$toAccountId = '';
|
||||||
$username = Env::get('api.username', '');
|
$username = Env::get('api.username2', '');
|
||||||
$password = Env::get('api.password', '');
|
$password = Env::get('api.password2', '');
|
||||||
if (!empty($username) || !empty($password)) {
|
if (!empty($username) || !empty($password)) {
|
||||||
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
|
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
|
||||||
}
|
}
|
||||||
@@ -889,10 +911,14 @@ class ContentLibraryController extends Controller
|
|||||||
$totalMomentsCount = 0;
|
$totalMomentsCount = 0;
|
||||||
|
|
||||||
foreach ($friends as $friend) {
|
foreach ($friends as $friend) {
|
||||||
|
$friendData = $friend;
|
||||||
if (!empty($username) && !empty($password)) {
|
if (!empty($username) && !empty($password)) {
|
||||||
//执行切换好友命令
|
//执行切换好友命令
|
||||||
$automaticAssign = new AutomaticAssign();
|
$automaticAssign = new AutomaticAssign();
|
||||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['id'],'toAccountId' => $toAccountId],true);
|
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['id'],'toAccountId' => $toAccountId],true);
|
||||||
|
//存入缓存
|
||||||
|
$friendData['friendId'] = $friend['id'];
|
||||||
|
artificialAllotWechatFriend($friendData);
|
||||||
//执行采集朋友圈命令
|
//执行采集朋友圈命令
|
||||||
$webSocket = new WebSocketController(['userName' => $username,'password' => $password,'accountId' => $toAccountId]);
|
$webSocket = new WebSocketController(['userName' => $username,'password' => $password,'accountId' => $toAccountId]);
|
||||||
$webSocket->getMoments(['wechatFriendId' => $friend['id'],'wechatAccountId' => $friend['wechatAccountId']]);
|
$webSocket->getMoments(['wechatFriendId' => $friend['id'],'wechatAccountId' => $friend['wechatAccountId']]);
|
||||||
@@ -1389,9 +1415,56 @@ class ContentLibraryController extends Controller
|
|||||||
$coverImage = $resUrls[0];
|
$coverImage = $resUrls[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断内容类型 (0=未知, 1=图片, 2=链接, 3=视频, 4=文本, 5=小程序, 6=图文)
|
// 判断内容类型 (0=未知, 1=图片, 2=链接, 3=视频, 4=文本, 5=小程序)
|
||||||
$contentType = $this->determineContentType($moment['content'] ?? '', $resUrls, $urls);
|
if($moment['type'] == 1) {
|
||||||
|
//图文
|
||||||
|
$contentType = 1;
|
||||||
|
}elseif ($moment['type'] == 3){
|
||||||
|
//链接
|
||||||
|
$contentType = 2;
|
||||||
|
$urls = [];
|
||||||
|
$url = is_string($moment['urls']) ? json_decode($moment['urls'], true) : $moment['urls'] ?? [];
|
||||||
|
$url = $url[0];
|
||||||
|
|
||||||
|
// 检查是否是飞书链接
|
||||||
|
if (strpos($url, 'feishu.cn') !== false) {
|
||||||
|
// 飞书文档需要登录,无法直接获取内容,返回默认信息
|
||||||
|
$urls[] = [
|
||||||
|
'url' => $url,
|
||||||
|
'image' => 'http://karuosiyujzk.oss-cn-shenzhen.aliyuncs.com/2025/07/09/3db2a5d7fe49011ab68175a42a5094ce.jpeg',
|
||||||
|
'desc' => '飞书文档'
|
||||||
|
];
|
||||||
|
}else{
|
||||||
|
$getUrlDetails = $this->getExternalPageDetails($url);
|
||||||
|
$icon = 'http://karuosiyujzk.oss-cn-shenzhen.aliyuncs.com/2025/07/09/ec039d96fad6eab1d960f207d3d9ca9f.jpeg';
|
||||||
|
if (!empty($getUrlDetails['title'])) {
|
||||||
|
$urls[] = [
|
||||||
|
'url' => $url,
|
||||||
|
'image' => $icon,
|
||||||
|
'desc' => '点击查看详情'
|
||||||
|
];
|
||||||
|
}else{
|
||||||
|
$urls[] = [
|
||||||
|
'url' => $url,
|
||||||
|
'image' => !empty($getUrlDetails['icon']) ? $getUrlDetails['icon'] : $icon,
|
||||||
|
'desc' => $getUrlDetails['title']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$moment['urls'] = $urls;
|
||||||
|
}elseif ($moment['type'] == 15){
|
||||||
|
//视频
|
||||||
|
$contentType = 3;
|
||||||
|
}elseif ($moment['type'] == 2){
|
||||||
|
//纯文本
|
||||||
|
$contentType = 4;
|
||||||
|
}elseif ($moment['type'] == 30){
|
||||||
|
//小程序
|
||||||
|
$contentType = 5;
|
||||||
|
}else{
|
||||||
|
$contentType = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果不存在,则创建新的内容项目
|
// 如果不存在,则创建新的内容项目
|
||||||
$item = new ContentItem();
|
$item = new ContentItem();
|
||||||
$item->libraryId = $libraryId;
|
$item->libraryId = $libraryId;
|
||||||
@@ -1695,4 +1768,167 @@ class ContentLibraryController extends Controller
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析URL获取网页信息(内部调用)
|
||||||
|
* @param string $url 要解析的URL
|
||||||
|
* @return array 包含title、icon的数组,失败返回空数组
|
||||||
|
*/
|
||||||
|
public function parseUrl($url)
|
||||||
|
{
|
||||||
|
if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 设置请求头,模拟浏览器访问
|
||||||
|
$context = stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'GET',
|
||||||
|
'header' => [
|
||||||
|
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||||
|
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||||
|
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
|
||||||
|
'Accept-Encoding: gzip, deflate',
|
||||||
|
'Connection: keep-alive',
|
||||||
|
'Upgrade-Insecure-Requests: 1'
|
||||||
|
],
|
||||||
|
'timeout' => 10,
|
||||||
|
'follow_location' => true,
|
||||||
|
'max_redirects' => 3
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 获取网页内容
|
||||||
|
$html = @file_get_contents($url, false, $context);
|
||||||
|
|
||||||
|
if ($html === false) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测编码并转换为UTF-8
|
||||||
|
$encoding = mb_detect_encoding($html, ['UTF-8', 'GBK', 'GB2312', 'BIG5', 'ASCII']);
|
||||||
|
if ($encoding && $encoding !== 'UTF-8') {
|
||||||
|
$html = mb_convert_encoding($html, 'UTF-8', $encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析HTML
|
||||||
|
$dom = new \DOMDocument();
|
||||||
|
@$dom->loadHTML($html, LIBXML_NOERROR | LIBXML_NOWARNING);
|
||||||
|
$xpath = new \DOMXPath($dom);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'title' => '',
|
||||||
|
'icon' => '',
|
||||||
|
'url' => $url
|
||||||
|
];
|
||||||
|
|
||||||
|
// 提取标题
|
||||||
|
$titleNodes = $xpath->query('//title');
|
||||||
|
if ($titleNodes->length > 0) {
|
||||||
|
$result['title'] = trim($titleNodes->item(0)->textContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取图标 - 优先获取favicon
|
||||||
|
$iconNodes = $xpath->query('//link[@rel="icon"]/@href | //link[@rel="shortcut icon"]/@href | //link[@rel="apple-touch-icon"]/@href');
|
||||||
|
if ($iconNodes->length > 0) {
|
||||||
|
$iconUrl = trim($iconNodes->item(0)->value);
|
||||||
|
$result['icon'] = $this->makeAbsoluteUrl($iconUrl, $url);
|
||||||
|
} else {
|
||||||
|
// 尝试获取Open Graph图片
|
||||||
|
$ogImageNodes = $xpath->query('//meta[@property="og:image"]/@content');
|
||||||
|
if ($ogImageNodes->length > 0) {
|
||||||
|
$result['icon'] = trim($ogImageNodes->item(0)->value);
|
||||||
|
} else {
|
||||||
|
// 默认favicon路径
|
||||||
|
$result['icon'] = $this->makeAbsoluteUrl('/favicon.ico', $url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理和验证数据
|
||||||
|
$result['title'] = $this->cleanText($result['title']);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// 记录错误日志但不抛出异常
|
||||||
|
\think\facade\Log::error('URL解析失败: ' . $e->getMessage() . ' URL: ' . $url);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将相对URL转换为绝对URL
|
||||||
|
* @param string $relativeUrl 相对URL
|
||||||
|
* @param string $baseUrl 基础URL
|
||||||
|
* @return string 绝对URL
|
||||||
|
*/
|
||||||
|
private function makeAbsoluteUrl($relativeUrl, $baseUrl)
|
||||||
|
{
|
||||||
|
if (empty($relativeUrl)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经是绝对URL,直接返回
|
||||||
|
if (filter_var($relativeUrl, FILTER_VALIDATE_URL)) {
|
||||||
|
return $relativeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析基础URL
|
||||||
|
$baseParts = parse_url($baseUrl);
|
||||||
|
if (!$baseParts) {
|
||||||
|
return $relativeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理以/开头的绝对路径
|
||||||
|
if (strpos($relativeUrl, '/') === 0) {
|
||||||
|
return $baseParts['scheme'] . '://' . $baseParts['host'] .
|
||||||
|
(isset($baseParts['port']) ? ':' . $baseParts['port'] : '') .
|
||||||
|
$relativeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理相对路径
|
||||||
|
$basePath = isset($baseParts['path']) ? dirname($baseParts['path']) : '/';
|
||||||
|
if ($basePath === '.') {
|
||||||
|
$basePath = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $baseParts['scheme'] . '://' . $baseParts['host'] .
|
||||||
|
(isset($baseParts['port']) ? ':' . $baseParts['port'] : '') .
|
||||||
|
$basePath . '/' . $relativeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理文本内容
|
||||||
|
* @param string $text 要清理的文本
|
||||||
|
* @return string 清理后的文本
|
||||||
|
*/
|
||||||
|
private function cleanText($text)
|
||||||
|
{
|
||||||
|
if (empty($text)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除HTML实体
|
||||||
|
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
|
|
||||||
|
// 移除多余的空白字符
|
||||||
|
$text = preg_replace('/\s+/', ' ', $text);
|
||||||
|
|
||||||
|
// 移除控制字符
|
||||||
|
$text = preg_replace('/[\x00-\x1F\x7F]/', '', $text);
|
||||||
|
|
||||||
|
return trim($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace app\cunkebao\controller;
|
namespace app\cunkebao\controller;
|
||||||
|
|
||||||
|
use app\common\model\Device as DeviceModel;
|
||||||
|
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||||
use app\cunkebao\model\Workbench;
|
use app\cunkebao\model\Workbench;
|
||||||
use app\cunkebao\model\WorkbenchAutoLike;
|
use app\cunkebao\model\WorkbenchAutoLike;
|
||||||
use app\cunkebao\model\WorkbenchMomentsSync;
|
use app\cunkebao\model\WorkbenchMomentsSync;
|
||||||
@@ -236,10 +238,16 @@ class WorkbenchController extends Controller
|
|||||||
$item->config = $item->momentsSync;
|
$item->config = $item->momentsSync;
|
||||||
$item->config->devices = json_decode($item->config->devices, true);
|
$item->config->devices = json_decode($item->config->devices, true);
|
||||||
$item->config->contentLibraries = json_decode($item->config->contentLibraries, true);
|
$item->config->contentLibraries = json_decode($item->config->contentLibraries, true);
|
||||||
|
//同步记录
|
||||||
|
$sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->count();
|
||||||
|
$item->syncCount = $sendNum;
|
||||||
|
$lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->order('id DESC')->value('createTime');
|
||||||
|
$item->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i',$lastTime) : '--';
|
||||||
|
|
||||||
|
|
||||||
// 获取内容库名称
|
// 获取内容库名称
|
||||||
if (!empty($item->config->contentLibraries)) {
|
if (!empty($item->config->contentLibraries)) {
|
||||||
$libraryNames = ContentLibrary::where('id', 'in', $item->config->contentLibraries)
|
$libraryNames = ContentLibrary::whereIn('id', $item->config->contentLibraries)
|
||||||
->column('name');
|
->column('name');
|
||||||
$item->config->contentLibraryNames = $libraryNames;
|
$item->config->contentLibraryNames = $libraryNames;
|
||||||
} else {
|
} else {
|
||||||
@@ -426,6 +434,42 @@ class WorkbenchController extends Controller
|
|||||||
$workbench->config = $workbench->momentsSync;
|
$workbench->config = $workbench->momentsSync;
|
||||||
$workbench->config->devices = json_decode($workbench->config->devices, true);
|
$workbench->config->devices = json_decode($workbench->config->devices, true);
|
||||||
$workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true);
|
$workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true);
|
||||||
|
|
||||||
|
//同步记录
|
||||||
|
$sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->count();
|
||||||
|
$workbench->syncCount = $sendNum;
|
||||||
|
$lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->value('createTime');
|
||||||
|
$workbench->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i',$lastTime) : '--';
|
||||||
|
|
||||||
|
// 获取内容库名称
|
||||||
|
if (!empty($workbench->config->contentLibraries)) {
|
||||||
|
$libraryNames = ContentLibrary::where('id', 'in', $workbench->config->contentLibraries)
|
||||||
|
->select();
|
||||||
|
$workbench->config->contentLibraries = $libraryNames;
|
||||||
|
} else {
|
||||||
|
$workbench->config->contentLibraryNames = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($workbench->config->devices)){
|
||||||
|
$deviceList = DeviceModel::alias('d')
|
||||||
|
->field([
|
||||||
|
'd.id', 'd.imei', 'd.memo', 'd.alive',
|
||||||
|
'l.wechatId',
|
||||||
|
'a.nickname', 'a.alias', 'a.avatar','a.alias'
|
||||||
|
])
|
||||||
|
->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId')
|
||||||
|
->leftJoin('wechat_account a', 'l.wechatId = a.wechatId')
|
||||||
|
->whereIn('d.id',$workbench->config->devices)
|
||||||
|
->order('d.id desc')
|
||||||
|
->select();
|
||||||
|
$workbench->config->deviceList = $deviceList;
|
||||||
|
}else{
|
||||||
|
$workbench->config->deviceList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
unset($workbench->momentsSync,$workbench->moments_sync);
|
unset($workbench->momentsSync,$workbench->moments_sync);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -635,6 +679,18 @@ class WorkbenchController extends Controller
|
|||||||
case self::TYPE_MOMENTS_SYNC:
|
case self::TYPE_MOMENTS_SYNC:
|
||||||
$config = WorkbenchMomentsSync::where('workbenchId', $param['id'])->find();
|
$config = WorkbenchMomentsSync::where('workbenchId', $param['id'])->find();
|
||||||
if ($config) {
|
if ($config) {
|
||||||
|
if (!empty($param['contentLibraries'])){
|
||||||
|
foreach ($param['contentLibraries'] as $library){
|
||||||
|
if(isset($library['id']) && !empty($library['id'])){
|
||||||
|
$contentLibraries[] = $library['id'];
|
||||||
|
}else{
|
||||||
|
$contentLibraries[] = $library;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
$contentLibraries = [];
|
||||||
|
}
|
||||||
|
|
||||||
$config->syncInterval = $param['syncInterval'];
|
$config->syncInterval = $param['syncInterval'];
|
||||||
$config->syncCount = $param['syncCount'];
|
$config->syncCount = $param['syncCount'];
|
||||||
$config->syncType = $param['syncType'];
|
$config->syncType = $param['syncType'];
|
||||||
@@ -642,7 +698,7 @@ class WorkbenchController extends Controller
|
|||||||
$config->endTime = $param['endTime'];
|
$config->endTime = $param['endTime'];
|
||||||
$config->accountType = $param['accountType'];
|
$config->accountType = $param['accountType'];
|
||||||
$config->devices = json_encode($param['devices']);
|
$config->devices = json_encode($param['devices']);
|
||||||
$config->contentLibraries = json_encode($param['contentLibraries'] ?? []);
|
$config->contentLibraries = json_encode($contentLibraries);
|
||||||
$config->updateTime = time();
|
$config->updateTime = time();
|
||||||
$config->save();
|
$config->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,25 +21,31 @@ class GetChatroomListV1Controller extends BaseController
|
|||||||
$limit = $this->request->param('limit', 20);
|
$limit = $this->request->param('limit', 20);
|
||||||
$keyword = $this->request->param('keyword', '');
|
$keyword = $this->request->param('keyword', '');
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
$wechatIds = Db::name('device')->alias('d')
|
||||||
|
->join('device_wechat_login dwl','dwl.deviceId=d.id AND dwl.companyId='.$this->getUserInfo('companyId'))
|
||||||
|
->where(['d.companyId' => $this->getUserInfo('companyId'),'d.deleteTime' => 0])
|
||||||
|
->column('dwl.wechatId');
|
||||||
|
|
||||||
|
|
||||||
$where = [];
|
$where = [];
|
||||||
if ($this->getUserInfo('isAdmin') == 1) {
|
if ($this->getUserInfo('isAdmin') == 1) {
|
||||||
$where[] = ['g.companyId', '=', $this->getUserInfo('companyId')];
|
|
||||||
$where[] = ['g.deleteTime', '=', 0];
|
$where[] = ['g.deleteTime', '=', 0];
|
||||||
|
$where[] = ['g.ownerWechatId', 'in', $wechatIds];
|
||||||
} else {
|
} else {
|
||||||
$where[] = ['g.companyId', '=', $this->getUserInfo('companyId')];
|
|
||||||
$where[] = ['g.deleteTime', '=', 0];
|
$where[] = ['g.deleteTime', '=', 0];
|
||||||
|
$where[] = ['g.ownerWechatId', 'in', $wechatIds];
|
||||||
//$where[] = ['g.userId', '=', $this->getUserInfo('id')];
|
//$where[] = ['g.userId', '=', $this->getUserInfo('id')];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($keyword)){
|
if(!empty($keyword)){
|
||||||
$where[] = ['g.name', 'like', '%'.$keyword.'%'];
|
$where[] = ['g.name', 'like', '%'.$keyword.'%'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = WechatChatroom::alias('g')
|
$data = WechatChatroom::alias('g')
|
||||||
->field(['g.id', 'g.chatroomId', 'g.name', 'g.avatar','g.ownerWechatId', 'g.identifier', 'g.createTime',
|
->field(['g.id', 'g.chatroomId', 'g.name', 'g.avatar','g.ownerWechatId', 'g.identifier', 'g.createTime',
|
||||||
'wa.nickname as ownerNickname','wa.avatar as ownerAvatar','wa.alias as ownerAlias'])
|
'wa.nickname as ownerNickname','wa.avatar as ownerAvatar','wa.alias as ownerAlias'])
|
||||||
->Join('wechat_account wa', 'g.ownerWechatId = wa.wechatId', 'LEFT')
|
->join('wechat_account wa', 'g.ownerWechatId = wa.wechatId', 'LEFT')
|
||||||
->where($where);
|
->where($where);
|
||||||
|
|
||||||
$total = $data->count();
|
$total = $data->count();
|
||||||
|
|||||||
@@ -38,23 +38,21 @@ class GetFriendListV1Controller extends BaseController
|
|||||||
$where[] = ['isDeleted','=',0];
|
$where[] = ['isDeleted','=',0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(!empty($keyword)){
|
if(!empty($keyword)){
|
||||||
$where[] = ['nickname|alias|wechatId','like','%'.$keyword.'%'];
|
$where[] = ['nickname|alias|wechatId','like','%'.$keyword.'%'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$wechatIds = Db::name('device')->alias('d')
|
||||||
|
->join('device_wechat_login dwl','dwl.deviceId=d.id AND dwl.companyId='.$this->getUserInfo('companyId'))
|
||||||
|
->where(['d.companyId' => $this->getUserInfo('companyId'),'d.deleteTime' => 0]);
|
||||||
|
|
||||||
$devices = Db::name('device_wechat_login')
|
if (!empty($deviceIds)){
|
||||||
->where(['companyId' => $this->getUserInfo('companyId'),'alive' => 1])
|
$wechatIds = $wechatIds->where('d.id','in',$deviceIds);
|
||||||
->order('id desc')
|
|
||||||
->group('wechatId');
|
|
||||||
|
|
||||||
if(!empty($deviceIds) && is_array($deviceIds)){
|
|
||||||
$devices = $devices->whereIn('deviceId',$deviceIds);
|
|
||||||
}
|
}
|
||||||
$devices = $devices->column('wechatId');
|
$wechatIds = $wechatIds->column('dwl.wechatId');
|
||||||
|
|
||||||
$where[] = ['ownerWechatId','in',$devices];
|
|
||||||
|
$where[] = ['ownerWechatId','in',$wechatIds];
|
||||||
|
|
||||||
$data = Db::table('s2_wechat_friend')
|
$data = Db::table('s2_wechat_friend')
|
||||||
->field(['nickname','avatar','alias','id','wechatId','ownerNickname','ownerAlias','ownerWechatId','createTime'])
|
->field(['nickname','avatar','alias','id','wechatId','ownerNickname','ownerAlias','ownerWechatId','createTime'])
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ namespace app\cunkebao\controller\plan;
|
|||||||
use library\ResponseHelper;
|
use library\ResponseHelper;
|
||||||
use think\Db;
|
use think\Db;
|
||||||
use app\cunkebao\controller\BaseController;
|
use app\cunkebao\controller\BaseController;
|
||||||
|
use app\cunkebao\controller\plan\PosterWeChatMiniProgram;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取计划任务列表控制器
|
* 获取计划任务列表控制器
|
||||||
*/
|
*/
|
||||||
@@ -54,18 +56,18 @@ class PlanSceneV1Controller extends BaseController
|
|||||||
$val['acquiredCount'] = Db::name('task_customer')->where('task_id',$val['id'])->count();
|
$val['acquiredCount'] = Db::name('task_customer')->where('task_id',$val['id'])->count();
|
||||||
$val['addedCount'] = Db::name('task_customer')->where('task_id',$val['id'])->whereIn('status',[1,2,3,4])->count();
|
$val['addedCount'] = Db::name('task_customer')->where('task_id',$val['id'])->whereIn('status',[1,2,3,4])->count();
|
||||||
$val['passCount'] = Db::name('task_customer')->where('task_id',$val['id'])->where('status',4)->count();
|
$val['passCount'] = Db::name('task_customer')->where('task_id',$val['id'])->where('status',4)->count();
|
||||||
|
|
||||||
$val['passRate'] = 0;
|
$val['passRate'] = 0;
|
||||||
if(!empty($val['passCount']) && !empty($val['addedCount'])){
|
if(!empty($val['passCount']) && !empty($val['addedCount'])){
|
||||||
$passRate = ($val['addedCount'] / $val['passCount']) * 100;
|
$passRate = ($val['passCount'] / $val['addedCount']) * 100;
|
||||||
$val['passRate'] = number_format($passRate,2);
|
$val['passRate'] = number_format($passRate,2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$lastTime = Db::name('task_customer')->where(['task_id'=>$val['id']])->max('updated_at');
|
||||||
|
$val['lastUpdated'] = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--';
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
unset($val);
|
unset($val);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return ResponseHelper::success([
|
return ResponseHelper::success([
|
||||||
'total' => $total,
|
'total' => $total,
|
||||||
'list' => $list
|
'list' => $list
|
||||||
@@ -75,41 +77,6 @@ class PlanSceneV1Controller extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 拷贝计划任务
|
|
||||||
*
|
|
||||||
* @return \think\response\Json
|
|
||||||
*/
|
|
||||||
public function copy()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$params = $this->request->param();
|
|
||||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
|
||||||
|
|
||||||
if ($planId <= 0) {
|
|
||||||
return ResponseHelper::error('计划ID不能为空', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$plan = Db::name('customer_acquisition_task')->where('id', $planId)->find();
|
|
||||||
if (!$plan) {
|
|
||||||
return ResponseHelper::error('计划不存在', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($plan['id']);
|
|
||||||
$plan['name'] = $plan['name'] . ' (拷贝)';
|
|
||||||
$plan['createTime'] = time();
|
|
||||||
$plan['updateTime'] = time();
|
|
||||||
|
|
||||||
$newPlanId = Db::name('customer_acquisition_task')->insertGetId($plan);
|
|
||||||
if (!$newPlanId) {
|
|
||||||
return ResponseHelper::error('拷贝计划失败', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseHelper::success(['planId' => $newPlanId], '拷贝计划任务成功');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除计划任务
|
* 删除计划任务
|
||||||
@@ -163,4 +130,273 @@ class PlanSceneV1Controller extends BaseController
|
|||||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取获客计划设备列表
|
||||||
|
*
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function getPlanDevices()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$params = $this->request->param();
|
||||||
|
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||||
|
$page = isset($params['page']) ? intval($params['page']) : 1;
|
||||||
|
$limit = isset($params['limit']) ? intval($params['limit']) : 10;
|
||||||
|
$deviceStatus = isset($params['deviceStatus']) ? $params['deviceStatus'] : '';
|
||||||
|
$searchKeyword = isset($params['searchKeyword']) ? trim($params['searchKeyword']) : '';
|
||||||
|
|
||||||
|
// 验证计划ID
|
||||||
|
if ($planId <= 0) {
|
||||||
|
return ResponseHelper::error('计划ID不能为空', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证计划是否存在且用户有权限
|
||||||
|
$plan = Db::name('customer_acquisition_task')
|
||||||
|
->where([
|
||||||
|
'id' => $planId,
|
||||||
|
'deleteTime' => 0,
|
||||||
|
'companyId' => $this->getUserInfo('companyId')
|
||||||
|
])
|
||||||
|
->find();
|
||||||
|
|
||||||
|
if (!$plan) {
|
||||||
|
return ResponseHelper::error('计划不存在或无权限访问', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是管理员,需要验证用户权限
|
||||||
|
if ($this->getUserInfo('isAdmin')) {
|
||||||
|
$userPlan = Db::name('customer_acquisition_task')
|
||||||
|
->where([
|
||||||
|
'id' => $planId,
|
||||||
|
'userId' => $this->getUserInfo('id')
|
||||||
|
])
|
||||||
|
->find();
|
||||||
|
|
||||||
|
if (!$userPlan) {
|
||||||
|
return ResponseHelper::error('您没有权限访问该计划', 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
$where = [
|
||||||
|
'pt.plan_id' => $planId,
|
||||||
|
'd.deleteTime' => 0,
|
||||||
|
'd.companyId' => $this->getUserInfo('companyId')
|
||||||
|
];
|
||||||
|
|
||||||
|
// 设备状态筛选
|
||||||
|
if (!empty($deviceStatus)) {
|
||||||
|
$where['d.alive'] = $deviceStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
$searchWhere = [];
|
||||||
|
if (!empty($searchKeyword)) {
|
||||||
|
$searchWhere[] = ['d.imei', 'like', "%{$searchKeyword}%"];
|
||||||
|
$searchWhere[] = ['d.memo', 'like', "%{$searchKeyword}%"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询设备总数
|
||||||
|
$totalQuery = Db::name('plan_task_device')->alias('pt')
|
||||||
|
->join('device d', 'pt.device_id = d.id')
|
||||||
|
->where($where);
|
||||||
|
|
||||||
|
if (!empty($searchWhere)) {
|
||||||
|
$totalQuery->where(function ($query) use ($searchWhere) {
|
||||||
|
foreach ($searchWhere as $condition) {
|
||||||
|
$query->whereOr($condition[0], $condition[1], $condition[2]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $totalQuery->count();
|
||||||
|
|
||||||
|
// 查询设备列表
|
||||||
|
$listQuery = Db::name('plan_task_device')->alias('pt')
|
||||||
|
->join('device d', 'pt.device_id = d.id')
|
||||||
|
->field([
|
||||||
|
'd.id',
|
||||||
|
'd.imei',
|
||||||
|
'd.memo',
|
||||||
|
'd.alive',
|
||||||
|
'd.extra',
|
||||||
|
'd.createTime',
|
||||||
|
'd.updateTime',
|
||||||
|
'pt.status as plan_device_status',
|
||||||
|
'pt.createTime as assign_time'
|
||||||
|
])
|
||||||
|
->where($where)
|
||||||
|
->order('pt.createTime', 'desc');
|
||||||
|
|
||||||
|
if (!empty($searchWhere)) {
|
||||||
|
$listQuery->where(function ($query) use ($searchWhere) {
|
||||||
|
foreach ($searchWhere as $condition) {
|
||||||
|
$query->whereOr($condition[0], $condition[1], $condition[2]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = $listQuery->page($page, $limit)->select();
|
||||||
|
|
||||||
|
// 处理设备数据
|
||||||
|
foreach ($list as &$device) {
|
||||||
|
// 格式化时间
|
||||||
|
$device['createTime'] = date('Y-m-d H:i:s', $device['createTime']);
|
||||||
|
$device['updateTime'] = date('Y-m-d H:i:s', $device['updateTime']);
|
||||||
|
$device['assign_time'] = date('Y-m-d H:i:s', $device['assign_time']);
|
||||||
|
|
||||||
|
// 解析设备额外信息
|
||||||
|
if (!empty($device['extra'])) {
|
||||||
|
$extra = json_decode($device['extra'], true);
|
||||||
|
$device['battery'] = isset($extra['battery']) ? intval($extra['battery']) : 0;
|
||||||
|
$device['device_info'] = $extra;
|
||||||
|
} else {
|
||||||
|
$device['battery'] = 0;
|
||||||
|
$device['device_info'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备状态文本
|
||||||
|
$device['alive_text'] = $this->getDeviceStatusText($device['alive']);
|
||||||
|
$device['plan_device_status_text'] = $this->getPlanDeviceStatusText($device['plan_device_status']);
|
||||||
|
|
||||||
|
// 获取设备当前微信登录信息
|
||||||
|
$wechatLogin = Db::name('device_wechat_login')
|
||||||
|
->where([
|
||||||
|
'deviceId' => $device['id'],
|
||||||
|
'companyId' => $this->getUserInfo('companyId'),
|
||||||
|
'alive' => 1
|
||||||
|
])
|
||||||
|
->order('createTime', 'desc')
|
||||||
|
->find();
|
||||||
|
|
||||||
|
$device['current_wechat'] = $wechatLogin ? [
|
||||||
|
'wechatId' => $wechatLogin['wechatId'],
|
||||||
|
'nickname' => $wechatLogin['nickname'] ?? '',
|
||||||
|
'loginTime' => date('Y-m-d H:i:s', $wechatLogin['createTime'])
|
||||||
|
] : null;
|
||||||
|
|
||||||
|
// 获取设备在该计划中的任务统计
|
||||||
|
$device['task_stats'] = $this->getDeviceTaskStats($device['id'], $planId);
|
||||||
|
|
||||||
|
// 移除原始extra字段
|
||||||
|
unset($device['extra']);
|
||||||
|
}
|
||||||
|
unset($device);
|
||||||
|
|
||||||
|
return ResponseHelper::success([
|
||||||
|
'total' => $total,
|
||||||
|
'list' => $list,
|
||||||
|
'plan_info' => [
|
||||||
|
'id' => $plan['id'],
|
||||||
|
'name' => $plan['name'],
|
||||||
|
'status' => $plan['status']
|
||||||
|
]
|
||||||
|
], '获取计划设备列表成功');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备状态文本
|
||||||
|
*
|
||||||
|
* @param int $status
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getDeviceStatusText($status)
|
||||||
|
{
|
||||||
|
$statusMap = [
|
||||||
|
0 => '离线',
|
||||||
|
1 => '在线',
|
||||||
|
2 => '忙碌',
|
||||||
|
3 => '故障'
|
||||||
|
];
|
||||||
|
return isset($statusMap[$status]) ? $statusMap[$status] : '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取计划设备状态文本
|
||||||
|
*
|
||||||
|
* @param int $status
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getPlanDeviceStatusText($status)
|
||||||
|
{
|
||||||
|
$statusMap = [
|
||||||
|
0 => '待分配',
|
||||||
|
1 => '已分配',
|
||||||
|
2 => '执行中',
|
||||||
|
3 => '已完成',
|
||||||
|
4 => '已暂停',
|
||||||
|
5 => '已取消'
|
||||||
|
];
|
||||||
|
return isset($statusMap[$status]) ? $statusMap[$status] : '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备在指定计划中的任务统计
|
||||||
|
*
|
||||||
|
* @param int $deviceId
|
||||||
|
* @param int $planId
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getDeviceTaskStats($deviceId, $planId)
|
||||||
|
{
|
||||||
|
// 获取该设备在计划中的任务总数
|
||||||
|
$totalTasks = Db::name('task_customer')
|
||||||
|
->where([
|
||||||
|
'task_id' => $planId,
|
||||||
|
'device_id' => $deviceId
|
||||||
|
])
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// 获取已完成的任务数
|
||||||
|
$completedTasks = Db::name('task_customer')
|
||||||
|
->where([
|
||||||
|
'task_id' => $planId,
|
||||||
|
'device_id' => $deviceId,
|
||||||
|
'status' => 4
|
||||||
|
])
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// 获取进行中的任务数
|
||||||
|
$processingTasks = Db::name('task_customer')
|
||||||
|
->where([
|
||||||
|
'task_id' => $planId,
|
||||||
|
'device_id' => $deviceId,
|
||||||
|
'status' => ['in', [1, 2, 3]]
|
||||||
|
])
|
||||||
|
->count();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total_tasks' => $totalTasks,
|
||||||
|
'completed_tasks' => $completedTasks,
|
||||||
|
'processing_tasks' => $processingTasks,
|
||||||
|
'completion_rate' => $totalTasks > 0 ? round(($completedTasks / $totalTasks) * 100, 2) : 0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function getWxMinAppCode()
|
||||||
|
{
|
||||||
|
$params = $this->request->param();
|
||||||
|
$taskId = isset($params['taskId']) ? intval($params['taskId']) : 0;
|
||||||
|
|
||||||
|
if($taskId <= 0) {
|
||||||
|
return ResponseHelper::error('任务ID或场景ID不能为空', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$task = Db::name('customer_acquisition_task')->where(['id' => $taskId, 'deleteTime' => 0])->find();
|
||||||
|
if(!$task) {
|
||||||
|
return ResponseHelper::error('任务不存在', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$posterWeChatMiniProgram = new PosterWeChatMiniProgram();
|
||||||
|
$result = $posterWeChatMiniProgram->generateMiniProgramCodeWithScene($taskId);
|
||||||
|
return ResponseHelper::success($result, '获取小程序码成功');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,15 +16,14 @@ class PosterWeChatMiniProgram extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MINI_PROGRAM_CONFIG = [
|
const MINI_PROGRAM_CONFIG = [
|
||||||
'app_id' => 'wx12345678',
|
'app_id' => 'wx789850448e26c91d',
|
||||||
'secret' => 'your-app-secret',
|
'secret' => 'd18f75b3a3623cb40da05648b08365a1',
|
||||||
|
|
||||||
'response_type' => 'array'
|
'response_type' => 'array'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// 生成小程序码,存客宝-操盘手调用
|
// 生成小程序码,存客宝-操盘手调用
|
||||||
public function generateMiniProgramCodeWithScene() {
|
public function generateMiniProgramCodeWithScene($taskId = '') {
|
||||||
|
|
||||||
$taskId = request()->param('id');
|
$taskId = request()->param('id');
|
||||||
|
|
||||||
@@ -106,7 +105,7 @@ class PosterWeChatMiniProgram extends Controller
|
|||||||
}
|
}
|
||||||
// return $result['phone_info']['phoneNumber'];
|
// return $result['phone_info']['phoneNumber'];
|
||||||
return json([
|
return json([
|
||||||
'code' => 0,
|
'code' => 200,
|
||||||
'message' => '获取手机号成功',
|
'message' => '获取手机号成功',
|
||||||
'data' => $result['phone_info']['phoneNumber']
|
'data' => $result['phone_info']['phoneNumber']
|
||||||
]);
|
]);
|
||||||
@@ -125,12 +124,45 @@ class PosterWeChatMiniProgram extends Controller
|
|||||||
// todo 获取海报获客任务的任务/海报数据 -- 表还没设计好,不急 ck_customer_acquisition_task
|
// todo 获取海报获客任务的任务/海报数据 -- 表还没设计好,不急 ck_customer_acquisition_task
|
||||||
public function getPosterTaskData() {
|
public function getPosterTaskData() {
|
||||||
$id = request()->param('id');
|
$id = request()->param('id');
|
||||||
$task = Db::name('customer_acquisition_task')->where('id', $id)->find();
|
$task = Db::name('customer_acquisition_task')->where(['id' => $id,'deleteTime' => 0])->find();
|
||||||
|
if (!$task) {
|
||||||
|
return json([
|
||||||
|
'code' => 400,
|
||||||
|
'message' => '任务不存在'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($task['status'] == 0) {
|
||||||
|
return json([
|
||||||
|
'code' => 400,
|
||||||
|
'message' => '任务已结束'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sceneConf = json_decode($task['sceneConf'], true);
|
||||||
|
|
||||||
|
if(isset($sceneConf['posters'][0]['preview'])) {
|
||||||
|
$posterUrl = $sceneConf['posters'][0]['preview'];
|
||||||
|
} else {
|
||||||
|
$posterUrl = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'id' => $task['id'],
|
||||||
|
'name' => $task['name'],
|
||||||
|
'poster' => ['sUrl' => $posterUrl],
|
||||||
|
'sTip' => '啦啦啦啦',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
// todo 只需 返回 poster_url success_tip
|
// todo 只需 返回 poster_url success_tip
|
||||||
return json([
|
return json([
|
||||||
'code' => 0,
|
'code' => 200,
|
||||||
'message' => '获取海报获客任务数据成功',
|
'message' => '获取海报获客任务数据成功',
|
||||||
'data' => $task
|
'data' => $data
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class AccountListJob
|
|||||||
$request->withGet($params);
|
$request->withGet($params);
|
||||||
|
|
||||||
// 调用公司账号列表获取方法
|
// 调用公司账号列表获取方法
|
||||||
$result = $accountController->getlist($pageIndex,$pageSize,true);
|
$result = $accountController->getlist(['pageIndex' => $pageIndex,'pageSize' => $pageSize],true);
|
||||||
$response = json_decode($result,true);
|
$response = json_decode($result,true);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ class WechatMomentsJob
|
|||||||
public function fire(Job $job, $data)
|
public function fire(Job $job, $data)
|
||||||
{
|
{
|
||||||
$toAccountId = '';
|
$toAccountId = '';
|
||||||
$username = Env::get('api.username', '');
|
$username = Env::get('api.username2', '');
|
||||||
$password = Env::get('api.password', '');
|
$password = Env::get('api.password2', '');
|
||||||
if (!empty($username) || !empty($password)) {
|
if (!empty($username) || !empty($password)) {
|
||||||
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
|
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
|
||||||
}else{
|
}else{
|
||||||
Log::error("没有账号配置");
|
Log::error("没有账号配置");
|
||||||
Cache::rm($queueLockKey);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +46,8 @@ class WechatMomentsJob
|
|||||||
// 执行切换好友命令
|
// 执行切换好友命令
|
||||||
$automaticAssign = new AutomaticAssign();
|
$automaticAssign = new AutomaticAssign();
|
||||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $toAccountId], true);
|
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $toAccountId], true);
|
||||||
|
//存入缓存
|
||||||
|
artificialAllotWechatFriend($friend);
|
||||||
|
|
||||||
// 执行采集朋友圈命令
|
// 执行采集朋友圈命令
|
||||||
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
|
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
|
||||||
@@ -54,6 +55,8 @@ class WechatMomentsJob
|
|||||||
|
|
||||||
// 处理完毕切换回原账号
|
// 处理完毕切换回原账号
|
||||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||||
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// 发生异常时也要切换回原账号
|
// 发生异常时也要切换回原账号
|
||||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||||
|
|||||||
@@ -209,8 +209,8 @@ class WorkbenchAutoLikeJob
|
|||||||
protected function processFriendMoments($workbench, $config, $friend)
|
protected function processFriendMoments($workbench, $config, $friend)
|
||||||
{
|
{
|
||||||
$toAccountId = '';
|
$toAccountId = '';
|
||||||
$username = Env::get('api.username', '');
|
$username = Env::get('api.username2', '');
|
||||||
$password = Env::get('api.password', '');
|
$password = Env::get('api.password2', '');
|
||||||
if (!empty($username) || !empty($password)) {
|
if (!empty($username) || !empty($password)) {
|
||||||
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
|
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,8 @@ class WorkbenchAutoLikeJob
|
|||||||
// 执行切换好友命令
|
// 执行切换好友命令
|
||||||
$automaticAssign = new AutomaticAssign();
|
$automaticAssign = new AutomaticAssign();
|
||||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $toAccountId], true);
|
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $toAccountId], true);
|
||||||
|
//存入缓存
|
||||||
|
artificialAllotWechatFriend($friend);
|
||||||
// 创建WebSocket链接
|
// 创建WebSocket链接
|
||||||
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
|
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ class WorkbenchMomentsJob
|
|||||||
|
|
||||||
// 获取设备
|
// 获取设备
|
||||||
$devices = $this->getDevice($workbench, $config);
|
$devices = $this->getDevice($workbench, $config);
|
||||||
|
|
||||||
if (empty($devices)) {
|
if (empty($devices)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,6 @@ class WorkbenchMomentsJob
|
|||||||
if (empty($contentLibrary)) {
|
if (empty($contentLibrary)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理内容发送
|
// 处理内容发送
|
||||||
$this->handleContentSend($workbench, $config, $devices, $contentLibrary);
|
$this->handleContentSend($workbench, $config, $devices, $contentLibrary);
|
||||||
}
|
}
|
||||||
@@ -234,27 +234,30 @@ class WorkbenchMomentsJob
|
|||||||
|
|
||||||
$newList = [];
|
$newList = [];
|
||||||
foreach ($list as $val) {
|
foreach ($list as $val) {
|
||||||
// 检查今日发送次数
|
// 检查发送间隔(新逻辑:根据startTime、endTime、syncCount动态计算)
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$startTimestamp = strtotime($today . ' ' . $config['startTime'] . ':00');
|
||||||
|
$endTimestamp = strtotime($today . ' ' . $config['endTime'] . ':00');
|
||||||
|
$totalSeconds = $endTimestamp - $startTimestamp;
|
||||||
|
if ($totalSeconds <= 0 || empty($config['syncCount'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$interval = floor($totalSeconds / $config['syncCount']);
|
||||||
|
|
||||||
|
// 查询今日已同步次数
|
||||||
$count = Db::name('workbench_moments_sync_item')
|
$count = Db::name('workbench_moments_sync_item')
|
||||||
->where('workbenchId', $workbench->id)
|
->where('workbenchId', $workbench->id)
|
||||||
->where('deviceId', $val['deviceId'])
|
->where('deviceId', $val['deviceId'])
|
||||||
->whereTime('createTime', 'between', [
|
->whereTime('createTime', 'between', [$startTimestamp, $endTimestamp])
|
||||||
strtotime(date('Y-m-d') . '00:00:00'),
|
->count();
|
||||||
strtotime(date('Y-m-d') . '23:59:59')
|
|
||||||
])->count();
|
|
||||||
|
|
||||||
if ($count >= $config['syncCount']) {
|
if ($count >= $config['syncCount']) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查发送间隔
|
// 计算本次同步的最早允许时间
|
||||||
$prevSend = Db::name('workbench_moments_sync_item')
|
$nextSyncTime = $startTimestamp + $count * $interval;
|
||||||
->where('workbenchId', $workbench->id)
|
if (time() < $nextSyncTime) {
|
||||||
->where('deviceId', $val['deviceId'])
|
|
||||||
->order('createTime DESC')
|
|
||||||
->find();
|
|
||||||
|
|
||||||
if (!empty($prevSend) && ($prevSend['createTime'] + $config['syncInterval'] * 60) > time()) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class WorkbenchTrafficDistributeJob
|
|||||||
protected function processSingleWorkbench($workbench)
|
protected function processSingleWorkbench($workbench)
|
||||||
{
|
{
|
||||||
$page = 1;
|
$page = 1;
|
||||||
$pageSize = 100;
|
$pageSize = 20;
|
||||||
|
|
||||||
$config = WorkbenchTrafficConfig::where('workbenchId', $workbench->id)->find();
|
$config = WorkbenchTrafficConfig::where('workbenchId', $workbench->id)->find();
|
||||||
if (!$config) {
|
if (!$config) {
|
||||||
@@ -89,8 +89,6 @@ class WorkbenchTrafficDistributeJob
|
|||||||
->group('a.id')
|
->group('a.id')
|
||||||
->having('todayCount <= ' . $config['maxPerDay'])
|
->having('todayCount <= ' . $config['maxPerDay'])
|
||||||
->select();
|
->select();
|
||||||
|
|
||||||
|
|
||||||
$accountNum = count($accounts);
|
$accountNum = count($accounts);
|
||||||
if ($accountNum < 1) {
|
if ($accountNum < 1) {
|
||||||
Log::info("流量分发工作台 {$workbench->id} 可分配账号少于1个");
|
Log::info("流量分发工作台 {$workbench->id} 可分配账号少于1个");
|
||||||
@@ -134,27 +132,33 @@ class WorkbenchTrafficDistributeJob
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行切换好友命令
|
// 执行切换好友命令
|
||||||
$automaticAssign->allotWechatFriend([
|
$res = $automaticAssign->allotWechatFriend([
|
||||||
'wechatFriendId' => $friend['id'],
|
'wechatFriendId' => $friend['id'],
|
||||||
'toAccountId' => $account['id']
|
'toAccountId' => $account['id']
|
||||||
], true);
|
], true);
|
||||||
Db::table('s2_wechat_friend')
|
|
||||||
->where('id',$friend['id'])
|
$res = json_decode($res,true);
|
||||||
->update([
|
if ($res['code'] == 200){
|
||||||
'accountId' => $account['id'],
|
Db::table('s2_wechat_friend')
|
||||||
'accountUserName' => $account['userName'],
|
->where('id',$friend['id'])
|
||||||
'accountRealName' => $account['realName'],
|
->update([
|
||||||
'accountNickname' => $account['nickname'],
|
'accountId' => $account['id'],
|
||||||
|
'accountUserName' => $account['userName'],
|
||||||
|
'accountRealName' => $account['realName'],
|
||||||
|
'accountNickname' => $account['nickname'],
|
||||||
]);
|
]);
|
||||||
// 写入分配记录表
|
// 写入分配记录表
|
||||||
Db::name('workbench_traffic_config_item')->insert([
|
Db::name('workbench_traffic_config_item')->insert([
|
||||||
'workbenchId' => $workbench->id,
|
'workbenchId' => $workbench->id,
|
||||||
'deviceId' => $friend['deviceId'],
|
'deviceId' => $friend['deviceId'],
|
||||||
'wechatFriendId' => $friend['id'],
|
'wechatFriendId' => $friend['id'],
|
||||||
'wechatAccountId' => $account['id'],
|
'wechatAccountId' => $account['id'],
|
||||||
'createTime' => time()
|
'createTime' => time(),
|
||||||
]);
|
'exp' => $config['exp'],
|
||||||
Log::info("流量分发工作台 {$workbench->id} 好友[{$friend['id']}]分配给客服[{$account['id']}] 成功");
|
'expTime' => time() + 86400 * $config['exp'],
|
||||||
|
]);
|
||||||
|
Log::info("流量分发工作台 {$workbench->id} 好友[{$friend['id']}]分配给客服[{$account['id']}] 成功");
|
||||||
|
}
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -204,7 +208,7 @@ class WorkbenchTrafficDistributeJob
|
|||||||
$query = Db::table('s2_wechat_friend')->alias('wf')
|
$query = Db::table('s2_wechat_friend')->alias('wf')
|
||||||
->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left')
|
->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left')
|
||||||
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
|
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
|
||||||
->join('workbench_traffic_config_item wtci', 'wtci.wechatFriendId = wf.id AND wtci.workbenchId = ' . $config['workbenchId'], 'left')
|
->join('workbench_traffic_config_item wtci', 'wtci.isRecycle = 0 and wtci.wechatFriendId = wf.id AND wtci.workbenchId = ' . $config['workbenchId'], 'left')
|
||||||
->where([
|
->where([
|
||||||
['wf.isDeleted', '=', 0],
|
['wf.isDeleted', '=', 0],
|
||||||
['wf.isPassed', '=', 1],
|
['wf.isPassed', '=', 1],
|
||||||
@@ -214,12 +218,6 @@ class WorkbenchTrafficDistributeJob
|
|||||||
->whereIn('wa.currentDeviceId', $devices)
|
->whereIn('wa.currentDeviceId', $devices)
|
||||||
->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId');
|
->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId');
|
||||||
|
|
||||||
//lllll
|
|
||||||
if($workbench->id == 65){
|
|
||||||
$query->where('wf.accountId',1602);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(!empty($labels)){
|
if(!empty($labels)){
|
||||||
$query->where(function ($q) use ($labels) {
|
$query->where(function ($q) use ($labels) {
|
||||||
|
|||||||
@@ -153,25 +153,43 @@ class Adapter implements WeChatServiceInterface
|
|||||||
|
|
||||||
public function handleCustomerTaskWithStatusIsNew(int $current_worker_id, int $process_count_for_status_0)
|
public function handleCustomerTaskWithStatusIsNew(int $current_worker_id, int $process_count_for_status_0)
|
||||||
{
|
{
|
||||||
$tasks = Db::name('task_customer')
|
$task = Db::name('customer_acquisition_task')
|
||||||
->where('status', 0)
|
->where(['status' => 1,'deleteTime' => 0])
|
||||||
->whereRaw("id % $process_count_for_status_0 = {$current_worker_id}")
|
->whereRaw("id % $process_count_for_status_0 = {$current_worker_id}")
|
||||||
->limit(50)
|
->order('id desc')
|
||||||
->order('id DESC')
|
|
||||||
->select();
|
->select();
|
||||||
|
|
||||||
|
if (empty($task)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ($tasks) {
|
$taskData = [];
|
||||||
|
foreach ($task as $item) {
|
||||||
|
$reqConf = json_decode($item['reqConf'], true);
|
||||||
|
$device = $reqConf['device'] ?? [];
|
||||||
|
$deviceCount = count($device);
|
||||||
|
if ($deviceCount <= 0){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$tasks = Db::name('task_customer')
|
||||||
|
->where(['status'=> 0,'task_id'=>$item['id']])
|
||||||
|
->order('id DESC')
|
||||||
|
->limit($deviceCount)
|
||||||
|
->select();
|
||||||
|
$taskData = array_merge($taskData, $tasks);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($tasks as $task) {
|
|
||||||
|
|
||||||
|
if ($taskData) {
|
||||||
|
|
||||||
|
foreach ($taskData as $task) {
|
||||||
$task_id = $task['task_id'];
|
$task_id = $task['task_id'];
|
||||||
|
|
||||||
$task_info = $this->getCustomerAcquisitionTask($task_id);
|
$task_info = $this->getCustomerAcquisitionTask($task_id);
|
||||||
|
|
||||||
if (empty($task_info['status']) || empty($task_info['reqConf']) || empty($task_info['reqConf']['device'])) {
|
if (empty($task_info['status']) || empty($task_info['reqConf']) || empty($task_info['reqConf']['device'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
//筛选出设备在线微信在线
|
||||||
$wechatIdAccountIdMap = $this->getWeChatIdsAccountIdsMapByDeviceIds($task_info['reqConf']['device']);
|
$wechatIdAccountIdMap = $this->getWeChatIdsAccountIdsMapByDeviceIds($task_info['reqConf']['device']);
|
||||||
|
|
||||||
if (empty($wechatIdAccountIdMap)) {
|
if (empty($wechatIdAccountIdMap)) {
|
||||||
@@ -235,8 +253,10 @@ class Adapter implements WeChatServiceInterface
|
|||||||
{
|
{
|
||||||
|
|
||||||
$tasks = Db::name('task_customer')
|
$tasks = Db::name('task_customer')
|
||||||
->whereIn('status', [1,3])
|
->whereIn('status', [1,2])
|
||||||
|
->where('updated_at', '>=', (time() - 86400 * 3))
|
||||||
->limit(50)
|
->limit(50)
|
||||||
|
->order('updated_at DESC')
|
||||||
->select();
|
->select();
|
||||||
|
|
||||||
if (empty($tasks)) {
|
if (empty($tasks)) {
|
||||||
@@ -272,15 +292,14 @@ class Adapter implements WeChatServiceInterface
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($passedWeChatId && !empty($task_info['msgConf'])) {
|
if ($passedWeChatId && !empty($task_info['msgConf'])) {
|
||||||
|
|
||||||
Db::name('task_customer')
|
Db::name('task_customer')
|
||||||
->where('id', $task['id'])
|
->where('id', $task['id'])
|
||||||
->update(['status' => 4, 'updated_at' => time()]);
|
->update(['status' => 4, 'updated_at' => time()]);
|
||||||
|
|
||||||
$wechatFriendRecord = $this->getWeChatAccoutIdAndFriendIdByWeChatIdAndFriendPhone($passedWeChatId, $task['phone']);
|
$wechatFriendRecord = $this->getWeChatAccoutIdAndFriendIdByWeChatIdAndFriendPhone($passedWeChatId, $task['phone']);
|
||||||
$msgConf = is_string($task_info['msgConf']) ? json_decode($task_info['msgConf'], 1) : $task_info['msgConf'];
|
$msgConf = is_string($task_info['msgConf']) ? json_decode($task_info['msgConf'], 1) : $task_info['msgConf'];
|
||||||
|
|
||||||
$wechatFriendRecord && $this->sendMsgToFriend($wechatFriendRecord['id'], $wechatFriendRecord['wechatAccountId'], $msgConf);
|
$wechatFriendRecord && $this->sendMsgToFriend($wechatFriendRecord['id'], $wechatFriendRecord['wechatAccountId'], $msgConf);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -451,6 +470,7 @@ class Adapter implements WeChatServiceInterface
|
|||||||
try {
|
try {
|
||||||
$id = Db::table('s2_wechat_friend')
|
$id = Db::table('s2_wechat_friend')
|
||||||
->where('ownerWechatId', $wxId)
|
->where('ownerWechatId', $wxId)
|
||||||
|
->where(['isPassed' => 1,'isDeleted' => 0])
|
||||||
->where('phone|alias|wechatId', 'like', $phone . '%')
|
->where('phone|alias|wechatId', 'like', $phone . '%')
|
||||||
->order('createTime', 'desc')
|
->order('createTime', 'desc')
|
||||||
->value('id');
|
->value('id');
|
||||||
@@ -598,6 +618,11 @@ class Adapter implements WeChatServiceInterface
|
|||||||
if (empty($wechatId)) {
|
if (empty($wechatId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
//强制请求添加好友的列表
|
||||||
|
$friendController = new FriendTaskController();
|
||||||
|
$friendController->getlist(0,50);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$record = $this->getLatestFriendTask($wechatId);
|
$record = $this->getLatestFriendTask($wechatId);
|
||||||
if (empty($record)) {
|
if (empty($record)) {
|
||||||
@@ -676,6 +701,7 @@ class Adapter implements WeChatServiceInterface
|
|||||||
}
|
}
|
||||||
$records = Db::table('s2_wechat_account')
|
$records = Db::table('s2_wechat_account')
|
||||||
->where('deviceAlive', 1)
|
->where('deviceAlive', 1)
|
||||||
|
->where('wechatAlive', 1)
|
||||||
->where('currentDeviceId', 'in', $deviceIds)
|
->where('currentDeviceId', 'in', $deviceIds)
|
||||||
->field('id,wechatId')
|
->field('id,wechatId')
|
||||||
->column('id,wechatId');
|
->column('id,wechatId');
|
||||||
|
|||||||
@@ -161,4 +161,35 @@ class RequestCancelManager {
|
|||||||
|
|
||||||
// 导出单例实例
|
// 导出单例实例
|
||||||
export const requestDeduplicator = new RequestDeduplicator();
|
export const requestDeduplicator = new RequestDeduplicator();
|
||||||
export const requestCancelManager = new RequestCancelManager();
|
export const requestCancelManager = new RequestCancelManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用文件上传方法(支持图片、文件)
|
||||||
|
* @param {File} file - 要上传的文件对象
|
||||||
|
* @param {string} [uploadUrl='/v1/attachment/upload'] - 上传接口地址
|
||||||
|
* @returns {Promise<string>} - 上传成功后返回文件url
|
||||||
|
*/
|
||||||
|
export async function uploadFile(file: File, uploadUrl: string = '/v1/attachment/upload'): Promise<string> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
const token = typeof window !== 'undefined' ? localStorage.getItem('token') : '';
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(uploadUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
const res = await response.json();
|
||||||
|
if (res?.url) {
|
||||||
|
return res.url;
|
||||||
|
}
|
||||||
|
if (res?.data?.url) {
|
||||||
|
return res.data.url;
|
||||||
|
}
|
||||||
|
throw new Error(res?.msg || '文件上传失败');
|
||||||
|
} catch (e: any) {
|
||||||
|
throw new Error(e?.message || '文件上传失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -304,6 +304,7 @@ export function BasicSettings({
|
|||||||
|
|
||||||
// 新增:用于文件选择的ref
|
// 新增:用于文件选择的ref
|
||||||
const uploadInputRef = useRef<HTMLInputElement>(null);
|
const uploadInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const uploadOrderInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// 更新电话获客设置
|
// 更新电话获客设置
|
||||||
const handlePhoneSettingsUpdate = () => {
|
const handlePhoneSettingsUpdate = () => {
|
||||||
@@ -648,6 +649,9 @@ export function BasicSettings({
|
|||||||
transition: "border 0.2s",
|
transition: "border 0.2s",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
height: 180 + 12, // 图片高度180+上下padding
|
||||||
|
overflow: "hidden",
|
||||||
|
minHeight: 192,
|
||||||
}}
|
}}
|
||||||
onClick={() => handleMaterialSelect(material)}
|
onClick={() => handleMaterialSelect(material)}
|
||||||
>
|
>
|
||||||
@@ -675,40 +679,31 @@ export function BasicSettings({
|
|||||||
</button>
|
</button>
|
||||||
{/* 删除自定义海报按钮 */}
|
{/* 删除自定义海报按钮 */}
|
||||||
{isCustom && (
|
{isCustom && (
|
||||||
<button
|
<div
|
||||||
type="button"
|
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 8,
|
top: 8,
|
||||||
right: 8,
|
right: 8,
|
||||||
width: 24,
|
width: 28,
|
||||||
height: 24,
|
height: 28,
|
||||||
background: "rgba(0,0,0,0.5)",
|
background: "rgba(0,0,0,0.5)",
|
||||||
border: "none",
|
border: "none",
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
padding: 0,
|
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
lineHeight: 20,
|
||||||
|
color: "#ffffff",
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleRemoveCustomPoster(material.id);
|
handleRemoveCustomPoster(material.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
×
|
||||||
style={{
|
</div>
|
||||||
color: "#fff",
|
|
||||||
fontWeight: 700,
|
|
||||||
fontSize: 16,
|
|
||||||
lineHeight: "24px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
<img
|
<img
|
||||||
src={material.preview}
|
src={material.preview}
|
||||||
@@ -719,12 +714,14 @@ export function BasicSettings({
|
|||||||
objectFit: "cover",
|
objectFit: "cover",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
|
display: "block",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
position: "absolute",
|
||||||
top: "-32px",
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
background: "rgba(0,0,0,0.5)",
|
background: "rgba(0,0,0,0.5)",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
@@ -733,6 +730,7 @@ export function BasicSettings({
|
|||||||
borderBottomLeftRadius: 4,
|
borderBottomLeftRadius: 4,
|
||||||
borderBottomRightRadius: 4,
|
borderBottomRightRadius: 4,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
zIndex: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{material.name}
|
{material.name}
|
||||||
@@ -753,7 +751,7 @@ export function BasicSettings({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
height: 220,
|
height: 190,
|
||||||
}}
|
}}
|
||||||
onClick={() => uploadInputRef.current?.click()}
|
onClick={() => uploadInputRef.current?.click()}
|
||||||
>
|
>
|
||||||
@@ -801,62 +799,75 @@ export function BasicSettings({
|
|||||||
index={0}
|
index={0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* 订单导入区块 */}
|
{/* 订单导入区块优化 */}
|
||||||
<div style={openOrder} className="my-4">
|
<div style={openOrder} className="my-4">
|
||||||
<Button theme="default" onClick={() => setIsImportDialogOpen(true)}>
|
<div style={{ fontWeight: 500, marginBottom: 8 }}>订单表格上传</div>
|
||||||
{importedTags.length
|
<div style={{ display: "flex", gap: 12, marginBottom: 4 }}>
|
||||||
? `已导入订单(${importedTags.length})`
|
<Button
|
||||||
: "导入订单"}
|
type="button"
|
||||||
</Button>
|
style={{ display: "flex", alignItems: "center", gap: 4 }}
|
||||||
<Dialog visible={isImportDialogOpen} onClose={handleImportDialogClose}>
|
theme="default"
|
||||||
<div style={{ fontWeight: 600, fontSize: 16, marginBottom: 12 }}>
|
onClick={handleDownloadTemplate}
|
||||||
导入订单
|
>
|
||||||
</div>
|
<span className="iconfont" style={{ fontSize: 18 }}>
|
||||||
<div style={{ marginBottom: 12 }}>
|
↓
|
||||||
<Button
|
</span>{" "}
|
||||||
theme="primary"
|
下载模板
|
||||||
onClick={handleDownloadTemplate}
|
</Button>
|
||||||
style={{ marginRight: 8 }}
|
<Button
|
||||||
>
|
type="button"
|
||||||
下载模板
|
style={{ display: "flex", alignItems: "center", gap: 4 }}
|
||||||
</Button>
|
theme="default"
|
||||||
|
onClick={() => uploadOrderInputRef.current?.click()}
|
||||||
|
>
|
||||||
|
<span className="iconfont" style={{ fontSize: 18 }}>
|
||||||
|
↑
|
||||||
|
</span>{" "}
|
||||||
|
上传订单表格
|
||||||
<input
|
<input
|
||||||
|
ref={uploadOrderInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
accept=".csv"
|
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||||
|
style={{ display: "none" }}
|
||||||
onChange={handleFileImport}
|
onChange={handleFileImport}
|
||||||
style={{ display: "inline-block" }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</Button>
|
||||||
{importedTags.length > 0 && (
|
</div>
|
||||||
<Table
|
<div style={{ color: "#888", fontSize: 13, marginBottom: 8 }}>
|
||||||
columns={[
|
支持 CSV、Excel 格式,上传后将文件保存到服务器
|
||||||
{ colKey: "phone", title: "手机号" },
|
</div>
|
||||||
{ colKey: "wechat", title: "微信号" },
|
{/* 已导入数据表格可复用原有Table渲染 */}
|
||||||
{ colKey: "source", title: "来源" },
|
{importedTags.length > 0 && (
|
||||||
{ colKey: "orderAmount", title: "订单金额" },
|
<Table
|
||||||
{ colKey: "orderDate", title: "下单日期" },
|
columns={[
|
||||||
]}
|
{ colKey: "phone", title: "手机号" },
|
||||||
data={importedTags}
|
{ colKey: "wechat", title: "微信号" },
|
||||||
rowKey="phone"
|
{ colKey: "source", title: "来源" },
|
||||||
style={{ marginTop: 12 }}
|
{ colKey: "orderAmount", title: "订单金额" },
|
||||||
/>
|
{ colKey: "orderDate", title: "下单日期" },
|
||||||
)}
|
]}
|
||||||
</Dialog>
|
data={importedTags}
|
||||||
|
rowKey="phone"
|
||||||
|
style={{ marginTop: 12 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 电话获客设置区块,仅在选择电话获客场景时显示 */}
|
{/* 电话获客设置区块,仅在选择电话获客场景时显示 */}
|
||||||
{formData.scenario === "phone" && (
|
{formData.scenario === 5 && (
|
||||||
<div style={{ margin: "16px 0" }}>
|
<div style={{ margin: "16px 0" }}>
|
||||||
<Button theme="default" onClick={() => setIsPhoneSettingsOpen(true)}>
|
<div
|
||||||
电话获客设置
|
style={{
|
||||||
</Button>
|
background: "#f7f8fa",
|
||||||
<Dialog
|
borderRadius: 10,
|
||||||
visible={isPhoneSettingsOpen}
|
padding: 20,
|
||||||
onClose={handlePhoneSettingsDialogClose}
|
boxShadow: "0 2px 8px rgba(0,0,0,0.03)",
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontWeight: 600, fontSize: 16, marginBottom: 12 }}>
|
<div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16 }}>
|
||||||
电话获客设置
|
电话获客设置
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -902,15 +913,12 @@ export function BasicSettings({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button block theme="primary" onClick={handlePhoneSettingsUpdate}>
|
|
||||||
保存设置
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 微信群设置区块,仅在选择微信群场景时显示 */}
|
{/* 微信群设置区块,仅在选择微信群场景时显示 */}
|
||||||
{formData.scenario === "weixinqun" && (
|
{formData.scenario === 7 && (
|
||||||
<div style={{ margin: "16px 0" }}>
|
<div style={{ margin: "16px 0" }}>
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<Input
|
<Input
|
||||||
@@ -932,7 +940,7 @@ export function BasicSettings({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Button block theme="primary" onClick={onNext}>
|
<Button className="mt-4" block theme="primary" onClick={onNext}>
|
||||||
下一步
|
下一步
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user