@@ -18,454 +18,40 @@ class WorkbenchGroupPushController extends Controller
const TYPE_GROUP_PUSH = 3 ; // 群消息推送
/**
* 创建工作台
* 获取群发统计数据
* @return \think\response\Json
*/
public function cre ate ()
public function getGroupPushSt ats ()
{
if ( ! $this -> request -> isPost ()) {
return json ([ 'code' => 400 , 'msg' => '请求方式错误 ' ] );
}
$workbenchId = $this -> request -> param ( 'workbenchId' , 0 );
$timeRange = $this -> request -> param ( 'timeRange' , '7 '); // 默认最近7天
$contentLibraryIds = $this -> request -> param ( 'contentLibraryIds' , '' ); // 话术组筛选
$userId = $this -> request -> userInfo [ 'id' ];
// 获取登录用户信息
$userInfo = request () -> userInfo ;
// 如果指定了工作台ID, 则验证权限
if ( ! empty ( $workbenchId )) {
$workbench = Workbench :: where ([
[ 'id' , '=' , $workbenchId ],
[ 'userId' , '=' , $userId ],
[ 'type' , '=' , self :: TYPE_GROUP_PUSH ],
[ 'isDel' , '=' , 0 ]
]) -> find ();
// 获取请求参数
$param = $this -> request -> post ( );
// 根据业务默认值补全参数
if (
isset ( $param [ 'type' ]) &&
intval ( $param [ 'type' ]) === self :: TYPE_GROUP_PUSH
) {
if ( empty ( $param [ 'startTime' ])) {
$param [ 'startTime' ] = '09:00' ;
}
if ( empty ( $param [ 'endTime' ])) {
$param [ 'endTime' ] = '21:00' ;
if ( empty ( $workbench )) {
return json ([ 'code' => 404 , 'msg' => '工作台不存在' ] );
}
}
// 验证数据
$validate = new WorkbenchValidat e ;
if ( ! $validate -> scene ( 'cre ate' ) -> check ( $param )) {
return json ([ 'code' => 400 , 'msg' => $validate -> getError ()] );
}
Db :: startTrans ();
try {
// 创建工作台基本信息
$workbench = new Workbench ;
$workbench -> name = $param [ 'name' ];
$workbench -> type = $param [ 'type' ];
$workbench -> status = ! empty ( $param [ 'status' ]) ? 1 : 0 ;
$workbench -> autoStart = ! empty ( $param [ 'autoStart' ]) ? 1 : 0 ;
$workbench -> userId = $userInfo [ 'id' ];
$workbench -> companyId = $userInfo [ 'companyId' ];
$workbench -> createTime = time ();
$workbench -> updateTime = time ();
$workbench -> save ();
// 根据类型创建对应的配置
switch ( $param [ 'type' ]) {
case self :: TYPE_AUTO_LIKE : // 自动点赞
$config = new WorkbenchAutoLike ;
$config -> workbenchId = $workbench -> id ;
$config -> interval = $param [ 'interval' ];
$config -> maxLikes = $param [ 'maxLikes' ];
$config -> startTime = $param [ 'startTime' ];
$config -> endTime = $param [ 'endTime' ];
$config -> contentTypes = json_encode ( $param [ 'contentTypes' ]);
$config -> devices = json_encode ( $param [ 'deviceGroups' ]);
$config -> friends = json_encode ( $param [ 'wechatFriends' ]);
// $config->targetGroups = json_encode($param['targetGroups']);
// $config->tagOperator = $param['tagOperator'];
$config -> friendMaxLikes = $param [ 'friendMaxLikes' ];
$config -> friendTags = $param [ 'friendTags' ];
$config -> enableFriendTags = $param [ 'enableFriendTags' ];
$config -> createTime = time ();
$config -> updateTime = time ();
$config -> save ();
break ;
case self :: TYPE_MOMENTS_SYNC : // 朋友圈同步
$config = new WorkbenchMomentsSync ;
$config -> workbenchId = $workbench -> id ;
$config -> syncInterval = $param [ 'syncInterval' ];
$config -> syncCount = $param [ 'syncCount' ];
$config -> syncType = $param [ 'syncType' ];
$config -> startTime = $param [ 'startTime' ];
$config -> endTime = $param [ 'endTime' ];
$config -> accountType = $param [ 'accountType' ];
$config -> devices = json_encode ( $param [ 'deviceGroups' ]);
$config -> contentLibraries = json_encode ( $param [ 'contentGroups' ] ? ? []);
$config -> createTime = time ();
$config -> updateTime = time ();
$config -> save ();
break ;
case self :: TYPE_GROUP_PUSH : // 群消息推送
$ownerWechatIds = $this -> normalizeOwnerWechatIds ( $param [ 'ownerWechatIds' ] ? ? []);
$groupPushData = $this -> prepareGroupPushData ( $param , $ownerWechatIds );
$groupPushData [ 'workbenchId' ] = $workbench -> id ;
$groupPushData [ 'createTime' ] = time ();
$groupPushData [ 'updateTime' ] = time ();
$config = new WorkbenchGroupPush ;
$config -> save ( $groupPushData );
break ;
case self :: TYPE_GROUP_CREATE : // 自动建群
$config = new WorkbenchGroupCreate ;
$config -> workbenchId = $workbench -> id ;
$config -> planType = ! empty ( $param [ 'planType' ]) ? $param [ 'planType' ] : 0 ;
$config -> executorId = ! empty ( $param [ 'executorId' ]) ? $param [ 'executorId' ] : 0 ;
$config -> devices = json_encode ( $param [ 'deviceGroups' ] ? ? [], JSON_UNESCAPED_UNICODE );
$config -> startTime = $param [ 'startTime' ] ? ? '' ;
$config -> endTime = $param [ 'endTime' ] ? ? '' ;
$config -> groupSizeMin = intval ( $param [ 'groupSizeMin' ] ? ? 3 );
$config -> groupSizeMax = intval ( $param [ 'groupSizeMax' ] ? ? 38 );
$config -> maxGroupsPerDay = intval ( $param [ 'maxGroupsPerDay' ] ? ? 20 );
$config -> groupNameTemplate = $param [ 'groupNameTemplate' ] ? ? '' ;
$config -> groupDescription = $param [ 'groupDescription' ] ? ? '' ;
$config -> poolGroups = json_encode ( $param [ 'poolGroups' ] ? ? [], JSON_UNESCAPED_UNICODE );
$config -> wechatGroups = json_encode ( $param [ 'wechatGroups' ] ? ? [], JSON_UNESCAPED_UNICODE );
// 处理群管理员: 如果启用了群管理员且有指定管理员, 则保存到admins字段
$admins = [];
if ( ! empty ( $param [ 'groupAdminEnabled' ]) && ! empty ( $param [ 'groupAdminWechatId' ])) {
// 如果groupAdminWechatId是数组, 取第一个; 如果是单个值, 直接使用
$adminWechatId = is_array ( $param [ 'groupAdminWechatId' ]) ? $param [ 'groupAdminWechatId' ][ 0 ] : $param [ 'groupAdminWechatId' ];
// 如果是好友ID, 直接添加到admins; 如果是wechatId, 需要转换为好友ID
if ( is_numeric ( $adminWechatId )) {
$admins [] = intval ( $adminWechatId );
} else {
// 如果是wechatId字符串, 需要查询对应的好友ID
$friend = Db :: table ( 's2_wechat_friend' ) -> where ( 'wechatId' , $adminWechatId ) -> find ();
if ( $friend ) {
$admins [] = intval ( $friend [ 'id' ]);
}
}
}
// 如果传入了admins参数, 优先使用( 兼容旧逻辑)
if ( ! empty ( $param [ 'admins' ]) && is_array ( $param [ 'admins' ])) {
$admins = array_merge ( $admins , $param [ 'admins' ]);
}
$config -> admins = json_encode ( array_unique ( $admins ), JSON_UNESCAPED_UNICODE );
$config -> fixedWechatIds = json_encode ( $param [ 'fixedWechatIds' ] ? ? [], JSON_UNESCAPED_UNICODE );
$config -> createTime = time ();
$config -> updateTime = time ();
$config -> save ();
break ;
case self :: TYPE_TRAFFIC_DISTRIBUTION : // 流量分发
$config = new WorkbenchTrafficConfig ;
$config -> workbenchId = $workbench -> id ;
$config -> distributeType = $param [ 'distributeType' ];
$config -> maxPerDay = $param [ 'maxPerDay' ];
$config -> timeType = $param [ 'timeType' ];
$config -> startTime = $param [ 'startTime' ];
$config -> endTime = $param [ 'endTime' ];
$config -> devices = json_encode ( $param [ 'deviceGroups' ], JSON_UNESCAPED_UNICODE );
$config -> pools = json_encode ( $param [ 'poolGroups' ], JSON_UNESCAPED_UNICODE );
$config -> account = json_encode ( $param [ 'accountGroups' ], JSON_UNESCAPED_UNICODE );
$config -> createTime = time ();
$config -> updateTime = time ();
$config -> save ();
break ;
case self :: TYPE_IMPORT_CONTACT : //联系人导入
$config = new WorkbenchImportContact ;
$config -> workbenchId = $workbench -> id ;
$config -> devices = json_encode ( $param [ 'deviceGroups' ], JSON_UNESCAPED_UNICODE );
$config -> pools = json_encode ( $param [ 'poolGroups' ], JSON_UNESCAPED_UNICODE );
$config -> num = $param [ 'num' ];
$config -> clearContact = $param [ 'clearContact' ];
$config -> remark = $param [ 'remark' ];
$config -> startTime = $param [ 'startTime' ];
$config -> endTime = $param [ 'endTime' ];
$config -> createTime = time ();
$config -> save ();
break ;
}
Db :: commit ();
return json ([ 'code' => 200 , 'msg' => '创建成功' , 'data' => [ 'id' => $workbench -> id ]]);
} catch ( \Exception $e ) {
Db :: rollback ();
return json ([ 'code' => 500 , 'msg' => '创建失败:' . $e -> getMessage ()]);
}
}
/**
* 获取工作台列表
* @return \think\response\Json
*/
public function getList ()
{
$page = $this -> request -> param ( 'page' , 1 );
$limit = $this -> request -> param ( 'limit' , 10 );
$type = $this -> request -> param ( 'type' , '' );
$keyword = $this -> request -> param ( 'keyword' , '' );
// 计算时间范围
$days = intval ( $timeRang e ) ;
$startTime = strtotime ( d ate( 'Y-m-d 00:00:00' , strtotime ( " - { $days } days " )));
$endTime = time ( );
// 构建查询条件
$where = [
[ 'companyId ' , '=' , $this -> request -> userInfo [ 'companyId' ]] ,
[ 'isDel ' , '=' , 0 ]
[ 'wgpi.createTime ' , '> =' , $startTime ],
[ 'wgpi.createTime ' , '< =' , $endTime ]
];
if ( empty ( $this -> request -> userInfo [ 'isAdmin' ])) {
$where [] = [ 'userId' , '=' , $this -> request -> userInfo [ 'id' ]];
}
// 添加类型筛选
if ( $type !== '' ) {
$where [] = [ 'type' , '=' , $type ];
}
// 添加名称模糊搜索
if ( $keyword !== '' ) {
$where [] = [ 'name' , 'like' , '%' . $keyword . '%' ];
}
// 定义关联关系
$with = [
'autoLike' => function ( $query ) {
$query -> field ( 'workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends' );
},
'momentsSync' => function ( $query ) {
$query -> field ( 'workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries' );
},
'trafficConfig' => function ( $query ) {
$query -> field ( 'workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account' );
},
'groupPush' => function ( $query ) {
$query -> field ( 'workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt' );
},
'groupCreate' => function ( $query ) {
$query -> field ( 'workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins' );
},
'importContact' => function ( $query ) {
$query -> field ( 'workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime' );
},
'user' => function ( $query ) {
$query -> field ( 'id,username' );
}
];
$list = Workbench :: where ( $where )
-> with ( $with )
-> field ( 'id,companyId,name,type,status,autoStart,userId,createTime,updateTime' )
-> order ( 'id' , 'desc' )
-> page ( $page , $limit )
-> select ()
-> each ( function ( $item ) {
// 处理配置信息
switch ( $item -> type ) {
case self :: TYPE_AUTO_LIKE :
if ( ! empty ( $item -> autoLike )) {
$item -> config = $item -> autoLike ;
$item -> config -> devices = json_decode ( $item -> config -> devices , true );
$item -> config -> contentTypes = json_decode ( $item -> config -> contentTypes , true );
$item -> config -> friends = json_decode ( $item -> config -> friends , true );
// 添加今日点赞数
$startTime = strtotime ( date ( 'Y-m-d' ) . ' 00:00:00' );
$endTime = strtotime ( date ( 'Y-m-d' ) . ' 23:59:59' );
$todayLikeCount = Db :: name ( 'workbench_auto_like_item' )
-> where ( 'workbenchId' , $item -> id )
-> whereTime ( 'createTime' , 'between' , [ $startTime , $endTime ])
-> count ();
// 添加总点赞数
$totalLikeCount = Db :: name ( 'workbench_auto_like_item' )
-> where ( 'workbenchId' , $item -> id )
-> count ();
$item -> config -> todayLikeCount = $todayLikeCount ;
$item -> config -> totalLikeCount = $totalLikeCount ;
}
unset ( $item -> autoLike , $item -> auto_like );
break ;
case self :: TYPE_MOMENTS_SYNC :
if ( ! empty ( $item -> momentsSync )) {
$item -> config = $item -> momentsSync ;
$item -> config -> devices = json_decode ( $item -> config -> devices , true );
$item -> config -> contentGroups = 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 -> contentGroups )) {
$libraryNames = ContentLibrary :: where ( 'id' , 'in' , $item -> config -> contentGroups ) -> select ();
$item -> config -> contentGroupsOptions = $libraryNames ;
} else {
$item -> config -> contentGroupsOptions = [];
}
}
unset ( $item -> momentsSync , $item -> moments_sync , $item -> config -> contentLibraries );
break ;
case self :: TYPE_GROUP_PUSH :
if ( ! empty ( $item -> groupPush )) {
$item -> config = $item -> groupPush ;
$item -> config -> pushType = $item -> config -> pushType ;
$item -> config -> targetType = isset ( $item -> config -> targetType ) ? intval ( $item -> config -> targetType ) : 1 ; // 默认1=群推送
$item -> config -> groupPushSubType = isset ( $item -> config -> groupPushSubType ) ? intval ( $item -> config -> groupPushSubType ) : 1 ; // 默认1=群群发
$item -> config -> startTime = $item -> config -> startTime ;
$item -> config -> endTime = $item -> config -> endTime ;
$item -> config -> maxPerDay = $item -> config -> maxPerDay ;
$item -> config -> pushOrder = $item -> config -> pushOrder ;
$item -> config -> isLoop = $item -> config -> isLoop ;
$item -> config -> status = $item -> config -> status ;
$item -> config -> ownerWechatIds = json_decode ( $item -> config -> ownerWechatIds ? ? '[]' , true ) ? : [];
// 根据targetType解析不同的数据
if ( $item -> config -> targetType == 1 ) {
// 群推送
$item -> config -> wechatGroups = json_decode ( $item -> config -> groups , true ) ? : [];
$item -> config -> wechatFriends = [];
// 群推送不需要devices字段
// 群公告相关字段
if ( $item -> config -> groupPushSubType == 2 ) {
$item -> config -> announcementContent = isset ( $item -> config -> announcementContent ) ? $item -> config -> announcementContent : '' ;
$item -> config -> enableAiRewrite = isset ( $item -> config -> enableAiRewrite ) ? intval ( $item -> config -> enableAiRewrite ) : 0 ;
$item -> config -> aiRewritePrompt = isset ( $item -> config -> aiRewritePrompt ) ? $item -> config -> aiRewritePrompt : '' ;
}
$item -> config -> trafficPools = [];
} else {
// 好友推送
$item -> config -> wechatFriends = json_decode ( $item -> config -> friends , true ) ? : [];
$item -> config -> wechatGroups = [];
$item -> config -> trafficPools = json_decode ( $item -> config -> trafficPools ? ? '[]' , true ) ? : [];
}
$item -> config -> contentLibraries = json_decode ( $item -> config -> contentLibraries , true );
$item -> config -> postPushTags = json_decode ( $item -> config -> postPushTags ? ? '[]' , true ) ? : [];
$item -> config -> lastPushTime = '' ;
if ( ! empty ( $item -> config -> ownerWechatIds )) {
$ownerWechatOptions = Db :: name ( 'wechat_account' )
-> whereIn ( 'id' , $item -> config -> ownerWechatIds )
-> field ( 'id,wechatId,nickName,avatar,alias' )
-> select ();
$item -> config -> ownerWechatOptions = $ownerWechatOptions ;
} else {
$item -> config -> ownerWechatOptions = [];
}
}
unset ( $item -> groupPush , $item -> group_push );
break ;
case self :: TYPE_GROUP_CREATE :
if ( ! empty ( $item -> groupCreate )) {
$item -> config = $item -> groupCreate ;
$item -> config -> devices = json_decode ( $item -> config -> devices , true );
$item -> config -> poolGroups = json_decode ( $item -> config -> poolGroups , true );
$item -> config -> wechatGroups = json_decode ( $item -> config -> wechatGroups , true );
$item -> config -> admins = json_decode ( $item -> config -> admins ? ? '[]' , true ) ? : [];
// 处理群管理员相关字段
$item -> config -> groupAdminEnabled = ! empty ( $item -> config -> admins ) ? 1 : 0 ;
if ( ! empty ( $item -> config -> admins )) {
$adminOptions = Db :: table ( 's2_wechat_friend' ) -> alias ( 'wf' )
-> join ([ 's2_wechat_account' => 'wa' ], 'wa.id = wf.wechatAccountId' , 'left' )
-> where ( 'wf.id' , 'in' , $item -> config -> admins )
-> order ( 'wf.id' , 'desc' )
-> field ( 'wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar' )
-> select ();
$item -> config -> adminsOptions = $adminOptions ;
// 如果有管理员, 设置groupAdminWechatId为第一个管理员的ID( 用于前端回显)
$item -> config -> groupAdminWechatId = ! empty ( $item -> config -> admins ) ? $item -> config -> admins [ 0 ] : null ;
} else {
$item -> config -> adminsOptions = [];
$item -> config -> groupAdminWechatId = null ;
}
}
unset ( $item -> groupCreate , $item -> group_create );
break ;
case self :: TYPE_TRAFFIC_DISTRIBUTION :
if ( ! empty ( $item -> trafficConfig )) {
$item -> config = $item -> trafficConfig ;
$item -> config -> devices = json_decode ( $item -> config -> devices , true );
$item -> config -> poolGroups = json_decode ( $item -> config -> pools , true );
$item -> config -> account = json_decode ( $item -> config -> account , true );
$config_item = Db :: name ( 'workbench_traffic_config_item' ) -> where ([ 'workbenchId' => $item -> id ]) -> order ( 'id DESC' ) -> find ();
$item -> config -> lastUpdated = ! empty ( $config_item ) ? date ( 'Y-m-d H:i' , $config_item [ 'createTime' ]) : '--' ;
//统计
$labels = $item -> config -> poolGroups ;
$totalUsers = Db :: table ( 's2_wechat_friend' ) -> alias ( 'wf' )
-> join ([ 's2_company_account' => 'sa' ], 'sa.id = wf.accountId' , 'left' )
-> join ([ 's2_wechat_account' => 'wa' ], 'wa.id = wf.wechatAccountId' , 'left' )
-> where ([
[ 'wf.isDeleted' , '=' , 0 ],
[ 'sa.departmentId' , '=' , $item -> companyId ]
])
-> whereIn ( 'wa.currentDeviceId' , $item -> config -> devices );
if ( ! empty ( $labels ) && count ( $labels ) > 0 ) {
$totalUsers = $totalUsers -> where ( function ( $q ) use ( $labels ) {
foreach ( $labels as $label ) {
$q -> whereOrRaw ( " JSON_CONTAINS(wf.labels, ' \" { $label } \" ') " );
}
});
}
$totalUsers = $totalUsers -> count ();
$totalAccounts = count ( $item -> config -> account );
$dailyAverage = Db :: name ( 'workbench_traffic_config_item' )
-> where ( 'workbenchId' , $item -> id )
-> count ();
$day = ( time () - strtotime ( $item -> createTime )) / 86400 ;
$day = intval ( $day );
if ( $dailyAverage > 0 && $totalAccounts > 0 && $day > 0 ) {
$dailyAverage = $dailyAverage / $totalAccounts / $day ;
}
$item -> config -> total = [
'dailyAverage' => intval ( $dailyAverage ),
'totalAccounts' => $totalAccounts ,
'deviceCount' => count ( $item -> config -> devices ),
'poolCount' => ! empty ( $item -> config -> poolGroups ) ? count ( $item -> config -> poolGroups ) : 'ALL' ,
'totalUsers' => $totalUsers >> 0
];
}
unset ( $item -> trafficConfig , $item -> traffic_config );
break ;
case self :: TYPE_IMPORT_CONTACT :
if ( ! empty ( $item -> importContact )) {
$item -> config = $item -> importContact ;
$item -> config -> devices = json_decode ( $item -> config -> devices , true );
$item -> config -> poolGroups = json_decode ( $item -> config -> pools , true );
}
unset ( $item -> importContact , $item -> import_contact );
break ;
}
// 添加创建人名称
$item [ 'creatorName' ] = $item -> user ? $item -> user -> username : '' ;
unset ( $item [ 'user' ]); // 移除关联数据
return $item ;
});
$total = Workbench :: where ( $where ) -> count ();
return json ([
'code' => 200 ,
'msg' => '获取成功' ,
'data' => [
'list' => $list ,
'total' => $total ,
'page' => $page ,
'limit' => $limit
]
]);
}
/**
* 获取工作台详情
* @param int $id 工作台ID
* @return \think\response\Json
*/
public function detail ()
{
$id = $this -> request -> param ( 'id' , '' );
@@ -1001,7 +587,7 @@ class WorkbenchGroupPushController extends Controller
case self :: TYPE_GROUP_PUSH :
$config = WorkbenchGroupPush :: where ( 'workbenchId' , $param [ 'id' ]) -> find ();
if ( $config ) {
$ownerWechatIds = $this -> normalizeOwnerWechatIds ( $param [ 'ownerWechatIds' ] ? ? null , $config );
$ownerWechatIds = $this -> normalizeOwnerWechatIds ( $param [ 'ownerWechatIds' ] ? ? null , $config , $param [ 'deviceGroups' ] ? ? [] );
$groupPushData = $this -> prepareGroupPushData ( $param , $ownerWechatIds , $config );
$groupPushData [ 'updateTime' ] = time ();
$config -> save ( $groupPushData );
@@ -1899,15 +1485,24 @@ class WorkbenchGroupPushController extends Controller
* 规范化客服微信ID列表
* @param mixed $ownerWechatIds
* @param WorkbenchGroupPush|null $originalConfig
* @param array $deviceGroups 设备ID数组
* @return array
* @throws \Exception
*/
private function normalizeOwnerWechatIds ( $ownerWechatIds , WorkbenchGroupPush $originalConfig = null ) : array
private function normalizeOwnerWechatIds ( $ownerWechatIds , WorkbenchGroupPush $originalConfig = null , array $deviceGroups = [] ): array
{
// 处理设备ID数组
$deviceGroupsList = $this -> extractIdList ( $deviceGroups , '设备参数格式错误' );
if ( $ownerWechatIds === null ) {
$existing = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> ownerWechatIds ? ? []) : [];
if ( empty ( $existing )) {
throw new \Exception ( '请至少选择一个客服微信' );
// 如果原有配置为空, 且没有传设备ID, 则报错
if ( empty ( $existing ) && empty ( $deviceGroupsList )) {
throw new \Exception ( '请至少选择一个客服微信或设备' );
}
// 如果原有配置为空但有设备ID, 返回空数组( 允许使用设备ID)
if ( empty ( $existing ) && ! empty ( $deviceGroupsList )) {
return [];
}
return $existing ;
}
@@ -1917,8 +1512,9 @@ class WorkbenchGroupPushController extends Controller
}
$normalized = $this -> extractIdList ( $ownerWechatIds , '客服参数格式错误' );
if ( empty ( $normalized )) {
throw new \Exception ( '请至少选择一个客服微信' );
// 如果 ownerWechatIds 为空, 但传了设备ID, 则允许( 两个传一个即可)
if ( empty ( $normalized ) && empty ( $deviceGroupsList )) {
throw new \Exception ( '请至少选择一个客服微信或设备' );
}
return $normalized ;
}
@@ -1960,6 +1556,14 @@ class WorkbenchGroupPushController extends Controller
'isRandomTemplate' => $this -> toBoolInt ( $this -> getParamValue ( $param , 'isRandomTemplate' , $originalConfig -> isRandomTemplate ? ? 0 )),
'ownerWechatIds' => json_encode ( $ownerWechatIds , JSON_UNESCAPED_UNICODE ),
];
// 处理设备ID数组( deviceGroups) , 保存到 devices 字段
$deviceGroupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> devices ? ? []) : [];
$deviceGroupsParam = $this -> getParamValue ( $param , 'deviceGroups' , null );
$deviceGroups = $deviceGroupsParam !== null
? $this -> extractIdList ( $deviceGroupsParam , '设备参数格式错误' )
: $deviceGroupsExisting ;
$data [ 'devices' ] = json_encode ( $deviceGroups , JSON_UNESCAPED_UNICODE );
if ( $data [ 'friendIntervalMin' ] > $data [ 'friendIntervalMax' ]) {
throw new \Exception ( '目标间最小间隔不能大于最大间隔' );
@@ -1968,11 +1572,35 @@ class WorkbenchGroupPushController extends Controller
throw new \Exception ( '消息间最小间隔不能大于最大间隔' );
}
$contentGroupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> contentLibraries ? ? []) : [];
$contentGroupsParam = $this -> getParamValue ( $param , 'contentGroups' , null );
$contentGroups = $contentGroupsParam !== null
? $this -> extractIdList ( $contentGroupsParam , '内容库参数格式错误' )
: $contentGroupsExisting ;
// 群公告( groupPushSubType = 2) 时, contentGroups 可以为空,不需要验证
if ( $targetType === 1 && $groupPushSubType === 2 ) {
// 群公告可以不传内容库,允许为空,不进行任何验证
$contentGroupsParam = $this -> getParamValue ( $param , 'contentGroups' , null );
if ( $contentGroupsParam !== null ) {
// 如果传入了参数,尝试解析,但不验证格式和是否为空
try {
$contentGroups = $this -> extractIdList ( $contentGroupsParam , '内容库参数格式错误' );
} catch ( \Exception $e ) {
// 群公告时忽略格式错误,使用空数组
$contentGroups = [];
}
} else {
// 如果没有传入参数,使用现有配置或空数组
$contentGroupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> contentLibraries ? ? []) : [];
$contentGroups = $contentGroupsExisting ;
}
} else {
// 其他情况,正常处理并验证
$contentGroupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> contentLibraries ? ? []) : [];
$contentGroupsParam = $this -> getParamValue ( $param , 'contentGroups' , null );
$contentGroups = $contentGroupsParam !== null
? $this -> extractIdList ( $contentGroupsParam , '内容库参数格式错误' )
: $contentGroupsExisting ;
// 其他情况,内容库为必填
if ( empty ( $contentGroups )) {
throw new \Exception ( '请至少选择一个内容库' );
}
}
$data [ 'contentLibraries' ] = json_encode ( $contentGroups , JSON_UNESCAPED_UNICODE );
$postPushTagsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> postPushTags ? ? []) : [];
@@ -2150,64 +1778,6 @@ class WorkbenchGroupPushController extends Controller
return false ;
}
/**
* 获取通讯录导入记录列表
* @return \think\response\Json
*/
public function getImportContact ()
{
$page = $this -> request -> param ( 'page' , 1 );
$limit = $this -> request -> param ( 'limit' , 10 );
$workbenchId = $this -> request -> param ( 'workbenchId' , 0 );
$where = [
[ 'wici.workbenchId' , '=' , $workbenchId ]
];
// 查询发布记录
$list = Db :: name ( 'workbench_import_contact_item' ) -> alias ( 'wici' )
-> join ( 'traffic_pool tp' , 'tp.id = wici.poolId' , 'left' )
-> join ( 'traffic_source tc' , 'tc.identifier = tp.identifier' , 'left' )
-> join ( 'wechat_account wa' , 'wa.wechatId = tp.wechatId' , 'left' )
-> field ([
'wici.id' ,
'wici.workbenchId' ,
'wici.createTime' ,
'tp.identifier' ,
'tp.mobile' ,
'tp.wechatId' ,
'tc.name' ,
'wa.nickName' ,
'wa.avatar' ,
'wa.alias' ,
])
-> where ( $where )
-> order ( 'tc.name DESC,wici.createTime DESC' )
-> group ( 'tp.identifier' )
-> page ( $page , $limit )
-> select ();
foreach ( $list as & $item ) {
$item [ 'createTime' ] = date ( 'Y-m-d H:i:s' , $item [ 'createTime' ]);
}
// 获取总记录数
$total = Db :: name ( 'workbench_import_contact_item' ) -> alias ( 'wici' )
-> where ( $where )
-> count ();
return json ([
'code' => 200 ,
'msg' => '获取成功' ,
'data' => [
'list' => $list ,
'total' => $total ,
]
]);
}
/**
* 获取群发统计数据
* @return \think\response\Json
@@ -3116,15 +2686,302 @@ class WorkbenchGroupPushController extends Controller
}
/**
* 获取已创建的群列表(自动建群)
* @return \think\response\Json
* 规范化客服微信ID列表
* @param mixed $ownerWechatIds
* @param WorkbenchGroupPush|null $originalConfig
* @param array $deviceGroups 设备ID数组
* @return array
* @throws \Exception
*/
public function getCreatedGroupsList ()
private function normalizeOwnerWechatIds ( $ownerWechatIds , WorkbenchGroupPush $originalConfig = null , array $deviceGroups = []) : array
{
$workbenchId = $this -> request -> param ( 'workbenchId' , 0 );
$page = $this -> request -> param ( 'page' , 1 );
$limit = $this -> request -> param ( 'limit' , 100 );
$keyword = $this -> request -> param ( 'keyword' , '' );
// 处理设备ID数组
$deviceGroupsList = $this -> extractIdList ( $deviceGroups , '设备参数格式错误' );
if ( $ownerWechatIds === null ) {
$existing = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> ownerWechatIds ? ? []) : [];
// 如果原有配置为空, 且没有传设备ID, 则报错
if ( empty ( $existing ) && empty ( $deviceGroupsList )) {
throw new \Exception ( '请至少选择一个客服微信或设备' );
}
// 如果原有配置为空但有设备ID, 返回空数组( 允许使用设备ID)
if ( empty ( $existing ) && ! empty ( $deviceGroupsList )) {
return [];
}
return $existing ;
}
if ( ! is_array ( $ownerWechatIds )) {
throw new \Exception ( '客服参数格式错误' );
}
$normalized = $this -> extractIdList ( $ownerWechatIds , '客服参数格式错误' );
// 如果 ownerWechatIds 为空, 但传了设备ID, 则允许( 两个传一个即可)
if ( empty ( $normalized ) && empty ( $deviceGroupsList )) {
throw new \Exception ( '请至少选择一个客服微信或设备' );
}
return $normalized ;
}
/**
* 构建群推送配置数据
* @param array $param
* @param array $ownerWechatIds
* @param WorkbenchGroupPush|null $originalConfig
* @return array
* @throws \Exception
*/
private function prepareGroupPushData ( array $param , array $ownerWechatIds , WorkbenchGroupPush $originalConfig = null ) : array
{
$targetTypeDefault = $originalConfig ? intval ( $originalConfig -> targetType ) : 1 ;
$targetType = intval ( $this -> getParamValue ( $param , 'targetType' , $targetTypeDefault )) ? : 1 ;
$groupPushSubTypeDefault = $originalConfig ? intval ( $originalConfig -> groupPushSubType ) : 1 ;
$groupPushSubType = intval ( $this -> getParamValue ( $param , 'groupPushSubType' , $groupPushSubTypeDefault )) ? : 1 ;
if ( ! in_array ( $groupPushSubType , [ 1 , 2 ], true )) {
$groupPushSubType = 1 ;
}
$data = [
'pushType' => $this -> toBoolInt ( $this -> getParamValue ( $param , 'pushType' , $originalConfig -> pushType ? ? 0 )),
'targetType' => $targetType ,
'startTime' => $this -> getParamValue ( $param , 'startTime' , $originalConfig -> startTime ? ? '' ),
'endTime' => $this -> getParamValue ( $param , 'endTime' , $originalConfig -> endTime ? ? '' ),
'maxPerDay' => intval ( $this -> getParamValue ( $param , 'maxPerDay' , $originalConfig -> maxPerDay ? ? 0 )),
'pushOrder' => $this -> getParamValue ( $param , 'pushOrder' , $originalConfig -> pushOrder ? ? 1 ),
'groupPushSubType' => $groupPushSubType ,
'status' => $this -> toBoolInt ( $this -> getParamValue ( $param , 'status' , $originalConfig -> status ? ? 0 )),
'socialMediaId' => $this -> getParamValue ( $param , 'socialMediaId' , $originalConfig -> socialMediaId ? ? '' ),
'promotionSiteId' => $this -> getParamValue ( $param , 'promotionSiteId' , $originalConfig -> promotionSiteId ? ? '' ),
'friendIntervalMin' => intval ( $this -> getParamValue ( $param , 'friendIntervalMin' , $originalConfig -> friendIntervalMin ? ? 10 )),
'friendIntervalMax' => intval ( $this -> getParamValue ( $param , 'friendIntervalMax' , $originalConfig -> friendIntervalMax ? ? 20 )),
'messageIntervalMin' => intval ( $this -> getParamValue ( $param , 'messageIntervalMin' , $originalConfig -> messageIntervalMin ? ? 1 )),
'messageIntervalMax' => intval ( $this -> getParamValue ( $param , 'messageIntervalMax' , $originalConfig -> messageIntervalMax ? ? 12 )),
'isRandomTemplate' => $this -> toBoolInt ( $this -> getParamValue ( $param , 'isRandomTemplate' , $originalConfig -> isRandomTemplate ? ? 0 )),
'ownerWechatIds' => json_encode ( $ownerWechatIds , JSON_UNESCAPED_UNICODE ),
];
// 处理设备ID数组( deviceGroups) , 保存到 devices 字段
$deviceGroupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> devices ? ? []) : [];
$deviceGroupsParam = $this -> getParamValue ( $param , 'deviceGroups' , null );
$deviceGroups = $deviceGroupsParam !== null
? $this -> extractIdList ( $deviceGroupsParam , '设备参数格式错误' )
: $deviceGroupsExisting ;
$data [ 'devices' ] = json_encode ( $deviceGroups , JSON_UNESCAPED_UNICODE );
if ( $data [ 'friendIntervalMin' ] > $data [ 'friendIntervalMax' ]) {
throw new \Exception ( '目标间最小间隔不能大于最大间隔' );
}
if ( $data [ 'messageIntervalMin' ] > $data [ 'messageIntervalMax' ]) {
throw new \Exception ( '消息间最小间隔不能大于最大间隔' );
}
// 群公告( groupPushSubType = 2) 时, contentGroups 可以为空,不需要验证
if ( $targetType === 1 && $groupPushSubType === 2 ) {
// 群公告可以不传内容库,允许为空,不进行任何验证
$contentGroupsParam = $this -> getParamValue ( $param , 'contentGroups' , null );
if ( $contentGroupsParam !== null ) {
// 如果传入了参数,尝试解析,但不验证格式和是否为空
try {
$contentGroups = $this -> extractIdList ( $contentGroupsParam , '内容库参数格式错误' );
} catch ( \Exception $e ) {
// 群公告时忽略格式错误,使用空数组
$contentGroups = [];
}
} else {
// 如果没有传入参数,使用现有配置或空数组
$contentGroupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> contentLibraries ? ? []) : [];
$contentGroups = $contentGroupsExisting ;
}
} else {
// 其他情况,正常处理并验证
$contentGroupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> contentLibraries ? ? []) : [];
$contentGroupsParam = $this -> getParamValue ( $param , 'contentGroups' , null );
$contentGroups = $contentGroupsParam !== null
? $this -> extractIdList ( $contentGroupsParam , '内容库参数格式错误' )
: $contentGroupsExisting ;
// 其他情况,内容库为必填
if ( empty ( $contentGroups )) {
throw new \Exception ( '请至少选择一个内容库' );
}
}
$data [ 'contentLibraries' ] = json_encode ( $contentGroups , JSON_UNESCAPED_UNICODE );
$postPushTagsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> postPushTags ? ? []) : [];
$postPushTagsParam = $this -> getParamValue ( $param , 'postPushTags' , null );
$postPushTags = $postPushTagsParam !== null
? $this -> extractIdList ( $postPushTagsParam , '推送标签参数格式错误' )
: $postPushTagsExisting ;
$data [ 'postPushTags' ] = json_encode ( $postPushTags , JSON_UNESCAPED_UNICODE );
if ( $targetType === 1 ) {
$data [ 'isLoop' ] = $this -> toBoolInt ( $this -> getParamValue ( $param , 'isLoop' , $originalConfig -> isLoop ? ? 0 ));
$groupsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> groups ? ? []) : [];
$wechatGroups = array_key_exists ( 'wechatGroups' , $param )
? $this -> extractIdList ( $param [ 'wechatGroups' ], '群参数格式错误' )
: $groupsExisting ;
if ( empty ( $wechatGroups )) {
throw new \Exception ( '群推送必须选择微信群' );
}
$data [ 'groups' ] = json_encode ( $wechatGroups , JSON_UNESCAPED_UNICODE );
$data [ 'friends' ] = json_encode ([], JSON_UNESCAPED_UNICODE );
$data [ 'trafficPools' ] = json_encode ([], JSON_UNESCAPED_UNICODE );
if ( $groupPushSubType === 2 ) {
$announcementContent = $this -> getParamValue ( $param , 'announcementContent' , $originalConfig -> announcementContent ? ? '' );
if ( empty ( $announcementContent )) {
throw new \Exception ( '群公告必须输入公告内容' );
}
$enableAiRewrite = $this -> toBoolInt ( $this -> getParamValue ( $param , 'enableAiRewrite' , $originalConfig -> enableAiRewrite ? ? 0 ));
$aiRewritePrompt = trim (( string ) $this -> getParamValue ( $param , 'aiRewritePrompt' , $originalConfig -> aiRewritePrompt ? ? '' ));
if ( $enableAiRewrite === 1 && $aiRewritePrompt === '' ) {
throw new \Exception ( '启用AI智能话术改写时, 必须输入改写提示词' );
}
$data [ 'announcementContent' ] = $announcementContent ;
$data [ 'enableAiRewrite' ] = $enableAiRewrite ;
$data [ 'aiRewritePrompt' ] = $aiRewritePrompt ;
} else {
$data [ 'groupPushSubType' ] = 1 ;
$data [ 'announcementContent' ] = '' ;
$data [ 'enableAiRewrite' ] = 0 ;
$data [ 'aiRewritePrompt' ] = '' ;
}
} else {
$data [ 'isLoop' ] = 0 ;
$friendsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> friends ? ? []) : [];
$trafficPoolsExisting = $originalConfig ? $this -> decodeJsonArray ( $originalConfig -> trafficPools ? ? []) : [];
$friendTargets = array_key_exists ( 'wechatFriends' , $param )
? $this -> extractIdList ( $param [ 'wechatFriends' ], '好友参数格式错误' )
: $friendsExisting ;
$trafficPools = array_key_exists ( 'trafficPools' , $param )
? $this -> extractIdList ( $param [ 'trafficPools' ], '流量池参数格式错误' )
: $trafficPoolsExisting ;
if ( empty ( $friendTargets ) && empty ( $trafficPools )) {
throw new \Exception ( '好友推送需至少选择好友或流量池' );
}
$data [ 'friends' ] = json_encode ( $friendTargets , JSON_UNESCAPED_UNICODE );
$data [ 'trafficPools' ] = json_encode ( $trafficPools , JSON_UNESCAPED_UNICODE );
$data [ 'groups' ] = json_encode ([], JSON_UNESCAPED_UNICODE );
$data [ 'groupPushSubType' ] = 1 ;
$data [ 'announcementContent' ] = '' ;
$data [ 'enableAiRewrite' ] = 0 ;
$data [ 'aiRewritePrompt' ] = '' ;
}
return $data ;
}
/**
* 获取参数值,若不存在则返回默认值
* @param array $param
* @param string $key
* @param mixed $default
* @return mixed
*/
private function getParamValue ( array $param , string $key , $default )
{
return array_key_exists ( $key , $param ) ? $param [ $key ] : $default ;
}
/**
* 将值转换为整型布尔
* @param mixed $value
* @return int
*/
private function toBoolInt ( $value ) : int
{
return empty ( $value ) ? 0 : 1 ;
}
/**
* 从参数中提取ID列表
* @param mixed $items
* @param string $errorMessage
* @return array
* @throws \Exception
*/
private function extractIdList ( $items , string $errorMessage = '参数格式错误' ) : array
{
if ( ! is_array ( $items )) {
throw new \Exception ( $errorMessage );
}
$ids = [];
foreach ( $items as $item ) {
if ( is_array ( $item ) && isset ( $item [ 'id' ])) {
$item = $item [ 'id' ];
}
if ( $item === '' || $item === null ) {
continue ;
}
$ids [] = $item ;
}
return array_values ( array_unique ( $ids ));
}
/**
* 解码JSON数组
* @param mixed $value
* @return array
*/
private function decodeJsonArray ( $value ) : array
{
if ( empty ( $value )) {
return [];
}
if ( is_array ( $value )) {
return $value ;
}
$decoded = json_decode ( $value , true );
return is_array ( $decoded ) ? $decoded : [];
}
/**
* 验证内容是否包含链接
* @param string $content 要检测的内容
* @return bool
*/
private function containsLink ( $content )
{
// 定义各种链接的正则表达式模式
$patterns = [
// HTTP/HTTPS链接
'/https?:\/\/[^\s]+/i' ,
// 京东商品链接
'/item\.jd\.com\/\d+/i' ,
// 京东短链接
'/u\.jd\.com\/[a-zA-Z0-9]+/i' ,
// 淘宝商品链接
'/item\.taobao\.com\/item\.htm\?id=\d+/i' ,
// 天猫商品链接
'/detail\.tmall\.com\/item\.htm\?id=\d+/i' ,
// 淘宝短链接
'/m\.tb\.cn\/[a-zA-Z0-9]+/i' ,
// 拼多多链接
'/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i' ,
// 苏宁易购链接
'/product\.suning\.com\/\d+\/\d+\.html/i' ,
// 通用域名模式(包含常见电商域名)
'/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i' ,
// 通用短链接模式
'/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i'
];
// 遍历所有模式进行匹配
foreach ( $patterns as $pattern ) {
if ( preg_match ( $pattern , $content )) {
return true ;
}
}
return false ;
}
}
if ( empty ( $workbenchId )) {
return json ([ 'code' => 400 , 'msg' => '工作台ID不能为空' ]);
@@ -3247,12 +3104,7 @@ class WorkbenchGroupPushController extends Controller
]
]);
}
/**
* 获取已创建群的详情(自动建群)
* @return \think\response\Json
*/
public function getCreatedGroupDetail ()
}
{
$workbenchId = $this -> request -> param ( 'workbenchId' , 0 );
$groupId = $this -> request -> param ( 'groupId' , 0 );