Merge branch 'develop' into yongpxu-dev
# Conflicts: # Cunkebao/app/content/[id]/materials/new/page.tsx resolved by develop version # Cunkebao/app/content/[id]/materials/page.tsx resolved by develop version # Cunkebao/app/workspace/moments-sync/[id]/page.tsx resolved by develop version
This commit is contained in:
@@ -7,6 +7,7 @@ use app\api\model\CompanyModel;
|
||||
use Library\S2\Logics\AccountLogic;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
use think\facade\Cache;
|
||||
|
||||
/**
|
||||
* 账号管理控制器
|
||||
@@ -174,7 +175,7 @@ class AutomaticAssign extends BaseController
|
||||
public function allotWechatFriend($data = [],$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
|
||||
@@ -32,4 +32,5 @@ return [
|
||||
'sync:wechatData' => 'app\command\SyncWechatDataToCkbTask', // 同步微信数据到存客宝
|
||||
'sync:allFriends' => 'app\command\SyncAllFriendsCommand', // 同步所有在线好友
|
||||
'workbench:trafficDistribute' => 'app\command\WorkbenchTrafficDistributeCommand', // 工作台流量分发任务
|
||||
'switch:friends' => 'app\command\SwitchFriendsCommand', // 切换好友
|
||||
];
|
||||
|
||||
88
Server/application/command/SwitchFriendsCommand.php
Normal file
88
Server/application/command/SwitchFriendsCommand.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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\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)
|
||||
{
|
||||
$cacheKey = 'allotWechatFriend';
|
||||
$now = time();
|
||||
$maxRetry = 5;
|
||||
$retry = 0;
|
||||
$switchedIds = [];
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$automaticAssign = new AutomaticAssign();
|
||||
foreach ($toSwitch as $friend) {
|
||||
$friendId = !empty($friend['friendId']) ? $friend['friendId'] : $friend['id'];
|
||||
$res = $automaticAssign->allotWechatFriend([
|
||||
'wechatFriendId' => $friendId,
|
||||
'toAccountId' => $friend['accountId'],
|
||||
], true);
|
||||
$res = json_decode($res, true);
|
||||
if ($res['code'] == 200){
|
||||
$output->writeln('切换好友:' . $friendId . ' 到账号:' . $friend['accountId']);
|
||||
$switchedIds[] = $friendId;
|
||||
}else{
|
||||
$output->writeln('切换好友:' . $friendId . ' 到账号:' . $friend['accountId'] .' 结果:' .$res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉已切换的,保留未切换和新进来的
|
||||
$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('切换完成,缓存已更新并排序');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -518,6 +518,34 @@ if (!function_exists('exit_data')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('artificialAllotWechatFriend')) {
|
||||
function artificialAllotWechatFriend($friend = [])
|
||||
{
|
||||
if (empty($friend)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//记录切换好友
|
||||
$cacheKey = 'allotWechatFriend';
|
||||
$cacheFriend = $friend;
|
||||
$cacheFriend['time'] = time() + 120;
|
||||
$maxRetry = 5;
|
||||
$retry = 0;
|
||||
do {
|
||||
$cacheFriendData = Cache::get($cacheKey, []);
|
||||
// 去重:移除同friendId的旧数据
|
||||
$cacheFriendData = array_filter($cacheFriendData, function($item) use ($cacheFriend) {
|
||||
return $item['friendId'] !== $cacheFriend['friendId'];
|
||||
});
|
||||
$cacheFriendData[] = $cacheFriend;
|
||||
$success = Cache::set($cacheKey, $cacheFriendData);
|
||||
$retry++;
|
||||
} while (!$success && $retry < $maxRetry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 调试打印变量但不终止程序
|
||||
* @return void
|
||||
|
||||
@@ -28,17 +28,17 @@ class Attachment extends Controller
|
||||
$validate = \think\facade\Validate::rule([
|
||||
'file' => [
|
||||
'fileSize' => 10485760, // 10MB
|
||||
'fileExt' => 'jpg,jpeg,png,gif,doc,docx,pdf,zip,rar,mp4,mp3',
|
||||
'fileExt' => 'jpg,jpeg,png,gif,doc,docx,pdf,zip,rar,mp4,mp3,csv,xls,xlsx',
|
||||
'fileMime' => 'image/jpeg,image/png,image/gif,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf,application/zip,application/x-rar-compressed,video/mp4,audio/mp3'
|
||||
]
|
||||
]);
|
||||
|
||||
if (!$validate->check(['file' => $file])) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => $validate->getError()
|
||||
]);
|
||||
}
|
||||
// if (!$validate->check(['file' => $file])) {
|
||||
// return json([
|
||||
// 'code' => 400,
|
||||
// 'msg' => $validate->getError()
|
||||
// ]);
|
||||
// }
|
||||
|
||||
// 生成文件hash
|
||||
$hashKey = md5_file($file->getRealPath());
|
||||
|
||||
@@ -40,7 +40,7 @@ Route::group('v1/', function () {
|
||||
Route::get('scenes-detail', 'app\cunkebao\controller\plan\GetPlanSceneListV1Controller@detail');
|
||||
Route::post('create', 'app\cunkebao\controller\plan\PostCreateAddFriendPlanV1Controller@index');
|
||||
Route::get('list', 'app\cunkebao\controller\plan\PlanSceneV1Controller@index');
|
||||
Route::get('copy', 'app\cunkebao\controller\plan\PlanSceneV1Controller@copy');
|
||||
Route::get('copy', 'app\cunkebao\controller\plan\GetCreateAddFriendPlanV1Controller@copy');
|
||||
Route::delete('delete', 'app\cunkebao\controller\plan\PlanSceneV1Controller@delete');
|
||||
Route::post('updateStatus', 'app\cunkebao\controller\plan\PlanSceneV1Controller@updateStatus');
|
||||
Route::get('detail', 'app\cunkebao\controller\plan\GetAddFriendPlanDetailV1Controller@index');
|
||||
|
||||
@@ -7,6 +7,7 @@ use app\cunkebao\model\ContentItem;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use app\api\controller\WebSocketController;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Env;
|
||||
use app\api\controller\AutomaticAssign;
|
||||
|
||||
@@ -761,7 +762,8 @@ class ContentLibraryController extends Controller
|
||||
// 查询条件:未删除且已开启的内容库
|
||||
$where = [
|
||||
['isDel', '=', 0], // 未删除
|
||||
['status', '=', 1] // 已开启
|
||||
['status', '=', 1], // 已开启
|
||||
['id', '=', 27]
|
||||
];
|
||||
|
||||
// 查询符合条件的内容库
|
||||
@@ -862,6 +864,7 @@ class ContentLibraryController extends Controller
|
||||
'message' => '没有指定要采集的好友'
|
||||
];
|
||||
}
|
||||
$friendData = [];
|
||||
try {
|
||||
$toAccountId = '';
|
||||
$username = Env::get('api.username', '');
|
||||
@@ -889,6 +892,7 @@ class ContentLibraryController extends Controller
|
||||
$totalMomentsCount = 0;
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
$friendData = $friend;
|
||||
if (!empty($username) && !empty($password)) {
|
||||
//执行切换好友命令
|
||||
$automaticAssign = new AutomaticAssign();
|
||||
@@ -897,7 +901,13 @@ class ContentLibraryController extends Controller
|
||||
$webSocket = new WebSocketController(['userName' => $username,'password' => $password,'accountId' => $toAccountId]);
|
||||
$webSocket->getMoments(['wechatFriendId' => $friend['id'],'wechatAccountId' => $friend['wechatAccountId']]);
|
||||
//采集完毕切换
|
||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['id'],'toAccountId' => $friend['accountId']],true);
|
||||
$res = $automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['id'],'toAccountId' => $friend['accountId']],true);
|
||||
$res = json_decode($res, true);
|
||||
if($res == '无效路径或登录状态失效'){
|
||||
$cacheFriend = $friend;
|
||||
$cacheFriend['friendId'] = $friend['id'];
|
||||
artificialAllotWechatFriend($friend);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1006,6 +1016,12 @@ class ContentLibraryController extends Controller
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$cacheFriend = $friendData;
|
||||
$cacheFriend['friendId'] = $friend['id'];
|
||||
artificialAllotWechatFriend($friend);
|
||||
|
||||
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => '采集过程发生错误: ' . $e->getMessage()
|
||||
@@ -1390,8 +1406,44 @@ class ContentLibraryController extends Controller
|
||||
}
|
||||
|
||||
// 判断内容类型 (0=未知, 1=图片, 2=链接, 3=视频, 4=文本, 5=小程序, 6=图文)
|
||||
$contentType = $this->determineContentType($moment['content'] ?? '', $resUrls, $urls);
|
||||
|
||||
if($moment['type'] == 1) {
|
||||
//图文
|
||||
$contentType = 6;
|
||||
}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{
|
||||
$urls[] = [
|
||||
'url' => $url,
|
||||
'image' => 'http://karuosiyujzk.oss-cn-shenzhen.aliyuncs.com/2025/07/09/ec039d96fad6eab1d960f207d3d9ca9f.jpeg',
|
||||
'desc' => '点击查看详情'
|
||||
];
|
||||
}
|
||||
$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->libraryId = $libraryId;
|
||||
@@ -1695,4 +1747,167 @@ class ContentLibraryController extends Controller
|
||||
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;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\cunkebao\model\Workbench;
|
||||
use app\cunkebao\model\WorkbenchAutoLike;
|
||||
use app\cunkebao\model\WorkbenchMomentsSync;
|
||||
@@ -236,7 +238,12 @@ class WorkbenchController extends Controller
|
||||
$item->config = $item->momentsSync;
|
||||
$item->config->devices = json_decode($item->config->devices, 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)) {
|
||||
$libraryNames = ContentLibrary::where('id', 'in', $item->config->contentLibraries)
|
||||
@@ -426,6 +433,42 @@ class WorkbenchController extends Controller
|
||||
$workbench->config = $workbench->momentsSync;
|
||||
$workbench->config->devices = json_decode($workbench->config->devices, 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);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -54,18 +54,18 @@ class PlanSceneV1Controller extends BaseController
|
||||
$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['passCount'] = Db::name('task_customer')->where('task_id',$val['id'])->where('status',4)->count();
|
||||
|
||||
$val['passRate'] = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
|
||||
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list
|
||||
@@ -75,41 +75,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 +128,251 @@ class PlanSceneV1Controller extends BaseController
|
||||
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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,6 @@ use think\facade\Request;
|
||||
class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
{
|
||||
|
||||
protected function yyyyyyy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一API密钥
|
||||
@@ -60,6 +56,7 @@ class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($params['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
@@ -91,16 +88,25 @@ class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['id'],
|
||||
$sceneConf['apiKey'],
|
||||
$sceneConf['userId'],
|
||||
$sceneConf['status'],
|
||||
$sceneConf['planId'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
$sceneConf['messagePlans'],
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
$sceneConf['device'],
|
||||
$sceneConf['orderTableFileName'],
|
||||
$sceneConf['userInfo'],
|
||||
$sceneConf['textUrl'],
|
||||
$sceneConf['remarkType'],
|
||||
$sceneConf['greeting'],
|
||||
$sceneConf['addFriendInterval'],
|
||||
$sceneConf['startTime'],
|
||||
$sceneConf['orderTableFile'],
|
||||
$sceneConf['endTime']
|
||||
);
|
||||
|
||||
@@ -122,9 +128,9 @@ class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
|
||||
|
||||
|
||||
// 开启事务
|
||||
Db::startTrans();
|
||||
|
||||
try {
|
||||
Db::startTrans();
|
||||
// 插入数据
|
||||
$planId = Db::name('customer_acquisition_task')->insertGetId($data);
|
||||
|
||||
@@ -132,7 +138,132 @@ class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
throw new \Exception('添加计划失败');
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
|
||||
//订单
|
||||
if($params['sceneId'] == 2){
|
||||
if(!empty($params['orderTableFile'])){
|
||||
// 先下载到本地临时文件,再分析,最后删除
|
||||
$originPath = $params['orderTableFile'];
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'order_');
|
||||
// 判断是否为远程文件
|
||||
if (preg_match('/^https?:\/\//i', $originPath)) {
|
||||
// 远程URL,下载到本地
|
||||
$fileContent = file_get_contents($originPath);
|
||||
if ($fileContent === false) {
|
||||
exit('远程文件下载失败: ' . $originPath);
|
||||
}
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
} else {
|
||||
// 本地文件,直接copy
|
||||
if (!file_exists($originPath)) {
|
||||
exit('文件不存在: ' . $originPath);
|
||||
}
|
||||
copy($originPath, $tmpFile);
|
||||
}
|
||||
// 解析临时文件
|
||||
$ext = strtolower(pathinfo($originPath, PATHINFO_EXTENSION));
|
||||
$rows = [];
|
||||
if (in_array($ext, ['xls', 'xlsx'])) {
|
||||
// 直接用composer自动加载的PHPExcel
|
||||
$excel = \PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) > 1) {
|
||||
array_shift($data); // 去掉表头
|
||||
}
|
||||
|
||||
foreach ($data as $cols) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechat' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
} elseif ($ext === 'csv') {
|
||||
$content = file_get_contents($tmpFile);
|
||||
$lines = preg_split('/\r\n|\r|\n/', $content);
|
||||
if (count($lines) > 1) {
|
||||
array_shift($lines); // 去掉表头
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$cols = str_getcsv($line);
|
||||
if (count($cols) >= 6) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechat' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unlink($tmpFile);
|
||||
exit('暂不支持的文件类型: ' . $ext);
|
||||
}
|
||||
// 删除临时文件
|
||||
unlink($tmpFile);
|
||||
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($rows);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($rows, $i, $batchSize);
|
||||
|
||||
if (!empty($batchRows)) {
|
||||
// 1. 提取当前批次的phone
|
||||
$phones = [];
|
||||
foreach ($batchRows as $row) {
|
||||
$phone = !empty($row['phone']) ? $row['phone'] : $row['wechat'];
|
||||
if (!empty($phone)) {
|
||||
$phones[] = $phone;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 批量查询已存在的phone
|
||||
$existingPhones = [];
|
||||
if (!empty($phones)) {
|
||||
$existing = Db::name('task_customer')
|
||||
->where('task_id', $planId)
|
||||
->where('phone', 'in', $phones)
|
||||
->field('phone')
|
||||
->select();
|
||||
$existingPhones = array_column($existing, 'phone');
|
||||
}
|
||||
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
$phone = !empty($row['phone']) ? $row['phone'] : $row['wechat'];
|
||||
if (!empty($phone) && !in_array($phone, $existingPhones)) {
|
||||
$newData[] = [
|
||||
'task_id' => $planId,
|
||||
'name' => $row['name'] ?? '',
|
||||
'source' => $row['source'] ?? '',
|
||||
'phone' => $phone,
|
||||
'tags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'siteTags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'created_at' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
Db::name('task_customer')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success(['planId' => $planId], '添加计划任务成功');
|
||||
|
||||
@@ -65,6 +65,10 @@ class PostUpdateAddFriendPlanV1Controller extends Controller
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['id'],
|
||||
$sceneConf['apiKey'],
|
||||
$sceneConf['userId'],
|
||||
$sceneConf['status'],
|
||||
$sceneConf['planId'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
@@ -72,10 +76,14 @@ class PostUpdateAddFriendPlanV1Controller extends Controller
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
$sceneConf['device'],
|
||||
$sceneConf['orderTableFileName'],
|
||||
$sceneConf['userInfo'],
|
||||
$sceneConf['textUrl'],
|
||||
$sceneConf['remarkType'],
|
||||
$sceneConf['greeting'],
|
||||
$sceneConf['addFriendInterval'],
|
||||
$sceneConf['startTime'],
|
||||
$sceneConf['orderTableFile'],
|
||||
$sceneConf['endTime']
|
||||
);
|
||||
|
||||
@@ -90,8 +98,8 @@ class PostUpdateAddFriendPlanV1Controller extends Controller
|
||||
'updateTime'=> time(),
|
||||
];
|
||||
|
||||
// 开启事务
|
||||
Db::startTrans();
|
||||
|
||||
|
||||
try {
|
||||
// 更新数据
|
||||
$result = Db::name('customer_acquisition_task')
|
||||
@@ -102,8 +110,132 @@ class PostUpdateAddFriendPlanV1Controller extends Controller
|
||||
throw new \Exception('更新计划失败');
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
//订单
|
||||
if($params['sceneId'] == 2){
|
||||
if(!empty($params['orderTableFile'])){
|
||||
// 先下载到本地临时文件,再分析,最后删除
|
||||
$originPath = $params['orderTableFile'];
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'order_');
|
||||
// 判断是否为远程文件
|
||||
if (preg_match('/^https?:\/\//i', $originPath)) {
|
||||
// 远程URL,下载到本地
|
||||
$fileContent = file_get_contents($originPath);
|
||||
if ($fileContent === false) {
|
||||
exit('远程文件下载失败: ' . $originPath);
|
||||
}
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
} else {
|
||||
// 本地文件,直接copy
|
||||
if (!file_exists($originPath)) {
|
||||
exit('文件不存在: ' . $originPath);
|
||||
}
|
||||
copy($originPath, $tmpFile);
|
||||
}
|
||||
// 解析临时文件
|
||||
$ext = strtolower(pathinfo($originPath, PATHINFO_EXTENSION));
|
||||
$rows = [];
|
||||
if (in_array($ext, ['xls', 'xlsx'])) {
|
||||
// 直接用composer自动加载的PHPExcel
|
||||
$excel = \PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) > 1) {
|
||||
array_shift($data); // 去掉表头
|
||||
}
|
||||
|
||||
foreach ($data as $cols) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechat' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
} elseif ($ext === 'csv') {
|
||||
$content = file_get_contents($tmpFile);
|
||||
$lines = preg_split('/\r\n|\r|\n/', $content);
|
||||
if (count($lines) > 1) {
|
||||
array_shift($lines); // 去掉表头
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$cols = str_getcsv($line);
|
||||
if (count($cols) >= 6) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechat' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unlink($tmpFile);
|
||||
exit('暂不支持的文件类型: ' . $ext);
|
||||
}
|
||||
// 删除临时文件
|
||||
unlink($tmpFile);
|
||||
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($rows);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($rows, $i, $batchSize);
|
||||
|
||||
if (!empty($batchRows)) {
|
||||
// 1. 提取当前批次的phone
|
||||
$phones = [];
|
||||
foreach ($batchRows as $row) {
|
||||
$phone = !empty($row['phone']) ? $row['phone'] : $row['wechat'];
|
||||
if (!empty($phone)) {
|
||||
$phones[] = $phone;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 批量查询已存在的phone
|
||||
$existingPhones = [];
|
||||
if (!empty($phones)) {
|
||||
$existing = Db::name('task_customer')
|
||||
->where('task_id', $params['planId'])
|
||||
->where('phone', 'in', $phones)
|
||||
->field('phone')
|
||||
->select();
|
||||
$existingPhones = array_column($existing, 'phone');
|
||||
}
|
||||
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
$phone = !empty($row['phone']) ? $row['phone'] : $row['wechat'];
|
||||
if (!empty($phone) && !in_array($phone, $existingPhones)) {
|
||||
$newData[] = [
|
||||
'task_id' => $params['planId'],
|
||||
'name' => $row['name'] ?? '',
|
||||
'source' => $row['source'] ?? '',
|
||||
'phone' => $phone,
|
||||
'tags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'siteTags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'created_at' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
Db::name('task_customer')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ResponseHelper::success(['planId' => $params['planId']], '更新计划任务成功');
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\model\Collection as ResultCollection;
|
||||
|
||||
/**
|
||||
@@ -100,9 +101,12 @@ class GetWechatsRelatedDeviceV1Controller extends BaseController
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getWechatAliveText(string $wechatId): string
|
||||
protected function getWechatAlive(string $wechatId): string
|
||||
{
|
||||
return 1 ? '正常' : '异常';
|
||||
$wechat_account = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->value('wechatAlive');
|
||||
return !empty($wechat_account) ? $wechat_account : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +144,9 @@ class GetWechatsRelatedDeviceV1Controller extends BaseController
|
||||
$account->lastActive = $this->getWechatLastActiveTime($account->wechatId);
|
||||
$account->statusText = $this->getWechatStatusText($account->wechatId);
|
||||
$account->totalFriend = $this->getCountFriend($account->wechatId);
|
||||
$account->wechatAliveText = $this->getWechatAliveText($account->wechatId);
|
||||
$getWechatAlive = $this->getWechatAlive($account->wechatId);
|
||||
$account->wechatAlive = $getWechatAlive;
|
||||
$account->wechatAliveText = !empty($getWechatAlive) ? '正常' : '异常';
|
||||
}
|
||||
|
||||
return $collection->toArray();
|
||||
|
||||
@@ -24,7 +24,6 @@ class WechatMomentsJob
|
||||
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
|
||||
}else{
|
||||
Log::error("没有账号配置");
|
||||
Cache::rm($queueLockKey);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,10 +52,20 @@ class WechatMomentsJob
|
||||
$webSocket->getMoments(['wechatFriendId' => $friend['friendId'], 'wechatAccountId' => $friend['wechatAccountId']]);
|
||||
|
||||
// 处理完毕切换回原账号
|
||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = $automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = json_decode($res, true);
|
||||
if ($res == '无效路径或登录状态失效'){
|
||||
artificialAllotWechatFriend($friend);
|
||||
}
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 发生异常时也要切换回原账号
|
||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = $automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = json_decode($res, true);
|
||||
if ($res == '无效路径或登录状态失效'){
|
||||
artificialAllotWechatFriend($friend);
|
||||
}
|
||||
Log::error("采集好友 {$friend['id']} 的朋友圈失败:" . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ class WorkbenchAutoLikeJob
|
||||
// 执行切换好友命令
|
||||
$automaticAssign = new AutomaticAssign();
|
||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $toAccountId], true);
|
||||
|
||||
|
||||
// 创建WebSocket链接
|
||||
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
|
||||
|
||||
@@ -234,7 +234,12 @@ class WorkbenchAutoLikeJob
|
||||
|
||||
if (empty($moments) || count($moments) == 0) {
|
||||
// 处理完毕切换回原账号
|
||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = $automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = json_decode($res, true);
|
||||
if($res == '无效路径或登录状态失效'){
|
||||
artificialAllotWechatFriend($friend);
|
||||
}
|
||||
|
||||
Log::info("好友 {$friend['friendId']} 没有需要点赞的朋友圈");
|
||||
return;
|
||||
}
|
||||
@@ -258,11 +263,19 @@ class WorkbenchAutoLikeJob
|
||||
}
|
||||
|
||||
// 处理完毕切换回原账号
|
||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = $automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = json_decode($res, true);
|
||||
if($res == '无效路径或登录状态失效'){
|
||||
artificialAllotWechatFriend($friend);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 异常情况下也要确保切换回原账号
|
||||
$automaticAssign = new AutomaticAssign();
|
||||
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = $automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
|
||||
$res = json_decode($res, true);
|
||||
if($res == '无效路径或登录状态失效'){
|
||||
artificialAllotWechatFriend($friend);
|
||||
}
|
||||
Log::error("处理好友 {$friend['friendId']} 朋友圈失败异常: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user